mTLS + カスタムドメインなAPI GatewayをSAMでデプロイしてみた

2022.03.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

いわさです。

以前、SAMテンプレートとOpenAPIで最小限のAPI Gatewayをデプロイしました。

本日は、このテンプレートを流用して、カスタムドメインとmTLS(Mutual TLS)を使うようにカスタマイズしてみます。

カスタムドメインとmTLSの手動設定は以下の記事を参考に

上記記事が最高です。

API Gatewayでは本機能でクライアント認証を行うことが出来ます。
そもそもカスタムドメインを使いたかったのもありますが、mTLSを使う上でカスタムドメインの利用が前提条件にもなっています。

SAMテンプレート

今回は、手動でクライアント証明書を事前に発行しておき、Route53パブリックホストゾーンを作成済みな環境を想定しています。

テンプレートをつかって、mTLSとカスタムドメインが構成されたAPI Gatewayと、パブリック証明書用のACMをデプロイします。

template-cfn.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: ---
Parameters:
  ApiName:
    Description: API Gateway Name
    Type: String
  HostedZoneId:
    Description: Hosted Zone Id
    Type: String
  CustomDomainName:
    Description: Custom Domain Name
    Type: String
  TrustStoreS3Uri:
    Description: TrustStore S3 URI
    Type: String
    Default: s3://xxx/xxx.pem
Resources:
  Api:
    Type: AWS::Serverless::Api
    Properties:
      Name: !Ref ApiName
      EndpointConfiguration: REGIONAL
      OpenApiVersion: 3.0.1
      StageName: iwasa-stage
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: ./openapi.json
      Domain: 
        DomainName: !Ref CustomDomainName
        CertificateArn: !Ref Certificate
        EndpointConfiguration: REGIONAL
        MutualTlsAuthentication: 
          TruststoreUri: !Ref TrustStoreS3Uri
        Route53: 
          DistributionDomainName: !Ref CustomDomainName
          HostedZoneId: !Ref HostedZoneId
        SecurityPolicy: TLS_1_2

  Certificate:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref CustomDomainName
      DomainValidationOptions:
        - DomainName: !Ref CustomDomainName
          HostedZoneId: !Ref HostedZoneId
      ValidationMethod: DNS

ポイントとして、mTLSを使う場合はTLSバージョンは1.2のみサポートされているのでSecurityPolicyで明示する必要があります。
また、今回はパブリックドメインを使うのでOwnershipVerificationCertificateArnの指定は不要です。
参考記事に記載があるように、MutualTlsAuthenticationでバージョンも指定することが推奨されていますが、ここでも割愛しています。

OpenAPIドキュメントはAWSのIPアドレスチェック用のエンドポイントをバックエンドに指しており、CodeBuildフェーズでsam packageしています。
詳細は前回の記事をご確認ください。

openapi.json

{
    "openapi" : "3.0.1",
    "paths" : {
      "/" : {
        "get" : {
          "responses" : {
            "200" : {
              "description" : "200 response",
              "content" : {
                "application/json" : {
                  "schema" : {
                    "$ref" : "#/components/schemas/Empty"
                  }
                }
              }
            }
          },
          "x-amazon-apigateway-integration" : {
            "type" : "http_proxy",
            "httpMethod" : "GET",
            "uri" : "http://checkip.amazonaws.com",
            "responses" : {
              "default" : {
                "statusCode" : "200"
              }
            },
            "passthroughBehavior" : "when_no_match"
          }
        }
      }
    },
    "components" : {
      "schemas" : {
        "Empty" : {
          "title" : "Empty Schema",
          "type" : "object"
        }
      }
    }
  }

リソース構成と接続の確認

デプロイは前回の記事のCodePipelineにまかせていますのでそちらをご確認ください。
ここではデプロイ後にまず接続を試してみます。

$ curl https://apihoge.tak1wa.com
curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to apihoge.tak1wa.com:443 
$ curl --key my_client.key --cert my_client.pem https://apihoge.tak1wa.com
203.0.113.1

クライアント証明書を使ってアクセスしたときだけアクセス出来ていますね。
次に、リソース構成も見てみましょう。

mTLSがONになっており、カスタムドメインが設定されています。

さいごに

お気づきかもしれませんが、デフォルトエンドポイントが有効になっていますね。
この状態だと、クライアント証明書なしでデフォルトエンドポイント経由でアクセスすることが出来てしまいます。

$ curl https://4pqf8o4iqj.execute-api.ap-northeast-1.amazonaws.com/iwasa-stage
203.0.113.1

デフォルトエンドポイント周りはSAMやCloudFormationの場合は有効化/無効化に少し癖があることがわかったので別途検証記事を紹介したいと思います。