Edited at

Serverless framework + API Gateway + Lambda で `--stage` の値を判別してステージング環境に基本認証

Serverless framework を使うと sls deploy --stage production とか sls deploy --stage staging みたいな感じで、本番とステージングを切り替えてデプロイするが、この --stage の値を判別してステージングへのデプロイだけ基本認証をかけるやり方。


serverless.yml


serverless.yml

functions:

app:
handler: handler.app
events: ${self:custom.events.${opt:stage,'staging'}}
authorizer:
handler: lib/authorizer.handler

resources:
Resources:
GatewayResponse:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.WWW-Authenticate: "'Basic'"
ResponseType: UNAUTHORIZED
RestApiId:
Ref: 'ApiGatewayRestApi'
StatusCode: '401'

custom:
events:
production:
- http:
path: /
method: get
- http:
path: /{any+}
method: get
staging:
- http:
path: /
method: get
authorizer:
name: authorizer
resultTtlInSeconds: 0
identitySource: method.request.header.Authorization
type: request
- http:
path: /{any+}
method: get
authorizer:
name: authorizer
resultTtlInSeconds: 0
identitySource: method.request.header.Authorization
type: request




  • functions.app.events の値 ${self:custom.events.${opt:stage,'staging'}} は、下にある custom.events.* の値を読み込むための記述。


  • functions.authorizer には認証用のハンドラを指定する。ハンドラの処理は後述。


  • resources.Resources.GatewayResponse 以下の記述は基本認証を行うためのレスポンスヘッダーを返すための設定。


  • custom 以下の記述は URL の設定及び基本認証を行うためのカスタムオーソライザーの設定。


認証用ハンドラ

'use strict';

exports.handler = (event, context, callback) => {
const authorizationHeader = event.headers.Authorization

if (!authorizationHeader) return callback('Unauthorized')

const encodedCreds = authorizationHeader.split(' ')[1]
const plainCreds = (new Buffer(encodedCreds, 'base64')).toString().split(':')
const username = plainCreds[0]
const password = plainCreds[1]

if (!(username === 'admin' && password === 'secret')) return callback('Unauthorized')

const authResponse = buildAllowAllPolicy(event, username);
callback(null, authResponse)
}

const buildAllowAllPolicy = (event, principalId) => {
const tmp = event.methodArn.split(':')
const apiGatewayArnTmp = tmp[5].split('/')
const awsAccountId = tmp[4]
const awsRegion = tmp[3]
const restApiId = apiGatewayArnTmp[0]
const stage = apiGatewayArnTmp[1]
const apiArn = `arn:aws:execute-api:${awsRegion}:${awsAccountId}:${restApiId}/${stage}/*/*`
const policy = {
principalId: principalId,
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'execute-api:Invoke',
Effect: 'Allow',
Resource: [apiArn]
}
]
}
}
return policy
}

ソースコードは以下の記事を参考にしました。

https://medium.com/@Da_vidgf/http-basic-auth-with-api-gateway-and-serverless-5ae14ad0a270

ユーザー名は admin、パスワードは secret で、ソースコード内に決め打ちで入ってますので注意。