🤩

GASでAWS APIを呼び出して、セキュリティグループをシートに反映しよう

2022/11/10に公開

この記事の概要

これを待ってたんだろ?

参考にした記事

こちらの記事を参考に、セキュリティグループ版を作りました。
https://akkinoc.dev/posts/2022/05/15/aws-api-from-google-apps-script/

必要なもの

AWSのアクセスキー・シークレットアクセスキー
→ IAMユーザーに適切な権限をつけて発行して下さい。

やってみよう

1. スプレッドシートを作成・シートの名前決め

結果を出力するためのシート名もつけておきましょう。

2. コードのコピペ

シートメニュー > 拡張機能 > Apps Script を開いてファイルを作成していきます。
ファイル名は任意ですが、一応載せておきます。

  • aws.gs

下のサイトaws.jsをコピーして貼り付けて下さい。

https://github.com/smithy545/aws-apps-scripts

  • credentials.gs
コード(クリックして開く)
credentials.gs
// Grobal Vars
let AWS_ACCESS_KEY_ID
let AWS_SECRET_ACCESS_KEY
// AWS Credentials
const keypair = {
  'demo': {
    'id': 'AKIAXXXXXXXXXXXXXXXXXX',
    'secret': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
  },
  'prod': {
    'id': 'AKIAXXXXXXXXXXXXXXXXXX',
    'secret': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
  }
}
// set credentials
function setAwsCredentials(env){
  console.log('env name -> '+env)
  AWS_ACCESS_KEY_ID = keypair[env].id
  AWS_SECRET_ACCESS_KEY = keypair[env].secret

  AWS.init(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
}
  • awsresourcectl.gs
コード(クリックして開く)
awsresourcectl.gs
// セキュリティグループの情報をlistで返却する。
function EC2DescribeSecurityGroups(envname) {
  setAwsCredentials(envname)
  
  const res = AWS.request(
    'ec2',
    'ap-northeast-1',
    'DescribeSecurityGroups',
    { Version: '2016-11-15' },
  )

  // ステータスコードと返却されたxmlを格納
  const code = res.getResponseCode()
  const text = res.getContentText()

  // xmlのパース
  if (code < 200 || code >= 300) throw Error(`AWS.request failed: ${code} - ${text}`)
  const root = XmlService.parse(text).getRootElement()
  const ns = root.getNamespace()
  const securitygroups = root.getChild('securityGroupInfo', ns).getChildren()
  const resultlist = []

  securitygroups.forEach(securitygroup => {
    const groupId = securitygroup.getChild('groupId', ns).getText()
    const groupName = securitygroup.getChild('groupName', ns).getText()
    ippermissions = securitygroup.getChild('ipPermissions', ns).getChildren()
    ippermissions.forEach(ippermission => {
      const ipProtocol = (ippermission.getChild('ipProtocol', ns) ? ippermission.getChild('ipProtocol', ns).getText() : 'ー')
      const fromPort = (ippermission.getChild('fromPort', ns) ? ippermission.getChild('fromPort', ns).getText() : 'ー')
      const toPort = (ippermission.getChild('toPort', ns) ? ippermission.getChild('toPort', ns).getText() : 'ー')
      // 許可sg群
      const groups = ippermission.getChild('groups', ns).getChildren()
      // 許可ip群
      const ipranges = ippermission.getChild('ipRanges', ns).getChildren()
      // sgを許可しているパターン
      groups.forEach(group => {
        const srcgroupId = (group.getChild('groupId', ns) ? group.getChild('groupId', ns).getText() : 'ー')
        const description = (group.getChild('description', ns) ? group.getChild('description', ns).getText() : 'ー')
        resultlist.push([groupId, groupName, ipProtocol, fromPort, toPort, srcgroupId, description])
      })
      // ipを許可しているパターン
      ipranges.forEach(iprange => {
        const cidrIp = (iprange.getChild('cidrIp', ns) ? iprange.getChild('cidrIp', ns).getText() : 'ー')
        const description = (iprange.getChild('description', ns) ? iprange.getChild('description', ns).getText() : 'ー')
        resultlist.push([groupId, groupName, ipProtocol, fromPort, toPort, cidrIp, description])
      })
    })
  })
  return {'list':resultlist, 'collength':resultlist[0].length, 'rowlength':resultlist.length}
}
  • spreadsheetctl.gs
コード(クリックして開く)
spreadsheet.gs
// CONST VALUE
const STARTROW = 1
const STARTCOL = 2
const COLUMNNAMES = ['Security Group ID','Security Group Name','Protocol','from Port','to Port','Access Source','Description']

// シートにセキュリティグループの情報を反映する。
function dispSecurityGroupRuletoSheet(sheetname, env){
  // INIT
  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetname)

  // 既存情報のクリア + カラム名のセット
  sheet.clearContents()
  sheet.getRange(STARTROW,STARTCOL,1,COLUMNNAMES.length).setValues([COLUMNNAMES])

  // 情報取得 + 反映
  const sginfo = EC2DescribeSecurityGroups(env)
  sheet.getRange(STARTROW+1, STARTCOL, sginfo.rowlength, sginfo.collength).setValues(sginfo.list)
}
  • main.gs
