AWS-CLIでELB+Cognitoを使ったBasic認証実装

# 背景
単発的なものだったらコンソールでここが○○であっちが△△でと思い出しながら調べながらでも別によいかなと思っていたのですが、散発的にELBとCognitoを使用したBasic認証を使うことがあり、またCognitoコンソールは新旧でUIがガラッと違っていて、これを期にコンソールに縛られないcliベースでの実装を整理しておこうというのと、cliベースで書かれたのものが見当たらなかったというのが主な背景になります。

ネタとしての真新しさは薄いのであしからず。

# Cognito

## ユーザープール作成
### 前提
- ユーザ及びパスワードは管理者が管理するケースを想定
- パスワードポリシーは以下の通り
    - パスワードの最小文字数 12 文字
    - 少なくとも 1 つの数字を含む
    - 少なくとも 1 つの特殊文字 (^ $ * . [ ] { } ( ) ? - " ! @ # % & / \ , > < ' : ; | _ ~ ` + =) を含む
    - 少なくとも 1 つの大文字を含む
    - 少なくとも 1 つの小文字を含む
    - 一時パスワード有効期限:7日

```sh
## 変数定義
PROFILE=
ENV=
PREFIX=
POOL_NAME=${PREFIX}_${ENV}_basicauth_pool

## user_pool作成
USER_POOL=$(aws cognito-idp create-user-pool \
--profile $PROFILE \
--pool-name $POOL_NAME \
--alias-attributes preferred_username \
--username-configuration CaseSensitive=true \
--admin-create-user-config AllowAdminCreateUserOnly=true \
--account-recovery-setting 'RecoveryMechanisms=[{Priority=1,Name=admin_only}]' \
--policies 'PasswordPolicy={MinimumLength=12,RequireUppercase=true,RequireLowercase=true,RequireNumbers=true,RequireSymbols=true,TemporaryPasswordValidityDays=7}')

## user_pool_id取得
USER_POOL_ID=$(echo ${USER_POOL} | jq -r .UserPool.Id)
```

## ユーザプールクライアント作成
- CALL_BACK_DOAMINはRoute53でELBと紐付けているレコードを指定

```sh
## 変数定義
CLIENT_NAME=${PREFIX}_${ENV}_basicauth_client
CALL_BACK_DOAMIN=

## client作成
USER_POOL_CLIENT=$(aws cognito-idp create-user-pool-client \
--profile $PROFILE \
--user-pool-id $USER_POOL_ID \
--client-name $CLIENT_NAME \
--generate-secret \
--explicit-auth-flows ALLOW_USER_PASSWORD_AUTH ALLOW_USER_SRP_AUTH ALLOW_REFRESH_TOKEN_AUTH \
--supported-identity-providers COGNITO \
--callback-urls https://$CALL_BACK_DOAMIN/oauth2/idpresponse \
--allowed-o-auth-flows code \
--allowed-o-auth-scopes openid \
--allowed-o-auth-flows-user-pool-client)

## client_id取得
CLIENT_ID=$(echo ${USER_POOL_CLIENT} | jq -r .UserPoolClient.ClientId)
```

## ユーザープールドメイン作成
```sh
## 変数定義
DOMAIN=${PREFIX}-${ENV}-elbbasic

## user_pool_domain作成
aws cognito-idp create-user-pool-domain \
--profile $PROFILE \
--domain $DOMAIN \
--user-pool-id $USER_POOL_ID
```

## ユーザ作成
```sh
## 変数定義
USER_NAME=
PASSWORD=

## user作成
aws cognito-idp admin-create-user \
--profile $PROFILE \
--user-pool-id $USER_POOL_ID \
--username $USER_NAME \
--temporary-password $PASSWORD

## パスワードステータス変更
aws cognito-idp admin-set-user-password \
--profile $PROFILE \
--user-pool-id $USER_POOL_ID \
--username $USER_NAME \
--password $PASSWORD \
--permanent
```

# ELB
## ListenerRule変更
### 前提
- Priorityが1(not default)の既存のListenerRuleを変更する場合を想定
- 認証後に任意のTargetGroupに転送する場合を想定
- リクエスト条件は既存のまま

※ListenerRuleの前提がPJによってだいぶ違うのでここは手でやった方が正直よさそう。。

```sh
## 変数定義
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --profile ${PROFILE} | jq -r '.Account')
USER_POOL_ARN=arn:aws:cognito-idp:ap-northeast-1:${AWS_ACCOUNT_ID}:userpool/${USER_POOL_ID}
LISTENER_ARN=
TARGETGROUP_ARN=

## 対象Rulearn取得
RULE_ARN=$(aws elbv2 describe-rules \
--profile $PROFILE \
--listener-arn $LISTENER_ARN \
| jq '[.Rules[]]' | jq 'map(select(.Priority=="1"))' | jq -r '.[].RuleArn')

## Rule変更
aws elbv2 modify-rule \
--rule-arn $RULE_ARN \
--actions Type=authenticate-cognito,AuthenticateCognitoConfig={"UserPoolArn=$USER_POOL_ARN, UserPoolClientId=$CLIENT_ID,UserPoolDomain=$DOMAIN"},Order=1 \
Type=forward,ForwardConfig={"TargetGroups={"TargetGroupArn=$TARGETGROUP_ARN"}"},Order=2
```

# まとめ
cliでリソース定義を細かく指定して作成する場合は動作確認で時間を取られガチですが、一度コマンド出来上がってしまえばUIの刷新に左右されず同じ結果を得られるなどの利点があるため、何度か繰り返しそうなタスクでは積極的に活用していきたいところ。

今回のケースでは叩くコマンドや変数の定義、必要なARNを事前に引っ張ったりと手数が多くなってしまったので、まだ改善の余地はありそうですね。