Skip to content

Commit

Permalink
feat(credential-provider-imds): accept custom logger (#3409)
Browse files Browse the repository at this point in the history
* fix(types): logger interface

* feat(credential-provider-imds): accept custom logger

* chore: remove unused imports
  • Loading branch information
AllanZhengYP authored Mar 10, 2022
1 parent b20d431 commit 11c4a7b
Show file tree
Hide file tree
Showing 10 changed files with 56 additions and 25 deletions.
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;
}

0 comments on commit 11c4a7b

Please sign in to comment.