handler

package
v0.68.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package handler hosts the infra.admin handler library — the shared business logic dispatched by both the host-side infra.admin workflow module's HTTP routes (T15) and the wfctl `infra admin *` CLI subcommands (T19-T20). Functions are pure: they take their dependencies as parameters (state store, providers, catalog) and return typed adminpb outputs. The HTTP transport + audit logging happens at the module layer; the CLI transport happens at wfctl.

Design: docs/plans/2026-05-27-infra-admin-dynamic-design.md §Handler library Plan: docs/plans/2026-05-27-infra-admin-dynamic.md (Tasks 5 + 6)

Authz contract (this file): every typed input MUST carry an AdminAuthzEvidence whose authz_checked AND authz_allowed are both true. The host module attaches admin-auth middleware on every registered route; the middleware sets the evidence after running authz.Casbin (or whatever the configured authz module is). If the evidence is missing or either bit is false, the handler refuses the request via the Output.error field (NOT a Go-level error, so HTTP transport returns 200 OK with a typed payload that consumers must inspect for non-empty error per the proto tag-100 discriminator).

Default-deny semantics: handler refuses unless BOTH bits prove the host auth pipeline ran AND approved. A missing evidence means the caller bypassed admin auth middleware — likely a wiring bug — and must be refused for safety per design §Authz row.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyResource added in v0.68.1

func ApplyResource(
	ctx context.Context,
	store interfaces.IaCStateStore,
	providers map[string]interfaces.IaCProvider,
	authz Enforcer,
	subject string,
	cfg *config.WorkflowConfig,
	desiredSpecs []interfaces.ResourceSpec,
	in *adminpb.AdminApplyInput,
) (*adminpb.AdminApplyOutput, error)

ApplyResource implements the ApplyResource RPC.

Security gates (in order):

  1. authzError: default-deny if evidence is missing or unchecked.
  2. Server-side Enforcer: if authz is non-nil, call Enforce(subject,"infra:apply","allow") — the client-body evidence is NOT trusted for RBAC; it is audit-only.
  3. TOCTOU: re-plan and recompute desired_hash; reject if it diverges from in.DesiredHash. This prevents a stale plan from being applied after config changes.
  4. ValidateAllowReplaceProtected: reject replace/delete on resources marked protected: true unless in.AllowReplace lists them.

The providers map is keyed by module name; the first entry is used (single-provider model for v1.1). When no providers are registered, Output.error is set.

func DesiredHash added in v0.68.1

func DesiredHash(cfg *config.WorkflowConfig, desired []interfaces.ResourceSpec, current []interfaces.ResourceState) string

DesiredHash mirrors wfctlhelpers.DesiredStateHash but is defined here to avoid an import cycle (iac/wfctlhelpers → module → iac/admin/handler). Exported so iac/wfctlhelpers/desired_hash_test.go can assert both implementations produce identical digests for the same inputs, preventing silent copy-drift. cfg is reserved for future secret-resolution parity.

func DestroyResource added in v0.68.1

func DestroyResource(
	ctx context.Context,
	providers map[string]interfaces.IaCProvider,
	authz Enforcer,
	subject string,
	in *adminpb.AdminDestroyInput,
) (*adminpb.AdminDestroyOutput, error)

DestroyResource implements the DestroyResource RPC.

Security gates:

  1. authzError: default-deny if evidence missing/unchecked.
  2. Server-side Enforcer: Enforce(subject,"infra:destroy","allow").
  3. TOCTOU: confirm_hash must match a server-computed hash of the refs being destroyed. Clients compute this hash from the resource list they obtained from ListResources/GetResource and echo it here; a mismatch means the list changed between listing and destroying.

