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