Skip to content

Commit

Permalink
add prefetch time in logic
Browse files Browse the repository at this point in the history
  • Loading branch information
peze committed Jan 2, 2025
1 parent 3d2787b commit a640d14
Show file tree
Hide file tree
Showing 16 changed files with 742 additions and 455 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@types/expect.js": "^0.3.29",
"@types/ini": "^1.3.30",
"@types/mocha": "^10.0.6",
"@types/node": "^16.18.123",
"@types/rewire": "^2.5.28",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.18.1",
Expand Down
1 change: 1 addition & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export default class Credential implements ICredential {
this.credential = new InnerCredentialsClient('ecs_ram_role', ECSRAMRoleCredentialsProvider.builder()
.withRoleName(config.roleName)
.withDisableIMDSv1(config.disableIMDSv1)
.withEnableAsyncChecker(config.enableAsyncChecker)
.withReadTimeout(config.timeout)
.withConnectTimeout(config.connectTimeout)
.build());
Expand Down
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default class Config extends $tea.Model {
roleName?: string;
enableIMDSv2?: boolean;
disableIMDSv1: boolean;
enableAsyncChecker: boolean;
metadataTokenDuration?: number;
credentialsURI?: string;
oidcProviderArn: string;
Expand Down Expand Up @@ -47,6 +48,7 @@ export default class Config extends $tea.Model {
roleName: 'roleName',
enableIMDSv2: 'enableIMDSv2',
disableIMDSv1: 'disableIMDSv1',
enableAsyncChecker: 'enableAsyncChecker',
metadataTokenDuration: 'metadataTokenDuration',
credentialsURI: 'credentialsURI',
oidcProviderArn: 'oidcProviderArn',
Expand Down Expand Up @@ -77,6 +79,7 @@ export default class Config extends $tea.Model {
roleName: 'string',
enableIMDSv2: 'boolean',
disableIMDSv1: 'boolean',
enableAsyncChecker: 'boolean',
metadataTokenDuration: 'number',
credentialsURI: 'string',
oidcProviderArn: 'string',
Expand Down
58 changes: 32 additions & 26 deletions src/providers/ecs_ram_role.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import Credentials from '../credentials'
import CredentialsProvider from '../credentials_provider'
import { Request, doRequest } from './http'
import Session from './session'
import { parseUTC } from './time'
import { Session, SessionCredentialProvider, STALE_TIME } from './session'

const PREFETCH_TIME = 60 * 60;
const defaultMetadataTokenDuration = 21600; // 6 hours

export default class ECSRAMRoleCredentialsProvider implements CredentialsProvider {
export default class ECSRAMRoleCredentialsProvider extends SessionCredentialProvider implements CredentialsProvider {
private readonly roleName: string
private readonly disableIMDSv1: boolean
// for sts
private session: Session
private expirationTimestamp: number
// for refresher
private checker: NodeJS.Timeout
// for mock
private doRequest = doRequest;
private readonly readTimeout: number;
Expand All @@ -22,34 +20,36 @@ export default class ECSRAMRoleCredentialsProvider implements CredentialsProvide
}

constructor(builder: ECSRAMRoleCredentialsProviderBuilder) {
super(STALE_TIME, PREFETCH_TIME);
this.refresher = this.getCredentialsInternal;
this.roleName = builder.roleName;
this.disableIMDSv1 = builder.disableIMDSv1;
this.readTimeout = builder.readTimeout;
this.connectTimeout = builder.connectTimeout;
this.checker = null;
if(builder.enableAsyncChecker) {
this.checker = this.checkCredentialsUpdateAsynchronously();
}
}

async getCredentials(): Promise<Credentials> {
if (!this.session || this.needUpdateCredential()) {
const session = await this.getCredentialsInternal();
const expirationTime = parseUTC(session.expiration);
this.session = session;
this.expirationTimestamp = expirationTime / 1000;
}

return Credentials.builder()
.withAccessKeyId(this.session.accessKeyId)
.withAccessKeySecret(this.session.accessKeySecret)
.withSecurityToken(this.session.securityToken)
.withProviderName(this.getProviderName())
.build();
checkCredentialsUpdateAsynchronously(): NodeJS.Timeout {
return setTimeout(async () => {
try {
await this.getCredentials();
} catch(err) {
console.error('CheckCredentialsUpdateAsynchronously Error:', err);
} finally {
this.checker = this.checkCredentialsUpdateAsynchronously();
}
}, 1000 * 60);
}

private needUpdateCredential(): boolean {
if (!this.expirationTimestamp) {
return true
close(): void {
if (this.checker != null) {
clearTimeout(this.checker);
this.checker = null;
}

return this.expirationTimestamp - (Date.now() / 1000) <= 180;
}

private async getMetadataToken(): Promise<string> {
Expand Down Expand Up @@ -139,7 +139,6 @@ export default class ECSRAMRoleCredentialsProvider implements CredentialsProvide

const request = builder.build();
const response = await this.doRequest(request);

if (response.statusCode !== 200) {
throw new Error(`get sts token failed, httpStatus: ${response.statusCode}, message = ${response.body.toString()}`);
}
Expand Down Expand Up @@ -172,9 +171,11 @@ class ECSRAMRoleCredentialsProviderBuilder {
disableIMDSv1: boolean
readTimeout?: number;
connectTimeout?: number;
enableAsyncChecker?: boolean;

constructor() {
this.disableIMDSv1 = false;
this.enableAsyncChecker = false;
}

withRoleName(roleName: string): ECSRAMRoleCredentialsProviderBuilder {
Expand All @@ -197,6 +198,11 @@ class ECSRAMRoleCredentialsProviderBuilder {
return this;
}

withEnableAsyncChecker(enableAsyncChecker: boolean): ECSRAMRoleCredentialsProviderBuilder {
this.enableAsyncChecker = enableAsyncChecker
return this;
}

build(): ECSRAMRoleCredentialsProvider {
// 允许通过环境变量强制关闭 IMDS
if (process.env.ALIBABA_CLOUD_ECS_METADATA_DISABLED && process.env.ALIBABA_CLOUD_ECS_METADATA_DISABLED.toLowerCase() === 'true') {
Expand Down
36 changes: 4 additions & 32 deletions src/providers/oidc_role_arn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import { promisify } from 'util';

import Credentials from '../credentials';
import CredentialsProvider from '../credentials_provider';
import Session from './session';
import { Session, SessionCredentialProvider, STALE_TIME } from './session';
import * as utils from '../util/utils';
import { doRequest, Request } from './http';
import { parseUTC } from './time';

const readFileAsync = promisify(readFile);

Expand Down Expand Up @@ -143,7 +142,7 @@ class OIDCRoleArnCredentialsProviderBuilder {
}
}

export default class OIDCRoleArnCredentialsProvider implements CredentialsProvider {
export default class OIDCRoleArnCredentialsProvider extends SessionCredentialProvider implements CredentialsProvider {
private readonly roleArn: string;
private readonly oidcProviderArn: string;
private readonly oidcTokenFilePath: string;
Expand All @@ -156,15 +155,15 @@ export default class OIDCRoleArnCredentialsProvider implements CredentialsProvid
private readonly readTimeout: number;
private readonly connectTimeout: number;

private session: Session;
expirationTimestamp: number;
lastUpdateTimestamp: number;

static builder() {
return new OIDCRoleArnCredentialsProviderBuilder();
}

constructor(builder: OIDCRoleArnCredentialsProviderBuilder) {
super(STALE_TIME);
this.refresher = this.getCredentialsInternal;
this.roleArn = builder.roleArn;
this.oidcProviderArn = builder.oidcProviderArn;
this.oidcTokenFilePath = builder.oidcTokenFilePath;
Expand All @@ -178,25 +177,6 @@ export default class OIDCRoleArnCredentialsProvider implements CredentialsProvid
this.doRequest = doRequest;
}

async getCredentials(): Promise<Credentials> {
if (!this.session || this.needUpdateCredential()) {
const session = await this.getCredentialsInternal();
// UTC time: 2015-04-09T11:52:19Z
const expirationTime = parseUTC(session.expiration)

this.expirationTimestamp = Math.floor(expirationTime / 1000);
this.lastUpdateTimestamp = Date.now();
this.session = session
}

return Credentials.builder()
.withAccessKeyId(this.session.accessKeyId)
.withAccessKeySecret(this.session.accessKeySecret)
.withSecurityToken(this.session.securityToken)
.withProviderName(this.getProviderName())
.build();
}

getProviderName(): string {
return 'oidc_role_arn';
}
Expand Down Expand Up @@ -255,12 +235,4 @@ export default class OIDCRoleArnCredentialsProvider implements CredentialsProvid

return new Session(AccessKeyId, AccessKeySecret, SecurityToken, Expiration);
}

needUpdateCredential(): boolean {
if (!this.expirationTimestamp) {
return true
}

return this.expirationTimestamp - Date.now() / 1000 <= 180
}
}
43 changes: 7 additions & 36 deletions src/providers/ram_role_arn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import * as utils from '../util/utils';
import Credentials from '../credentials';
import CredentialsProvider from '../credentials_provider'
import { doRequest, Request } from './http';
import { parseUTC } from './time';
import Session from './session';
import { Session, SessionCredentialProvider, STALE_TIME } from './session';

const log = debug('sign');

Expand Down Expand Up @@ -139,7 +138,7 @@ function encode(str: string): string {
.replace(/\*/g, '%2A');
}

export default class RAMRoleARNCredentialsProvider implements CredentialsProvider {
export default class RAMRoleARNCredentialsProvider extends SessionCredentialProvider implements CredentialsProvider {
private readonly credentialsProvider: CredentialsProvider;
private readonly stsEndpoint: string;
private readonly roleSessionName: string;
Expand All @@ -153,15 +152,15 @@ export default class RAMRoleARNCredentialsProvider implements CredentialsProvide
// used for mock
private doRequest = doRequest;

private session: Session;
private lastUpdateTimestamp: number;
private expirationTimestamp: any;

static builder(): RAMRoleARNCredentialsProviderBuilder {
return new RAMRoleARNCredentialsProviderBuilder();
}

constructor(builder: RAMRoleARNCredentialsProviderBuilder) {
super(STALE_TIME);
this.refresher = this.getCredentialsInternal;
this.credentialsProvider = builder.credentialsProvider;
this.stsEndpoint = builder.stsEndpoint;
this.roleSessionName = builder.roleSessionName;
Expand All @@ -173,7 +172,8 @@ export default class RAMRoleARNCredentialsProvider implements CredentialsProvide
this.connectTimeout = builder.connectTimeout;
}

private async getCredentialsInternal(credentials: Credentials): Promise<Session> {
private async getCredentialsInternal(): Promise<Session> {
const credentials = await this.credentialsProvider.getCredentials();
const method = 'POST';
const builder = Request.builder().withMethod(method).withProtocol('https').withHost(this.stsEndpoint).withReadTimeout(this.readTimeout || 10000).withConnectTimeout(this.connectTimeout || 5000);

Expand Down Expand Up @@ -274,36 +274,7 @@ export default class RAMRoleARNCredentialsProvider implements CredentialsProvide
return new Session(AccessKeyId, AccessKeySecret, SecurityToken, Expiration);
}

async getCredentials(): Promise<Credentials> {
if (!this.session || this.needUpdateCredential()) {
// 获取前置凭证
const previousCredentials = await this.credentialsProvider.getCredentials();
const session = await this.getCredentialsInternal(previousCredentials);
// UTC time: 2015-04-09T11:52:19Z
const expirationTime = parseUTC(session.expiration)

this.expirationTimestamp = Math.floor(expirationTime / 1000);
this.lastUpdateTimestamp = Date.now();
this.session = session
}

return Credentials.builder()
.withAccessKeyId(this.session.accessKeyId)
.withAccessKeySecret(this.session.accessKeySecret)
.withSecurityToken(this.session.securityToken)
.withProviderName(`${this.getProviderName()}/${this.credentialsProvider.getProviderName()}`)
.build();
}

needUpdateCredential(): boolean {
if (!this.expirationTimestamp) {
return true
}

return this.expirationTimestamp - Date.now() / 1000 <= 180
}

getProviderName(): string {
return 'ram_role_arn';
return `ram_role_arn/${this.credentialsProvider.getProviderName()}`;
}
}
Loading

0 comments on commit a640d14

Please sign in to comment.