Slide 1

Slide 1 text

GitHub Actionsに「強い」AWSの権限を渡したい 2021.05.14 ⾯⽩法⼈カヤック 藤原俊⼀郎 @fujiwara

Slide 2

Slide 2 text

@fujiwara SREチーム所属 2011年〜カヤック(10年!) ISUCON 1,2,5優勝、ISUCON 3,8出題 github.com/kayac/ecspresso Amazon ECS デプロイツール github.com/fujiwara/lambroll AWS Lambda デプロイツール

Slide 3

Slide 3 text

最近の仕事 ぼくらの甲⼦園ポケット ECS 移⾏ 2014年リリースの⻑寿ゲーム before: EC2 (ondemand & spot) after: ECS (Fargate & EC2 spot 100%) Perl5.16→5.30 ログ処理をKinesis+Lambdaに API に CloudFront 導⼊ EC2 & Chef 全廃

Slide 4

Slide 4 text

最近の悩み AWSのリソースの管理は基本 Terraform で⾏っている (アプリケーションだけ ecspresso / lambroll) terraform plan とその通知は tfnotify github.com/mercari/tfnotify を使って GitHub Actions / CircleCI で実⾏している 現状 terraform apply は各⾃の⼿元(⼀部は専⽤EC2)で実⾏している apply も CI/CD 環境で実⾏したい!

Slide 5

Slide 5 text

現状、terraform apply を CI/CD 環境で実⾏していない理由 実⾏に「強い」権限が必要 (⼤抵 Administrator 権限と同等) 強すぎる IAM User のアクセスキーを外部に預けたくない アクセスキーは環境変数に設定される リポジトリへの書き込み権限を取得されると、奪取される可能性がある Classi の事故 corp.classi.jp/news/2416/ フィッシングからGitHubアクセス キーをコードに書いていなくても、CIをいじって実⾏されたらキーは抜かれる GitHubで全員にMFA強制はなかなか難しい (外部の⼈も全員強制になる)

Slide 6

Slide 6 text

でも本当はやりたい 開発者、運⽤者の⼿元が安全とは限らない ⼈に依存しない環境で実⾏できるようにしておきたい 条件 権限は安全に保ちたい GitHubリポジトリの書き込み権が奪われてもAWSの権限は渡したくない うっかり誤クリックや誤mergeでは発動したくない 狙ったタイミングで、特定の⼈だけが発動したい

Slide 7

Slide 7 text

作戦1 - AssumeRole with MFA MFA を使って assume role して「強い」権限を得る作戦

Slide 8

Slide 8 text

