Skip to content

Instantly share code, notes, and snippets.

@jmlrt
Last active November 9, 2022 16:55
Show Gist options
  • Save jmlrt/482b3d984588c1d3e10a84ce5fcd7b3b to your computer and use it in GitHub Desktop.
Save jmlrt/482b3d984588c1d3e10a84ce5fcd7b3b to your computer and use it in GitHub Desktop.
Node JS script to create an Elasticsearch token for Kibana and register it as a K8S secret
const https = require('https');
const fs = require('fs');
// Read environment variables
function getEnvVar(name) {
if (name in process.env) {
return process.env[name]
} else {
throw new Error(name + ' environment variable is missing')
}
}
// Elasticsearch API
const esPath = '_security/service/elastic/kibana/credential/token/kb-kibana';
const esUrl = 'https://elasticsearch-master:9200' + '/' + esPath
const esUsername = getEnvVar('ELASTICSEARCH_USERNAME');
const esPassword = getEnvVar('ELASTICSEARCH_PASSWORD');
const esAuth = esUsername + ':' + esPassword;
const esCaFile = getEnvVar('ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES');
const esCa = fs.readFileSync(esCaFile);
// Kubernetes API
const k8sHostname = getEnvVar('KUBERNETES_SERVICE_HOST');
const k8sPort = getEnvVar('KUBERNETES_SERVICE_PORT_HTTPS');
const k8sPostSecretPath = 'api/v1/namespaces/default/secrets';
const k8sDeleteSecretPath = 'api/v1/namespaces/default/secrets/kb-kibana-es-token';
const k8sPostSecretUrl = `https://${k8sHostname}:${k8sPort}/${k8sPostSecretPath}`;
const k8sDeleteSecretUrl = `https://${k8sHostname}:${k8sPort}/${k8sDeleteSecretPath}`;
const k8sBearer = fs.readFileSync('/run/secrets/kubernetes.io/serviceaccount/token');
const k8sCa = fs.readFileSync('/run/secrets/kubernetes.io/serviceaccount/ca.crt');
// Post Data
const esTokenDeleteOptions = {
method: 'DELETE',
auth: esAuth,
ca: esCa,
};
const esTokenCreateOptions = {
method: 'POST',
auth: esAuth,
ca: esCa,
};
const secretCreateOptions = {
method: 'POST',
ca: k8sCa,
headers: {
'Authorization': 'Bearer ' + k8sBearer,
'Accept': 'application/json',
'Content-Type': 'application/json',
}
};
const secretDeleteOptions = {
method: 'DELETE',
ca: k8sCa,
headers: {
'Authorization': 'Bearer ' + k8sBearer,
'Accept': 'application/json',
'Content-Type': 'application/json',
}
};
// With thanks to https://stackoverflow.com/questions/57332374/how-to-chain-http-request
function requestPromise(url, httpsOptions, extraOptions = {}) {
return new Promise((resolve, reject) => {
const request = https.request(url, httpsOptions, response => {
console.log('statusCode:', response.statusCode);
let isSuccess = undefined;
if (typeof(extraOptions.extraStatusCode) != "undefined" && extraOptions.extraStatusCode != null) {
isSuccess = response.statusCode >= 200 && response.statusCode < 300 || response.statusCode == extraOptions.extraStatusCode;
} else {
isSuccess = response.statusCode >= 200 && response.statusCode < 300;
}
let data = '';
response.on('data', chunk => data += chunk); // accumulate data
response.once('end', () => isSuccess ? resolve(data) : reject(data)); // resolve promise here
});
request.once('error', err => {
// This won't log anything for e.g. an HTTP 404 or 500 response,
// since from HTTP's point-of-view we successfully received a
// response.
console.log(`${httpsOptions.method} ${httpsOptions.path} failed: `, err.message || err);
reject(err); // if promise is not already resolved, then we can reject it here
});
if (typeof(extraOptions.payload) != "undefined") {
request.write(extraOptions.payload);
}
request.end();
});
}
function createEsToken() {
// Chaining requests
console.log('Cleaning previous token');
// 404 status code is accepted if there is no previous token to clean
return requestPromise(esUrl, esTokenDeleteOptions, {extraStatusCode: 404}).then(() => {
console.log('Creating new token');
requestPromise(esUrl, esTokenCreateOptions).then(response => {
const body = JSON.parse(response);
const token = body.token.value
// Encode the token in base64
const base64Token = Buffer.from(token, 'utf8').toString('base64');
// Prepare the k8s secret
secretData = JSON.stringify({
"apiVersion": "v1",
"kind": "Secret",
"metadata": {
"namespace": "default",
"name": "kb-kibana-es-token",
},
"type": "Opaque",
"data": {
"token": base64Token,
}
})
// Create the k8s secret
console.log('Creating K8S secret');
requestPromise(k8sPostSecretUrl, secretCreateOptions, {payload: secretData})
});
});
}
function cleanEsToken() {
// Chaining requests
console.log('Cleaning token');
return requestPromise(esUrl, esTokenDeleteOptions).then(() => {
// Create the k8s secret
console.log('Delete K8S secret');
requestPromise(k8sDeleteSecretUrl, secretDeleteOptions)
});
}
const command = process.argv[2];
switch (command) {
case 'create':
console.log('Creating a new Elasticsearch token for Kibana')
createEsToken().catch(err => {
console.error(err);
process.exit(1);
});
break;
case 'clean':
console.log('Cleaning the Kibana Elasticsearch token')
cleanEsToken().catch(err => {
console.error(err);
process.exit(1);
});
break;
default:
console.log('Unknown command');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment