configmap-helm-scripts.yaml 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. ---
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: {{ template "kibana.fullname" . }}-helm-scripts
  6. labels: {{ include "kibana.labels" . | nindent 4 }}
  7. annotations:
  8. "helm.sh/hook": pre-install,pre-upgrade,post-delete
  9. "helm.sh/hook-delete-policy": hook-succeeded
  10. {{- if .Values.annotations }}
  11. {{- range $key, $value := .Values.annotations }}
  12. {{ $key }}: {{ $value | quote }}
  13. {{- end }}
  14. {{- end }}
  15. data:
  16. manage-es-token.js: |
  17. const https = require('https');
  18. const fs = require('fs');
  19. // Read environment variables
  20. function getEnvVar(name) {
  21. if (!process.env[name]) {
  22. throw new Error(name + ' environment variable is missing')
  23. }
  24. return process.env[name]
  25. }
  26. // Elasticsearch API
  27. const esPath = '_security/service/elastic/kibana/credential/token/{{ template "kibana.fullname" . }}';
  28. const esUrl = '{{ .Values.elasticsearchHosts }}' + '/' + esPath
  29. const esUsername = getEnvVar('ELASTICSEARCH_USERNAME');
  30. const esPassword = getEnvVar('ELASTICSEARCH_PASSWORD');
  31. const esAuth = esUsername + ':' + esPassword;
  32. const esCaFile = getEnvVar('ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES');
  33. const esCa = fs.readFileSync(esCaFile);
  34. // Kubernetes API
  35. const k8sHostname = getEnvVar('KUBERNETES_SERVICE_HOST');
  36. const k8sPort = getEnvVar('KUBERNETES_SERVICE_PORT_HTTPS');
  37. const k8sPostSecretPath = 'api/v1/namespaces/{{ .Release.Namespace }}/secrets';
  38. const k8sDeleteSecretPath = 'api/v1/namespaces/{{ .Release.Namespace }}/secrets/{{ template "kibana.fullname" . }}-es-token';
  39. const k8sPostSecretUrl = `https://${k8sHostname}:${k8sPort}/${k8sPostSecretPath}`;
  40. const k8sDeleteSecretUrl = `https://${k8sHostname}:${k8sPort}/${k8sDeleteSecretPath}`;
  41. const k8sBearer = fs.readFileSync('/run/secrets/kubernetes.io/serviceaccount/token');
  42. const k8sCa = fs.readFileSync('/run/secrets/kubernetes.io/serviceaccount/ca.crt');
  43. // Post Data
  44. const esTokenDeleteOptions = {
  45. method: 'DELETE',
  46. auth: esAuth,
  47. ca: esCa,
  48. };
  49. const esTokenCreateOptions = {
  50. method: 'POST',
  51. auth: esAuth,
  52. ca: esCa,
  53. };
  54. const secretCreateOptions = {
  55. method: 'POST',
  56. ca: k8sCa,
  57. headers: {
  58. 'Authorization': 'Bearer ' + k8sBearer,
  59. 'Accept': 'application/json',
  60. 'Content-Type': 'application/json',
  61. }
  62. };
  63. const secretDeleteOptions = {
  64. method: 'DELETE',
  65. ca: k8sCa,
  66. headers: {
  67. 'Authorization': 'Bearer ' + k8sBearer,
  68. 'Accept': 'application/json',
  69. 'Content-Type': 'application/json',
  70. }
  71. };
  72. // With thanks to https://stackoverflow.com/questions/57332374/how-to-chain-http-request
  73. function requestPromise(url, httpsOptions, extraOptions = {}) {
  74. return new Promise((resolve, reject) => {
  75. const request = https.request(url, httpsOptions, response => {
  76. console.log('statusCode:', response.statusCode);
  77. let isSuccess = undefined;
  78. if (typeof(extraOptions.extraStatusCode) != "undefined" && extraOptions.extraStatusCode != null) {
  79. isSuccess = response.statusCode >= 200 && response.statusCode < 300 || response.statusCode == extraOptions.extraStatusCode;
  80. } else {
  81. isSuccess = response.statusCode >= 200 && response.statusCode < 300;
  82. }
  83. let data = '';
  84. response.on('data', chunk => data += chunk); // accumulate data
  85. response.once('end', () => isSuccess ? resolve(data) : reject(data)); // resolve promise here
  86. });
  87. request.once('error', err => {
  88. // This won't log anything for e.g. an HTTP 404 or 500 response,
  89. // since from HTTP's point-of-view we successfully received a
  90. // response.
  91. console.log(`${httpsOptions.method} ${httpsOptions.path} failed: `, err.message || err);
  92. reject(err); // if promise is not already resolved, then we can reject it here
  93. });
  94. if (typeof(extraOptions.payload) != "undefined") {
  95. request.write(extraOptions.payload);
  96. }
  97. request.end();
  98. });
  99. }
  100. function createEsToken() {
  101. // Chaining requests
  102. console.log('Cleaning previous token');
  103. // 404 status code is accepted if there is no previous token to clean
  104. return requestPromise(esUrl, esTokenDeleteOptions, {extraStatusCode: 404}).then(() => {
  105. console.log('Creating new token');
  106. return requestPromise(esUrl, esTokenCreateOptions).then(response => {
  107. const body = JSON.parse(response);
  108. const token = body.token.value
  109. // Encode the token in base64
  110. const base64Token = Buffer.from(token, 'utf8').toString('base64');
  111. // Prepare the k8s secret
  112. const secretData = JSON.stringify({
  113. "apiVersion": "v1",
  114. "kind": "Secret",
  115. "metadata": {
  116. "namespace": "{{ .Release.Namespace }}",
  117. "name": "{{ template "kibana.fullname" . }}-es-token",
  118. },
  119. "type": "Opaque",
  120. "data": {
  121. "token": base64Token,
  122. }
  123. })
  124. // Create the k8s secret
  125. console.log('Creating K8S secret');
  126. return requestPromise(k8sPostSecretUrl, secretCreateOptions, {payload: secretData})
  127. });
  128. });
  129. }
  130. function cleanEsToken() {
  131. // Chaining requests
  132. console.log('Cleaning token');
  133. return requestPromise(esUrl, esTokenDeleteOptions).then(() => {
  134. // Create the k8s secret
  135. console.log('Delete K8S secret');
  136. return requestPromise(k8sDeleteSecretUrl, secretDeleteOptions)
  137. });
  138. }
  139. const command = process.argv[2];
  140. switch (command) {
  141. case 'create':
  142. console.log('Creating a new Elasticsearch token for Kibana')
  143. createEsToken().catch(err => {
  144. console.error(err);
  145. process.exit(1);
  146. });
  147. break;
  148. case 'clean':
  149. console.log('Cleaning the Kibana Elasticsearch token')
  150. cleanEsToken().catch(err => {
  151. console.error(err);
  152. process.exit(1);
  153. });
  154. break;
  155. default:
  156. console.log('Unknown command');
  157. process.exit(1);
  158. }