Skip to content

Commit

Permalink
resolving comments
Browse files Browse the repository at this point in the history
  • Loading branch information
smilkuri committed Feb 5, 2025
1 parent 050bdcb commit d189e56
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 58 deletions.
1 change: 1 addition & 0 deletions packages/credential-providers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@aws-sdk/types": "*",
"@smithy/core": "^3.1.1",
"@smithy/credential-provider-imds": "^4.0.1",
"@smithy/node-config-provider": "^4.0.1",
"@smithy/property-provider": "^4.0.1",
"@smithy/types": "^4.1.0",
"tslib": "^2.6.2"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,59 +1,77 @@
import { fromEnv } from "@aws-sdk/credential-provider-env";
import { fromIni } from "@aws-sdk/credential-provider-ini";
import { defaultProvider } from "@aws-sdk/credential-provider-node";
import type { CredentialProviderOptions, Provider } from "@aws-sdk/types";
import { chain, memoize } from "@smithy/property-provider";
import type { AwsCredentialIdentity } from "@smithy/types";
import { remoteProvider } from "@aws-sdk/credential-provider-node/src/remoteProvider";
import { createCredentialChain } from "@aws-sdk/credential-providers";
import type { RuntimeConfigAwsCredentialIdentityProvider } from "@aws-sdk/types";
import type { AwsCredentialIdentity } from "@aws-sdk/types";
import { CredentialsProviderError } from "@smithy/property-provider";

