LoginSignup
10
1

AWS Security Token Service(STS)を使ってIAM認証のLambda関数を突破する

Last updated at Posted at 2023-12-01

はじめに

今回はLambda関数URLをIAM認証にして、Security Token Service(STS)で作成したIAMユーザーを利用して、Lambda関数を実行してます。

この記事はTDCソフト株式会社Advent Calendarの2日目です。

Lambda関数URLとは

関数 URL は、Lambda 関数のための専用 HTTP エンドポイントです。関数 URL の作成と設定には、Lambda コンソールまたは Lambda API を使用します。関数 URL を作成すると、一意の URL エンドポイントが Lambda により自動的に生成されます。関数 URL を作成した後に、その URL エンドポイントが変更されることはありません。関数 URL のエンドポイントでは、次の形式を使用します。
https://<url-id>.lambda-url.<region>.on.aws

今まではAPIGatewayやALBなどを用意して、外部からのアクセスしていましたが、関数内の専用のエンドポイントを実行するだけでLambda関数が実行できるようになりました。

また、今回はLambdaの実行時間がAPIGatewayのレスポンスのタイムアウト時間をオーバーすることが見込まれていたため、関数URLを採用することにしました。
関数URLはレスポンスのタイムアウトはLambda関数のタイムアウト値である15分までとなっています。

ただ関数URLは、現時点で「認証なし」か「IAM認証」しか選べません。
今回の実装ではクライアントから実行することを想定しており、IAM情報を持たせることができないため、Security Token Service(STS)を利用し、一時的なIAMユーザーを作成し、Lambda関数をエンドポイントから実行することにしました。

Security Token Service(STS)とは

AWS Security Token Service (AWS STS) を使用して、AWS リソースへのアクセスをコントロールできる一時的セキュリティ認証情報を持つ、信頼されたユーザーを作成および提供することができます。

Security Token Service(STS)は一時的なユーザーを作成できるサービスです。
IAMのアクセスキーとは異なり、有効期限を設定して一時的に許可できます。
また、STSへのリクエストに応じて、その都度動的に生成されるため、ユーザーに紐づいて保存されません。

構成

わざわざ図に示す必要もないくらいシンプルではありますが、最終的な構成は以下を目指します。

sts記事.png

ユーザーはブラウザの静的なwebアプリから実行したいLambdaを実行します。
また、IAMユーザー発行時の認証は、記事では省きますが、実際はAPI Gateway Lambda オーソライザーを実装し、JWTのトークン検証を予定しています。

一時的なIAMユーザーを作成する

今回はLambdaでSTSによる一時的なIAMユーザーを作成します。
具体的にはlambdaに紐づくIAMロールを使用して、STSのAssumeRole APIを叩き、指定したロールが付与された一時的なIAMユーザーを発行します。

今回は以下の記事をとても参考にしました。

下準備

LambdaやIAMユーザーに紐づけるポリシーやロールを作成します。

まずは、Lambda関数を作成します。
なお、Lambda関数の作成方法については、省きます。

Lambda関数の作成が終わったら、IAMから先ほど作成(設定)したロールを編集していきます。
ロールに紐づいたポリシーに以下のStatementを追加します。

許可ポリシー
{
    "Version": "2012-10-17"
    "Statement": [
        {
            "Action": "sts:AssumeRole",
            "Effect": "Allow",
            "Resource": "*"
        }
    ],
}

これで今回作成したLambdaに紐づくロールがSTSでAssumeRoleできるようになりました。

次に、一時的なIAMユーザーに付与するロールを作成します。
このロールにはLambdaの実行権限を付与します。

許可ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Statement1",
            "Effect": "Allow",
            "Action": [
                "lambda:*"
            ],
            "Resource": "実行するlambdaのARN"
        }
    ]
}

次に、信頼関係タブから信頼ポリシーを編集します。
Principalには先ほど作成したLambda関数を実行するロールのARNを入力します。

信頼ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "先ほど作成したロールのARN"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

これで一通りの準備は終わりました。

ソースコード

実際にIAMユーザーを発行するLambda関数を実装していきます。

lambda_function.py
import json
import boto3

sts = boto3.client('sts')

