Closed21

github actionsからprivateなweb apiを叩く

inomotoinomoto

AWSでIP制限があるサーバのweb apiを、github actionsからキックしてみるテスト

inomotoinomoto

まずは対象となるsandbox環境を作る

  • 適当なVPC
  • 対象APIのサーバを模したEC2(セキュリティグループでIP制限してhttpdでも入れとく)
  • EC2に入ってアクセスログが見れる状態
inomotoinomoto

VPCは特に何でもいいのでデフォルトのものを使う。特にサブネットを切り分けたりする予定も無い。

EC2はいつものお砂場を立てて適当にSSMで入り、 sudo yum install httpdしてsudo systemctl start httpdしておく。
https://zenn.dev/cumet04/articles/ssm-ec2-sandbox-template

セキュリティグループで手元のグローバルIPから80番への通信を許可し、Test Pageが見えればok。
そもそもSSMで入ってるのでログも見れる。

inomotoinomoto

EC2へのアクセス経路としてはLambda in VPCを使う。対象EC2と同一VPC内の適当なサブネットにlambdaを配置し、それをactionsからAPIで発火する、という方針。

というわけでlambdaを生やす。

inomotoinomoto

lambdaの作成画面にて適当に名前を入れ、ランタイムを選ぶ。手っ取り早くhttpリクエストが投げられれば良いので、慣れているrubyをチョイス。

実行ロールは放置し、詳細設定を開く。
VPCとサブネットを選択するが、サブネットは2つ以上選んでおけとのお言葉なので選んでおく。まぁなんでもよい。

セキュリティグループ...はアウトバウンドが適当に通ってればよいはず。インバウンド無し・アウトバウンド全許可のルールが手元にあったのでつけておく。

inomotoinomoto

適当にコードを書く。動作確認ができればいいのでごく最低限で。

require 'json'
require 'net/http'
require 'uri'

def lambda_handler(event:, context:)
    resp = Net::HTTP.get(URI.parse('http://172.31.31.12/'))
    { statusCode: 200, body: JSON.generate(resp) }
end

で、EC2のセキュリティグループでサブネット内からの80番通信を通し、lambdaをデプロイしてTestする。

良い感じ。

ここでは叩き先としてローカルIPを指定したが、ここはセキュリティグループの開け方やDNS設定によって適当に。

inomotoinomoto

ブラウザ上で実行できたらawscliからも実行してみる。
EC2に入ってアクセスログを見つつ、awscliがセットアップされた手元から

aws lambda invoke --function-name kick-private-api out

kick-private-apiは今回作ったlambdaの関数名
outはレスポンスが入るファイル名。戻り値見ないけど必須なので適当に指定

アクセスログが更新されてればok。

あとはactions用に適当なユーザ生やしてそこからinvokeすればよいはず。

inomotoinomoto

actionsからlambdaをキックする用のIAMユーザを生やす。
ポリシーはこんな感じ。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "lambda:InvokeFunction",
            "Resource": "arn:aws:lambda:ap-northeast-1:xxacountnumberxxx:function:kick-private-api"
        }
    ]
}
inomotoinomoto

動作確認のため、認証情報からアクセスキー・シークレットキーを取ってきてshellでexportし、awscliを叩いてみる。

$ export AWS_ACCESS_KEY_ID=xxxxx
$ export AWS_SECRET_ACCESS_KEY=xxxx
$ aws s3 ls # 関係ない権限がついてないことをざっと確認
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
$ aws lambda invoke --function-name kick-private-api out

例によってoutに書き込みがあったりアクセスログがあればok

inomotoinomoto

あとはこれを叩くactionsを作る。

actionsから実行できることさえわかればいいので、凝ったことはしない。
下記参考にworkflow_dispatchのものを作る。
https://dev.classmethod.jp/articles/review-actions-handly-retry-events/

inomotoinomoto

actinos定義を作る。

.github/workflows/kick-lambda.yml
name: kick-lambda

on:
  workflow_dispatch:

jobs:
  invoke:
    runs-on: ubuntu-latest
    steps:
      - name: invoke lambda
        run: |
          aws lambda invoke --function-name kick-private-api out
          echo 'ok, output is:'
          cat out
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ap-northeast-1

完全に最小限で。この場合だと actions/checkout すらいらない。

inomotoinomoto

リポジトリページのActionsタブから該当のものを選び、Run workflowsする。
成功してアクセスログが増えてればok

inomotoinomoto

lambdaをVPC内に作り、それをgithub actions -> awscliでかんたんに実行することができた。

仕組みは非常に単純なので、actionsに限らずslackbotなど色々やりようはあるだろう。

inomotoinomoto

この手のユースケースにはAPI keyなどが必要な場合が多いことに気がついた。
SSM ParameterStoreから値を読み込んで使うことも試しておく。

inomotoinomoto

まずは適当にパラメータを生やす。SSMのパラメータストアのページに行き、SecureStringを作る。keyなどは適当。
ここでは名前を/kick-private-api/keyとしておいた。

inomotoinomoto

VPCエンドポイントのページから新規にエンドポイントを作る。サービス名はちとわからんが、一旦com.amazonaws.ap-northeast-1.ssmで試す。
VPCやサブネットは使うものを適当に選ぶ。エンドポイントの設置場所なので、プライベートなところに置いておけばよかろう。またプライベートDNS名も有効にしておく。特にアクセス方法を頑張る気はないので。
セキュリティグループもlambdaから443が通ればokのはず。

ポリシーはこの場合の付け方がよくわからなかった。ポリシー作成ツールを使うとS3とDynamoのアクションしか選択できず。
ここを絞る場合はセキュリティグループでやるのがよさそう。

inomotoinomoto

Lambdaの実行ロールにSSMの読み込み権限を付与しておく。ここでは手早くSSMのReadOnlyで。

inomotoinomoto

コードはこんな感じで軽く試す。

require 'json'
require 'net/http'
require 'uri'
require 'aws-sdk-ssm'

def lambda_handler(event:, context:)
    key = Aws::SSM::Client.new.get_parameter(name: '/kick-private-api/key', with_decryption: true)[:parameter][:value]
    
    resp = Net::HTTP.get(URI.parse("http://172.31.22.249/#{key}"))
    { statusCode: 200, body: JSON.generate(resp) }
end

aws-sdk-ssmをrequireして普通に使えばok。
動作テストのためにサーバ側をいじりたくないので、リクエストパスに突っ込む。

アクセスログにキーが出ていればok。

このスクラップは2021/06/27にクローズされました