Skip to content

Commit fb6c06c

Browse files
authored
Merge pull request #179 from panter/fix/refresh-token-if-empty
fix: access-token does not get refreshed if not present
2 parents d8d1193 + e3979c9 commit fb6c06c

File tree

2 files changed

+120
-39
lines changed

2 files changed

+120
-39
lines changed

src/cloud_auth.ts

Lines changed: 68 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,83 @@ import * as shelljs from 'shelljs';
55
import { Authenticator } from './auth';
66
import { User } from './config_types';
77

8+
/* FIXME: maybe we can extend the User and User.authProvider type to have a proper type.
9+
Currently user.authProvider has `any` type and so we don't have a type for user.authProvider.config.
10+
We therefore define its type here
11+
*/
12+
interface Config {
13+
expiry: string;
14+
['cmd-args']?: string;
15+
['cmd-path']?: string;
16+
['token-key']: string;
17+
['expiry-key']: string;
18+
['access-token']?: string;
19+
}
820
export class CloudAuth implements Authenticator {
921
public isAuthProvider(user: User): boolean {
10-
return user.authProvider.name === 'azure' ||
11-
user.authProvider.name === 'gcp';
22+
return (
23+
user.authProvider.name === 'azure' ||
24+
user.authProvider.name === 'gcp'
25+
);
1226
}
1327

1428
public getToken(user: User): string | null {
1529
const config = user.authProvider.config;
16-
// This should probably be extracted as auth-provider specific plugins...
17-
let token: string = 'Bearer ' + config['access-token'];
30+
if (this.isExpired(config)) {
31+
this.updateAccessToken(config);
32+
}
33+
return 'Bearer ' + config['access-token'];
34+
}
35+
36+
private isExpired(config: Config) {
37+
const token = config['access-token'];
1838
const expiry = config.expiry;
39+
if (!token) {
40+
return true;
41+
}
42+
if (!expiry) {
43+
return false;
44+
}
1945

20-
if (expiry) {
21-
const expiration = Date.parse(expiry);
22-
if (expiration < Date.now()) {
23-
if (config['cmd-path']) {
24-
const args = config['cmd-args'];
25-
// TODO: Cache to file?
26-
// TODO: do this asynchronously
27-
let result: any;
28-
try {
29-
let cmd = config['cmd-path'];
30-
if (args) {
31-
cmd = `${cmd} ${args}`;
32-
}
33-
result = shelljs.exec(cmd);
34-
if (result.code !== 0) {
35-
throw new Error(result.stderr);
36-
}
37-
} catch (err) {
38-
throw new Error('Failed to refresh token: ' + err.message);
39-
}
40-
41-
const output = result.stdout.toString();
42-
const resultObj = JSON.parse(output);
43-
44-
let pathKey = config['token-key'];
45-
// Format in file is {<query>}, so slice it out and add '$'
46-
pathKey = '$' + pathKey.slice(1, -1);
47-
48-
config['access-token'] = jsonpath.query(resultObj, pathKey);
49-
token = 'Bearer ' + config['access-token'];
50-
} else {
51-
throw new Error('Token is expired!');
52-
}
46+
const expiration = Date.parse(expiry);
47+
if (expiration < Date.now()) {
48+
return true;
49+
}
50+
return false;
51+
}
52+
53+
private updateAccessToken(config: Config) {
54+
if (!config['cmd-path']) {
55+
throw new Error('Token is expired!');
56+
}
57+
const args = config['cmd-args'];
58+
// TODO: Cache to file?
59+
// TODO: do this asynchronously
60+
let result: any;
61+
try {
62+
let cmd = config['cmd-path'];
63+
if (args) {
64+
cmd = `${cmd} ${args}`;
5365
}
66+
result = shelljs.exec(cmd, { silent: true });
67+
if (result.code !== 0) {
68+
throw new Error(result.stderr);
69+
}
70+
} catch (err) {
71+
throw new Error('Failed to refresh token: ' + err.message);
5472
}
55-
return token;
73+
74+
const output = result.stdout.toString();
75+
const resultObj = JSON.parse(output);
76+
77+
const tokenPathKeyInConfig = config['token-key'];
78+
const expiryPathKeyInConfig = config['expiry-key'];
79+
80+
// Format in file is {<query>}, so slice it out and add '$'
81+
const tokenPathKey = '$' + tokenPathKeyInConfig.slice(1, -1);
82+
const expiryPathKey = '$' + expiryPathKeyInConfig.slice(1, -1);
83+
84+
config['access-token'] = jsonpath.query(resultObj, tokenPathKey);
85+
config.expiry = jsonpath.query(resultObj, expiryPathKey);
5686
}
5787
}

src/config_test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ describe('KubeConfig', () => {
573573
config.applyToRequest(opts);
574574
expect(opts.headers.Authorization).to.equal(`Bearer ${token}`);
575575
});
576+
576577
it('should populate from auth provider without expirty', () => {
577578
const config = new KubeConfig();
578579
const token = 'token';
@@ -660,14 +661,15 @@ describe('KubeConfig', () => {
660661
authProvider: {
661662
name: 'azure',
662663
config: {
664+
'access-token': 'token',
663665
'expiry': 'Aug 24 07:32:05 PDT 2017',
664666
'cmd-path': 'non-existent-command',
665667
},
666668
},
667669
} as User);
668670
const opts = {} as requestlib.Options;
669671
expect(() => config.applyToRequest(opts)).to.throw(
670-
'Failed to refresh token: /bin/sh: 1: non-existent-command: not found');
672+
/Failed to refresh token/);
671673
});
672674

673675
it('should exec with expired token', () => {
@@ -684,6 +686,55 @@ describe('KubeConfig', () => {
684686
'cmd-path': 'echo',
685687
'cmd-args': `'${responseStr}'`,
686688
'token-key': '{.token.accessToken}',
689+
'expiry-key': '{.token.token_expiry}',
690+
},
691+
},
692+
} as User);
693+
const opts = {} as requestlib.Options;
694+
config.applyToRequest(opts);
695+
expect(opts.headers).to.not.be.undefined;
696+
if (opts.headers) {
697+
expect(opts.headers.Authorization).to.equal(`Bearer ${token}`);
698+
}
699+
});
700+
it('should exec without access-token', () => {
701+
const config = new KubeConfig();
702+
const token = 'token';
703+
const responseStr = `{ "token": { "accessToken": "${token}" } }`;
704+
config.loadFromClusterAndUser(
705+
{ skipTLSVerify: false } as Cluster,
706+
{
707+
authProvider: {
708+
name: 'azure',
709+
config: {
710+
'cmd-path': 'echo',
711+
'cmd-args': `'${responseStr}'`,
712+
'token-key': '{.token.accessToken}',
713+
'expiry-key': '{.token.token_expiry}',
714+
},
715+
},
716+
} as User);
717+
const opts = {} as requestlib.Options;
718+
config.applyToRequest(opts);
719+
expect(opts.headers).to.not.be.undefined;
720+
if (opts.headers) {
721+
expect(opts.headers.Authorization).to.equal(`Bearer ${token}`);
722+
}
723+
});
724+
it('should exec without access-token', () => {
725+
const config = new KubeConfig();
726+
const token = 'token';
727+
const responseStr = `{ "token": { "accessToken": "${token}" } }`;
728+
config.loadFromClusterAndUser(
729+
{ skipTLSVerify: false } as Cluster,
730+
{
731+
authProvider: {
732+
name: 'azure',
733+
config: {
734+
'cmd-path': 'echo',
735+
'cmd-args': `'${responseStr}'`,
736+
'token-key': '{.token.accessToken}',
737+
'expiry-key': '{.token.token_expiry}',
687738
},
688739
},
689740
} as User);

0 commit comments

Comments
 (0)