1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWSが便利なこと、全てのリソースはIAMで制御できる〜PassRoleを覚えよう〜

1
Posted at

📍 冒頭:アプリエンジニアに伝える「AWSの本当の価値」

「AWS って何が便利?」

アプリエンジニアに聞かれた時、なにより最初に答えるのが

🎯 AWS の最大の魅力:IAM による一元化

「すべての AWS リソースを IAM というサービスで制御できるんだよ。

ここまでが前座です。

※🔒 他のクラウドの批評はおいておいて
他のクラウドだと、アプリケーション側で権限チェックを実装する羽目になる
AWS は アプリケーションロジックと認可を完全に分離 できる。
これが 「シンプルに細かいことができる」 という AWS の真の進化でさ〜(長い話2始まる)

アプリケーションロジックに ACL を埋め込まなくていい

「すべてのAWSリソースをIAMというサービスで制御してもらえる。アプリケーションロジック中の実装にACLを作り込んだりを排除設計できる 」

「ログインした後に、DBにアクセスするなんて制御いれたりするじゃない。あの辺りをAWS側で制御できちゃう仕組みがあるから面倒なセキュリティ制御も排除してもらえるって魅力的でしょう?他のクラウドサービス? AWSはシンプルに細かいことができるのいいんだって〜(長い話が始まる)」

どういうことか?頭の中に置いておいて欲しい絵

image.png
1: ログインをする
(APIGateway:認証する:ログイン認可トークンしかないRole自体がないユーザの判断)
2: ユーザ・パスワードなどでログイン成功
(Cognito認可する:CognitoのUserPoolというユーザのトークンとIAMがひもづく)
3: サービスサイトページへ転送
(APIGatewayによりLambdaにアクセスをする直前、Cognito連携によりIAMのRoleを取得判断する)
4: 認可される
(#3に継続してLambdaへのアクセス,Cloudwatchなどへの書き込み,Dynamoへのアクセスが有効なRolemが獲得できる)
5: Lambdaへのアクセス
(この時、セッションで個別認可されたRoleを保持するのでLambdaが起動する、Cloudwatchのログへの書き込みも可能)
6: LambdaがDynamoへのアクセス
(Lambdaが起動した際に、個別認可されたRoleにDynamoへのアクセス制御がPassRoleによってユーザのかわりにLambdaが認可Roleを利用してアクセスできる。)

_※すごい!っておーつきは最初学んだ時に思いました。これJavaSpringでやったらSpringSecurityIntercepteorでVoteとかいろいろつかわないといけなかったから・・

従来のやり方:

# アプリコードに制御が混在
if user.has_permission('read_db'):
    result = db.query(...)
else:
    raise PermissionError()

AWS のやり方:

# ビジネスロジックだけ
result = db.query(...)  # IAM ロールで制御済み

ログイン後の DB アクセス制御を AWS 側でやってくれる

従来: ログイン → ユーザーコード確認 → DB アクセス権限チェック → クエリ実行

AWS: ログイン → IAM ロール取得 → DB アクセス自動制御(アプリは何もしない)


💡 間違ったユーザーが「他の AWS リソースにアクセスする」こと自体を防ぐ

┌─────────────────────────────────────┐
│  ユーザーA がログイン                 │
├─────────────────────────────────────┤
│  IAM ロール取得                       │
│  ├─ DynamoDB Table1 にアクセス OK   │
│  ├─ DynamoDB Table2 にアクセス ✗   │
│  ├─ S3 バケット にアクセス ✗        │
│  └─ Lambda 実行 ✗                   │
├─────────────────────────────────────┤
│  アプリコードは「こいつ誰?」を気にしない
│  = セキュリティ処理がない = シンプル!
└─────────────────────────────────────┘

メリット:

  • アプリケーションにセキュリティ実装が不要
  • 権限漏れがない(IAM が一元管理)
  • 誰がいつ何をしたか CloudWatch Logs で全記録
  • 間違った実装でも AWS 側で防ぐ(多層防御)

※厳密なビジネスロジック的なACL制御は別にしておきます。間違って入ったユーザがAWSの他のリソースへのアクセス自体を制御できるってとても便利だと思いませんか


Part 1: AWS Cognito + IAM PassRole で理解する認可設計

📚 基礎から応用まで

🎯 3ステップで理解する全体像

ユーザー認証 → ロール取得 → リソースアクセス
(Cognito)    (IAM)      (PassRole)

Cognito で「あなたは誰か」を確認 → IAM ロールで「何ができるか」を決定


1. 通常の認可(ロールなし)

[API Gateway] → Lambda → [問題] 
               ↓
               DB接続に固定の認証情報を埋め込み
               ↓
               全ユーザーが同じ権限

問題点:

  • 誰がどこにアクセスしたかトレースできない
  • 権限制御が粗い

2. Cognito + PassRole で変わること

[ユーザー]
   ↓
[Cognito認証] → 一意の「Identity ID」を発行
   ↓
[IAM ロール取得] ← ここが PassRole
   ↓
[AWS リソース] → ロール情報付きでアクセス
   ↓
[監査ログ] → 「ユーザーX が DB の行Y を読んだ」が記録される

3. PassRole の実装フロー

ステップA: Cognito Identity Pool を作成

Cognito Identity Pool
  ├─ 認証ユーザー用ロール(Authenticated Role)
  └─ 未認証ユーザー用ロール(Unauthenticated Role)

ステップB: IAM ロールに権限を定義

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:Query"
      ],
      "Resource": "arn:aws:dynamodb:ap-northeast-1:*:table/UserData",
      "Condition": {
        "StringEquals": {
          "dynamodb:LeadingKeys": "${aws:username}"
        }
      }
    }
  ]
}

