Envoy OAuth2 Filterを試す(未完)
EnvoyのOAuth2 Filterを試してみました。残念ながら期待通りに動きませんでした。メモだけ残しておきます。
構成
- EKS 1.18
- Envoy 1.17.0-dev-483dd3
以下のトラフィックパスを構築します。
Browser ↓ Internet-facing ALB ↓ Service/NodePort ↓ Pod (envoy) ↓ Service ↓ Pod (ingress-nginx) ↓ Ingress (echoserver) ↓ Service (echoserver) ↓ Pod (echoserver)
Route53やALBを設定してインターネットからドメイン名でアクセス可能にしておきます。
また、Google Identity PlatformであらかじめClient IDを作成しておきます。Redirect URIsには https://echoserver.example.com/callback
を指定しておきます(example.com
は自分のドメインに置き換えてください)。
Envoyの設定
Example configuration を参考にYAMLを書きます。まずは検証のために必要最小限に設定にします。分かりにくいのでコメントを入れました。
# /etc/envoy/envoy.yaml static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 10000 } filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager codec_type: "AUTO" stat_prefix: ingress_http route_config: virtual_hosts: # すべてのトラフィックを service cluster に転送する - name: service domains: ["*"] routes: - match: prefix: "/" route: cluster: service timeout: 5s http_filters: # OAuth2 Filterを適用する - name: envoy.filters.http.oauth2 typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2 config: # Google OAuth2 Endpointを定義する token_endpoint: cluster: auth uri: oauth2.googleapis.com/token timeout: 3s authorization_endpoint: https://accounts.google.com/o/oauth2/v2/auth redirect_uri: "https://%REQ(:authority)%/callback" redirect_path_matcher: path: exact: /callback signout_path: path: exact: /signout credentials: # Client ID/Secretを定義する client_id: REDACTED.apps.googleusercontent.com token_secret: name: token sds_config: path: "/etc/envoy/secret.yaml" hmac_secret: name: hmac sds_config: path: "/etc/envoy/secret.yaml" - name: envoy.router clusters: - name: service connect_timeout: 5s type: LOGICAL_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: service endpoints: - lb_endpoints: - endpoint: address: socket_address: # 後続のバックエンドに転送する address: ingress-nginx-controller.ingress-nginx.svc.cluster.local port_value: 80 - name: auth connect_timeout: 5s type: LOGICAL_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: auth endpoints: - lb_endpoints: - endpoint: address: socket_address: address: oauth2.googleapis.com port_value: 443
# /etc/envoy/secret.yaml resources: - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" name: token generic_secret: secret: inline_string: REDACTED - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" name: hmac generic_secret: secret: inline_string: REDACTED
Envoyのデプロイ
以下のYAMLをデプロイします。
apiVersion: apps/v1 kind: Deployment metadata: name: envoy spec: replicas: 1 selector: matchLabels: app: envoy template: metadata: labels: app: envoy spec: containers: - args: - --config-path - /etc/envoy/envoy.yaml - --bootstrap-version - "3" - --service-node - ingress-envoy - --service-cluster - ingress-envoy image: envoyproxy/envoy-dev:483dd3007f15e47deed0a29d945ff776abb37815 name: envoy ports: - name: http containerPort: 10000 protocol: TCP resources: limits: memory: 256Mi requests: cpu: 10m memory: 256Mi volumeMounts: - mountPath: /etc/envoy name: envoy volumes: - name: envoy configMap: name: envoy --- apiVersion: v1 kind: Service metadata: name: envoy spec: type: NodePort ports: - name: http port: 80 nodePort: 30280 protocol: TCP targetPort: http selector: app: envoy
Envoy v3 APIを利用するには引数に --bootstrap-version 3
が必要です。OAuth2 Filter APIはv3で定義されています。
また、 generic_secret
を定義するには引数に --service-node
--service-cluster
が必要です。これらを指定しないと以下のエラーが出ます。
GenericSecretSdsApi: node 'id' and 'cluster' are required. Set it either in 'node' config or via --service-node and --service-cluster options.
動作確認
ブラウザで https://echoserver.example.com
にアクセスします。OAuth2 authorization endpointへのリダイレクトまでは動作したのですが、残念ながら以下のエラー画面になってしまいました。
承認エラー エラー 400: invalid_scope Some requested scopes were invalid. {invalid=[user]}
ブラウザのログを確認すると、以下のようなリクエストが飛んでいました。
https://echoserver.example.com ↓302 https://accounts.google.com/o/oauth2/v2/auth?client_id=REDACTED&scope=user&response_type=code&redirect_uri=https%3A%2F%2Fechoserver.example.com%2Fcallback&state=https%3A%2F%2Fechoserver.example.com%2F ↓302 https://accounts.google.com/signin/oauth/error?authError=REDACTED
OAuth2 scopeをemailなどに変更すれば動作すると思いますが、Envoyのドキュメントには設定項目が見当たりませんでした。v1.16時点ではoauth.protoに定義がないようです。ここでお手上げになりました。
(12/25追記) OAuth2 scopeを指定できるようにするPull Requestがあるようです。
このPull Requestを実際に動かしているコードもあるようです。
今後の課題
実用になるまでは以下の課題がありそうです。
pass_through_matcher
でFilterの除外条件を定義できるが実用に耐えられるか。authority
,path
のregex matchが使えるので大丈夫そう。email
などでリソースにアクセスできる条件を絞り込めるか。現状は UserInfo から属性を取得する実装がなさそうなので無理そう。
EKS Managed Node GroupでSpot Instancesを使う
Amazon EKSのManaged Node Groupsがスポットインスタンスに対応しました。
これまでManaged Node Groupsではオンデマンドしか利用できず、スポットを利用するには自分でAuto Scaling Groups(mixed instance types)を管理する必要がありました。今回のアップデートで簡単にスポットが利用できるようになりました。
設計で気にする点
元記事から重要と思われる点を挙げてみます。
Allocation strategy
まず、スポットインスタンスはなるべく複数のAZやインスタンスタイプに分散して配置されます。ASGでmixed instance typesを利用する場合は lowest-price と capacity-optimized が選べますが、MNGでは capacity-optimized に固定されています。両者の違いは以下です。
具体例は以下の記事が分かりやすいです。
なるべく多くのAZとインスタンスタイプを指定しておくことで、スポット価格が高騰してインスタンスが上がらない確率が低くなります。
Cluster Autoscalerとの併用
Cluster Autoscalerを利用する場合、Cluster Autoscalerが必要台数を正しく計算できるように、MNGには同じCPU数と同じメモリ量のインスタンスタイプを指定する必要があります。これはASGでmixed instance typesを利用する時と同じですね。
Cluster Autoscalerのドキュメントにも記載されています。
ノード停止時の振る舞い
ASGでスポットを利用する場合はaws-node-termination-handlerを入れる必要がありましたが、MNGでは不要です。もともとMNGではEC2インスタンスの停止時にdrainが実行されますが、スポットの停止通知にも対応したことになります。
また、ASGのcapacity rebalance機能でなるべく多くのAZやインスタンスタイプをカバーするように動的に再配置されます。 これはスポットの場合のみ有効になるようです。 詳しくは以下の記事が参考になります。
(2023-01-27 追記) ASG の rebalance 機能はスポットに限らずオンデマンドインスタンスでも有効のようです。ASG のイベントに以下が出ている場合は rebalance が原因です。MNG を AZ 単位に分割すると改善が期待できます。
instances were launched to balance instances in zones ap-northeast-1a with other zones resulting in more than desired number of instances in the group.
Launch Templateとの併用
10月のアップデートでMNGでLaunch Templateが利用できるようになりました。Launch Templateでは1種類のインスタンスタイプしか指定できませんが、MNGでは複数のインスタンスタイプを指定できます。スポットを利用する場合は複数のインスタンスタイプを指定することが望ましいので、基本的にMNGで定義することになると思います。以下のドキュメントに記載があります。
Terraformの実装例
Terraform AWS provider v3.19.0からMNGのスポットに対応しています。
ENHANCEMENTS
- resource/aws_eks_node_group: Add capacity_type argument and support multiple instance_types (Support Spot Node Groups) (#16510)
https://github.com/hashicorp/terraform-provider-aws/releases/tag/v3.19.0
aws_eks_node_group
リソースに capacity_type
という属性が増えています。デフォルトは ON_DEMAND
になっているので、 SPOT
を指定します。
resource "aws_eks_node_group" "default" { cluster_name = aws_eks_cluster.example.name node_group_name = "default" node_role_arn = aws_iam_role.example.arn subnet_ids = local.private_subnet_ids capacity_type = "SPOT" instance_types = [ # 4 vCPU and 32 GiB "r5.xlarge", "r5a.xlarge", "r5n.xlarge", "r5d.xlarge", "r5ad.xlarge", "r5dn.xlarge", ] }
なお、EKSクラスタがすでにあってTerraform AWS providerをv3.19.0にバージョンアップすると capacity_type
の差分が出てしまう場合があります。私の環境ではEKS 1.15のクラスタで差分が出てしまいました。その場合はignore changesに指定すれば差分が無視されるようになります。
resource "aws_eks_node_group" "default" { lifecycle { ignore_changes = [ # 既存クラスタでcapacity_typeの差分が出る場合 capacity_type, ] } }
既存のクラスタで capacity_type
を SPOT
に切り替える場合は、MNGが再作成されてしまうので注意が必要です。MNGが削除されるタイミングでMNG内の全ノードが停止します。
その他
12月のEKSアップデートでは、Management Consoleにノードやワークロードの一覧が確認できる画面が追加されていました。また、Management Consoleからアドオンをデプロイできるようです。この辺はGKEを意識した機能追加な感じがします。EKSはまだまだ伸び代があって継続的に進化しているので面白いですね。
AWS SSOでサードパーティツールを実行する
AWS SSOを利用すると、IAM Access KeyやIAM Secret Access Keyの代わりにブラウザベースの認証を利用してAWS APIにアクセスできます。一方で、AWS SSOに対応しているものはAWS CLI v2ぐらいしかなく、Terraformなどのサードパーティツールはそのままでは使えません。そのため、AWS SSOでサードパーティツールを利用するためのヘルパーツールがいくつか公開されています。例えば aws2-wrap などがあります。
本稿では、AWS SSOやSTSの仕組みを理解するため、手動でShort-term credentialsを取得してサードパーティツールを実行する方法を紹介します。
AWS SSOはすでに設定済みである前提とします。参考までに、AWS SSOの設定例として公式ブログにある How to use G Suite as an external identity provider for AWS SSO を挙げておきます。
Short-term credentialsの取得
SSOセッションが切れている場合は再ログインします。例えば、G Suiteと連携している場合はブラウザでGoogleのログイン画面が表示されます。
% aws s3 ls The SSO session associated with this profile has expired or is otherwise invalid. To refresh this SSO session run aws sso login with the corresponding profile. % aws sso login Attempting to automatically open the SSO authorization page in your default browser. If the browser does not open or you wish to use a different device to authorize this request, open the following URL: https://device.sso.ap-northeast-1.amazonaws.com/ ...
まず ~/.aws/config
の内容を確認します。ここにはAWS SSOでログインするための設定が格納されています。
[profile example] sso_start_url = https://d-********.awsapps.com/start sso_region = ap-northeast-1 sso_account_id = ******** sso_role_name = PowerUserAccess
~/.aws/sso/cache
にあるキャッシュファイルの内容を確認します。ここにはSSOログイン時に取得したアクセストークンが格納されています。
{"startUrl": "https://d-********.awsapps.com/start", "region": "ap-northeast-1", "accessToken": "ey********", "expiresAt": "2020-09-09T22:43:10UTC"}
必要な情報が揃ったら get-role-credentials
コマンドを実行します。以下の引数が必要です。
- --role-name:
~/.aws/config
のsso_role_name
を指定します。これはログイン時に選択したロール名になります。 - --region:
~/.aws/config
のsso_region
を指定します。これはAWS SSOを有効にしたリージョンになります。 - --account-id:
~/.aws/config
のsso_account_id
を指定します。これはログイン先のAWSアカウントになります。 - --access-token:
~/.aws/sso/cache
にあるキャッシュファイルからaccessToken
の値を指定します。
get-role-credentials
コマンドを実行すると、以下のような出力が得られます。
% aws sso get-role-credentials --role-name PowerUserAccess --region ap-northeast-1 --account-id ******** --access-token "ey********" { "roleCredentials": { "accessKeyId": "********", "secretAccessKey": "********", "sessionToken": "********", "expiration": 1599667863000 } }
上記で表示されている情報が Short-term credentials になります。
環境変数の設定とコマンドの実行
前項のコマンドで取得した情報を以下の環境変数に設定します。
% export AWS_ACCESS_KEY_ID=******** % export AWS_SECRET_ACCESS_KEY=******** % export AWS_SESSION_TOKEN=********
環境変数を設定した状態でTerraformを実行してみましょう。AWS APIの呼び出しに成功するはずです。
% terraform apply