def lambda_handler(event, context):
    sts = boto3.client('sts')
    role = '一時的なIAMユーザーに付与するロールのARN'
    caller_info = sts.get_caller_identity()
    session = caller_info.get('Arn').split('/')[1]
    response = sts.assume_role(
        RoleArn=role,
        RoleSessionName=session
    )

    print('export AWS_ACCESS_KEY_ID=' + response.get('Credentials').get('AccessKeyId'))
    print('export AWS_SECRET_ACCESS_KEY=' + response.get('Credentials').get('SecretAccessKey'))
    print('export AWS_SESSION_TOKEN=' + response.get('Credentials').get('SessionToken'))
    print('export AWS_EXPIRATION=' + str(response.get('Credentials').get('Expiration')))

    return {
        'statusCode': 200,
        'body': json.dumps({
            "AWS_ACCESS_KEY_ID":response.get('Credentials').get('AccessKeyId'),
            "AWS_SECRET_ACCESS_KEY":response.get('Credentials').get('SecretAccessKey'),
            "AWS_SESSION_TOKEN":response.get('Credentials').get('SessionToken'),
            "AWS_EXPIRATION":str(response.get('Credentials').get('Expiration')),
        })
    }

これで、一時的なIAMユーザーを作成できるようになりました。

Lambda関数 IAM認証を突破する

次にIAM認証がかかったAPIを叩くには、リクエストを署名する必要があります。
先ほど作ったLambda関数から受け取ったIAMユーザーを使用してリクエストの署名を実装しました。

AWS SDK (「サンプルコードとライブラリ」を参照) または AWS コマンドライン (CLI) ツール を使用して API リクエストを AWS に送信する場合、SDK および CLI クライアントが指定したアクセスキーを使用してリクエストを認証するため、このセクションをスキップできます。正当な理由がない限り、常に SDK または CLI を使用することをお勧めします。
複数の署名バージョンをサポートするリージョンでは、リクエストに手動で署名する場合、使用する署名バージョンを指定する必要があります。マルチリージョンアクセスポイントにリクエストを送信すると、SDK と CLI は Signature Version 4A の使用に自動的に切り替えます。追加の設定は不要です。

なお、説明にもある通り、署名を自作するのは大変なので、SDKを利用して署名していきます。

AWSの署名バージョン4とは

HTTP で送信される AWS リクエストに認証情報を追加するプロセスです。
HTTPのリクエスト情報とIAMのアクセスキー (アクセスキー ID、シークレットアクセスキー) を使用して署名します。
また、一部のリクエスト要素からリクエストのハッシュを計算し、リクエストに含まれているハッシュ値とAWS のサービス内で同様の情報を利用してでたハッシュ値を比較することで、改ざんを防ぐことができます。

ソースコード

実際にクライアントサイドに実装していきます。
また、今回はSDKライブラリを利用するので、それぞれインストールします。
HTTP クライアントにはaxiosを使用しています。

npm i @smithy/protocol-http @smithy/signature-v4 @aws-crypto/sha256-js
getAWS.ts
import axios from 'axios'
import { HttpRequest } from '@smithy/protocol-http'
import { SignatureV4 } from '@smithy/signature-v4'
import { Sha256 } from '@aws-crypto/sha256-js'
import { AwsCredentialIdentity, HttpRequest as IHttpRequest } from '@smithy/types'

// lambdaに渡すbody
type Body = {
  test: string
}

/**
 * AWSの署名バージョン4による署名を行います
 * @param url 実行したいlambda関数URL
 * @param body lambdaに渡すbody
 * @param IAMSecKey IAM認証情報
 * @returns 標準的なメッセージプロパティやアドレス指定情報
 */
async function getSignaturev4(url: string, body: Body, IAMSecKey: AwsCredentialIdentity) {
  const apiUrl = new URL(url)
  const json = JSON.stringify(body)
  const signatureV4 = new SignatureV4({
    service: 'lambda',
    region: 'ap-northeast-1',
    credentials: IAMSecKey,
    sha256: Sha256,
  })
  const httpRequest = new HttpRequest({
    headers: {
      'Content-Type': 'application/json',
      host: apiUrl.hostname,
    },
    hostname: apiUrl.hostname,
    method: 'POST',
    path: apiUrl.pathname,
    body: json,
  })
  const signedRequest = await signatureV4.sign(httpRequest)
  return signedRequest
}

/**
 * lambda関数URLを叩きます
 * @param url 実行したいlambda関数URL
 * @param signedRequest 署名済みリクエスト
 * @returns lambda関数の戻り値
 */
async function getLambda(url: string, signedRequest: IHttpRequest) {
  const response = await axios.post(url, signedRequest.body, {
    headers: signedRequest.headers,
  })
  return response.data
}

まとめ

STSを使用してIAMユーザーを発行する際にロールやポリシー、ユーザーを気にしながら記述する必要があり、とても理解が深まりました。

また、実装序盤では別のライブラリを使用していたため、署名の検証に失敗していました。
署名部分で使用する暗号化のライブラリには複数あり、誤ったものを使用したため、失敗していたと思われます。

今回はクライアントサイドだったため、'@aws-crypto/sha256-js'を利用することで成功しましたが、今後もう少し詳しく見ていきたいと思います。

参考

10
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
1