github actionsからprivateなweb apiを叩く
AWSでIP制限があるサーバのweb apiを、github actionsからキックしてみるテスト
まずは対象となるsandbox環境を作る
- 適当なVPC
- 対象APIのサーバを模したEC2(セキュリティグループでIP制限してhttpdでも入れとく)
- EC2に入ってアクセスログが見れる状態
VPCは特に何でもいいのでデフォルトのものを使う。特にサブネットを切り分けたりする予定も無い。
EC2はいつものお砂場を立てて適当にSSMで入り、 sudo yum install httpd
してsudo systemctl start httpd
しておく。
セキュリティグループで手元のグローバルIPから80番への通信を許可し、Test Pageが見えればok。
そもそもSSMで入ってるのでログも見れる。
EC2へのアクセス経路としてはLambda in VPCを使う。対象EC2と同一VPC内の適当なサブネットにlambdaを配置し、それをactionsからAPIで発火する、という方針。
というわけでlambdaを生やす。
lambdaの作成画面にて適当に名前を入れ、ランタイムを選ぶ。手っ取り早くhttpリクエストが投げられれば良いので、慣れているrubyをチョイス。
実行ロールは放置し、詳細設定を開く。
VPCとサブネットを選択するが、サブネットは2つ以上選んでおけとのお言葉なので選んでおく。まぁなんでもよい。
セキュリティグループ...はアウトバウンドが適当に通ってればよいはず。インバウンド無し・アウトバウンド全許可のルールが手元にあったのでつけておく。
適当にコードを書く。動作確認ができればいいのでごく最低限で。
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設定によって適当に。
ブラウザ上で実行できたらawscliからも実行してみる。
EC2に入ってアクセスログを見つつ、awscliがセットアップされた手元から
aws lambda invoke --function-name kick-private-api out
※kick-private-api
は今回作ったlambdaの関数名
※out
はレスポンスが入るファイル名。戻り値見ないけど必須なので適当に指定
アクセスログが更新されてればok。
あとはactions用に適当なユーザ生やしてそこからinvokeすればよいはず。
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"
}
]
}
動作確認のため、認証情報からアクセスキー・シークレットキーを取ってきて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
あとはこれを叩くactionsを作る。
actionsから実行できることさえわかればいいので、凝ったことはしない。
下記参考にworkflow_dispatchのものを作る。
まずは適当にリポジトリを生やし、AWS_ACCESS_KEY_ID
とAWS_SECRET_ACCESS_KEY
で。
actinos定義を作る。
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
すらいらない。
リポジトリページのActionsタブから該当のものを選び、Run workflowsする。
成功してアクセスログが増えてればok
lambdaをVPC内に作り、それをgithub actions -> awscliでかんたんに実行することができた。
仕組みは非常に単純なので、actionsに限らずslackbotなど色々やりようはあるだろう。
この手のユースケースにはAPI keyなどが必要な場合が多いことに気がついた。
SSM ParameterStoreから値を読み込んで使うことも試しておく。
まずは適当にパラメータを生やす。SSMのパラメータストアのページに行き、SecureStringを作る。keyなどは適当。
ここでは名前を/kick-private-api/key
としておいた。
Lambda in VPCからパラメータストアを使う場合にはVPCエンドポイントが必要。
VPCエンドポイントのページから新規にエンドポイントを作る。サービス名はちとわからんが、一旦com.amazonaws.ap-northeast-1.ssm
で試す。
VPCやサブネットは使うものを適当に選ぶ。エンドポイントの設置場所なので、プライベートなところに置いておけばよかろう。またプライベートDNS名も有効にしておく。特にアクセス方法を頑張る気はないので。
セキュリティグループもlambdaから443が通ればokのはず。
ポリシーはこの場合の付け方がよくわからなかった。ポリシー作成ツールを使うとS3とDynamoのアクションしか選択できず。
ここを絞る場合はセキュリティグループでやるのがよさそう。
Lambdaの実行ロールにSSMの読み込み権限を付与しておく。ここでは手早くSSMのReadOnlyで。
コードはこんな感じで軽く試す。
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。