From ad6ea5b3c14e29f03151058a2e1e34aff598b1c6 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:43:09 +0000 Subject: [PATCH] Add PUT /instances/{id}/credentials endpoint for secret rotation Adds a credential update endpoint that replaces credential brokering policies on an existing instance without touching the VM. When the instance has an active egress proxy, the proxy's header injection rules are updated immediately via a new UpdateInstancePolicy method. Changes: - openapi.yaml: new UpdateInstanceCredentialsRequest schema and endpoint - lib/oapi/oapi.go: regenerated from updated spec - lib/egressproxy/service.go: add UpdateInstancePolicy for in-place policy updates without re-registering the full instance - lib/instances/manager.go: add UpdateInstanceCredentials to Manager interface and manager implementation - lib/instances/credentials.go: core logic for credential replacement, validation, env merging, and egress proxy policy refresh - cmd/api/api/instances.go: API handler with OAPI-to-domain conversion - Tests: 4 handler tests + 3 egress proxy UpdateInstancePolicy tests Co-Authored-By: Claude Opus 4.6 --- cmd/api/api/instances.go | 74 +++ cmd/api/api/instances_test.go | 205 +++++++ lib/egressproxy/service.go | 20 + lib/egressproxy/update_policy_test.go | 82 +++ lib/instances/credentials.go | 84 +++ lib/instances/manager.go | 12 + lib/oapi/oapi.go | 736 ++++++++++++++++++-------- openapi.yaml | 69 +++ 8 files changed, 1071 insertions(+), 211 deletions(-) create mode 100644 lib/egressproxy/update_policy_test.go create mode 100644 lib/instances/credentials.go diff --git a/cmd/api/api/instances.go b/cmd/api/api/instances.go index ba5b448a..38a42711 100644 --- a/cmd/api/api/instances.go +++ b/cmd/api/api/instances.go @@ -798,6 +798,80 @@ func (s *ApiService) StatInstancePath(ctx context.Context, request oapi.StatInst return response, nil } +// UpdateInstanceCredentials replaces credential brokering policies for an instance. +// The id parameter can be an instance ID, name, or ID prefix. +// Note: Resolution is handled by ResolveResource middleware. +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 credential types to domain types + 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.UpdateInstanceCredentials(ctx, inst.Id, credentials, 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 instance credentials", "error", err) + return oapi.UpdateInstanceCredentials500JSONResponse{ + Code: "internal_error", + Message: "failed to update instance 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/cmd/api/api/instances_test.go b/cmd/api/api/instances_test.go index ea883a08..c5f767de 100644 --- a/cmd/api/api/instances_test.go +++ b/cmd/api/api/instances_test.go @@ -191,6 +191,25 @@ func TestCreateInstance_InvalidSizeFormat(t *testing.T) { assert.Contains(t, badReq.Message, "invalid size format") } +type captureUpdateCredentialsManager struct { + instances.Manager + lastID string + lastCredentials map[string]instances.CredentialPolicy + lastEnv map[string]string + result *instances.Instance + err error +} + +func (m *captureUpdateCredentialsManager) UpdateInstanceCredentials(ctx context.Context, id string, credentials map[string]instances.CredentialPolicy, env map[string]string) (*instances.Instance, error) { + m.lastID = id + m.lastCredentials = credentials + m.lastEnv = env + if m.err != nil { + return nil, m.err + } + return m.result, nil +} + type captureCreateManager struct { instances.Manager lastReq *instances.CreateInstanceRequest @@ -688,3 +707,189 @@ func waitForState(t *testing.T, svc *ApiService, instanceID string, expectedStat } t.Fatalf("Timeout waiting for instance to reach %s state", expectedState) } + +func TestUpdateInstanceCredentials_Success(t *testing.T) { + t.Parallel() + svc := newTestService(t) + + now := time.Now() + source := instances.Instance{ + StoredMetadata: instances.StoredMetadata{ + Id: "inst-creds", + Name: "inst-creds", + Image: "docker.io/library/alpine:latest", + CreatedAt: now, + HypervisorType: hypervisor.TypeCloudHypervisor, + }, + State: instances.StateRunning, + } + + mockMgr := &captureUpdateCredentialsManager{ + Manager: svc.InstanceManager, + result: &source, + } + svc.InstanceManager = mockMgr + + credentials := map[string]oapi.CreateInstanceRequestCredential{ + "OPENAI_KEY": { + Source: oapi.CreateInstanceRequestCredentialSource{Env: "OPENAI_KEY"}, + Inject: []oapi.CreateInstanceRequestCredentialInject{ + { + Hosts: &[]string{"api.openai.com"}, + As: oapi.CreateInstanceRequestCredentialInjectAs{ + Header: "Authorization", + Format: "Bearer ${value}", + }, + }, + }, + }, + } + env := map[string]string{"OPENAI_KEY": "sk-rotated-key"} + + resp, err := svc.UpdateInstanceCredentials( + mw.WithResolvedInstance(ctx(), source.Id, source), + oapi.UpdateInstanceCredentialsRequestObject{ + Id: source.Id, + Body: &oapi.UpdateInstanceCredentialsRequest{ + Credentials: credentials, + Env: &env, + }, + }, + ) + require.NoError(t, err) + + ok200, ok := resp.(oapi.UpdateInstanceCredentials200JSONResponse) + require.True(t, ok, "expected 200 response, got %T", resp) + assert.Equal(t, "inst-creds", ok200.Id) + + // Verify domain conversion + assert.Equal(t, source.Id, mockMgr.lastID) + require.Contains(t, mockMgr.lastCredentials, "OPENAI_KEY") + policy := mockMgr.lastCredentials["OPENAI_KEY"] + assert.Equal(t, "OPENAI_KEY", policy.Source.Env) + require.Len(t, policy.Inject, 1) + assert.Equal(t, []string{"api.openai.com"}, policy.Inject[0].Hosts) + assert.Equal(t, "Authorization", policy.Inject[0].As.Header) + assert.Equal(t, "Bearer ${value}", policy.Inject[0].As.Format) + assert.Equal(t, "sk-rotated-key", mockMgr.lastEnv["OPENAI_KEY"]) +} + +func TestUpdateInstanceCredentials_EmptyCredentialsClears(t *testing.T) { + t.Parallel() + svc := newTestService(t) + + now := time.Now() + source := instances.Instance{ + StoredMetadata: instances.StoredMetadata{ + Id: "inst-clear-creds", + Name: "inst-clear-creds", + Image: "docker.io/library/alpine:latest", + CreatedAt: now, + HypervisorType: hypervisor.TypeCloudHypervisor, + }, + State: instances.StateRunning, + } + + mockMgr := &captureUpdateCredentialsManager{ + Manager: svc.InstanceManager, + result: &source, + } + svc.InstanceManager = mockMgr + + resp, err := svc.UpdateInstanceCredentials( + mw.WithResolvedInstance(ctx(), source.Id, source), + oapi.UpdateInstanceCredentialsRequestObject{ + Id: source.Id, + Body: &oapi.UpdateInstanceCredentialsRequest{ + Credentials: map[string]oapi.CreateInstanceRequestCredential{}, + }, + }, + ) + require.NoError(t, err) + + _, ok := resp.(oapi.UpdateInstanceCredentials200JSONResponse) + require.True(t, ok, "expected 200 response") + assert.Empty(t, mockMgr.lastCredentials) +} + +func TestUpdateInstanceCredentials_InvalidRequest(t *testing.T) { + t.Parallel() + svc := newTestService(t) + + source := instances.Instance{ + StoredMetadata: instances.StoredMetadata{ + Id: "inst-invalid-creds", + Name: "inst-invalid-creds", + Image: "docker.io/library/alpine:latest", + CreatedAt: time.Now(), + HypervisorType: hypervisor.TypeCloudHypervisor, + }, + State: instances.StateRunning, + } + + mockMgr := &captureUpdateCredentialsManager{ + Manager: svc.InstanceManager, + err: fmt.Errorf("%w: credentials require network.egress.enabled=true", instances.ErrInvalidRequest), + } + svc.InstanceManager = mockMgr + + resp, err := svc.UpdateInstanceCredentials( + mw.WithResolvedInstance(ctx(), source.Id, source), + oapi.UpdateInstanceCredentialsRequestObject{ + Id: source.Id, + Body: &oapi.UpdateInstanceCredentialsRequest{ + Credentials: map[string]oapi.CreateInstanceRequestCredential{ + "KEY": { + Source: oapi.CreateInstanceRequestCredentialSource{Env: "KEY"}, + Inject: []oapi.CreateInstanceRequestCredentialInject{ + {As: oapi.CreateInstanceRequestCredentialInjectAs{Header: "X-Key", Format: "${value}"}}, + }, + }, + }, + }, + }, + ) + require.NoError(t, err) + + badReq, ok := resp.(oapi.UpdateInstanceCredentials400JSONResponse) + require.True(t, ok, "expected 400 response, got %T", resp) + assert.Equal(t, "invalid_request", badReq.Code) + assert.Contains(t, badReq.Message, "credentials require network.egress.enabled=true") +} + +func TestUpdateInstanceCredentials_NotFound(t *testing.T) { + t.Parallel() + svc := newTestService(t) + + source := instances.Instance{ + StoredMetadata: instances.StoredMetadata{ + Id: "inst-gone", + Name: "inst-gone", + Image: "docker.io/library/alpine:latest", + CreatedAt: time.Now(), + HypervisorType: hypervisor.TypeCloudHypervisor, + }, + State: instances.StateRunning, + } + + mockMgr := &captureUpdateCredentialsManager{ + Manager: svc.InstanceManager, + err: instances.ErrNotFound, + } + svc.InstanceManager = mockMgr + + resp, err := svc.UpdateInstanceCredentials( + mw.WithResolvedInstance(ctx(), source.Id, source), + oapi.UpdateInstanceCredentialsRequestObject{ + Id: source.Id, + Body: &oapi.UpdateInstanceCredentialsRequest{ + Credentials: map[string]oapi.CreateInstanceRequestCredential{}, + }, + }, + ) + require.NoError(t, err) + + notFound, ok := resp.(oapi.UpdateInstanceCredentials404JSONResponse) + require.True(t, ok, "expected 404 response, got %T", resp) + assert.Equal(t, "not_found", notFound.Code) +} diff --git a/lib/egressproxy/service.go b/lib/egressproxy/service.go index 75cd2e7f..5d170c7a 100644 --- a/lib/egressproxy/service.go +++ b/lib/egressproxy/service.go @@ -217,6 +217,26 @@ func compileHeaderInjectRules(cfgRules []HeaderInjectRuleConfig) ([]headerInject return out, nil } +// UpdateInstancePolicy updates the header injection rules for an already-registered instance. +// Returns false if the instance is not registered (no-op). +func (s *Service) UpdateInstancePolicy(instanceID string, rules []HeaderInjectRuleConfig) (bool, error) { + compiled, err := compileHeaderInjectRules(rules) + if err != nil { + return false, err + } + + s.mu.Lock() + defer s.mu.Unlock() + + sourceIP, ok := s.sourceIPByInstance[instanceID] + if !ok { + return false, nil + } + + s.policiesBySourceIP[sourceIP] = sourcePolicy{headerInjectRules: compiled} + return true, nil +} + func (s *Service) UnregisterInstance(_ context.Context, instanceID string) { s.mu.Lock() sourceIP, ok := s.sourceIPByInstance[instanceID] diff --git a/lib/egressproxy/update_policy_test.go b/lib/egressproxy/update_policy_test.go new file mode 100644 index 00000000..848c17ed --- /dev/null +++ b/lib/egressproxy/update_policy_test.go @@ -0,0 +1,82 @@ +package egressproxy + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func newTestService(t *testing.T) *Service { + t.Helper() + svc, err := NewService(t.TempDir(), 0) + require.NoError(t, err) + return svc +} + +func TestUpdateInstancePolicy_NotRegistered(t *testing.T) { + t.Parallel() + svc := newTestService(t) + + updated, err := svc.UpdateInstancePolicy("unknown-id", []HeaderInjectRuleConfig{ + {HeaderName: "Authorization", HeaderValue: "Bearer new-key", AllowedDomains: []string{"api.example.com"}}, + }) + require.NoError(t, err) + assert.False(t, updated, "should return false for unregistered instance") +} + +func TestUpdateInstancePolicy_UpdatesExisting(t *testing.T) { + t.Parallel() + svc := newTestService(t) + + // Manually register an instance (bypass RegisterInstance which needs networking) + svc.mu.Lock() + svc.sourceIPByInstance["inst-1"] = "10.0.0.2" + svc.policiesBySourceIP["10.0.0.2"] = sourcePolicy{ + headerInjectRules: []headerInjectRule{ + {headerName: "Authorization", headerValue: "Bearer old-key"}, + }, + } + svc.mu.Unlock() + + // Update the policy + updated, err := svc.UpdateInstancePolicy("inst-1", []HeaderInjectRuleConfig{ + {HeaderName: "Authorization", HeaderValue: "Bearer rotated-key", AllowedDomains: []string{"api.example.com"}}, + }) + require.NoError(t, err) + assert.True(t, updated) + + // Verify the policy was updated + svc.mu.Lock() + policy := svc.policiesBySourceIP["10.0.0.2"] + svc.mu.Unlock() + + require.Len(t, policy.headerInjectRules, 1) + assert.Equal(t, "Authorization", policy.headerInjectRules[0].headerName) + assert.Equal(t, "Bearer rotated-key", policy.headerInjectRules[0].headerValue) +} + +func TestUpdateInstancePolicy_ClearsRulesWithEmptySlice(t *testing.T) { + t.Parallel() + svc := newTestService(t) + + // Register instance with a rule + svc.mu.Lock() + svc.sourceIPByInstance["inst-2"] = "10.0.0.3" + svc.policiesBySourceIP["10.0.0.3"] = sourcePolicy{ + headerInjectRules: []headerInjectRule{ + {headerName: "Authorization", headerValue: "Bearer old-key"}, + }, + } + svc.mu.Unlock() + + // Update with empty rules + updated, err := svc.UpdateInstancePolicy("inst-2", nil) + require.NoError(t, err) + assert.True(t, updated) + + svc.mu.Lock() + policy := svc.policiesBySourceIP["10.0.0.3"] + svc.mu.Unlock() + assert.Empty(t, policy.headerInjectRules) +} diff --git a/lib/instances/credentials.go b/lib/instances/credentials.go new file mode 100644 index 00000000..9a56bfdf --- /dev/null +++ b/lib/instances/credentials.go @@ -0,0 +1,84 @@ +package instances + +import ( + "context" + "fmt" + + "github.com/kernel/hypeman/lib/logger" +) + +// updateInstanceCredentials replaces credential policies for an instance. +// It validates the new credentials, updates stored metadata, and refreshes the +// live egress proxy policy if the instance has one registered. +func (m *manager) updateInstanceCredentials(ctx context.Context, id string, credentials map[string]CredentialPolicy, env map[string]string) (*Instance, error) { + log := logger.FromContext(ctx) + + meta, err := m.loadMetadata(id) + if err != nil { + return nil, ErrNotFound + } + stored := &meta.StoredMetadata + + // Credentials require an active egress proxy policy + if len(credentials) > 0 { + if stored.NetworkEgress == nil || !stored.NetworkEgress.Enabled { + return nil, fmt.Errorf("%w: credentials require network.egress.enabled=true", ErrInvalidRequest) + } + } + + // Normalize and validate credential policies + normalized, err := normalizeCredentialPolicies(credentials) + if err != nil { + return nil, err + } + + // Merge supplied env values into the stored env for validation. + // We build a merged view: stored env overridden by supplied env. + mergedEnv := make(map[string]string, len(stored.Env)+len(env)) + for k, v := range stored.Env { + mergedEnv[k] = v + } + for k, v := range env { + mergedEnv[k] = v + } + + if len(normalized) > 0 { + if err := validateCredentialEnvBindings(normalized, mergedEnv); err != nil { + return nil, err + } + } + + // Update stored metadata + stored.Credentials = cloneCredentialPolicies(normalized) + // Persist new env values referenced by credentials + for k, v := range env { + stored.Env[k] = v + } + + // Update live egress proxy policy if one is registered + m.egressProxyMu.Lock() + svc := m.egressProxy + m.egressProxyMu.Unlock() + + if svc != nil { + rules := buildEgressProxyInjectRules(stored.NetworkEgress, stored.Credentials, stored.Env) + updated, err := svc.UpdateInstancePolicy(id, rules) + if err != nil { + log.ErrorContext(ctx, "failed to update egress proxy policy", "instance_id", id, "error", err) + return nil, fmt.Errorf("update egress proxy policy: %w", err) + } + if updated { + log.InfoContext(ctx, "updated egress proxy credential policy", "instance_id", id) + } + } + + // Save metadata + if err := m.saveMetadata(&metadata{StoredMetadata: *stored}); err != nil { + return nil, fmt.Errorf("save metadata: %w", err) + } + + log.InfoContext(ctx, "updated instance credentials", "instance_id", id, "credential_count", len(normalized)) + + inst := m.toInstanceWithoutHydration(ctx, &metadata{StoredMetadata: *stored}) + return &inst, nil +} diff --git a/lib/instances/manager.go b/lib/instances/manager.go index 8305a0c5..f32a1038 100644 --- a/lib/instances/manager.go +++ b/lib/instances/manager.go @@ -52,6 +52,10 @@ type Manager interface { // SetResourceValidator sets the validator for aggregate resource limit checking. // Called after initialization to avoid circular dependencies. SetResourceValidator(v ResourceValidator) + // UpdateInstanceCredentials replaces the credential policies for an instance. + // It updates stored metadata and the live egress proxy policy (if running). + // The env map provides real secret values referenced by credential sources. + UpdateInstanceCredentials(ctx context.Context, id string, credentials map[string]CredentialPolicy, env map[string]string) (*Instance, error) // GetVsockDialer returns a VsockDialer for the specified instance. GetVsockDialer(ctx context.Context, instanceID string) (hypervisor.VsockDialer, error) } @@ -441,6 +445,14 @@ func (m *manager) RotateLogs(ctx context.Context, maxBytes int64, maxFiles int) return lastErr } +// UpdateInstanceCredentials replaces the credential policies for an instance. +func (m *manager) UpdateInstanceCredentials(ctx context.Context, id string, credentials map[string]CredentialPolicy, env map[string]string) (*Instance, error) { + lock := m.getInstanceLock(id) + lock.Lock() + defer lock.Unlock() + return m.updateInstanceCredentials(ctx, id, credentials, env) +} + // 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/oapi/oapi.go b/lib/oapi/oapi.go index 161067ad..09ab8dc2 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -1039,6 +1039,17 @@ type SnapshotTargetState string // Tags User-defined key-value tags. type Tags map[string]string +// UpdateInstanceCredentialsRequest defines model for UpdateInstanceCredentialsRequest. +type UpdateInstanceCredentialsRequest struct { + // Credentials Full replacement of credential brokering policies keyed by logical name. + // Pass an empty object `{}` to remove all credentials. + Credentials map[string]CreateInstanceRequestCredential `json:"credentials"` + + // Env Environment variable values referenced by credential sources. + // These are merged into the instance's stored env (overwriting existing keys). + 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 +1275,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 = UpdateInstanceCredentialsRequest + // ForkInstanceJSONRequestBody defines body for ForkInstance for application/json ContentType. type ForkInstanceJSONRequestBody = ForkInstanceRequest @@ -1435,6 +1449,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 +1859,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 +3131,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 +4367,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 +5011,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 +5821,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 +7054,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 +8178,9 @@ type ServerInterface interface { // Get instance details // (GET /instances/{id}) GetInstance(w http.ResponseWriter, r *http.Request, id string) + // Update instance credentials (secret rotation) + // (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 +8391,12 @@ func (_ Unimplemented) GetInstance(w http.ResponseWriter, r *http.Request, id st w.WriteHeader(http.StatusNotImplemented) } +// Update instance credentials (secret rotation) +// (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 +9197,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 +10218,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 +11208,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 +12302,9 @@ type StrictServerInterface interface { // Get instance details // (GET /instances/{id}) GetInstance(ctx context.Context, request GetInstanceRequestObject) (GetInstanceResponseObject, error) + // Update instance credentials (secret rotation) + // (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 +13022,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 +13682,223 @@ 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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", + "91uf3n36/wEAAP//ri2vqaQxAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi.yaml b/openapi.yaml index 1f0b5e1a..e3b9ff95 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -203,6 +203,25 @@ components: $ref: "#/components/schemas/CreateInstanceRequestCredentialInject" minItems: 1 + UpdateInstanceCredentialsRequest: + type: object + required: [credentials] + properties: + credentials: + type: object + additionalProperties: + $ref: "#/components/schemas/CreateInstanceRequestCredential" + description: | + Full replacement of credential brokering policies keyed by logical name. + Pass an empty object `{}` to remove all credentials. + env: + type: object + additionalProperties: + type: string + description: | + Environment variable values referenced by credential sources. + These are merged into the instance's stored env (overwriting existing keys). + CreateInstanceRequest: type: object required: [name, image] @@ -2397,6 +2416,56 @@ paths: schema: $ref: "#/components/schemas/Error" + /instances/{id}/credentials: + put: + summary: Update instance credentials (secret rotation) + description: | + Replaces the instance's credential brokering policies. This is a full replacement — + the new credentials map completely replaces the old one. + If the instance has an active egress proxy, the proxy's header injection rules + are updated immediately. This does NOT touch the VM. + 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/UpdateInstanceCredentialsRequest" + responses: + 200: + description: Credentials updated + content: + application/json: + schema: + $ref: "#/components/schemas/Instance" + 400: + description: Invalid request (validation error) + 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