The destroy path calls provider.Destroy directly (no plan step — the caller supplies refs from the UI's resource list, which already came from the state store).

func DriftCheckResource added in v0.68.1

func DriftCheckResource(
	ctx context.Context,
	providers map[string]interfaces.IaCProvider,
	in *adminpb.AdminDriftInput,
) (*adminpb.AdminDriftOutput, error)

DriftCheckResource implements the DriftCheckResource RPC: calls provider.DetectDrift on the supplied resource refs and returns per-resource drift results.

The providers map is keyed by module name; the first entry is used (single-provider model for v1.1). When no providers are registered, Output.error is set so the client receives a typed diagnostic.

Evidence default-deny: if authzError is non-empty, the handler short-circuits with Output.error (HTTP stays 200; consumer sniffs tag-100).

func GenerateConfig

GenerateConfig implements InfraAdminService.GenerateConfig by type-coercing the form-builder's field_values map against the FieldSpecCatalog Kind dispatch, assembling a module config map, and yaml.Marshal-ing the result. Output is a single module entry (name + type + config) the user pastes under their existing `modules:` block. Per plan §Task 6.

**Strict-contract invariant**: never fmt.Sprintf user input into YAML. All values flow through yaml.Marshal of a typed struct or map. TestGenerateConfig_NoFmtSprintfUserInput pins this against regression.

Array encoding contract (cross-task, locked 2026-05-27): array_string + array_object field values arrive JSON-encoded (e.g. `field_values["ingress"] = "[\"rule a\", \"rule b, c\"]"`) so values containing commas survive the wire. Handler decodes via json.Unmarshal. Defensive fallback: a value that doesn't parse as a JSON array is wrapped into a one-element slice so a malformed UI submission doesn't crash the server. See TestGenerateConfig_ArrayValuesJSONDecoded + TestGenerateConfig_PlainStringNotJSONDecoded.

Per design §Authz: default-deny via the shared authz guard.

func GetResource

GetResource implements InfraAdminService.GetResource by reading the named ResourceState from the host's iac.state backend and projecting it into an AdminResourceDetail. AppliedConfig is JSON-encoded into applied_config_json verbatim; Outputs are MASKED via secrets.MaskSensitiveOutputs against secrets.DefaultSensitiveKeys() and then JSON-encoded into outputs_json. The masked-key names are surfaced in sensitive_outputs_redacted so the UI can render a "redacted" affordance per design §Secret redaction:

"GetResource.outputs_json redacts keys matching
 secrets.DefaultSensitiveKeys()."

Per design §Authz row: default-deny when evidence is missing or either authz_checked / authz_allowed is false; refusal surfaces via Output.error rather than a Go-level error.

Not-found surfaces via Output.error too — the design treats missing resources as a non-exceptional condition the UI must handle (e.g. a stale URL after a destroy).

func HashDestroyRefs added in v0.68.1

func HashDestroyRefs(refs []*adminpb.AdminResourceRef) string

HashDestroyRefs computes a deterministic SHA-256 hex digest of the refs being destroyed. Exported so UI and tests can compute the expected hash before calling DestroyResource. The hash is over the refs sorted by Name, serialised as JSON [{name,type},...].

func ListProviders

func ListProviders(
	ctx context.Context,
	providers map[string]interfaces.IaCProvider,
	providerTypeByModule map[string]string,
	fieldCat *catalog.FieldSpecCatalog,
	regionCat *catalog.RegionCatalog,
	engineCat *catalog.EngineCatalog,
	in *adminpb.AdminListProvidersInput,
) (*adminpb.AdminListProvidersOutput, error)

ListProviders implements InfraAdminService.ListProviders by walking the provided `providers` map (keyed by host YAML module name) and emitting one AdminProviderSummary per entry. The summary carries the YAML-config provider_type string from the caller-supplied providerTypeByModule map (NOT provider.Name() — see invariant below), the catalogued region + engine lists for that provider type, and the full catalog type list as supported_types.

**provider_type MUST come from the YAML config string, not provider.Name()** — per spec-reviewer T6 F1 (commit 1ea231fdd) + design cycle-5/6 backports:

  • interfaces.IaCProvider.Name() returns the plugin's DISPLAY name (e.g. "DigitalOcean Provider"). This is operator-facing decoration, not a stable identifier.
  • The YAML-config provider: field (e.g. "digitalocean") is the stable identifier the region + engine catalogs key against.
  • The host module (T15) reads each iac.provider module's config at Init time and populates providerTypeByModule keyed by module-name → provider-type-string.
  • If providerTypeByModule[modName] is missing (e.g. a stale module loaded without re-Init), provider_type stays empty and SupportedRegions + SupportedEngines come back empty — UI degrades gracefully rather than rendering wrong dropdowns.

Signature deviation from design §Handler library (informational — not blocking): the design listed

ListProviders(ctx, providers, regionCat, in)

The proto's AdminProviderSummary requires supported_engines + supported_types (so fieldCat + engineCat are needed) AND the F1 fix requires providerTypeByModule. Final shape is 7 params; design line 233 was underspecified.

regions_source is the literal "local-catalog" per design §FieldSpec Catalog so consumers can distinguish v1's local lookup from a future v1.1 IaCProviderRegionLister gRPC service.

Per design §Authz: default-deny via the shared authz guard.

func ListResourceTypes

ListResourceTypes implements InfraAdminService.ListResourceTypes by walking the FieldSpecCatalog and emitting one AdminResourceTypeMetadata per registered type. Each metadata entry carries the catalog's full FieldSpec list so the new-resource form-builder UI can render the right inputs without an extra RPC per type. Per plan §Task 6 + design §Handler library.

The providers parameter is reserved for symmetry with the other handler functions; v1 of this endpoint does not filter types by live providers (the FieldSpecCatalog is the authoritative type list and assumes every registered type is supportable by every known provider — see TestListProviders_PopulatesRegionsAndEngines AndTypes for the cross-task assumption).

Per design §Authz: default-deny via the shared authz guard.

func ListResources

ListResources implements InfraAdminService.ListResources by reading every ResourceState from the host's iac.state backend, applying the type / provider / app-context filters from the input, and returning AdminResourceSummary rows (no per-resource secrets — outputs are intentionally absent from the list view; the detail view uses GetResource).

Per design §Handler library + plan §Task 5: the function takes `providers` + `catalog` as parameters so the host module and CLI callers can share one dispatch. v1 of this list endpoint does not consult providers or catalog directly — every populated field on AdminResourceSummary derives from the ResourceState itself — but the parameters are preserved in the signature for symmetry with ListResourceTypes / GenerateConfig and to keep the dispatch shape stable when T6 / future enhancements need to cross-check against the live providers map (e.g. dropping resources whose state.ProviderRef no longer matches a registered module).

Per design §Authz row: default-deny when evidence is missing or either authz_checked / authz_allowed is false; refusal surfaces via Output.error rather than a Go-level error so the HTTP transport returns 200 OK with the typed payload (consumers sniff for non-empty error per the proto tag-100 discriminator).

func PlanResource added in v0.68.1

PlanResource implements the PlanResource RPC: plans the in-process desired specs against the current state, returning the plan actions and a desired_hash for TOCTOU protection.

Signature deviates from the v1 (ctx, state, providers, fieldCatalog, in) shape by adding cfg + desiredSpecs (plan-review I-2): the handler needs cfg to compute DesiredStateHash correctly and desiredSpecs to scope the plan without coupling the handler to the module's config loading.

The providers map is keyed by module name; the first entry is used for planning (single-provider per-route model for v1.1).

Evidence default-deny: if authzError is non-empty, the handler short-circuits with Output.error (HTTP stays 200; consumer sniffs tag-100).

Types

type Enforcer added in v0.68.1

type Enforcer interface {
	Enforce(sub, obj, act string, extra ...string) (bool, error)
}

Enforcer is the server-side RBAC interface that the authz.casbin module implements. The variadic extra ...string matches the concrete Casbin wrapper's method signature (plan-review C-NEW-1). See module.Enforcer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL