Last active
November 9, 2022 16:55
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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