From 63fa3504a76c693ec4c1e729a6f0bee2e177eda4 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:34:06 +0000 Subject: [PATCH 1/5] Add PUT /instances/{id}/credentials endpoint for secret rotation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an API endpoint to replace credential brokering policies on an existing instance. Since real secrets live host-side and the egress proxy rewrites headers at request time, updating credentials requires no VM changes — hypeman updates stored metadata and the proxy policy. Key changes: - openapi.yaml: UpdateCredentialsRequest schema + PUT endpoint - lib/egressproxy: UpdateInstancePolicy method for atomic rule replacement - lib/instances: UpdateCredentials on Manager interface + implementation - cmd/api/api: UpdateInstanceCredentials handler - Tests: 6 egress proxy tests + 7 instance domain tests Co-Authored-By: Claude Opus 4.6 --- cmd/api/api/instances.go | 77 +++ lib/egressproxy/service.go | 21 + lib/egressproxy/types.go | 3 +- lib/egressproxy/update_policy_test.go | 175 ++++++ lib/instances/manager.go | 9 + lib/instances/types.go | 7 + lib/instances/update_credentials.go | 87 +++ lib/instances/update_credentials_test.go | 231 +++++++ lib/oapi/oapi.go | 739 ++++++++++++++++------- openapi.yaml | 82 +++ 10 files changed, 1219 insertions(+), 212 deletions(-) create mode 100644 lib/egressproxy/update_policy_test.go create mode 100644 lib/instances/update_credentials.go create mode 100644 lib/instances/update_credentials_test.go diff --git a/cmd/api/api/instances.go b/cmd/api/api/instances.go index ba5b448a..1c3b4585 100644 --- a/cmd/api/api/instances.go +++ b/cmd/api/api/instances.go @@ -798,6 +798,83 @@ func (s *ApiService) StatInstancePath(ctx context.Context, request oapi.StatInst return response, nil } +// UpdateInstanceCredentials replaces credential brokering policies for an instance +func (s *ApiService) UpdateInstanceCredentials(ctx context.Context, request oapi.UpdateInstanceCredentialsRequestObject) (oapi.UpdateInstanceCredentialsResponseObject, error) { + inst := mw.GetResolvedInstance[instances.Instance](ctx) + if inst == nil { + return oapi.UpdateInstanceCredentials500JSONResponse{ + Code: "internal_error", + Message: "resource not resolved", + }, nil + } + log := logger.FromContext(ctx) + + if request.Body == nil { + return oapi.UpdateInstanceCredentials400JSONResponse{ + Code: "invalid_request", + Message: "request body is required", + }, nil + } + + // Convert OAPI credentials to domain type + var credentials map[string]instances.CredentialPolicy + if request.Body.Credentials != nil { + credentials = make(map[string]instances.CredentialPolicy, len(*request.Body.Credentials)) + for credentialName, credential := range *request.Body.Credentials { + policy := instances.CredentialPolicy{ + Source: instances.CredentialSource{ + Env: credential.Source.Env, + }, + Inject: make([]instances.CredentialInjectRule, 0, len(credential.Inject)), + } + for _, inject := range credential.Inject { + rule := instances.CredentialInjectRule{ + As: instances.CredentialInjectAs{ + Header: inject.As.Header, + Format: inject.As.Format, + }, + } + if inject.Hosts != nil { + rule.Hosts = append([]string(nil), (*inject.Hosts)...) + } + policy.Inject = append(policy.Inject, rule) + } + credentials[credentialName] = policy + } + } + + env := make(map[string]string) + if request.Body.Env != nil { + env = *request.Body.Env + } + + result, err := s.InstanceManager.UpdateCredentials(ctx, inst.Id, instances.UpdateCredentialsRequest{ + Credentials: credentials, + Env: env, + }) + if err != nil { + switch { + case errors.Is(err, instances.ErrNotFound): + return oapi.UpdateInstanceCredentials404JSONResponse{ + Code: "not_found", + Message: "instance not found", + }, nil + case errors.Is(err, instances.ErrInvalidRequest): + return oapi.UpdateInstanceCredentials400JSONResponse{ + Code: "invalid_request", + Message: err.Error(), + }, nil + default: + log.ErrorContext(ctx, "failed to update credentials", "error", err) + return oapi.UpdateInstanceCredentials500JSONResponse{ + Code: "internal_error", + Message: "failed to update credentials", + }, nil + } + } + return oapi.UpdateInstanceCredentials200JSONResponse(instanceToOAPI(*result)), nil +} + // AttachVolume attaches a volume to an instance (not yet implemented) func (s *ApiService) AttachVolume(ctx context.Context, request oapi.AttachVolumeRequestObject) (oapi.AttachVolumeResponseObject, error) { return oapi.AttachVolume500JSONResponse{ diff --git a/lib/egressproxy/service.go b/lib/egressproxy/service.go index 75cd2e7f..7fcddfa0 100644 --- a/lib/egressproxy/service.go +++ b/lib/egressproxy/service.go @@ -217,6 +217,27 @@ func compileHeaderInjectRules(cfgRules []HeaderInjectRuleConfig) ([]headerInject return out, nil } +// UpdateInstancePolicy atomically replaces the header inject rules for an +// already-registered instance without touching iptables enforcement rules. +// Returns ErrInstanceNotRegistered if the instance has no active policy. +func (s *Service) UpdateInstancePolicy(instanceID string, rules []HeaderInjectRuleConfig) error { + compiled, err := compileHeaderInjectRules(rules) + if err != nil { + return err + } + + s.mu.Lock() + defer s.mu.Unlock() + + sourceIP, ok := s.sourceIPByInstance[instanceID] + if !ok { + return ErrInstanceNotRegistered + } + + s.policiesBySourceIP[sourceIP] = sourcePolicy{headerInjectRules: compiled} + return nil +} + func (s *Service) UnregisterInstance(_ context.Context, instanceID string) { s.mu.Lock() sourceIP, ok := s.sourceIPByInstance[instanceID] diff --git a/lib/egressproxy/types.go b/lib/egressproxy/types.go index 29c2e838..41dd5472 100644 --- a/lib/egressproxy/types.go +++ b/lib/egressproxy/types.go @@ -7,7 +7,8 @@ const ( ) var ( - ErrGatewayMismatch = errors.New("egress proxy already initialized with different gateway") + ErrGatewayMismatch = errors.New("egress proxy already initialized with different gateway") + ErrInstanceNotRegistered = errors.New("instance not registered with egress proxy") ) // InstanceConfig defines per-instance proxy behavior. diff --git a/lib/egressproxy/update_policy_test.go b/lib/egressproxy/update_policy_test.go new file mode 100644 index 00000000..5810f480 --- /dev/null +++ b/lib/egressproxy/update_policy_test.go @@ -0,0 +1,175 @@ +package egressproxy + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUpdateInstancePolicy_ReplacesRules(t *testing.T) { + t.Parallel() + + svc := &Service{ + policiesBySourceIP: map[string]sourcePolicy{ + "10.0.0.2": { + headerInjectRules: []headerInjectRule{ + {headerName: "Authorization", headerValue: "Bearer old-key"}, + }, + }, + }, + sourceIPByInstance: map[string]string{ + "inst-1": "10.0.0.2", + }, + } + + err := svc.UpdateInstancePolicy("inst-1", []HeaderInjectRuleConfig{ + {HeaderName: "Authorization", HeaderValue: "Bearer new-key", AllowedDomains: []string{"api.openai.com"}}, + }) + require.NoError(t, err) + + // Verify the new rules are applied + hdr := http.Header{} + svc.applyHeaderInjections("10.0.0.2", "api.openai.com", hdr, true) + require.Equal(t, "Bearer new-key", hdr.Get("Authorization")) + + // Verify domain scoping works (should not inject for other domains) + hdr2 := http.Header{} + svc.applyHeaderInjections("10.0.0.2", "api.github.com", hdr2, true) + require.Empty(t, hdr2.Get("Authorization")) +} + +func TestUpdateInstancePolicy_ClearsRulesWithEmptySlice(t *testing.T) { + t.Parallel() + + svc := &Service{ + policiesBySourceIP: map[string]sourcePolicy{ + "10.0.0.2": { + headerInjectRules: []headerInjectRule{ + {headerName: "Authorization", headerValue: "Bearer old-key"}, + }, + }, + }, + sourceIPByInstance: map[string]string{ + "inst-1": "10.0.0.2", + }, + } + + err := svc.UpdateInstancePolicy("inst-1", []HeaderInjectRuleConfig{}) + require.NoError(t, err) + + hdr := http.Header{} + svc.applyHeaderInjections("10.0.0.2", "api.openai.com", hdr, true) + require.Empty(t, hdr.Get("Authorization")) +} + +func TestUpdateInstancePolicy_ErrorsForUnregisteredInstance(t *testing.T) { + t.Parallel() + + svc := &Service{ + policiesBySourceIP: map[string]sourcePolicy{}, + sourceIPByInstance: map[string]string{}, + } + + err := svc.UpdateInstancePolicy("nonexistent", []HeaderInjectRuleConfig{ + {HeaderName: "Authorization", HeaderValue: "Bearer key"}, + }) + require.ErrorIs(t, err, ErrInstanceNotRegistered) +} + +func TestUpdateInstancePolicy_IsIdempotent(t *testing.T) { + t.Parallel() + + svc := &Service{ + policiesBySourceIP: map[string]sourcePolicy{ + "10.0.0.2": {}, + }, + sourceIPByInstance: map[string]string{ + "inst-1": "10.0.0.2", + }, + } + + rules := []HeaderInjectRuleConfig{ + {HeaderName: "Authorization", HeaderValue: "Bearer same-key"}, + } + + // Call twice — should produce the same result + require.NoError(t, svc.UpdateInstancePolicy("inst-1", rules)) + require.NoError(t, svc.UpdateInstancePolicy("inst-1", rules)) + + hdr := http.Header{} + svc.applyHeaderInjections("10.0.0.2", "api.example.com", hdr, true) + require.Equal(t, "Bearer same-key", hdr.Get("Authorization")) +} + +func TestUpdateInstancePolicy_DoesNotAffectOtherInstances(t *testing.T) { + t.Parallel() + + matchers1, _ := compileDomainMatchers([]string{"api.openai.com"}) + svc := &Service{ + policiesBySourceIP: map[string]sourcePolicy{ + "10.0.0.2": { + headerInjectRules: []headerInjectRule{ + {headerName: "Authorization", headerValue: "Bearer inst1-key", domainMatchers: matchers1}, + }, + }, + "10.0.0.3": { + headerInjectRules: []headerInjectRule{ + {headerName: "Authorization", headerValue: "Bearer inst2-key"}, + }, + }, + }, + sourceIPByInstance: map[string]string{ + "inst-1": "10.0.0.2", + "inst-2": "10.0.0.3", + }, + } + + // Update inst-1 only + err := svc.UpdateInstancePolicy("inst-1", []HeaderInjectRuleConfig{ + {HeaderName: "Authorization", HeaderValue: "Bearer inst1-new-key"}, + }) + require.NoError(t, err) + + // inst-2 should be unaffected + hdr := http.Header{} + svc.applyHeaderInjections("10.0.0.3", "api.example.com", hdr, true) + require.Equal(t, "Bearer inst2-key", hdr.Get("Authorization")) +} + +func TestUpdateInstancePolicy_RegisterThenUpdate(t *testing.T) { + t.Parallel() + + // Simulate a service with a pre-registered instance (without starting a real listener) + matchers, err := compileDomainMatchers([]string{"api.openai.com"}) + require.NoError(t, err) + + svc := &Service{ + policiesBySourceIP: map[string]sourcePolicy{ + "10.0.0.2": { + headerInjectRules: []headerInjectRule{ + {headerName: "Authorization", headerValue: "Bearer original-key", domainMatchers: matchers}, + }, + }, + }, + sourceIPByInstance: map[string]string{ + "inst-1": "10.0.0.2", + }, + } + + // Verify original key + hdr := http.Header{} + svc.applyHeaderInjections("10.0.0.2", "api.openai.com", hdr, true) + require.Equal(t, "Bearer original-key", hdr.Get("Authorization")) + + // Update the policy with a rotated key + err = svc.UpdateInstancePolicy("inst-1", []HeaderInjectRuleConfig{ + {HeaderName: "Authorization", HeaderValue: "Bearer rotated-key", AllowedDomains: []string{"api.openai.com"}}, + }) + require.NoError(t, err) + + // Verify rotated key is used + hdr2 := http.Header{} + svc.applyHeaderInjections("10.0.0.2", "api.openai.com", hdr2, true) + require.Equal(t, "Bearer rotated-key", hdr2.Get("Authorization")) +} diff --git a/lib/instances/manager.go b/lib/instances/manager.go index 8305a0c5..f5203f46 100644 --- a/lib/instances/manager.go +++ b/lib/instances/manager.go @@ -41,6 +41,7 @@ type Manager interface { StartInstance(ctx context.Context, id string, req StartInstanceRequest) (*Instance, error) StreamInstanceLogs(ctx context.Context, id string, tail int, follow bool, source LogSource) (<-chan string, error) RotateLogs(ctx context.Context, maxBytes int64, maxFiles int) error + UpdateCredentials(ctx context.Context, id string, req UpdateCredentialsRequest) (*Instance, error) AttachVolume(ctx context.Context, id string, volumeId string, req AttachVolumeRequest) (*Instance, error) DetachVolume(ctx context.Context, id string, volumeId string) (*Instance, error) // ListInstanceAllocations returns resource allocations for all instances. @@ -441,6 +442,14 @@ func (m *manager) RotateLogs(ctx context.Context, maxBytes int64, maxFiles int) return lastErr } +// UpdateCredentials replaces the credential policies for an instance. +func (m *manager) UpdateCredentials(ctx context.Context, id string, req UpdateCredentialsRequest) (*Instance, error) { + lock := m.getInstanceLock(id) + lock.Lock() + defer lock.Unlock() + return m.updateCredentials(ctx, id, req) +} + // AttachVolume attaches a volume to an instance (not yet implemented) func (m *manager) AttachVolume(ctx context.Context, id string, volumeId string, req AttachVolumeRequest) (*Instance, error) { return nil, fmt.Errorf("attach volume not yet implemented") diff --git a/lib/instances/types.go b/lib/instances/types.go index 8dc48837..e16425d8 100644 --- a/lib/instances/types.go +++ b/lib/instances/types.go @@ -267,6 +267,13 @@ type ForkSnapshotRequest struct { TargetHypervisor hypervisor.Type // Optional, allowed only for Stopped snapshots } +// UpdateCredentialsRequest is the domain request for replacing instance credentials. +// This is a full replacement — the provided credentials map replaces the existing one entirely. +type UpdateCredentialsRequest struct { + Credentials map[string]CredentialPolicy // New credential policies (replaces existing) + Env map[string]string // Updated env map containing real secret values +} + // AttachVolumeRequest is the domain request for attaching a volume (used for API compatibility) type AttachVolumeRequest struct { MountPath string diff --git a/lib/instances/update_credentials.go b/lib/instances/update_credentials.go new file mode 100644 index 00000000..92aa3c33 --- /dev/null +++ b/lib/instances/update_credentials.go @@ -0,0 +1,87 @@ +package instances + +import ( + "context" + "fmt" + + "github.com/kernel/hypeman/lib/egressproxy" + "github.com/kernel/hypeman/lib/logger" +) + +// updateCredentials replaces the credential policies for an instance. +// If the instance has an active egress proxy registration (i.e., it is running), +// the proxy policy is updated atomically. For stopped/standby instances, only +// the stored metadata is updated — the proxy policy will be applied on next start/restore. +func (m *manager) updateCredentials(ctx context.Context, id string, req UpdateCredentialsRequest) (*Instance, error) { + log := logger.FromContext(ctx) + log.InfoContext(ctx, "updating credentials", "instance_id", id) + + // 1. Load instance metadata + meta, err := m.loadMetadata(id) + if err != nil { + return nil, err + } + stored := &meta.StoredMetadata + + // 2. Validate: credentials require egress proxy enabled + if len(req.Credentials) > 0 { + if stored.NetworkEgress == nil || !stored.NetworkEgress.Enabled { + return nil, fmt.Errorf("%w: credentials require network.egress.enabled=true", ErrInvalidRequest) + } + } + + // 3. Normalize and validate credential policies + normalized, err := normalizeCredentialPolicies(req.Credentials) + if err != nil { + return nil, err + } + + // 4. Validate credential env bindings against the provided env map + // Merge stored env with request env (request values override stored values) + mergedEnv := make(map[string]string, len(stored.Env)+len(req.Env)) + for k, v := range stored.Env { + mergedEnv[k] = v + } + for k, v := range req.Env { + mergedEnv[k] = v + } + + if len(normalized) > 0 { + if err := validateCredentialEnvBindings(normalized, mergedEnv); err != nil { + return nil, err + } + } + + // 5. Update stored metadata + stored.Credentials = cloneCredentialPolicies(normalized) + stored.Env = mergedEnv + + // 6. Update proxy policy if instance has an active egress proxy registration + m.egressProxyMu.Lock() + svc := m.egressProxy + m.egressProxyMu.Unlock() + + if svc != nil { + rules := buildEgressProxyInjectRules(stored.NetworkEgress, stored.Credentials, stored.Env) + if err := svc.UpdateInstancePolicy(id, rules); err != nil { + // ErrInstanceNotRegistered means instance isn't running with egress proxy — + // that's fine, the updated metadata will be picked up on next start/restore. + if err != egressproxy.ErrInstanceNotRegistered { + return nil, fmt.Errorf("update egress proxy policy: %w", err) + } + log.DebugContext(ctx, "instance not registered with egress proxy, skipping policy update", "instance_id", id) + } else { + log.InfoContext(ctx, "updated egress proxy policy", "instance_id", id) + } + } + + // 7. Persist metadata + if err := m.saveMetadata(meta); err != nil { + return nil, fmt.Errorf("save metadata: %w", err) + } + + // 8. Return updated instance + inst := m.toInstance(ctx, meta) + log.InfoContext(ctx, "credentials updated", "instance_id", id, "credential_count", len(stored.Credentials)) + return &inst, nil +} diff --git a/lib/instances/update_credentials_test.go b/lib/instances/update_credentials_test.go new file mode 100644 index 00000000..7ea09424 --- /dev/null +++ b/lib/instances/update_credentials_test.go @@ -0,0 +1,231 @@ +package instances + +import ( + "os" + "testing" + + "github.com/kernel/hypeman/lib/paths" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// setupCredentialsTestManager creates a minimal manager with temp paths and prepopulated metadata. +func setupCredentialsTestManager(t *testing.T, meta *metadata) *manager { + t.Helper() + tmpDir := t.TempDir() + p := paths.New(tmpDir) + + m := &manager{paths: p} + + // Create instance directory so saveMetadata can write + require.NoError(t, os.MkdirAll(p.InstanceDir(meta.Id), 0755)) + require.NoError(t, m.saveMetadata(meta)) + + return m +} + +func TestUpdateCredentials_ValidatesEgressRequired(t *testing.T) { + t.Parallel() + + m := setupCredentialsTestManager(t, &metadata{ + StoredMetadata: StoredMetadata{ + Id: "test-inst", + Name: "test", + NetworkEnabled: true, + NetworkEgress: nil, + Env: map[string]string{}, + }, + }) + + _, err := m.updateCredentials(t.Context(), "test-inst", UpdateCredentialsRequest{ + Credentials: map[string]CredentialPolicy{ + "MY_KEY": { + Source: CredentialSource{Env: "MY_KEY"}, + Inject: []CredentialInjectRule{ + {As: CredentialInjectAs{Header: "Authorization", Format: "Bearer ${value}"}}, + }, + }, + }, + Env: map[string]string{"MY_KEY": "real-secret"}, + }) + require.Error(t, err) + assert.ErrorIs(t, err, ErrInvalidRequest) + assert.Contains(t, err.Error(), "credentials require network.egress.enabled=true") +} + +func TestUpdateCredentials_ValidatesEnvBinding(t *testing.T) { + t.Parallel() + + m := setupCredentialsTestManager(t, &metadata{ + StoredMetadata: StoredMetadata{ + Id: "test-inst", + Name: "test", + NetworkEnabled: true, + NetworkEgress: &NetworkEgressPolicy{Enabled: true}, + Env: map[string]string{}, + }, + }) + + _, err := m.updateCredentials(t.Context(), "test-inst", UpdateCredentialsRequest{ + Credentials: map[string]CredentialPolicy{ + "MY_KEY": { + Source: CredentialSource{Env: "MY_KEY"}, + Inject: []CredentialInjectRule{ + {As: CredentialInjectAs{Header: "Authorization", Format: "Bearer ${value}"}}, + }, + }, + }, + Env: map[string]string{}, + }) + require.Error(t, err) + assert.ErrorIs(t, err, ErrInvalidRequest) + assert.Contains(t, err.Error(), "must be present in env") +} + +func TestUpdateCredentials_ClearsCredentials(t *testing.T) { + t.Parallel() + + m := setupCredentialsTestManager(t, &metadata{ + StoredMetadata: StoredMetadata{ + Id: "test-inst", + Name: "test", + NetworkEnabled: true, + NetworkEgress: &NetworkEgressPolicy{Enabled: true}, + Env: map[string]string{"MY_KEY": "old-secret"}, + Credentials: map[string]CredentialPolicy{ + "MY_KEY": { + Source: CredentialSource{Env: "MY_KEY"}, + Inject: []CredentialInjectRule{ + {As: CredentialInjectAs{Header: "Authorization", Format: "Bearer ${value}"}}, + }, + }, + }, + }, + }) + + result, err := m.updateCredentials(t.Context(), "test-inst", UpdateCredentialsRequest{ + Credentials: nil, + Env: map[string]string{}, + }) + require.NoError(t, err) + require.NotNil(t, result) + + reloaded, err := m.loadMetadata("test-inst") + require.NoError(t, err) + assert.Empty(t, reloaded.StoredMetadata.Credentials) +} + +func TestUpdateCredentials_MergesEnv(t *testing.T) { + t.Parallel() + + m := setupCredentialsTestManager(t, &metadata{ + StoredMetadata: StoredMetadata{ + Id: "test-inst", + Name: "test", + NetworkEnabled: true, + NetworkEgress: &NetworkEgressPolicy{Enabled: true}, + Env: map[string]string{ + "MY_KEY": "old-secret", + "EXISTING_VAR": "keep-me", + }, + }, + }) + + result, err := m.updateCredentials(t.Context(), "test-inst", UpdateCredentialsRequest{ + Credentials: map[string]CredentialPolicy{ + "MY_KEY": { + Source: CredentialSource{Env: "MY_KEY"}, + Inject: []CredentialInjectRule{ + {As: CredentialInjectAs{Header: "Authorization", Format: "Bearer ${value}"}}, + }, + }, + }, + Env: map[string]string{"MY_KEY": "new-rotated-secret"}, + }) + require.NoError(t, err) + require.NotNil(t, result) + + reloaded, err := m.loadMetadata("test-inst") + require.NoError(t, err) + assert.Equal(t, "new-rotated-secret", reloaded.StoredMetadata.Env["MY_KEY"]) + assert.Equal(t, "keep-me", reloaded.StoredMetadata.Env["EXISTING_VAR"]) +} + +func TestUpdateCredentials_NormalizesCredentials(t *testing.T) { + t.Parallel() + + m := setupCredentialsTestManager(t, &metadata{ + StoredMetadata: StoredMetadata{ + Id: "test-inst", + Name: "test", + NetworkEnabled: true, + NetworkEgress: &NetworkEgressPolicy{Enabled: true}, + Env: map[string]string{"MY_KEY": "secret"}, + }, + }) + + result, err := m.updateCredentials(t.Context(), "test-inst", UpdateCredentialsRequest{ + Credentials: map[string]CredentialPolicy{ + " MY_KEY ": { + Source: CredentialSource{Env: " MY_KEY "}, + Inject: []CredentialInjectRule{ + { + Hosts: []string{" API.OpenAI.com "}, + As: CredentialInjectAs{Header: " Authorization ", Format: " Bearer ${value} "}, + }, + }, + }, + }, + Env: map[string]string{}, + }) + require.NoError(t, err) + require.NotNil(t, result) + + reloaded, err := m.loadMetadata("test-inst") + require.NoError(t, err) + policy, ok := reloaded.StoredMetadata.Credentials["MY_KEY"] + require.True(t, ok) + assert.Equal(t, "MY_KEY", policy.Source.Env) + assert.Equal(t, "Authorization", policy.Inject[0].As.Header) + assert.Equal(t, "Bearer ${value}", policy.Inject[0].As.Format) + assert.Equal(t, []string{"api.openai.com"}, policy.Inject[0].Hosts) +} + +func TestUpdateCredentials_RejectsInvalidFormat(t *testing.T) { + t.Parallel() + + m := setupCredentialsTestManager(t, &metadata{ + StoredMetadata: StoredMetadata{ + Id: "test-inst", + Name: "test", + NetworkEnabled: true, + NetworkEgress: &NetworkEgressPolicy{Enabled: true}, + Env: map[string]string{"MY_KEY": "secret"}, + }, + }) + + _, err := m.updateCredentials(t.Context(), "test-inst", UpdateCredentialsRequest{ + Credentials: map[string]CredentialPolicy{ + "MY_KEY": { + Source: CredentialSource{Env: "MY_KEY"}, + Inject: []CredentialInjectRule{ + {As: CredentialInjectAs{Header: "Authorization", Format: "Bearer no-template"}}, + }, + }, + }, + Env: map[string]string{}, + }) + require.Error(t, err) + assert.Contains(t, err.Error(), "must include ${value}") +} + +func TestUpdateCredentials_NotFoundForMissingInstance(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + m := &manager{paths: paths.New(tmpDir)} + + _, err := m.updateCredentials(t.Context(), "nonexistent", UpdateCredentialsRequest{}) + require.Error(t, err) + assert.ErrorIs(t, err, ErrNotFound) +} diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index 161067ad..edb1a326 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -1039,6 +1039,19 @@ type SnapshotTargetState string // Tags User-defined key-value tags. type Tags map[string]string +// UpdateCredentialsRequest defines model for UpdateCredentialsRequest. +type UpdateCredentialsRequest struct { + // Credentials Replacement credential brokering policies keyed by guest-visible env var name. + // This is a **full replacement** — the provided map replaces the existing credentials entirely. + // Omit this field or pass an empty object to clear all credentials. + Credentials *map[string]CreateInstanceRequestCredential `json:"credentials,omitempty"` + + // Env Environment variable updates. Values here are merged with the existing env map + // (new keys are added, existing keys are overwritten). Use this to supply or rotate + // the real secret values referenced by credential policies. + Env *map[string]string `json:"env,omitempty"` +} + // Volume defines model for Volume. type Volume struct { // Attachments List of current attachments (empty if not attached) @@ -1264,6 +1277,9 @@ type CreateIngressJSONRequestBody = CreateIngressRequest // CreateInstanceJSONRequestBody defines body for CreateInstance for application/json ContentType. type CreateInstanceJSONRequestBody = CreateInstanceRequest +// UpdateInstanceCredentialsJSONRequestBody defines body for UpdateInstanceCredentials for application/json ContentType. +type UpdateInstanceCredentialsJSONRequestBody = UpdateCredentialsRequest + // ForkInstanceJSONRequestBody defines body for ForkInstance for application/json ContentType. type ForkInstanceJSONRequestBody = ForkInstanceRequest @@ -1435,6 +1451,11 @@ type ClientInterface interface { // GetInstance request GetInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + // UpdateInstanceCredentialsWithBody request with any body + UpdateInstanceCredentialsWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateInstanceCredentials(ctx context.Context, id string, body UpdateInstanceCredentialsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ForkInstanceWithBody request with any body ForkInstanceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1840,6 +1861,30 @@ func (c *Client) GetInstance(ctx context.Context, id string, reqEditors ...Reque return c.Client.Do(req) } +func (c *Client) UpdateInstanceCredentialsWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateInstanceCredentialsRequestWithBody(c.Server, id, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateInstanceCredentials(ctx context.Context, id string, body UpdateInstanceCredentialsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateInstanceCredentialsRequest(c.Server, id, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) ForkInstanceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewForkInstanceRequestWithBody(c.Server, id, contentType, body) if err != nil { @@ -3088,6 +3133,53 @@ func NewGetInstanceRequest(server string, id string) (*http.Request, error) { return req, nil } +// NewUpdateInstanceCredentialsRequest calls the generic UpdateInstanceCredentials builder with application/json body +func NewUpdateInstanceCredentialsRequest(server string, id string, body UpdateInstanceCredentialsJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateInstanceCredentialsRequestWithBody(server, id, "application/json", bodyReader) +} + +// NewUpdateInstanceCredentialsRequestWithBody generates requests for UpdateInstanceCredentials with any type of body +func NewUpdateInstanceCredentialsRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances/%s/credentials", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewForkInstanceRequest calls the generic ForkInstance builder with application/json body func NewForkInstanceRequest(server string, id string, body ForkInstanceJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -4277,6 +4369,11 @@ type ClientWithResponsesInterface interface { // GetInstanceWithResponse request GetInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetInstanceResponse, error) + // UpdateInstanceCredentialsWithBodyWithResponse request with any body + UpdateInstanceCredentialsWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateInstanceCredentialsResponse, error) + + UpdateInstanceCredentialsWithResponse(ctx context.Context, id string, body UpdateInstanceCredentialsJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateInstanceCredentialsResponse, error) + // ForkInstanceWithBodyWithResponse request with any body ForkInstanceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ForkInstanceResponse, error) @@ -4916,6 +5013,31 @@ func (r GetInstanceResponse) StatusCode() int { return 0 } +type UpdateInstanceCredentialsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Instance + JSON400 *Error + JSON404 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r UpdateInstanceCredentialsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateInstanceCredentialsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type ForkInstanceResponse struct { Body []byte HTTPResponse *http.Response @@ -5701,6 +5823,23 @@ func (c *ClientWithResponses) GetInstanceWithResponse(ctx context.Context, id st return ParseGetInstanceResponse(rsp) } +// UpdateInstanceCredentialsWithBodyWithResponse request with arbitrary body returning *UpdateInstanceCredentialsResponse +func (c *ClientWithResponses) UpdateInstanceCredentialsWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateInstanceCredentialsResponse, error) { + rsp, err := c.UpdateInstanceCredentialsWithBody(ctx, id, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateInstanceCredentialsResponse(rsp) +} + +func (c *ClientWithResponses) UpdateInstanceCredentialsWithResponse(ctx context.Context, id string, body UpdateInstanceCredentialsJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateInstanceCredentialsResponse, error) { + rsp, err := c.UpdateInstanceCredentials(ctx, id, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateInstanceCredentialsResponse(rsp) +} + // ForkInstanceWithBodyWithResponse request with arbitrary body returning *ForkInstanceResponse func (c *ClientWithResponses) ForkInstanceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ForkInstanceResponse, error) { rsp, err := c.ForkInstanceWithBody(ctx, id, contentType, body, reqEditors...) @@ -6917,6 +7056,53 @@ func ParseGetInstanceResponse(rsp *http.Response) (*GetInstanceResponse, error) return response, nil } +// ParseUpdateInstanceCredentialsResponse parses an HTTP response from a UpdateInstanceCredentialsWithResponse call +func ParseUpdateInstanceCredentialsResponse(rsp *http.Response) (*UpdateInstanceCredentialsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateInstanceCredentialsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Instance + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseForkInstanceResponse parses an HTTP response from a ForkInstanceWithResponse call func ParseForkInstanceResponse(rsp *http.Response) (*ForkInstanceResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -7994,6 +8180,9 @@ type ServerInterface interface { // Get instance details // (GET /instances/{id}) GetInstance(w http.ResponseWriter, r *http.Request, id string) + // Update instance credentials + // (PUT /instances/{id}/credentials) + UpdateInstanceCredentials(w http.ResponseWriter, r *http.Request, id string) // Fork an instance from stopped, standby, or running (with from_running=true) // (POST /instances/{id}/fork) ForkInstance(w http.ResponseWriter, r *http.Request, id string) @@ -8204,6 +8393,12 @@ func (_ Unimplemented) GetInstance(w http.ResponseWriter, r *http.Request, id st w.WriteHeader(http.StatusNotImplemented) } +// Update instance credentials +// (PUT /instances/{id}/credentials) +func (_ Unimplemented) UpdateInstanceCredentials(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + // Fork an instance from stopped, standby, or running (with from_running=true) // (POST /instances/{id}/fork) func (_ Unimplemented) ForkInstance(w http.ResponseWriter, r *http.Request, id string) { @@ -9004,6 +9199,37 @@ func (siw *ServerInterfaceWrapper) GetInstance(w http.ResponseWriter, r *http.Re handler.ServeHTTP(w, r) } +// UpdateInstanceCredentials operation middleware +func (siw *ServerInterfaceWrapper) UpdateInstanceCredentials(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateInstanceCredentials(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // ForkInstance operation middleware func (siw *ServerInterfaceWrapper) ForkInstance(w http.ResponseWriter, r *http.Request) { @@ -9994,6 +10220,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/instances/{id}", wrapper.GetInstance) }) + r.Group(func(r chi.Router) { + r.Put(options.BaseURL+"/instances/{id}/credentials", wrapper.UpdateInstanceCredentials) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/instances/{id}/fork", wrapper.ForkInstance) }) @@ -10981,6 +11210,51 @@ func (response GetInstance500JSONResponse) VisitGetInstanceResponse(w http.Respo return json.NewEncoder(w).Encode(response) } +type UpdateInstanceCredentialsRequestObject struct { + Id string `json:"id"` + Body *UpdateInstanceCredentialsJSONRequestBody +} + +type UpdateInstanceCredentialsResponseObject interface { + VisitUpdateInstanceCredentialsResponse(w http.ResponseWriter) error +} + +type UpdateInstanceCredentials200JSONResponse Instance + +func (response UpdateInstanceCredentials200JSONResponse) VisitUpdateInstanceCredentialsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type UpdateInstanceCredentials400JSONResponse Error + +func (response UpdateInstanceCredentials400JSONResponse) VisitUpdateInstanceCredentialsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type UpdateInstanceCredentials404JSONResponse Error + +func (response UpdateInstanceCredentials404JSONResponse) VisitUpdateInstanceCredentialsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type UpdateInstanceCredentials500JSONResponse Error + +func (response UpdateInstanceCredentials500JSONResponse) VisitUpdateInstanceCredentialsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type ForkInstanceRequestObject struct { Id string `json:"id"` Body *ForkInstanceJSONRequestBody @@ -12030,6 +12304,9 @@ type StrictServerInterface interface { // Get instance details // (GET /instances/{id}) GetInstance(ctx context.Context, request GetInstanceRequestObject) (GetInstanceResponseObject, error) + // Update instance credentials + // (PUT /instances/{id}/credentials) + UpdateInstanceCredentials(ctx context.Context, request UpdateInstanceCredentialsRequestObject) (UpdateInstanceCredentialsResponseObject, error) // Fork an instance from stopped, standby, or running (with from_running=true) // (POST /instances/{id}/fork) ForkInstance(ctx context.Context, request ForkInstanceRequestObject) (ForkInstanceResponseObject, error) @@ -12747,6 +13024,39 @@ func (sh *strictHandler) GetInstance(w http.ResponseWriter, r *http.Request, id } } +// UpdateInstanceCredentials operation middleware +func (sh *strictHandler) UpdateInstanceCredentials(w http.ResponseWriter, r *http.Request, id string) { + var request UpdateInstanceCredentialsRequestObject + + request.Id = id + + var body UpdateInstanceCredentialsJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UpdateInstanceCredentials(ctx, request.(UpdateInstanceCredentialsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UpdateInstanceCredentials") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UpdateInstanceCredentialsResponseObject); ok { + if err := validResponse.VisitUpdateInstanceCredentialsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // ForkInstance operation middleware func (sh *strictHandler) ForkInstance(w http.ResponseWriter, r *http.Request, id string) { var request ForkInstanceRequestObject @@ -13374,217 +13684,224 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi.yaml b/openapi.yaml index 1f0b5e1a..92fcc8a1 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -203,6 +203,35 @@ components: $ref: "#/components/schemas/CreateInstanceRequestCredentialInject" minItems: 1 + UpdateCredentialsRequest: + type: object + properties: + credentials: + type: object + description: | + Replacement credential brokering policies keyed by guest-visible env var name. + This is a **full replacement** — the provided map replaces the existing credentials entirely. + Omit this field or pass an empty object to clear all credentials. + additionalProperties: + $ref: "#/components/schemas/CreateInstanceRequestCredential" + example: + OUTBOUND_OPENAI_KEY: + source: + env: OUTBOUND_OPENAI_KEY + inject: + - hosts: [api.openai.com, "*.openai.com"] + as: + header: Authorization + format: "Bearer ${value}" + env: + type: object + additionalProperties: + type: string + description: | + Environment variable updates. Values here are merged with the existing env map + (new keys are added, existing keys are overwritten). Use this to supply or rotate + the real secret values referenced by credential policies. + CreateInstanceRequest: type: object required: [name, image] @@ -2397,6 +2426,59 @@ paths: schema: $ref: "#/components/schemas/Error" + /instances/{id}/credentials: + put: + summary: Update instance credentials + description: | + Replaces credential brokering policies for an instance. Real secrets stay on the + host; the proxy rewrites headers at request time, so updating credentials does NOT + require any VM-side changes. + + If the instance is running with an active egress proxy, the proxy policy is updated + atomically. For stopped or standby instances, the updated config is persisted and + takes effect on next start/restore. + operationId: updateInstanceCredentials + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + description: Instance ID or name + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateCredentialsRequest" + responses: + 200: + description: Credentials updated + content: + application/json: + schema: + $ref: "#/components/schemas/Instance" + 400: + description: Bad request - invalid credentials + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 404: + description: Instance not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /instances/{id}/volumes/{volumeId}: post: summary: Attach volume to instance From 2cc9c35cbe7dac94154c1a03f4af573781c2deff Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:45:19 +0000 Subject: [PATCH 2/5] Change credentials endpoint from PUT to PATCH with merge semantics Credentials included in the request are added or updated by name; credentials not mentioned in the request are left unchanged. This is more useful for secret rotation where you typically rotate one credential at a time without disturbing others. - openapi.yaml: PUT -> PATCH, updated descriptions for merge behavior - lib/instances/update_credentials.go: merge incoming over existing credentials map instead of full replacement - Tests: replaced clear-credentials test with preserve/merge/override tests (9 total, all passing) - lib/oapi/oapi.go: regenerated Co-Authored-By: Claude Opus 4.6 --- lib/instances/update_credentials.go | 33 +- lib/instances/update_credentials_test.go | 118 +++++- lib/oapi/oapi.go | 453 ++++++++++++----------- openapi.yaml | 15 +- 4 files changed, 373 insertions(+), 246 deletions(-) diff --git a/lib/instances/update_credentials.go b/lib/instances/update_credentials.go index 92aa3c33..beab79ec 100644 --- a/lib/instances/update_credentials.go +++ b/lib/instances/update_credentials.go @@ -8,10 +8,12 @@ import ( "github.com/kernel/hypeman/lib/logger" ) -// updateCredentials replaces the credential policies for an instance. -// If the instance has an active egress proxy registration (i.e., it is running), -// the proxy policy is updated atomically. For stopped/standby instances, only -// the stored metadata is updated — the proxy policy will be applied on next start/restore. +// updateCredentials merges credential policies into an instance's existing set. +// Credentials included in the request are added or updated by name; credentials +// not mentioned are left unchanged. If the instance has an active egress proxy +// registration (i.e., it is running), the proxy policy is updated atomically. +// For stopped/standby instances, only the stored metadata is updated — the proxy +// policy will be applied on next start/restore. func (m *manager) updateCredentials(ctx context.Context, id string, req UpdateCredentialsRequest) (*Instance, error) { log := logger.FromContext(ctx) log.InfoContext(ctx, "updating credentials", "instance_id", id) @@ -30,14 +32,13 @@ func (m *manager) updateCredentials(ctx context.Context, id string, req UpdateCr } } - // 3. Normalize and validate credential policies + // 3. Normalize and validate the incoming credential policies normalized, err := normalizeCredentialPolicies(req.Credentials) if err != nil { return nil, err } - // 4. Validate credential env bindings against the provided env map - // Merge stored env with request env (request values override stored values) + // 4. Merge env: stored env + request env (request values override) mergedEnv := make(map[string]string, len(stored.Env)+len(req.Env)) for k, v := range stored.Env { mergedEnv[k] = v @@ -46,14 +47,24 @@ func (m *manager) updateCredentials(ctx context.Context, id string, req UpdateCr mergedEnv[k] = v } - if len(normalized) > 0 { - if err := validateCredentialEnvBindings(normalized, mergedEnv); err != nil { + // 5. Merge credentials: start from existing, overlay with incoming + mergedCreds := cloneCredentialPolicies(stored.Credentials) + if mergedCreds == nil { + mergedCreds = make(map[string]CredentialPolicy) + } + for name, policy := range normalized { + mergedCreds[name] = policy + } + + // 6. Validate env bindings for the full merged credential set + if len(mergedCreds) > 0 { + if err := validateCredentialEnvBindings(mergedCreds, mergedEnv); err != nil { return nil, err } } - // 5. Update stored metadata - stored.Credentials = cloneCredentialPolicies(normalized) + // 7. Update stored metadata + stored.Credentials = mergedCreds stored.Env = mergedEnv // 6. Update proxy policy if instance has an active egress proxy registration diff --git a/lib/instances/update_credentials_test.go b/lib/instances/update_credentials_test.go index 7ea09424..d7c1795a 100644 --- a/lib/instances/update_credentials_test.go +++ b/lib/instances/update_credentials_test.go @@ -82,7 +82,7 @@ func TestUpdateCredentials_ValidatesEnvBinding(t *testing.T) { assert.Contains(t, err.Error(), "must be present in env") } -func TestUpdateCredentials_ClearsCredentials(t *testing.T) { +func TestUpdateCredentials_PreservesExistingWhenNilRequest(t *testing.T) { t.Parallel() m := setupCredentialsTestManager(t, &metadata{ @@ -103,6 +103,7 @@ func TestUpdateCredentials_ClearsCredentials(t *testing.T) { }, }) + // PATCH with nil credentials should preserve existing credentials result, err := m.updateCredentials(t.Context(), "test-inst", UpdateCredentialsRequest{ Credentials: nil, Env: map[string]string{}, @@ -112,7 +113,120 @@ func TestUpdateCredentials_ClearsCredentials(t *testing.T) { reloaded, err := m.loadMetadata("test-inst") require.NoError(t, err) - assert.Empty(t, reloaded.StoredMetadata.Credentials) + require.Len(t, reloaded.StoredMetadata.Credentials, 1) + _, ok := reloaded.StoredMetadata.Credentials["MY_KEY"] + assert.True(t, ok, "existing credential should be preserved") +} + +func TestUpdateCredentials_MergesNewCredentialWithExisting(t *testing.T) { + t.Parallel() + + m := setupCredentialsTestManager(t, &metadata{ + StoredMetadata: StoredMetadata{ + Id: "test-inst", + Name: "test", + NetworkEnabled: true, + NetworkEgress: &NetworkEgressPolicy{Enabled: true}, + Env: map[string]string{ + "OPENAI_KEY": "openai-secret", + "ANTHROPIC_KEY": "anthropic-secret", + }, + Credentials: map[string]CredentialPolicy{ + "OPENAI_KEY": { + Source: CredentialSource{Env: "OPENAI_KEY"}, + Inject: []CredentialInjectRule{ + { + Hosts: []string{"api.openai.com"}, + As: CredentialInjectAs{Header: "Authorization", Format: "Bearer ${value}"}, + }, + }, + }, + }, + }, + }) + + // PATCH: add a new credential without touching the existing one + result, err := m.updateCredentials(t.Context(), "test-inst", UpdateCredentialsRequest{ + Credentials: map[string]CredentialPolicy{ + "ANTHROPIC_KEY": { + Source: CredentialSource{Env: "ANTHROPIC_KEY"}, + Inject: []CredentialInjectRule{ + { + Hosts: []string{"api.anthropic.com"}, + As: CredentialInjectAs{Header: "x-api-key", Format: "${value}"}, + }, + }, + }, + }, + Env: map[string]string{}, + }) + require.NoError(t, err) + require.NotNil(t, result) + + reloaded, err := m.loadMetadata("test-inst") + require.NoError(t, err) + require.Len(t, reloaded.StoredMetadata.Credentials, 2) + + // Existing credential preserved + openai, ok := reloaded.StoredMetadata.Credentials["OPENAI_KEY"] + require.True(t, ok, "existing OPENAI_KEY credential should be preserved") + assert.Equal(t, "OPENAI_KEY", openai.Source.Env) + assert.Equal(t, []string{"api.openai.com"}, openai.Inject[0].Hosts) + + // New credential added + anthropic, ok := reloaded.StoredMetadata.Credentials["ANTHROPIC_KEY"] + require.True(t, ok, "new ANTHROPIC_KEY credential should be added") + assert.Equal(t, "ANTHROPIC_KEY", anthropic.Source.Env) + assert.Equal(t, []string{"api.anthropic.com"}, anthropic.Inject[0].Hosts) +} + +func TestUpdateCredentials_OverridesExistingByName(t *testing.T) { + t.Parallel() + + m := setupCredentialsTestManager(t, &metadata{ + StoredMetadata: StoredMetadata{ + Id: "test-inst", + Name: "test", + NetworkEnabled: true, + NetworkEgress: &NetworkEgressPolicy{Enabled: true}, + Env: map[string]string{"MY_KEY": "old-secret"}, + Credentials: map[string]CredentialPolicy{ + "MY_KEY": { + Source: CredentialSource{Env: "MY_KEY"}, + Inject: []CredentialInjectRule{ + { + Hosts: []string{"api.old.com"}, + As: CredentialInjectAs{Header: "Authorization", Format: "Bearer ${value}"}, + }, + }, + }, + }, + }, + }) + + // PATCH: update existing credential with new hosts + result, err := m.updateCredentials(t.Context(), "test-inst", UpdateCredentialsRequest{ + Credentials: map[string]CredentialPolicy{ + "MY_KEY": { + Source: CredentialSource{Env: "MY_KEY"}, + Inject: []CredentialInjectRule{ + { + Hosts: []string{"api.new.com"}, + As: CredentialInjectAs{Header: "Authorization", Format: "Bearer ${value}"}, + }, + }, + }, + }, + Env: map[string]string{}, + }) + require.NoError(t, err) + require.NotNil(t, result) + + reloaded, err := m.loadMetadata("test-inst") + require.NoError(t, err) + require.Len(t, reloaded.StoredMetadata.Credentials, 1) + policy := reloaded.StoredMetadata.Credentials["MY_KEY"] + assert.Equal(t, []string{"api.new.com"}, policy.Inject[0].Hosts, "credential should be updated with new hosts") } func TestUpdateCredentials_MergesEnv(t *testing.T) { diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index edb1a326..578467d7 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -1041,9 +1041,9 @@ type Tags map[string]string // UpdateCredentialsRequest defines model for UpdateCredentialsRequest. type UpdateCredentialsRequest struct { - // Credentials Replacement credential brokering policies keyed by guest-visible env var name. - // This is a **full replacement** — the provided map replaces the existing credentials entirely. - // Omit this field or pass an empty object to clear all credentials. + // Credentials Credential brokering policies to add or update, keyed by guest-visible env var name. + // Credentials included here are merged into the existing set: matching names are + // overwritten, new names are added, and credentials not mentioned are left unchanged. Credentials *map[string]CreateInstanceRequestCredential `json:"credentials,omitempty"` // Env Environment variable updates. Values here are merged with the existing env map @@ -3170,7 +3170,7 @@ func NewUpdateInstanceCredentialsRequestWithBody(server string, id string, conte return nil, err } - req, err := http.NewRequest("PUT", queryURL.String(), body) + req, err := http.NewRequest("PATCH", queryURL.String(), body) if err != nil { return nil, err } @@ -8181,7 +8181,7 @@ type ServerInterface interface { // (GET /instances/{id}) GetInstance(w http.ResponseWriter, r *http.Request, id string) // Update instance credentials - // (PUT /instances/{id}/credentials) + // (PATCH /instances/{id}/credentials) UpdateInstanceCredentials(w http.ResponseWriter, r *http.Request, id string) // Fork an instance from stopped, standby, or running (with from_running=true) // (POST /instances/{id}/fork) @@ -8394,7 +8394,7 @@ func (_ Unimplemented) GetInstance(w http.ResponseWriter, r *http.Request, id st } // Update instance credentials -// (PUT /instances/{id}/credentials) +// (PATCH /instances/{id}/credentials) func (_ Unimplemented) UpdateInstanceCredentials(w http.ResponseWriter, r *http.Request, id string) { w.WriteHeader(http.StatusNotImplemented) } @@ -10221,7 +10221,7 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/instances/{id}", wrapper.GetInstance) }) r.Group(func(r chi.Router) { - r.Put(options.BaseURL+"/instances/{id}/credentials", wrapper.UpdateInstanceCredentials) + r.Patch(options.BaseURL+"/instances/{id}/credentials", wrapper.UpdateInstanceCredentials) }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/instances/{id}/fork", wrapper.ForkInstance) @@ -12305,7 +12305,7 @@ type StrictServerInterface interface { // (GET /instances/{id}) GetInstance(ctx context.Context, request GetInstanceRequestObject) (GetInstanceResponseObject, error) // Update instance credentials - // (PUT /instances/{id}/credentials) + // (PATCH /instances/{id}/credentials) UpdateInstanceCredentials(ctx context.Context, request UpdateInstanceCredentialsRequestObject) (UpdateInstanceCredentialsResponseObject, error) // Fork an instance from stopped, standby, or running (with from_running=true) // (POST /instances/{id}/fork) @@ -13684,224 +13684,225 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "/w8AAP//eOC5FdwzAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi.yaml b/openapi.yaml index 92fcc8a1..ca36b210 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -209,9 +209,9 @@ components: credentials: type: object description: | - Replacement credential brokering policies keyed by guest-visible env var name. - This is a **full replacement** — the provided map replaces the existing credentials entirely. - Omit this field or pass an empty object to clear all credentials. + Credential brokering policies to add or update, keyed by guest-visible env var name. + Credentials included here are merged into the existing set: matching names are + overwritten, new names are added, and credentials not mentioned are left unchanged. additionalProperties: $ref: "#/components/schemas/CreateInstanceRequestCredential" example: @@ -2427,12 +2427,13 @@ paths: $ref: "#/components/schemas/Error" /instances/{id}/credentials: - put: + patch: summary: Update instance credentials description: | - Replaces credential brokering policies for an instance. Real secrets stay on the - host; the proxy rewrites headers at request time, so updating credentials does NOT - require any VM-side changes. + Merges credential brokering policies for an instance. Credentials included in the + request are added or updated by name; credentials not mentioned are left unchanged. + Real secrets stay on the host; the proxy rewrites headers at request time, so + updating credentials does NOT require any VM-side changes. If the instance is running with an active egress proxy, the proxy policy is updated atomically. For stopped or standby instances, the updated config is persisted and From 12dc7527cac2471d50949ddf694aea30deded3e7 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:46:21 +0000 Subject: [PATCH 3/5] docs: document credential rotation in egress proxy README Co-Authored-By: Claude Opus 4.6 --- lib/egressproxy/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/egressproxy/README.md b/lib/egressproxy/README.md index 42d77d42..f2eb03a3 100644 --- a/lib/egressproxy/README.md +++ b/lib/egressproxy/README.md @@ -37,6 +37,10 @@ This keeps real secrets out of the VM while still allowing authenticated egress - Egress enforcement is applied per instance TAP device and removed when the instance stops/standbys/deletes. - Enforcement intentionally targets TCP egress only. DNS/other non-TCP traffic is not rewritten and is not blocked by `all` mode. +## Credential rotation + +Credentials can be updated at runtime via `PATCH /instances/{id}/credentials` without restarting the VM or touching the guest. The endpoint uses merge semantics: only credentials included in the request are added or updated by name; credentials not mentioned are left unchanged. The proxy picks up new values immediately for running instances. For stopped or standby instances, the updated config takes effect on next start/restore. + ## Limits of enforcement - Header injection is applied to HTTP headers only (not request/response bodies). From 48e86561760e4994ae9e5d39999d5dbbdbcbd8f7 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:58:47 +0000 Subject: [PATCH 4/5] fix: gofmt alignment in update_credentials_test.go Co-Authored-By: Claude Opus 4.6 --- lib/instances/update_credentials_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/instances/update_credentials_test.go b/lib/instances/update_credentials_test.go index d7c1795a..a5e82097 100644 --- a/lib/instances/update_credentials_test.go +++ b/lib/instances/update_credentials_test.go @@ -128,7 +128,7 @@ func TestUpdateCredentials_MergesNewCredentialWithExisting(t *testing.T) { NetworkEnabled: true, NetworkEgress: &NetworkEgressPolicy{Enabled: true}, Env: map[string]string{ - "OPENAI_KEY": "openai-secret", + "OPENAI_KEY": "openai-secret", "ANTHROPIC_KEY": "anthropic-secret", }, Credentials: map[string]CredentialPolicy{ From 84bf287cefc004033ebbb4be11f08444c9ce4bd5 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Thu, 19 Mar 2026 20:01:49 +0000 Subject: [PATCH 5/5] Add scope mapping for PATCH /instances/{id}/credentials Fixes CI failure: TestAllRoutesHaveScopes requires every registered route to have a scope mapping. Uses InstanceWrite, matching other instance mutation endpoints. Co-Authored-By: Claude Opus 4.6 --- lib/scopes/scopes.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/scopes/scopes.go b/lib/scopes/scopes.go index 1ebbc49a..dba90766 100644 --- a/lib/scopes/scopes.go +++ b/lib/scopes/scopes.go @@ -239,6 +239,7 @@ var RouteScopes = map[string]Scope{ "GET /instances/{id}/stat": InstanceRead, "GET /instances/{id}/stats": InstanceRead, "POST /instances/{id}/stop": InstanceWrite, + "PATCH /instances/{id}/credentials": InstanceWrite, "DELETE /instances/{id}/volumes/{volumeId}": VolumeWrite, "POST /instances/{id}/volumes/{volumeId}": VolumeWrite,