@apolitical/server 4.1.0 → 4.2.0-bht.0
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/package.json +4 -4
- package/src/config.js +3 -1
- package/src/container.js +2 -2
- package/src/services/secrets.service.js +15 -48
- package/src/helpers/impersonation.helper.js +0 -42
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apolitical/server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0-bht.0",
|
|
4
4
|
"description": "Node.js module to encapsulate Apolitical's express server setup",
|
|
5
5
|
"author": "Apolitical Group Limited <engineering@apolitical.co>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"Node Modules"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@apolitical/logger": "
|
|
27
|
-
"@google-cloud/secret-manager": "5.6.0",
|
|
26
|
+
"@apolitical/logger": "3.0.0-bht.0",
|
|
28
27
|
"@cloudnative/health-connect": "2.1.0",
|
|
28
|
+
"@google-cloud/secret-manager": "6.1.1",
|
|
29
29
|
"@opentelemetry/api": "1.9.0",
|
|
30
30
|
"@opentelemetry/auto-instrumentations-node": "0.57.1",
|
|
31
31
|
"@opentelemetry/exporter-trace-otlp-grpc": "0.200.0",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"cookie-parser": "1.4.6",
|
|
41
41
|
"cors": "2.8.5",
|
|
42
42
|
"dotenv": "16.0.3",
|
|
43
|
-
"express": "4.
|
|
43
|
+
"express": "4.22.0",
|
|
44
44
|
"express-jwt": "8.3.0",
|
|
45
45
|
"http-status-codes": "2.2.0",
|
|
46
46
|
"http-terminator": "3.2.0",
|
package/src/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { NODE_ENV, LOG_LEVEL } = process.env;
|
|
3
|
+
const { NODE_ENV, LOG_LEVEL, GOOGLE_CLOUD_PROJECT, GOOGLE_IMPERSONATE_SERVICE_ACCOUNT } = process.env;
|
|
4
4
|
|
|
5
5
|
const NAME = 'apolitical-server';
|
|
6
6
|
const UUID = '00000000-0000-0000-0000-000000000000';
|
|
@@ -10,6 +10,8 @@ const ADMIN_ROLE = 'administrator';
|
|
|
10
10
|
module.exports = {
|
|
11
11
|
NODE_ENV,
|
|
12
12
|
LOG_LEVEL,
|
|
13
|
+
GOOGLE_CLOUD_PROJECT,
|
|
14
|
+
GOOGLE_IMPERSONATE_SERVICE_ACCOUNT,
|
|
13
15
|
LOGGER_OPTIONS: {
|
|
14
16
|
logLevel: LOG_LEVEL,
|
|
15
17
|
labels: {
|
package/src/container.js
CHANGED
|
@@ -22,6 +22,7 @@ const prerender = require('prerender-node');
|
|
|
22
22
|
const qs = require('qs');
|
|
23
23
|
const swaggerUi = require('swagger-ui-express');
|
|
24
24
|
const xss = require('xss');
|
|
25
|
+
const secretManager = require('@google-cloud/secret-manager');
|
|
25
26
|
// Internal Modules
|
|
26
27
|
const apoliticalLogger = require('@apolitical/logger');
|
|
27
28
|
// Configuration
|
|
@@ -36,7 +37,6 @@ const jwtEncodeHelper = require('./helpers/jwt/encode.helper');
|
|
|
36
37
|
const jwtPassportHelper = require('./helpers/jwt/passport.helper');
|
|
37
38
|
const loggerHelper = require('./helpers/logger.helper');
|
|
38
39
|
const requestHelper = require('./helpers/request.helper');
|
|
39
|
-
const impersonationHelper = require('./helpers/impersonation.helper');
|
|
40
40
|
// Loaders
|
|
41
41
|
const documentationLoader = require('./loaders/documentation.loader');
|
|
42
42
|
const expressLoader = require('./loaders/express.loader');
|
|
@@ -82,6 +82,7 @@ container.register({
|
|
|
82
82
|
qs: asValue(qs),
|
|
83
83
|
swaggerUi: asValue(swaggerUi),
|
|
84
84
|
xss: asValue(xss),
|
|
85
|
+
secretManager: asValue(secretManager),
|
|
85
86
|
// Internal Modules
|
|
86
87
|
apoliticalLogger: asValue(apoliticalLogger),
|
|
87
88
|
// Configuration
|
|
@@ -96,7 +97,6 @@ container.register({
|
|
|
96
97
|
jwtPassportHelper: asFunction(jwtPassportHelper).singleton(),
|
|
97
98
|
loggerHelper: asFunction(loggerHelper).singleton(),
|
|
98
99
|
requestHelper: asFunction(requestHelper).singleton(),
|
|
99
|
-
impersonationHelper: asFunction(impersonationHelper).singleton(),
|
|
100
100
|
// Loaders
|
|
101
101
|
documentationLoader: asFunction(documentationLoader).singleton(),
|
|
102
102
|
expressLoader: asFunction(expressLoader).singleton(),
|
|
@@ -1,57 +1,32 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
module.exports = ({ secretManager, config, logger }) => {
|
|
4
|
+
let client = null;
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
-
projectId: 'hazel-tea-194609',
|
|
7
|
-
impersonateAccount: 'development-platform@hazel-tea-194609.iam.gserviceaccount.com',
|
|
8
|
-
};
|
|
6
|
+
const resolvedImpersonationTarget = config.GOOGLE_IMPERSONATE_SERVICE_ACCOUNT?.trim() || null;
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
let client = null;
|
|
8
|
+
function isImpersonationEnabled() {
|
|
9
|
+
return resolvedImpersonationTarget !== null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function impersonationLogContext() {
|
|
13
|
+
return resolvedImpersonationTarget ? { targetServiceAccount: resolvedImpersonationTarget } : {};
|
|
14
|
+
}
|
|
18
15
|
|
|
19
|
-
/**
|
|
20
|
-
* Get or create the Secret Manager client.
|
|
21
|
-
* In non-production, auto-configures impersonation if not already set.
|
|
22
|
-
* @returns {SecretManagerServiceClient}
|
|
23
|
-
*/
|
|
24
16
|
function getClient() {
|
|
25
17
|
if (!client) {
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
logger.info('[secrets] set default impersonation for development', {
|
|
29
|
-
targetServiceAccount: DEV_DEFAULTS.impersonateAccount,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (impersonationHelper.isImpersonationEnabled()) {
|
|
34
|
-
logger.info('[secrets] using impersonation', impersonationHelper.impersonationLogContext());
|
|
18
|
+
if (isImpersonationEnabled()) {
|
|
19
|
+
logger.info('[secrets] using impersonation', impersonationLogContext());
|
|
35
20
|
}
|
|
36
21
|
|
|
37
|
-
client = new SecretManagerServiceClient();
|
|
22
|
+
client = new secretManager.SecretManagerServiceClient();
|
|
38
23
|
}
|
|
39
24
|
return client;
|
|
40
25
|
}
|
|
41
26
|
|
|
42
|
-
/**
|
|
43
|
-
* Load secrets from Google Secret Manager and set them as environment variables.
|
|
44
|
-
* @param {Object} opts
|
|
45
|
-
* @param {string} [opts.projectId] - GCP project ID (defaults to DEV_DEFAULTS in non-production)
|
|
46
|
-
* @param {Array<{name: string, envVar: string, version?: string}>} opts.secrets - Secrets to load
|
|
47
|
-
* @throws {Error} If projectId is missing or secret cannot be loaded
|
|
48
|
-
*/
|
|
49
27
|
async function loadSecrets(opts) {
|
|
50
28
|
const { secrets } = opts;
|
|
51
|
-
const projectId =
|
|
52
|
-
opts.projectId ??
|
|
53
|
-
process.env['GOOGLE_CLOUD_PROJECT'] ??
|
|
54
|
-
(process.env['NODE_ENV'] !== 'production' ? DEV_DEFAULTS.projectId : undefined);
|
|
29
|
+
const projectId = opts.projectId ?? config.GOOGLE_CLOUD_PROJECT;
|
|
55
30
|
|
|
56
31
|
if (!projectId) {
|
|
57
32
|
throw new Error('Missing projectId for Secret Manager');
|
|
@@ -74,7 +49,6 @@ module.exports = ({ impersonationHelper, logger }) => {
|
|
|
74
49
|
throw new Error(`Empty payload for secret ${spec.name}`);
|
|
75
50
|
}
|
|
76
51
|
|
|
77
|
-
cache[spec.envVar] = payload;
|
|
78
52
|
process.env[spec.envVar] = payload;
|
|
79
53
|
|
|
80
54
|
logger.info('[secrets] loaded', {
|
|
@@ -88,14 +62,8 @@ module.exports = ({ impersonationHelper, logger }) => {
|
|
|
88
62
|
}
|
|
89
63
|
}
|
|
90
64
|
|
|
91
|
-
/**
|
|
92
|
-
* Get a secret value from cache or environment.
|
|
93
|
-
* @param {string} envVar - The environment variable name
|
|
94
|
-
* @returns {string} The secret value
|
|
95
|
-
* @throws {Error} If secret is not loaded
|
|
96
|
-
*/
|
|
97
65
|
function getSecret(envVar) {
|
|
98
|
-
const val =
|
|
66
|
+
const val = process.env[envVar];
|
|
99
67
|
if (!val) {
|
|
100
68
|
throw new Error(`Secret "${envVar}" not loaded`);
|
|
101
69
|
}
|
|
@@ -105,6 +73,5 @@ module.exports = ({ impersonationHelper, logger }) => {
|
|
|
105
73
|
return {
|
|
106
74
|
loadSecrets,
|
|
107
75
|
getSecret,
|
|
108
|
-
impersonation: impersonationHelper,
|
|
109
76
|
};
|
|
110
77
|
};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Helpers for Application Default Credentials (ADC) impersonation detection.
|
|
5
|
-
*
|
|
6
|
-
* These utilities do not acquire credentials; they only surface intent
|
|
7
|
-
* based on environment variables so applications can log and enforce policy.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Returns the target service account to impersonate if configured via env.
|
|
12
|
-
* GOOGLE_IMPERSONATE_SERVICE_ACCOUNT is respected by Google client libraries
|
|
13
|
-
* when acquiring credentials via ADC.
|
|
14
|
-
* @returns {string|null}
|
|
15
|
-
*/
|
|
16
|
-
function getImpersonationTarget() {
|
|
17
|
-
const target = process.env['GOOGLE_IMPERSONATE_SERVICE_ACCOUNT'];
|
|
18
|
-
return target && target.trim().length > 0 ? target.trim() : null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Whether impersonation is enabled for the current process.
|
|
23
|
-
* @returns {boolean}
|
|
24
|
-
*/
|
|
25
|
-
function isImpersonationEnabled() {
|
|
26
|
-
return getImpersonationTarget() !== null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Produce a safe log payload for audit visibility.
|
|
31
|
-
* @returns {{ targetServiceAccount: string } | {}}
|
|
32
|
-
*/
|
|
33
|
-
function impersonationLogContext() {
|
|
34
|
-
const target = getImpersonationTarget();
|
|
35
|
-
return target ? { targetServiceAccount: target } : {};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
module.exports = () => ({
|
|
39
|
-
getImpersonationTarget,
|
|
40
|
-
isImpersonationEnabled,
|
|
41
|
-
impersonationLogContext,
|
|
42
|
-
});
|