5
0
Fork 0
mirror of https://github.com/hashicorp/vault-action.git synced 2025-11-07 07:06:56 +00:00

feat(namespace): handle request on vault namespace (#5)

* feat(namespace): handle request on vault namespace
This commit is contained in:
Antoine Méausoone 2019-11-24 22:21:11 +01:00 committed by Richard Simpson
parent 0104a02854
commit 3747195c5f
9 changed files with 214 additions and 6 deletions

View file

@ -34,6 +34,39 @@ jobs:
VAULT_PORT: ${{ job.services.vault.ports[8200] }} VAULT_PORT: ${{ job.services.vault.ports[8200] }}
CI: true CI: true
test-ent:
runs-on: ubuntu-latest
services:
vault:
image: hashicorp/vault-enterprise:1.3.0_ent
ports:
- 8200/tcp
env:
VAULT_DEV_ROOT_TOKEN_ID: testtoken
options: --cap-add=IPC_LOCK
steps:
- uses: actions/checkout@v1
- name: Use Node.js 10.x
uses: actions/setup-node@v1
with:
node-version: 10.x
- name: npm install
run: npm ci
- name: npm build
run: npm run build
- name: npm run test
run: npm run test
env:
CI: true
- name: npm run test:integration-ent
run: npm run test:integration-ent
env:
VAULT_HOST: localhost
VAULT_PORT: ${{ job.services.vault.ports[8200] }}
CI: true
e2e: e2e:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View file

@ -73,6 +73,25 @@ with:
ci/aws secretKey | AWS_SECRET_ACCESS_KEY ci/aws secretKey | AWS_SECRET_ACCESS_KEY
``` ```
### Namespace
This action could be use with namespace Vault Enterprise feature. You can specify namespace in request :
```yaml
steps:
# ...
- name: Import Secrets
uses: RichiCoder1/vault-action
with:
url: https://vault-enterprise.mycompany.com:8200
token: ${{ secrets.VaultToken }}
namespace: ns1
secrets: |
ci/aws accessKey | AWS_ACCESS_KEY_ID ;
ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
ci npm_token
```
## Masking ## Masking
This action uses Github Action's built in masking, so all variables will automatically be masked if printed to the console or to logs. This action uses Github Action's built in masking, so all variables will automatically be masked if printed to the console or to logs.

View file

@ -5,17 +5,23 @@ const got = require('got');
async function exportSecrets() { async function exportSecrets() {
const vaultUrl = core.getInput('url', { required: true }); const vaultUrl = core.getInput('url', { required: true });
const vaultToken = core.getInput('token', { required: true }); const vaultToken = core.getInput('token', { required: true });
const vaultNamespace = core.getInput('namespace', { required: false });
const secretsInput = core.getInput('secrets', { required: true }); const secretsInput = core.getInput('secrets', { required: true });
const secrets = parseSecretsInput(secretsInput); const secrets = parseSecretsInput(secretsInput);
for (const secret of secrets) { for (const secret of secrets) {
const { secretPath, outputName, secretKey } = secret; const { secretPath, outputName, secretKey } = secret;
const result = await got(`${vaultUrl}/v1/secret/data/${secretPath}`, { const requestOptions = {
headers: { headers: {
'X-Vault-Token': vaultToken 'X-Vault-Token': vaultToken
} }};
});
if (vaultNamespace != null){
requestOptions.headers["X-Vault-Namespace"] = vaultNamespace
}
const result = await got(`${vaultUrl}/v1/secret/data/${secretPath}`, requestOptions);
const parsedResponse = JSON.parse(result.body); const parsedResponse = JSON.parse(result.body);
const vaultKeyData = parsedResponse.data; const vaultKeyData = parsedResponse.data;
@ -91,4 +97,4 @@ module.exports = {
exportSecrets, exportSecrets,
parseSecretsInput, parseSecretsInput,
normalizeOutputKey normalizeOutputKey
}; };

View file

@ -10,9 +10,12 @@ inputs:
secrets: secrets:
description: 'A semicolon-separated list of secrets to retrieve. These will automatically be converted to environmental variable keys. See README for more details' description: 'A semicolon-separated list of secrets to retrieve. These will automatically be converted to environmental variable keys. See README for more details'
required: true required: true
namespace:
description: 'The Vault namespace from which to query secrets. Vault Enterprise only, unset by default'
required: false
runs: runs:
using: 'node12' using: 'node12'
main: 'dist/index.js' main: 'dist/index.js'
branding: branding:
icon: 'unlock' icon: 'unlock'
color: 'gray-dark' color: 'gray-dark'

12
docker-compose.yml Normal file
View file

@ -0,0 +1,12 @@
# Start vault server locally
# You can run integration tests against server by running
# `VAULT_HOST=localhost VAULT_PORT=8200 CI=true npm run test:integration-ent`
version: "3.0"
services:
vault:
image: hashicorp/vault-enterprise:1.3.0_ent
environment:
VAULT_DEV_ROOT_TOKEN_ID: testtoken
ports:
- 8200:8200
privileged: true

View file

@ -0,0 +1,131 @@
jest.mock('@actions/core');
jest.mock('@actions/core/lib/command');
const core = require('@actions/core');
const got = require('got');
const { when } = require('jest-when');
const { exportSecrets } = require('../action');
describe('integration', () => {
beforeAll(async () => {
// Verify Connection
await got(`http://${process.env.VAULT_HOST}:${process.env.VAULT_PORT}/v1/secret/config`, {
headers: {
'X-Vault-Token': 'testtoken',
},
});
// Create namespace
await got(`http://${process.env.VAULT_HOST}:${process.env.VAULT_PORT}/v1/sys/namespaces/ns1`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
},
json: true,
});
// Enable secret engine
await got(`http://${process.env.VAULT_HOST}:${process.env.VAULT_PORT}/v1/sys/mounts/secret`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Namespace': 'ns1',
},
body: {"path":"secret","type":"kv","config":{},"options":{"version":2},"generate_signing_key":true},
json: true,
});
await got(`http://${process.env.VAULT_HOST}:${process.env.VAULT_PORT}/v1/secret/data/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Namespace': 'ns1',
},
body: {
data: {
secret: "SUPERSECRET_IN_NAMESPACE",
},
},
json: true,
});
await got(`http://${process.env.VAULT_HOST}:${process.env.VAULT_PORT}/v1/secret/data/nested/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Namespace': 'ns1',
},
body: {
data: {
otherSecret: "OTHERSUPERSECRET_IN_NAMESPACE",
},
},
json: true,
});
})
beforeEach(() => {
jest.resetAllMocks();
when(core.getInput)
.calledWith('url')
.mockReturnValue(`http://${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`);
when(core.getInput)
.calledWith('token')
.mockReturnValue('testtoken');
when(core.getInput)
.calledWith('namespace')
.mockReturnValue('ns1');
});
function mockInput(secrets) {
when(core.getInput)
.calledWith('secrets')
.mockReturnValue(secrets);
}
it('get simple secret', async () => {
mockInput('test secret')
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET_IN_NAMESPACE');
});
it('re-map secret', async () => {
mockInput('test secret | TEST_KEY')
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('TEST_KEY', 'SUPERSECRET_IN_NAMESPACE');
});
it('get nested secret', async () => {
mockInput('nested/test otherSecret')
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('OTHERSECRET', 'OTHERSUPERSECRET_IN_NAMESPACE');
});
it('get multiple secrets', async () => {
mockInput(`
test secret ;
test secret | NAMED_SECRET ;
nested/test otherSecret ;`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(3);
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET_IN_NAMESPACE');
expect(core.exportVariable).toBeCalledWith('NAMED_SECRET', 'SUPERSECRET_IN_NAMESPACE');
expect(core.exportVariable).toBeCalledWith('OTHERSECRET', 'OTHERSUPERSECRET_IN_NAMESPACE');
});
});

View file

@ -0,0 +1,3 @@
module.exports = {
verbose: true
};

View file

@ -1,3 +1,3 @@
module.exports = { module.exports = {
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/integration/', '<rootDir>/e2e/'], testPathIgnorePatterns: ['/node_modules/', '<rootDir>/integration/', '<rootDir>/e2e/','<rootDir>/integration-ent'],
}; };

View file

@ -7,6 +7,7 @@
"build": "ncc build index.js -o dist", "build": "ncc build index.js -o dist",
"test": "jest", "test": "jest",
"test:integration": "jest -c integration/jest.config.js", "test:integration": "jest -c integration/jest.config.js",
"test:integration-ent": "jest -c integration-ent/jest.config.js",
"test:e2e": "jest -c e2e/jest.config.js" "test:e2e": "jest -c e2e/jest.config.js"
}, },
"release": { "release": {