interface AwsCliV2CompatibleProviderOptions extends CredentialProviderOptions {
interface AwsCliV2CompatibleProviderOptions extends Partial<AwsCredentialIdentity> {
profile?: string;
accessKeyId?: string;
secretAccessKey?: string;
sessionToken?: string;
logger?: Console;
}

/**
* AWS CLI V2 Compatible Credential Provider Chain
* If profile is explicitly provided, uses fromIni with that profile.
* Otherwise, uses a chain of fromEnv and fromNodeProviderChain.
* Custom AWS CLI V2 Compatible Credential Provider Chain.
* Uses dynamic imports and `createCredentialChain` to mimic AWS CLI V2 behavior.
*/
export const fromAwsCliV2CompatibleProviderChain = (
options: AwsCliV2CompatibleProviderOptions = {}
): Provider<AwsCredentialIdentity> => {
const { profile, accessKeyId, secretAccessKey, sessionToken } = options;
export const fromAwsCliV2CompatibleProviderChain =
(_init: AwsCliV2CompatibleProviderOptions = {}): RuntimeConfigAwsCredentialIdentityProvider =>
async ({ callerClientConfig } = {}): Promise<AwsCredentialIdentity> => {
// Merge init with caller's client config (profile/region).
const init: AwsCliV2CompatibleProviderOptions = {
..._init,
...callerClientConfig,
logger: (_init.logger ?? callerClientConfig?.logger ?? console) as Console,
};

return memoize(
async (): Promise<AwsCredentialIdentity> => {
// If explicit credentials are provided in the constructor, use them.
if (accessKeyId && secretAccessKey) {
return {
accessKeyId,
secretAccessKey,
sessionToken, // Optional
};
}
// If profile is explicitly provided, use fromIni directly
if (profile) {
return fromIni({ profile })();
}
init.logger?.debug("@aws-sdk/custom-credential-chain - Initializing credential chain");

// Otherwise, use the chain of providers
const credentials = await chain(fromEnv(), async () => {
return defaultProvider()();
})();
const { profile, ...awsCredentials } = init;

if (!credentials) {
throw new Error("Failed to retrieve valid AWS credentials");
}
// 1. If credentials are explicitly provided, return them.
if (awsCredentials.accessKeyId && awsCredentials.secretAccessKey) {
init.logger?.debug("@aws-sdk/custom-credential-chain - Using credentials from constructor");
return awsCredentials as AwsCredentialIdentity;
}

return credentials;
},
credentialsTreatedAsExpired,
credentialsWillNeedRefresh
);
};
// 2. If a profile is explicitly passed, use `fromIni`.
if (profile) {
init.logger?.debug("@aws-sdk/custom-credential-chain - Using fromIni with profile:", profile);
const { fromIni } = await import("@aws-sdk/credential-provider-ini");
return createCredentialChain(fromIni({ profile }))();
}

export const credentialsTreatedAsExpired = (credentials: AwsCredentialIdentity) =>
credentials?.expiration !== undefined && credentials.expiration.getTime() - Date.now() < 300000;

export const credentialsWillNeedRefresh = (credentials: AwsCredentialIdentity) => credentials?.expiration !== undefined;
init.logger?.debug("@aws-sdk/cli-compatible-chain - Using from custom credential chain.");
return createCredentialChain(
async () => {
init.logger?.debug("@aws-sdk/cli-compatible-chain - Trying fromEnv");
const { fromEnv } = await import("@aws-sdk/credential-provider-env");
return fromEnv()();
},
async () => {
init.logger?.debug("@aws-sdk/cli-compatible-chain - Trying fromTokenFile");
const { fromTokenFile } = await import("@aws-sdk/credential-provider-web-identity");
return fromTokenFile()();
},
async () => {
init.logger?.debug("@aws-sdk/cli-compatible-chain - Trying fromSSO");
const { fromSSO } = await import("@aws-sdk/credential-provider-sso");
return fromSSO()();
},
async () => {
init.logger?.debug("@aws-sdk/cli-compatible-chain- Trying fromProcess");
const { fromProcess } = await import("@aws-sdk/credential-provider-process");
return fromProcess()();
},
async () => {
init.logger?.debug("@aws-sdk/credential-provider-node - defaultProvider::remoteProvider");
return (await remoteProvider(init))();
},
async () => {
init.logger?.debug("@aws-sdk/custom-credential-chain - No valid credentials found. Throwing error.");
throw new CredentialsProviderError("Could not load credentials from any providers", {
tryNextLink: false,
logger: init.logger,
});
}
)();
};
31 changes: 21 additions & 10 deletions packages/credential-providers/src/resolveAwsCliV2Region.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,50 @@
import { MetadataService } from "@aws-sdk/ec2-metadata-service";
import { logger } from "@aws-sdk/smithy-client/dist-types";
import { loadSharedConfigFiles } from "@smithy/shared-ini-file-loader";
import { fromSharedConfigFiles } from "@smithy/node-config-provider";

interface ResolveRegionOptions {
defaultRegion?: string;
profile?: string;
}

/**
* Resolves the AWS region following AWS CLI V2 precedence order.
*/
export const resolveAwsCliV2Region = async (defaultRegion?: string, maybeProfile?: string): Promise<string> => {
const profile = maybeProfile ?? process.env.AWS_PROFILE ?? process.env.AWS_DEFAULT_PROFILE ?? "default";
export const resolveAwsCliV2Region = async ({
defaultRegion,
profile,
}: ResolveRegionOptions): Promise<string | undefined> => {
const resolvedProfile = profile ?? process.env.AWS_PROFILE ?? process.env.AWS_DEFAULT_PROFILE ?? "default";

const region =
process.env.AWS_REGION ||
process.env.AWS_DEFAULT_REGION ||
(await getRegionFromIni(profile)) ||
(await getRegionFromIni(resolvedProfile)) ||
(await regionFromMetadataService());

if (!region) {
const usedProfile = !profile ? "" : ` (profile: "${profile}")`;
const usedProfile = resolvedProfile ? ` (profile: "${resolvedProfile}")` : "";

if (defaultRegion) {
logger.warn(
console.warn(
`Unable to determine AWS region from environment or AWS configuration${usedProfile}, defaulting to '${defaultRegion}'`
);
return defaultRegion;
}
throw new Error(
`Unable to determine AWS region from environment or AWS configuration${usedProfile}. Please specify a region.`

console.warn(
`Unable to determine AWS region from environment or AWS configuration${usedProfile}. Returning undefined.`
);
return undefined;
}

return region;
};

/**
* Fetches the region from the AWS shared config files.
*/
export async function getRegionFromIni(profile: string): Promise<string | undefined> {
const sharedFiles = await loadSharedConfigFiles({ ignoreCache: true });
const sharedFiles = await fromSharedConfigFiles({ ignoreCache: true });
return sharedFiles.configFile?.[profile]?.region || sharedFiles.credentialsFile?.[profile]?.region;
}

Expand Down

0 comments on commit d189e56

Please sign in to comment.