Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for Exec auth #54

Merged
merged 3 commits into from
Jul 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ endif::[]

* [x] Configuration from _kubeconfig_ files (`KUBECONFIG` environment variable or `$HOME/.kube`)
* [x] Switch contexts interactively
* [x] Authentication support (token, username / password, private key / cert, OpenID Connect)
* [x] Authentication support (token, username / password, private key / cert, OpenID Connect, Amazon EKS, Google Kubernetes Engine, Digital Ocean)
* [x] Namespace selection and pods list watching
* [x] Container log scrolling / watching
* [x] Container resources usage (memory, CPU, network charts) footnote:[Currently requires priviledged access / role.]
Expand Down Expand Up @@ -131,6 +131,27 @@ This can be achived with the {uri-kube-apiserver}[Kubernetes API server CLI], e.
$ kube-apiserver --cors-allowed-origins .*
```

== Authentication

We try to support the various authentication strategies supported by https://kubernetes.io/docs/reference/kubectl/overview/[`kubectl`] in order to provide seamless integration with your local setup. Here are the different authentication strategies we support depending on how you're using Kubebox:

[cols="<,^,^,^", options="header"]
|===
||Executable|Docker|Online

|OpenID Connect|icon:check[]|icon:check[]|icon:check[]*

|Amazon EKS| icon:check[]||

|Digital Ocean|icon:check[]||

|Google Kubernetes Engine|icon:check[]||
|===
*Custom IDP certificate authority files are not supported in Web version.

If the mode you're using isn't marked as supported, you'll have to refresh the authentication token/certs manually and update your configuration file accordingly.


== Commands

[cols="1v,2v"]
Expand Down
64 changes: 64 additions & 0 deletions lib/auth/exec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use strict';

const { execFile } = require('child_process'),
os = require('os');

class ExecAuth {

constructor(/* User.ExecAuthProvider */ auth_provider) {
this.auth_provider = auth_provider;
}

provideAuth(options, cancellations) {
let promise = Promise.resolve(this.auth_provider);
if (this.has_token_expired()) {
const { promise: p, cancellation } = this.refresh_token();
promise = p;
cancellations.push(cancellation);
}
return promise.then(auth_provider => {
if (auth_provider.token) {
options.headers['Authorization'] = `Bearer ${auth_provider.token}`;
}
if (auth_provider.clientCertificateData && auth_provider.clientKeyData && options.secureContext) {
options.secureContext.context.setCert(auth_provider.clientCertificateData);
options.secureContext.context.setKey(auth_provider.clientKeyData);
}
});
}

refresh_token() {
let promise, cancel = function (){};
if (os.platform() === 'browser') {
console.error('Refreshing Exec token in the browser is not supported! Please refresh token manually via the \'%s\' command', this.auth_provider.command);
promise = Promise.resolve(this.auth_provider);
} else {
promise = new Promise((resolve, reject) => {
const process = execFile(this.auth_provider.command, this.auth_provider.args , {'env' : this.auth_provider.env}, (error, stdout, stderr) => {
cancel = function (){};
if (error) {
reject(error);
}
resolve(stdout);
});
cancel = function() { process.kill() };
});
promise = promise.then( response => {
const json = JSON.parse(response);
this.auth_provider.expiry = Date.parse(json.status.expirationTimestamp);
this.auth_provider.token = json.status.token;
this.auth_provider.clientCertificateData = json.status.clientCertificateData;
this.auth_provider.clientKeyData = json.status.clientKeyData;
return this.auth_provider;
});
}

return { promise, cancellation : () => cancel() };
}

has_token_expired() {
return (this.auth_provider.expiry - Date.now()) < 5000;
}
}

module.exports = ExecAuth;
36 changes: 18 additions & 18 deletions lib/auth/gcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,28 @@ class GoogleCloudPlatform {
}

refresh_token() {
let promise, cancel;
let promise, cancel = function (){};
if (os.platform() === 'browser') {
console.error('Refreshing GCP token in the browser is not supported! Please refresh token manually via \'gcloud container clusters get-credentials\'. ');
promise = Promise.resolve(this.auth_provider.access_token);
}
promise = new Promise((resolve, reject) => {
const process = execFile(this.auth_provider.cmd_path, this.auth_provider.cmd_args , (error, stdout, stderr) => {
cancel = function() {};
if (error) {
reject(error);
}
resolve(stdout);
} else {
promise = new Promise((resolve, reject) => {
const process = execFile(this.auth_provider.cmd_path, this.auth_provider.cmd_args , (error, stdout, stderr) => {
cancel = function() {};
if (error) {
reject(error);
}
resolve(stdout);
});
cancel = function() { process.kill() };
});
cancel = function() { process.kill() };
});
promise = promise.then( response => {
const json = JSON.parse(response);
this.auth_provider.expiry = Date.parse(JSONPath(this.auth_provider.expiry_key, json)[0]);
this.auth_provider.access_token = JSONPath(this.auth_provider.token_key, json)[0];
return this.auth_provider.access_token;
});

promise = promise.then( response => {
const json = JSON.parse(response);
this.auth_provider.expiry = Date.parse(JSONPath(this.auth_provider.expiry_key, json)[0]);
this.auth_provider.access_token = JSONPath(this.auth_provider.token_key, json)[0];
return this.auth_provider.access_token;
});
}
return { promise, cancellation : () => cancel() };
}

Expand Down
3 changes: 3 additions & 0 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const crypto = require('crypto'),
get = require('./http-then').get,
ExecAuth = require('./auth/exec'),
OpenIdConnect = require('./auth/oidc'),
GoogleCloudPlatform = require('./auth/gcp'),
URI = require('urijs');
Expand Down Expand Up @@ -164,6 +165,8 @@ class Client {
this.auth_provider = new OpenIdConnect(this.master_api.auth_provider.provider);
} else if (this.master_api.auth_provider.name === 'gcp') {
this.auth_provider = new GoogleCloudPlatform(this.master_api.auth_provider.provider);
} else if (this.master_api.auth_provider.name === 'exec') {
this.auth_provider = new ExecAuth(this.master_api.auth_provider.provider);
}
} else {
delete this.auth_provider;
Expand Down
32 changes: 28 additions & 4 deletions lib/config/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ class User {

constructor({ name, token, username, password, 'client-certificate': certificatePath,
'client-certificate-data': certificateBase64, 'client-key': keyPath,
'client-key-data': keyBase64, 'auth-provider': auth_provider }) {
'client-key-data': keyBase64, 'auth-provider': auth_provider, exec }) {
if (typeof name === 'undefined') {
throw Error('User name must be defined!');
}
if (typeof auth_provider !== 'undefined') {
this.auth_provider = new AuthProvider(Object.assign({ name: auth_provider.name, config: auth_provider.config }));
}
if (typeof exec !== 'undefined') {
// for now let's treat exec like an AuthProvider
this.auth_provider = new AuthProvider(Object.assign({ name: 'exec', config: exec }));
}
this.name = name;
this.token = token;
this.password = password;
Expand All @@ -57,11 +61,13 @@ class AuthProvider {
this.provider = new OpenIdConnectAuthProvider(config);
} else if ( name === 'gcp') {
this.provider = new GoogleCloudPlatformAuthProvider(config);
} else if (name == 'exec') {
this.provider = new ExecAuthProvider(config);
}
}

get token() {
return this.provider.token;
return this.provider.auth_token;
}
}

Expand All @@ -88,7 +94,11 @@ class OpenIdConnectAuthProvider {
this.url = url;
}
this.idp_certificate_authority = ca;
}
}

get auth_token() {
return this.token;
}
}

// TODO: support scopes and different time formats
Expand Down Expand Up @@ -119,10 +129,24 @@ class GoogleCloudPlatformAuthProvider {
this.time_fmt = time_fmt; // defaults to Golang's RFC3339Nano https://golang.org/pkg/time/
}

get token() {
get auth_token() {
return this.access_token;
}
}

class ExecAuthProvider {
constructor({ 'apiVersion': api_version, args, command, env }) {
this.api_version = api_version;
this.args = args;
this.command = command;
this.env = env;
this.expiry = Date.now();
}

get auth_token() {
return this.token;
}
}

module.exports.AuthProvider = AuthProvider;
module.exports.User = User;