@@ -5,33 +5,31 @@ import { existsSync, readFileSync } from 'node:fs';
55import { dirname , join , resolve } from 'node:path' ;
66import { fileURLToPath , pathToFileURL } from 'node:url' ;
77
8+ import {
9+ APP_HEALTHCHECK ,
10+ CADDY_HEALTHCHECK ,
11+ COMPOSE_PATH ,
12+ DEFAULT_RENDER_ENVIRONMENT ,
13+ DIGEST_PATTERN ,
14+ EXPECTED_DEPENDENCIES ,
15+ EXPECTED_TMPFS ,
16+ EXPECTED_USERS ,
17+ LONG_RUNNING_SERVICES ,
18+ POSTGRES_HEALTHCHECK ,
19+ PROFILES ,
20+ PROFILE_SERVICES ,
21+ compareText ,
22+ isObject ,
23+ isPositive ,
24+ isPositiveDuration ,
25+ resultFor ,
26+ sameSequence ,
27+ sameValues ,
28+ } from './verify-hosted-container-hardening-contracts.mjs' ;
829import { verifyHostedNoTerminalDockerfile } from './verify-hosted-no-terminal-artifact.mjs' ;
930
10- const RESULT_FORMAT = 'hosted-container-hardening-verifier-result/v2' ;
1131const SCRIPT_DIRECTORY = dirname ( fileURLToPath ( import . meta. url ) ) ;
1232const REPOSITORY_ROOT = resolve ( SCRIPT_DIRECTORY , '../..' ) ;
13- const COMPOSE_PATH = 'docker/docker-compose.yml' ;
14- const PROFILES = Object . freeze ( [ 'personal' , 'keycloak' ] ) ;
15- const DIGEST_PATTERN = / ^ s h a 2 5 6 : [ a - f 0 - 9 ] { 64 } $ / iu;
16- const POSITIVE_DURATION_PATTERN = / ^ [ 1 - 9 ] \d * (?: m s | s | m | h ) (?: \d + (?: m s | s | m | h ) ) * $ / u;
17-
18- // prettier-ignore
19- const PROFILE_SERVICES = Object . freeze ( { personal : Object . freeze ( [ 'agent-teams-personal' , 'caddy-personal' ] ) , keycloak : Object . freeze ( [ 'agent-teams-keycloak' , 'agent-teams-keycloak-secret-init' , 'caddy' , 'keycloak' , 'keycloak-postgres' , 'keycloak-volume-init' ] ) } ) ;
20- // prettier-ignore
21- const LONG_RUNNING_SERVICES = new Set ( [ 'agent-teams-personal' , 'agent-teams-keycloak' , 'caddy' , 'caddy-personal' , 'keycloak' , 'keycloak-postgres' ] ) ;
22- // prettier-ignore
23- const EXPECTED_USERS = Object . freeze ( { 'agent-teams-personal' : '1000:1000' , 'agent-teams-keycloak' : '1000:1000' , 'agent-teams-keycloak-secret-init' : '1000:1000' , caddy : '1000:1000' , 'caddy-personal' : '1000:1000' , keycloak : '1000:0' , 'keycloak-postgres' : '70:70' , 'keycloak-volume-init' : '1000:1000' } ) ;
24- // prettier-ignore
25- const EXPECTED_DEPENDENCIES = Object . freeze ( { 'agent-teams-personal' : Object . freeze ( { 'caddy-personal' : 'service_healthy' } ) , 'agent-teams-keycloak' : Object . freeze ( { 'agent-teams-keycloak-secret-init' : 'service_completed_successfully' , caddy : 'service_healthy' , keycloak : 'service_healthy' , 'keycloak-volume-init' : 'service_completed_successfully' } ) , keycloak : Object . freeze ( { caddy : 'service_healthy' , 'keycloak-postgres' : 'service_healthy' , 'keycloak-volume-init' : 'service_completed_successfully' } ) , 'keycloak-volume-init' : Object . freeze ( { caddy : 'service_healthy' } ) } ) ;
26- // prettier-ignore
27- const APP_HEALTHCHECK = Object . freeze ( [ 'CMD' , 'node' , '-e' , "fetch('http://127.0.0.1:3456/api/auth/status').then(r=>{if(!r.ok)process.exit(1)})" ] ) ;
28- // prettier-ignore
29- const CADDY_HEALTHCHECK = Object . freeze ( [ 'CMD-SHELL' , 'test -s /data/caddy/pki/authorities/local/root.crt && wget -q --spider http://127.0.0.1:2019/config/' ] ) ;
30- const POSTGRES_HEALTHCHECK = Object . freeze ( [ 'CMD-SHELL' , 'pg_isready -U keycloak -d keycloak' ] ) ;
31- // prettier-ignore
32- const EXPECTED_TMPFS = Object . freeze ( { 'agent-teams-personal' : Object . freeze ( [ '/run/agent-teams:mode=0700,uid=1000,gid=1000' , '/tmp:mode=1777' ] ) , 'agent-teams-keycloak' : Object . freeze ( [ '/run/agent-teams:mode=0700,uid=1000,gid=1000' , '/tmp:mode=1777' ] ) , caddy : Object . freeze ( [ '/tmp:mode=1777' ] ) , 'caddy-personal' : Object . freeze ( [ '/tmp:mode=1777' ] ) , keycloak : Object . freeze ( [ '/opt/keycloak/data/import:mode=0700,uid=1000,gid=0' , '/opt/keycloak/data/tmp:mode=0700,uid=1000,gid=0' , '/run/keycloak:mode=0700,uid=1000,gid=0' , '/tmp:mode=1777' ] ) , 'keycloak-postgres' : Object . freeze ( [ '/tmp:mode=1777' , '/var/run/postgresql:mode=0775,uid=70,gid=70' ] ) , 'keycloak-volume-init' : Object . freeze ( [ ] ) , 'agent-teams-keycloak-secret-init' : Object . freeze ( [ ] ) } ) ;
33- // prettier-ignore
34- const DEFAULT_RENDER_ENVIRONMENT = Object . freeze ( { CLAUDE_DIR : '/tmp/agent-teams-hosted-config-claude' , HOSTED_SECRETS_DIR : '/tmp/agent-teams-hosted-config-secrets' , NODE_IMAGE_DIGEST : `sha256:${ 'a' . repeat ( 64 ) } ` , KEYCLOAK_IMAGE_DIGEST : `sha256:${ 'b' . repeat ( 64 ) } ` , POSTGRES_IMAGE_DIGEST : `sha256:${ 'c' . repeat ( 64 ) } ` , CADDY_IMAGE_DIGEST : `sha256:${ 'd' . repeat ( 64 ) } ` } ) ;
3533
3634/**
3735 * Verifies the effective Docker Compose model, not indentation or YAML spelling.
@@ -751,49 +749,6 @@ function verifyExactNames(actual, expected, prefix, violations) {
751749 if ( ! sameValues ( actual , expected ) ) violations . push ( `${ prefix } _inventory_invalid` ) ;
752750}
753751
754- function isObject ( value ) {
755- return value !== null && typeof value === 'object' && ! Array . isArray ( value ) ;
756- }
757-
758- function sameValues ( actual , expected ) {
759- if ( ! Array . isArray ( actual ) ) return expected . length === 0 ;
760- return (
761- JSON . stringify ( [ ...actual ] . sort ( compareText ) ) ===
762- JSON . stringify ( [ ...expected ] . sort ( compareText ) )
763- ) ;
764- }
765-
766- function sameSequence ( actual , expected ) {
767- return Array . isArray ( actual ) && JSON . stringify ( actual ) === JSON . stringify ( expected ) ;
768- }
769-
770- function isPositive ( value ) {
771- const number = typeof value === 'number' ? value : Number ( value ) ;
772- return Number . isFinite ( number ) && number > 0 ;
773- }
774-
775- function isPositiveDuration ( value ) {
776- return typeof value === 'string' && POSITIVE_DURATION_PATTERN . test ( value ) ;
777- }
778-
779- function resultFor ( checkedServices , checkedProfiles , violations ) {
780- const uniqueViolations = [ ...new Set ( violations ) ] . sort ( compareText ) ;
781- return {
782- format : RESULT_FORMAT ,
783- status : uniqueViolations . length === 0 ? 'passed' : 'failed' ,
784- summary : {
785- checkedProfiles,
786- checkedServices,
787- violations : uniqueViolations . length ,
788- } ,
789- violations : uniqueViolations ,
790- } ;
791- }
792-
793- function compareText ( left , right ) {
794- return String ( left ) . localeCompare ( String ( right ) ) ;
795- }
796-
797752function parseArguments ( argv ) {
798753 const options = { } ;
799754 for ( let index = 0 ; index < argv . length ; index += 1 ) {
0 commit comments