のぴぴのメモ

自分用のLinuxとかの技術メモ

Lambdaの実行ロールのクレデンシャルを確認してみた

はじめに

Lambdaの関数からAWS APIを実行する場合、Lambdaに付与した実行ロール(Execution role)の権限が利用されます。
docs.aws.amazon.com
というところまではすぐにわかったのですが、この実行ロールのクレデンシャルをLambda上でどう取得しているかがパッとわからなかったので、実機で確認しました。確認はPythonで実施しています。

結論

結論を述べると、Lambdaを実行する環境の「環境変数」から実行ロールのクレデンシャルを取得することができます。

Lambda検証コード(python)

import os
import json
import boto3

def lambda_handler(event, context):
    
    print('AWS_ACCESS_KEY_ID={}'.format(os.environ['AWS_ACCESS_KEY_ID']))
    print('AWS_SECRET_ACCESS_KEY={}'.format(os.environ['AWS_SECRET_ACCESS_KEY']))
    print('AWS_SESSION_TOKEN={}'.format(os.environ['AWS_SESSION_TOKEN']))
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

検証コードの実行結果

START RequestId: 6b84d91a-2cb7-49ec-83d1-0844247f2188 Version: $LATEST
AWS_ACCESS_KEY_ID=ASIAT5XxxxxxxxxxxS4G
AWS_SECRET_ACCESS_KEY=UJ7JT9M7kxxxxxxxxxxxxxxxxxRbujmy7DwdyrK+
AWS_SESSION_TOKEN=FQoGZXIvYXdzEEcaFlCTJtylI<中略>IBTtPdOoott/04wU=
END RequestId: 1b54a3fa-4793-XXXX-XXXX-e6a1f102c0ef
REPORT RequestId: 1b54a3fa-4793-XXXX-XXXX-e6a1f102c0ef	Duration: 124.07 ms	Billed Duration: 200 ms 	Memory Size: 128 MB	Max Memory Used: 76 MB

クレデンシャルはマスクしています。
ドキュメントを確認すると、Lambdaの環境変数一覧にクレデンシャルがありました。
docs.aws.amazon.com

調査履歴

Lambdaの実行ロールを利用したAWS API実行

Lambda上でAWS APIを実行する場合は通常、EC2やオンプレ環境と同様にAWS SDKを利用してAPIを実行します。例えばS3のバケット一覧取得をLambda上でpythonで実行する場合は、以下のようなコードを書きます。

import json
import boto3

def lambda_handler(event, context):
    
    s3 = boto3.resource('s3')
    for bucket in s3.buckets.all():
        print('{}'.format(bucket.name))
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

このコード例を見ると、Lambda固有の書き方をする部分もありますが、AWS APIを実行するためのセッション取得の部分は” s3 = boto3.resource('s3')”とオンプレやEC2から実行する場合と変わらないです。またAWS SDKもLambda専用のSDKという記載はドキュメントには(私が調べた限り)ありませんでした。

ということから、セッション取得のためのクレデンシャル情報の取得はオンプレやEC2インスタンスと同様の挙動で取得しているのではないかということが考えられます。

AWS SDK(boto3)におけるクレデンシャル情報の取得

次にAWS SDKがどのようにクレデンシャル情報を取得しているかということを、ここでおさらいします。
boto3のクレデンシャルの取得手順は、公式ドキュメントでは下記に記載があります。

こちらの内容によると、boto3でのクレデンシャル取得は、以下の優先順で取得するとあります。

  1. boto.client() 実行時に、パラメータとしてクレデンシャル情報を明示的に引き渡す
  2. セッションオブジェクト作成時に、パラメータとしてクレデンシャル情報を明示的に引き渡す
  3. 環境変数
  4. AWS config file (~/.aws/config)
  5. Assume Role provider
  6. Boto2のconfigファイル(/etc/boto.cfg and ~/.boto)
  7. EC2インスタンスロール(EC2メタデータ: http://169.254.169.254/latest/meta-data/iam/security-credentials/ロール名称)

クレデンシャル情報取得方法の絞り込み

上記のboto3のクレデンシャル情報取得方法の優先順位とLambdaのサンプルコードからLambdaの実行ロールのクレデンシャル取得で可能性のない方式を除外します。

  • boto.client()実行時にコード上でクレデンシャル情報を明示的にしていない・・・該当しない(コードでクレデンシャルの指定なし)
  • セッションオブジェクト作成時にクレデンシャル情報を指定する ・・・・・・該当しない(セッション取得の関数は利用していない)
  • Assume Role provid・・・該当しない(コード上に該当する箇所がない。また後述するがconfigに該当箇所がない)

絞り込んで残った候補は以下の4つです。

  1. 環境変数
  2. AWS config file (~/.aws/config)
  3. Boto2のconfigファイル(/etc/boto.cfg and ~/.boto)
  4. EC2インスタンスロール(EC2メタデータ: http://169.254.169.254/latest/meta-data/iam/security-credentials/ロール名称)

configtと boto.cfgの確認 > なし

雑なコードですが下記コードでconfig, boto.cfg, .botoファイルの有無を確認します。(file=のファイル名にそれぞれのファイルのパスを設定して実行)
結果として、3つのファイルとも存在していないことが確認できました。

import json
import boto3

def lambda_handler(event, context):

    file='~/.aws/config'
    fp = open(file, "r")
    for line in fp:
        print('{}'.format(line))
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

EC2インスタンスロールの確認

EC2インスタンスロールと同様にインスタンスメタデータ(http://169.254.169.254/latest/meta-data)から取得できるか確認します。

検証コード

この検証コードではLambdaが実行されているインスタンスの、インスタンスメターデータの取得を試みています。

import urllib.request
import json
import boto3

def lambda_handler(event, context):
    
    url = 'http://169.254.169.254/latest/meta-data/'
    
    try:
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req) as res:
            print(res.read())
    except urllib.error.URLError as e:
        print(e.reason)
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
結果 > 取得失敗

実行の結果、”Connection refused”とエラー応答があり、メタデータである'http://169.254.169.254’からクレデンシャル取得を行うことは出来ませんでした。

START RequestId: df6064d2-ee6e-4c73-9dd5-d99f44de5ea3 Version: $LATEST
[Errno 111] Connection refused
END RequestId: df6064d2-ee6e-4c73-9dd5-d99f44de5ea3
REPORT RequestId: df6064d2-ee6e-4c73-9dd5-d99f44de5ea3  Duration: 1057.08 ms	Billed Duration: 1100 ms 	Memory Size: 128 MB	Max Memory Used: 77 MB	

環境変数の確認

下記環境を準備し、Lambdaの実行環境の環境変数を確認します。

import os
import json
import boto3

def lambda_handler(event, context):
    for key in os.environ:
        print('{:30s}= {}'.format(key, os.environ[key]))
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

確認すると、Lambdaの実行環境内にAWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、下記のクレデンシャルコードが存在していることがわかりました。