AWS Client VPNを、Azure ADとのSSOで利用する

AWS Clinet VPNを、Azure AD側ユーザーにてシングルサインオンして利用する手順を確認しています。AWSとAzureそれぞれで、必要手順を紹介しているページがあります。

AWS側の資料はこちら。

How to Integrate AWS Client VPN with Azure Active Directory

Azure側の資料はこちら。

チュートリアル:Azure Active Directory シングル サインオン (SSO) と AWS ClientVPN の統合

Azure AD側での作業

前提情報です。

AWS ClientVPN では、SP Initiated SSO がサポートされます

SP Initiated SSO とは、以下認証フローとのこと。

ユーザーがSPにアクセスすると、SPはそのリクエストをSAML認証要求と共に、IdPへとリダイレクトします。IdPはこの認証要求に基づき、ユーザーの認証処理を実行します。IdPでのユーザー認証が成功すると、IdPはSPに対して認証情報やユーザーの属性等の情報を発行します。

f:id:goodbyegangster:20210326170653p:plain

CYBERNET SAMLとは

AWS ClientVPN では、Just-In-Time ユーザー プロビジョニングがサポートされます

Just-In-Time ユーザー プロビジョニング とは、以下とのこと。ユーザーがログイン処理した時に、SP側でユーザーを作成するよ、ぐらいの認識です。

Just-In-Time (JIT) provisioning enables automatic user account creation in Okta the first time a user authenticates with Active Directory (AD) Delegated Authentication, Desktop SSO, or inbound SAML.

Add and update users with Just-In-Time provisioning

Azure ADにAWS Client VPN用アプリの設定

Azure AD側で、AWS Client VPNSAML連携用アプリを作成します。ActiveDirectory Federation Serviceで設定するような内容を、ここで設定するイメージとなります。

Azure ADのホーム画面より、左ペインの「Enterprise applications」を選択。

「New application」をクリックします。

f:id:goodbyegangster:20210326170658p:plain

検索フォームで AWS ClientVPN と入力すると、AWS ClientVPNのアイコンが表示されるのでクリックします。

f:id:goodbyegangster:20210326170704p:plain

Nameフォームに適当な名前を入れて、「Create」をクリック。

f:id:goodbyegangster:20210326170708p:plain

まず、このSAML連携を利用できるADユーザーを登録します。表示された画面のGetting Start内の「Assign users and groups」をクリック。

f:id:goodbyegangster:20210326170553p:plain

「Add user/gourp」をクリック。

f:id:goodbyegangster:20210326170557p:plain

Usersを選択。表示されているWarningはAzure無料版だからです。

f:id:goodbyegangster:20210326170601p:plain

利用させたいユーザを選択して、「Select」をクリック。

f:id:goodbyegangster:20210326170606p:plain

「Assign」をクリック。

f:id:goodbyegangster:20210326170610p:plain

続いてSSOの設定を入力します。左ペインの「Overview」をクリックして、Getting Start内の「Set up single sign on」をクリックします。

f:id:goodbyegangster:20210326170713p:plain

SAML」をクリック。

f:id:goodbyegangster:20210326170536p:plain

①の「Basic SAML Configuration」で以下の値を設定します。

パラメーター
Identifier (Entity ID) urn:amazon:webservices:clientvpn
Reply URL (Assertion Consumer Service URL) https://127.0.0.1:35001
Sign on URL https://127.0.0.1:35001
Relay State (ブランク)
Logout Url (ブランク)

SP Initiated SSOの認証フローであるゆえ、 Sign on URL が求められるのですが、AWS Client VPNにログインWEBページなんてないぞ、と混乱しました。こちらは Reply URL と同じ値でいいようです。

f:id:goodbyegangster:20210326170541p:plain

②の「User Attributes & Claims」で、以下の通りクレームを登録します。

Claim Name Value 備考
nameidentifier user.userprincipalname
FirstName user.givenname
LastName user.surname
memberOf user.groups group claimの All groupsGroup ID
Unique User Identifier user.userprincipalname

f:id:goodbyegangster:20210326170546p:plain

③の「SAML Signing Certificate」にて、SAML名証明書の設定を変更しておきます。「Edit」をクリックします。

f:id:goodbyegangster:20210326170549p:plain

「Signing Option」で、「Sign SAML response and assertion」を設定しておきます。

f:id:goodbyegangster:20210326170614p:plain

コンフィグファイルを直接いじってSAML定義を修正必要な箇所があり、コンフィグファイル(マニフェストファイル)を、Azureポータル上で開きます。Azure ADのホーム画面に移動し左ペインの「App registrations」を選択します。「All applications」に、これまでに作成したAWS ClientVPNのAppsがいるので、クリックします。

f:id:goodbyegangster:20210326170619p:plain

