"use strict";
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.AwsClient = void 0;
const awsrequestsigner_1 = require("./awsrequestsigner");
const baseexternalclient_1 = require("./baseexternalclient");
const defaultawssecuritycredentialssupplier_1 = require("./defaultawssecuritycredentialssupplier");
const util_1 = require("../util");
const gaxios_1 = require("gaxios");
/**
 * AWS external account client. This is used for AWS workloads, where
 * AWS STS GetCallerIdentity serialized signed requests are exchanged for
 * GCP access token.
 */
class AwsClient extends baseexternalclient_1.BaseExternalAccountClient {
    environmentId;
    awsSecurityCredentialsSupplier;
    regionalCredVerificationUrl;
    awsRequestSigner;
    region;
    static #DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL = 'https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15';
    /**
     * @deprecated AWS client no validates the EC2 metadata address.
     **/
    static AWS_EC2_METADATA_IPV4_ADDRESS = '169.254.169.254';
    /**
     * @deprecated AWS client no validates the EC2 metadata address.
     **/
    static AWS_EC2_METADATA_IPV6_ADDRESS = 'fd00:ec2::254';
    /**
     * Instantiates an AwsClient instance using the provided JSON
     * object loaded from an external account credentials file.
     * An error is thrown if the credential is not a valid AWS credential.
     * @param options The external account options object typically loaded
     *   from the external account JSON credential file.
     */
    constructor(options) {
        super(options);
        const opts = (0, util_1.originalOrCamelOptions)(options);
        const credentialSource = opts.get('credential_source');
        const awsSecurityCredentialsSupplier = opts.get('aws_security_credentials_supplier');
        // Validate credential sourcing configuration.
        if (!credentialSource && !awsSecurityCredentialsSupplier) {
            throw new Error('A credential source or AWS security credentials supplier must be specified.');
        }
        if (credentialSource && awsSecurityCredentialsSupplier) {
            throw new Error('Only one of credential source or AWS security credentials supplier can be specified.');
        }
        if (awsSecurityCredentialsSupplier) {
            this.awsSecurityCredentialsSupplier = awsSecurityCredentialsSupplier;
            this.regionalCredVerificationUrl =
                AwsClient.#DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL;
            this.credentialSourceType = 'programmatic';
        }
        else {
            const credentialSourceOpts = (0, util_1.originalOrCamelOptions)(credentialSource);
            this.environmentId = credentialSourceOpts.get('environment_id');
            // This is only required if the AWS region is not available in the
            // AWS_REGION or AWS_DEFAULT_REGION environment variables.
            const regionUrl = credentialSourceOpts.get('region_url');
            // This is only required if AWS security credentials are not available in
            // environment variables.
            const securityCredentialsUrl = credentialSourceOpts.get('url');
            const imdsV2SessionTokenUrl = credentialSourceOpts.get('imdsv2_session_token_url');
            this.awsSecurityCredentialsSupplier =
                new defaultawssecuritycredentialssupplier_1.DefaultAwsSecurityCredentialsSupplier({
                    regionUrl: regionUrl,
                    securityCredentialsUrl: securityCredentialsUrl,
                    imdsV2SessionTokenUrl: imdsV2SessionTokenUrl,
                });
            this.regionalCredVerificationUrl = credentialSourceOpts.get('regional_cred_verification_url');
            this.credentialSourceType = 'aws';
            // Data validators.
            this.validateEnvironmentId();
        }
        this.awsRequestSigner = null;
        this.region = '';
    }
    validateEnvironmentId() {
        const match = this.environmentId?.match(/^(aws)(\d+)$/);
        if (!match || !this.regionalCredVerificationUrl) {
            throw new Error('No valid AWS "credential_source" provided');
        }
        else if (parseInt(match[2], 10) !== 1) {
            throw new Error(`aws version "${match[2]}" is not supported in the current build.`);
        }
    }
    /**
     * Triggered when an external subject token is needed to be exchanged for a
     * GCP access token via GCP STS endpoint. This will call the
     * {@link AwsSecurityCredentialsSupplier} to retrieve an AWS region and AWS
     * Security Credentials, then use them to create a signed AWS STS request that
     * can be exchanged for a GCP access token.
     * @return A promise that resolves with the external subject token.
     */
    async retrieveSubjectToken() {
        // Initialize AWS request signer if not already initialized.
        if (!this.awsRequestSigner) {
            this.region = await this.awsSecurityCredentialsSupplier.getAwsRegion(this.supplierContext);
            this.awsRequestSigner = new awsrequestsigner_1.AwsRequestSigner(async () => {
                return this.awsSecurityCredentialsSupplier.getAwsSecurityCredentials(this.supplierContext);
            }, this.region);
        }
        // Generate signed request to AWS STS GetCallerIdentity API.
        // Use the required regional endpoint. Otherwise, the request will fail.
        const options = await this.awsRequestSigner.getRequestOptions({
            ...AwsClient.RETRY_CONFIG,
            url: this.regionalCredVerificationUrl.replace('{region}', this.region),
            method: 'POST',
        });
        // The GCP STS endpoint expects the headers to be formatted as:
        // [
        //   {key: 'x-amz-date', value: '...'},
        //   {key: 'authorization', value: '...'},
        //   ...
        // ]
        // And then serialized as:
        // encodeURIComponent(JSON.stringify({
        //   url: '...',
        //   method: 'POST',
        //   headers: [{key: 'x-amz-date', value: '...'}, ...]
        // }))
        const reformattedHeader = [];
        const extendedHeaders = gaxios_1.Gaxios.mergeHeaders({
            // The full, canonical resource name of the workload identity pool
            // provider, with or without the HTTPS prefix.
            // Including this header as part of the signature is recommended to
            // ensure data integrity.
            'x-goog-cloud-target-resource': this.audience,
        }, options.headers);
        // Reformat header to GCP STS expected format.
        extendedHeaders.forEach((value, key) => reformattedHeader.push({ key, value }));
        // Serialize the reformatted signed request.
        return encodeURIComponent(JSON.stringify({
            url: options.url,
            method: options.method,
            headers: reformattedHeader,
        }));
    }
}
exports.AwsClient = AwsClient;
//# sourceMappingURL=awsclient.js.map