@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.
@@ -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.4",
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.4"
25
+ "@clearance/auth": "0.2.1"
26
26
  },
27
27
  "devDependencies": {
28
- "@types/node": "^22.13.10",
28
+ "@types/node": "^22.20.1",
29
29
  "@types/pg": "^8.11.11",
30
- "tsdown": "0.21.1",
31
- "typescript": "^5.9.3",
32
- "vitest": "^4.1.5",
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
  }