左ペインの「Manifest」をクリックします。

f:id:goodbyegangster:20210326170624p:plain

以下にようなJSONのコンフィグファイルが表示されるので、 replyUrlsWithTypeurlプロトコルを「https」から「http」に変更します。これは、AWS Client VPNACS(Assertion Consumer Service) URLがHTTPプロトコルのみサポートしているために必要となる作業となります。

f:id:goodbyegangster:20210326172040j:plain

最後に、「Enterprise applications」の作成したAWS ClientVPNのapp画面に戻り、Federation MetadataのXMLファイルをダウンロードしておきます。

f:id:goodbyegangster:20210326170632p:plain

AWS Client VPNの設定

AWS側での設定を実施します。

IAM Identity Providerの作成

上記でダウンロードしておいたメタデータファイルを利用して、SAML用のIAM Identity Providerを作成します。

$ aws iam create-saml-provider --saml-metadata-document file://"AWS ClientVPN Sample.xml" --name azure

aws iam create-saml-provider

AWSはServiceProvider側なのに、IAM Identity Providerとか、まるでIdPであるかのような紛らわしい名前なのですが、実施していることはAzure AD側との信頼関係を結ぶ(つまり証明書利用者信頼の設定をしている)だけになります。

AWS Client VPN用のサーバー証明書の作成

AWS Client VPNで利用する自己署名のサーバー証明書を作成します。公式ドキュメントにて easy-rsa を利用した手順書が紹介されており、それに沿って実行しています。

$ git clone https://github.com/OpenVPN/easy-rsa.git

$ cd easy-rsa/easyrsa3

$ ./easyrsa init-pki

$ ./easyrsa build-ca nopass

$ ./easyrsa build-server-full server nopass

Mutual authentication

作成した証明書一式をACMにインポートします。

$ aws acm import-certificate \
--certificate fileb://pki/issued/server.crt \
--private-key fileb://pki/private/server.key \
--certificate-chain fileb://pki/ca.crt

acm import-certificate

AWS ClientVPNの作成

AWS ClientVPNを作成します。以下、作成用のCloudFormationです。

AWSTemplateFormatVersion: 2010-09-09
Description: "CFn for Client VPN"
Metadata: 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: "CFn for Client VPN"
        Parameters:
          - ClinetVpnName
          - ProviderName
          - ACMIdentifier
          - VpcId
          - Subnet
          - VpnCidr

Parameters: 
  ClinetVpnName:
    Type: "String"
  ProviderName:
    Type: "String"
  ACMIdentifier:
    Type: "String"
  VpcId:
    Type: "AWS::EC2::VPC::Id"
  Subnet:
    Type: "AWS::EC2::Subnet::Id"
  VpnCidr:
    Type: "String"
    Description: "Client cidr block must be at least a /22 range"

Resources:
  CloudwatchLogGroup:
    Type: "AWS::Logs::LogGroup"
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html
    DeletionPolicy: "Delete"
    Properties:
      # KmsKeyId: String
      LogGroupName: !Sub /aws/clientvpn/${ClinetVpnName}
      RetentionInDays: 30
  CloudwatchLogStream:
    Type: "AWS::Logs::LogStream"
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-logstream.html
    DeletionPolicy: "Delete"
    DependsOn: "CloudwatchLogGroup"
    Properties:
      LogGroupName: !Ref CloudwatchLogGroup
      LogStreamName: !Ref ClinetVpnName
  Endpoint:
    Type: "AWS::EC2::ClientVpnEndpoint"
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-clientvpnendpoint.html
    DependsOn: "CloudwatchLogStream"
    DeletionPolicy: "Delete"
    Properties:
      AuthenticationOptions:
        - Type: "federated-authentication"
          FederatedAuthentication:
            SAMLProviderArn: !Sub arn:aws:iam::${AWS::AccountId}:saml-provider/${ProviderName}
            # SelfServiceSAMLProviderArn: String
      ClientCidrBlock: !Ref VpnCidr
      # ClientConnectOptions:
      #   Enabled: Boolean
      #   LambdaFunctionArn: String
      ConnectionLogOptions:
        Enabled: True
        CloudwatchLogGroup: !Ref CloudwatchLogGroup
        CloudwatchLogStream: !Ref CloudwatchLogStream
      Description: "sample"
      # DnsServers:
      #   - String
      # SecurityGroupIds:
      #   - String
      # SelfServicePortal: Boolean
      ServerCertificateArn: !Sub arn:aws:acm:${AWS::Region}:${AWS::AccountId}:certificate/${ACMIdentifier}
      SplitTunnel: False
      TagSpecifications:
        - ResourceType: "client-vpn-endpoint"
          Tags:
          - Key: "Name"
            Value: !Ref ClinetVpnName
      TransportProtocol: "udp"
      VpcId: !Ref VpcId
      VpnPort: 443
  Association:
    Type: "AWS::EC2::ClientVpnTargetNetworkAssociation"
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-clientvpntargetnetworkassociation.html
    DependsOn: "Endpoint"
    DeletionPolicy: "Delete"
    Properties:
      ClientVpnEndpointId: !Ref Endpoint
      SubnetId: !Ref Subnet
  Authorization:
    Type: AWS::EC2::ClientVpnAuthorizationRule
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-clientvpnauthorizationrule.html
    DependsOn: "Endpoint"
    DeletionPolicy: "Delete"
    Properties:
      # AccessGroupId: String
      AuthorizeAllGroups: true
      ClientVpnEndpointId: !Ref Endpoint
      Description: "for internet"
      TargetNetworkCidr: "0.0.0.0/0"
  VpnRoute:
    Type: "AWS::EC2::ClientVpnRoute"
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-clientvpnroute.html
    DependsOn: "Association"
    DeletionPolicy: "Delete"
    Properties:
      ClientVpnEndpointId: !Ref Endpoint
      Description: "for internet"
      DestinationCidrBlock: "0.0.0.0/0"
      TargetVpcSubnetId: !Ref Subnet