作戦1 - AssumeRole with MFA IAM Userを作ってアクセスキーを発⾏する sts:AssumeRole だけを付与する MFAを必須にする(QRコードは控えておく) ユーザーのIAM Policy { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::123456789012:role/TerraformApply", "Condition": { "BoolIfExists": { "aws:MultiFactorAuthPresent": "true" } } }

Slide 9

Slide 9 text

作戦1 - AssumeRole with MFA terraform apply ⽤の「強い」 IAM Role を作る 作った IAM User を信頼する assume role policy { "Effect": "Allow", "Action": "sts:AssumeRole", "Principal": { "AWS": "arn:aws:iam::123456789012:user/GitHubActions" }, "Condition": { "BoolIfExists": { "aws:MultiFactorAuthPresent": "true" } } }

Slide 10

Slide 10 text

作戦1 - AssumeRole with MFA GitHub Actionsで、 . IAM User のアクセスキーを secrets に設定 このキーが取られてもMFAがないとなにもできない . manual trigger のジョブを作る MFA token⼊⼒欄も定義する name: apply on: workflow_dispatch: inputs: token-code: description: 'mfa code' required: true github.blog/changelog/2020-07-06-github-actions-manual-triggers-with- workflow_dispatch/

Slide 11

Slide 11 text

作戦1 - AssumeRole with MFA

Slide 12

Slide 12 text

作戦1 - AssumeRole with MFA . secrets のアクセスキーを設定 . aws sts assume-role をマニュアル⼊⼒した MFA token で実⾏ . 「強い」Roleの権限を取得できる! jobs: manual: runs-on: ubuntu-latest steps: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 - name: aws sts AssumeRole with MFA run: | OUTPUT=`aws sts assume-role \ --role-arn arn:aws:iam::123456789012:role/TerraformApply \ --role-session-name github-actions \ --duration-seconds 900 \ --serial-number arn:aws:iam::123456789012:mfa/GithubActions \ --token-code ${{ github.event.inputs.token-code }}` echo AWS_ACCESS_KEY_ID=`echo $OUTPUT | jq -r .Credentials.AccessKeyId` >> $GITHUB_ENV echo AWS_SECRET_ACCESS_KEY=`echo $OUTPUT | jq -r .Credentials.SecretAccessKey` >> $GITHUB_ENV echo AWS_SESSION_TOKEN=`echo $OUTPUT | jq -r .Credentials.SessionToken` >> $GITHUB_ENV shell: bash

Slide 13

Slide 13 text

作戦1 - AssumeRole with MFA いいところ Actionsに設定されているアクセスキー単体では何もできないので漏洩が怖くない 実⾏時に⼈が MFA token を⼊⼒するのでうっかり誤発動しない 再実⾏しても token が有効期限切れになっているので動かない いまいちなところ MFAの設定を複数⼈で共有する必要がある (QRコード) IAM User の MFA はひとつしか作れないため MFA token の取得から実⾏までに時間が掛かると有効期限が切れる Actionsの環境起動に10〜20秒かかるので、残り時間を⾒極めて投⼊が必要

Slide 14

Slide 14 text

作戦2 - CloudShell exports credentials AWS CloudShellが持っている権限を頂いてしまおう作戦

Slide 15

Slide 15 text

作戦2 - CloudShell exports credentials AWS CloudShell マネージメントコンソールを開いている⼈の権限が設定されている 外部へのネットワークアクセスが可能

Slide 16

Slide 16 text

作戦2 - CloudShell exports credentials CloudShell で設定されている AWS_ の環境変数 $ env | grep AWS_ |sort AWS_CONTAINER_AUTHORIZATION_TOKEN=******* AWS_CONTAINER_CREDENTIALS_FULL_URI=http://localhost:1338/latest/meta-data/container/security-credentials AWS_DEFAULT_REGION=ap-northeast-1 AWS_EXECUTION_ENV=CloudShell AWS_REGION=ap-northeast-1 これでアクセスキーが取得できる $ curl -H"Authorization: $AWS_CONTAINER_AUTHORIZATION_TOKEN" \ $AWS_CONTAINER_CREDENTIALS_FULL_URI { "LastUpdated": "1970-01-01T00:00:00Z", "Type": "", "AccessKeyId": "ASIAUSOAHXO5WFCZF5GW", "SecretAccessKey": "xxxxxxxxxx", "Token": "xxxxxxxxxxxx" "Expiration": "2021-05-13T06:09:04Z", "Code": "Success" }

Slide 17

Slide 17 text

作戦2 - CloudShell exports credentials AWS_CONTAINER_CREDENTIALS_FULL_URI http://localhost:1338/latest/meta-data/container/security-credentials つまり CloudShell の localhost:1338 に外部から繋げれば CloudShell が持っているアクセスキーを取得できるのでは…?

Slide 18

Slide 18 text

作戦2 - CloudShell exports credentials cloudflared - Argo Tunnel client github.com/cloudflare/cloudflared ngrok みたいなやつ by Cloudflare CloudShell にインストールして起動。バイナリを置くだけ $ curl -sL https://bin.equinox.io/c/VdrWdbjqyF/cloudflared-stable-linux-amd64.tgz | tar zxvf - cloudflared $ ./cloudflared tunnel --url localhost:1338 ( 略) 2021-05-13T06:00:37Z INF +-----------------------------------------------------------+ 2021-05-13T06:00:37Z INF | Your free tunnel has started! Visit it: | 2021-05-13T06:00:37Z INF | https://hardcover-mixing-bs-madness.trycloudflare.com | 2021-05-13T06:00:37Z INF +-----------------------------------------------------------+ 表⽰されるURL(毎回異なる)にアクセスするとCloudShellのlocalhost:1338に繋がる

Slide 19

Slide 19 text

作戦2 - CloudShell exports credentials これでどこからでも ($AWS_CONTAINER_AUTHORIZATION_TOKENがあれば) HTTPSで取得できる $ curl -H"Authorization: $AWS_CONTAINER_AUTHORIZATION_TOKEN" \ https://hardcover-mixing-bs-madness.trycloudflare.com/latest/meta-data/container/security-credentials { "LastUpdated": "1970-01-01T00:00:00Z", "Type": "", "AccessKeyId": "ASIAUSOAHXO54XC5257P", ...

Slide 20

Slide 20 text

作戦2 - CloudShell exports credentials まとめ . 「強い」権限を持っている⼈が CloudShell を起動 . CloudShell で cloudflared を起動して外部に localhost:1338 を公開する . $AWS_CONTAINER_AUTHORIZATION_TOKEN と公開URLをActionsに⼊⼒ . Actions からURLにアクセスしてアクセスキーを取得

Slide 21

Slide 21 text

作戦2 - CloudShell exports credentials いいところ 強い権限を持った⼈間 (マネージメントコンソールにログインできている) がいるときだ け、その⼈の権限をその場で渡せる cloudflared を停⽌したり CloudShell を閉じればアクセスできなくなる 閉じてしまえばうっかり再実⾏しても権限は取れない 渡したアクセスキー⾃体にも有効期限がある CloudShellは無操作だと20〜30分で停⽌される いまいちなところ cloudflared の中をアクセスキーが通過するのをどう考えるか Cloudflareがここで変なことはしないと信頼するかどうかによる AWSが同様のサービスを出してくれたら万事解決では…?

Slide 22

Slide 22 text

まとめ AWS外に強すぎる権限のアクセスキーを保管しないために、いろいろ考えてみました . AssumeRole with MFA 作戦 実はMFAなしでも簡易的な対策になる キーだけでは sts:AssumeRole しかできない とはいえassume roleの⼿順は .github/workflows に書いてあるのでちゃんと読まれ たら危ない . CloudShell exports credentials 作戦 没案: Self Hosted Runner EC2でActionsの実⾏環境を⽤意して強い権限を設定しておけば…? workflowをいじられたら強い権限でなんでもできてしまう うっかり再実⾏を防げない