From 0a09a3a390e08b455400074dbe7e5bffebb6b4f2 Mon Sep 17 00:00:00 2001 From: Chris Wynne Date: Mon, 3 Jun 2024 10:30:13 +0100 Subject: [PATCH 1/4] PYIC-5872: Expose internal API in dev This change allows core's internal API to be accessed with an API key when deployed to either of the dev accounts. It adds a usage plan with API key to the internal API, and then makes use of an API key required only for the dev accounts. It also adds DNS to the API gateway to make use of the internal API easier. Also, to allow programatic access to the API key, a new resource policy is created to allow the OIDC identity provider enough permissions to fetch the API key. It removes the `CoreFrontLocal` parameter in favour of defaulting to this behavour for dev accounts. This is because changing back to a private API from the new regional version is a two step process - so not as easy as just toggling the parameter. It's a two step process as you can't change from regional to private if it has custom domains associated with it. In CloudFormation there isn't a way to specify that something should be removed before making changes, resulting in having to remove the custom domain before changing the API to private. Ideally I would have liked to have conditionally created the UsagePlan - only for the dev accounts. However there appears to be a bug in cfn-lint that means we can't use an If function with the CreateUsagePlan property. The GHA workflow included in this PR is just to demonstrate that the gateway is now available with an API key, and also how to programatically access the API key. This would not be merged with the PR. --- .github/workflows/dev-env-api-test.yaml | 36 ++++++ .secrets.baseline | 25 ++-- deploy/template.yaml | 148 +++++++++++++++++++----- 3 files changed, 171 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/dev-env-api-test.yaml diff --git a/.github/workflows/dev-env-api-test.yaml b/.github/workflows/dev-env-api-test.yaml new file mode 100644 index 0000000000..0bccb26bbd --- /dev/null +++ b/.github/workflows/dev-env-api-test.yaml @@ -0,0 +1,36 @@ +name: Dev env internal API test + +on: + pull_request: + types: + - opened + - reopened + - ready_for_review + - synchronize + +jobs: + make-api-call: + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - name: Auth with AWS dev account + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.PYIC_5872_INTERNAL_API_TOKEN_ACCESS_AWS_ROLE_ARN }} + aws-region: eu-west-2 + + - name: Fetch API token + id: fetch-api-token + run: | + apiToken=$(aws apigateway get-api-key \ + --api-key $(aws cloudformation describe-stacks --stack-name core-back-chrisw | jq -r '.Stacks[0].Outputs | .[] | select(.OutputKey == "InternalApiKeyId") | .OutputValue') \ + --include-value \ + | jq -r .value) + + echo "API_TOKEN=${apiToken}" >> "$GITHUB_OUTPUT" + + - name: Make auth'd API call + env: + API_TOKEN: ${{ steps.fetch-api-token.outputs.API_TOKEN }} + run: curl -v -H "x-api-key:${API_TOKEN}" https://internal-api-dev-chrisw.01.dev.identity.account.gov.uk/user/proven-identity-details diff --git a/.secrets.baseline b/.secrets.baseline index 9d903f66b3..25f0bf12c7 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -75,10 +75,6 @@ { "path": "detect_secrets.filters.allowlist.is_line_allowlisted" }, - { - "path": "detect_secrets.filters.common.is_baseline_file", - "filename": ".secrets.baseline" - }, { "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", "min_level": 2 @@ -160,42 +156,49 @@ "filename": "deploy/template.yaml", "hashed_secret": "b811ac90fe7fab03f6144a17aaebc38dcf3e007b", "is_verified": false, - "line_number": 158 + "line_number": 143 }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "690de9fd42add772818ae392cb68a4f81d1511e3", "is_verified": false, - "line_number": 213 + "line_number": 198 + }, + { + "type": "Secret Keyword", + "filename": "deploy/template.yaml", + "hashed_secret": "d3053d5db9cc8cb93b26db3c26c76bdfdff06ace", + "is_verified": false, + "line_number": 331 }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "49edc8e5cce3d7f30610b919b21c6722f4553131", "is_verified": false, - "line_number": 936 + "line_number": 1025 }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "2f4012d62ceff52b17fe028aeb7a5efa6e6e23cf", "is_verified": false, - "line_number": 938 + "line_number": 1027 }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "6afab4c634af2dd2b9c344a98f96667277c56df0", "is_verified": false, - "line_number": 2102 + "line_number": 2191 }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "38450ffe4ff65a68053ea5083d47521010709df2", "is_verified": false, - "line_number": 2570 + "line_number": 2659 } ], "lambdas/build-user-identity/src/test/java/uk/gov/di/ipv/core/builduseridentity/pact/BuildUserIdentityHandlerTest.java": [ @@ -1925,5 +1928,5 @@ } ] }, - "generated_at": "2024-06-05T11:35:43Z" + "generated_at": "2024-06-05T13:16:17Z" } diff --git a/deploy/template.yaml b/deploy/template.yaml index 79deab8e6c..e0c24e4c84 100644 --- a/deploy/template.yaml +++ b/deploy/template.yaml @@ -96,20 +96,6 @@ Parameters: - "False" - "True" Default: "False" - LocalCoreFront: - Type: String - Description: | - Whether to expect core-front to be run on a dev machine at localhost. - If "True" expect localhost for core-front, if "False" expect core-front to run on AWS. - AllowedValues: - - "False" - - "True" - Default: "False" - LocalIpAddress: - Type: String - Description: | - The IP address that requests to AWS from a local core-front will come from. - Default: "" Conditions: IsDevelopment: !Or @@ -136,7 +122,6 @@ Conditions: UseIndividualCiMitStubs: !And - !Condition IsDevelopment - !Equals [ !Ref IndividualCiMitStubs, "True"] - UseLocalCoreFront: !Equals [ !Ref LocalCoreFront, "True"] # The AWS Account Id is used in the following mapping section because we have # multiple developer environments and it is undesirable to have to keep this @@ -321,23 +306,36 @@ Resources: IPVCorePrivateAPI: Type: AWS::Serverless::Api + Metadata: + cfn-lint: + config: + ignore_checks: + - W3005 # Obsolete DependsOn - issue with API Key resource generated by UsagePlan Properties: # checkov:skip=CKV_AWS_120: We are not implementing API Gateway caching at the time. Name: !Sub IPV Core Private API Gateway ${Environment} EndpointConfiguration: - Type: !If [UseLocalCoreFront, REGIONAL, PRIVATE] + Type: !If [IsDevelopment, REGIONAL, PRIVATE] VPCEndpointIds: - - !If [UseLocalCoreFront, !Ref AWS::NoValue, Fn::ImportValue: !Sub "${VpcStackName}-ExecuteApiGatewayEndpointId"] + - !If [IsDevelopment, !Ref AWS::NoValue, Fn::ImportValue: !Sub "${VpcStackName}-ExecuteApiGatewayEndpointId"] DefinitionBody: openapi: "3.0.3" # workaround to get `sam validate` to work paths: # workaround to get `sam validate` to work /foo: - bar: baz # workaround to get `sam validate` to work + bar: + baz: thing # workaround to get `sam validate` to work Fn::Transform: Name: "AWS::Include" Parameters: Location: "../openAPI/core-back-internal.yaml" + ApiKeySourceType: HEADER Auth: + ApiKeyRequired: !If + - IsDevelopment + - true + - false + UsagePlan: + CreateUsagePlan: PER_API ResourcePolicy: CustomStatements: - Action: 'execute-api:Invoke' @@ -346,15 +344,8 @@ Resources: Resource: - 'execute-api:/*' - !If - - UseLocalCoreFront - - Action: 'execute-api:Invoke' - Effect: Deny - Principal: '*' - Resource: - - 'execute-api:/*' - Condition: - NotIpAddress: - 'aws:SourceIp': [!Ref LocalIpAddress] + - IsDevelopment + - Ref: AWS::NoValue - Action: 'execute-api:Invoke' Effect: Deny Principal: '*' @@ -382,6 +373,104 @@ Resources: "responseLength":"$context.responseLength" } + # ssl cert + IPVCorePrivateApiSSLCert: + Type: AWS::CertificateManager::Certificate + Condition: IsDevelopment + Properties: + DomainName: !If + - IsDev01 + - !Sub "internal-api-${Environment}.01.dev.identity.account.gov.uk" + - !If [IsDev02, !Sub "internal-api-${Environment}.02.dev.identity.account.gov.uk", !Ref AWS::NoValue] + DomainValidationOptions: + - DomainName: !If + - IsDev01 + - !Sub "internal-api-${Environment}.01.dev.identity.account.gov.uk" + - !If [IsDev02, !Sub "internal-api-${Environment}.02.dev.identity.account.gov.uk", !Ref AWS::NoValue] + HostedZoneId: !If + - IsDev01 + - !ImportValue Dev01IdentityHostedZoneId + - !If [IsDev02, !ImportValue Dev02IdentityHostedZoneId, DevIdentityHostedZoneId] + ValidationMethod: DNS + + # api domain entries / mapping + IPVCorePrivateApiDomain: + Type: AWS::ApiGatewayV2::DomainName + # checkov:skip=CKV_AWS_120: doing it later + Condition: IsDevelopment + Properties: + DomainName: !If + - IsDev01 + - !Sub "internal-api-${Environment}.01.dev.identity.account.gov.uk" + - !If [IsDev02, !Sub "internal-api-${Environment}.02.dev.identity.account.gov.uk", !Ref AWS::NoValue] + DomainNameConfigurations: + - CertificateArn: !Ref IPVCorePrivateApiSSLCert + EndpointType: REGIONAL + SecurityPolicy: TLS_1_2 + + IPVCorePrivateApiMapping: + Type: AWS::ApiGatewayV2::ApiMapping + Condition: IsDevelopment + Properties: + DomainName: !If + - IsDev01 + - !Sub "internal-api-${Environment}.01.dev.identity.account.gov.uk" + - !If [IsDev02, !Sub "internal-api-${Environment}.02.dev.identity.account.gov.uk", !Ref AWS::NoValue] + ApiId: !Ref IPVCorePrivateAPI + Stage: !Ref IPVCorePrivateAPI.Stage + DependsOn: + - IPVCorePrivateApiDomain + + # dns record + IPVCorePrivateApiRecord: + Type: AWS::Route53::RecordSet + Condition: IsDevelopment + Properties: + Type: A + Name: !If + - IsDev01 + - !Sub "internal-api-${Environment}.01.dev.identity.account.gov.uk" + - !If [IsDev02, !Sub "internal-api-${Environment}.02.dev.identity.account.gov.uk", !Ref AWS::NoValue] + HostedZoneId: !If + - IsDev01 + - !ImportValue Dev01IdentityHostedZoneId + - !If [IsDev02, !ImportValue Dev02IdentityHostedZoneId, DevIdentityHostedZoneId] + AliasTarget: + DNSName: !GetAtt IPVCorePrivateApiDomain.RegionalDomainName + HostedZoneId: !GetAtt IPVCorePrivateApiDomain.RegionalHostedZoneId + + IPVCorePrivateApiTokenFetchPolicy: + Type: AWS::IAM::ManagedPolicy + Condition: IsDevelopment + Properties: + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: "cloudformation:DescribeStacks" + Resource: !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*" + - Effect: Allow + Action: "apigateway:GET" + Resource: !Sub "arn:aws:apigateway:${AWS::Region}::/apikeys/${IPVCorePrivateAPI.ApiKey}" + + IPVCorePrivateApiTokenFetchRole: + Type: AWS::IAM::Role + Condition: IsDevelopment + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + Effect: Allow + Action: "sts:AssumeRoleWithWebIdentity" + Principal: + Federated: !ImportValue GitHubIdentityProviderArn + Condition: + StringLike: + "token.actions.githubusercontent.com:sub": + - "repo:govuk-one-login/ipv-core-back:*" + ManagedPolicyArns: + - !Ref IPVCorePrivateApiTokenFetchPolicy + IPVCorePrivateAPILogGroup: Type: AWS::Logs::LogGroup # checkov:skip=CKV_AWS_158: No need for customer managed keys for short lived logs @@ -3114,3 +3203,8 @@ Outputs: Value: !GetAtt LoggingKmsKey.Arn Export: Name: !Sub "CoreBackLoggingKmsKeyArn-${Environment}" + InternalApiKeyId: + Description: The ID of the API key for the internal API, if exposed + Value: !Ref IPVCorePrivateAPI.ApiKey + Export: + Name: !Sub "InternalApiKeyId-${Environment}" From a0e937713ad96dc49ece5de3cd83aa61a4fd89ed Mon Sep 17 00:00:00 2001 From: Chris Wynne Date: Mon, 3 Jun 2024 12:37:24 +0100 Subject: [PATCH 2/4] PYIC-5872: Don't use SAM UsagePlan SAM allows an easy way to create a usage plan and API key, by defining the `UsagePlan` property in an `Api` resource. Unfortunately you can't use an `!If` function with it do conditionally create it. This removes the use of it and defines the resources requied using plain old CloudFormation. This allows us to only create them when we want. Using the previous method would have caused a usage plan and api key to be created in all envs including prod. This felt like murky ground - the non dev api's would still be private so it wouldn't have been an issue, but feels safer to only create them when actually needed. --- .secrets.baseline | 17 ++----- deploy/template.yaml | 106 +++++++++++++++++++++++-------------------- 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 25f0bf12c7..1aeaa0c82a 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -165,40 +165,33 @@ "is_verified": false, "line_number": 198 }, - { - "type": "Secret Keyword", - "filename": "deploy/template.yaml", - "hashed_secret": "d3053d5db9cc8cb93b26db3c26c76bdfdff06ace", - "is_verified": false, - "line_number": 331 - }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "49edc8e5cce3d7f30610b919b21c6722f4553131", "is_verified": false, - "line_number": 1025 + "line_number": 1036 }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "2f4012d62ceff52b17fe028aeb7a5efa6e6e23cf", "is_verified": false, - "line_number": 1027 + "line_number": 1038 }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "6afab4c634af2dd2b9c344a98f96667277c56df0", "is_verified": false, - "line_number": 2191 + "line_number": 2202 }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "38450ffe4ff65a68053ea5083d47521010709df2", "is_verified": false, - "line_number": 2659 + "line_number": 2670 } ], "lambdas/build-user-identity/src/test/java/uk/gov/di/ipv/core/builduseridentity/pact/BuildUserIdentityHandlerTest.java": [ @@ -1928,5 +1921,5 @@ } ] }, - "generated_at": "2024-06-05T13:16:17Z" + "generated_at": "2024-06-05T13:16:40Z" } diff --git a/deploy/template.yaml b/deploy/template.yaml index e0c24e4c84..22c2c42a8d 100644 --- a/deploy/template.yaml +++ b/deploy/template.yaml @@ -306,11 +306,6 @@ Resources: IPVCorePrivateAPI: Type: AWS::Serverless::Api - Metadata: - cfn-lint: - config: - ignore_checks: - - W3005 # Obsolete DependsOn - issue with API Key resource generated by UsagePlan Properties: # checkov:skip=CKV_AWS_120: We are not implementing API Gateway caching at the time. Name: !Sub IPV Core Private API Gateway ${Environment} @@ -328,14 +323,8 @@ Resources: Name: "AWS::Include" Parameters: Location: "../openAPI/core-back-internal.yaml" - ApiKeySourceType: HEADER + ApiKeySourceType: !If [ IsDevelopment, HEADER, !Ref AWS::NoValue ] Auth: - ApiKeyRequired: !If - - IsDevelopment - - true - - false - UsagePlan: - CreateUsagePlan: PER_API ResourcePolicy: CustomStatements: - Action: 'execute-api:Invoke' @@ -373,6 +362,60 @@ Resources: "responseLength":"$context.responseLength" } + IpvCorePrivateApiUsagePlan: + Type: AWS::ApiGateway::UsagePlan + Condition: IsDevelopment + Properties: + ApiStages: + - ApiId: !Ref IPVCorePrivateAPI + Stage: !Sub ${Environment} + + IpvCorePrivateApiKey: + Type: AWS::ApiGateway::ApiKey + Condition: IsDevelopment + Properties: + Enabled: true + + IpvCorePrivateApiUsagePlanKeyAssociation: + Type: AWS::ApiGateway::UsagePlanKey + Condition: IsDevelopment + Properties: + KeyId: !Ref IpvCorePrivateApiKey + KeyType: API_KEY + UsagePlanId: !Ref IpvCorePrivateApiUsagePlan + + IPVCorePrivateApiTokenFetchPolicy: + Type: AWS::IAM::ManagedPolicy + Condition: IsDevelopment + Properties: + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: "cloudformation:DescribeStacks" + Resource: !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*" + - Effect: Allow + Action: "apigateway:GET" + Resource: !Sub "arn:aws:apigateway:${AWS::Region}::/apikeys/${IpvCorePrivateApiKey}" + + IPVCorePrivateApiTokenFetchRole: + Type: AWS::IAM::Role + Condition: IsDevelopment + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + Effect: Allow + Action: "sts:AssumeRoleWithWebIdentity" + Principal: + Federated: !ImportValue GitHubIdentityProviderArn + Condition: + StringLike: + "token.actions.githubusercontent.com:sub": + - "repo:govuk-one-login/ipv-core-back:*" + ManagedPolicyArns: + - !Ref IPVCorePrivateApiTokenFetchPolicy + # ssl cert IPVCorePrivateApiSSLCert: Type: AWS::CertificateManager::Certificate @@ -393,7 +436,7 @@ Resources: - !If [IsDev02, !ImportValue Dev02IdentityHostedZoneId, DevIdentityHostedZoneId] ValidationMethod: DNS - # api domain entries / mapping + # api domain entries / mapping IPVCorePrivateApiDomain: Type: AWS::ApiGatewayV2::DomainName # checkov:skip=CKV_AWS_120: doing it later @@ -439,38 +482,6 @@ Resources: DNSName: !GetAtt IPVCorePrivateApiDomain.RegionalDomainName HostedZoneId: !GetAtt IPVCorePrivateApiDomain.RegionalHostedZoneId - IPVCorePrivateApiTokenFetchPolicy: - Type: AWS::IAM::ManagedPolicy - Condition: IsDevelopment - Properties: - PolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Action: "cloudformation:DescribeStacks" - Resource: !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*" - - Effect: Allow - Action: "apigateway:GET" - Resource: !Sub "arn:aws:apigateway:${AWS::Region}::/apikeys/${IPVCorePrivateAPI.ApiKey}" - - IPVCorePrivateApiTokenFetchRole: - Type: AWS::IAM::Role - Condition: IsDevelopment - Properties: - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - Effect: Allow - Action: "sts:AssumeRoleWithWebIdentity" - Principal: - Federated: !ImportValue GitHubIdentityProviderArn - Condition: - StringLike: - "token.actions.githubusercontent.com:sub": - - "repo:govuk-one-login/ipv-core-back:*" - ManagedPolicyArns: - - !Ref IPVCorePrivateApiTokenFetchPolicy - IPVCorePrivateAPILogGroup: Type: AWS::Logs::LogGroup # checkov:skip=CKV_AWS_158: No need for customer managed keys for short lived logs @@ -3204,7 +3215,6 @@ Outputs: Export: Name: !Sub "CoreBackLoggingKmsKeyArn-${Environment}" InternalApiKeyId: + Condition: IsDevelopment Description: The ID of the API key for the internal API, if exposed - Value: !Ref IPVCorePrivateAPI.ApiKey - Export: - Name: !Sub "InternalApiKeyId-${Environment}" + Value: !Ref IpvCorePrivateApiKey From 8133275b8b82590a2a833f2334ce46935f666188 Mon Sep 17 00:00:00 2001 From: Chris Wynne Date: Mon, 3 Jun 2024 15:20:19 +0100 Subject: [PATCH 3/4] PYIC-5872: Create separate API for internal testing This creates a new API gateway to run in parallel with the original. This means that the VPC protected internal API can still be used by core-front without any changes, and the new regional, API token protected API gateway can be used for testing. cfn-lint has an issue with having an event defined in a lambda that includes a reference to a conditionally created resource, even if that resource is behind the same condition. This has lead to the ignore written into the metadata of a number of resources. --- .github/workflows/dev-env-api-test.yaml | 2 +- .secrets.baseline | 24 ++- deploy/template.yaml | 232 ++++++++++++++++-------- 3 files changed, 181 insertions(+), 77 deletions(-) diff --git a/.github/workflows/dev-env-api-test.yaml b/.github/workflows/dev-env-api-test.yaml index 0bccb26bbd..151f2c8885 100644 --- a/.github/workflows/dev-env-api-test.yaml +++ b/.github/workflows/dev-env-api-test.yaml @@ -24,7 +24,7 @@ jobs: id: fetch-api-token run: | apiToken=$(aws apigateway get-api-key \ - --api-key $(aws cloudformation describe-stacks --stack-name core-back-chrisw | jq -r '.Stacks[0].Outputs | .[] | select(.OutputKey == "InternalApiKeyId") | .OutputValue') \ + --api-key $(aws cloudformation describe-stacks --stack-name core-back-chrisw | jq -r '.Stacks[0].Outputs | .[] | select(.OutputKey == "InternalTestingApiKeyId") | .OutputValue') \ --include-value \ | jq -r .value) diff --git a/.secrets.baseline b/.secrets.baseline index 1aeaa0c82a..be57943908 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -165,33 +165,47 @@ "is_verified": false, "line_number": 198 }, + { + "type": "Secret Keyword", + "filename": "deploy/template.yaml", + "hashed_secret": "d3053d5db9cc8cb93b26db3c26c76bdfdff06ace", + "is_verified": false, + "line_number": 391 + }, + { + "type": "Secret Keyword", + "filename": "deploy/template.yaml", + "hashed_secret": "5ffe533b830f08a0326348a9160afafc8ada44db", + "is_verified": false, + "line_number": 393 + }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "49edc8e5cce3d7f30610b919b21c6722f4553131", "is_verified": false, - "line_number": 1036 + "line_number": 1088 }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "2f4012d62ceff52b17fe028aeb7a5efa6e6e23cf", "is_verified": false, - "line_number": 1038 + "line_number": 1090 }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "6afab4c634af2dd2b9c344a98f96667277c56df0", "is_verified": false, - "line_number": 2202 + "line_number": 2288 }, { "type": "Secret Keyword", "filename": "deploy/template.yaml", "hashed_secret": "38450ffe4ff65a68053ea5083d47521010709df2", "is_verified": false, - "line_number": 2670 + "line_number": 2756 } ], "lambdas/build-user-identity/src/test/java/uk/gov/di/ipv/core/builduseridentity/pact/BuildUserIdentityHandlerTest.java": [ @@ -1921,5 +1935,5 @@ } ] }, - "generated_at": "2024-06-05T13:16:40Z" + "generated_at": "2024-06-05T13:17:18Z" } diff --git a/deploy/template.yaml b/deploy/template.yaml index 22c2c42a8d..aac2b98dc1 100644 --- a/deploy/template.yaml +++ b/deploy/template.yaml @@ -310,20 +310,18 @@ Resources: # checkov:skip=CKV_AWS_120: We are not implementing API Gateway caching at the time. Name: !Sub IPV Core Private API Gateway ${Environment} EndpointConfiguration: - Type: !If [IsDevelopment, REGIONAL, PRIVATE] + Type: PRIVATE VPCEndpointIds: - - !If [IsDevelopment, !Ref AWS::NoValue, Fn::ImportValue: !Sub "${VpcStackName}-ExecuteApiGatewayEndpointId"] + - Fn::ImportValue: !Sub "${VpcStackName}-ExecuteApiGatewayEndpointId" DefinitionBody: openapi: "3.0.3" # workaround to get `sam validate` to work paths: # workaround to get `sam validate` to work /foo: - bar: - baz: thing # workaround to get `sam validate` to work + bar: baz Fn::Transform: Name: "AWS::Include" Parameters: Location: "../openAPI/core-back-internal.yaml" - ApiKeySourceType: !If [ IsDevelopment, HEADER, !Ref AWS::NoValue ] Auth: ResourcePolicy: CustomStatements: @@ -332,18 +330,6 @@ Resources: Principal: '*' Resource: - 'execute-api:/*' - - !If - - IsDevelopment - - Ref: AWS::NoValue - - Action: 'execute-api:Invoke' - Effect: Deny - Principal: '*' - Resource: - - 'execute-api:/*' - Condition: - StringNotEquals: - 'aws:SourceVpce': - Fn::ImportValue: !Sub "${VpcStackName}-ExecuteApiGatewayEndpointId" StageName: !Sub ${Environment} TracingEnabled: true AccessLogSetting: @@ -362,29 +348,77 @@ Resources: "responseLength":"$context.responseLength" } - IpvCorePrivateApiUsagePlan: - Type: AWS::ApiGateway::UsagePlan - Condition: IsDevelopment + IPVCorePrivateAPILogGroup: + Type: AWS::Logs::LogGroup + # checkov:skip=CKV_AWS_158: No need for customer managed keys for short lived logs Properties: - ApiStages: - - ApiId: !Ref IPVCorePrivateAPI - Stage: !Sub ${Environment} + LogGroupName: !If + - IsDevelopment + - !Sub /aws/vendedlogs/apigateway/${AWS::StackName}-CoreBackPrivate-API-GW-AccessLogs + - !Sub /aws/apigateway/${AWS::StackName}-CoreBackPrivate-API-GW-AccessLogs + RetentionInDays: 30 - IpvCorePrivateApiKey: - Type: AWS::ApiGateway::ApiKey - Condition: IsDevelopment + IPVCorePrivateAPILogGroupSubscriptionFilter: + Type: AWS::Logs::SubscriptionFilter + Condition: IsSubscriptionEnviroment Properties: - Enabled: true + DestinationArn: "arn:aws:logs:eu-west-2:885513274347:destination:csls_cw_logs_destination_prodpython" + FilterPattern: "" + LogGroupName: !Ref IPVCorePrivateAPILogGroup - IpvCorePrivateApiUsagePlanKeyAssociation: - Type: AWS::ApiGateway::UsagePlanKey + IPVCoreInternalTestingApi: + Type: AWS::Serverless::Api + Metadata: + cfn-lint: + config: + ignore_checks: + - W3005 # Obsolete DependsOn - issue with API Key resource generated by UsagePlan Condition: IsDevelopment Properties: - KeyId: !Ref IpvCorePrivateApiKey - KeyType: API_KEY - UsagePlanId: !Ref IpvCorePrivateApiUsagePlan + # checkov:skip=CKV_AWS_120: We are not implementing API Gateway caching at the time. + EndpointConfiguration: + Type: REGIONAL + DefinitionBody: + openapi: "3.0.3" # workaround to get `sam validate` to work + paths: # workaround to get `sam validate` to work + /foo: + bar: + baz: thing # workaround to get `sam validate` to work + Fn::Transform: + Name: "AWS::Include" + Parameters: + Location: "../openAPI/core-back-internal.yaml" + ApiKeySourceType: HEADER + Auth: + ApiKeyRequired: true + UsagePlan: + CreateUsagePlan: PER_API + ResourcePolicy: + CustomStatements: + - Action: 'execute-api:Invoke' + Effect: Allow + Principal: '*' + Resource: + - 'execute-api:/*' + StageName: !Sub ${Environment} + TracingEnabled: true + AccessLogSetting: + DestinationArn: !GetAtt IPVCorePrivateAPILogGroup.Arn + Format: >- + { + "requestId":"$context.requestId", + "ip":"$context.identity.sourceIp", + "requestTime":"$context.requestTime", + "httpMethod":"$context.httpMethod", + "path":"$context.path", + "routeKey":"$context.routeKey", + "status":"$context.status", + "protocol":"$context.protocol", + "responseLatency":"$context.responseLatency", + "responseLength":"$context.responseLength" + } - IPVCorePrivateApiTokenFetchPolicy: + IPVCoreInternalTestingApiTokenFetchPolicy: Type: AWS::IAM::ManagedPolicy Condition: IsDevelopment Properties: @@ -396,9 +430,9 @@ Resources: Resource: !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*" - Effect: Allow Action: "apigateway:GET" - Resource: !Sub "arn:aws:apigateway:${AWS::Region}::/apikeys/${IpvCorePrivateApiKey}" + Resource: !Sub "arn:aws:apigateway:${AWS::Region}::/apikeys/${IPVCoreInternalTestingApi.ApiKey}" - IPVCorePrivateApiTokenFetchRole: + IPVCoreInternalTestingApiTokenFetchRole: Type: AWS::IAM::Role Condition: IsDevelopment Properties: @@ -414,10 +448,10 @@ Resources: "token.actions.githubusercontent.com:sub": - "repo:govuk-one-login/ipv-core-back:*" ManagedPolicyArns: - - !Ref IPVCorePrivateApiTokenFetchPolicy + - !Ref IPVCoreInternalTestingApiTokenFetchPolicy # ssl cert - IPVCorePrivateApiSSLCert: + IPVCoreInternalTestingApiSSLCert: Type: AWS::CertificateManager::Certificate Condition: IsDevelopment Properties: @@ -437,7 +471,7 @@ Resources: ValidationMethod: DNS # api domain entries / mapping - IPVCorePrivateApiDomain: + IPVCoreInternalTestingApiDomain: Type: AWS::ApiGatewayV2::DomainName # checkov:skip=CKV_AWS_120: doing it later Condition: IsDevelopment @@ -447,11 +481,11 @@ Resources: - !Sub "internal-api-${Environment}.01.dev.identity.account.gov.uk" - !If [IsDev02, !Sub "internal-api-${Environment}.02.dev.identity.account.gov.uk", !Ref AWS::NoValue] DomainNameConfigurations: - - CertificateArn: !Ref IPVCorePrivateApiSSLCert + - CertificateArn: !Ref IPVCoreInternalTestingApiSSLCert EndpointType: REGIONAL SecurityPolicy: TLS_1_2 - IPVCorePrivateApiMapping: + IPVCoreInternalTestingApiMapping: Type: AWS::ApiGatewayV2::ApiMapping Condition: IsDevelopment Properties: @@ -459,13 +493,13 @@ Resources: - IsDev01 - !Sub "internal-api-${Environment}.01.dev.identity.account.gov.uk" - !If [IsDev02, !Sub "internal-api-${Environment}.02.dev.identity.account.gov.uk", !Ref AWS::NoValue] - ApiId: !Ref IPVCorePrivateAPI - Stage: !Ref IPVCorePrivateAPI.Stage + ApiId: !Ref IPVCoreInternalTestingApi + Stage: !Ref IPVCoreInternalTestingApi.Stage DependsOn: - - IPVCorePrivateApiDomain + - IPVCoreInternalTestingApiDomain # dns record - IPVCorePrivateApiRecord: + IPVCoreInternalTestingApiRecord: Type: AWS::Route53::RecordSet Condition: IsDevelopment Properties: @@ -479,27 +513,17 @@ Resources: - !ImportValue Dev01IdentityHostedZoneId - !If [IsDev02, !ImportValue Dev02IdentityHostedZoneId, DevIdentityHostedZoneId] AliasTarget: - DNSName: !GetAtt IPVCorePrivateApiDomain.RegionalDomainName - HostedZoneId: !GetAtt IPVCorePrivateApiDomain.RegionalHostedZoneId + DNSName: !GetAtt IPVCoreInternalTestingApiDomain.RegionalDomainName + HostedZoneId: !GetAtt IPVCoreInternalTestingApiDomain.RegionalHostedZoneId - IPVCorePrivateAPILogGroup: + IPVCoreInternalTestingApiLogGroup: + Condition: IsDevelopment Type: AWS::Logs::LogGroup # checkov:skip=CKV_AWS_158: No need for customer managed keys for short lived logs Properties: - LogGroupName: !If - - IsDevelopment - - !Sub /aws/vendedlogs/apigateway/${AWS::StackName}-CoreBackPrivate-API-GW-AccessLogs - - !Sub /aws/apigateway/${AWS::StackName}-CoreBackPrivate-API-GW-AccessLogs + LogGroupName: !Sub /aws/apigateway/internal-testing-api-access-logs-${Environment} RetentionInDays: 30 - IPVCorePrivateAPILogGroupSubscriptionFilter: - Type: AWS::Logs::SubscriptionFilter - Condition: IsSubscriptionEnviroment - Properties: - DestinationArn: "arn:aws:logs:eu-west-2:885513274347:destination:csls_cw_logs_destination_prodpython" - FilterPattern: "" - LogGroupName: !Ref IPVCorePrivateAPILogGroup - IPVCoreExternalAPI: Type: AWS::Serverless::Api DependsOn: @@ -583,6 +607,11 @@ Resources: IssueClientAccessTokenFunction: Type: AWS::Serverless::Function + Metadata: + cfn-lint: + config: + ignore_checks: + - W1001 # Ref to resource "IPVCoreInternalTestingApi" that may not be available when condition "IsDevelopment" is False DependsOn: - "IssueClientAccessTokenFunctionLogGroup" Properties: @@ -641,6 +670,13 @@ Resources: RestApiId: !Ref IPVCoreExternalAPI Path: /token Method: POST + IPVCoreInternalTestingApi: + Condition: IsDevelopment + Type: Api + Properties: + RestApiId: !Ref IPVCoreInternalTestingApi + Path: /token + Method: POST AutoPublishAlias: live IssueClientAccessTokenFunctionLogGroup: @@ -738,6 +774,11 @@ Resources: InitialiseIpvSessionFunction: Type: AWS::Serverless::Function + Metadata: + cfn-lint: + config: + ignore_checks: + - W1001 # Ref to resource "IPVCoreInternalTestingApi" that may not be available when condition "IsDevelopment" is False DependsOn: - "InitialiseIpvSessionFunctionLogGroup" Properties: @@ -813,8 +854,14 @@ Resources: IPVCorePrivateAPI: Type: Api Properties: - RestApiId: - Ref: IPVCorePrivateAPI + RestApiId: !Ref IPVCorePrivateAPI + Path: /session/initialise + Method: POST + IPVCoreInternalTestingApi: + Condition: IsDevelopment + Type: Api + Properties: + RestApiId: !Ref IPVCoreInternalTestingApi Path: /session/initialise Method: POST AutoPublishAlias: live @@ -929,6 +976,11 @@ Resources: ProcessCriCallbackFunction: Type: AWS::Serverless::Function + Metadata: + cfn-lint: + config: + ignore_checks: + - W1001 # Ref to resource "IPVCoreInternalTestingApi" that may not be available when condition "IsDevelopment" is False DependsOn: - "ProcessCriCallbackLogGroup" Properties: @@ -1128,8 +1180,14 @@ Resources: IPVCoreCriCallback: Type: Api Properties: - RestApiId: - Ref: IPVCorePrivateAPI + RestApiId: !Ref IPVCorePrivateAPI + Path: /cri/callback + Method: POST + IPVCoreInternalTestingApi: + Condition: IsDevelopment + Type: Api + Properties: + RestApiId: !Ref IPVCoreInternalTestingApi Path: /cri/callback Method: POST AutoPublishAlias: live @@ -1151,6 +1209,11 @@ Resources: BuildUserIdentityFunction: Type: AWS::Serverless::Function + Metadata: + cfn-lint: + config: + ignore_checks: + - W1001 # Ref to resource "IPVCoreInternalTestingApi" that may not be available when condition "IsDevelopment" is False DependsOn: - "BuildUserIdentityFunctionLogGroup" Properties: @@ -1257,8 +1320,14 @@ Resources: IPVCoreExternalAPIUserIdentity: Type: Api Properties: - RestApiId: - Ref: IPVCoreExternalAPI + RestApiId: !Ref IPVCoreExternalAPI + Path: /user-identity + Method: GET + IPVCoreInternalTestingApi: + Condition: IsDevelopment + Type: Api + Properties: + RestApiId: !Ref IPVCoreInternalTestingApi Path: /user-identity Method: GET AutoPublishAlias: live @@ -1378,8 +1447,14 @@ Resources: IPVCorePrivateAPI: Type: Api Properties: - RestApiId: - Ref: IPVCorePrivateAPI + RestApiId: !Ref IPVCorePrivateAPI + Path: /journey/{journeyStep+} + Method: POST + IPVCoreInternalTestingApi: + Condition: IsDevelopment + Type: Api + Properties: + RestApiId: !Ref IPVCoreInternalTestingApi Path: /journey/{journeyStep+} Method: POST Logging: @@ -1638,6 +1713,11 @@ Resources: BuildProvenUserIdentityDetailsFunction: Type: AWS::Serverless::Function + Metadata: + cfn-lint: + config: + ignore_checks: + - W1001 # Ref to resource "IPVCoreInternalTestingApi" that may not be available when condition "IsDevelopment" is False DependsOn: - BuildProvenUserIdentityDetailsFunctionLogGroup Properties: @@ -1698,8 +1778,14 @@ Resources: IPVCorePrivateAPI: Type: Api Properties: - RestApiId: - Ref: IPVCorePrivateAPI + RestApiId: !Ref IPVCorePrivateAPI + Path: /user/proven-identity-details + Method: GET + IPVCoreInternalTestingApi: + Condition: IsDevelopment + Type: Api + Properties: + RestApiId: !Ref IPVCoreInternalTestingApi Path: /user/proven-identity-details Method: GET AutoPublishAlias: live @@ -3214,7 +3300,11 @@ Outputs: Value: !GetAtt LoggingKmsKey.Arn Export: Name: !Sub "CoreBackLoggingKmsKeyArn-${Environment}" - InternalApiKeyId: + InternalTestingApiKeyId: Condition: IsDevelopment Description: The ID of the API key for the internal API, if exposed - Value: !Ref IpvCorePrivateApiKey + Value: !Ref IPVCoreInternalTestingApi.ApiKey + InternalTestingApiTokenFetchRole: + Condition: IsDevelopment + Description: The Arn of the role for GHA to assume to be able to fetch the API token + Value: !GetAtt IPVCoreInternalTestingApiTokenFetchRole.Arn From 6796c1f515e5a2d89a4ed1326a1fb480cc74f5d4 Mon Sep 17 00:00:00 2001 From: Chris Wynne Date: Tue, 4 Jun 2024 16:25:28 +0100 Subject: [PATCH 4/4] PYIC-5872: Remove test workflow This workflow was purely to demonstrate the new gateway was function and that the API key could be retrieved. Removing in a separate commit rather than squashing into the previous commit to keep a record of the workflow in git in case we want to refer to it. --- .github/workflows/dev-env-api-test.yaml | 36 ------------------------- 1 file changed, 36 deletions(-) delete mode 100644 .github/workflows/dev-env-api-test.yaml diff --git a/.github/workflows/dev-env-api-test.yaml b/.github/workflows/dev-env-api-test.yaml deleted file mode 100644 index 151f2c8885..0000000000 --- a/.github/workflows/dev-env-api-test.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: Dev env internal API test - -on: - pull_request: - types: - - opened - - reopened - - ready_for_review - - synchronize - -jobs: - make-api-call: - runs-on: ubuntu-latest - permissions: - id-token: write - steps: - - name: Auth with AWS dev account - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ secrets.PYIC_5872_INTERNAL_API_TOKEN_ACCESS_AWS_ROLE_ARN }} - aws-region: eu-west-2 - - - name: Fetch API token - id: fetch-api-token - run: | - apiToken=$(aws apigateway get-api-key \ - --api-key $(aws cloudformation describe-stacks --stack-name core-back-chrisw | jq -r '.Stacks[0].Outputs | .[] | select(.OutputKey == "InternalTestingApiKeyId") | .OutputValue') \ - --include-value \ - | jq -r .value) - - echo "API_TOKEN=${apiToken}" >> "$GITHUB_OUTPUT" - - - name: Make auth'd API call - env: - API_TOKEN: ${{ steps.fetch-api-token.outputs.API_TOKEN }} - run: curl -v -H "x-api-key:${API_TOKEN}" https://internal-api-dev-chrisw.01.dev.identity.account.gov.uk/user/proven-identity-details