コード(クリックして開く)
main.gs
// シートにカスタムメニュー追加
function onOpen() {
  customMenulist = []
  customMenulist.push({name: 'デモ:sg情報をシートに反映', functionName: 'dispDemoSecurityGroupRuletoSheet'})
  customMenulist.push({name: '本番:sg情報をシートに反映', functionName: 'dispProdSecurityGroupRuletoSheet'})
  SpreadsheetApp.getActiveSpreadsheet().addMenu('カスタム',customMenulist)
}

// シートにセキュリティグループの情報を反映(デモ)
function dispDemoSecurityGroupRuletoSheet(){
  dispSecurityGroupRuletoSheet('demo_sg','demo')
}
// シートにセキュリティグループの情報を反映(本番)
function dispProdSecurityGroupRuletoSheet(){
  dispSecurityGroupRuletoSheet('prod_sg','prod')
}

3. 実行してみよう

スプレッドシートをリロードして再読み込みすると
カスタムメニューが表示されているはずです。

メニューを選択すると、初回のみ "承認が必要" 的な文言が出ます。

アカウントを選択して、許可を押せばOKです。これ以降は聞かれません。

コードのちょっとした説明

xmlのパースとテストのやり方

xmlのパースを理解すれば、改変や応用は容易だと思います。

awsresource.gs @line 14
  // ステータスコードと返却されたxmlを格納
  const code = res.getResponseCode()
  const text = res.getContentText()

このtextを一旦セル等に吐き出して構造を理解する
<item>のブロックをforEachで回す、値をgetChild('値', ns).getText()で取ればだいたい行けます。
あまり本番のデータをいじりたくない場合、は別リージョンにテスト用のsgを作ると良いです。
その場合、awsresourcectl.gsで指定したap-northeast-1を別リージョンのものに指定するのをお忘れなく。

nullの場合だけデフォルト値を代入

また、nullの場合は別の値を入れたい という場合
↓のような書き方が使えるようです。

// valがnullだったらnone、nullじゃなかったらvalをresultに入れる
const result = ( val ? val : "none" )

今回はdescriptionがnullの場合がありえると思って使いました。
途中からヤケクソで連発していますが。。

データの一括挿入

gasはセルの範囲を二次元配列として扱います。
その特性上、setValues()で二次元配列を渡せば一発でデータの挿入が出来ます。
但し、getRange()の大きさと二次元配列の大きさが一致している必要があるので
適宜lengthなどで動的に範囲を取るようにしています。

spreadsheetctl.gs @line15-17
  // 情報取得 + 反映
  const sginfo = EC2DescribeSecurityGroups(env)
  sheet.getRange(STARTROW+1, STARTCOL, sginfo.rowlength, sginfo.collength).setValues(sginfo.list)

Discussion