Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(credentials-provider-ini): include general http provider when sourcing EcsContainer credentials #6132

Merged
merged 7 commits into from
May 30, 2024
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CredentialProviderOptions } from "@aws-sdk/types";
import { CredentialsProviderError } from "@smithy/property-provider";
import { AwsCredentialIdentity, Provider } from "@smithy/types";
import { AwsCredentialIdentity, Logger, Provider } from "@smithy/types";

import { CognitoProviderParameters } from "./CognitoProviderParameters";
import { resolveLogins } from "./resolveLogins";
Expand Down Expand Up @@ -30,16 +30,16 @@ export type CognitoIdentityCredentialProvider = Provider<CognitoIdentityCredenti
*/
export function fromCognitoIdentity(parameters: FromCognitoIdentityParameters): CognitoIdentityCredentialProvider {
return async (): Promise<CognitoIdentityCredentials> => {
parameters.logger?.debug("@aws-sdk/credential-provider-cognito-identity", "fromCognitoIdentity");
parameters.logger?.debug("@aws-sdk/credential-provider-cognito-identity - fromCognitoIdentity");
const { GetCredentialsForIdentityCommand, CognitoIdentityClient } = await import("./loadCognitoIdentity");

const {
Credentials: {
AccessKeyId = throwOnMissingAccessKeyId(),
AccessKeyId = throwOnMissingAccessKeyId(parameters.logger),
Expiration,
SecretKey = throwOnMissingSecretKey(),
SecretKey = throwOnMissingSecretKey(parameters.logger),
SessionToken,
} = throwOnMissingCredentials(),
} = throwOnMissingCredentials(parameters.logger),
} = await (
parameters.client ??
new CognitoIdentityClient(
Expand Down Expand Up @@ -76,14 +76,14 @@ export interface FromCognitoIdentityParameters extends CognitoProviderParameters
identityId: string;
}

function throwOnMissingAccessKeyId(): never {
throw new CredentialsProviderError("Response from Amazon Cognito contained no access key ID");
function throwOnMissingAccessKeyId(logger?: Logger): never {
throw new CredentialsProviderError("Response from Amazon Cognito contained no access key ID", { logger });
}

function throwOnMissingCredentials(): never {
throw new CredentialsProviderError("Response from Amazon Cognito contained no credentials");
function throwOnMissingCredentials(logger?: Logger): never {
throw new CredentialsProviderError("Response from Amazon Cognito contained no credentials", { logger });
}

function throwOnMissingSecretKey(): never {
throw new CredentialsProviderError("Response from Amazon Cognito contained no secret key");
function throwOnMissingSecretKey(logger?: Logger): never {
throw new CredentialsProviderError("Response from Amazon Cognito contained no secret key", { logger });
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { CredentialProviderOptions } from "@aws-sdk/types";
import { CredentialsProviderError } from "@smithy/property-provider";
import { Logger } from "@smithy/types";

import { CognitoProviderParameters } from "./CognitoProviderParameters";
import { CognitoIdentityCredentialProvider, fromCognitoIdentity } from "./fromCognitoIdentity";
Expand Down Expand Up @@ -29,7 +30,7 @@ export function fromCognitoIdentityPool({
logger,
parentClientConfig,
}: FromCognitoIdentityPoolParameters): CognitoIdentityCredentialProvider {
logger?.debug("@aws-sdk/credential-provider-cognito-identity", "fromCognitoIdentity");
logger?.debug("@aws-sdk/credential-provider-cognito-identity - fromCognitoIdentity");
const cacheKey: string | undefined = userIdentifier
? `aws:cognito-identity-credentials:${identityPoolId}:${userIdentifier}`
: undefined;
Expand All @@ -44,7 +45,7 @@ export function fromCognitoIdentityPool({

let identityId: string | undefined = (cacheKey && (await cache.getItem(cacheKey))) as string | undefined;
if (!identityId) {
const { IdentityId = throwOnMissingId() } = await _client.send(
const { IdentityId = throwOnMissingId(logger) } = await _client.send(
new GetIdCommand({
AccountId: accountId,
IdentityPoolId: identityPoolId,
Expand Down Expand Up @@ -116,6 +117,6 @@ export interface FromCognitoIdentityPoolParameters extends CognitoProviderParame
userIdentifier?: string;
}

function throwOnMissingId(): never {
throw new CredentialsProviderError("Response from Amazon Cognito contained no identity ID");
function throwOnMissingId(logger?: Logger): never {
throw new CredentialsProviderError("Response from Amazon Cognito contained no identity ID", { logger });
}
4 changes: 2 additions & 2 deletions packages/credential-provider-env/src/fromEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const ENV_CREDENTIAL_SCOPE = "AWS_CREDENTIAL_SCOPE";
export const fromEnv =
(init?: FromEnvInit): AwsCredentialIdentityProvider =>
async () => {
init?.logger?.debug("@aws-sdk/credential-provider-env", "fromEnv");
init?.logger?.debug("@aws-sdk/credential-provider-env - fromEnv");
const accessKeyId: string | undefined = process.env[ENV_KEY];
const secretAccessKey: string | undefined = process.env[ENV_SECRET];
const sessionToken: string | undefined = process.env[ENV_SESSION];
Expand All @@ -52,5 +52,5 @@ export const fromEnv =
};
}

throw new CredentialsProviderError("Unable to find environment variable credentials.");
throw new CredentialsProviderError("Unable to find environment variable credentials.", { logger: init?.logger });
};
7 changes: 5 additions & 2 deletions packages/credential-provider-http/src/fromHttp/checkUrl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CredentialsProviderError } from "@smithy/property-provider";
import { Logger } from "@smithy/types";

/**
* @internal
Expand Down Expand Up @@ -28,9 +29,10 @@ const EKS_CONTAINER_HOST_IPv6 = "[fd00:ec2::23]";
* @internal
*
* @param url - to be validated.
* @param logger - passed to CredentialsProviderError.
* @throws if not acceptable to this provider.
*/
export const checkUrl = (url: URL): void => {
export const checkUrl = (url: URL, logger?: Logger): void => {
if (url.protocol === "https:") {
// no additional requirements for HTTPS.
return;
Expand Down Expand Up @@ -74,6 +76,7 @@ export const checkUrl = (url: URL): void => {
`URL not accepted. It must either be HTTPS or match one of the following:
- loopback CIDR 127.0.0.0/8 or [::1/128]
- ECS container host 169.254.170.2
- EKS container host 169.254.170.23 or [fd00:ec2::23]`
- EKS container host 169.254.170.23 or [fd00:ec2::23]`,
{ logger }
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ import { retryWrapper } from "./retry-wrapper";
/**
* Creates a provider that gets credentials via HTTP request.
*/
export const fromHttp = (options: FromHttpOptions): AwsCredentialIdentityProvider => {
options.logger?.debug("@aws-sdk/credential-provider-http", "fromHttp");
export const fromHttp = (options: FromHttpOptions = {}): AwsCredentialIdentityProvider => {
options.logger?.debug("@aws-sdk/credential-provider-http - fromHttp");
let host: string;

const full = options.credentialsFullUri;

if (full) {
host = full;
} else {
throw new CredentialsProviderError("No HTTP credential provider host provided.");
throw new CredentialsProviderError("No HTTP credential provider host provided.", { logger: options.logger });
}

// throws if invalid format.
const url = new URL(host);

// throws if not to spec for provider.
checkUrl(url);
checkUrl(url, options.logger);

const requestHandler = new FetchHttpHandler();

Expand Down
30 changes: 17 additions & 13 deletions packages/credential-provider-http/src/fromHttp/fromHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,32 @@ const AWS_CONTAINER_AUTHORIZATION_TOKEN = "AWS_CONTAINER_AUTHORIZATION_TOKEN";
/**
* Creates a provider that gets credentials via HTTP request.
*/
export const fromHttp = (options: FromHttpOptions): AwsCredentialIdentityProvider => {
options.logger?.debug("@aws-sdk/credential-provider-http", "fromHttp");
export const fromHttp = (options: FromHttpOptions = {}): AwsCredentialIdentityProvider => {
options.logger?.debug("@aws-sdk/credential-provider-http - fromHttp");
let host: string;

const relative = options.awsContainerCredentialsRelativeUri ?? process.env[AWS_CONTAINER_CREDENTIALS_RELATIVE_URI];
const full = options.awsContainerCredentialsFullUri ?? process.env[AWS_CONTAINER_CREDENTIALS_FULL_URI];
const token = options.awsContainerAuthorizationToken ?? process.env[AWS_CONTAINER_AUTHORIZATION_TOKEN];
const tokenFile = options.awsContainerAuthorizationTokenFile ?? process.env[AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE];

const warn: (warning: string) => void =
options.logger?.constructor?.name === "NoOpLogger" || !options.logger ? console.warn : options.logger.warn;

if (relative && full) {
console.warn(
"AWS SDK HTTP credentials provider:",
"you have set both awsContainerCredentialsRelativeUri and awsContainerCredentialsFullUri."
warn(
"@aws-sdk/credential-provider-http: " +
"you have set both awsContainerCredentialsRelativeUri and awsContainerCredentialsFullUri."
);
console.warn("awsContainerCredentialsFullUri will take precedence.");
warn("awsContainerCredentialsFullUri will take precedence.");
}

if (token && tokenFile) {
console.warn(
"AWS SDK HTTP credentials provider:",
"you have set both awsContainerAuthorizationToken and awsContainerAuthorizationTokenFile."
warn(
"@aws-sdk/credential-provider-http: " +
"you have set both awsContainerAuthorizationToken and awsContainerAuthorizationTokenFile."
);
console.warn("awsContainerAuthorizationToken will take precedence.");
warn("awsContainerAuthorizationToken will take precedence.");
}

if (full) {
Expand All @@ -49,15 +52,16 @@ export const fromHttp = (options: FromHttpOptions): AwsCredentialIdentityProvide
} else {
throw new CredentialsProviderError(
`No HTTP credential provider host provided.
Set AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.`
Set AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.`,
{ logger: options.logger }
);
}

// throws if invalid format.
const url = new URL(host);

// throws if not to spec for provider.
checkUrl(url);
checkUrl(url, options.logger);

const requestHandler = new NodeHttpHandler({
requestTimeout: options.timeout ?? 1000,
Expand All @@ -79,7 +83,7 @@ Set AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
const result = await requestHandler.handle(request);
return getCredentials(result.response);
} catch (e: unknown) {
throw new CredentialsProviderError(String(e));
throw new CredentialsProviderError(String(e), { logger: options.logger });
}
},
options.maxRetries ?? 3,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AwsCredentialIdentity } from "@aws-sdk/types";
import { CredentialsProviderError } from "@smithy/property-provider";
import { HttpRequest } from "@smithy/protocol-http";
import { parseRfc3339DateTime } from "@smithy/smithy-client";
import { HttpResponse } from "@smithy/types";
import { HttpResponse, Logger } from "@smithy/types";
import { sdkStreamMixin } from "@smithy/util-stream";

import { HttpProviderCredentials } from "./fromHttpTypes";
Expand All @@ -27,11 +27,13 @@ export function createGetRequest(url: URL): HttpRequest {
/**
* @internal
*/
export async function getCredentials(response: HttpResponse): Promise<AwsCredentialIdentity> {
export async function getCredentials(response: HttpResponse, logger?: Logger): Promise<AwsCredentialIdentity> {
const contentType = response?.headers["content-type"] ?? response?.headers["Content-Type"] ?? "";

if (!contentType.includes("json")) {
console.warn(
const warn: (warning: string) => void =
logger?.constructor?.name === "NoOpLogger" || !logger ? console.warn : logger.warn;
warn(
"HTTP credential provider response header content-type was not application/json. Observed: " + contentType + "."
);
}
Expand All @@ -50,7 +52,8 @@ export async function getCredentials(response: HttpResponse): Promise<AwsCredent
) {
throw new CredentialsProviderError(
"HTTP credential provider response not of the required format, an object matching: " +
"{ AccessKeyId: string, SecretAccessKey: string, Token: string, Expiration: string(rfc3339) }"
"{ AccessKeyId: string, SecretAccessKey: string, Token: string, Expiration: string(rfc3339) }",
{ logger }
);
}

Expand All @@ -67,10 +70,13 @@ export async function getCredentials(response: HttpResponse): Promise<AwsCredent
parsedBody = JSON.parse(str);
} catch (e) {}

throw Object.assign(new CredentialsProviderError(`Server responded with status: ${response.statusCode}`), {
Code: parsedBody.Code,
Message: parsedBody.Message,
});
throw Object.assign(
new CredentialsProviderError(`Server responded with status: ${response.statusCode}`, { logger }),
{
Code: parsedBody.Code,
Message: parsedBody.Message,
}
);
}
throw new CredentialsProviderError(`Server responded with status: ${response.statusCode}`);
throw new CredentialsProviderError(`Server responded with status: ${response.statusCode}`, { logger });
}
1 change: 1 addition & 0 deletions packages/credential-provider-ini/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/credential-provider-env": "*",
"@aws-sdk/credential-provider-http": "*",
"@aws-sdk/credential-provider-process": "*",
"@aws-sdk/credential-provider-sso": "*",
"@aws-sdk/credential-provider-web-identity": "*",
Expand Down
2 changes: 1 addition & 1 deletion packages/credential-provider-ini/src/fromIni.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export interface FromIniInit extends SourceProfileInit, CredentialProviderOption
export const fromIni =
(init: FromIniInit = {}): AwsCredentialIdentityProvider =>
async () => {
init.logger?.debug("@aws-sdk/credential-provider-ini", "fromIni");
init.logger?.debug("@aws-sdk/credential-provider-ini - fromIni");
const profiles = await parseKnownFiles(init);
return resolveProfileData(getProfileName(init), profiles, init);
};
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ describe(resolveAssumeRoleCredentials.name, () => {
source_profile: mockProfileName,
...mockRoleAssumeParams,
},
[mockProfileName]: {},
[mockProfileName]: {
role_arn: "mock_role_arn",
},
};

const receivedCreds = await resolveAssumeRoleCredentials(mockProfileCurrent, mockProfilesWithSource, mockOptions);
Expand All @@ -186,7 +188,7 @@ describe(resolveAssumeRoleCredentials.name, () => {
const receivedCreds = await resolveAssumeRoleCredentials(mockProfileName, mockProfilesWithCredSource, mockOptions);
expect(receivedCreds).toStrictEqual(mockCreds);
expect(resolveProfileData).not.toHaveBeenCalled();
expect(resolveCredentialSource).toHaveBeenCalledWith(mockCredentialSource, mockProfileName);
expect(resolveCredentialSource).toHaveBeenCalledWith(mockCredentialSource, mockProfileName, undefined);
expect(mockOptions.roleAssumer).toHaveBeenCalledWith(mockSourceCredsFromCredential, {
RoleArn: mockRoleAssumeParams.role_arn,
RoleSessionName: mockRoleAssumeParams.role_session_name,
Expand Down
Loading
Loading