From c479e725230f338828506a32b3f1c74a9d88a901 Mon Sep 17 00:00:00 2001 From: Christian Elsen Date: Mon, 30 Jun 2025 22:47:53 -0700 Subject: [PATCH 01/13] feature: add server env variable implementation with unit tests to prevent schema breakage --- api/lib/config.ts | 55 ++++++++++- api/test/config.srv.test.ts | 133 ++++++++++++++++++++++++- docs/ENVIRONMENT_VARIABLES.md | 180 ++++++++++++++++++++++++++++++++++ 3 files changed, 366 insertions(+), 2 deletions(-) create mode 100644 docs/ENVIRONMENT_VARIABLES.md diff --git a/api/lib/config.ts b/api/lib/config.ts index c1ab3c9ff..3ee6e87fe 100644 --- a/api/lib/config.ts +++ b/api/lib/config.ts @@ -8,7 +8,7 @@ import ConnectionPool from './connection-pool.js'; import { ConnectionWebSocket } from './connection-web.js'; import Cacher from './cacher.js'; import type { Server } from './schema.js'; -import { type InferSelectModel } from 'drizzle-orm'; +import { type InferSelectModel, sql } from 'drizzle-orm'; import Models from './models.js'; import process from 'node:process'; import * as pgtypes from './schema.js'; @@ -177,6 +177,59 @@ export default class Config { if (process.env.SubnetPublicB) config.SubnetPublicB = process.env.SubnetPublicB; if (process.env.MediaSecurityGroup) config.MediaSecurityGroup = process.env.MediaSecurityGroup; + // Handle server configuration via environment variables + const serverEnvUpdates: Partial> = {}; + let hasServerUpdates = false; + + if (process.env.CLOUDTAK_Server_name) { + serverEnvUpdates.name = process.env.CLOUDTAK_Server_name; + hasServerUpdates = true; + } + if (process.env.CLOUDTAK_Server_url) { + serverEnvUpdates.url = process.env.CLOUDTAK_Server_url; + hasServerUpdates = true; + } + if (process.env.CLOUDTAK_Server_api) { + serverEnvUpdates.api = process.env.CLOUDTAK_Server_api; + hasServerUpdates = true; + } + if (process.env.CLOUDTAK_Server_webtak) { + serverEnvUpdates.webtak = process.env.CLOUDTAK_Server_webtak; + hasServerUpdates = true; + } + // Support both P12 file + password OR separate cert + key + if (process.env.CLOUDTAK_Server_auth_p12 && process.env.CLOUDTAK_Server_auth_password) { + try { + // Import p12-pem library for P12 extraction + const { convertToPem } = await import('p12-pem/lib/lib/p12.js'); + const p12Buffer = Buffer.from(process.env.CLOUDTAK_Server_auth_p12, 'base64'); + const certs = convertToPem(p12Buffer, process.env.CLOUDTAK_Server_auth_password); + + serverEnvUpdates.auth = { + cert: certs.pemCertificate, + key: certs.pemKey + }; + hasServerUpdates = true; + console.error('ok - Extracted certificate and key from P12 file'); + } catch (err) { + console.error(`Error extracting P12 file: ${err instanceof Error ? err.message : String(err)}`); + } + } else if (process.env.CLOUDTAK_Server_auth_cert && process.env.CLOUDTAK_Server_auth_key) { + serverEnvUpdates.auth = { + cert: process.env.CLOUDTAK_Server_auth_cert, + key: process.env.CLOUDTAK_Server_auth_key + }; + hasServerUpdates = true; + } + + if (hasServerUpdates) { + console.error('ok - Updating server configuration from environment variables'); + config.server = await config.models.Server.commit(config.server.id, { + ...serverEnvUpdates, + updated: sql`Now()` + }); + } + for (const envkey in process.env) { if (!envkey.startsWith('CLOUDTAK')) continue; diff --git a/api/test/config.srv.test.ts b/api/test/config.srv.test.ts index 5c9e68cfd..ea69b4aa8 100644 --- a/api/test/config.srv.test.ts +++ b/api/test/config.srv.test.ts @@ -1,5 +1,7 @@ import test from 'tape'; import Flight from './flight.js'; +import sinon from 'sinon'; +import Config from '../lib/config.js'; const flight = new Flight(); @@ -168,4 +170,133 @@ test('GET api/config/map', async (t) => { t.end(); }); -flight.landing(); +// Server Environment Variable Integration Tests +test('Server Env: CLOUDTAK_Server_name updates database', async (t) => { + const originalEnv = process.env.CLOUDTAK_Server_name; + process.env.CLOUDTAK_Server_name = 'Env Test Server'; + + try { + const initialServer = await flight.config.models.Server.from(1); + + // Simulate server env update + const updatedServer = await flight.config.models.Server.commit(initialServer.id, { + name: process.env.CLOUDTAK_Server_name + }); + + t.equal(updatedServer.name, 'Env Test Server'); + } catch (err) { + t.error(err, 'no error'); + } + + process.env.CLOUDTAK_Server_name = originalEnv; + t.end(); +}); + +test('Server Env: CLOUDTAK_Server_url updates database', async (t) => { + const originalEnv = process.env.CLOUDTAK_Server_url; + process.env.CLOUDTAK_Server_url = 'ssl://env.test.com:8089'; + + try { + const initialServer = await flight.config.models.Server.from(1); + + const updatedServer = await flight.config.models.Server.commit(initialServer.id, { + url: process.env.CLOUDTAK_Server_url + }); + + t.equal(updatedServer.url, 'ssl://env.test.com:8089'); + } catch (err) { + t.error(err, 'no error'); + } + + process.env.CLOUDTAK_Server_url = originalEnv; + t.end(); +}); + +test('Server Env: auth object updates database', async (t) => { + const originalCert = process.env.CLOUDTAK_Server_auth_cert; + const originalKey = process.env.CLOUDTAK_Server_auth_key; + + process.env.CLOUDTAK_Server_auth_cert = 'test-cert-data'; + process.env.CLOUDTAK_Server_auth_key = 'test-key-data'; + + try { + const initialServer = await flight.config.models.Server.from(1); + + const updatedServer = await flight.config.models.Server.commit(initialServer.id, { + auth: { + cert: process.env.CLOUDTAK_Server_auth_cert, + key: process.env.CLOUDTAK_Server_auth_key + } + }); + + t.deepEqual(updatedServer.auth, { + cert: 'test-cert-data', + key: 'test-key-data' + }); + } catch (err) { + t.error(err, 'no error'); + } + + process.env.CLOUDTAK_Server_auth_cert = originalCert; + process.env.CLOUDTAK_Server_auth_key = originalKey; + t.end(); +}); + +test('Server Env: multiple fields update database', async (t) => { + const originalVars = { + name: process.env.CLOUDTAK_Server_name, + api: process.env.CLOUDTAK_Server_api, + webtak: process.env.CLOUDTAK_Server_webtak + }; + + process.env.CLOUDTAK_Server_name = 'Multi Field Server'; + process.env.CLOUDTAK_Server_api = 'https://multi.test.com:8443'; + process.env.CLOUDTAK_Server_webtak = 'http://multi.test.com:8444'; + + try { + const initialServer = await flight.config.models.Server.from(1); + + const updatedServer = await flight.config.models.Server.commit(initialServer.id, { + name: process.env.CLOUDTAK_Server_name, + api: process.env.CLOUDTAK_Server_api, + webtak: process.env.CLOUDTAK_Server_webtak + }); + + t.equal(updatedServer.name, 'Multi Field Server'); + t.equal(updatedServer.api, 'https://multi.test.com:8443'); + t.equal(updatedServer.webtak, 'http://multi.test.com:8444'); + } catch (err) { + t.error(err, 'no error'); + } + + // Restore environment + Object.keys(originalVars).forEach(key => { + const envVar = `CLOUDTAK_Server_${key}`; + if (originalVars[key] !== undefined) { + process.env[envVar] = originalVars[key]; + } else { + delete process.env[envVar]; + } + }); + + t.end(); +}); + +test('Server Env: schema validation with invalid field', async (t) => { + try { + const initialServer = await flight.config.models.Server.from(1); + + // This should fail if schema validation is working + await flight.config.models.Server.commit(initialServer.id, { + invalidField: 'should-fail' + }); + + t.fail('Should have thrown error for invalid field'); + } catch (err) { + t.pass('Correctly rejected invalid field'); + } + + t.end(); +}); + +flight.landing(); \ No newline at end of file diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md new file mode 100644 index 000000000..f1770990b --- /dev/null +++ b/docs/ENVIRONMENT_VARIABLES.md @@ -0,0 +1,180 @@ +# CloudTAK Environment Variables + +CloudTAK supports configuration through environment variables that override database settings when present at startup. This document lists all available configuration environment variables. + +## Format + +Environment variables follow the pattern: +- Prefix: `CLOUDTAK_Config_` +- Replace `::` with `_` in the config key +- All characters after `CLOUDTAK_Config_` are case SENSITIVE + +**Example:** `media::url` becomes `CLOUDTAK_Config_media_url` + +## Available Environment Variables + +### TAK Server Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Server_name` | string | TAK Server name/description | +| `CLOUDTAK_Server_url` | string | TAK Server connection URL (e.g., "ssl://tak.example.com:8089") | +| `CLOUDTAK_Server_api` | string | TAK Server API URL (e.g., "https://tak.example.com:8443") | +| `CLOUDTAK_Server_webtak` | string | TAK Server WebTAK URL (e.g., "https://tak.example.com:8443") | +| `CLOUDTAK_Server_auth_cert` | string | TAK Server client certificate (PEM format) | +| `CLOUDTAK_Server_auth_key` | string | TAK Server client private key (PEM format) | +| `CLOUDTAK_Server_auth_p12` | string | TAK Server P12/PKCS12 file (base64 encoded) | +| `CLOUDTAK_Server_auth_password` | string | Password for P12/PKCS12 file | + +### ArcGIS Online Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_agol_enabled` | boolean | Enable/disable ArcGIS Online integration | +| `CLOUDTAK_Config_agol_token` | string | ArcGIS Online API token | + +### Media Server Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_media_url` | string | CloudTAK Hosted MediaMTX Service URL | + +### Map Configuration + +| Environment Variable | Type | Default | Description | +|---------------------|------|---------|-------------| +| `CLOUDTAK_Config_map_center` | string | "-100,40" | Initial map center coordinates | +| `CLOUDTAK_Config_map_pitch` | integer (0-90) | 0 | Initial map pitch | +| `CLOUDTAK_Config_map_bearing` | integer (0-360) | 0 | Initial map bearing | +| `CLOUDTAK_Config_map_zoom` | integer (0-20) | 4 | Initial map zoom level | + +### TAK User Group Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_group_Yellow` | string | Yellow group configuration | +| `CLOUDTAK_Config_group_Cyan` | string | Cyan group configuration | +| `CLOUDTAK_Config_group_Green` | string | Green group configuration | +| `CLOUDTAK_Config_group_Red` | string | Red group configuration | +| `CLOUDTAK_Config_group_Purple` | string | Purple group configuration | +| `CLOUDTAK_Config_group_Orange` | string | Orange group configuration | +| `CLOUDTAK_Config_group_Blue` | string | Blue group configuration | +| `CLOUDTAK_Config_group_Magenta` | string | Magenta group configuration | +| `CLOUDTAK_Config_group_White` | string | White group configuration | +| `CLOUDTAK_Config_group_Maroon` | string | Maroon group configuration | +| `CLOUDTAK_Config_group_Dark_Blue` | string | Dark Blue group configuration | +| `CLOUDTAK_Config_group_Teal` | string | Teal group configuration | +| `CLOUDTAK_Config_group_Dark_Green` | string | Dark Green group configuration | +| `CLOUDTAK_Config_group_Brown` | string | Brown group configuration | + +### OIDC (OpenID Connect) Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_oidc_enabled` | boolean | Enable/disable OIDC authentication | +| `CLOUDTAK_Config_oidc_enforced` | boolean | Enforce OIDC authentication | +| `CLOUDTAK_Config_oidc_name` | string | OIDC provider name | +| `CLOUDTAK_Config_oidc_discovery` | string | OIDC discovery URL | +| `CLOUDTAK_Config_oidc_client` | string | OIDC client ID | +| `CLOUDTAK_Config_oidc_secret` | string | OIDC client secret | + +### COTAK OAuth Provider Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_provider_url` | string | Provider URL | +| `CLOUDTAK_Config_provider_secret` | string | Provider secret | +| `CLOUDTAK_Config_provider_client` | string | Provider client | + +### Login Page Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_login_signup` | string | TAK Server signup link URL | +| `CLOUDTAK_Config_login_forgot` | string | TAK Server password reset link URL | +| `CLOUDTAK_Config_login_logo` | string | Base64 encoded login logo (must start with "data:image/png;base64,") | + +## Usage Examples + +### TAK Server Configuration +```bash +# Configure TAK Server connection +export CLOUDTAK_Server_name="Production TAK Server" +export CLOUDTAK_Server_url="ssl://tak.example.com:8089" +export CLOUDTAK_Server_api="https://tak.example.com:8443" +export CLOUDTAK_Server_webtak="https://tak.example.com:8443" + +# Option 1: Use P12 file (recommended for automation) +export CLOUDTAK_Server_auth_p12="$(base64 -w 0 /path/to/client.p12)" +export CLOUDTAK_Server_auth_password="atakatak" + +# Option 2: Use separate certificate and key files +export CLOUDTAK_Server_auth_cert="-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" +export CLOUDTAK_Server_auth_key="-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----" +``` + +### Basic Configuration +```bash +# Set map center to New Zealand coordinates +export CLOUDTAK_Config_map_center="-41.2865,174.7762" + +# Set initial zoom level +export CLOUDTAK_Config_map_zoom=6 +``` + +### ArcGIS Online Integration +```bash +# Enable ArcGIS Online +export CLOUDTAK_Config_agol_enabled=true +export CLOUDTAK_Config_agol_token="your_agol_token_here" +``` + +### Group Configuration +```bash +# Configure emergency response groups +export CLOUDTAK_Config_group_Red="Emergency Response Team" +export CLOUDTAK_Config_group_Blue="Police Units" +export CLOUDTAK_Config_group_Green="Medical Teams" +``` + +### OIDC Authentication +```bash +# Configure OIDC authentication +export CLOUDTAK_Config_oidc_enabled=true +export CLOUDTAK_Config_oidc_enforced=false +export CLOUDTAK_Config_oidc_name="Corporate SSO" +export CLOUDTAK_Config_oidc_discovery="https://your-oidc-provider.com/.well-known/openid_configuration" +export CLOUDTAK_Config_oidc_client="your-client-id" +export CLOUDTAK_Config_oidc_secret="your-client-secret" +``` + +### Docker Compose Example +```yaml +version: '3.8' +services: + cloudtak: + image: cloudtak:latest + environment: + # TAK Server Configuration + - CLOUDTAK_Server_name=Production TAK Server + - CLOUDTAK_Server_url=ssl://tak.example.com:8089 + - CLOUDTAK_Server_api=https://tak.example.com:8443 + - CLOUDTAK_Server_webtak=https://tak.example.com:8443 + # Application Configuration + - CLOUDTAK_Config_map_center=-41.2865,174.7762 + - CLOUDTAK_Config_map_zoom=6 + - CLOUDTAK_Config_agol_enabled=true + - CLOUDTAK_Config_agol_token=your_token_here + - CLOUDTAK_Config_group_Red=Emergency Response +``` + +## Important Notes + +- Environment variables present at launch will **OVERRIDE** any values stored in the database +- **Server configuration** environment variables solve the first-boot configuration problem by allowing automated TAK server setup without requiring API authentication +- All characters after `CLOUDTAK_Config_` are case sensitive +- Boolean values should be set as `true` or `false` +- Integer values should be provided as numbers without quotes +- String values can be provided with or without quotes +- Empty string values will be treated as valid configuration +- For certificate/key values, use proper PEM format with escaped newlines (`\n`) in environment variables \ No newline at end of file From 6cad02d572ed75b31e2fa479e287ab076835c30b Mon Sep 17 00:00:00 2001 From: Christian Elsen Date: Thu, 3 Jul 2025 22:18:04 -0700 Subject: [PATCH 02/13] Add admin user creation via environment variables --- api/lib/config.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/api/lib/config.ts b/api/lib/config.ts index 3ee6e87fe..c82f5c89d 100644 --- a/api/lib/config.ts +++ b/api/lib/config.ts @@ -230,6 +230,35 @@ export default class Config { }); } + // Create admin user if credentials provided and server is configured with auth + if (process.env.CLOUDTAK_ADMIN_USERNAME && process.env.CLOUDTAK_ADMIN_PASSWORD && + config.server.auth.cert && config.server.auth.key && config.server.webtak) { + try { + // Check if admin user already exists + const existingAdmin = await config.models.Profile.list({ + filter: 'system_admin', + limit: 1 + }); + + if (existingAdmin.total === 0) { + console.error('ok - Creating admin user from environment variables'); + const { TAKAPI, APIAuthPassword } = await import('@tak-ps/node-tak'); + + const auth = new APIAuthPassword(process.env.CLOUDTAK_ADMIN_USERNAME, process.env.CLOUDTAK_ADMIN_PASSWORD); + const api = await TAKAPI.init(new URL(config.server.webtak), auth); + const certs = await api.Credentials.generate(); + + await config.models.Profile.generate({ + auth: certs, + username: process.env.CLOUDTAK_ADMIN_USERNAME, + system_admin: true + }); + } + } catch (err) { + console.error(`Error creating admin user: ${err instanceof Error ? err.message : String(err)}`); + } + } + for (const envkey in process.env) { if (!envkey.startsWith('CLOUDTAK')) continue; From 6f1b2fa81438ee7b5314da8a21f79e3bd399a847 Mon Sep 17 00:00:00 2001 From: Christian Elsen Date: Thu, 3 Jul 2025 22:24:37 -0700 Subject: [PATCH 03/13] Add tests for admin user environment variables - Test admin creation with CLOUDTAK_ADMIN_USERNAME/PASSWORD - Test skipping admin creation when credentials missing --- api/test/config.srv.test.ts | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/api/test/config.srv.test.ts b/api/test/config.srv.test.ts index ea69b4aa8..c519b7e08 100644 --- a/api/test/config.srv.test.ts +++ b/api/test/config.srv.test.ts @@ -299,4 +299,51 @@ test('Server Env: schema validation with invalid field', async (t) => { t.end(); }); +// Admin User Environment Variable Tests +test('Admin Env: CLOUDTAK_ADMIN_USERNAME and CLOUDTAK_ADMIN_PASSWORD create admin user', async (t) => { + const originalUsername = process.env.CLOUDTAK_ADMIN_USERNAME; + const originalPassword = process.env.CLOUDTAK_ADMIN_PASSWORD; + + process.env.CLOUDTAK_ADMIN_USERNAME = 'test-admin'; + process.env.CLOUDTAK_ADMIN_PASSWORD = 'test-password'; + + try { + // Mock the Profile.list method to simulate no existing admin + const listStub = sinon.stub(flight.config.models.Profile, 'list').resolves({ total: 0 }); + const generateStub = sinon.stub(flight.config.models.Profile, 'generate').resolves(); + + // Test that admin creation logic would be triggered + const hasAdminCredentials = process.env.CLOUDTAK_ADMIN_USERNAME && process.env.CLOUDTAK_ADMIN_PASSWORD; + t.ok(hasAdminCredentials, 'Admin credentials are set'); + + listStub.restore(); + generateStub.restore(); + } catch (err) { + t.error(err, 'no error'); + } + + process.env.CLOUDTAK_ADMIN_USERNAME = originalUsername; + process.env.CLOUDTAK_ADMIN_PASSWORD = originalPassword; + t.end(); +}); + +test('Admin Env: Missing credentials skip admin creation', async (t) => { + const originalUsername = process.env.CLOUDTAK_ADMIN_USERNAME; + const originalPassword = process.env.CLOUDTAK_ADMIN_PASSWORD; + + delete process.env.CLOUDTAK_ADMIN_USERNAME; + delete process.env.CLOUDTAK_ADMIN_PASSWORD; + + try { + const hasAdminCredentials = process.env.CLOUDTAK_ADMIN_USERNAME && process.env.CLOUDTAK_ADMIN_PASSWORD; + t.notOk(hasAdminCredentials, 'Admin credentials are not set'); + } catch (err) { + t.error(err, 'no error'); + } + + if (originalUsername) process.env.CLOUDTAK_ADMIN_USERNAME = originalUsername; + if (originalPassword) process.env.CLOUDTAK_ADMIN_PASSWORD = originalPassword; + t.end(); +}); + flight.landing(); \ No newline at end of file From 98aef3d54e170237b5a7e47a8c2c84e6e84aa9bd Mon Sep 17 00:00:00 2001 From: Christian Elsen Date: Thu, 3 Jul 2025 22:50:24 -0700 Subject: [PATCH 04/13] fix: Linting errors fixed - Removing the unused Config import - Removing the unused err parameter in the catch block for the schema validation test --- api/test/config.srv.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/test/config.srv.test.ts b/api/test/config.srv.test.ts index c519b7e08..b2eab72d3 100644 --- a/api/test/config.srv.test.ts +++ b/api/test/config.srv.test.ts @@ -1,7 +1,6 @@ import test from 'tape'; import Flight from './flight.js'; import sinon from 'sinon'; -import Config from '../lib/config.js'; const flight = new Flight(); @@ -292,7 +291,7 @@ test('Server Env: schema validation with invalid field', async (t) => { }); t.fail('Should have thrown error for invalid field'); - } catch (err) { + } catch { t.pass('Correctly rejected invalid field'); } @@ -346,4 +345,4 @@ test('Admin Env: Missing credentials skip admin creation', async (t) => { t.end(); }); -flight.landing(); \ No newline at end of file +flight.landing(); From aaaca0b2ddfe64437dbda825f66240ab45b93218 Mon Sep 17 00:00:00 2001 From: Christian Elsen Date: Fri, 4 Jul 2025 18:22:33 -0700 Subject: [PATCH 05/13] Tested version of config.ts Works with CDK deployed CloudTAK layer. --- api/lib/config.ts | 236 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 166 insertions(+), 70 deletions(-) diff --git a/api/lib/config.ts b/api/lib/config.ts index c82f5c89d..c2da5bdc8 100644 --- a/api/lib/config.ts +++ b/api/lib/config.ts @@ -146,39 +146,84 @@ export default class Config { } catch (err) { console.log(`ok - no server config found: ${err instanceof Error ? err.message : String(err)}`); - server = await models.Server.generate({ - name: 'Default Server', - url: 'ssl://localhost:8089', - api: 'https://localhost:8443' - }); + // Create server with environment variables if available + const serverData: Record = { + name: process.env.CLOUDTAK_Server_name || 'Default Server', + url: process.env.CLOUDTAK_Server_url || 'ssl://localhost:8089', + api: process.env.CLOUDTAK_Server_api || 'https://localhost:8443' + }; + + if (process.env.CLOUDTAK_Server_webtak) { + serverData.webtak = process.env.CLOUDTAK_Server_webtak; + } + + // Handle auth certificates + if (process.env.CLOUDTAK_Server_auth_p12_secret_arn && process.env.CLOUDTAK_Server_auth_password) { + try { + const secrets = new SecretsManager.SecretsManagerClient({ region: process.env.AWS_REGION }); + const secretValue = await secrets.send(new SecretsManager.GetSecretValueCommand({ + SecretId: process.env.CLOUDTAK_Server_auth_p12_secret_arn + })); + + if (secretValue.SecretBinary) { + const pem = (await import('pem')).default; + const p12Buffer = Buffer.from(secretValue.SecretBinary); + + const certs = await new Promise<{ pemCertificate: string; pemKey: string }>((resolve, reject) => { + pem.readPkcs12(p12Buffer, { p12Password: process.env.CLOUDTAK_Server_auth_password }, (err: Error | null, result: { cert: string; key: string }) => { + if (err) { + reject(err); + } else { + resolve({ pemCertificate: result.cert, pemKey: result.key }); + } + }); + }); + + serverData.auth = { + cert: certs.pemCertificate, + key: certs.pemKey + }; + console.error('ok - Extracted certificate and key from P12 binary secret'); + } + } catch (err) { + console.error(`Error extracting P12 from binary secret: ${err instanceof Error ? err.message : String(err)}`); + } + } else if (process.env.CLOUDTAK_Server_auth_cert && process.env.CLOUDTAK_Server_auth_key) { + serverData.auth = { + cert: process.env.CLOUDTAK_Server_auth_cert, + key: process.env.CLOUDTAK_Server_auth_key + }; + } + + server = await models.Server.generate(serverData); } - - const config = new Config({ - silent: (args.silent || false), - noevents: (args.noevents || false), - nosinks: (args.nosinks || false), - nocache: (args.nocache || false), - StackName: process.env.StackName, - wsClients: new Map(), - server, SigningSecret, MediaSecret, API_URL, DynamoDB, Bucket, pg, models, PMTILES_URL - }); - - if (!config.silent) { - console.error('ok - set env AWS_REGION: us-east-1'); - console.log(`ok - PMTiles: ${config.PMTILES_URL}`); - console.error(`ok - StackName: ${config.StackName}`); + + // Update server with environment variables + console.error(`ok - Initial server state: auth.cert=${!!server.auth?.cert}, auth.key=${!!server.auth?.key}, webtak=${!!server.webtak}`); + console.error(`ok - Environment variables: CLOUDTAK_Server_name=${process.env.CLOUDTAK_Server_name}, CLOUDTAK_Server_url=${process.env.CLOUDTAK_Server_url}, CLOUDTAK_Server_api=${process.env.CLOUDTAK_Server_api}, CLOUDTAK_Server_webtak=${process.env.CLOUDTAK_Server_webtak}`); + console.error(`ok - Auth env vars: CLOUDTAK_Server_auth_cert=${!!process.env.CLOUDTAK_Server_auth_cert}, CLOUDTAK_Server_auth_key=${!!process.env.CLOUDTAK_Server_auth_key}, CLOUDTAK_Server_auth_p12=${!!process.env.CLOUDTAK_Server_auth_p12}`); + console.error(`ok - Admin env vars: CLOUDTAK_ADMIN_USERNAME=${!!process.env.CLOUDTAK_ADMIN_USERNAME}, CLOUDTAK_ADMIN_PASSWORD=${!!process.env.CLOUDTAK_ADMIN_PASSWORD}`); + + // Debug all CLOUDTAK environment variables + const cloudtakEnvs = Object.keys(process.env).filter(key => key.startsWith('CLOUDTAK_')).sort(); + console.error(`ok - All CLOUDTAK env vars: ${cloudtakEnvs.join(', ')}`); + + if (process.env.CLOUDTAK_Server_auth_p12) { + console.error(`ok - P12 content length: ${process.env.CLOUDTAK_Server_auth_p12.length}`); + console.error(`ok - P12 starts with: ${process.env.CLOUDTAK_Server_auth_p12.substring(0, 50)}...`); + } else { + console.error('ok - CLOUDTAK_Server_auth_p12 is undefined/empty'); } - - const external = await External.init(config); - config.external = external; - - if (process.env.VpcId) config.VpcId = process.env.VpcId; - if (process.env.SubnetPublicA) config.SubnetPublicA = process.env.SubnetPublicA; - if (process.env.SubnetPublicB) config.SubnetPublicB = process.env.SubnetPublicB; - if (process.env.MediaSecurityGroup) config.MediaSecurityGroup = process.env.MediaSecurityGroup; - - // Handle server configuration via environment variables - const serverEnvUpdates: Partial> = {}; + + if (process.env.CLOUDTAK_Server_auth_cert) { + console.error(`ok - Direct cert length: ${process.env.CLOUDTAK_Server_auth_cert.length}`); + } + + if (process.env.CLOUDTAK_Server_auth_key) { + console.error(`ok - Direct key length: ${process.env.CLOUDTAK_Server_auth_key.length}`); + } + + const serverEnvUpdates: Record = {}; let hasServerUpdates = false; if (process.env.CLOUDTAK_Server_name) { @@ -197,65 +242,116 @@ export default class Config { serverEnvUpdates.webtak = process.env.CLOUDTAK_Server_webtak; hasServerUpdates = true; } - // Support both P12 file + password OR separate cert + key - if (process.env.CLOUDTAK_Server_auth_p12 && process.env.CLOUDTAK_Server_auth_password) { + + // Handle auth certificates for existing server + console.error('ok - Updating server configuration from environment variables'); + + if (process.env.CLOUDTAK_Server_auth_p12_secret_arn && process.env.CLOUDTAK_Server_auth_password) { + console.error('ok - Processing P12 certificate from binary secret'); try { - // Import p12-pem library for P12 extraction - const { convertToPem } = await import('p12-pem/lib/lib/p12.js'); - const p12Buffer = Buffer.from(process.env.CLOUDTAK_Server_auth_p12, 'base64'); - const certs = convertToPem(p12Buffer, process.env.CLOUDTAK_Server_auth_password); + const secrets = new SecretsManager.SecretsManagerClient({ region: process.env.AWS_REGION }); + const secretValue = await secrets.send(new SecretsManager.GetSecretValueCommand({ + SecretId: process.env.CLOUDTAK_Server_auth_p12_secret_arn + })); - serverEnvUpdates.auth = { - cert: certs.pemCertificate, - key: certs.pemKey - }; - hasServerUpdates = true; - console.error('ok - Extracted certificate and key from P12 file'); + if (secretValue.SecretBinary) { + const pem = (await import('pem')).default; + const p12Buffer = Buffer.from(secretValue.SecretBinary); + + const certs = await new Promise<{ pemCertificate: string; pemKey: string }>((resolve, reject) => { + pem.readPkcs12(p12Buffer, { p12Password: process.env.CLOUDTAK_Server_auth_password }, (err: Error | null, result: { cert: string; key: string }) => { + if (err) { + reject(err); + } else { + resolve({ pemCertificate: result.cert, pemKey: result.key }); + } + }); + }); + + if (certs.pemCertificate && certs.pemKey) { + serverEnvUpdates.auth = { + ...(server.auth || {}), + cert: certs.pemCertificate, + key: certs.pemKey + }; + hasServerUpdates = true; + console.error('ok - Successfully extracted certificate and key from P12 binary secret'); + } else { + console.error('ok - P12 conversion failed: missing certificate or key'); + } + } else { + console.error('ok - No SecretBinary found in secret'); + } } catch (err) { - console.error(`Error extracting P12 file: ${err instanceof Error ? err.message : String(err)}`); + console.error(`ok - Error processing P12 binary secret: ${err instanceof Error ? err.message : String(err)}`); } } else if (process.env.CLOUDTAK_Server_auth_cert && process.env.CLOUDTAK_Server_auth_key) { + console.error('ok - Using direct certificate and key from environment variables'); serverEnvUpdates.auth = { + ...(server.auth || {}), cert: process.env.CLOUDTAK_Server_auth_cert, key: process.env.CLOUDTAK_Server_auth_key }; hasServerUpdates = true; + console.error(`ok - Direct auth configured: cert=${process.env.CLOUDTAK_Server_auth_cert.length} chars, key=${process.env.CLOUDTAK_Server_auth_key.length} chars`); + } else { + console.error('ok - No certificate environment variables found - server will run without client certificates'); } - + if (hasServerUpdates) { - console.error('ok - Updating server configuration from environment variables'); - config.server = await config.models.Server.commit(config.server.id, { + console.error(`ok - Updates to apply: ${JSON.stringify(Object.keys(serverEnvUpdates))}`); + server = await models.Server.commit(server.id, { ...serverEnvUpdates, updated: sql`Now()` }); + console.error(`ok - Server updated: auth.cert=${!!server.auth?.cert}, auth.key=${!!server.auth?.key}, webtak=${!!server.webtak}`); + } else { + console.error('ok - No server updates needed from environment variables'); + } + + console.error(`ok - Final server state before Config creation: auth.cert=${!!server.auth?.cert}, auth.key=${!!server.auth?.key}, webtak=${!!server.webtak}`); + + const config = new Config({ + silent: (args.silent || false), + noevents: (args.noevents || false), + nosinks: (args.nosinks || false), + nocache: (args.nocache || false), + StackName: process.env.StackName, + wsClients: new Map(), + server, SigningSecret, MediaSecret, API_URL, DynamoDB, Bucket, pg, models, PMTILES_URL + }); + + console.error(`ok - Config created with server: auth.cert=${!!config.server.auth?.cert}, auth.key=${!!config.server.auth?.key}, webtak=${!!config.server.webtak}`); + + if (!config.silent) { + console.error(`ok - set env AWS_REGION: ${process.env.AWS_REGION}`); + console.log(`ok - PMTiles: ${config.PMTILES_URL}`); + console.error(`ok - StackName: ${config.StackName}`); } - // Create admin user if credentials provided and server is configured with auth - if (process.env.CLOUDTAK_ADMIN_USERNAME && process.env.CLOUDTAK_ADMIN_PASSWORD && - config.server.auth.cert && config.server.auth.key && config.server.webtak) { + const external = await External.init(config); + config.external = external; + + if (process.env.VpcId) config.VpcId = process.env.VpcId; + if (process.env.SubnetPublicA) config.SubnetPublicA = process.env.SubnetPublicA; + if (process.env.SubnetPublicB) config.SubnetPublicB = process.env.SubnetPublicB; + if (process.env.MediaSecurityGroup) config.MediaSecurityGroup = process.env.MediaSecurityGroup; + + // Ensure admin user has admin permissions if credentials provided + if (process.env.CLOUDTAK_ADMIN_USERNAME && process.env.CLOUDTAK_ADMIN_PASSWORD) { try { - // Check if admin user already exists - const existingAdmin = await config.models.Profile.list({ - filter: 'system_admin', - limit: 1 - }); + console.error('ok - Ensuring admin user has admin permissions'); - if (existingAdmin.total === 0) { - console.error('ok - Creating admin user from environment variables'); - const { TAKAPI, APIAuthPassword } = await import('@tak-ps/node-tak'); - - const auth = new APIAuthPassword(process.env.CLOUDTAK_ADMIN_USERNAME, process.env.CLOUDTAK_ADMIN_PASSWORD); - const api = await TAKAPI.init(new URL(config.server.webtak), auth); - const certs = await api.Credentials.generate(); - - await config.models.Profile.generate({ - auth: certs, - username: process.env.CLOUDTAK_ADMIN_USERNAME, - system_admin: true - }); - } + // Create admin user directly in database with admin permissions + await config.models.Profile.generate({ + username: process.env.CLOUDTAK_ADMIN_USERNAME, + auth: { password: process.env.CLOUDTAK_ADMIN_PASSWORD }, + system_admin: true + }, { upsert: GenerateUpsert.UPDATE }); + + console.error('ok - Admin user ensured with admin permissions'); } catch (err) { - console.error(`Error creating admin user: ${err instanceof Error ? err.message : String(err)}`); + console.error(`Error ensuring admin user: ${err instanceof Error ? err.message : String(err)}`); } } From a4a39ff37e3dc125f69ff2ef91f15341abcae3e5 Mon Sep 17 00:00:00 2001 From: Christian Elsen Date: Fri, 4 Jul 2025 18:23:25 -0700 Subject: [PATCH 06/13] Updated config.srv.test.ts Updated tests for working config.ts version --- api/test/config.srv.test.ts | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/api/test/config.srv.test.ts b/api/test/config.srv.test.ts index b2eab72d3..f64810854 100644 --- a/api/test/config.srv.test.ts +++ b/api/test/config.srv.test.ts @@ -241,6 +241,29 @@ test('Server Env: auth object updates database', async (t) => { t.end(); }); +test('Server Env: P12 certificate processing from secret ARN', async (t) => { + const originalSecretArn = process.env.CLOUDTAK_Server_auth_p12_secret_arn; + const originalPassword = process.env.CLOUDTAK_Server_auth_password; + + process.env.CLOUDTAK_Server_auth_p12_secret_arn = 'arn:aws:secretsmanager:region:account:secret:test-secret'; + process.env.CLOUDTAK_Server_auth_password = 'test-password'; + + try { + // Test that P12 processing environment variables are set correctly + const hasP12Config = process.env.CLOUDTAK_Server_auth_p12_secret_arn && process.env.CLOUDTAK_Server_auth_password; + t.ok(hasP12Config, 'P12 certificate configuration is set'); + + // Test that the secret ARN format is valid + t.ok(process.env.CLOUDTAK_Server_auth_p12_secret_arn.startsWith('arn:aws:secretsmanager:'), 'Secret ARN has correct format'); + } catch (err) { + t.error(err, 'no error'); + } + + process.env.CLOUDTAK_Server_auth_p12_secret_arn = originalSecretArn; + process.env.CLOUDTAK_Server_auth_password = originalPassword; + t.end(); +}); + test('Server Env: multiple fields update database', async (t) => { const originalVars = { name: process.env.CLOUDTAK_Server_name, @@ -326,6 +349,33 @@ test('Admin Env: CLOUDTAK_ADMIN_USERNAME and CLOUDTAK_ADMIN_PASSWORD create admi t.end(); }); +test('Admin Env: Admin user creation requires server auth configuration', async (t) => { + const originalUsername = process.env.CLOUDTAK_ADMIN_USERNAME; + const originalPassword = process.env.CLOUDTAK_ADMIN_PASSWORD; + + process.env.CLOUDTAK_ADMIN_USERNAME = 'test-admin'; + process.env.CLOUDTAK_ADMIN_PASSWORD = 'test-password'; + + try { + const server = await flight.config.models.Server.from(1); + + // Admin user creation requires server to have auth.cert, auth.key, and webtak configured + const canCreateAdmin = server.auth?.cert && server.auth?.key && server.webtak; + + if (canCreateAdmin) { + t.pass('Server has required auth configuration for admin user creation'); + } else { + t.pass('Server missing auth configuration - admin user creation will be skipped'); + } + } catch (err) { + t.error(err, 'no error'); + } + + process.env.CLOUDTAK_ADMIN_USERNAME = originalUsername; + process.env.CLOUDTAK_ADMIN_PASSWORD = originalPassword; + t.end(); +}); + test('Admin Env: Missing credentials skip admin creation', async (t) => { const originalUsername = process.env.CLOUDTAK_ADMIN_USERNAME; const originalPassword = process.env.CLOUDTAK_ADMIN_PASSWORD; From 25a92020f671f82f11ec3b0d994033542de46398 Mon Sep 17 00:00:00 2001 From: Christian Elsen Date: Fri, 4 Jul 2025 18:27:34 -0700 Subject: [PATCH 07/13] Updated ENVIRONMENT_VARIABLES.md Updated documentation --- docs/ENVIRONMENT_VARIABLES.md | 109 +++++++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 29 deletions(-) diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md index f1770990b..70bc04606 100644 --- a/docs/ENVIRONMENT_VARIABLES.md +++ b/docs/ENVIRONMENT_VARIABLES.md @@ -2,16 +2,9 @@ CloudTAK supports configuration through environment variables that override database settings when present at startup. This document lists all available configuration environment variables. -## Format +## Server Configuration Variables -Environment variables follow the pattern: -- Prefix: `CLOUDTAK_Config_` -- Replace `::` with `_` in the config key -- All characters after `CLOUDTAK_Config_` are case SENSITIVE - -**Example:** `media::url` becomes `CLOUDTAK_Config_media_url` - -## Available Environment Variables +These variables configure the TAK server connection and are processed during application startup to solve the first-boot configuration problem. ### TAK Server Configuration @@ -20,11 +13,41 @@ Environment variables follow the pattern: | `CLOUDTAK_Server_name` | string | TAK Server name/description | | `CLOUDTAK_Server_url` | string | TAK Server connection URL (e.g., "ssl://tak.example.com:8089") | | `CLOUDTAK_Server_api` | string | TAK Server API URL (e.g., "https://tak.example.com:8443") | -| `CLOUDTAK_Server_webtak` | string | TAK Server WebTAK URL (e.g., "https://tak.example.com:8443") | +| `CLOUDTAK_Server_webtak` | string | TAK Server WebTAK URL (e.g., "https://tak.example.com:8446") | + +### TAK Server Authentication + +CloudTAK supports multiple authentication methods for connecting to TAK servers: + +#### Option 1: P12/PKCS12 Certificate (Recommended for AWS) +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Server_auth_p12_secret_arn` | string | AWS Secrets Manager ARN containing P12 certificate | +| `CLOUDTAK_Server_auth_password` | string | Password for P12/PKCS12 file | + +#### Option 2: Direct Certificate and Key +| Environment Variable | Type | Description | +|---------------------|------|-------------| | `CLOUDTAK_Server_auth_cert` | string | TAK Server client certificate (PEM format) | | `CLOUDTAK_Server_auth_key` | string | TAK Server client private key (PEM format) | -| `CLOUDTAK_Server_auth_p12` | string | TAK Server P12/PKCS12 file (base64 encoded) | -| `CLOUDTAK_Server_auth_password` | string | Password for P12/PKCS12 file | + +### Admin User Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_ADMIN_USERNAME` | string | Admin username for CloudTAK system | +| `CLOUDTAK_ADMIN_PASSWORD` | string | Admin password for CloudTAK system | + +## Application Configuration Variables + +These variables follow the pattern `CLOUDTAK_Config_` and override database settings. + +### Format Rules +- Prefix: `CLOUDTAK_Config_` +- Replace `::` with `_` in the config key +- All characters after `CLOUDTAK_Config_` are case SENSITIVE + +**Example:** `media::url` becomes `CLOUDTAK_Config_media_url` ### ArcGIS Online Configuration @@ -96,41 +119,46 @@ Environment variables follow the pattern: ## Usage Examples -### TAK Server Configuration +### TAK Server Configuration with P12 Certificate (AWS) ```bash # Configure TAK Server connection export CLOUDTAK_Server_name="Production TAK Server" export CLOUDTAK_Server_url="ssl://tak.example.com:8089" export CLOUDTAK_Server_api="https://tak.example.com:8443" -export CLOUDTAK_Server_webtak="https://tak.example.com:8443" +export CLOUDTAK_Server_webtak="https://tak.example.com:8446" -# Option 1: Use P12 file (recommended for automation) -export CLOUDTAK_Server_auth_p12="$(base64 -w 0 /path/to/client.p12)" +# Use P12 certificate from AWS Secrets Manager +export CLOUDTAK_Server_auth_p12_secret_arn="arn:aws:secretsmanager:region:account:secret:tak-cert" export CLOUDTAK_Server_auth_password="atakatak" -# Option 2: Use separate certificate and key files +# Admin user configuration +export CLOUDTAK_ADMIN_USERNAME="admin" +export CLOUDTAK_ADMIN_PASSWORD="secure-password" +``` + +### TAK Server Configuration with Direct Certificates +```bash +# Configure TAK Server connection +export CLOUDTAK_Server_name="Development TAK Server" +export CLOUDTAK_Server_url="ssl://tak-dev.example.com:8089" +export CLOUDTAK_Server_api="https://tak-dev.example.com:8443" +export CLOUDTAK_Server_webtak="https://tak-dev.example.com:8446" + +# Use direct certificate and key export CLOUDTAK_Server_auth_cert="-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" export CLOUDTAK_Server_auth_key="-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----" ``` -### Basic Configuration +### Application Configuration ```bash # Set map center to New Zealand coordinates export CLOUDTAK_Config_map_center="-41.2865,174.7762" - -# Set initial zoom level export CLOUDTAK_Config_map_zoom=6 -``` -### ArcGIS Online Integration -```bash # Enable ArcGIS Online export CLOUDTAK_Config_agol_enabled=true export CLOUDTAK_Config_agol_token="your_agol_token_here" -``` -### Group Configuration -```bash # Configure emergency response groups export CLOUDTAK_Config_group_Red="Emergency Response Team" export CLOUDTAK_Config_group_Blue="Police Units" @@ -159,7 +187,14 @@ services: - CLOUDTAK_Server_name=Production TAK Server - CLOUDTAK_Server_url=ssl://tak.example.com:8089 - CLOUDTAK_Server_api=https://tak.example.com:8443 - - CLOUDTAK_Server_webtak=https://tak.example.com:8443 + - CLOUDTAK_Server_webtak=https://tak.example.com:8446 + - CLOUDTAK_Server_auth_cert=-----BEGIN CERTIFICATE-----... + - CLOUDTAK_Server_auth_key=-----BEGIN PRIVATE KEY-----... + + # Admin Configuration + - CLOUDTAK_ADMIN_USERNAME=admin + - CLOUDTAK_ADMIN_PASSWORD=secure-password + # Application Configuration - CLOUDTAK_Config_map_center=-41.2865,174.7762 - CLOUDTAK_Config_map_zoom=6 @@ -168,13 +203,29 @@ services: - CLOUDTAK_Config_group_Red=Emergency Response ``` +## Configuration Processing + +### Server Configuration +1. **First Boot**: If no server configuration exists in the database, CloudTAK creates initial server configuration using environment variables +2. **Runtime Updates**: Server configuration environment variables override database values when present at startup +3. **Certificate Handling**: Supports both P12/PKCS12 files (via AWS Secrets Manager) and direct PEM certificates +4. **Admin User Creation**: Automatically creates admin user with system admin permissions when credentials are provided + +### Application Configuration +1. **Database Override**: `CLOUDTAK_Config_*` variables override corresponding database settings +2. **Startup Processing**: All matching environment variables are processed and stored in the database during startup +3. **Case Sensitivity**: Configuration keys after `CLOUDTAK_Config_` are case-sensitive +4. **Key Transformation**: `::` in database keys becomes `_` in environment variable names + ## Important Notes -- Environment variables present at launch will **OVERRIDE** any values stored in the database - **Server configuration** environment variables solve the first-boot configuration problem by allowing automated TAK server setup without requiring API authentication +- Environment variables present at launch will **OVERRIDE** any values stored in the database - All characters after `CLOUDTAK_Config_` are case sensitive - Boolean values should be set as `true` or `false` - Integer values should be provided as numbers without quotes - String values can be provided with or without quotes - Empty string values will be treated as valid configuration -- For certificate/key values, use proper PEM format with escaped newlines (`\n`) in environment variables \ No newline at end of file +- For certificate/key values, use proper PEM format with escaped newlines (`\n`) in environment variables +- P12 certificates from AWS Secrets Manager are automatically converted to PEM format +- Admin users created via environment variables receive full system administrator privileges From 5166fdc02249bc7e48353018282870d2f4349aea Mon Sep 17 00:00:00 2001 From: ingalls Date: Fri, 11 Jul 2025 16:12:54 -0700 Subject: [PATCH 08/13] Bump v --- api/package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index c59e87736..9cd6a8970 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tak-ps/CloudTAK.api", - "version": "10.22.1", + "version": "10.25.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tak-ps/CloudTAK.api", - "version": "10.22.1", + "version": "10.25.0", "license": "ISC", "dependencies": { "@aws-sdk/client-batch": "^3.301.0", From a1eb69354e8fc073dbfa854a14a01a196d0ff3c3 Mon Sep 17 00:00:00 2001 From: ingalls Date: Fri, 11 Jul 2025 16:25:20 -0700 Subject: [PATCH 09/13] Remove Floating Whitespace --- api/lib/config.ts | 44 ++++++++++----------- api/test/config.srv.test.ts | 78 ++++++++++++++++++------------------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/api/lib/config.ts b/api/lib/config.ts index c2da5bdc8..ce8634c23 100644 --- a/api/lib/config.ts +++ b/api/lib/config.ts @@ -152,11 +152,11 @@ export default class Config { url: process.env.CLOUDTAK_Server_url || 'ssl://localhost:8089', api: process.env.CLOUDTAK_Server_api || 'https://localhost:8443' }; - + if (process.env.CLOUDTAK_Server_webtak) { serverData.webtak = process.env.CLOUDTAK_Server_webtak; } - + // Handle auth certificates if (process.env.CLOUDTAK_Server_auth_p12_secret_arn && process.env.CLOUDTAK_Server_auth_password) { try { @@ -164,11 +164,11 @@ export default class Config { const secretValue = await secrets.send(new SecretsManager.GetSecretValueCommand({ SecretId: process.env.CLOUDTAK_Server_auth_p12_secret_arn })); - + if (secretValue.SecretBinary) { const pem = (await import('pem')).default; const p12Buffer = Buffer.from(secretValue.SecretBinary); - + const certs = await new Promise<{ pemCertificate: string; pemKey: string }>((resolve, reject) => { pem.readPkcs12(p12Buffer, { p12Password: process.env.CLOUDTAK_Server_auth_password }, (err: Error | null, result: { cert: string; key: string }) => { if (err) { @@ -178,7 +178,7 @@ export default class Config { } }); }); - + serverData.auth = { cert: certs.pemCertificate, key: certs.pemKey @@ -194,35 +194,35 @@ export default class Config { key: process.env.CLOUDTAK_Server_auth_key }; } - + server = await models.Server.generate(serverData); } - + // Update server with environment variables console.error(`ok - Initial server state: auth.cert=${!!server.auth?.cert}, auth.key=${!!server.auth?.key}, webtak=${!!server.webtak}`); console.error(`ok - Environment variables: CLOUDTAK_Server_name=${process.env.CLOUDTAK_Server_name}, CLOUDTAK_Server_url=${process.env.CLOUDTAK_Server_url}, CLOUDTAK_Server_api=${process.env.CLOUDTAK_Server_api}, CLOUDTAK_Server_webtak=${process.env.CLOUDTAK_Server_webtak}`); console.error(`ok - Auth env vars: CLOUDTAK_Server_auth_cert=${!!process.env.CLOUDTAK_Server_auth_cert}, CLOUDTAK_Server_auth_key=${!!process.env.CLOUDTAK_Server_auth_key}, CLOUDTAK_Server_auth_p12=${!!process.env.CLOUDTAK_Server_auth_p12}`); console.error(`ok - Admin env vars: CLOUDTAK_ADMIN_USERNAME=${!!process.env.CLOUDTAK_ADMIN_USERNAME}, CLOUDTAK_ADMIN_PASSWORD=${!!process.env.CLOUDTAK_ADMIN_PASSWORD}`); - + // Debug all CLOUDTAK environment variables const cloudtakEnvs = Object.keys(process.env).filter(key => key.startsWith('CLOUDTAK_')).sort(); console.error(`ok - All CLOUDTAK env vars: ${cloudtakEnvs.join(', ')}`); - + if (process.env.CLOUDTAK_Server_auth_p12) { console.error(`ok - P12 content length: ${process.env.CLOUDTAK_Server_auth_p12.length}`); console.error(`ok - P12 starts with: ${process.env.CLOUDTAK_Server_auth_p12.substring(0, 50)}...`); } else { console.error('ok - CLOUDTAK_Server_auth_p12 is undefined/empty'); } - + if (process.env.CLOUDTAK_Server_auth_cert) { console.error(`ok - Direct cert length: ${process.env.CLOUDTAK_Server_auth_cert.length}`); } - + if (process.env.CLOUDTAK_Server_auth_key) { console.error(`ok - Direct key length: ${process.env.CLOUDTAK_Server_auth_key.length}`); } - + const serverEnvUpdates: Record = {}; let hasServerUpdates = false; @@ -242,10 +242,10 @@ export default class Config { serverEnvUpdates.webtak = process.env.CLOUDTAK_Server_webtak; hasServerUpdates = true; } - + // Handle auth certificates for existing server console.error('ok - Updating server configuration from environment variables'); - + if (process.env.CLOUDTAK_Server_auth_p12_secret_arn && process.env.CLOUDTAK_Server_auth_password) { console.error('ok - Processing P12 certificate from binary secret'); try { @@ -253,11 +253,11 @@ export default class Config { const secretValue = await secrets.send(new SecretsManager.GetSecretValueCommand({ SecretId: process.env.CLOUDTAK_Server_auth_p12_secret_arn })); - + if (secretValue.SecretBinary) { const pem = (await import('pem')).default; const p12Buffer = Buffer.from(secretValue.SecretBinary); - + const certs = await new Promise<{ pemCertificate: string; pemKey: string }>((resolve, reject) => { pem.readPkcs12(p12Buffer, { p12Password: process.env.CLOUDTAK_Server_auth_password }, (err: Error | null, result: { cert: string; key: string }) => { if (err) { @@ -267,7 +267,7 @@ export default class Config { } }); }); - + if (certs.pemCertificate && certs.pemKey) { serverEnvUpdates.auth = { ...(server.auth || {}), @@ -297,7 +297,7 @@ export default class Config { } else { console.error('ok - No certificate environment variables found - server will run without client certificates'); } - + if (hasServerUpdates) { console.error(`ok - Updates to apply: ${JSON.stringify(Object.keys(serverEnvUpdates))}`); server = await models.Server.commit(server.id, { @@ -310,7 +310,7 @@ export default class Config { } console.error(`ok - Final server state before Config creation: auth.cert=${!!server.auth?.cert}, auth.key=${!!server.auth?.key}, webtak=${!!server.webtak}`); - + const config = new Config({ silent: (args.silent || false), noevents: (args.noevents || false), @@ -320,7 +320,7 @@ export default class Config { wsClients: new Map(), server, SigningSecret, MediaSecret, API_URL, DynamoDB, Bucket, pg, models, PMTILES_URL }); - + console.error(`ok - Config created with server: auth.cert=${!!config.server.auth?.cert}, auth.key=${!!config.server.auth?.key}, webtak=${!!config.server.webtak}`); if (!config.silent) { @@ -341,14 +341,14 @@ export default class Config { if (process.env.CLOUDTAK_ADMIN_USERNAME && process.env.CLOUDTAK_ADMIN_PASSWORD) { try { console.error('ok - Ensuring admin user has admin permissions'); - + // Create admin user directly in database with admin permissions await config.models.Profile.generate({ username: process.env.CLOUDTAK_ADMIN_USERNAME, auth: { password: process.env.CLOUDTAK_ADMIN_PASSWORD }, system_admin: true }, { upsert: GenerateUpsert.UPDATE }); - + console.error('ok - Admin user ensured with admin permissions'); } catch (err) { console.error(`Error ensuring admin user: ${err instanceof Error ? err.message : String(err)}`); diff --git a/api/test/config.srv.test.ts b/api/test/config.srv.test.ts index f64810854..ec32ea6de 100644 --- a/api/test/config.srv.test.ts +++ b/api/test/config.srv.test.ts @@ -173,20 +173,20 @@ test('GET api/config/map', async (t) => { test('Server Env: CLOUDTAK_Server_name updates database', async (t) => { const originalEnv = process.env.CLOUDTAK_Server_name; process.env.CLOUDTAK_Server_name = 'Env Test Server'; - + try { const initialServer = await flight.config.models.Server.from(1); - + // Simulate server env update const updatedServer = await flight.config.models.Server.commit(initialServer.id, { name: process.env.CLOUDTAK_Server_name }); - + t.equal(updatedServer.name, 'Env Test Server'); } catch (err) { t.error(err, 'no error'); } - + process.env.CLOUDTAK_Server_name = originalEnv; t.end(); }); @@ -194,19 +194,19 @@ test('Server Env: CLOUDTAK_Server_name updates database', async (t) => { test('Server Env: CLOUDTAK_Server_url updates database', async (t) => { const originalEnv = process.env.CLOUDTAK_Server_url; process.env.CLOUDTAK_Server_url = 'ssl://env.test.com:8089'; - + try { const initialServer = await flight.config.models.Server.from(1); - + const updatedServer = await flight.config.models.Server.commit(initialServer.id, { url: process.env.CLOUDTAK_Server_url }); - + t.equal(updatedServer.url, 'ssl://env.test.com:8089'); } catch (err) { t.error(err, 'no error'); } - + process.env.CLOUDTAK_Server_url = originalEnv; t.end(); }); @@ -214,20 +214,20 @@ test('Server Env: CLOUDTAK_Server_url updates database', async (t) => { test('Server Env: auth object updates database', async (t) => { const originalCert = process.env.CLOUDTAK_Server_auth_cert; const originalKey = process.env.CLOUDTAK_Server_auth_key; - + process.env.CLOUDTAK_Server_auth_cert = 'test-cert-data'; process.env.CLOUDTAK_Server_auth_key = 'test-key-data'; - + try { const initialServer = await flight.config.models.Server.from(1); - + const updatedServer = await flight.config.models.Server.commit(initialServer.id, { auth: { cert: process.env.CLOUDTAK_Server_auth_cert, key: process.env.CLOUDTAK_Server_auth_key } }); - + t.deepEqual(updatedServer.auth, { cert: 'test-cert-data', key: 'test-key-data' @@ -235,7 +235,7 @@ test('Server Env: auth object updates database', async (t) => { } catch (err) { t.error(err, 'no error'); } - + process.env.CLOUDTAK_Server_auth_cert = originalCert; process.env.CLOUDTAK_Server_auth_key = originalKey; t.end(); @@ -244,21 +244,21 @@ test('Server Env: auth object updates database', async (t) => { test('Server Env: P12 certificate processing from secret ARN', async (t) => { const originalSecretArn = process.env.CLOUDTAK_Server_auth_p12_secret_arn; const originalPassword = process.env.CLOUDTAK_Server_auth_password; - + process.env.CLOUDTAK_Server_auth_p12_secret_arn = 'arn:aws:secretsmanager:region:account:secret:test-secret'; process.env.CLOUDTAK_Server_auth_password = 'test-password'; - + try { // Test that P12 processing environment variables are set correctly const hasP12Config = process.env.CLOUDTAK_Server_auth_p12_secret_arn && process.env.CLOUDTAK_Server_auth_password; t.ok(hasP12Config, 'P12 certificate configuration is set'); - + // Test that the secret ARN format is valid t.ok(process.env.CLOUDTAK_Server_auth_p12_secret_arn.startsWith('arn:aws:secretsmanager:'), 'Secret ARN has correct format'); } catch (err) { t.error(err, 'no error'); } - + process.env.CLOUDTAK_Server_auth_p12_secret_arn = originalSecretArn; process.env.CLOUDTAK_Server_auth_password = originalPassword; t.end(); @@ -270,27 +270,27 @@ test('Server Env: multiple fields update database', async (t) => { api: process.env.CLOUDTAK_Server_api, webtak: process.env.CLOUDTAK_Server_webtak }; - + process.env.CLOUDTAK_Server_name = 'Multi Field Server'; process.env.CLOUDTAK_Server_api = 'https://multi.test.com:8443'; process.env.CLOUDTAK_Server_webtak = 'http://multi.test.com:8444'; - + try { const initialServer = await flight.config.models.Server.from(1); - + const updatedServer = await flight.config.models.Server.commit(initialServer.id, { name: process.env.CLOUDTAK_Server_name, api: process.env.CLOUDTAK_Server_api, webtak: process.env.CLOUDTAK_Server_webtak }); - + t.equal(updatedServer.name, 'Multi Field Server'); t.equal(updatedServer.api, 'https://multi.test.com:8443'); t.equal(updatedServer.webtak, 'http://multi.test.com:8444'); } catch (err) { t.error(err, 'no error'); } - + // Restore environment Object.keys(originalVars).forEach(key => { const envVar = `CLOUDTAK_Server_${key}`; @@ -300,24 +300,24 @@ test('Server Env: multiple fields update database', async (t) => { delete process.env[envVar]; } }); - + t.end(); }); test('Server Env: schema validation with invalid field', async (t) => { try { const initialServer = await flight.config.models.Server.from(1); - + // This should fail if schema validation is working await flight.config.models.Server.commit(initialServer.id, { invalidField: 'should-fail' }); - + t.fail('Should have thrown error for invalid field'); } catch { t.pass('Correctly rejected invalid field'); } - + t.end(); }); @@ -325,25 +325,25 @@ test('Server Env: schema validation with invalid field', async (t) => { test('Admin Env: CLOUDTAK_ADMIN_USERNAME and CLOUDTAK_ADMIN_PASSWORD create admin user', async (t) => { const originalUsername = process.env.CLOUDTAK_ADMIN_USERNAME; const originalPassword = process.env.CLOUDTAK_ADMIN_PASSWORD; - + process.env.CLOUDTAK_ADMIN_USERNAME = 'test-admin'; process.env.CLOUDTAK_ADMIN_PASSWORD = 'test-password'; - + try { // Mock the Profile.list method to simulate no existing admin const listStub = sinon.stub(flight.config.models.Profile, 'list').resolves({ total: 0 }); const generateStub = sinon.stub(flight.config.models.Profile, 'generate').resolves(); - + // Test that admin creation logic would be triggered const hasAdminCredentials = process.env.CLOUDTAK_ADMIN_USERNAME && process.env.CLOUDTAK_ADMIN_PASSWORD; t.ok(hasAdminCredentials, 'Admin credentials are set'); - + listStub.restore(); generateStub.restore(); } catch (err) { t.error(err, 'no error'); } - + process.env.CLOUDTAK_ADMIN_USERNAME = originalUsername; process.env.CLOUDTAK_ADMIN_PASSWORD = originalPassword; t.end(); @@ -352,16 +352,16 @@ test('Admin Env: CLOUDTAK_ADMIN_USERNAME and CLOUDTAK_ADMIN_PASSWORD create admi test('Admin Env: Admin user creation requires server auth configuration', async (t) => { const originalUsername = process.env.CLOUDTAK_ADMIN_USERNAME; const originalPassword = process.env.CLOUDTAK_ADMIN_PASSWORD; - + process.env.CLOUDTAK_ADMIN_USERNAME = 'test-admin'; process.env.CLOUDTAK_ADMIN_PASSWORD = 'test-password'; - + try { const server = await flight.config.models.Server.from(1); - + // Admin user creation requires server to have auth.cert, auth.key, and webtak configured const canCreateAdmin = server.auth?.cert && server.auth?.key && server.webtak; - + if (canCreateAdmin) { t.pass('Server has required auth configuration for admin user creation'); } else { @@ -370,7 +370,7 @@ test('Admin Env: Admin user creation requires server auth configuration', async } catch (err) { t.error(err, 'no error'); } - + process.env.CLOUDTAK_ADMIN_USERNAME = originalUsername; process.env.CLOUDTAK_ADMIN_PASSWORD = originalPassword; t.end(); @@ -379,17 +379,17 @@ test('Admin Env: Admin user creation requires server auth configuration', async test('Admin Env: Missing credentials skip admin creation', async (t) => { const originalUsername = process.env.CLOUDTAK_ADMIN_USERNAME; const originalPassword = process.env.CLOUDTAK_ADMIN_PASSWORD; - + delete process.env.CLOUDTAK_ADMIN_USERNAME; delete process.env.CLOUDTAK_ADMIN_PASSWORD; - + try { const hasAdminCredentials = process.env.CLOUDTAK_ADMIN_USERNAME && process.env.CLOUDTAK_ADMIN_PASSWORD; t.notOk(hasAdminCredentials, 'Admin credentials are not set'); } catch (err) { t.error(err, 'no error'); } - + if (originalUsername) process.env.CLOUDTAK_ADMIN_USERNAME = originalUsername; if (originalPassword) process.env.CLOUDTAK_ADMIN_PASSWORD = originalPassword; t.end(); From 71dac220d746e2655b41509ede7da4459177c8e7 Mon Sep 17 00:00:00 2001 From: ingalls Date: Fri, 11 Jul 2025 16:26:58 -0700 Subject: [PATCH 10/13] Ensure promise is awaited --- api/test/flight.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/test/flight.ts b/api/test/flight.ts index a350a170b..7e5c7a53a 100644 --- a/api/test/flight.ts +++ b/api/test/flight.ts @@ -243,7 +243,7 @@ export default class Flight { Object.assign(this.config, custom); - this.config.models.Server.generate({ + await this.config.models.Server.generate({ name: 'Test Runner', url: 'ssl://localhost', auth: { From a58d1165b452bbd449f720148cec9838799f8e50 Mon Sep 17 00:00:00 2001 From: ingalls Date: Fri, 11 Jul 2025 16:58:32 -0700 Subject: [PATCH 11/13] Remove P12 test --- api/test/config.srv.test.ts | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/api/test/config.srv.test.ts b/api/test/config.srv.test.ts index ec32ea6de..dab8631a2 100644 --- a/api/test/config.srv.test.ts +++ b/api/test/config.srv.test.ts @@ -241,29 +241,6 @@ test('Server Env: auth object updates database', async (t) => { t.end(); }); -test('Server Env: P12 certificate processing from secret ARN', async (t) => { - const originalSecretArn = process.env.CLOUDTAK_Server_auth_p12_secret_arn; - const originalPassword = process.env.CLOUDTAK_Server_auth_password; - - process.env.CLOUDTAK_Server_auth_p12_secret_arn = 'arn:aws:secretsmanager:region:account:secret:test-secret'; - process.env.CLOUDTAK_Server_auth_password = 'test-password'; - - try { - // Test that P12 processing environment variables are set correctly - const hasP12Config = process.env.CLOUDTAK_Server_auth_p12_secret_arn && process.env.CLOUDTAK_Server_auth_password; - t.ok(hasP12Config, 'P12 certificate configuration is set'); - - // Test that the secret ARN format is valid - t.ok(process.env.CLOUDTAK_Server_auth_p12_secret_arn.startsWith('arn:aws:secretsmanager:'), 'Secret ARN has correct format'); - } catch (err) { - t.error(err, 'no error'); - } - - process.env.CLOUDTAK_Server_auth_p12_secret_arn = originalSecretArn; - process.env.CLOUDTAK_Server_auth_password = originalPassword; - t.end(); -}); - test('Server Env: multiple fields update database', async (t) => { const originalVars = { name: process.env.CLOUDTAK_Server_name, From 214165adc13b9ee5531aded9c31cc4dd4368db41 Mon Sep 17 00:00:00 2001 From: ingalls Date: Fri, 11 Jul 2025 17:00:00 -0700 Subject: [PATCH 12/13] Remove Server Tests --- api/test/config.srv.test.ts | 74 ------------------------------------- 1 file changed, 74 deletions(-) diff --git a/api/test/config.srv.test.ts b/api/test/config.srv.test.ts index dab8631a2..77b32a102 100644 --- a/api/test/config.srv.test.ts +++ b/api/test/config.srv.test.ts @@ -298,78 +298,4 @@ test('Server Env: schema validation with invalid field', async (t) => { t.end(); }); -// Admin User Environment Variable Tests -test('Admin Env: CLOUDTAK_ADMIN_USERNAME and CLOUDTAK_ADMIN_PASSWORD create admin user', async (t) => { - const originalUsername = process.env.CLOUDTAK_ADMIN_USERNAME; - const originalPassword = process.env.CLOUDTAK_ADMIN_PASSWORD; - - process.env.CLOUDTAK_ADMIN_USERNAME = 'test-admin'; - process.env.CLOUDTAK_ADMIN_PASSWORD = 'test-password'; - - try { - // Mock the Profile.list method to simulate no existing admin - const listStub = sinon.stub(flight.config.models.Profile, 'list').resolves({ total: 0 }); - const generateStub = sinon.stub(flight.config.models.Profile, 'generate').resolves(); - - // Test that admin creation logic would be triggered - const hasAdminCredentials = process.env.CLOUDTAK_ADMIN_USERNAME && process.env.CLOUDTAK_ADMIN_PASSWORD; - t.ok(hasAdminCredentials, 'Admin credentials are set'); - - listStub.restore(); - generateStub.restore(); - } catch (err) { - t.error(err, 'no error'); - } - - process.env.CLOUDTAK_ADMIN_USERNAME = originalUsername; - process.env.CLOUDTAK_ADMIN_PASSWORD = originalPassword; - t.end(); -}); - -test('Admin Env: Admin user creation requires server auth configuration', async (t) => { - const originalUsername = process.env.CLOUDTAK_ADMIN_USERNAME; - const originalPassword = process.env.CLOUDTAK_ADMIN_PASSWORD; - - process.env.CLOUDTAK_ADMIN_USERNAME = 'test-admin'; - process.env.CLOUDTAK_ADMIN_PASSWORD = 'test-password'; - - try { - const server = await flight.config.models.Server.from(1); - - // Admin user creation requires server to have auth.cert, auth.key, and webtak configured - const canCreateAdmin = server.auth?.cert && server.auth?.key && server.webtak; - - if (canCreateAdmin) { - t.pass('Server has required auth configuration for admin user creation'); - } else { - t.pass('Server missing auth configuration - admin user creation will be skipped'); - } - } catch (err) { - t.error(err, 'no error'); - } - - process.env.CLOUDTAK_ADMIN_USERNAME = originalUsername; - process.env.CLOUDTAK_ADMIN_PASSWORD = originalPassword; - t.end(); -}); - -test('Admin Env: Missing credentials skip admin creation', async (t) => { - const originalUsername = process.env.CLOUDTAK_ADMIN_USERNAME; - const originalPassword = process.env.CLOUDTAK_ADMIN_PASSWORD; - - delete process.env.CLOUDTAK_ADMIN_USERNAME; - delete process.env.CLOUDTAK_ADMIN_PASSWORD; - - try { - const hasAdminCredentials = process.env.CLOUDTAK_ADMIN_USERNAME && process.env.CLOUDTAK_ADMIN_PASSWORD; - t.notOk(hasAdminCredentials, 'Admin credentials are not set'); - } catch (err) { - t.error(err, 'no error'); - } - - if (originalUsername) process.env.CLOUDTAK_ADMIN_USERNAME = originalUsername; - if (originalPassword) process.env.CLOUDTAK_ADMIN_PASSWORD = originalPassword; - t.end(); -}); - flight.landing(); From cbf0de926fe28f1814a796f7b6e5b7f6473d2599 Mon Sep 17 00:00:00 2001 From: Christian Elsen Date: Thu, 14 Aug 2025 20:43:45 -0700 Subject: [PATCH 13/13] fix: correct debug logging variable in P12 certificate processing Fix debug output to use correct variable name in certificate length logging --- api/lib/config.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/api/lib/config.ts b/api/lib/config.ts index ce8634c23..d98d85a02 100644 --- a/api/lib/config.ts +++ b/api/lib/config.ts @@ -201,18 +201,17 @@ export default class Config { // Update server with environment variables console.error(`ok - Initial server state: auth.cert=${!!server.auth?.cert}, auth.key=${!!server.auth?.key}, webtak=${!!server.webtak}`); console.error(`ok - Environment variables: CLOUDTAK_Server_name=${process.env.CLOUDTAK_Server_name}, CLOUDTAK_Server_url=${process.env.CLOUDTAK_Server_url}, CLOUDTAK_Server_api=${process.env.CLOUDTAK_Server_api}, CLOUDTAK_Server_webtak=${process.env.CLOUDTAK_Server_webtak}`); - console.error(`ok - Auth env vars: CLOUDTAK_Server_auth_cert=${!!process.env.CLOUDTAK_Server_auth_cert}, CLOUDTAK_Server_auth_key=${!!process.env.CLOUDTAK_Server_auth_key}, CLOUDTAK_Server_auth_p12=${!!process.env.CLOUDTAK_Server_auth_p12}`); + console.error(`ok - Auth env vars: CLOUDTAK_Server_auth_cert=${!!process.env.CLOUDTAK_Server_auth_cert}, CLOUDTAK_Server_auth_key=${!!process.env.CLOUDTAK_Server_auth_key}, CLOUDTAK_Server_auth_p12_secret_arn=${!!process.env.CLOUDTAK_Server_auth_p12_secret_arn}`); console.error(`ok - Admin env vars: CLOUDTAK_ADMIN_USERNAME=${!!process.env.CLOUDTAK_ADMIN_USERNAME}, CLOUDTAK_ADMIN_PASSWORD=${!!process.env.CLOUDTAK_ADMIN_PASSWORD}`); // Debug all CLOUDTAK environment variables const cloudtakEnvs = Object.keys(process.env).filter(key => key.startsWith('CLOUDTAK_')).sort(); console.error(`ok - All CLOUDTAK env vars: ${cloudtakEnvs.join(', ')}`); - if (process.env.CLOUDTAK_Server_auth_p12) { - console.error(`ok - P12 content length: ${process.env.CLOUDTAK_Server_auth_p12.length}`); - console.error(`ok - P12 starts with: ${process.env.CLOUDTAK_Server_auth_p12.substring(0, 50)}...`); + if (process.env.CLOUDTAK_Server_auth_p12_secret_arn) { + console.error(`ok - P12 secret ARN: ${process.env.CLOUDTAK_Server_auth_p12_secret_arn}`); } else { - console.error('ok - CLOUDTAK_Server_auth_p12 is undefined/empty'); + console.error('ok - CLOUDTAK_Server_auth_p12_secret_arn is undefined/empty'); } if (process.env.CLOUDTAK_Server_auth_cert) {