Skip to content

feat(credential-provider-imds): accept custom logger #3409

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

Merged
merged 3 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const IMDS_TOKEN_PATH = "/latest/api/token";
* Instance Metadata Service
*/
export const fromInstanceMetadata = (init: RemoteProviderInit = {}): Provider<InstanceMetadataCredentials> =>
staticStabilityProvider(getInstanceImdsProvider(init));
staticStabilityProvider(getInstanceImdsProvider(init), { logger: init.logger });

const getInstanceImdsProvider = (init: RemoteProviderInit) => {
// when set to true, metadata service will not fetch token
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Logger } from "@aws-sdk/types";

export const DEFAULT_TIMEOUT = 1000;

// The default in AWS SDK for Python and CLI (botocore) is no retry or one attempt
Expand All @@ -16,7 +18,9 @@ export interface RemoteProviderConfig {
maxRetries: number;
}

export type RemoteProviderInit = Partial<RemoteProviderConfig>;
export interface RemoteProviderInit extends Partial<RemoteProviderConfig> {
logger?: Logger;
}

export const providerConfigFromInit = ({
maxRetries = DEFAULT_MAX_RETRIES,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Logger } from "@aws-sdk/types";

import { getExtendedInstanceMetadataCredentials } from "./getExtendedInstanceMetadataCredentials";

describe("getExtendedInstanceMetadataCredentials()", () => {
Expand All @@ -6,9 +8,11 @@ describe("getExtendedInstanceMetadataCredentials()", () => {
accessKeyId: "key",
secretAccessKey: "secret",
};
const logger: Logger = {
warn: jest.fn(),
} as any;

beforeEach(() => {
jest.spyOn(global.console, "warn").mockImplementation(() => {});
jest.spyOn(global.Math, "random");
nowMock = jest.spyOn(Date, "now").mockReturnValueOnce(new Date("2022-02-22T00:00:00Z").getTime());
});
Expand All @@ -20,7 +24,7 @@ describe("getExtendedInstanceMetadataCredentials()", () => {
it("should extend the expiration random time(~15 mins) from now", () => {
const anyDate: Date = "any date" as unknown as Date;
(Math.random as jest.Mock).mockReturnValue(0.5);
expect(getExtendedInstanceMetadataCredentials({ ...staticSecret, expiration: anyDate })).toEqual({
expect(getExtendedInstanceMetadataCredentials({ ...staticSecret, expiration: anyDate }, logger)).toEqual({
...staticSecret,
originalExpiration: anyDate,
expiration: new Date("2022-02-22T00:17:30Z"),
Expand All @@ -30,8 +34,7 @@ describe("getExtendedInstanceMetadataCredentials()", () => {

it("should print warning message when extending the credentials", () => {
const anyDate: Date = "any date" as unknown as Date;
getExtendedInstanceMetadataCredentials({ ...staticSecret, expiration: anyDate });
// TODO: fill the doc link
expect(console.warn).toBeCalledWith(expect.stringContaining("Attempting credential expiration extension"));
getExtendedInstanceMetadataCredentials({ ...staticSecret, expiration: anyDate }, logger);
expect(logger.warn).toBeCalledWith(expect.stringContaining("Attempting credential expiration extension"));
});
});
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { Logger } from "@aws-sdk/types";

import { InstanceMetadataCredentials } from "../types";

const STATIC_STABILITY_REFRESH_INTERVAL_SECONDS = 15 * 60;
const STATIC_STABILITY_REFRESH_INTERVAL_JITTER_WINDOW_SECONDS = 5 * 60;
// TODO
const STATIC_STABILITY_DOC_URL = "https://docs.aws.amazon.com/sdkref/latest/guide/feature-static-credentials.html";

export const getExtendedInstanceMetadataCredentials = (
credentials: InstanceMetadataCredentials
credentials: InstanceMetadataCredentials,
logger: Logger
): InstanceMetadataCredentials => {
const refreshInterval =
STATIC_STABILITY_REFRESH_INTERVAL_SECONDS +
Math.floor(Math.random() * STATIC_STABILITY_REFRESH_INTERVAL_JITTER_WINDOW_SECONDS);
const newExpiration = new Date(Date.now() + refreshInterval * 1000);
// ToDo: Call warn function on logger from configuration
console.warn(
logger.warn(
"Attempting credential expiration extension due to a credential service availability issue. A refresh of these " +
"credentials will be attempted after ${new Date(newExpiration)}.\nFor more information, please visit: " +
STATIC_STABILITY_DOC_URL
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Logger } from "@aws-sdk/types";

import { getExtendedInstanceMetadataCredentials } from "./getExtendedInstanceMetadataCredentials";
import { staticStabilityProvider } from "./staticStabilityProvider";

Expand Down Expand Up @@ -84,4 +86,14 @@ describe("staticStabilityProvider", () => {
expect(getExtendedInstanceMetadataCredentials).toBeCalledTimes(repeat);
expect(console.warn).not.toBeCalled();
});

it("should allow custom logger to print warning messages", async () => {
const provider = jest.fn().mockResolvedValueOnce(mockCreds).mockRejectedValue("Error");
const logger = { warn: jest.fn() } as unknown as Logger;
const stableProvider = staticStabilityProvider(provider, { logger });
expect(await stableProvider()).toEqual(mockCreds); // load initial creds
await stableProvider();
expect(logger.warn).toBeCalledTimes(1);
expect(console.warn).toBeCalledTimes(0);
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Credentials, Provider } from "@aws-sdk/types";
import { Credentials, Logger, Provider } from "@aws-sdk/types";

import { InstanceMetadataCredentials } from "../types";
import { getExtendedInstanceMetadataCredentials } from "./getExtendedInstanceMetadataCredentials";
Expand All @@ -13,21 +13,26 @@ import { getExtendedInstanceMetadataCredentials } from "./getExtendedInstanceMet
* @returns A credential provider that supports static stability
*/
export const staticStabilityProvider = (
provider: Provider<InstanceMetadataCredentials>
provider: Provider<InstanceMetadataCredentials>,
options: {
logger?: Logger;
} = {}
): Provider<InstanceMetadataCredentials> => {
// Unlike normal SDK logger message, the key extension message must be transparent to users.
// When customer doesn't supply a custom logger, we need to log the warnings to console.
const logger = options?.logger || console;
let pastCredentials: InstanceMetadataCredentials;
return async () => {
let credentials: InstanceMetadataCredentials;
try {
credentials = await provider();
if (credentials.expiration && credentials.expiration.getTime() < Date.now()) {
credentials = getExtendedInstanceMetadataCredentials(credentials);
credentials = getExtendedInstanceMetadataCredentials(credentials, logger);
}
} catch (e) {
if (pastCredentials) {
// ToDo: Call warn function on logger from configuration
console.warn("Credential renew failed: ", e);
credentials = getExtendedInstanceMetadataCredentials(pastCredentials);
logger.warn("Credential renew failed: ", e);
credentials = getExtendedInstanceMetadataCredentials(pastCredentials, logger);
} else {
throw e;
}
Expand Down
1 change: 0 additions & 1 deletion packages/middleware-logger/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"tslib": "^2.3.0"
},
"devDependencies": {
"@aws-sdk/protocol-http": "*",
"@tsconfig/recommended": "1.0.1",
"@types/node": "^10.0.0",
"concurrently": "7.0.0",
Expand Down
1 change: 0 additions & 1 deletion packages/middleware-logger/src/loggerMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { HttpResponse } from "@aws-sdk/protocol-http";
import {
AbsoluteLocation,
HandlerExecutionContext,
Expand Down
12 changes: 10 additions & 2 deletions packages/middleware-signing/src/configurations.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { memoize } from "@aws-sdk/property-provider";
import { SignatureV4, SignatureV4CryptoInit, SignatureV4Init } from "@aws-sdk/signature-v4";
import { Credentials, HashConstructor, Provider, RegionInfo, RegionInfoProvider, RequestSigner } from "@aws-sdk/types";
import { options } from "yargs";
import {
Credentials,
HashConstructor,
Logger,
Provider,
RegionInfo,
RegionInfoProvider,
RequestSigner,
} from "@aws-sdk/types";

// 5 minutes buffer time the refresh the credential before it really expires
const CREDENTIAL_EXPIRE_WINDOW = 300000;
Expand Down Expand Up @@ -83,6 +90,7 @@ interface SigV4PreviouslyResolved {
region: string | Provider<string>;
signingName: string;
sha256: HashConstructor;
logger?: Logger;
}

export interface AwsAuthResolvedConfig {
Expand Down
8 changes: 4 additions & 4 deletions packages/types/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export interface LoggerOptions {
* throughout the middleware stack.
*/
export interface Logger {
debug(content: object): void;
info(content: object): void;
warn(content: object): void;
error(content: object): void;
debug(...content: any[]): void;
info(...content: any[]): void;
warn(...content: any[]): void;
error(...content: any[]): void;
}