vvauth 1.2.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/index.js +72 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ vauth configuration file is a simple yaml file with a specific macro expansion s
|
|
|
18
18
|
The configuration file should abide the following schema
|
|
19
19
|
|
|
20
20
|
### configuration macro expansion set
|
|
21
|
-
* $${profile.XXX} expand to vault entity metadata
|
|
21
|
+
* $${profile.XXX} expand to vault entity metadata and user `.vauth_database` vars
|
|
22
22
|
* $${env.XXX} expand to local environement vars
|
|
23
23
|
* $${secrets.XXX} expand to remote scrapped secrets (see the env.paths)
|
|
24
24
|
|
package/index.js
CHANGED
|
@@ -5,8 +5,9 @@ const os = require('os');
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const url = require('url');
|
|
8
|
-
const {spawn} = require('child_process');
|
|
8
|
+
const {spawn, execFileSync} = require('child_process');
|
|
9
9
|
const passthru = require('nyks/child_process/passthru');
|
|
10
|
+
const wait = require('nyks/child_process/wait');
|
|
10
11
|
|
|
11
12
|
const {parse} = require('yaml');
|
|
12
13
|
const semver = require('semver');
|
|
@@ -60,7 +61,28 @@ class vvauth {
|
|
|
60
61
|
let vauth_rc = VAUTH_RC.filter(path => path && fs.existsSync(path))[0];
|
|
61
62
|
if(vauth_rc) {
|
|
62
63
|
let body = fs.readFileSync(vauth_rc, 'utf8');
|
|
63
|
-
|
|
64
|
+
let rc = parse(body);
|
|
65
|
+
let env = process.env;
|
|
66
|
+
|
|
67
|
+
if(get(rc, 'env.gitlab') && !env.CI) {
|
|
68
|
+
try {
|
|
69
|
+
let remote_url = String(execFileSync('git', ['remote', 'get-url', 'origin'], {cwd : path.dirname(vauth_rc), stdio : ['ignore', 'pipe', 'ignore']})).trim();
|
|
70
|
+
let match = remote_url.match(/^[^@]+@[^:]+:(.+)$/);
|
|
71
|
+
let CI_PROJECT_PATH = match ? match[1] : trim(new URL(remote_url).pathname, '/');
|
|
72
|
+
CI_PROJECT_PATH = trim(CI_PROJECT_PATH, '/').replace(/\.git$/, '');
|
|
73
|
+
|
|
74
|
+
if(CI_PROJECT_PATH) {
|
|
75
|
+
let parts = CI_PROJECT_PATH.split('/');
|
|
76
|
+
let CI_PROJECT_NAME = parts.pop();
|
|
77
|
+
let CI_PROJECT_NAMESPACE = parts.join('/');
|
|
78
|
+
let CI_PROJECT_PATH_SLUG = String(CI_PROJECT_PATH).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
79
|
+
|
|
80
|
+
env = {...env, CI_PROJECT_PATH, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_PROJECT_PATH_SLUG};
|
|
81
|
+
}
|
|
82
|
+
} catch(err) {}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.rc = walk(rc, v => replaceEnv(v, {env}));
|
|
64
86
|
}
|
|
65
87
|
|
|
66
88
|
this.VAULT_ADDR = this.rc.vault_addr;
|
|
@@ -112,12 +134,12 @@ class vvauth {
|
|
|
112
134
|
|
|
113
135
|
|
|
114
136
|
async set(k, v) {
|
|
115
|
-
let {
|
|
116
|
-
if(!
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
await this.
|
|
137
|
+
let {profile, database} = await this._vault_get_profile();
|
|
138
|
+
if(!profile.VAUTH_USER_LOGIN)
|
|
139
|
+
throw "Could not resolve VAUTH_USER_LOGIN from vault identity";
|
|
140
|
+
|
|
141
|
+
database[k.toUpperCase()] = v;
|
|
142
|
+
await this._vault_write(`private/${profile.VAUTH_USER_LOGIN}`, '.vauth_database', database);
|
|
121
143
|
}
|
|
122
144
|
|
|
123
145
|
async unset(k) {
|
|
@@ -125,8 +147,8 @@ class vvauth {
|
|
|
125
147
|
}
|
|
126
148
|
|
|
127
149
|
async show() {
|
|
128
|
-
let {profile} = await this._vault_get_profile();
|
|
129
|
-
return profile;
|
|
150
|
+
let {profile, database} = await this._vault_get_profile();
|
|
151
|
+
return {...database, ...profile};
|
|
130
152
|
}
|
|
131
153
|
|
|
132
154
|
async _vault_get_profile() {
|
|
@@ -137,22 +159,18 @@ class vvauth {
|
|
|
137
159
|
|
|
138
160
|
let {entity_id} = await this._lookup_token(this.VAULT_TOKEN);
|
|
139
161
|
let identity = await this._lookup_identity(this.VAULT_TOKEN, entity_id);
|
|
140
|
-
let profile = {};
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
for(let [k, v] of Object.entries(identity.metadata || {})) {
|
|
148
|
-
if(k.startsWith('env_'))
|
|
149
|
-
profile[k.substr(4)] = v;
|
|
150
|
-
}
|
|
151
|
-
return {entity_id, identity, profile};
|
|
162
|
+
let profile = {...(identity.metadata || {})};
|
|
163
|
+
let database = {};
|
|
164
|
+
|
|
165
|
+
if(profile.VAUTH_USER_LOGIN)
|
|
166
|
+
database = await this._vault_read(`private/${profile.VAUTH_USER_LOGIN}`, '.vauth_database', true);
|
|
167
|
+
|
|
168
|
+
return {entity_id, identity, profile, database};
|
|
152
169
|
}
|
|
153
170
|
|
|
154
171
|
async _get_env() {
|
|
155
|
-
let {profile} = await this._vault_get_profile();
|
|
172
|
+
let {profile, database} = await this._vault_get_profile();
|
|
173
|
+
profile = {...database, ...profile};
|
|
156
174
|
|
|
157
175
|
let env = {VAULT_TOKEN : this.VAULT_TOKEN, VAULT_ADDR : this.VAULT_ADDR}, secrets = {},
|
|
158
176
|
{git, map = {}, paths, path : mount = "secrets"} = this.rc.env || {};
|
|
@@ -163,7 +181,14 @@ class vvauth {
|
|
|
163
181
|
let child = spawn('ssh-agent-crypt', ["-decrypt", identity]);
|
|
164
182
|
|
|
165
183
|
child.stdin.end(fs.readFileSync(path));
|
|
166
|
-
|
|
184
|
+
child.stderr.pipe(process.stderr);
|
|
185
|
+
|
|
186
|
+
const [exit, body] = await Promise.all([wait(child, false), drain(child.stdout)]);
|
|
187
|
+
if(exit !== 0) {
|
|
188
|
+
console.error("Could not expand armored %s using %s", path, identity);
|
|
189
|
+
process.exit();
|
|
190
|
+
}
|
|
191
|
+
const result = JSON.parse(body);
|
|
167
192
|
secrets = {...secrets, ...result};
|
|
168
193
|
}
|
|
169
194
|
|
|
@@ -211,11 +236,31 @@ class vvauth {
|
|
|
211
236
|
return env;
|
|
212
237
|
}
|
|
213
238
|
|
|
214
|
-
async _vault_read(mount, secret_path) {
|
|
239
|
+
async _vault_read(mount, secret_path, optional = false) {
|
|
215
240
|
let remote_url = `${trim(this.VAULT_ADDR, '/')}/v1/${mount}/data/${trim(secret_path, '/')}`;
|
|
216
|
-
let query = {...url.parse(remote_url), headers : {'x-vault-token' : this.VAULT_TOKEN}
|
|
241
|
+
let query = {...url.parse(remote_url), headers : {'x-vault-token' : this.VAULT_TOKEN}};
|
|
217
242
|
let res = await request(query);
|
|
218
|
-
|
|
243
|
+
let body = String(await drain(res));
|
|
244
|
+
|
|
245
|
+
if(optional && res.statusCode == 404)
|
|
246
|
+
return {};
|
|
247
|
+
|
|
248
|
+
if(res.statusCode != 200)
|
|
249
|
+
throw `Could not read vault secret '${mount}/${trim(secret_path, '/')}' : ${body}`;
|
|
250
|
+
|
|
251
|
+
return get(JSON.parse(body), 'data.data');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async _vault_write(mount, secret_path, data) {
|
|
255
|
+
let remote_url = `${trim(this.VAULT_ADDR, '/')}/v1/${mount}/data/${trim(secret_path, '/')}`;
|
|
256
|
+
let query = {...url.parse(remote_url), headers : {'x-vault-token' : this.VAULT_TOKEN}, json : true};
|
|
257
|
+
let res = await request(query, {data});
|
|
258
|
+
let body = String(await drain(res));
|
|
259
|
+
|
|
260
|
+
if(res.statusCode != 200)
|
|
261
|
+
throw `Could not write vault secret '${mount}/${trim(secret_path, '/')}' : ${body}`;
|
|
262
|
+
|
|
263
|
+
return body ? JSON.parse(body) : {};
|
|
219
264
|
}
|
|
220
265
|
|
|
221
266
|
|