3
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?

Amazon CloudFrontとAWS Lambda@Edgeを用いてIPアドレスに基づく認証を構築してみた

Posted at

1. 概要

インターネット経由でのAmazon Virtual Private Cloud (VPC)内のリソースへのアクセスについて、特定のIPアドレスからのアクセスは認証無しで許可し、それ以外のIPアドレスからのアクセスには認証を要求したいというケースがあります。

その実現手段の一つとして、Amazon CloudFrontAWS Lambda@Edgeを用いて、特定のIPアドレス以外からのアクセスに対してAmazon Cognito認証を要求する方法を試してみました。

2. 試作した構成

試作した構成を下記に示します。

architecture.png

図1:CloudFrontとLambda@Edgeを用いてCognito認証を行う構成

上記の構成に示されているコンポーネントを説明します。

  • CloudFront: CloudFrontはエッジロケーションを用いるコンテンツ配信サービスです。ここではCloudFrontのオリジンはApplication Load Balancers (ALB)とします。
  • Lambda@Edge: Lambda@Edgeは、CloudFrontのエッジロケーションにおいて、Lambdaで定義した任意のコードを実⾏できる機能です。ここでは、リクエスト元のIPアドレスをが許可されたものかを確認し、それ以外のIPアドレスに対して、Amazon Cognitoから提供されるJWTトークンを検証することにより認証を処理するコードを実行します。
  • Amazon S3: Lambda@Edge関数のソースコードはS3バケットに保存されます。
  • AWS Systems Maneger (SSM): 認証無しでアクセスを許可するIPアドレスのリストをParameter Storeに保存します。
  • Amazon Cognito: ユーザープールを作成し、ユーザー認証を管理します。
  • Application LoadBalancer(ALB): EC2へのリクエストの負荷分散に使用します。
  • Amazon EC2: ここではアクセス先のVPC内のリソースとしてEC2を使用し、nginxを実行します。他のサービスやアプリケーションでも構いません。

3. Lambda@Edgeを利用する理由

CloudFrontとLambda@Edgeを用いて認証処理を実装することには、以下のメリットが考えられます。

  • Lambda@EdgeによりAWSエッジロケーションで認証処理を実行するため、高いパフォーマンスを期待できる。
  • 不正なリクエストをエッジロケーションで拒否できるため、セキュリティの強化と、オリジンの負荷軽減ができる。
  • 様々なCloudFrontのオリジンに対して応用できる。VPC内のリソースへのアクセスだけでなく、S3の静的コンテンツへのアクセスへの認証にも利用可能。
  • 既存アプリケーションに変更を加えずに認証処理を追加することが可能。
  • Cognito認証以外の認証方法に変更することが容易。

一方で、通常のAWS Lambdaを利用して認証処理を実装する方が、開発・運用コストを抑えられることや、AWSリージョン内で実行される他サービスとの統合が容易になることも考えられます。

4. 構築手順

構築手順を説明します。

  1. VPCおよびVPC内のリソースを構築する
  2. Amazon Cognitoのユーザープールとユーザーを作成する
  3. Amazon CloudFrontを作成する
  4. Lambda@Edge関数を作成する
  5. 動作確認をする

ステップ1:VPCおよびVPC内のリソースを構築する

アクセス先のVPCおよびVPC内のリソースを作成します。VPC内のリソースの構成は自由です。上記構成では、EC2上でWebアプリケーションを実行して、ALBで負荷分散することを想定しています。

ステップ2:Amazon Cognitoのユーザープールとユーザーを作成する

Amazon Cognitoでユーザープールを作成した後、ユーザープールにユーザーを作成します。
まずはユーザー認証の動作確認に用いるメールアドレスとパスワードを設定しておきます。

ステップ3:CloudFrontを作成する

CloudFrontディストリビューションを作成します。CloudFrontのオリジンとして何を選ぶかで設定が変わります。今回の構成では、CloudFront経由でALBにアクセスするように設定します。
ALBはインターネット向けでも内部向けでも構いません。インターネット向けのALBを使用する場合には、CloudFrontがリクエストにカスタムHTTPヘッダーを追加するようにして、ALBでそのカスタムHTTPヘッダーが含まれるリクエストのみアクセスを許可するルールを設定します。

