Skip to content

Commit

Permalink
fix(credentials-provider-ini): include general http provider when sou…
Browse files Browse the repository at this point in the history
…rcing EcsContainer credentials (#6132)

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

* chore(credential-providers): pass logger through to CredentialsProviderError

* fix(credential-provider-ini): pass role_arn to credential_source source_profile

* test: add scenario in credential chain integration test

* fix(credential-provider-ini): consistency in tryNextLink=false

* fix(credential-providers): test updates for credential chain logging

* test: suppress logger warnings in integ tests
  • Loading branch information
kuhe authored May 30, 2024
1 parent 6bba0d6 commit 8cdaf0a
Show file tree
Hide file tree
Showing 37 changed files with 344 additions and 142 deletions.
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
24 changes: 15 additions & 9 deletions packages/credential-provider-http/src/fromHttp/requestHelpers.ts
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

0 comments on commit 8cdaf0a

Please sign in to comment.