diff --git a/config/service-clients.example.yaml b/config/service-clients.example.yaml index 2d96530..9d74948 100644 --- a/config/service-clients.example.yaml +++ b/config/service-clients.example.yaml @@ -117,3 +117,19 @@ clients: roles: - secrets-engine-requester tokenLifetime: 15m + + # Compact sitting presenter; actor is informed-decision, not secrets-engine. + - clientId: informed-decision-sitting-requester + displayName: Informed Decision compact-sitting approval requester + audience: approval-engine + allowedScopes: + - approval:create + grantTypes: + - client_credentials + clientType: confidential + secretRef: env:KEYCAPE_INFORMED_DECISION_SITTING_REQUESTER_CLIENT_SECRET + serviceSubject: informed-decision + tenant: tenant:platform + roles: + - informed-decision-sitting-requester + tokenLifetime: 15m diff --git a/docs/sitting-requester-registration.json b/docs/sitting-requester-registration.json new file mode 100644 index 0000000..ce5f533 --- /dev/null +++ b/docs/sitting-requester-registration.json @@ -0,0 +1,19 @@ +{ + "clientId": "informed-decision-sitting-requester", + "displayName": "Informed Decision compact-sitting approval requester", + "audience": "approval-engine", + "allowedScopes": [ + "approval:create" + ], + "grantTypes": [ + "client_credentials" + ], + "clientType": "confidential", + "secretRef": "env:KEYCAPE_INFORMED_DECISION_SITTING_REQUESTER_CLIENT_SECRET", + "serviceSubject": "informed-decision", + "tenant": "tenant:platform", + "roles": [ + "informed-decision-sitting-requester" + ], + "tokenLifetime": "15m" +} diff --git a/src/cmd/keycape/clients_test.go b/src/cmd/keycape/clients_test.go index 1012d36..4c7f143 100644 --- a/src/cmd/keycape/clients_test.go +++ b/src/cmd/keycape/clients_test.go @@ -64,6 +64,8 @@ func TestServiceRegistrationTenantsAreExactPerDecision(t *testing.T) { // new client carrying a tenant cannot arrive unnoticed, and it did its // job when the approver registration first landed (KEY-WP-0013-T05). "informed-decision-approver": "tenant:platform", + "secrets-engine-requester": "tenant:platform", + "informed-decision-sitting-requester": "tenant:platform", } seen := map[string]bool{} for _, c := range cfg.Clients { @@ -156,6 +158,45 @@ func TestApproverRegistrationShapeIsExact(t *testing.T) { } } +func TestSittingRequesterRegistrationShapeIsExact(t *testing.T) { + cfg, err := config.Load("../../../config/service-clients.example.yaml") + if err != nil { + t.Fatal(err) + } + var client *config.ClientConfig + for i := range cfg.Clients { + if cfg.Clients[i].ClientID == "informed-decision-sitting-requester" { + client = &cfg.Clients[i] + } + } + if client == nil { + t.Fatal("the sitting-requester registration is absent") + } + if client.ClientType != "confidential" || len(client.GrantTypes) != 1 || client.GrantTypes[0] != "client_credentials" { + t.Errorf("client type %q grants %v; want confidential client_credentials", client.ClientType, client.GrantTypes) + } + if client.SecretRef != "env:KEYCAPE_INFORMED_DECISION_SITTING_REQUESTER_CLIENT_SECRET" { + t.Errorf("secretRef = %q", client.SecretRef) + } + if client.ServiceSubject != "informed-decision" { + t.Errorf("serviceSubject = %q, want informed-decision", client.ServiceSubject) + } + if client.Tenant != "tenant:platform" || client.Audience != "approval-engine" { + t.Errorf("tenant/audience = %q %q", client.Tenant, client.Audience) + } + if len(client.AllowedScopes) != 1 || client.AllowedScopes[0] != "approval:create" { + t.Fatalf("scopes = %v; want only approval:create", client.AllowedScopes) + } + for _, scope := range client.AllowedScopes { + if scope == "approval:consume" || scope == "approval:approve" || scope == "approval:read" { + t.Fatalf("excess sitting-requester scope %q", scope) + } + } + if len(client.RedirectURIs) != 0 { + t.Errorf("redirect URIs = %v; sitting-requester is not a browser client", client.RedirectURIs) + } +} + // writeTestKeyPEM writes a placeholder key file; ValidateConfig checks the path // exists, not the key material. func writeTestKeyPEM(t *testing.T) string {