接続テスト

実際にVPN接続してみます。

AWS提供のクライアントモジュールが公開されており、未インストールの場合にインストール。

client-vpn-download

ClientVPNのコンソール画面より、作成したClientVPN設定のコンフィグをダウンロードしてきます。

f:id:goodbyegangster:20210326170636p:plain

ダウンロードしたコンフィグを開いて、ファイル内に記載されているCA証明書部分を、前述で作成したものに変更します。以下の部分です。

cipher AES-256-GCM
verb 3
<ca>
-----BEGIN CERTIFICATE-----
MIIDSzCCAjOgAwIBAgIUa+/Z+wG2DhFeX3MY2LrYu04eJX8wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMjEwMzI1MDkwMDUzWhcNMzEw
MzIzMDkwMDUzWjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBALMqsn5EnmeS16pN3tBWKkHe6o+nNnybey7fZNro
lsuGiznEskhOIuKYAP64POQwZo1Qm2rFTLHF89Z+VECkSuzTNKErL/2mJAo9bbFv
Gg2qD8CTDeUFUEonBIVRRLNuDuMgWO9TDxSgx5JaXvNCpE0tCwDv/WNNRC4ODz8s
OX5G8CbsRpTH23be863JsREFLOOHpdvbFAM8xKGMh74csuvtXOafcXDOLAu4n4h5
NXNDoehqXYtlRAOwHYqEDoUinCXEAEI5+xThOUOUfYqi/dqNxpK1GsJ5b2L1ztwp
zllNr5B45Ps18YEIvPhVICbcfHEBoRo6HOY6bRQFUIzCOD0CAwEAAaOBkDCBjTAd
BgNVHQ4EFgQU/nx1hrU9ZRwra4WfUEUZj0jmG0kwUQYDVR0jBEowSIAU/nx1hrU9
ZRwra4WfUEUZj0jmG0mhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghRr79n7
AbYOEV5fcxjYuti7Th4lfzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkq
hkiG9w0BAQsFAAOCAQEAHTZKqcRqlmqDFQ3AmidyxLITtwcbYU0Q9NvQZoQfaDrY
pIlqd50G6sLszJkBHhobkGYxyWhuZ1Qwv0zrafzkrI+soKzTxK/z94gK7KW5XPln
RR4QC8EhRxtcox7nYEg7JhBg2gOLmJirTHMnWF7wNdlLqgOTN5+UjezZ+oKYlMb3
qOoDSQKkxNLy5G+ebn8wtGUFgBrVrgt2JGx+J0QfTXq0H0YgPFWTb35F4jzTw79v
FNv+GQh8kCy4FnC96DWHhsyDqJqfPvWO/7GiYa5lQLb2K1f9hXNlukcy+XMrw8LC
rB1jU4iSi1skIc3v4MLPb8mLNAM6SkDB2DsbqK5alg==
-----END CERTIFICATE-----

</ca>

ClientVPNのAgentを起動させて、上記で編集したコンフィグファイルを食わせてあげます。

食わせたコンフィグの定義を利用して、接続してみます。

f:id:goodbyegangster:20210326170641p:plain

ブラウザが立ち上がって、Microsoftのサインインページが表示されるので、SAML連携の許可設定をしたユーザー情報を入力します。

f:id:goodbyegangster:20210326170645p:plain

サインインできると、ClientVPNも接続できます。

f:id:goodbyegangster:20210326170649p:plain

試しに、利用しているグローバルIPを確認を確認すると、AWS側提供のIPアドレス(いつもとは違うIPアドレス)になっているはずです。