ポイント: ${aws:username} で「その人のデータだけ」に制限

ステップC: Lambda(またはアプリ)が ロール情報を使用

import boto3
import json
from aws_lambda_powertools import logger

logger = logger.Logger()

def lambda_handler(event, context):
    # Cognito から発行されたトークンを取得
    id_token = event['headers']['Authorization']
    
    # 認証済みユーザー用ロールを AssumeRole
    cognito_client = boto3.client('cognito-identity')
    
    credentials = cognito_client.get_credentials_for_identity(
        IdentityId=event['requestContext']['authorizer']['claims']['sub']
    )
    
    # 一時的な認証情報でDynamoDBアクセス
    dynamodb = boto3.resource(
        'dynamodb',
        aws_access_key_id=credentials['Credentials']['AccessKeyId'],
        aws_secret_access_key=credentials['Credentials']['SecretKey'],
        aws_session_token=credentials['Credentials']['SessionToken']
    )
    
    # ロール権限の範囲でのみアクセス可能
    table = dynamodb.Table('UserData')
    response = table.get_item(Key={'userId': event['userId']})
    
    logger.info(f"User {event['userId']} accessed their data")
    return {
        'statusCode': 200,
        'body': json.dumps(response['Item'])
    }

4. PassRole とは何か?

PassRole = 「ロールを他のAWSサービスに使わせる権限」

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iam:PassRole",
      "Resource": "arn:aws:iam::*:role/CognitoAuthRole"
    }
  ]
}

これがないと:

[Cognito] → ロール作成 ✓
         → Lambda に使わせる ✗ (Permission Denied)

5. CloudFormation で一気に構築

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Cognito + IAM PassRole 認可設計'

Resources:
  # Cognito Identity Pool
  IdentityPool:
    Type: AWS::Cognito::IdentityPool
    Properties:
      IdentityPoolName: MyAuthPool
      AllowUnauthenticatedIdentities: false
      CognitoIdentityProviders:
        - ClientId: !Ref UserPoolClient
          ProviderName: !GetAtt UserPool.ProviderName

  # 認証ユーザー用IAMロール
  AuthenticatedRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Federated: cognito-identity.amazonaws.com
            Action: sts:AssumeRoleWithWebIdentity
            Condition:
              StringEquals:
                cognito-identity.amazonaws.com:aud: !Ref IdentityPool
              ForAllValues:StringLike:
                cognito-identity.amazonaws.com:sub_id: authenticated
      Policies:
        - PolicyName: DynamoDBUserAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:GetItem
                  - dynamodb:Query
                Resource: arn:aws:dynamodb:ap-northeast-1:*:table/UserData
                Condition:
                  StringEquals:
                    dynamodb:LeadingKeys: ${aws:username}

  # ロール と Identity Pool を関連付け
  IdentityPoolRoleAttachment:
    Type: AWS::Cognito::IdentityPoolRoleAttachment
    Properties:
      IdentityPoolId: !Ref IdentityPool
      Roles:
        authenticated: !GetAtt AuthenticatedRole.Arn

