こんにちは!フリージアの東山です。
今回は、API Gateway Lambdaオーソライザーの機能を使って、API Gatewayで公開しているエンドポイントにBasic認証をかける方法を説明しようと思います。
API Gatewayは、AWS (Amazon Web Service) の提供しているサービスの1つで、「あらゆる規模のRESTおよびWebSocket APIを作成、公開、保守、モニタリング、および保護する」ことが出来ます。
詳細はAmazon API Gateway とは?をご覧ください。
API Gatewayの機能の1つに、Lambda関数を使用してAPIへのアクセスを制御するLambdaオーソライザーがあります。
Lambdaオーソライザーでのアクセス制御フローは以下になります。
- クライアントがAPI Gatewayのメソッドを呼び出す
- API Gatewayは、メソッドに対してLambdaオーソライザーが設定されているかどうかを確認し、設定されている場合は、発信者IDを入力としてLambda関数に渡す
- Lambda関数は、発信者IDをBearerトークンまたはリクエストパラメータとして受け取り、IAMポリシーを出力として返却する
- API Gatewayは、受け取ったIAMポリシーを評価し、その結果、アクセスが許可されている場合はメソッドを実行する
今回は、この機構を利用して、Basic認証を実装しようと思います。
テスト用エンドポイントの作成と公開
まず始めに、API Gatewayにテスト用のエンドポイントを作成・公開しましょう。
テスト用エンドポイントの作成
テスト用のアプリケーションは、Node.jsのExpressフレームワークで作成します。
本記事の趣旨とはズレますので、説明は割愛します。Hello World の例 と同じ内容となっていますので、気になる方はご覧ください。
# プロジェクトディレクトリの作成
$ mkdir lambda-authorizer-serverless
$ cd lambda-authorizer-serverless
$ yarn init
$ yarn add express
$ vi app.js
app.jsは、以下の内容で作成します。
// app.js
const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(3000, () => console.log('Example app listening on port 3000!'))
このアプリケーションは、サーバを始動して、3000番ポートで接続をlistenします。
以下のコマンドでアプリケーションを実行後、「http://localhost:3000/」にアクセスし、「Hello World!」が表示されることを確認して下さい。
$ node app.js
テスト用エンドポイントの公開
先程作ったExpressアプリケーションを、早速公開しましょう。
API Gateway × Lambdaでの動作環境を、serverlessフレームワークで構築します。
まずは、必要なパッケージをインストールして下さい。
$ yarn add -D serverless
$ yarn add aws-serverless-express
次に、serverlessの設定を行います。
プロジェクトディレクトリ配下に「serverless.yml」を以下の内容で作成して下さい。
# serverless.yml
service: lambda-authorizer-serverless
provider:
name: aws
runtime: nodejs10.x
region: ap-northeast-1
stg: ${opt:stage}
functions:
app:
handler: app.handler
events:
- http:
path: '/'
method: any
- http:
path: '{proxy+}'
method: any
今回は、serverlessの標準設定での構成となっています。
serviceセクションはサービスの定義、providerセクションは動作環境の指定、functionsセクションはLambda関数 (app) のhandlerメソッドの指定およびAPI Gatewayの設定をそれぞれ行っています。
次に、Lambda関数を呼び出した際に実行される、handlerメソッドを作成します。
app.jsを次のように修正して下さい。
// app.js
const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Hello World!'))
const awsServerlessExpress = require('aws-serverless-express')
const server = awsServerlessExpress.createServer(app)
exports.handler = (event, context) => { awsServerlessExpress.proxy(server, event, context) }
ExpressアプリケーションをAPI Gateway × Lambdaの環境で動かすために、Amazon公式パッケージであるaws-serverless-expressを利用しています。
Lambda関数のhandlerメソッドが実行された際に、awsServerlessExpressがExpressアプリケーションにリクエストをプロキシします。
以上で、serverlessの設定は完了です。
最後に、以下のコマンドを実行し、AWSへアプリケーションをデプロイして下さい。
(※ AWSへの登録や、AWS アクセスキーの取得がまだの方は、AWS アカウントとアクセスキーを実施して下さい)
# {YOUR-XXX}は適宜置き換えて下さい
$ AWS_ACCESS_KEY_ID={YOUR-KEY} AWS_SECRET_ACCESS_KEY={YOUR-SECRET-KEY} yarn sls deploy --stage prod
yarn run v1.17.3
$ /path/to/lambda-authorizer-serverless/node_modules/.bin/sls deploy --stage prod
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service lambda-authorizer-serverless.zip file to S3 (802.01 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.......................
Serverless: Stack update finished...
Service Information
service: lambda-authorizer-serverless
stage: prod
region: ap-northeast-1
stack: lambda-authorizer-serverless-prod
resources: 11
api keys:
None
endpoints:
ANY - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/
ANY - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/{proxy+}
functions:
app: lambda-authorizer-serverless-prod-app
layers:
None
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.
✨ Done in 43.60s.
ログで出力される、
https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/
にアクセスし、「Hello World!」が表示されれば、テスト用エンドポイントの公開は完了です。
LambdaオーソライザーでのBasic認証の実装
長くなりましたが、いよいよ本題のLambdaオーソライザーでのBasic認証の実装です。
実装手順は次のようになります。
- API Gatewayのメソッドに対するLambdaオーソライザーの設定
- Lambdaオーソライザーで用いるLambda関数の実装
- AWSへのデプロイおよびBasic認証の検証
では早速、実装していきましょう!
API Gatewayのメソッドに対するLambdaオーソライザーの設定
AWSマネジメントコンソールにアクセスして〜、ではなく、serverlessで設定します。
serverless.ymlに対し、次の設定を追記して下さい。
# serverless.yml
service: # 省略
provider: # 省略
custom:
definitions:
authorizer:
# Lambda関数名
name: authorizer
# キャッシュ時間
resultTtlInSeconds: 0
# Lambda関数に渡すヘッダー名
identitySource: method.request.header.Authorization
# Lambdaイベントペイロード
type: request
functions:
app:
handler: app.handler
events:
- http:
path: '/'
method: any
# Lambdaオーソライザーの設定
authorizer: ${self:custom.definitions.authorizer}
- http:
path: '{proxy+}'
method: any
# Lambdaオーソライザーの設定
authorizer: ${self:custom.definitions.authorizer}
# Lambda関数 (authorizer) のhandlerメソッドの指定
authorizer:
handler: authorizer.handler
resources:
Resources:
GatewayResponse:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.WWW-Authenticate: "'Basic'"
ResponseType: UNAUTHORIZED
RestApiId:
Ref: 'ApiGatewayRestApi'
StatusCode: '401'
さっきまで無かったセクションとして、customとresourcesを追加しました。
customセクションは変数定義で用いられるセクションです。
例えば、Serverless Pluginsを導入した場合、各プラグインの設定変数などはcustomセクションで定義します。
今回は、認証に係る設定を独自変数としてcustomセクションに定義しています。
resourcesセクションは、AWSリソースの設定を行なうセクションです。
今回は、Lambdaオーソライザーが401エラーを返却した場合、「WWW-Authenticate: 'Basic'」ヘッダーをクライアントに返すようAPI Gatewayのゲートウェイレスポンスを設定しています。
また、functionsセクションには、API Gatewayのメソッドに対するLambdaオーソライザーの設定とLambda関数 (authorizer) のhandlerメソッドの指定を追記しています。
以上で、Lambdaオーソライザーの設定は完了です。
Lambdaオーソライザーで用いるLambda関数の実装
Lambda関数 (authorizer) を呼び出した際に実行される、handlerメソッドを作成します。
プロジェクトルート配下に、authorizer.jsを次の内容で作成して下さい。
// authorizer.js
// Authorizationヘッダーの照合
module.exports.handler = (event, context, callback) => {
const authorizationHeader = event.headers.Authorization
if (!authorizationHeader) {
return callback('Unauthorized')
}
const encodedCredentials = authorizationHeader.split(' ')[1]
const [ username, password ] = (Buffer.from(encodedCredentials, 'base64')).toString().split(':')
if (!(username === 'admin' && password === 'password')) {
return callback('Unauthorized')
}
const authResponse = buildPolicy(event, username)
callback(null, authResponse)
}
// IAMポリシーの作成
function buildPolicy (event, principalId) {
const [ identifier, service, action, region, accountId, apiGatewayArn ] = event.methodArn.split(':')
const [ apiId, stage, ..._rest ] = apiGatewayArn.split('/')
return {
principalId,
policyDocument: {
Version: '2012-10-17',
Statement: [
{
// 実行アクション
Action: 'execute-api:Invoke',
// 許可
Effect: 'Allow',
// API Gatewayエンドポイント
Resource: [`${identifier}:${service}:${action}:${region}:${accountId}:${apiId}/${stage}/*/*`]
}
]
}
}
}
ここでは大きく分けて2つのことを行なっています。
- Authorizationヘッダーからユーザ名、パスワードを取り出し照合する
- IAMポリシーを作成する
Authorizationヘッダーの照合に成功すれば作成したIAMポリシーを、失敗すればUnAuthorizedエラーを返却します。
IAMポリシーが返却された場合、API Gatewayはポリシーを評価し、メソッドの実行を実行します。
AWSへのデプロイおよびBasic認証の検証
では、AWSへアプリケーションをデプロイし、Basic認証が有効になっていることを確認しましょう!
先程同様、serverless deployコマンドを実行して下さい。
# {YOUR-XXX}は適宜置き換えて下さい
$ AWS_ACCESS_KEY_ID={YOUR-KEY} AWS_SECRET_ACCESS_KEY={YOUR-SECRET-KEY} yarn sls deploy --stage prod
...
API Gatewayエンドポイントにアクセスし、Basic認証のポップアップが表示されれば成功です。
なお、ユーザ名、パスワードはauthorizer.jsで指定したものになります。
以上で、検証終了です。
ここまでのソースコードはfreegian/lambda-authorizer-serverlessにプッシュしていますので、参照下さい。
最後に
今回は、API Gateway Lambdaオーソライザーの機能を使って、API Gatewayで公開しているエンドポイントにBasic認証をかけてみました。
また、AWSの環境構築をserverlessフレームワークで実践しました。
API Gateway Lambdaオーソライザーを使えば、OAuthやBasic認証のようなBearerトークンを利用する認可方式だけでなく、リクエストパラメーターを使用するカスタム認証方式も簡単に実装できます。
IP制限を用いない社内テスト環境の構築や、外部クライアントに対する仮想環境の提供など、認証認可が発生する様々なシーンで活用できる機能だと思いますので、その際にはぜひ検討してみて下さい。
本記事を見て、ご意見やご指摘等ございましたら、「[email protected]」まで是非ご連絡ください。