はじめに
- Verified Accessはプライベートアプリケーション(LB等)に対するVPNレスでのアクセス制御を実施するためのAWSサービスです。
- OIDCのIdPと連携することで、認証ステータスやIdP側のユーザグループに応じた細かな認証認可を実装することも出来ます。
- OIDCのIdPとなるKeycloak及びAWS資源を実際に設定し、認証認可の動作確認をしてみます。(AWS資源設定にはAWS CDKを利用します。)
OIDC認証基盤設定
- 既存の構築済みKeycloakに対して、Verfied Accessと連携するための設定をしていきます。
本記事ではKeycloak実行環境構築の詳細は割愛します。
Client設定
- Keycloak管理コンソールの
Clients
から、OIDC Clientを作成します。- Valid redirect URIs
- https://Verified Accessで設定するカスタムドメイン/oauth2/idpresponse
- Valid redirect URIs
- Keycloak管理コンソールの
Clients scopes
から、client scopeを作成します。- Mapper type
- Group Membership
- Name
- groups
- Token Claim Name
- groups
- Mapper type
ユーザ設定
- Keycloak管理コンソールの
Groups
から、動作確認用グループを作成します。 - Keycloak管理コンソールの
Users
から、動作確認用ユーザを作成します。 - 動作確認用ユーザの詳細画面にて
Groups
タブを選択し、動作確認用グループをアタッチします。 - Keycloak管理コンソールの
Clients
から、上記で作成したOIDC Clientを選択してClient scopes
タブを選択し、Evaluate
を選択します。動作確認用ユーザを選択すると、各種トークンやUserInfoエンドポイント提供情報にgroups情報が内包されていることが分かります。
OIDC情報確認
- Keycloak管理コンソールの
Clients
から、先ほど作成したClientを選択し、clientIdを控えておきます。 -
Credentials
タブを選択し、clientSecretを控えておきます。 - Keycloak管理コンソールの
Realm settings
から、OpenID Endpoint Configuration
にアクセスします。 - 遷移先のOpenID Configurationエンドポイントで、issuerと各種エンドポイント情報を控えておきます。
- authorizationEndpoint
- issuer
- tokenEndpoint
- userInfoEndpoint
ネットワーク設定
- ここからはCDKを用いて、AWS資源を設定していきます。
- BleaのNetworkingコンストラクトを用いて、VPC資源を設定します。
バックエンドアプリ設定
- 内部向けALB及びLambda関数から構成されるバックエンドアプリ資源を設定します。
- 検証用のため、内部向けALBのプロトコルはHTTPにしています。
// ---- Lambda
const itemLambda = new lambda.Function(this, "ItemLambda", {
functionName: `${props.prefix}-lambda-backend-item`,
runtime: lambda.Runtime.PYTHON_3_12,
code: lambda.Code.fromAsset('lambda/python/backend'),
handler: 'test.lambda_handler',
memorySize: 256,
tracing: lambda.Tracing.ACTIVE,
insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_98_0,
vpc: props.vpc,
vpcSubnets: props.vpc.selectSubnets({
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS
})
})
// ---- ALB
// Security Group
const backendAlbSecurityGroup = new ec2.SecurityGroup(this, "BackendAlbSecurityGroup", {
securityGroupName: `${props.prefix}-sg-alb-back`,
vpc: props.vpc,
description: "Security Group for Backend ALB"
})
backendAlbSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(80),
"Allow Inbound HTTP Access"
)
backendAlbSecurityGroup.addEgressRule(
ec2.Peer.ipv4(props.vpc.vpcCidrBlock),
ec2.Port.tcp(443),
"Allow Outbound HTTPS Access to inside of VPC"
)
backendAlbSecurityGroup.addEgressRule(
ec2.Peer.ipv4(props.vpc.vpcCidrBlock),
ec2.Port.tcp(80),
"Allow Outbound HTTP Access to inside of VPC"
)
// Alb
this.alb = new elbv2.ApplicationLoadBalancer(this, 'Alb', {
vpc: props.vpc,
vpcSubnets: props.vpc.selectSubnets({
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS
}),
loadBalancerName: `${props.prefix}-alb-back`,
internetFacing: false,
securityGroup: backendAlbSecurityGroup,
});
// Listener
const albListener = this.alb.addListener('AlbListener', {
protocol: elbv2.ApplicationProtocol.HTTP,
port: 80,
});
// Target
albListener.addTargets(
"LambdaTarget",
{
targetGroupName: `${props.prefix}-tg-back-lambda`,
targets: [new elbv2Targets.LambdaTarget(itemLambda)],
healthCheck: {
enabled: true,
}
}
);
- 検証用のため、Lambda関数はrepostに記載のコードを流用しています。(且つ、Lambda関数のレスポンス形式が不正な場合、502エラーが返却されることに留意ください。)
import json
def lambda_handler(event, context):
return {
"statusCode": 200,
"statusDescription": "200 OK",
"headers": {
"Content-Type": "text/html"
},
"isBase64Encoded": False,
"body": "<h1>Hello from Lambda!</h1>"
}
Verified Accessの設定
事前準備
- Verified Accessエンドポイントで利用するSecurity Groupを作成します。
// Security Group
const verifiedSecurityGroup = new ec2.SecurityGroup(this, "VerifiedSecurityGroup", {
securityGroupName: `${props.prefix}-sg-verified`,
vpc: props.vpc,
description: "Security Group for Verified Access"
}) verifiedSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(443),
"Allow Inbound HTTPS Access"
)
- Verified Accessエンドポイントで利用するパブリック証明書をACMで作成します。
// ACM
const verifiedAcm = new acm.Certificate(this, "VerifiedAcm", {
domainName: props.domainName,
validation: acm.CertificateValidation.fromDns(props.hostedZone)
})
信頼プロバイダー設定
- Keycloak管理コンソールで取得した情報を用いて、信頼プロバイダーを作成します。
// Trust Provider
const verifiedAccessTrustProvider = new ec2.CfnVerifiedAccessTrustProvider(this, "VerifiedAccessTrustProvider", {
tags: [{
key: 'Name',
value: `${props.prefix}-verified-access-trust-provider-oidc`,
}],
description: "OIDC VerifiedAccessTrustProvider",
policyReferenceName: `${props.prefix}_user_trust_policy`,
trustProviderType: "user",
userTrustProviderType: "oidc",
oidcOptions: {
authorizationEndpoint: props.authorizationEndpoint,
clientId: props.clientId,
clientSecret: props.clientSecret,
issuer: props.issuer,
scope: "openid profile groups",
tokenEndpoint: props.tokenEndpoint,
userInfoEndpoint: props.userInfoEndpoint,
}
})
インスタンス設定
- 上記で作成した信頼プロバイダーをアタッチしたインスタンスを作成します。
// Instance
const verifiedAccessInstance = new ec2.CfnVerifiedAccessInstance(this, "VerifiedAccessInstance", {
tags: [{
key: 'Name',
value: `${props.prefix}-verified-access-instance-oidc`,
}],
description: "OIDC VerifiedAccessInstance",
verifiedAccessTrustProviderIds: [verifiedAccessTrustProvider.attrVerifiedAccessTrustProviderId]
})
グループ設定
- 上記で作成したインスタンスをアタッチしたVerified Accessグループを作成します。
- まずは、Keycloak認証済みユーザに対してアクセスを許可するよう、設定します。
// Access Group
const verifiedAccessGroup = new ec2.CfnVerifiedAccessGroup(this, "VerifiedAccessGroup", {
tags: [{
key: 'Name',
value: `${props.prefix}-verified-access-group-oidc`,
}],
description: "OIDC VerifiedAccessGroup",
verifiedAccessInstanceId: verifiedAccessInstance.attrVerifiedAccessInstanceId,
policyDocument: "permit(principal,action,resource) when {true};"
}
エンドポイント設定
上記で作成したグループ・ACM・Security Group・ALBをアタッチしたエンドポイントを作成します。
// Access Endpoint
const verifiedAccessEndpoint = new ec2.CfnVerifiedAccessEndpoint(this, "VerifiedAccessEndpoint", {
tags: [{
key: 'Name',
value: `${props.prefix}-verified-access-endpoint-oidc`,
}],
description: "OIDC VerifiedAccessEndpoint",
verifiedAccessGroupId: verifiedAccessGroup.attrVerifiedAccessGroupId,
applicationDomain: props.domainName,
domainCertificateArn: verifiedAcm.certificateArn,
attachmentType: "vpc",
securityGroupIds: [verifiedSecurityGroup.securityGroupId],
endpointDomainPrefix: props.domainName.split(".")[0],
endpointType: "load-balancer",
loadBalancerOptions: {
loadBalancerArn: props.LoadBalancerArn,
port: 80,
protocol: "http",
subnetIds: props.vpc.selectSubnets({
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS
}).subnetIds
}
})
Route53設定
- Verified Accessにアクセスするためのカスタムドメインについて、CNAMEレコードとして設定します。
// Route53 CNAME Record
new route53.CnameRecord(this, "VerifiedAccessCnameRecord", {
zone: props.hostedZone,
recordName: props.domainName,
domainName: verifiedAccessEndpoint.attrEndpointDomain,
});
動作確認
- カスタムドメインに対してアクセスすると、Keycloakの認証画面にリダイレクトされます。
- Keycloakで認証を済ますと、無事にLambda関数からレスポンスが返却されます。
- グループポリシーを設定変更し、特定のグループに所属している場合のみアクセスを許可するよう設定します。
permit(principal,action,resource)
when {
context.poc_user_trust_policy.groups.contains("/XXX")
};
- 上記設定後、当該グループに未所属のユーザでアクセスした場合には403エラーが返却されました。(反映までタイムラグがありました。)
- 特定のグループに所属のユーザでは、引き続きアクセス可能なことも確認済みです。
注意事項
- 本記事は万全を期して作成していますが、お気づきの点がありましたら、ご連絡よろしくお願いします。
- なお、本記事の内容を利用した結果及び影響について、筆者は一切の責任を負いませんので、予めご了承ください。