6. PassRole 利点まとめ

項目 利点
監査 誰がいつ何をしたかCloudWatch Logsに記録
最小権限 ユーザーごと、テーブルごと、行ごとに制御可能
秘密管理 認証情報をアプリに埋め込まない
一時的 認証情報は1時間程度で自動失効
OpenSearchも同じ SigV4署名でOpenSearchもPassRoleで制御可能

7. よくある質問

Q: PassRole がないとどうなる?
A: Cognito が Identity を作っても、そのロールをLambdaが使えず、AccessDenied エラー

Q: ユーザーごとに違うロール使いたい場合は?
A: Cognito User Pool の custom:role 属性で ロール名を管理し、Lambda側で動的に AssumeRole

Q: 既存アプリに導入するコストは?
A: CloudFormation テンプレートで15分。あとはアプリ側で AssumeRole の1行追加するだけ



Part 2: API Gateway Authorizer による実装簡素化

🚀 認可処理を API Gateway で一元化

🎯 API Gateway Authorizer による認可フロー

[API Request]
    ↓
[API Gateway Authorizer Lambda] ← トークン検証 & ロール取得を一元化
    ↓ (Context に認可情報を埋め込む)
[ビジネスロジック Lambda] ← すでにロール情報がある状態で実行
    ↓
[DynamoDB] ← Lambda実行ロールで自動アクセス

つまり: ビジネスロジック Lambda は「認可なんて知らない」状態で動作


1. API Gateway Authorizer Lambda(認可処理)

import json
import boto3
from jose import jwt
import time

# Cognito 設定
COGNITO_REGION = 'ap-northeast-1'
USER_POOL_ID = 'ap-northeast-1_xxxxx'
COGNITO_CLIENT_ID = 'xxxxx'

cognito = boto3.client('cognito-idp', region_name=COGNITO_REGION)

def lambda_handler(event, context):
    """
    API Gateway からトークンを受け取り、
    検証 → Context にロール情報を埋め込んで返す
    """
    
    # API Gateway から Authorization ヘッダーを取得
    token = event['authorizationToken']
    
    try:
        # トークンを検証
        payload = jwt.get_unverified_claims(token)
        user_id = payload['sub']
        
        # 検証OKなら、認可情報をContext に埋め込む
        return {
            'principalId': user_id,
            'policyDocument': {
                'Version': '2012-10-17',
                'Statement': [
                    {
                        'Action': 'execute-api:Invoke',
                        'Effect': 'Allow',
                        'Resource': event['methodArn']
                    }
                ]
            },
            # ← ここがポイント!
            'context': {
                'userId': user_id,
                'userRole': payload.get('custom:role', 'user'),
                'timestamp': str(int(time.time()))
            }
        }
    
    except Exception as e:
        print(f"Auth failed: {e}")
        raise Exception('Unauthorized')

2. ビジネスロジック Lambda(簡潔!)

import json
import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('UserData')

def lambda_handler(event, context):
    """
    API Gateway Authorizer が認可済みのリクエストのみ到達
    → トークン検証コード不要
    """
    
    # Authorizer Lambda から渡されたContext 情報を取得
    user_id = event['requestContext']['authorizer']['userId']
    user_role = event['requestContext']['authorizer']['userRole']
    
    # ビジネスロジックだけに集中
    response = table.get_item(
        Key={'userId': user_id}
    )
    
    return {
        'statusCode': 200,
        'body': json.dumps(response.get('Item', {}))
    }

比較:

従来(Authorizer なし) Authorizer あり
Lambda内でトークン取得 トークン取得なし
jwt検証コード 検証コード消える
AssumeRole呼び出し 呼び出しなし
コード行数:30行 コード行数:10行

3. CloudFormation で接続

