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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/index.js +72 -27
  3. 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/custom_metadata vars
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
- this.rc = walk(parse(body), v => replaceEnv(v, {env : process.env}));
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 {entity_id, identity : {metadata}} = await this._vault_get_profile();
116
- if(!metadata)
117
- metadata = {};
118
- let key_name = `env_${k.toUpperCase()}`;
119
- metadata[key_name] = v;
120
- await this._update_identity(this.VAULT_TOKEN, entity_id, {metadata});
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
- for(let alias of identity.aliases) {
142
- for(let [k, v] of Object.entries(alias.custom_metadata || {})) {
143
- if(k.startsWith('env_'))
144
- profile[k.substr(4)] = v;
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
- const result = JSON.parse(await drain(child.stdout));
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}, expect : 200};
241
+ let query = {...url.parse(remote_url), headers : {'x-vault-token' : this.VAULT_TOKEN}};
217
242
  let res = await request(query);
218
- return get(JSON.parse(String(await drain(res))), 'data.data');
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vvauth",
3
- "version": "1.2.1",
3
+ "version": "2.0.1",
4
4
  "description": "Vault Auth helper",
5
5
  "main": "index.js",
6
6
  "bin": {