Skip to content

Commit 9dfe1f8

Browse files
Lucabermfal
andauthored
Remove autorenew, request new token if request fails with permission error (#14)
* Fix token renew and renew token on vault request permission error * do not retry token renew requests to prevent loops * refactor request parameters * remove autoRenew feature * Update src/Vault.ts Co-authored-by: Marco Falkenberg <[email protected]> Co-authored-by: Marco Falkenberg <[email protected]>
1 parent c7a681d commit 9dfe1f8

File tree

6 files changed

+59
-65
lines changed

6 files changed

+59
-65
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ const k8sauth = client.KubernetesAuth({
4545
role: "myrole",
4646
});
4747

48-
const onAutoRenewError = (e) => console.error(e);
49-
await client.Auth(k8sauth).enableAutoRenew(onAutoRenewError);
48+
await client.Auth(k8sauth).login();
5049

5150
client
5251
.Health()

src/Vault.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ export class VaultRequestError extends VaultError {
4040
}
4141
}
4242

43+
export interface VaultRequestOptions {
44+
retryWithTokenRenew?: boolean;
45+
acceptedReturnCodes?: number[];
46+
}
47+
4348
export class Vault {
4449
public readonly config: IVaultConfig;
4550
private tokenClient?: VaultTokenClient;
@@ -60,20 +65,20 @@ export class Vault {
6065
return this.config.vaultToken;
6166
}
6267

63-
public async read(path: string | string[], parameters?: HTTPGETParameters, acceptedReturnCodes?: number[]): Promise<any> {
64-
return this.request("GET", path, {}, parameters, acceptedReturnCodes);
68+
public async read(path: string | string[], parameters?: HTTPGETParameters, options?: VaultRequestOptions): Promise<any> {
69+
return this.request("GET", path, {}, parameters, options);
6570
}
6671

67-
public async write(path: string | string[], body: any, acceptedReturnCodes?: number[]): Promise<any> {
68-
return this.request("POST", path, body, undefined, acceptedReturnCodes);
72+
public async write(path: string | string[], body: any, options?: VaultRequestOptions): Promise<any> {
73+
return this.request("POST", path, body, undefined, options);
6974
}
7075

71-
public async delete(path: string | string[], body: any, acceptedReturnCodes?: number[]): Promise<any> {
72-
return this.request("DELETE", path, body, undefined, acceptedReturnCodes);
76+
public async delete(path: string | string[], body: any, options?: VaultRequestOptions): Promise<any> {
77+
return this.request("DELETE", path, body, undefined, options);
7378
}
7479

75-
public async list(path: string | string[], acceptedReturnCodes?: number[]): Promise<any> {
76-
return this.request("LIST", path, {}, undefined, acceptedReturnCodes);
80+
public async list(path: string | string[], options?: VaultRequestOptions): Promise<any> {
81+
return this.request("LIST", path, {}, undefined, options);
7782
}
7883

7984
public Health(): VaultHealthClient {
@@ -113,8 +118,14 @@ export class Vault {
113118
path: string | string[],
114119
body: any,
115120
parameters?: HTTPGETParameters,
116-
acceptedReturnCodes: number[] = [200, 204],
121+
options?: VaultRequestOptions,
117122
): Promise<any> {
123+
options = {
124+
retryWithTokenRenew: true,
125+
acceptedReturnCodes: [200, 204],
126+
...options,
127+
};
128+
118129
if (typeof path === "string") {
119130
path = [path];
120131
}
@@ -136,9 +147,21 @@ export class Vault {
136147
qs: parameters,
137148
};
138149

139-
const res = await request(requestOptions);
150+
let res = await request(requestOptions);
151+
152+
if (this.tokenClient && options.retryWithTokenRenew && res.statusCode === 403) {
153+
// token could be expired, try a new one
154+
await this.tokenClient.login();
155+
res = await request({
156+
...requestOptions,
157+
headers: {
158+
...requestOptions.headers,
159+
"X-Vault-Token": this.token,
160+
},
161+
});
162+
}
140163

141-
if (!acceptedReturnCodes.some((c) => c === res.statusCode)) {
164+
if (!options.acceptedReturnCodes?.includes(res.statusCode)) {
142165
let errorResponse: IVaultErrorResponse = {
143166
statusCode: res.statusCode,
144167
};

src/VaultClient.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HTTPGETParameters, Vault } from "./Vault";
1+
import { HTTPGETParameters, Vault, VaultRequestOptions } from "./Vault";
22

33
export abstract class AbstractVaultClient {
44
private readonly mountPoint: string[];
@@ -9,19 +9,19 @@ export abstract class AbstractVaultClient {
99
this.vault = vault;
1010
}
1111

12-
protected async rawRead(path: string[], parameters?: HTTPGETParameters, acceptedReturnCodes?: number[]): Promise<any> {
13-
return this.vault.read([...this.mountPoint, ...path], parameters, acceptedReturnCodes);
12+
protected async rawRead(path: string[], parameters?: HTTPGETParameters, options?: VaultRequestOptions): Promise<any> {
13+
return this.vault.read([...this.mountPoint, ...path], parameters, options);
1414
}
1515

16-
protected async rawWrite(path: string[], body?: any, acceptedReturnCodes?: number[]): Promise<any> {
17-
return this.vault.write([...this.mountPoint, ...path], body, acceptedReturnCodes);
16+
protected async rawWrite(path: string[], body?: any, options?: VaultRequestOptions): Promise<any> {
17+
return this.vault.write([...this.mountPoint, ...path], body, options);
1818
}
1919

20-
protected async rawDelete(path: string[], body?: any, acceptedReturnCodes?: number[]): Promise<any> {
21-
return this.vault.delete([...this.mountPoint, ...path], body, acceptedReturnCodes);
20+
protected async rawDelete(path: string[], body?: any, options?: VaultRequestOptions): Promise<any> {
21+
return this.vault.delete([...this.mountPoint, ...path], body, options);
2222
}
2323

24-
protected async rawList(path: string[], acceptedReturnCodes?: number[]): Promise<any> {
25-
return this.vault.list([...this.mountPoint, ...path], acceptedReturnCodes);
24+
protected async rawList(path: string[], options?: VaultRequestOptions): Promise<any> {
25+
return this.vault.list([...this.mountPoint, ...path], options);
2626
}
2727
}

src/auth/kubernetes.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ export class VaultKubernetesAuthClient extends AbstractVaultClient implements IV
2828
if (!this.config.jwt) {
2929
this.initConfig(this.config);
3030
}
31-
return this.rawWrite(["/login"], this.config).then((res) => {
31+
return this.rawWrite(["/login"], this.config, {
32+
retryWithTokenRenew: false,
33+
}).then((res) => {
3234
tiChecker.IVaultTokenAuthResponse.check(res);
3335
return res;
3436
});

src/auth/token.ts

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,9 @@ import { createCheckers } from "ts-interface-checker";
66

77
const tiChecker = createCheckers(tokenTi);
88

9-
// Time in ms to renew token before expiration
10-
const RENEW_BEFORE_MS = 10000;
11-
12-
export type AutoRenewErrorHandler = (error: any) => void;
13-
149
export class VaultTokenClient extends AbstractVaultClient {
1510
private state?: IVaultTokenAuthResponse;
16-
private expires?: Date;
1711
private readonly authProvider?: IVaultAuthProvider;
18-
private readonly autoRenewErrorHandlers = new Set<AutoRenewErrorHandler>();
19-
private autoRenewEnabled = false;
2012

2113
public constructor(vault: Vault, mountPoint: string = "token", authProvider?: IVaultAuthProvider) {
2214
super(vault, ["auth", mountPoint]);
@@ -52,7 +44,9 @@ export class VaultTokenClient extends AbstractVaultClient {
5244
public async renewSelf(options?: IVaultTokenRenewSelfOptions, authProviderFallback: boolean = false): Promise<IVaultTokenAuthResponse> {
5345
let newState: IVaultTokenAuthResponse;
5446
try {
55-
newState = await this.rawWrite(["/renew-self"], options).then((res) => {
47+
newState = await this.rawWrite(["/renew-self"], options, {
48+
retryWithTokenRenew: false,
49+
}).then((res) => {
5650
tiChecker.IVaultTokenAuthResponse.check(res);
5751
return res;
5852
});
@@ -62,45 +56,19 @@ export class VaultTokenClient extends AbstractVaultClient {
6256
}
6357
newState = await this.authProvider.auth();
6458
}
65-
const expires = new Date();
66-
expires.setSeconds(expires.getSeconds() + newState.auth.lease_duration);
6759
this.state = newState;
68-
this.expires = expires;
6960
return this.state;
7061
}
7162

7263
/**
73-
* Enables a periodic job that renews the token before expiration.
74-
* To receive renew errors, subscribe to the "error" event on the vault instance.
64+
* Updates the token using the configured authProvider
7565
*/
76-
public async enableAutoRenew(onError?: AutoRenewErrorHandler): Promise<IVaultTokenAuthResponse> {
77-
return this.autoRenew(onError);
78-
}
79-
80-
private async autoRenew(onError?: AutoRenewErrorHandler): Promise<IVaultTokenAuthResponse> {
81-
if (onError) {
82-
this.autoRenewErrorHandlers.add(onError);
83-
}
84-
85-
const result = await this.renewSelf(undefined, true);
86-
87-
if (!this.autoRenewEnabled) {
88-
setTimeout(() => {
89-
this.autoRenew().catch((error) => {
90-
this.autoRenewErrorHandlers.forEach((handler) => {
91-
try {
92-
handler(error);
93-
} catch (handlerCallError) {
94-
// ignore errors from error handler
95-
}
96-
});
97-
});
98-
}, this.expires!.getTime() - new Date().getTime() - RENEW_BEFORE_MS);
99-
100-
this.autoRenewEnabled = true;
66+
public async login(): Promise<IVaultTokenAuthResponse> {
67+
if (!this.authProvider) {
68+
throw new Error("No Authprovider configured");
10169
}
102-
103-
return result;
70+
this.state = await this.authProvider.auth();
71+
return this.state;
10472
}
10573
}
10674

src/sys/VaultHealthClient.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export class VaultHealthClient extends AbstractVaultClient {
2424
* Throws an VaultRequestError if vault is unhealthy
2525
*/
2626
public async health(): Promise<IVaultHealthResponse> {
27-
return this.rawRead(["/health"], undefined, [200, 429]);
27+
return this.rawRead(["/health"], undefined, {
28+
acceptedReturnCodes: [200, 429],
29+
});
2830
}
2931
}

0 commit comments

Comments
 (0)