@clearance/management 0.1.4 → 0.2.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/dist/index.d.mts +241 -52
- package/dist/index.mjs +1169 -10462
- package/dist/ops/deploy/compose/docker-compose.production.yml +129 -0
- package/dist/ops/deploy/upgrades/README.md +24 -0
- package/dist/ops/deploy/upgrades/steps/0.2.0/apply.sh +66 -0
- package/dist/ops/deploy/upgrades/steps/0.2.1/apply.sh +66 -0
- package/dist/ops/scripts/backup-create.sh +197 -0
- package/dist/ops/scripts/backup-restore-verify.sh +129 -0
- package/dist/ops/scripts/backup-verify.sh +121 -0
- package/dist/ops/scripts/lib/ops-common.sh +375 -0
- package/dist/ops/scripts/scim-legacy-preflight.sh +103 -0
- package/dist/ops/scripts/upgrade-apply.sh +168 -0
- package/dist/ops/scripts/upgrade-plan.sh +129 -0
- package/dist/ops/scripts/upgrade-preflight.sh +136 -0
- package/dist/ops/scripts/upgrade-rollback.sh +394 -0
- package/dist/ops/scripts/upgrade-verify.sh +98 -0
- package/dist/ops/scripts/validate-production-env.sh +232 -0
- package/package.json +7 -7
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Fail-closed production environment validation for Compose/Helm/TF operators.
|
|
3
|
+
# Does not print secret values. Exits non-zero on any missing/weak/default secret.
|
|
4
|
+
set -Eeuo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
7
|
+
# shellcheck source=lib/ops-common.sh
|
|
8
|
+
source "$ROOT/scripts/lib/ops-common.sh"
|
|
9
|
+
require_cmd node
|
|
10
|
+
|
|
11
|
+
usage() {
|
|
12
|
+
cat <<'EOF'
|
|
13
|
+
Usage: validate-production-env.sh
|
|
14
|
+
|
|
15
|
+
Validates operator-supplied environment for production Compose overlay:
|
|
16
|
+
deploy/compose/docker-compose.production.yml
|
|
17
|
+
|
|
18
|
+
Required (strong secrets, no defaults):
|
|
19
|
+
CLEARANCE_OPERATOR_TOKEN
|
|
20
|
+
CLEARANCE_SECRET
|
|
21
|
+
CLEARANCE_CREDENTIAL_KEY
|
|
22
|
+
CLEARANCE_CREDENTIAL_KEY_ID
|
|
23
|
+
CLEARANCE_CONSOLE_ADMIN_USER
|
|
24
|
+
CLEARANCE_CONSOLE_ADMIN_PASSWORD
|
|
25
|
+
CLEARANCE_CONSOLE_SESSION_SECRET
|
|
26
|
+
CLEARANCE_DB_USER
|
|
27
|
+
CLEARANCE_DB_PASSWORD
|
|
28
|
+
CLEARANCE_DB_NAME
|
|
29
|
+
DATABASE_URL # full postgres URL; no compose password interpolation
|
|
30
|
+
CLEARANCE_BASE_URL
|
|
31
|
+
CLEARANCE_CONSOLE_URL
|
|
32
|
+
CLEARANCE_CORS_ORIGINS
|
|
33
|
+
CLEARANCE_API_PORT
|
|
34
|
+
CLEARANCE_CONSOLE_PORT
|
|
35
|
+
CLEARANCE_SAMPLE_PORT
|
|
36
|
+
CLEARANCE_PG_VOLUME
|
|
37
|
+
CLEARANCE_BACKUP_VOLUME
|
|
38
|
+
CLEARANCE_IMAGE_REPOSITORY
|
|
39
|
+
CLEARANCE_IMAGE_DIGEST # sha256:... from the signed release
|
|
40
|
+
CLEARANCE_BACKUP_IMAGE_REPOSITORY
|
|
41
|
+
CLEARANCE_BACKUP_IMAGE_DIGEST # sha256:... from the signed release
|
|
42
|
+
|
|
43
|
+
Optional:
|
|
44
|
+
CLEARANCE_POSTGRES_PORT # only if intentionally publishing Postgres to the host
|
|
45
|
+
CLEARANCE_GITHUB_CLIENT_ID + CLEARANCE_GITHUB_CLIENT_SECRET
|
|
46
|
+
CLEARANCE_GOOGLE_CLIENT_ID + CLEARANCE_GOOGLE_CLIENT_SECRET
|
|
47
|
+
|
|
48
|
+
Fails closed on missing, empty, short, or known-weak values.
|
|
49
|
+
EOF
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
53
|
+
usage
|
|
54
|
+
exit 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
errors=0
|
|
58
|
+
note() { printf 'ok: %s\n' "$*"; }
|
|
59
|
+
fail() { printf 'fail: %s\n' "$*" >&2; errors=$((errors + 1)); }
|
|
60
|
+
|
|
61
|
+
check_secret() {
|
|
62
|
+
local label="$1"
|
|
63
|
+
local value="${2-}"
|
|
64
|
+
if is_forbidden_secret "$value"; then
|
|
65
|
+
fail "$label is missing, empty, short (<16), or a known weak default"
|
|
66
|
+
else
|
|
67
|
+
note "$label present and not a known weak default (len=${#value})"
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
check_present() {
|
|
72
|
+
local label="$1"
|
|
73
|
+
local value="${2-}"
|
|
74
|
+
if [[ -z "$value" ]]; then
|
|
75
|
+
fail "$label is required and must be non-empty"
|
|
76
|
+
else
|
|
77
|
+
note "$label is set"
|
|
78
|
+
fi
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
check_port() {
|
|
82
|
+
local label="$1"
|
|
83
|
+
local value="$2"
|
|
84
|
+
if [[ ! "$value" =~ ^[0-9]+$ ]] || (( 10#$value < 1 || 10#$value > 65535 )); then
|
|
85
|
+
fail "$label must be an integer from 1 through 65535"
|
|
86
|
+
else
|
|
87
|
+
note "$label is a valid TCP port"
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
check_https_url() {
|
|
92
|
+
local label="$1"
|
|
93
|
+
local value="$2"
|
|
94
|
+
if [[ "$value" =~ ^https://[^[:space:]]+$ ]]; then
|
|
95
|
+
note "$label uses HTTPS"
|
|
96
|
+
elif [[ "${CLEARANCE_ALLOW_LOCALHOST_PRODUCTION:-}" == "1" \
|
|
97
|
+
&& "$value" =~ ^http://(localhost|127\.0\.0\.1)(:[0-9]+)?(/.*)?$ ]]; then
|
|
98
|
+
note "$label uses explicitly allowed local HTTP"
|
|
99
|
+
else
|
|
100
|
+
fail "$label must be an absolute HTTPS URL (local HTTP requires CLEARANCE_ALLOW_LOCALHOST_PRODUCTION=1)"
|
|
101
|
+
fi
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Secrets / credentials
|
|
105
|
+
check_secret CLEARANCE_OPERATOR_TOKEN "${CLEARANCE_OPERATOR_TOKEN-}"
|
|
106
|
+
check_secret CLEARANCE_SECRET "${CLEARANCE_SECRET-}"
|
|
107
|
+
check_secret CLEARANCE_CREDENTIAL_KEY "${CLEARANCE_CREDENTIAL_KEY-}"
|
|
108
|
+
check_present CLEARANCE_CREDENTIAL_KEY_ID "${CLEARANCE_CREDENTIAL_KEY_ID-}"
|
|
109
|
+
check_present CLEARANCE_CONSOLE_ADMIN_USER "${CLEARANCE_CONSOLE_ADMIN_USER-}"
|
|
110
|
+
check_secret CLEARANCE_CONSOLE_ADMIN_PASSWORD "${CLEARANCE_CONSOLE_ADMIN_PASSWORD-}"
|
|
111
|
+
check_secret CLEARANCE_CONSOLE_SESSION_SECRET "${CLEARANCE_CONSOLE_SESSION_SECRET-}"
|
|
112
|
+
check_secret CLEARANCE_DB_PASSWORD "${CLEARANCE_DB_PASSWORD-}"
|
|
113
|
+
|
|
114
|
+
# Non-secret required production knobs (no compose defaults in overlay)
|
|
115
|
+
check_present CLEARANCE_DB_USER "${CLEARANCE_DB_USER-}"
|
|
116
|
+
check_present CLEARANCE_DB_NAME "${CLEARANCE_DB_NAME-}"
|
|
117
|
+
check_present CLEARANCE_BASE_URL "${CLEARANCE_BASE_URL-}"
|
|
118
|
+
check_present CLEARANCE_CONSOLE_URL "${CLEARANCE_CONSOLE_URL-}"
|
|
119
|
+
check_present CLEARANCE_CORS_ORIGINS "${CLEARANCE_CORS_ORIGINS-}"
|
|
120
|
+
check_port CLEARANCE_API_PORT "${CLEARANCE_API_PORT-}"
|
|
121
|
+
check_port CLEARANCE_CONSOLE_PORT "${CLEARANCE_CONSOLE_PORT-}"
|
|
122
|
+
check_port CLEARANCE_SAMPLE_PORT "${CLEARANCE_SAMPLE_PORT-}"
|
|
123
|
+
check_present CLEARANCE_PG_VOLUME "${CLEARANCE_PG_VOLUME-}"
|
|
124
|
+
check_present CLEARANCE_BACKUP_VOLUME "${CLEARANCE_BACKUP_VOLUME-}"
|
|
125
|
+
check_present CLEARANCE_IMAGE_REPOSITORY "${CLEARANCE_IMAGE_REPOSITORY-}"
|
|
126
|
+
check_present CLEARANCE_BACKUP_IMAGE_REPOSITORY "${CLEARANCE_BACKUP_IMAGE_REPOSITORY-}"
|
|
127
|
+
if [[ "${CLEARANCE_IMAGE_DIGEST-}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
|
|
128
|
+
note "CLEARANCE_IMAGE_DIGEST is an immutable sha256 digest"
|
|
129
|
+
else
|
|
130
|
+
fail "CLEARANCE_IMAGE_DIGEST must be sha256 followed by 64 lowercase hex characters"
|
|
131
|
+
fi
|
|
132
|
+
if [[ "${CLEARANCE_BACKUP_IMAGE_DIGEST-}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
|
|
133
|
+
note "CLEARANCE_BACKUP_IMAGE_DIGEST is an immutable sha256 digest"
|
|
134
|
+
else
|
|
135
|
+
fail "CLEARANCE_BACKUP_IMAGE_DIGEST must be sha256 followed by 64 lowercase hex characters"
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
check_optional_pair() {
|
|
139
|
+
local provider="$1"
|
|
140
|
+
local client_id="$2"
|
|
141
|
+
local client_secret="$3"
|
|
142
|
+
if [[ -n "$client_id" && -n "$client_secret" ]]; then
|
|
143
|
+
check_secret "${provider} client secret" "$client_secret"
|
|
144
|
+
note "${provider} social credentials are configured as a complete pair"
|
|
145
|
+
elif [[ -n "$client_id" || -n "$client_secret" ]]; then
|
|
146
|
+
fail "${provider} social credentials must set both client id and client secret"
|
|
147
|
+
else
|
|
148
|
+
note "${provider} social provider is disabled"
|
|
149
|
+
fi
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
check_optional_pair GitHub "${CLEARANCE_GITHUB_CLIENT_ID-}" "${CLEARANCE_GITHUB_CLIENT_SECRET-}"
|
|
153
|
+
check_optional_pair Google "${CLEARANCE_GOOGLE_CLIENT_ID-}" "${CLEARANCE_GOOGLE_CLIENT_SECRET-}"
|
|
154
|
+
|
|
155
|
+
# DATABASE_URL: full string required — refuse weak defaults and incomplete forms.
|
|
156
|
+
if is_weak_database_url "${DATABASE_URL-}"; then
|
|
157
|
+
fail "DATABASE_URL missing, not a postgres URL, or uses weak/default credentials (redacted=$(redact_url "${DATABASE_URL-}"))"
|
|
158
|
+
else
|
|
159
|
+
note "DATABASE_URL looks like a non-default postgres URL (redacted=$(redact_url "$DATABASE_URL"))"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# The database container credentials and application URL must describe the same
|
|
163
|
+
# user, password, and database. Compare via process environment; print no values.
|
|
164
|
+
if [[ -n "${DATABASE_URL-}" ]]; then
|
|
165
|
+
if DATABASE_URL_CHECK="$DATABASE_URL" \
|
|
166
|
+
EXPECT_DB_USER="${CLEARANCE_DB_USER-}" \
|
|
167
|
+
EXPECT_DB_PASSWORD="${CLEARANCE_DB_PASSWORD-}" \
|
|
168
|
+
EXPECT_DB_NAME="${CLEARANCE_DB_NAME-}" \
|
|
169
|
+
node -e '
|
|
170
|
+
const u=new URL(process.env.DATABASE_URL_CHECK);
|
|
171
|
+
if(!/^postgres(ql)?:$/.test(u.protocol)) process.exit(1);
|
|
172
|
+
if(decodeURIComponent(u.username)!==process.env.EXPECT_DB_USER) process.exit(2);
|
|
173
|
+
if(decodeURIComponent(u.password)!==process.env.EXPECT_DB_PASSWORD) process.exit(3);
|
|
174
|
+
if(decodeURIComponent(u.pathname.replace(/^\//,""))!==process.env.EXPECT_DB_NAME) process.exit(4);
|
|
175
|
+
' 2>/dev/null; then
|
|
176
|
+
note "DATABASE_URL credentials and database match Compose Postgres settings"
|
|
177
|
+
else
|
|
178
|
+
fail "DATABASE_URL user/password/database must match CLEARANCE_DB_USER/CLEARANCE_DB_PASSWORD/CLEARANCE_DB_NAME"
|
|
179
|
+
fi
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
check_https_url CLEARANCE_BASE_URL "${CLEARANCE_BASE_URL-}"
|
|
183
|
+
check_https_url CLEARANCE_CONSOLE_URL "${CLEARANCE_CONSOLE_URL-}"
|
|
184
|
+
IFS=',' read -r -a cors_origins <<<"${CLEARANCE_CORS_ORIGINS-}"
|
|
185
|
+
for origin in "${cors_origins[@]}"; do
|
|
186
|
+
check_https_url "CLEARANCE_CORS_ORIGINS entry" "${origin//[[:space:]]/}"
|
|
187
|
+
done
|
|
188
|
+
|
|
189
|
+
# Refuse localhost-only defaults that are fine for dev but not production profiles
|
|
190
|
+
if [[ "${CLEARANCE_BASE_URL-}" == *"localhost"* || "${CLEARANCE_BASE_URL-}" == *"127.0.0.1"* ]]; then
|
|
191
|
+
if [[ "${CLEARANCE_ALLOW_LOCALHOST_PRODUCTION:-}" != "1" ]]; then
|
|
192
|
+
fail "CLEARANCE_BASE_URL points at localhost; set CLEARANCE_ALLOW_LOCALHOST_PRODUCTION=1 only for intentional local prod-profile tests"
|
|
193
|
+
else
|
|
194
|
+
note "CLEARANCE_BASE_URL is localhost (explicitly allowed for local prod-profile tests)"
|
|
195
|
+
fi
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# NODE_ENV must not be development when operators claim production
|
|
199
|
+
if [[ "${NODE_ENV-}" == "development" || "${CLEARANCE_NODE_ENV-}" == "development" ]]; then
|
|
200
|
+
fail "NODE_ENV/CLEARANCE_NODE_ENV must not be 'development' for production validation"
|
|
201
|
+
else
|
|
202
|
+
note "NODE_ENV is not development"
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
# Compose production overlay must exist
|
|
206
|
+
overlay="$ROOT/deploy/compose/docker-compose.production.yml"
|
|
207
|
+
if [[ ! -f "$overlay" ]]; then
|
|
208
|
+
fail "missing production overlay: deploy/compose/docker-compose.production.yml"
|
|
209
|
+
else
|
|
210
|
+
# Static fail-closed markers in overlay
|
|
211
|
+
if grep -qE 'CLEARANCE_SECRET:-\$\{|:-dev|:-secret|:-change-me|:-clearance\}' "$overlay"; then
|
|
212
|
+
fail "production overlay appears to embed weak defaults"
|
|
213
|
+
fi
|
|
214
|
+
if grep -qE 'DATABASE_URL:-\$\{' "$overlay"; then
|
|
215
|
+
fail "production overlay must not construct DATABASE_URL from password parts"
|
|
216
|
+
fi
|
|
217
|
+
if ! grep -q 'DATABASE_URL: \${DATABASE_URL:?' "$overlay"; then
|
|
218
|
+
fail "production overlay must require DATABASE_URL with fail-closed \${DATABASE_URL:?...}"
|
|
219
|
+
fi
|
|
220
|
+
if ! grep -q 'NODE_ENV: production' "$overlay"; then
|
|
221
|
+
fail "production overlay must force NODE_ENV: production"
|
|
222
|
+
fi
|
|
223
|
+
note "production overlay present with fail-closed DATABASE_URL and NODE_ENV"
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
if [[ "$errors" -ne 0 ]]; then
|
|
227
|
+
printf '\nvalidate-production-env: FAILED (%s checks)\n' "$errors" >&2
|
|
228
|
+
exit 1
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
printf '\nvalidate-production-env: OK\n'
|
|
232
|
+
exit 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clearance/management",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Clearance management plane — services, diagnostics, migration, backup on real auth runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,19 +22,19 @@
|
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"pg": "^8.14.1",
|
|
25
|
-
"@clearance/auth": "0.1
|
|
25
|
+
"@clearance/auth": "0.2.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@types/node": "^22.
|
|
28
|
+
"@types/node": "^22.20.1",
|
|
29
29
|
"@types/pg": "^8.11.11",
|
|
30
|
-
"tsdown": "0.
|
|
31
|
-
"typescript": "^
|
|
32
|
-
"vitest": "^4.1.
|
|
30
|
+
"tsdown": "0.22.7",
|
|
31
|
+
"typescript": "^6.0.3",
|
|
32
|
+
"vitest": "^4.1.10",
|
|
33
33
|
"@clearance/scim": "1.6.23",
|
|
34
34
|
"@clearance/sso": "1.6.23"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
-
"build": "tsdown",
|
|
37
|
+
"build": "tsdown && node scripts/stage-ops.mjs",
|
|
38
38
|
"test": "vitest run",
|
|
39
39
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
40
40
|
}
|