mirror of
https://github.com/hashicorp/vault-action.git
synced 2025-11-10 08:36:55 +00:00
feat(authenticate): add approle auth method
This commit is contained in:
parent
e394527e23
commit
2ae5b6c884
5 changed files with 222 additions and 5 deletions
|
|
@ -14,6 +14,7 @@ jobs:
|
|||
uses: RichiCoder1/vault-action
|
||||
with:
|
||||
url: https://vault.mycompany.com:8200
|
||||
method: token
|
||||
token: ${{ secrets.VaultToken }}
|
||||
secrets: |
|
||||
ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
||||
|
|
@ -22,6 +23,12 @@ jobs:
|
|||
# ...
|
||||
```
|
||||
|
||||
## Authentication method
|
||||
|
||||
The `method` parameter can have these value :
|
||||
- **token**: you must provide a token parameter
|
||||
- **approle**: you must provide a roleId & secretId parameter
|
||||
|
||||
## Key Syntax
|
||||
|
||||
The `secrets` parameter is a set of multiple secret requests separated by the `;` character.
|
||||
|
|
@ -84,6 +91,7 @@ steps:
|
|||
uses: RichiCoder1/vault-action
|
||||
with:
|
||||
url: https://vault-enterprise.mycompany.com:8200
|
||||
method: token
|
||||
token: ${{ secrets.VaultToken }}
|
||||
namespace: ns1
|
||||
secrets: |
|
||||
|
|
|
|||
38
action.js
38
action.js
|
|
@ -3,13 +3,45 @@ const command = require('@actions/core/lib/command');
|
|||
const got = require('got');
|
||||
|
||||
async function exportSecrets() {
|
||||
const _methods = ['approle', 'token'];
|
||||
|
||||
const vaultUrl = core.getInput('url', { required: true });
|
||||
const vaultToken = core.getInput('token', { required: true });
|
||||
var vaultMethod = core.getInput('method', { required: false });
|
||||
const vaultRoleId = core.getInput('roleId', { required: false });
|
||||
const vaultSecretId = core.getInput('secretId', { required: false });
|
||||
var vaultToken = core.getInput('token', { required: false });
|
||||
const vaultNamespace = core.getInput('namespace', { required: false });
|
||||
|
||||
const secretsInput = core.getInput('secrets', { required: true });
|
||||
const secrets = parseSecretsInput(secretsInput);
|
||||
|
||||
if (!vaultMethod){
|
||||
vaultMethod = 'token'
|
||||
}
|
||||
|
||||
if (!_methods.includes(vaultMethod)) {
|
||||
throw Error(`Sorry, method ${vaultMethod} currently not implemented.`);
|
||||
}
|
||||
|
||||
switch (vaultMethod) {
|
||||
case 'approle':
|
||||
core.debug('Try to retrieve Vault Token from approle')
|
||||
var options = { headers: { }, json: true, body: { role_id: vaultRoleId, secret_id: vaultSecretId }, responseType: 'json' };
|
||||
if (vaultNamespace != null){
|
||||
options.headers["X-Vault-Namespace"] = vaultNamespace
|
||||
}
|
||||
const result = await got.post(`${vaultUrl}/v1/auth/approle/login`, options);
|
||||
if (result && result.body && result.body.auth && result.body.auth.client_token) {
|
||||
vaultToken = result.body.auth.client_token;
|
||||
core.debug('✔ Vault Token has retrieved from approle')
|
||||
} else {
|
||||
throw Error(`No token was retrieved with the role_id and secret_id provided.`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (const secret of secrets) {
|
||||
const { secretPath, outputName, secretKey } = secret;
|
||||
const requestOptions = {
|
||||
|
|
@ -35,7 +67,7 @@ async function exportSecrets() {
|
|||
|
||||
/**
|
||||
* Parses a secrets input string into key paths and their resulting environment variable name.
|
||||
* @param {string} secretsInput
|
||||
* @param {string} secretsInput
|
||||
*/
|
||||
function parseSecretsInput(secretsInput) {
|
||||
const secrets = secretsInput
|
||||
|
|
@ -86,7 +118,7 @@ function parseSecretsInput(secretsInput) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Replaces any forward-slash characters to
|
||||
* Replaces any forward-slash characters to
|
||||
* @param {string} dataKey
|
||||
*/
|
||||
function normalizeOutputKey(dataKey) {
|
||||
|
|
|
|||
31
dist/index.js
vendored
31
dist/index.js
vendored
|
|
@ -2875,7 +2875,7 @@ module.exports = function (obj) {
|
|||
/***/ 482:
|
||||
/***/ (function(module) {
|
||||
|
||||
module.exports = {"_from":"got","_id":"got@9.6.0","_inBundle":false,"_integrity":"sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==","_location":"/got","_phantomChildren":{},"_requested":{"type":"tag","registry":true,"raw":"got","name":"got","escapedName":"got","rawSpec":"","saveSpec":null,"fetchSpec":"latest"},"_requiredBy":["#USER","/"],"_resolved":"https://registry.npmjs.org/got/-/got-9.6.0.tgz","_shasum":"edf45e7d67f99545705de1f7bbeeeb121765ed85","_spec":"got","_where":"C:\\src\\richicoder1\\vault-action","ava":{"concurrency":4},"browser":{"decompress-response":false,"electron":false},"bugs":{"url":"https://github.com/sindresorhus/got/issues"},"bundleDependencies":false,"dependencies":{"@sindresorhus/is":"^0.14.0","@szmarczak/http-timer":"^1.1.2","cacheable-request":"^6.0.0","decompress-response":"^3.3.0","duplexer3":"^0.1.4","get-stream":"^4.1.0","lowercase-keys":"^1.0.1","mimic-response":"^1.0.1","p-cancelable":"^1.0.0","to-readable-stream":"^1.0.0","url-parse-lax":"^3.0.0"},"deprecated":false,"description":"Simplified HTTP requests","devDependencies":{"ava":"^1.1.0","coveralls":"^3.0.0","delay":"^4.1.0","form-data":"^2.3.3","get-port":"^4.0.0","np":"^3.1.0","nyc":"^13.1.0","p-event":"^2.1.0","pem":"^1.13.2","proxyquire":"^2.0.1","sinon":"^7.2.2","slow-stream":"0.0.4","tempfile":"^2.0.0","tempy":"^0.2.1","tough-cookie":"^3.0.0","xo":"^0.24.0"},"engines":{"node":">=8.6"},"files":["source"],"homepage":"https://github.com/sindresorhus/got#readme","keywords":["http","https","get","got","url","uri","request","util","utility","simple","curl","wget","fetch","net","network","electron"],"license":"MIT","main":"source","name":"got","repository":{"type":"git","url":"git+https://github.com/sindresorhus/got.git"},"scripts":{"release":"np","test":"xo && nyc ava"},"version":"9.6.0"};
|
||||
module.exports = {"_args":[["got@9.6.0","/Users/20012243/Dev/vault-action"]],"_from":"got@9.6.0","_id":"got@9.6.0","_inBundle":false,"_integrity":"sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==","_location":"/got","_phantomChildren":{},"_requested":{"type":"version","registry":true,"raw":"got@9.6.0","name":"got","escapedName":"got","rawSpec":"9.6.0","saveSpec":null,"fetchSpec":"9.6.0"},"_requiredBy":["/"],"_resolved":"https://registry.npmjs.org/got/-/got-9.6.0.tgz","_spec":"9.6.0","_where":"/Users/20012243/Dev/vault-action","ava":{"concurrency":4},"browser":{"decompress-response":false,"electron":false},"bugs":{"url":"https://github.com/sindresorhus/got/issues"},"dependencies":{"@sindresorhus/is":"^0.14.0","@szmarczak/http-timer":"^1.1.2","cacheable-request":"^6.0.0","decompress-response":"^3.3.0","duplexer3":"^0.1.4","get-stream":"^4.1.0","lowercase-keys":"^1.0.1","mimic-response":"^1.0.1","p-cancelable":"^1.0.0","to-readable-stream":"^1.0.0","url-parse-lax":"^3.0.0"},"description":"Simplified HTTP requests","devDependencies":{"ava":"^1.1.0","coveralls":"^3.0.0","delay":"^4.1.0","form-data":"^2.3.3","get-port":"^4.0.0","np":"^3.1.0","nyc":"^13.1.0","p-event":"^2.1.0","pem":"^1.13.2","proxyquire":"^2.0.1","sinon":"^7.2.2","slow-stream":"0.0.4","tempfile":"^2.0.0","tempy":"^0.2.1","tough-cookie":"^3.0.0","xo":"^0.24.0"},"engines":{"node":">=8.6"},"files":["source"],"homepage":"https://github.com/sindresorhus/got#readme","keywords":["http","https","get","got","url","uri","request","util","utility","simple","curl","wget","fetch","net","network","electron"],"license":"MIT","main":"source","name":"got","repository":{"type":"git","url":"git+https://github.com/sindresorhus/got.git"},"scripts":{"release":"np","test":"xo && nyc ava"},"version":"9.6.0"};
|
||||
|
||||
/***/ }),
|
||||
|
||||
|
|
@ -3747,12 +3747,41 @@ const command = __webpack_require__(431);
|
|||
const got = __webpack_require__(798);
|
||||
|
||||
async function exportSecrets() {
|
||||
const _methods = ['approle', 'token'];
|
||||
|
||||
const vaultUrl = core.getInput('url', { required: true });
|
||||
const vaultToken = core.getInput('token', { required: true });
|
||||
const vaultNamespace = core.getInput('namespace', { required: false });
|
||||
const vaultMethod = core.getInput('method', { required: true });
|
||||
const vaultRoleId = core.getInput('roleId', { required: false });
|
||||
const vaultSecretId = core.getInput('secretId', { required: false });
|
||||
|
||||
const secretsInput = core.getInput('secrets', { required: true });
|
||||
const secrets = parseSecretsInput(secretsInput);
|
||||
|
||||
if (!_methods.includes(vaultMethod)) {
|
||||
throw Error(`Sorry, method ${vaultMethod} currently not implemented.`);
|
||||
}
|
||||
|
||||
switch (vaultMethod) {
|
||||
case 'approle':
|
||||
core.debug('Try to retrieve Vault Token from approle')
|
||||
var options = { headers: { }, json: true, body: { role_id: vaultRoleId, secret_id: vaultSecretId }, responseType: 'json' };
|
||||
if (vaultNamespace != null){
|
||||
options.headers["X-Vault-Namespace"] = vaultNamespace
|
||||
}
|
||||
const result = await got.post(`${vaultUrl}/v1/auth/approle/login`, options);
|
||||
if (result && result.body && result.body.auth && result.body.auth.client_token) {
|
||||
vaultToken = result.body.auth.client_token;
|
||||
core.debug('✔ Vault Token has retrieved from approle')
|
||||
} else {
|
||||
throw Error(`No token was retrieved with the role_id and secret_id provided.`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (const secret of secrets) {
|
||||
const { secretPath, outputName, secretKey } = secret;
|
||||
const result = await got(`${vaultUrl}/v1/secret/data/${secretPath}`, {
|
||||
|
|
|
|||
|
|
@ -13,5 +13,5 @@ services:
|
|||
environment:
|
||||
VAULT_DEV_ROOT_TOKEN_ID: testtoken
|
||||
ports:
|
||||
- 8201:8201
|
||||
- 8201:8200
|
||||
privileged: true
|
||||
|
|
@ -128,3 +128,151 @@ describe('integration', () => {
|
|||
expect(core.exportVariable).toBeCalledWith('OTHERSECRET', 'OTHERSUPERSECRET_IN_NAMESPACE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('authenticate with approle', () => {
|
||||
let roleId;
|
||||
let secretId;
|
||||
beforeAll(async () => {
|
||||
try {
|
||||
// Verify Connection
|
||||
await got(`${vaultUrl}/v1/secret/config`, {
|
||||
headers: {
|
||||
'X-Vault-Token': 'testtoken',
|
||||
},
|
||||
});
|
||||
|
||||
// Create namespace
|
||||
await got(`${vaultUrl}/v1/sys/namespaces/ns2`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': 'testtoken',
|
||||
},
|
||||
json: true,
|
||||
});
|
||||
|
||||
// Enable secret engine
|
||||
await got(`${vaultUrl}/v1/sys/mounts/secret`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': 'testtoken',
|
||||
'X-Vault-Namespace': 'ns2',
|
||||
},
|
||||
body: { path: 'secret', type: 'kv', config: {}, options: { version: 2 }, generate_signing_key: true },
|
||||
json: true,
|
||||
});
|
||||
|
||||
// Add secret
|
||||
await got(`${vaultUrl}/v1/secret/data/test`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': 'testtoken',
|
||||
'X-Vault-Namespace': 'ns2',
|
||||
},
|
||||
body: {
|
||||
data: {
|
||||
secret: 'SUPERSECRET_WITH_APPROLE',
|
||||
},
|
||||
},
|
||||
json: true,
|
||||
});
|
||||
|
||||
// Enable approle
|
||||
await got(`${vaultUrl}/v1/sys/auth/approle`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': 'testtoken',
|
||||
'X-Vault-Namespace': 'ns2',
|
||||
},
|
||||
body: {
|
||||
type: 'approle'
|
||||
},
|
||||
json: true,
|
||||
});
|
||||
|
||||
// Create policies
|
||||
await got(`${vaultUrl}/v1/sys/policies/acl/test`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': 'testtoken',
|
||||
'X-Vault-Namespace': 'ns2',
|
||||
},
|
||||
body: {
|
||||
"name":"test",
|
||||
"policy":"path \"auth/approle/*\" {\n capabilities = [\"read\", \"list\"]\n}\npath \"auth/approle/role/my-role/role-id\"\n{\n capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\"]\n}\npath \"auth/approle/role/my-role/secret-id\"\n{\n capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\"]\n}\n\npath \"secret/data/*\" {\n capabilities = [\"list\"]\n}\npath \"secret/metadata/*\" {\n capabilities = [\"list\"]\n}\n\npath \"secret/data/test\" {\n capabilities = [\"read\", \"list\"]\n}\npath \"secret/metadata/test\" {\n capabilities = [\"read\", \"list\"]\n}\npath \"secret/data/test/*\" {\n capabilities = [\"read\", \"list\"]\n}\npath \"secret/metadata/test/*\" {\n capabilities = [\"read\", \"list\"]\n}\n"
|
||||
},
|
||||
json: true,
|
||||
});
|
||||
|
||||
// Create approle
|
||||
await got(`${vaultUrl}/v1/auth/approle/role/my-role`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': 'testtoken',
|
||||
'X-Vault-Namespace': 'ns2',
|
||||
},
|
||||
body: {
|
||||
policies: 'test'
|
||||
},
|
||||
json: true,
|
||||
});
|
||||
|
||||
// Get role-id
|
||||
const roldIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/role-id`, {
|
||||
headers: {
|
||||
'X-Vault-Token': 'testtoken',
|
||||
'X-Vault-Namespace': 'ns2',
|
||||
},
|
||||
json: true,
|
||||
});
|
||||
roleId = roldIdResponse.body.data.role_id;
|
||||
|
||||
// Get secret-id
|
||||
const secretIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/secret-id`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': 'testtoken',
|
||||
'X-Vault-Namespace': 'ns2',
|
||||
},
|
||||
json: true,
|
||||
});
|
||||
secretId = secretIdResponse.body.data.secret_id;
|
||||
} catch(err) {
|
||||
console.warn('Create approle',err);
|
||||
throw err
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
when(core.getInput)
|
||||
.calledWith('method')
|
||||
.mockReturnValue('approle');
|
||||
when(core.getInput)
|
||||
.calledWith('roleId')
|
||||
.mockReturnValue(roleId);
|
||||
when(core.getInput)
|
||||
.calledWith('secretId')
|
||||
.mockReturnValue(secretId);
|
||||
when(core.getInput)
|
||||
.calledWith('url')
|
||||
.mockReturnValue(`${vaultUrl}`);
|
||||
when(core.getInput)
|
||||
.calledWith('namespace')
|
||||
.mockReturnValue('ns2');
|
||||
});
|
||||
|
||||
function mockInput(secrets) {
|
||||
when(core.getInput)
|
||||
.calledWith('secrets')
|
||||
.mockReturnValue(secrets);
|
||||
}
|
||||
|
||||
it('authenticate with approle', async()=> {
|
||||
mockInput('test secret');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET_WITH_APPROLE');
|
||||
})
|
||||
});
|
||||
Loading…
Reference in a new issue