├── README.md ├── package.json ├── CHANGELOG.md ├── LICENSE ├── .gitignore └── bin └── cli.js /README.md: -------------------------------------------------------------------------------- 1 | # kubernetes-external-secrets-cli 2 | 3 | CLI for converting [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) to 4 | [Kubernetes ExternalSecrets](https://github.com/godaddy/kubernetes-external-secrets). 5 | 6 | ## Install 7 | 8 | ``` 9 | npm i -g kubernetes-external-secrets-cli 10 | ``` 11 | 12 | ## Usage 13 | 14 | Get the `Secret` object you'd like to managed as an 15 | `ExternalSecret`. For example: 16 | 17 | ``` 18 | kubectl -n my-app get secret my-app -o json > secret.json 19 | ``` 20 | 21 | Decode secret data from your `Secret`; print commands to load that 22 | data into your secret store (*e.g.*, AWS Secrets Manager); and create 23 | an `ExternalSecret` manifest: 24 | 25 | ``` 26 | mkdir data 27 | kescli --input secret.json --ouput data 28 | ``` 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kubernetes-external-secrets-cli", 3 | "version": "0.0.5", 4 | "description": "CLI for manipulating Kubernetes ExternalSecrets", 5 | "main": "index.js", 6 | "bin": { 7 | "kescli": "bin/cli.js" 8 | }, 9 | "scripts": { 10 | "release": "standard-version --tag-prefix=''", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/silasbw/kubernetes-external-secrets-cli.git" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/silasbw/kubernetes-external-secrets-cli/issues" 22 | }, 23 | "homepage": "https://github.com/silasbw/kubernetes-external-secrets-cli#readme", 24 | "dependencies": { 25 | "js-yaml": "^3.12.2" 26 | }, 27 | "devDependencies": { 28 | "standard-version": "^5.0.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [0.0.5](https://github.com/silasbw/kubernetes-external-secrets-cli/compare/0.0.4...0.0.5) (2019-03-09) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * **shebang:** add missing #! ([#6](https://github.com/silasbw/kubernetes-external-secrets-cli/issues/6)) ([5782f35](https://github.com/silasbw/kubernetes-external-secrets-cli/commit/5782f35)) 11 | 12 | 13 | 14 | ## [0.0.4](https://github.com/silasbw/kubernetes-external-secrets-cli/compare/0.0.3...0.0.4) (2019-03-09) 15 | 16 | 17 | 18 | ## [0.0.3](https://github.com/silasbw/external-secrets-cli/compare/0.0.2...0.0.3) (2019-03-09) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * **package:** use correct cli.js path ([#4](https://github.com/silasbw/external-secrets-cli/issues/4)) ([5698121](https://github.com/silasbw/external-secrets-cli/commit/5698121)) 24 | 25 | 26 | 27 | ## 0.0.2 (2019-03-09) 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Silas Boyd-Wickizer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-sync, no-console */ 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const yaml = require('js-yaml'); 7 | 8 | function basenameGrouping(input) { 9 | const data = input.data; 10 | const grouped = Object.keys(data).reduce((awsSecrets, name) => { 11 | const base = name.split('.').slice(0, -1).join('.'); 12 | if (!(base in awsSecrets)) { 13 | awsSecrets[base] = []; 14 | } 15 | awsSecrets[base].push({ [name]: Buffer.from(data[name], 'base64').toString() }); 16 | return awsSecrets; 17 | }, {}); 18 | return grouped; 19 | } 20 | 21 | function singleValue(input) { 22 | const data = input.data; 23 | return Object.keys(data).reduce((awsSecrets, name) => { 24 | awsSecrets[name] = [{ [name]: Buffer.from(data[name], 'base64').toString() }]; 25 | return awsSecrets; 26 | }, {}); 27 | } 28 | 29 | const argv = require('yargs') 30 | .option('input', { 31 | describe: 'Input Secret manifest file', 32 | demand: true 33 | }) 34 | .option('output', { 35 | describe: 'Directory to store results', 36 | demand: true 37 | }) 38 | .option('aws-sm-output', { 39 | describe: 'Output format for AWS Secret Manager secrets', 40 | choices: ['single-value', 'basename-key-values'], 41 | default: 'basename-key-values' 42 | }) 43 | .option('name-prefix', { 44 | describe: 'AWS Secret name prefix (e.g., "simple-service/")' 45 | }) 46 | .help() 47 | .argv; 48 | 49 | function main(args) { 50 | const input = yaml.safeLoad((fs.readFileSync(args.input).toString())); 51 | const awsSecrets = { 52 | 'single-value': singleValue, 53 | 'basename-key-values': basenameGrouping 54 | }[args['aws-sm-output']](input); 55 | 56 | // 57 | // Make AWS Secret Manager entries. 58 | // 59 | const properties = []; 60 | Object.keys(awsSecrets).forEach(awsSecretName => { 61 | const keyValues = awsSecrets[awsSecretName]; 62 | 63 | if (keyValues.length > 1) { 64 | const key = [args.namePrefix, awsSecretName].filter(Boolean).join(''); 65 | keyValues.forEach(keyValue => { 66 | const name = Object.keys(keyValue)[0]; 67 | const externalSecretProperty = { 68 | key, 69 | name: name, 70 | property: name 71 | }; 72 | properties.push(externalSecretProperty); 73 | }); 74 | const secretManagerKeyValues = Object.assign({}, ...keyValues); 75 | const outputFile = path.join(args.output, `${awsSecretName}.json`); 76 | fs.writeFileSync(outputFile, JSON.stringify(secretManagerKeyValues)); 77 | console.log(`aws secretsmanager create-secret --name ${key} --secret-string file://${outputFile}`); 78 | } else { 79 | const name = Object.keys(keyValues[0])[0]; 80 | const key = [args.namePrefix, name].filter(Boolean).join(''); 81 | properties.push({ 82 | key, 83 | name 84 | }); 85 | const outputFile = path.join(args.output, `${awsSecretName}.json`); 86 | fs.writeFileSync(outputFile, keyValues[0][name]); 87 | console.log(`aws secretsmanager create-secret --name ${key} --secret-string file://${outputFile}`); 88 | } 89 | }); 90 | 91 | // 92 | // Make the ExternalSecret. 93 | // 94 | const externalSecretManifest = yaml.safeDump({ 95 | apiVersion: 'kubernetes-client.io/v1', 96 | kind: 'ExternalSecret', 97 | metadata: { 98 | name: input.metadata.name, 99 | namespace: input.metadata.namespace 100 | }, 101 | secretDescriptor: { 102 | backendType: 'secretManager', 103 | properties 104 | } 105 | }); 106 | const outputFile = path.join(args.output, 'external-secret.yaml'); 107 | fs.writeFileSync(outputFile, externalSecretManifest); 108 | console.log(`kubectl apply -f ${outputFile}`); 109 | } 110 | 111 | main(argv); 112 | --------------------------------------------------------------------------------