はじめに
社内で社員や色んなサービスがTLSクライアント証明書を使います。社内のアイデンティティ管理システムとAWSに関係になりたいから、このGatewayのシステムを開発しました。
TLSクライアント証明書は不要。 SAML、OAuth2などがこの認証パタンを使う可能性です。
信用モデル
Graphvizのソース
digraph trust {
graph[style="filled" color="#ffffff" fontcolor="#333333" fontname="sans" fontsize="16" rankdir="LR"];
node[shape="rect" color="#333333" fontcolor="#333333" fillcolor="#f8f8f8" style="filled" fontname="sans"];
edge[fontname="sans" color="#79b74a" fontcolor="#333333" fontsize="12"];
engineer[label="エンジニア"]
gateway[label="Gateway"];
ca[label="社内のCA"];
iam[label="AWS IAM"]
engineer -> gateway[label="TLS 相互認証" dir="both"];
gateway -> ca[label="CRL/OCSP"];
gateway -> iam[color="#eea236" label="sts:assumeRole"];
iam -> gateway[color="#eea236" label="認証情報セット"];
}
このGatewayの責任は認証、そしてIAMは認証と認可です。二つのIAMロールがある:Gatewayのインスタンスプロファイルとエンジャニヤの個人的なロール。GatewayがTLSクライアント証明書のSubjectを読むからデータベースが不要です。絶対にGatewayがエンジニアの認証情報セットを保存してません。
アーキテクチャ
Graphvizのソース
digraph architecture {
graph[style="filled" color="#333333" fillcolor="#ffffff" style="filled" fontcolor="#333333" fontname="sans" fontsize="16" rankdir="LR"];
node[shape="rect" color="#333333" fontcolor="#333333" fillcolor="#f8f8f8" style="filled" fontname="sans"];
edge[fontname="sans" color="#79b74a" fontcolor="#333333" fontsize="12"];
engineer[label="エンジニア" color="#ffffff" fillcolor="#ffffff"];
iam[label="IAM" color="#ffffff" fillcolor="#ffffff"];
subgraph cluster0{
graph[label="VPC" labelloc="b" style="dashed"];
elb[label="ELB" fixedsize="true" height="2" width="0.5"];
subgraph cluster1 {
graph[label="EC2" labelloc="t" labeljust="r" style="filled"];
apache[label="Apache"];
gateway[label="Gateway"];
}
nat[label="NAT"];
}
engineer -> elb[label="https://gateway/"];
elb -> apache[];
apache -> gateway[color="#eea236"];
gateway -> nat[dir="none"];
nat -> iam[];
}
API GatewayがTLSクライアント証明書が使わないからELBとEC2は必要です。そしてTLSのセッションがエンジニアからApacheまで、ELBのリスナーはTCP:TCPです。Proxy Protocol のサポートとApacheのmod_proxy_protocolを設定しない限り、クライアントのIPアドレスは受け取りません。
ログインのシーケンス
PlantUMLのソース
@startuml
skinparam monochrome true
skinparam shadowing false
エンジニア -> Gateway: /login?account=1234
Gateway -> Gateway: Validate Certificate \n (CRL/OCSPなど)
Gateway -> STS: assumeRole
STS -> Gateway: 認証情報セット
Gateway -> エンジニア: 302 https://signin.aws.amazon.com/federation?
エンジニア -> "フェデレーションエンドポイント": GET /federation
"フェデレーションエンドポイント" -> エンジニア: 302 https://console.aws.amazon.com/
エンジニア -> コンソール: GET /
コンソール -> エンジニア: 200
@enduml
上の図は、最後のリクエストから追加のリクエストがありますから完成しません。
開発のメモ
リバースプロキシサーバーの設定
Listen 443
<VirtualHost *:443>
SSLEngine On
SSLVerifyClient require
SSLVerifyDepth 2
# 他の設定はこちら
RequestHeader unset client-ssl-certificate
RequestHeader set client-ssl-certificate "/%{SSL_CLIENT_S_DN_O}s/%{SSL_CLIENT_S_DN_OU}s/%{SSL_CLIENT_S_DN_Email}s/"
ProxyPass / https://localhost:8080
ProxyPassReverse / https://localhost:8080
</VirtualHost>
そのRequestHeaderのunsetの前にsetはヘッダインジェクションの対策です。
Gatewayの例
'use strict';
var express = require('express'),
AWS = require('aws-sdk');
var app = express();
app.get('/login', function(req, res){
var params = {
'RoleArn': 'arn:aws:iam::' + req.query.account + ':role/' + req.header('x-ssl-certificate'),
'RoleSessionName': 'Gateway'
};
sts.assumeRole(params, function(err, data) {
if(err){
res.status(400).send('Access Denied.').end();
} else {
var signInToken = encodeURIComponent(JSON.stringify({
'sessionId': data.credentials.accessKeyId,
'sessionKey': data.credentials.secretAccessKey,
'sessionToken': data.credentials.sessionToken
}));
var redirectUrl = [
'https://signin.aws.amazon.com/federation',
'?Action=login',
'&Issuer=' , encodeURIComponent('https://gateway/'),
'&Destination=', encodeURIComponent('https://console.aws.amazon.com/'),
'&SigninToken=', signInToken].join('');
res.redirect(302, redirectUrl).end();
}
});
});
app.listen(8080);
エンジニアのロール
AWSのアカウントで、全てのエンジニアのために、新しいのロールを設定するべきです。
CloudFormation例
{
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": "pnktsrmn",
"Path": "/Company_CA/Department_OU/pnktsrmn@himitsukaisha.com/",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::1234567890:role/gateway-role"
},
"Action": "sts:AssumeRole"
}
]
}
}
}
PrincipalはGatewayのインスタンスプロファイルのARN。パスはTLSクライアント証明書のSubjectです。