Skip to content

Commit b8d7639

Browse files
committed
Merge branch 'main' into wagnerm/add-error-message-output
* main: Add userpass auth and ldap auth support (hashicorp#440) chore(deps-dev): bump jest from 29.4.3 to 29.5.0 (hashicorp#438)
2 parents 2259b06 + 1d767e3 commit b8d7639

8 files changed

Lines changed: 960 additions & 668 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Unreleased
22

3+
Features:
4+
5+
* Added support for userpass and ldap authentication methods [GH-440](https://github.com/hashicorp/vault-action/pull/440)
6+
37
## 2.5.0 (Jan 26th, 2023)
48

59
Features:

action.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ inputs:
3636
description: 'The path to the Kubernetes service account secret'
3737
required: false
3838
default: '/var/run/secrets/kubernetes.io/serviceaccount/token'
39+
username:
40+
description: 'The username of the user to log in to Vault as. Available to both Userpass and LDAP auth methods'
41+
required: false
42+
password:
43+
description: 'The password of the user to log in to Vault as. Available to both Userpass and LDAP auth methods'
44+
required: false
3945
authPayload:
4046
description: 'The JSON payload to be sent to Vault when using a custom authentication method.'
4147
required: false
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
jest.mock('@actions/core');
2+
jest.mock('@actions/core/lib/command');
3+
const core = require('@actions/core');
4+
5+
const got = require('got');
6+
const { when } = require('jest-when');
7+
8+
const { exportSecrets } = require('../../src/action');
9+
10+
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
11+
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
12+
13+
describe('authenticate with approle', () => {
14+
let roleId;
15+
let secretId;
16+
beforeAll(async () => {
17+
try {
18+
// Verify Connection
19+
await got(`${vaultUrl}/v1/secret/config`, {
20+
headers: {
21+
'X-Vault-Token': vaultToken,
22+
},
23+
});
24+
25+
await got(`${vaultUrl}/v1/secret/data/approle-test`, {
26+
method: 'POST',
27+
headers: {
28+
'X-Vault-Token': vaultToken,
29+
},
30+
json: {
31+
data: {
32+
secret: 'SUPERSECRET_WITH_APPROLE',
33+
},
34+
},
35+
});
36+
37+
// Enable approle
38+
try {
39+
await got(`${vaultUrl}/v1/sys/auth/approle`, {
40+
method: 'POST',
41+
headers: {
42+
'X-Vault-Token': vaultToken
43+
},
44+
json: {
45+
type: 'approle'
46+
},
47+
});
48+
} catch (error) {
49+
const {response} = error;
50+
if (response.statusCode === 400 && response.body.includes("path is already in use")) {
51+
// Approle might already be enabled from previous test runs
52+
} else {
53+
throw error;
54+
}
55+
}
56+
57+
// Create policies
58+
await got(`${vaultUrl}/v1/sys/policies/acl/test`, {
59+
method: 'POST',
60+
headers: {
61+
'X-Vault-Token': vaultToken
62+
},
63+
json: {
64+
"name":"test",
65+
"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/approle-test\" {\n capabilities = [\"read\", \"list\"]\n}\npath \"secret/metadata/approle-test\" {\n capabilities = [\"read\", \"list\"]\n}\n"
66+
},
67+
});
68+
69+
// Create approle
70+
await got(`${vaultUrl}/v1/auth/approle/role/my-role`, {
71+
method: 'POST',
72+
headers: {
73+
'X-Vault-Token': vaultToken
74+
},
75+
json: {
76+
policies: 'test'
77+
},
78+
});
79+
80+
// Get role-id
81+
const roldIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/role-id`, {
82+
headers: {
83+
'X-Vault-Token': vaultToken
84+
},
85+
responseType: 'json',
86+
});
87+
roleId = roldIdResponse.body.data.role_id;
88+
89+
// Get secret-id
90+
const secretIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/secret-id`, {
91+
method: 'POST',
92+
headers: {
93+
'X-Vault-Token': vaultToken
94+
},
95+
responseType: 'json',
96+
});
97+
secretId = secretIdResponse.body.data.secret_id;
98+
} catch(err) {
99+
console.warn('Create approle', err.response.body);
100+
throw err;
101+
}
102+
});
103+
104+
beforeEach(() => {
105+
jest.resetAllMocks();
106+
107+
when(core.getInput)
108+
.calledWith('method', expect.anything())
109+
.mockReturnValueOnce('approle');
110+
when(core.getInput)
111+
.calledWith('roleId', expect.anything())
112+
.mockReturnValueOnce(roleId);
113+
when(core.getInput)
114+
.calledWith('secretId', expect.anything())
115+
.mockReturnValueOnce(secretId);
116+
when(core.getInput)
117+
.calledWith('url', expect.anything())
118+
.mockReturnValueOnce(`${vaultUrl}`);
119+
});
120+
121+
function mockInput(secrets) {
122+
when(core.getInput)
123+
.calledWith('secrets', expect.anything())
124+
.mockReturnValueOnce(secrets);
125+
}
126+
127+
it('authenticate with approle', async() => {
128+
mockInput('secret/data/approle-test secret');
129+
130+
await exportSecrets();
131+
132+
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET_WITH_APPROLE');
133+
})
134+
});
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
jest.mock('@actions/core');
2+
jest.mock('@actions/core/lib/command');
3+
const core = require('@actions/core');
4+
5+
const got = require('got');
6+
const { when } = require('jest-when');
7+
8+
const { exportSecrets } = require('../../src/action');
9+
10+
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
11+
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
12+
13+
describe('authenticate with userpass', () => {
14+
const username = `testUsername`;
15+
const password = `testPassword`;
16+
beforeAll(async () => {
17+
try {
18+
// Verify Connection
19+
await got(`${vaultUrl}/v1/secret/config`, {
20+
headers: {
21+
'X-Vault-Token': vaultToken,
22+
},
23+
});
24+
25+
await got(`${vaultUrl}/v1/secret/data/userpass-test`, {
26+
method: 'POST',
27+
headers: {
28+
'X-Vault-Token': vaultToken,
29+
},
30+
json: {
31+
data: {
32+
secret: 'SUPERSECRET_WITH_USERPASS',
33+
},
34+
},
35+
});
36+
37+
// Enable userpass
38+
try {
39+
await got(`${vaultUrl}/v1/sys/auth/userpass`, {
40+
method: 'POST',
41+
headers: {
42+
'X-Vault-Token': vaultToken
43+
},
44+
json: {
45+
type: 'userpass'
46+
},
47+
});
48+
} catch (error) {
49+
const {response} = error;
50+
if (response.statusCode === 400 && response.body.includes("path is already in use")) {
51+
// Userpass might already be enabled from previous test runs
52+
} else {
53+
throw error;
54+
}
55+
}
56+
57+
// Create policies
58+
await got(`${vaultUrl}/v1/sys/policies/acl/userpass-test`, {
59+
method: 'POST',
60+
headers: {
61+
'X-Vault-Token': vaultToken
62+
},
63+
json: {
64+
"name":"userpass-test",
65+
"policy":`path \"auth/userpass/*\" {\n capabilities = [\"read\", \"list\"]\n}\npath \"auth/userpass/users/${username}\"\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/userpass-test\" {\n capabilities = [\"read\", \"list\"]\n}\npath \"secret/metadata/userpass-test\" {\n capabilities = [\"read\", \"list\"]\n}\n`
66+
},
67+
});
68+
69+
// Create user
70+
await got(`${vaultUrl}/v1/auth/userpass/users/${username}`, {
71+
method: 'POST',
72+
headers: {
73+
'X-Vault-Token': vaultToken
74+
},
75+
json: {
76+
password: `${password}`,
77+
policies: 'userpass-test'
78+
},
79+
});
80+
} catch(err) {
81+
console.warn('Create user in userpass', err.response.body);
82+
throw err;
83+
}
84+
});
85+
86+
beforeEach(() => {
87+
jest.resetAllMocks();
88+
89+
when(core.getInput)
90+
.calledWith('method', expect.anything())
91+
.mockReturnValueOnce('userpass');
92+
when(core.getInput)
93+
.calledWith('username', expect.anything())
94+
.mockReturnValueOnce(username);
95+
when(core.getInput)
96+
.calledWith('password', expect.anything())
97+
.mockReturnValueOnce(password);
98+
when(core.getInput)
99+
.calledWith('url', expect.anything())
100+
.mockReturnValueOnce(`${vaultUrl}`);
101+
});
102+
103+
function mockInput(secrets) {
104+
when(core.getInput)
105+
.calledWith('secrets', expect.anything())
106+
.mockReturnValueOnce(secrets);
107+
}
108+
109+
it('authenticate with userpass', async() => {
110+
mockInput('secret/data/userpass-test secret');
111+
112+
await exportSecrets();
113+
114+
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET_WITH_USERPASS');
115+
})
116+
});

0 commit comments

Comments
 (0)