ステップ4:Lambda関数をデプロイしてCloudFrontと関連付ける

Lambda@Edge関数を作成してCloudFrontと関連付けを実施します。前のステップでCloudFrontを作成済みなので、Lambda@Edge関数を作成して、トリガーを設定します。
また、この構成では認証無しでアクセスを許可するIPアドレスのリストをパラメータとしてSSMに格納します。そのため、Lambda@Edge関数がS3に格納されるソースコードとSSMに格納したIPアドレスのリストにアクセスできるように、Lambda@Edge関数にIAMロールを与えます
参考として、IAMロールを作成するCloudFormationテンプレートの一部を掲載します。

  CognitoAuthorizerFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
                - edgelambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: CognitoAuthorizerFunctionPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 's3:GetObject'
                Resource: !Sub arn:aws:s3:::${EnvironmentName}-lambdacode/*
              - Effect: Allow
                Action:
                  - cognito-idp:AdminGetUser
                Resource: "*"
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
              - Effect: Allow
                Action:
                  - ssm:GetParameter
                Resource:
                  - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/myapp/allowed_ips'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

Lambda@Edgeに登録するコードは、Amazon CloudFrontとAWS Lambda@Edgeを使用した認証処理のAWSサンプルコードを参考にしてください。サンプルコードに、認証無しでアクセスを許可するIPアドレスのリストを取得して、リクエスト元IPアドレスが含まれているかをチェックする処理を追加します。追加するコードを示します。

const allowedIPsParameter = '/myapp/allowed_ips';

async function getAllowedIPs() {
  try {
    const data = await ssm.getParameter({
      Name: config.allowedIPsParameter,
      WithDecryption: true
    }).promise();
    return data.Parameter.Value.split(",").map(ip => ip.trim());
  } catch (error) {
    console.error('Error fetching allowed IPs from SSM:', error);
    return [];
  }
}

async function isIPAllowed(clientIP) {
  const allowedIPs = await getAllowedIPs();
  return allowedIPs.includes(clientIP);
}

exports.handler = (event, context, callback) => {
    const cfrequest = event.Records[0].cf.request;
    const headers = cfrequest.headers;
    console.log('getting started');
    console.log('USERPOOLID=' + USERPOOLID);
    console.log('region=' + region);
    console.log('pems=' + pems);
  
  const clientIP = cfrequest.clientIp;

  if (await isIPAllowed(clientIP)) {
    return cfrequest;
  }

  <以降はサンプルコード通りなので省略>
  
};

ステップ5:動作確認をする

動作確認のために、認証無しでアクセスを許可されたIPアドレスと許可されていないIPアドレスのそれぞれから、CloudFrontのURLにアクセスします。
許可されたIPアドレスに対してはCognito認証は実行されず、コンテンツに直接アクセスできることを確認します。
許可されていないIPアドレスに対してのみCognito認証が実行されることを確認します。

  1. クライアントのIPアドレスが許可されたIPリストに含まれている場合: クライアントは認証無しで、ウェブサイトに直接アクセスできます。

cognito-allowIP.png

  1. クライアントのIPアドレスが許可されたIPリストに含まれていない場合: クライアントはアクセス用ユーザー名とパスワードを求められます。

    • ユーザー名またはパスワードのいずれかが不正な場合: 401 Unauthorizedエラーが返却されます。

cognito-unallowIP.png

  • ユーザー名とパスワードが正確な場合: クライアントはウェブサイトに正常にアクセスできます。


    cognito-afterauthen.png

5. まとめ

アクセス元のIPアドレスで認証有無を選択する方法として、AWS CloudFrontとLambda@Edgeを用いて、特定のIPアドレス以外にはCognito認証を実施する方法を試しました。LambdaまたはLambda@Edgeを用いた認証の応用例としてもご参考になれば幸いです。

3
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
3
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?