Resources:
  # Authorizer Lambda
  AuthorizerFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: api-authorizer
      Runtime: python3.11
      Code:
        ZipFile: |
          # さっきのAuthorizer コード
      Role: !GetAtt AuthorizerRole.Arn

  # API Gateway に Authorizer をアタッチ
  ApiAuthorizer:
    Type: AWS::ApiGateway::Authorizer
    Properties:
      RestApiId: !Ref RestApi
      Name: CognitoAuthorizer
      Type: TOKEN
      IdentitySource: method.request.header.Authorization
      FunctionName: !GetAtt AuthorizerFunction.Arn

  # API リソース & メソッド
  ApiResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref RestApi
      ParentId: !GetAtt RestApi.RootResourceId
      PathPart: users

  ApiMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref RestApi
      ResourceId: !Ref ApiResource
      HttpMethod: GET
      AuthorizationType: CUSTOM
      AuthorizerId: !Ref ApiAuthorizer  # ← ここで Authorizer を指定
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${BusinessLogicFunction.Arn}/invocations'

4. 実行フロー図

[クライアント]
    ↓
GET /users
Authorization: Bearer eyJxxx...
    ↓
[API Gateway]
    ├─ Authorizer Lambda が割り込む
    │  ├─ トークン検証
    │  ├─ Context に userId, userRole を格納
    │  └─ Allow policy を返す
    ↓
[ビジネスロジック Lambda]
    ├─ event['requestContext']['authorizer']['userId'] で取得
    └─ DynamoDB Query
    ↓
[レスポンス]

5. さらに簡潔:Cognito User Pool Authorizer

ApiAuthorizer:
  Type: AWS::ApiGateway::Authorizer
  Properties:
    RestApiId: !Ref RestApi
    Name: CognitoAuthorizer
    Type: COGNITO_USER_POOLS  # ← カスタム Lambda ではなく Cognito 直接
    IdentitySource: method.request.header.Authorization
    ProviderARNs:
      - !GetAtt UserPool.Arn

この場合:

  • Authorizer Lambda さえも不要
  • Cognito User Pool が直接検証
  • Context には User Pool の属性が自動で渡される

6. パターン選択フローチャート

認可が必要か?
├─ Yes
│  ├─ 複雑な認可ロジック?
│  │  └─ Yes → Authorizer Lambda(カスタム)+ PassRole
│  └─ No (単にトークン検証だけ)
│     └─ Cognito User Pool Authorizer(最もシンプル)
└─ No
   └─ API Gateway にプロキシ設定


🎯 最終結論

AWS IAM の本質

「アプリケーションロジックと認可の分離」

これにより:

  1. 開発が簡単 - セキュリティ実装がない
  2. テストが簡単 - 権限チェック不要
  3. 保守が簡単 - ビジネスロジックだけを修正
  4. セキュアになる - 実装ミスがない

実装コスト比較

方式 実装時間 コード行数 認可管理 推奨度
PassRole のみ 1-2日 50+ 複雑
API Gateway Authorizer 1日 30 中程度 ⭐⭐⭐

次のステップ

API Gateway Authorizer の習得後、さらに高度な認可設計として Bedrock Agents による自動化を検討してください。別途記事で詳しく説明しています。


最後に

「0を1にする」をミッションとするなら、AWS IAM の仕組みを理解して活用することが、もっともシンプルで、セキュアで、スケーラブルな設計につながります。

これがAWSのみんなに知って欲しい「Building and Block」という考え方。プログラマはさまざまなライブラリをつなぎ、組み合わせいろいろ考慮して、挙げ句の果てに徹夜までして頑張って書くことがあります。ただ、この思想を覚えると 「プログラムをかくべきところ」と「AWSの機能を利用して作り込まないことを選択する」っていうことができます。 ちょっと昔のじぶんなら、プログラマとしてなんでもかんでもかけちゃうからそんなもの使わないで書いちまおうという考えでした。ただし、(そのあと、セキュリティの観点、セキュリティおじさんのマウントとマサカリアタック、アプリケーションサービス感の連携、ログの設計、ACLとは、おーつきくん見積もり終わった?えっ?高くない?)なんてことから解放されたのが最も言いたいことでした。
こういうのって、横道外れるけどAWS Practitionarの認定資格を取得する時に設計思想系をまなぶのでとても大事な認定試験だなというのも気付かされます。

アプリエンジニアに「AWS って何が便利?」と聞かれたら、この資料を見せてあげてください。

きっと、AWS の本当の価値を理解してくれます。


参考リソース

1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?