CloudFront + Lambda@Edge + SecretsManagerでBasic認証

こんにちは。katoです。

 

今回はCloudFront + Lambda@EdegのBasic認証の仕組みにSecretsManagerを組み込んでいきたいと思います。

概要

CloudFront + Lambda@EdgeでのBasic認証は、多くの方が利用されたことがあるのではと思われますが、Lambda@Edgeならではの、

 

環境変数が利用できない(認証情報がコードにべた書き)

・LATESTバージョンをCloudFrontに設定できない(更新の手間がかかる)

 

などの部分で不便に思ったり、運用の手間がかかったりしたことがあるかと思われます。

 

SecretsManagerと連携することで、上記の問題を解決し、認証情報もセキュアに管理することが可能となります。

 

なお、今回はLambda@Edgeの関数は、Python(3.7)にて作成していきます。

CloudFront + Lambda@EdegでのBasic認証を設定する場合、広く利用されているのはnode.jsのおなじみの関数かと思われますが、個人的に使い慣れたPythonを今回は利用します。

 

手順

手順といってもCloudFrontやLambdaの設定は一般的なものなので今回はご説明は省きます。

今回はLambda関数のご説明とSecretsManagerの設定を簡単に記載させていただきます。

 

いきなりですがSecretsManagerの設定をご説明していきます。

 

SecretsManagerのサービスページにアクセスし、新しいシークレットの作成を行います。

 

シークレットの種類の選択にて、「その他のシークレット」を選択します。

 

シークレットキーに「Password」を入力し、値にBasic認証用のパスワードを入力します。

 

今回はデフォルトの暗号化キーを利用しておりますが、暗号化のレベルを高めたい場合には、適宜KMSキーの指定を行ってください。

 

後はシークレットの名前を指定して、シークレットを発行するのみなのですが、シークレットの名前は以下のフォーマットに則るようにしてください。

BasicAuth/<Basic認証用ユーザ名>

シークレットの名前を基に、Lambdaから認証情報を取得しておりますので、シークレットの名前の指定は上記フォーマットに則り、Basic認証用のユーザ名を含む形で指定してください。

なお、シークレットの自動ローテーションは今回は無効にしてります。

以上でSecretsManagerの設定は完了となります。

Basic認証用のユーザを複数登録したい場合には、それぞれ上記の内容に沿って、シークレットの作成を行ってください。

続いて、Lambda関数の設定を行います。

 

下記のコードにてLambdaを作成します。

import json
import boto3
import base64

secret = boto3.client('secretsmanager', region_name='us-east-1')

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    headers = request['headers']
    
    error_response = {
        'status': '401',
        'statusDescription': 'Unauthorized',
        'body': 'Authentication Failed',
        'headers': {
            'www-authenticate': [
                {
                    'key': 'WWW-Authenticate',
                    'value':'Basic realm=&quot;Basic Authentication&quot;'
                }
            ]
        }
    }

    if (headers.get('authorization') != None):
        auth_values = headers['authorization'][0]['value'].split(&quot; &quot;)
        auths = base64.b64decode(auth_values[1]).decode().split(&quot;:&quot;)
        user = auths[0]
        password = auths[1]
        secretID = &quot;BasicAuth/&quot; + str(user)
        
        try:
            get_secret = secret.get_secret_value(
                SecretId=secretID
            )
            if ('SecretString' in get_secret):
                secret_password = json.loads(get_secret['SecretString'])
                if (secret_password['Password'] == password):
                    return request
                else:
                    return error_response
            else:
                return error_response
        except:
            return error_response
    elif (headers.get('authorization') == None):
        return error_response

コードの内容としては、authorizationとして入力されたユーザ名を基に、SecretsManagerからシークレット値を取得し、入力パスワードとシークレット値の比較を行っております。

処理自体は普通のBasic認証の処理で、判定部分にSecretsManagerを組み込んだのみとなります。

Lambdaを作成したらCloudFrontにLambda@Edgeとして関連付けて完了となります。


動作確認

CloudFrontのページにアクセスし、Basic認証の画面が表示されることを確認します。

SecretsManagerに設定したユーザ名、パスワードでログインできれば正常な動作となります。

※認証失敗時に401としたかったのでうまくいきませんでした(401は表示されず認証が繰り返し求められる)。


まとめ

CloudFront + Lambda@EdgeのBasic認証の仕組みに、SecretsManagerを組み込む方法をご紹介いたしました。

DynamoDB等を利用してLambda外部で認証情報を保持するなどの方法も可能ですが、SecretsManagerを利用すれば、より簡単でセキュアに認証情報を管理することが可能となります。

ユーザ追加/削除、パスワードの変更といった作業も、Lambda関数を修正することなく、SecretsManager側の設定で完結致しますので、運用の手間も大幅に削減可能となっております。

今回は単純なBasic認証の仕組みをご紹介いたしましたが、シークレットキーにIPAddressなどを追加すれば、IPアドレスでのアクセス元制御なども併せて設定することが可能となります(もちろん認証なしでIP判定のみなども可能です)。

複数のシークレットキーをひとつのシークレットに設定することが可能なので、環境変数の代わりとして利用するようなことも可能なのかなと思ったりもしました。

個人的にあまり触れてこなかったSecretsManagerですが、簡単に利用できて認証情報を安全に管理することが可能なので、今後様々なシステムやAWSサービスで導入できそう(したほうが良さそう)という風に感じました。

便利なサービスとなっているので、お試しになってみてはいかがでしょうか。

 

 

 

このブログの著者

 

 

アプリケーション開発バナー

AWS相談会バナー