Skip to content

Commit

Permalink
feat(credential-provider-node): throw cannot load credential error fr…
Browse files Browse the repository at this point in the history
…om credentail chain (#2408)

* feat(credential-provider-node): throw no credential found error in chain

* fix(credential-provider-imds): continue privder chain when IMDS times out

* feat(credentials): deprecate ProviderError and use CredentialsProviderError

Co-authored-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com>
  • Loading branch information
AllanZhengYP and trivikr authored Jun 16, 2021
1 parent 6d4e6b5 commit 5e0a46a
Show file tree
Hide file tree
Showing 26 changed files with 213 additions and 141 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GetCredentialsForIdentityCommand } from "@aws-sdk/client-cognito-identity";
import { ProviderError } from "@aws-sdk/property-provider";
import { CredentialsProviderError } from "@aws-sdk/property-provider";

import { fromCognitoIdentity } from "./fromCognitoIdentity";

Expand Down Expand Up @@ -76,7 +76,7 @@ describe("fromCognitoIdentity", () => {
identityId,
customRoleArn: "myArn",
})()
).rejects.toMatchObject(new ProviderError("Response from Amazon Cognito contained no credentials"));
).rejects.toMatchObject(new CredentialsProviderError("Response from Amazon Cognito contained no credentials"));
});

it("should convert a GetCredentialsForIdentity response without an access key ID to a provider error", async () => {
Expand All @@ -93,7 +93,7 @@ describe("fromCognitoIdentity", () => {
identityId,
customRoleArn: "myArn",
})()
).rejects.toMatchObject(new ProviderError("Response from Amazon Cognito contained no access key ID"));
).rejects.toMatchObject(new CredentialsProviderError("Response from Amazon Cognito contained no access key ID"));
});

it("should convert a GetCredentialsForIdentity response without a secret key to a provider error", async () => {
Expand All @@ -110,6 +110,6 @@ describe("fromCognitoIdentity", () => {
identityId,
customRoleArn: "myArn",
})()
).rejects.toMatchObject(new ProviderError("Response from Amazon Cognito contained no secret key"));
).rejects.toMatchObject(new CredentialsProviderError("Response from Amazon Cognito contained no secret key"));
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GetCredentialsForIdentityCommand } from "@aws-sdk/client-cognito-identity";
import { ProviderError } from "@aws-sdk/property-provider";
import { CredentialsProviderError } from "@aws-sdk/property-provider";
import { Credentials, Provider } from "@aws-sdk/types";

import { CognitoProviderParameters } from "./CognitoProviderParameters";
Expand Down Expand Up @@ -56,13 +56,13 @@ export interface FromCognitoIdentityParameters extends CognitoProviderParameters
}

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

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

function throwOnMissingSecretKey(): never {
throw new ProviderError("Response from Amazon Cognito contained no secret key");
throw new CredentialsProviderError("Response from Amazon Cognito contained no secret key");
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GetIdCommand } from "@aws-sdk/client-cognito-identity";
import { ProviderError } from "@aws-sdk/property-provider";
import { CredentialsProviderError } from "@aws-sdk/property-provider";

import { fromCognitoIdentityPool } from "./fromCognitoIdentityPool";

Expand Down Expand Up @@ -127,7 +127,7 @@ describe("fromCognitoIdentityPool", () => {
client: mockClient,
identityPoolId,
})()
).rejects.toMatchObject(new ProviderError("Response from Amazon Cognito contained no identity ID"));
).rejects.toMatchObject(new CredentialsProviderError("Response from Amazon Cognito contained no identity ID"));
});

it("should allow injecting a custom cache", async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GetIdCommand } from "@aws-sdk/client-cognito-identity";
import { ProviderError } from "@aws-sdk/property-provider";
import { CredentialsProviderError } from "@aws-sdk/property-provider";

import { CognitoProviderParameters } from "./CognitoProviderParameters";
import { CognitoIdentityCredentialProvider, fromCognitoIdentity } from "./fromCognitoIdentity";
Expand Down Expand Up @@ -99,5 +99,5 @@ export interface FromCognitoIdentityPoolParameters extends CognitoProviderParame
}

function throwOnMissingId(): never {
throw new ProviderError("Response from Amazon Cognito contained no identity ID");
throw new CredentialsProviderError("Response from Amazon Cognito contained no identity ID");
}
6 changes: 3 additions & 3 deletions packages/credential-provider-env/src/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ProviderError } from "@aws-sdk/property-provider";
import { CredentialsProviderError } from "@aws-sdk/property-provider";

