diff --git a/cmd/core-runtime/main.go b/cmd/core-runtime/main.go index 4b0921e..be1a367 100644 --- a/cmd/core-runtime/main.go +++ b/cmd/core-runtime/main.go @@ -37,6 +37,14 @@ import ( "time" ) +const ( + creatorPasswordPath = "/run/secrets/creator_password" + creatorBootstrapTokenPath = "/run/secrets/creator_bootstrap_token" + creatorSessionKeyPath = "/run/secrets/creator_session_signing_key" + epistemicsDBPasswordPath = "/run/secrets/epistemics_db_password" + interactionDBPasswordPath = "/run/secrets/interaction_db_password" +) + const maxCreatorCredentialsBytes = 4 << 10 func main() { @@ -44,15 +52,15 @@ func main() { if addr == "" { addr = ":8080" } - bootstrapToken, err := readRequiredSecret("CREATOR_BOOTSTRAP_TOKEN") + bootstrapToken, err := readRequiredSecretFile("CREATOR_BOOTSTRAP_TOKEN", creatorBootstrapTokenPath) if err != nil { log.Fatal(err) } - sessionKeyValue, err := readRequiredSecret("CREATOR_SESSION_SIGNING_KEY") + sessionKeyValue, err := readRequiredSecretFile("CREATOR_SESSION_SIGNING_KEY", creatorSessionKeyPath) if err != nil { log.Fatal(err) } - creatorPassword, err := readRequiredSecret("CREATOR_PASSWORD") + creatorPassword, err := readRequiredSecretFile("CREATOR_PASSWORD", creatorPasswordPath) if err != nil { log.Fatal(err) } @@ -73,7 +81,7 @@ func main() { log.Fatal(err) } defer capabilityRepository.Close() - epistemicsPassword, err := readCredentialRefSecret("EPISTEMICS_POSTGRES_PASSWORD", "EPISTEMICS_POSTGRES_CREDENTIAL_REF") + epistemicsPassword, err := readCredentialRefSecret("EPISTEMICS_POSTGRES_PASSWORD", "EPISTEMICS_POSTGRES_CREDENTIAL_REF", epistemicsDBPasswordPath) if err != nil { log.Fatal(err) } @@ -91,7 +99,7 @@ func main() { log.Fatal(err) } defer identityRepository.Close() - interactionPassword, err := readCredentialRefSecret("INTERACTION_POSTGRES_PASSWORD", "INTERACTION_POSTGRES_CREDENTIAL_REF") + interactionPassword, err := readCredentialRefSecret("INTERACTION_POSTGRES_PASSWORD", "INTERACTION_POSTGRES_CREDENTIAL_REF", interactionDBPasswordPath) if err != nil { log.Fatal(err) } @@ -1726,10 +1734,7 @@ func readRequiredSecret(name string) (string, error) { if err != nil { return "", fmt.Errorf("read %s_FILE: %w", name, err) } - direct = strings.TrimRight(string(contents), "\r\n") - if strings.ContainsAny(direct, "\r\n") { - return "", fmt.Errorf("%s_FILE must contain one line", name) - } + return normalizeSecretFile(name, contents) } if direct == "" { return "", fmt.Errorf("%s is required", name) @@ -1737,13 +1742,28 @@ func readRequiredSecret(name string) (string, error) { return direct, nil } -func readCredentialRefSecret(name, refName string) (string, error) { - if _, directSet := os.LookupEnv(name); directSet { - return "", fmt.Errorf("%s direct environment secret is forbidden; use %s_FILE", name, name) +func readRequiredSecretFile(name, path string) (string, error) { + contents, err := os.ReadFile(path) + if err != nil { + return "", fmt.Errorf("read %s secret file: %w", name, err) } - fileName := os.Getenv(name + "_FILE") - if fileName == "" { - return "", fmt.Errorf("%s_FILE is required", name) + return normalizeSecretFile(name, contents) +} + +func normalizeSecretFile(name string, contents []byte) (string, error) { + value := strings.TrimRight(string(contents), "\r\n") + if strings.ContainsAny(value, "\r\n") { + return "", fmt.Errorf("%s secret file must contain one line", name) + } + if value == "" { + return "", fmt.Errorf("%s is required", name) + } + return value, nil +} + +func readCredentialRefSecret(name, refName, fileName string) (string, error) { + if _, directSet := os.LookupEnv(name); directSet { + return "", fmt.Errorf("%s direct environment secret is forbidden; use the standard secret file", name) } ref := os.Getenv(refName) if ref == "" { diff --git a/cmd/reflection-worker/main.go b/cmd/reflection-worker/main.go index fa75273..9901d1b 100644 --- a/cmd/reflection-worker/main.go +++ b/cmd/reflection-worker/main.go @@ -14,6 +14,8 @@ import ( platformsecrets "git.cin.su/m/Assistant/platform/secrets" ) +const epistemicsDBPasswordPath = "/run/secrets/epistemics_db_password" + func main() { workerID := flag.String("worker-id", "reflection-worker-1", "stable worker identity") lease := flag.Duration("lease-ttl", 5*time.Minute, "candidate claim lease") @@ -21,7 +23,7 @@ func main() { once := flag.Bool("once", false, "run one cycle and exit") flag.Parse() ctx := context.Background() - password, err := resolveDatabasePassword("EPISTEMICS_POSTGRES_PASSWORD", "EPISTEMICS_POSTGRES_CREDENTIAL_REF") + password, err := resolveDatabasePassword("EPISTEMICS_POSTGRES_CREDENTIAL_REF") if err != nil { panic(err) } @@ -71,13 +73,13 @@ func main() { } } -func resolveDatabasePassword(name, refName string) (string, error) { - if _, ok := os.LookupEnv(name); ok { - return "", fmt.Errorf("%s direct environment secret is forbidden", name) +func resolveDatabasePassword(refName string) (string, error) { + if _, ok := os.LookupEnv("EPISTEMICS_POSTGRES_PASSWORD"); ok { + return "", fmt.Errorf("EPISTEMICS_POSTGRES_PASSWORD direct environment secret is forbidden") } - path, ref := os.Getenv(name+"_FILE"), os.Getenv(refName) - if path == "" || ref == "" { - return "", fmt.Errorf("%s_FILE and %s are required", name, refName) + ref := os.Getenv(refName) + if ref == "" { + return "", fmt.Errorf("%s is required", refName) } root := os.Getenv("SECRET_BACKEND_ROOT") if root == "" { @@ -87,5 +89,5 @@ func resolveDatabasePassword(name, refName string) (string, error) { if err != nil { return "", err } - return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeDatabasePassword, Consumer: secretcontracts.RuntimeConsumerEpistemicsWorker, Path: path}) + return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeDatabasePassword, Consumer: secretcontracts.RuntimeConsumerEpistemicsWorker, Path: epistemicsDBPasswordPath}) } diff --git a/cmd/research-runtime/main.go b/cmd/research-runtime/main.go index 8c6a737..8891369 100644 --- a/cmd/research-runtime/main.go +++ b/cmd/research-runtime/main.go @@ -19,6 +19,8 @@ import ( "git.cin.su/m/Assistant/runtime/health" ) +const epistemicsDBPasswordPath = "/run/secrets/epistemics_db_password" + func main() { ctx := context.Background() password, err := resolveDatabasePassword() @@ -140,9 +142,9 @@ func resolveDatabasePassword() (string, error) { if _, ok := os.LookupEnv("EPISTEMICS_POSTGRES_PASSWORD"); ok { return "", fmt.Errorf("EPISTEMICS_POSTGRES_PASSWORD direct environment secret is forbidden") } - path, ref := os.Getenv("EPISTEMICS_POSTGRES_PASSWORD_FILE"), os.Getenv("EPISTEMICS_POSTGRES_CREDENTIAL_REF") - if path == "" || ref == "" { - return "", fmt.Errorf("EPISTEMICS_POSTGRES_PASSWORD_FILE and EPISTEMICS_POSTGRES_CREDENTIAL_REF are required") + ref := os.Getenv("EPISTEMICS_POSTGRES_CREDENTIAL_REF") + if ref == "" { + return "", fmt.Errorf("EPISTEMICS_POSTGRES_CREDENTIAL_REF is required") } root := os.Getenv("SECRET_BACKEND_ROOT") if root == "" { @@ -152,5 +154,5 @@ func resolveDatabasePassword() (string, error) { if err != nil { return "", err } - return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeDatabasePassword, Consumer: secretcontracts.RuntimeConsumerEpistemicsWorker, Path: path}) + return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeDatabasePassword, Consumer: secretcontracts.RuntimeConsumerEpistemicsWorker, Path: epistemicsDBPasswordPath}) } diff --git a/deploy/docker-compose.prod.yml b/deploy/docker-compose.prod.yml index 5712366..3ee5217 100644 --- a/deploy/docker-compose.prod.yml +++ b/deploy/docker-compose.prod.yml @@ -65,20 +65,15 @@ services: POSTGRES_PASSWORD: ${SITUATION_DB_PASSWORD} IDENTITY_POSTGRES_USER: identity_role IDENTITY_POSTGRES_PASSWORD: ${IDENTITY_DB_PASSWORD} - CREATOR_BOOTSTRAP_TOKEN_FILE: /run/secrets/creator_bootstrap_token - CREATOR_SESSION_SIGNING_KEY_FILE: /run/secrets/creator_session_signing_key CREATOR_USERNAME: ${CREATOR_USERNAME} - CREATOR_PASSWORD_FILE: /run/secrets/creator_password WORK_POSTGRES_USER: work_role WORK_POSTGRES_PASSWORD: ${WORK_DB_PASSWORD} CAPABILITY_POSTGRES_USER: capability_role CAPABILITY_POSTGRES_PASSWORD: ${CAPABILITY_DB_PASSWORD} EPISTEMICS_POSTGRES_USER: epistemics_role EPISTEMICS_POSTGRES_CREDENTIAL_REF: credential://domovoy/epistemics/db-password/v1 - EPISTEMICS_POSTGRES_PASSWORD_FILE: /run/secrets/epistemics_db_password INTERACTION_POSTGRES_USER: interaction_role INTERACTION_POSTGRES_CREDENTIAL_REF: credential://domovoy/interaction/db-password/v1 - INTERACTION_POSTGRES_PASSWORD_FILE: /run/secrets/interaction_db_password SECRET_BACKEND_ROOT: /run/secrets COGNITIVE_ADMISSION_POLICY_ID: creator-advisory-local-v1 COGNITIVE_ADMISSION_MAX_CONCURRENT: ${COGNITIVE_ADMISSION_MAX_CONCURRENT:-1} @@ -182,7 +177,6 @@ services: POSTGRES_DATABASE: ${POSTGRES_DB:-assistant} EPISTEMICS_POSTGRES_USER: epistemics_role EPISTEMICS_POSTGRES_CREDENTIAL_REF: credential://domovoy/epistemics/db-password/v1 - EPISTEMICS_POSTGRES_PASSWORD_FILE: /run/secrets/epistemics_db_password SECRET_BACKEND_ROOT: /run/secrets depends_on: { postgres: { condition: service_healthy } } volumes: @@ -202,7 +196,6 @@ services: POSTGRES_DATABASE: ${POSTGRES_DB:-assistant} EPISTEMICS_POSTGRES_USER: epistemics_role EPISTEMICS_POSTGRES_CREDENTIAL_REF: credential://domovoy/epistemics/db-password/v1 - EPISTEMICS_POSTGRES_PASSWORD_FILE: /run/secrets/epistemics_db_password SECRET_BACKEND_ROOT: /run/secrets depends_on: { postgres: { condition: service_healthy } } volumes: diff --git a/tests/cognitive_advisory/verify-remote.ps1 b/tests/cognitive_advisory/verify-remote.ps1 index 59da669..536b123 100644 --- a/tests/cognitive_advisory/verify-remote.ps1 +++ b/tests/cognitive_advisory/verify-remote.ps1 @@ -40,14 +40,14 @@ $page = Invoke-WebRequest -Uri "$BaseURL/" -Method Get if ($page.StatusCode -ne 200 -or $page.Content -notmatch 'Спросить Домового') { throw 'Advisory console is not exposed' } $coreEnvironment = Get-ContainerEnvironment $CoreContainer -$required = @('CREATOR_USERNAME', 'CREATOR_PASSWORD_FILE', 'POSTGRES_DATABASE', 'POSTGRES_USER', 'POSTGRES_PASSWORD') +$required = @('CREATOR_USERNAME', 'POSTGRES_DATABASE', 'POSTGRES_USER', 'POSTGRES_PASSWORD') foreach ($name in $required) { if (-not $coreEnvironment.ContainsKey($name) -or [string]::IsNullOrWhiteSpace($coreEnvironment[$name])) { throw "Core runtime is missing $name" } } -$creatorPassword = ((& docker.exe --context $DockerContext exec $CoreContainer sh -c "cat '$($coreEnvironment['CREATOR_PASSWORD_FILE'])'") -join "`n").Trim() +$creatorPassword = ((& docker.exe --context $DockerContext exec $CoreContainer sh -c "cat '/run/secrets/creator_password'") -join "`n").Trim() if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($creatorPassword)) { throw 'Creator password secret is unavailable' } $sessionRequest = @{username = $coreEnvironment['CREATOR_USERNAME']; password = $creatorPassword} | ConvertTo-Json -Compress $issued = Invoke-RestMethod -Uri "$BaseURL/api/session" -Method Post -ContentType 'application/json' -Body $sessionRequest diff --git a/tests/creator_commit/verify-remote.ps1 b/tests/creator_commit/verify-remote.ps1 index 467197a..578da81 100644 --- a/tests/creator_commit/verify-remote.ps1 +++ b/tests/creator_commit/verify-remote.ps1 @@ -62,7 +62,7 @@ function Expect-CreatorStatus([string]$url, [object]$body, [string]$token, [int] } } -$sessionResponse = docker --context $DockerContext exec $coreContainer sh -c 'token=$CREATOR_BOOTSTRAP_TOKEN; if test x$CREATOR_BOOTSTRAP_TOKEN_FILE != x; then token=$(cat $CREATOR_BOOTSTRAP_TOKEN_FILE); fi; if test x$token = x; then exit 7; fi; header=Authorization:\ Bearer\ $token; IFS=; wget -qO- --header=$header --post-data={} http://localhost:8080/v1/creator/sessions' +$sessionResponse = docker --context $DockerContext exec $coreContainer sh -c 'token=$(cat /run/secrets/creator_bootstrap_token); if test x$token = x; then exit 7; fi; header=Authorization:\ Bearer\ $token; IFS=; wget -qO- --header=$header --post-data={} http://localhost:8080/v1/creator/sessions' if ($LASTEXITCODE -ne 0) { throw "Creator session issuance failed" } $creatorSession = ($sessionResponse | ConvertFrom-Json).Token