Skip to content

Commit 4d8ad74

Browse files
authored
Merge pull request kubernetes-client#304 from brendandburns/cert-auth
Add support for certificate based exec auth.
2 parents 159b32d + b676607 commit 4d8ad74

File tree

6 files changed

+101
-13
lines changed

6 files changed

+101
-13
lines changed

src/auth.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import https = require('https');
2+
import request = require('request');
3+
14
import { User } from './config_types';
25

36
export interface Authenticator {
47
isAuthProvider(user: User): boolean;
8+
// TODO: Deprecate this and roll it into applyAuthentication
59
getToken(user: User): string | null;
10+
applyAuthentication(user: User, opts: request.Options | https.RequestOptions);
611
}

src/cloud_auth.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as proc from 'child_process';
2+
import https = require('https');
23
import * as jsonpath from 'jsonpath-plus';
4+
import request = require('request');
35

46
import { Authenticator } from './auth';
57
import { User } from './config_types';
@@ -32,6 +34,10 @@ export class CloudAuth implements Authenticator {
3234
return 'Bearer ' + config['access-token'];
3335
}
3436

37+
public applyAuthentication(user: User, opts: request.Options | https.RequestOptions) {
38+
// pass
39+
}
40+
3541
private isExpired(config: Config) {
3642
const token = config['access-token'];
3743
const expiry = config.expiry;

src/config.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -346,14 +346,16 @@ export class KubeConfig {
346346
if (!user) {
347347
return;
348348
}
349-
let token: string | null = null;
350-
351-
KubeConfig.authenticators.forEach((authenticator: Authenticator) => {
352-
if (authenticator.isAuthProvider(user)) {
353-
token = authenticator.getToken(user);
354-
}
349+
const authenticator = KubeConfig.authenticators.find((elt: Authenticator) => {
350+
return elt.isAuthProvider(user);
355351
});
356352

353+
let token: string | null = null;
354+
if (authenticator) {
355+
token = authenticator.getToken(user);
356+
authenticator.applyAuthentication(user, opts);
357+
}
358+
357359
if (user.token) {
358360
token = 'Bearer ' + user.token;
359361
}

src/exec_auth.ts

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
import execa = require('execa');
2+
import https = require('https');
3+
import request = require('request');
24

35
import { Authenticator } from './auth';
46
import { User } from './config_types';
57

8+
export interface CredentialStatus {
9+
readonly token: string;
10+
readonly clientCertificateData: string;
11+
readonly clientKeyData: string;
12+
readonly expirationTimestamp: string;
13+
}
14+
15+
export interface Credential {
16+
readonly status: CredentialStatus;
17+
}
18+
619
export class ExecAuth implements Authenticator {
7-
private readonly tokenCache: { [key: string]: any } = {};
20+
private readonly tokenCache: { [key: string]: Credential | null } = {};
821
private execFn: (cmd: string, args: string[], opts: execa.SyncOptions) => execa.ExecaSyncReturnValue =
922
execa.sync;
1023

@@ -24,15 +37,36 @@ export class ExecAuth implements Authenticator {
2437
}
2538

2639
public getToken(user: User): string | null {
27-
// TODO: Handle client cert auth here, requires auth refactor.
28-
// See https://kubernetes.io/docs/reference/access-authn-authz/authentication/#input-and-output-formats
29-
// for details on this protocol.
40+
const credential = this.getCredential(user);
41+
if (!credential) {
42+
return null;
43+
}
44+
if (credential.status.token) {
45+
return `Bearer ${credential.status.token}`;
46+
}
47+
return null;
48+
}
49+
50+
public async applyAuthentication(user: User, opts: request.Options | https.RequestOptions) {
51+
const credential = this.getCredential(user);
52+
if (!credential) {
53+
return;
54+
}
55+
if (credential.status.clientCertificateData) {
56+
opts.cert = credential.status.clientCertificateData;
57+
}
58+
if (credential.status.clientKeyData) {
59+
opts.key = credential.status.clientKeyData;
60+
}
61+
}
62+
63+
private getCredential(user: User): Credential | null {
3064
// TODO: Add a unit test for token caching.
3165
const cachedToken = this.tokenCache[user.name];
3266
if (cachedToken) {
3367
const date = Date.parse(cachedToken.status.expirationTimestamp);
3468
if (date > Date.now()) {
35-
return `Bearer ${cachedToken.status.token}`;
69+
return cachedToken;
3670
}
3771
this.tokenCache[user.name] = null;
3872
}
@@ -57,9 +91,9 @@ export class ExecAuth implements Authenticator {
5791
}
5892
const result = this.execFn(exec.command, exec.args, opts);
5993
if (result.code === 0) {
60-
const obj = JSON.parse(result.stdout);
94+
const obj = JSON.parse(result.stdout) as Credential;
6195
this.tokenCache[user.name] = obj;
62-
return `Bearer ${obj.status.token}`;
96+
return obj;
6397
}
6498
throw new Error(result.stderr);
6599
}

src/exec_auth_test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { expect } from 'chai';
22
import * as shell from 'shelljs';
33

44
import execa = require('execa');
5+
import request = require('request');
6+
57
import { ExecAuth } from './exec_auth';
68
import { User } from './config_types';
79

@@ -73,6 +75,38 @@ describe('ExecAuth', () => {
7375
expect(token).to.equal('Bearer foo');
7476
});
7577

78+
it('should correctly exec for certs', async () => {
79+
const auth = new ExecAuth();
80+
(auth as any).execFn = (
81+
command: string,
82+
args: string[],
83+
opts: execa.SyncOptions,
84+
): execa.ExecaSyncReturnValue => {
85+
return {
86+
code: 0,
87+
stdout: JSON.stringify({ status: { clientCertificateData: 'foo', clientKeyData: 'bar' } }),
88+
} as execa.ExecaSyncReturnValue;
89+
};
90+
91+
const user = {
92+
name: 'user',
93+
authProvider: {
94+
config: {
95+
exec: {
96+
command: 'echo',
97+
},
98+
},
99+
},
100+
};
101+
const token = auth.getToken(user);
102+
expect(token).to.be.null;
103+
104+
const opts = {} as request.Options;
105+
auth.applyAuthentication(user, opts);
106+
expect(opts.cert).to.equal('foo');
107+
expect(opts.key).to.equal('bar');
108+
});
109+
76110
it('should correctly exec and cache', async () => {
77111
const auth = new ExecAuth();
78112
var execCount = 0;

src/oidc_auth.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import https = require('https');
2+
import request = require('request');
3+
14
import { Authenticator } from './auth';
25
import { User } from './config_types';
36

@@ -17,4 +20,8 @@ export class OpenIDConnectAuth implements Authenticator {
1720
// TODO: Extract the 'Bearer ' to config.ts?
1821
return `Bearer ${user.authProvider.config['id-token']}`;
1922
}
23+
24+
public async applyAuthentication(user: User, opts: request.Options | https.RequestOptions) {
25+
// pass
26+
}
2027
}

0 commit comments

Comments
 (0)