import { ENV_EXPIRATION, ENV_KEY, ENV_SECRET, ENV_SESSION, fromEnv } from "./";

Expand Down Expand Up @@ -49,7 +49,7 @@ describe("fromEnv", () => {

it("should reject the promise if no environmental credentials can be found", async () => {
await expect(fromEnv()()).rejects.toMatchObject(
new ProviderError("Unable to find environment variable credentials.")
new CredentialsProviderError("Unable to find environment variable credentials.")
);
});

Expand All @@ -59,7 +59,7 @@ describe("fromEnv", () => {
throw new Error("The promise should have been rejected.");
},
(err) => {
expect((err as ProviderError).tryNextLink).toBe(true);
expect((err as CredentialsProviderError).tryNextLink).toBe(true);
}
);
});
Expand Down
4 changes: 2 additions & 2 deletions packages/credential-provider-env/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ProviderError } from "@aws-sdk/property-provider";
import { CredentialsProviderError } from "@aws-sdk/property-provider";
import { CredentialProvider } from "@aws-sdk/types";

export const ENV_KEY = "AWS_ACCESS_KEY_ID";
Expand All @@ -25,6 +25,6 @@ export function fromEnv(): CredentialProvider {
});
}

return Promise.reject(new ProviderError("Unable to find environment variable credentials."));
return Promise.reject(new CredentialsProviderError("Unable to find environment variable credentials."));
};
}
16 changes: 11 additions & 5 deletions packages/credential-provider-imds/src/fromContainerMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ProviderError } from "@aws-sdk/property-provider";
import { CredentialsProviderError } from "@aws-sdk/property-provider";
import { CredentialProvider } from "@aws-sdk/types";
import { RequestOptions } from "http";
import { parse } from "url";
Expand All @@ -23,7 +23,7 @@ export const fromContainerMetadata = (init: RemoteProviderInit = {}): Credential
const requestOptions = await getCmdsUri();
const credsResponse = JSON.parse(await requestFromEcsImds(timeout, requestOptions));
if (!isImdsCredentials(credsResponse)) {
throw new ProviderError("Invalid response received from instance metadata service.");
throw new CredentialsProviderError("Invalid response received from instance metadata service.");
}
return fromImdsCredentials(credsResponse);
}, maxRetries);
Expand Down Expand Up @@ -65,11 +65,17 @@ const getCmdsUri = async (): Promise<RequestOptions> => {
if (process.env[ENV_CMDS_FULL_URI]) {
const parsed = parse(process.env[ENV_CMDS_FULL_URI]!);
if (!parsed.hostname || !(parsed.hostname in GREENGRASS_HOSTS)) {
throw new ProviderError(`${parsed.hostname} is not a valid container metadata service hostname`, false);
throw new CredentialsProviderError(
`${parsed.hostname} is not a valid container metadata service hostname`,
false
);
}

if (!parsed.protocol || !(parsed.protocol in GREENGRASS_PROTOCOLS)) {
throw new ProviderError(`${parsed.protocol} is not a valid container metadata service protocol`, false);
throw new CredentialsProviderError(
`${parsed.protocol} is not a valid container metadata service protocol`,
false
);
}

return {
Expand All @@ -78,7 +84,7 @@ const getCmdsUri = async (): Promise<RequestOptions> => {
};
}

throw new ProviderError(
throw new CredentialsProviderError(
"The container metadata credential provider cannot be used unless" +
` the ${ENV_CMDS_RELATIVE_URI} or ${ENV_CMDS_FULL_URI} environment` +
" variable is set",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ProviderError } from "@aws-sdk/property-provider";
import { CredentialsProviderError } from "@aws-sdk/property-provider";

import { fromInstanceMetadata } from "./fromInstanceMetadata";
import { httpRequest } from "./remoteProvider/httpRequest";
Expand Down Expand Up @@ -124,7 +124,7 @@ describe("fromInstanceMetadata", () => {
expect((retry as jest.Mock).mock.calls[1][1]).toBe(mockMaxRetries);
});

it("throws ProviderError if credentials returned are incorrect", async () => {
it("throws CredentialsProviderError if credentials returned are incorrect", async () => {
(httpRequest as jest.Mock)
.mockResolvedValueOnce(mockToken)
.mockResolvedValueOnce(mockProfile)
Expand All @@ -134,7 +134,7 @@ describe("fromInstanceMetadata", () => {
(isImdsCredentials as unknown as jest.Mock).mockReturnValueOnce(false);

await expect(fromInstanceMetadata()()).rejects.toEqual(
new ProviderError("Invalid response received from instance metadata service.")
new CredentialsProviderError("Invalid response received from instance metadata service.")
);
expect(retry).toHaveBeenCalledTimes(2);
expect(httpRequest).toHaveBeenCalledTimes(3);
Expand Down
4 changes: 2 additions & 2 deletions packages/credential-provider-imds/src/fromInstanceMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ProviderError } from "@aws-sdk/property-provider";
import { CredentialsProviderError } from "@aws-sdk/property-provider";
import { CredentialProvider, Credentials } from "@aws-sdk/types";
import { RequestOptions } from "http";

Expand Down Expand Up @@ -103,7 +103,7 @@ const getCredentialsFromProfile = async (profile: string, options: RequestOption
);

if (!isImdsCredentials(credsResponse)) {
throw new ProviderError("Invalid response received from instance metadata service.");
throw new CredentialsProviderError("Invalid response received from instance metadata service.");
}

return fromImdsCredentials(credsResponse);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ describe("httpRequest", () => {
.delay(timeout * 2)
.reply(200, "expectedResponse");

await expect(httpRequest({ host, path, port, timeout })).rejects.toStrictEqual(new Error("TimeoutError"));
await expect(httpRequest({ host, path, port, timeout })).rejects.toStrictEqual(
new ProviderError("TimeoutError from instance metadata service")
);

scope.done();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function httpRequest(options: RequestOptions): Promise<Buffer> {
});

req.on("timeout", () => {
reject(new Error("TimeoutError"));
reject(new ProviderError("TimeoutError from instance metadata service"));
});

req.on("response", (res: IncomingMessage) => {
Expand Down
14 changes: 8 additions & 6 deletions packages/credential-provider-ini/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { fromEnv } from "@aws-sdk/credential-provider-env";
import { fromContainerMetadata, fromInstanceMetadata } from "@aws-sdk/credential-provider-imds";
import { AssumeRoleWithWebIdentityParams, fromTokenFile } from "@aws-sdk/credential-provider-web-identity";
import { ProviderError } from "@aws-sdk/property-provider";
import { CredentialsProviderError } from "@aws-sdk/property-provider";
import {
loadSharedConfigFiles,
ParsedIniData,
Expand Down Expand Up @@ -203,14 +203,14 @@ const resolveProfileData = async (
} = data;

if (!options.roleAssumer) {
throw new ProviderError(
throw new CredentialsProviderError(
`Profile ${profileName} requires a role to be assumed, but no` + ` role assumption callback was provided.`,
false
);
}

if (source_profile && source_profile in visitedProfiles) {
throw new ProviderError(
throw new CredentialsProviderError(
`Detected a cycle attempting to resolve credentials for profile` +
` ${getMasterProfileName(options)}. Profiles visited: ` +
Object.keys(visitedProfiles).join(", "),
Expand All @@ -228,7 +228,7 @@ const resolveProfileData = async (
const params: AssumeRoleParams = { RoleArn, RoleSessionName, ExternalId };
if (mfa_serial) {
if (!options.mfaCodeProvider) {
throw new ProviderError(
throw new CredentialsProviderError(
`Profile ${profileName} requires multi-factor authentication,` + ` but no MFA code callback was provided.`,
false
);
Expand Down Expand Up @@ -257,7 +257,9 @@ const resolveProfileData = async (
// terminal resolution error if a profile has been specified by the user
// (whether via a parameter, an environment variable, or another profile's
// `source_profile` key).
throw new ProviderError(`Profile ${profileName} could not be found or parsed in shared` + ` credentials file.`);
throw new CredentialsProviderError(
`Profile ${profileName} could not be found or parsed in shared` + ` credentials file.`
);
};

/**
Expand All @@ -276,7 +278,7 @@ const resolveCredentialSource = (credentialSource: string, profileName: string):
if (credentialSource in sourceProvidersMap) {
return sourceProvidersMap[credentialSource]();
} else {
throw new ProviderError(
throw new CredentialsProviderError(
`Unsupported credential source in profile ${profileName}. Got ${credentialSource}, ` +
`expected EcsContainer or Ec2InstanceMetadata or Environment.`
);
Expand Down
Loading

0 comments on commit 5e0a46a

Please sign in to comment.