This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -22,7 +23,7 @@ import (
|
||||
const (
|
||||
delegatedControlPlaneActor identity.ActorID = "domovoy-control-plane-actor"
|
||||
delegatedControlPlanePrincipal identity.PrincipalID = "domovoy-control-plane-principal"
|
||||
delegatedControlPlaneTokenPath = "/run/secrets/control_plane_service_token"
|
||||
delegatedControlPlaneTokenFile = "control_plane_service_token"
|
||||
)
|
||||
|
||||
type delegatedControlPlaneAuthoritySource interface {
|
||||
@@ -157,13 +158,13 @@ func delegatedControlPlaneCredential() (string, error) {
|
||||
if ref == "" {
|
||||
return "", errors.New("control-plane service credential ref is required")
|
||||
}
|
||||
root := os.Getenv("SECRET_BACKEND_ROOT")
|
||||
root := os.Getenv("CONTROL_PLANE_SERVICE_SECRET_ROOT")
|
||||
if root == "" {
|
||||
root = "/run/secrets"
|
||||
root = "/run/control-plane-secrets"
|
||||
}
|
||||
resolver, err := platformsecrets.NewFileResolver(root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeServiceAuthentication, Consumer: secretcontracts.RuntimeConsumerCommit, Path: delegatedControlPlaneTokenPath})
|
||||
return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeServiceAuthentication, Consumer: secretcontracts.RuntimeConsumerCommit, Path: filepath.Join(root, delegatedControlPlaneTokenFile)})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -20,6 +22,33 @@ type delegatedAuthorityFake struct {
|
||||
revoked bool
|
||||
}
|
||||
|
||||
func TestDelegatedAuthorityCredentialRotationRejectsOldToken(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
tokenPath := filepath.Join(root, "control_plane_service_token")
|
||||
if err := os.WriteFile(tokenPath, []byte("old-token\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("CONTROL_PLANE_SERVICE_CREDENTIAL_REF", "credential://domovoy/control-plane/service-token/v1")
|
||||
t.Setenv("CONTROL_PLANE_SERVICE_SECRET_ROOT", root)
|
||||
h := delegatedControlPlaneHandler{credential: delegatedControlPlaneCredential}
|
||||
oldRequest := httptest.NewRequest(http.MethodPost, "/v1/internal/control-plane/authorize", nil)
|
||||
oldRequest.Header.Set("Authorization", "Bearer old-token")
|
||||
if !h.serviceAuthenticated(oldRequest) {
|
||||
t.Fatal("active credential was rejected before rotation")
|
||||
}
|
||||
if err := os.WriteFile(tokenPath, []byte("new-token\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if h.serviceAuthenticated(oldRequest) {
|
||||
t.Fatal("old credential remained valid after rotation")
|
||||
}
|
||||
newRequest := httptest.NewRequest(http.MethodPost, "/v1/internal/control-plane/authorize", nil)
|
||||
newRequest.Header.Set("Authorization", "Bearer new-token")
|
||||
if !h.serviceAuthenticated(newRequest) {
|
||||
t.Fatal("new credential was rejected after rotation")
|
||||
}
|
||||
}
|
||||
|
||||
func (f *delegatedAuthorityFake) LoadDelegatedControlPlaneGrant(context.Context, authority.GrantID) (authority.DelegatedControlPlaneGrant, error) {
|
||||
if f.grant.ID == "" {
|
||||
return authority.DelegatedControlPlaneGrant{}, errors.New("missing")
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/authority"
|
||||
@@ -23,3 +25,15 @@ func TestControlPlaneClientSendsOnlyServiceCredentialToAuthorityRuntime(t *testi
|
||||
t.Fatalf("authorize: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestControlPlaneCredentialUsesDedicatedSecretRoot(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(root, controlPlaneServiceTokenFile), []byte("test-machine-token\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("CONTROL_PLANE_SERVICE_CREDENTIAL_REF", "credential://domovoy/control-plane/service-token/v1")
|
||||
t.Setenv("CONTROL_PLANE_SERVICE_SECRET_ROOT", root)
|
||||
if got, err := readControlPlaneServiceCredential(); err != nil || got != "test-machine-token" {
|
||||
t.Fatalf("control-plane credential resolution failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -45,7 +46,7 @@ const (
|
||||
epistemicsDBPasswordPath = "/run/secrets/epistemics_db_password"
|
||||
interactionDBPasswordPath = "/run/secrets/interaction_db_password"
|
||||
selfDBPasswordPath = "/run/secrets/self_db_password"
|
||||
controlPlaneServiceTokenPath = "/run/secrets/control_plane_service_token"
|
||||
controlPlaneServiceTokenFile = "control_plane_service_token"
|
||||
)
|
||||
|
||||
const maxCreatorCredentialsBytes = 4 << 10
|
||||
@@ -141,7 +142,7 @@ func main() {
|
||||
var delegatedAuthority *controlPlaneAuthority
|
||||
if os.Getenv("CONTROL_PLANE_SERVICE_CREDENTIAL_REF") != "" {
|
||||
delegatedAuthority = &controlPlaneAuthority{baseURL: envOr("COMMIT_RUNTIME_URL", "http://commit-runtime:8082"), client: &http.Client{Timeout: 5 * time.Second}, credential: func() (string, error) {
|
||||
return readCredentialRefSecretPurpose("CONTROL_PLANE_SERVICE_TOKEN", "CONTROL_PLANE_SERVICE_CREDENTIAL_REF", controlPlaneServiceTokenPath, secretcontracts.SecretPurposeServiceAuthentication)
|
||||
return readControlPlaneServiceCredential()
|
||||
}}
|
||||
} else {
|
||||
log.Printf("delegated control-plane authority is not configured; existing human control remains available")
|
||||
@@ -2088,6 +2089,19 @@ func readCredentialRefSecret(name, refName, fileName string) (string, error) {
|
||||
return readCredentialRefSecretPurpose(name, refName, fileName, secretcontracts.SecretPurposeDatabasePassword)
|
||||
}
|
||||
|
||||
func readControlPlaneServiceCredential() (string, error) {
|
||||
ref := os.Getenv("CONTROL_PLANE_SERVICE_CREDENTIAL_REF")
|
||||
if ref == "" {
|
||||
return "", errors.New("CONTROL_PLANE_SERVICE_CREDENTIAL_REF is required")
|
||||
}
|
||||
root := envOr("CONTROL_PLANE_SERVICE_SECRET_ROOT", "/run/control-plane-secrets")
|
||||
resolver, err := platformsecrets.NewFileResolver(root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeServiceAuthentication, Consumer: secretcontracts.RuntimeConsumerCore, Path: filepath.Join(root, controlPlaneServiceTokenFile)})
|
||||
}
|
||||
|
||||
func readCredentialRefSecretPurpose(name, refName, fileName string, purpose secretcontracts.SecretPurpose) (string, error) {
|
||||
if _, directSet := os.LookupEnv(name); directSet {
|
||||
return "", fmt.Errorf("%s direct environment secret is forbidden; use the standard secret file", name)
|
||||
|
||||
@@ -23,7 +23,6 @@ services:
|
||||
SECRET_BACKEND_ROOT: /run/secrets
|
||||
volumes:
|
||||
- assistant_core_secrets:/run/secrets:ro
|
||||
- assistant_control_plane_service_secrets:/run/control-plane-secrets:ro
|
||||
- assistant_research_sources:/var/lib/research-sources:ro
|
||||
networks:
|
||||
shared:
|
||||
@@ -55,6 +54,7 @@ services:
|
||||
CAPABILITY_POSTGRES_USER: capability_role
|
||||
CAPABILITY_POSTGRES_PASSWORD: ${CAPABILITY_DB_PASSWORD}
|
||||
CONTROL_PLANE_SERVICE_CREDENTIAL_REF: credential://domovoy/control-plane/service-token/v1
|
||||
CONTROL_PLANE_SERVICE_SECRET_ROOT: /run/control-plane-secrets
|
||||
SELF_POSTGRES_USER: self_role
|
||||
SELF_POSTGRES_CREDENTIAL_REF: credential://domovoy/self/db-password/v1
|
||||
EPISTEMICS_POSTGRES_USER: epistemics_role
|
||||
@@ -71,6 +71,7 @@ services:
|
||||
RESEARCH_RUNTIME_URL: http://research-runtime-candidate:8087
|
||||
volumes:
|
||||
- assistant_core_secrets:/run/secrets:ro
|
||||
- assistant_control_plane_service_secrets:/run/control-plane-secrets:ro
|
||||
networks:
|
||||
shared:
|
||||
aliases: [core-runtime-candidate]
|
||||
|
||||
Reference in New Issue
Block a user