authwebhook

package
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Apr 12, 2026 License: Apache-2.0 Imports: 33 Imported by: 0

Documentation

Index

Constants

View Source
const (
	EventTypeBlockCleared    = "workflowexecution.block.cleared"
	EventTypeTimeoutModified = "webhook.remediationrequest.timeout_modified"
	EventTypeRARDecided      = "webhook.remediationapprovalrequest.decided"
	EventTypeNotifCancelled  = "webhook.notification.cancelled"

	// ADR-058: RemediationWorkflow CRD admission event types
	EventTypeRWAdmittedCreate = "remediationworkflow.admitted.create"
	EventTypeRWAdmittedDelete = "remediationworkflow.admitted.delete"
	EventTypeRWAdmittedDenied = "remediationworkflow.admitted.denied"

	// ADR-059: ActionType CRD admission event types (BR-WORKFLOW-007)
	EventTypeATAdmittedCreate = "actiontype.admitted.create"
	EventTypeATAdmittedUpdate = "actiontype.admitted.update"
	EventTypeATAdmittedDelete = "actiontype.admitted.delete"
	EventTypeATDeniedCreate   = "actiontype.denied.create"
	EventTypeATDeniedUpdate   = "actiontype.denied.update"
	EventTypeATDeniedDelete   = "actiontype.denied.delete"
)

Webhook event type constants (L-3 SOC2 Fix: compile-time safety for event type strings)

View Source
const (
	EventCategoryWebhook    = "webhook"
	EventCategoryActionType = "actiontype"
)

Event category constants per ADR-034 v1.8: event_category = business domain

View Source
const (
	// MaxReasonWords defines maximum word count for audit justifications
	// SOC2 CC7.4: Prevent excessively verbose justifications that reduce audit readability
	// Test Plan Reference: AUTH-007, AUTH-008
	MaxReasonWords = 100
)
View Source
const (
	// RWFinalizerName ensures DS catalog consistency before RW deletion.
	// Issue #418: fire-and-forget goroutines in the webhook cannot guarantee
	// that the DS disable + AT count refresh complete before the CRD is removed.
	RWFinalizerName = "remediationworkflow.kubernaut.ai/catalog-cleanup"
)

Variables

This section is empty.

Functions

func BuildRARApprovalAuditEvent

func BuildRARApprovalAuditEvent(
	rar *remediationv1.RemediationApprovalRequest,
	authenticatedUser string,
	parentRRName string,
) (*api.AuditEventRequest, error)

BuildRARApprovalAuditEvent is a convenience function that builds the complete audit event including correlation ID, namespace, actor, and resource.

This encapsulates the complete audit event construction pattern for RAR approvals.

func BuildRARApprovalAuditPayload

BuildRARApprovalAuditPayload constructs a structured audit payload for RAR approval decisions.

Per DD-WEBHOOK-003: Business context ONLY (attribution in structured columns). Per DD-AUDIT-004: Zero unstructured data in audit events.

Note: Uses toRemediationApprovalAuditPayloadDecision from audit_helpers.go

func DetectAndLogForgeryAttempt

func DetectAndLogForgeryAttempt(logger logr.Logger, userProvidedDecidedBy, authenticatedUser string) bool

DetectAndLogForgeryAttempt checks if a user-provided DecidedBy field exists and logs a security warning if identity forgery is attempted.

Per BR-AUTH-001, SOC 2 CC8.1: User attribution MUST be tamper-proof. This function provides forensic evidence of forgery attempts.

Returns true if forgery was detected (user provided DecidedBy), false otherwise.

func ExtractDeliveryChannels

func ExtractDeliveryChannels(attempts []notificationv1.DeliveryAttempt) []string

ExtractDeliveryChannels returns a sorted, deduplicated list of channel names from the given delivery attempts. Returns nil if attempts is nil or empty.

func RetryGetCRD

func RetryGetCRD(ctx context.Context, k8sClient client.Client, key types.NamespacedName, obj client.Object, maxRetries int) error

RetryGetCRD fetches a CRD object with exponential backoff. After an admission response, the CRD may not yet be committed to etcd by the API server; this helper retries up to maxRetries times (500ms, 1s, 2s, 4s...) to handle that race.

func ValidateApprovalDecision

func ValidateApprovalDecision(decision remediationv1.ApprovalDecision) error

ValidateApprovalDecision validates that a decision is one of the allowed enum values. Returns an error if the decision is invalid.

func ValidateReason

func ValidateReason(reason string, minWords int) error

ValidateReason validates clearance/approval reason has sufficient detail minWords specifies minimum word count required for audit trail

BR-WEBHOOK-001: Reasons must be sufficiently detailed for SOC2 audit trail Returns error if reason is empty, only whitespace, has fewer than minWords, or exceeds MaxReasonWords

Test Plan Reference: AUTH-005 (accept valid), AUTH-006 (reject empty), AUTH-007 (reject too long), AUTH-008 (accept at max), AUTH-013-016 (edge cases)

func ValidateTimestamp

func ValidateTimestamp(ts time.Time) error

ValidateTimestamp validates request timestamp is not in future and not older than 5 minutes (replay attack prevention)

BR-WEBHOOK-001: Timestamp validation prevents replay attacks and ensures timely actions Returns error if timestamp is zero, in future, or older than 5 minutes

func WrapRARApprovalPayloadWithDiscriminator

func WrapRARApprovalPayloadWithDiscriminator(payload api.RemediationApprovalAuditPayload) api.AuditEventRequestEventData

WrapRARApprovalPayloadWithDiscriminator wraps the audit payload with the correct discriminator.

Per DD-AUDIT-002 V2.0: OpenAPI oneOf discriminators require specific wrapper types.

Types

type ActionTypeCatalogClient

type ActionTypeCatalogClient interface {
	CreateActionType(ctx context.Context, name string, description ogenclient.ActionTypeDescription, registeredBy string) (*ActionTypeRegistrationResult, error)
	UpdateActionType(ctx context.Context, name string, description ogenclient.ActionTypeDescription, updatedBy string) (*ActionTypeUpdateResult, error)
	DisableActionType(ctx context.Context, name string, disabledBy string) (*ActionTypeDisableResult, error)
	// ForceDisableActionType disables the named orphaned workflows and then
	// attempts to disable the action type. Issue #512: orphan recovery.
	ForceDisableActionType(ctx context.Context, name string, disabledBy string, orphanedWorkflows []string) (*ActionTypeDisableResult, error)
}

ActionTypeCatalogClient defines the DS REST API operations required by the AW handler to manage action types on behalf of CRD lifecycle events. BR-WORKFLOW-007: ActionType CRD lifecycle management via AW bridge.

type ActionTypeDSClient added in v1.2.0

type ActionTypeDSClient interface {
	CreateActionType(ctx context.Context, name string, description ogenclient.ActionTypeDescription, registeredBy string) (*ActionTypeRegistrationResult, error)
}

ActionTypeDSClient abstracts the DS action type operations needed by the startup reconciler.

type ActionTypeDisableResult

type ActionTypeDisableResult struct {
	Disabled               bool
	DependentWorkflowCount int
	DependentWorkflows     []string
}

ActionTypeDisableResult holds the DS response when disabling an action type.

type ActionTypeHandler

type ActionTypeHandler struct {
	// contains filtered or unexported fields
}

ActionTypeHandler handles admission requests for ActionType CRD CREATE, UPDATE, and DELETE operations, bridging CRD lifecycle with the DS catalog. BR-WORKFLOW-007, DD-ACTIONTYPE-001, ADR-059.

func NewActionTypeHandler

func NewActionTypeHandler(
	dsClient ActionTypeCatalogClient,
	auditStore audit.AuditStore,
	k8sClient client.Client,
) *ActionTypeHandler

NewActionTypeHandler creates a handler for ActionType admission.

func (*ActionTypeHandler) Handle

Handle processes admission requests for ActionType CRD. Intercepts CREATE, UPDATE, and DELETE per BR-WORKFLOW-007.

type ActionTypeRegistrationResult

type ActionTypeRegistrationResult struct {
	ActionType   string
	Status       string // "created", "exists", "reenabled"
	WasReenabled bool
}

ActionTypeRegistrationResult holds the DS response after registering or re-enabling an action type.

type ActionTypeUpdateResult

type ActionTypeUpdateResult struct {
	ActionType    string
	UpdatedFields []string
}

ActionTypeUpdateResult holds the DS response after updating an action type description.

type ActionTypeWorkflowCounter

type ActionTypeWorkflowCounter interface {
	GetActiveWorkflowCount(ctx context.Context, actionType string) (int, error)
}

ActionTypeWorkflowCounter retrieves the authoritative active workflow count from DS. Used for best-effort cross-update of ActionType CRD status.activeWorkflowCount after RW CREATE/DELETE (Phase 3c, BR-WORKFLOW-007).

type AuthContext

type AuthContext struct {
	Username string
	UID      string
	Groups   []string
	Extra    map[string]authenticationv1.ExtraValue
}

AuthContext holds authenticated user information extracted from admission requests. This struct is used for SOC2 CC8.1 operator attribution and audit trail persistence. Test Plan Reference: AUTH-001 to AUTH-012

func (*AuthContext) String

func (a *AuthContext) String() string

String returns formatted authentication string for audit trails Format: "username (UID: uid)"

type Authenticator

type Authenticator struct{}

Authenticator extracts authenticated user identity from Kubernetes admission requests BR-WEBHOOK-001: SOC2 CC8.1 Attribution - captures WHO performed operator actions

func NewAuthenticator

func NewAuthenticator() *Authenticator

NewAuthenticator creates a new authenticator instance

func (*Authenticator) ExtractUser

ExtractUser extracts authenticated user information from admission request Returns error if user info is missing or invalid

BR-WEBHOOK-001: Extract authenticated user identity from K8s admission request SOC2 CC8.1: Attribution requirement - must capture WHO performed the action

type DSClientAdapter

type DSClientAdapter struct {
	// contains filtered or unexported fields
}

DSClientAdapter wraps the ogen-generated Data Storage client to implement WorkflowCatalogClient. This adapter translates between the admission handler's domain interface and the OpenAPI-generated HTTP client.

ADR-058: Used by RemediationWorkflowHandler to register/disable workflows in DS.

func NewDSClientAdapter

func NewDSClientAdapter(baseURL string, timeout time.Duration, logger logr.Logger) (*DSClientAdapter, error)

NewDSClientAdapter creates a DSClientAdapter from a Data Storage service URL.

func NewDSClientAdapterFromClient

func NewDSClientAdapterFromClient(client *ogenclient.Client, logger logr.Logger) *DSClientAdapter

NewDSClientAdapterFromClient wraps an existing ogen client as a DSClientAdapter. Use when the ogen client is shared across multiple adapters (e.g., audit + workflow).

func (*DSClientAdapter) CreateActionType

func (a *DSClientAdapter) CreateActionType(ctx context.Context, name string, description ogenclient.ActionTypeDescription, registeredBy string) (*ActionTypeRegistrationResult, error)

func (*DSClientAdapter) CreateWorkflowInline

func (a *DSClientAdapter) CreateWorkflowInline(ctx context.Context, content, source, registeredBy string) (*WorkflowRegistrationResult, error)

func (*DSClientAdapter) DisableActionType

func (a *DSClientAdapter) DisableActionType(ctx context.Context, name string, disabledBy string) (*ActionTypeDisableResult, error)

func (*DSClientAdapter) DisableWorkflow

func (a *DSClientAdapter) DisableWorkflow(ctx context.Context, workflowID, reason, updatedBy string) error

func (*DSClientAdapter) ForceDisableActionType added in v1.2.0

func (a *DSClientAdapter) ForceDisableActionType(ctx context.Context, name string, disabledBy string, orphanedWorkflows []string) (*ActionTypeDisableResult, error)

ForceDisableActionType sends a force-disable request to DS, disabling the specified orphaned workflows before attempting to disable the action type. Issue #512: raw HTTP call because the ogen client doesn't support the force fields.

func (*DSClientAdapter) GetActiveWorkflowCount

func (a *DSClientAdapter) GetActiveWorkflowCount(ctx context.Context, actionType string) (int, error)

GetActiveWorkflowCount returns the number of active workflows referencing the given action type. Used by the RW handler for best-effort cross-update of ActionType CRD status.activeWorkflowCount.

func (*DSClientAdapter) UpdateActionType

func (a *DSClientAdapter) UpdateActionType(ctx context.Context, name string, description ogenclient.ActionTypeDescription, updatedBy string) (*ActionTypeUpdateResult, error)

type NotificationRequestDeleteHandler

type NotificationRequestDeleteHandler struct {
	// contains filtered or unexported fields
}

NotificationRequestDeleteHandler handles authentication for NotificationRequest cancellation via DELETE BR-AUTH-001: SOC2 CC8.1 Operator Attribution DD-NOT-005: Immutable Spec - Cancellation via DELETE Operation DD-WEBHOOK-003: Webhook-Complete Audit Pattern

This webhook intercepts NotificationRequest DELETE operations and: 1. Extracts authenticated user from admission request 2. Writes complete deletion audit event (WHO + WHAT + ACTION) 3. Allows DELETE to proceed

Note: Kubernetes API prevents mutating objects during DELETE, so attribution is captured via audit trail rather than CRD annotations/status.

func NewNotificationRequestDeleteHandler

func NewNotificationRequestDeleteHandler(auditStore audit.AuditStore) *NotificationRequestDeleteHandler

NewNotificationRequestDeleteHandler creates a new NotificationRequest DELETE authentication handler

func (*NotificationRequestDeleteHandler) Handle

Handle processes the admission request for NotificationRequest DELETE Implements admission.Handler interface from controller-runtime

func (*NotificationRequestDeleteHandler) InjectDecoder

InjectDecoder injects the decoder into the handler Required by controller-runtime admission webhook framework

type NotificationRequestValidator

type NotificationRequestValidator struct {
	// contains filtered or unexported fields
}

NotificationRequestValidator validates NotificationRequest CRDs BR-AUTH-001: SOC2 CC8.1 Operator Attribution DD-NOT-005: Immutable Spec - Cancellation via DELETE Operation DD-WEBHOOK-003: Webhook-Complete Audit Pattern

This validator implements webhook.CustomValidator interface for Kubebuilder-style webhooks. It intercepts NotificationRequest DELETE operations and: 1. Extracts authenticated user from admission request context 2. Writes complete deletion audit event (WHO + WHAT + ACTION) 3. Allows DELETE to proceed (returns nil error)

Reference: https://book.kubebuilder.io/cronjob-tutorial/webhook-implementation Reference: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/

func NewNotificationRequestValidator

func NewNotificationRequestValidator(auditStore audit.AuditStore) *NotificationRequestValidator

NewNotificationRequestValidator creates a new NotificationRequest validator

func (*NotificationRequestValidator) ValidateCreate

ValidateCreate implements webhook.CustomValidator NotificationRequest doesn't require validation on CREATE

func (*NotificationRequestValidator) ValidateDelete

ValidateDelete implements webhook.CustomValidator Captures operator attribution for DELETE operations via audit trail

This method is invoked by envtest/K8s API server for DELETE admission requests. Per Kubebuilder pattern, returning nil allows the DELETE to proceed.

Reference: https://book.kubebuilder.io/cronjob-tutorial/webhook-implementation

func (*NotificationRequestValidator) ValidateUpdate

func (v *NotificationRequestValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error)

ValidateUpdate implements webhook.CustomValidator NotificationRequest doesn't require validation on UPDATE

type RWHandlerOption

type RWHandlerOption func(*RemediationWorkflowHandler)

RWHandlerOption configures optional dependencies on RemediationWorkflowHandler.

func WithActionTypeWorkflowCounter

func WithActionTypeWorkflowCounter(counter ActionTypeWorkflowCounter) RWHandlerOption

WithActionTypeWorkflowCounter enables best-effort cross-update of ActionType CRD status.activeWorkflowCount after RW CREATE/DELETE (Phase 3c, BR-WORKFLOW-007).

type RemediationApprovalRequestAuthHandler

type RemediationApprovalRequestAuthHandler struct {
	// contains filtered or unexported fields
}

RemediationApprovalRequestAuthHandler handles authentication for RemediationApprovalRequest decisions BR-AUTH-001: SOC2 CC8.1 Operator Attribution ADR-040: RemediationApprovalRequest CRD Architecture DD-WEBHOOK-003: Webhook-Complete Audit Pattern

This mutating webhook intercepts RemediationApprovalRequest status updates and: 1. Populates status.DecidedBy (operator email/username) 2. Populates status.DecidedAt (timestamp) 3. Writes complete audit event (WHO + WHAT + ACTION)

func NewRemediationApprovalRequestAuthHandler

func NewRemediationApprovalRequestAuthHandler(auditStore audit.AuditStore) *RemediationApprovalRequestAuthHandler

NewRemediationApprovalRequestAuthHandler creates a new RemediationApprovalRequest authentication handler

func (*RemediationApprovalRequestAuthHandler) Handle

Handle processes the admission request for RemediationApprovalRequest Implements admission.Handler interface from controller-runtime

func (*RemediationApprovalRequestAuthHandler) InjectDecoder

InjectDecoder injects the decoder into the handler Required by controller-runtime admission webhook framework

type RemediationRequestStatusHandler

type RemediationRequestStatusHandler struct {
	// contains filtered or unexported fields
}

RemediationRequestStatusHandler handles status updates to RemediationRequest CRDs BR-AUTH-001: SOC2 CC8.1 Operator Attribution (Gap #8) BR-AUDIT-005 v2.0: Gap #8 - TimeoutConfig mutation audit capture ADR-034 v1.5: webhook.remediationrequest.timeout_modified event

This mutating webhook intercepts RemediationRequest status updates and: 1. Detects TimeoutConfig changes (old vs new) 2. Populates status.LastModifiedBy (operator email/username) 3. Populates status.LastModifiedAt (timestamp) 4. Writes complete audit event (WHO + WHAT + WHEN + OLD + NEW)

Per Gap #8: Operators can adjust TimeoutConfig mid-remediation via kubectl edit. This webhook ensures all mutations are audited for SOC2 compliance.

func NewRemediationRequestStatusHandler

func NewRemediationRequestStatusHandler(auditStore audit.AuditStore) *RemediationRequestStatusHandler

NewRemediationRequestStatusHandler creates a new RemediationRequest status handler

func (*RemediationRequestStatusHandler) Handle

Handle processes the admission request for RemediationRequest status updates Implements admission.Handler interface from controller-runtime

func (*RemediationRequestStatusHandler) InjectDecoder

InjectDecoder injects the decoder into the handler Required by controller-runtime admission webhook framework

type RemediationWorkflowHandler

type RemediationWorkflowHandler struct {
	// contains filtered or unexported fields
}

RemediationWorkflowHandler handles admission requests for RemediationWorkflow CRD CREATE and DELETE operations, bridging CRD lifecycle with the DS workflow catalog. BR-WORKFLOW-006, DD-WEBHOOK-003, ADR-058.

CREATE: Extracts CRD spec, POSTs inline schema to DS, updates CRD .status asynchronously via k8sClient.Status().Update(). DELETE: Extracts workflowId from status, PATCHes DS to disable.

func NewRemediationWorkflowHandler

func NewRemediationWorkflowHandler(
	dsClient WorkflowCatalogClient,
	auditStore audit.AuditStore,
	k8sClient client.Client,
	opts ...RWHandlerOption,
) *RemediationWorkflowHandler

NewRemediationWorkflowHandler creates a handler for RemediationWorkflow admission.

func (*RemediationWorkflowHandler) Handle

Handle processes admission requests for RemediationWorkflow CRD. Implements admission.Handler from controller-runtime.

ADR-058: ValidatingWebhookConfiguration intercepts CREATE, UPDATE, and DELETE. Issue #371: UPDATE now forwards CRD spec changes to DS so that version upgrades supersede the old active catalog entry.

type RemediationWorkflowReconciler added in v1.1.0

type RemediationWorkflowReconciler struct {
	client.Client
	Log       logr.Logger
	DSClient  WorkflowCatalogClient
	ATCounter ActionTypeWorkflowCounter
}

RemediationWorkflowReconciler ensures DS catalog consistency for RW CRD lifecycle events. It adds a finalizer on creation and, during deletion, guarantees the workflow is disabled in DS and the parent ActionType's activeWorkflowCount is refreshed before the CRD is removed from etcd.

Issue #418, BR-WORKFLOW-006, BR-WORKFLOW-007.

func (*RemediationWorkflowReconciler) Reconcile added in v1.1.0

func (*RemediationWorkflowReconciler) SetupWithManager added in v1.1.0

func (r *RemediationWorkflowReconciler) SetupWithManager(mgr ctrl.Manager) error

type StartupReconciler added in v1.2.0

type StartupReconciler struct {
	K8sClient      client.Client
	DSWorkflow     WorkflowDSClient
	DSActionType   ActionTypeDSClient
	Logger         logr.Logger
	Timeout        time.Duration
	InitialBackoff time.Duration
}

StartupReconciler lists all ActionType and RemediationWorkflow CRDs at startup and syncs them with DataStorage. It implements manager.Runnable so that the controller manager blocks readiness until the reconciliation completes.

Issue #548: Ensures PVC-wipe resilience by re-registering CRDs on startup. Ordering: ActionType CRDs are synced first, then RemediationWorkflow CRDs, because workflows reference action types.

func (*StartupReconciler) NeedLeaderElection added in v1.2.0

func (r *StartupReconciler) NeedLeaderElection() bool

NeedLeaderElection returns false so the reconciler runs on every replica, ensuring every authwebhook instance has a consistent view.

func (*StartupReconciler) Start added in v1.2.0

func (r *StartupReconciler) Start(ctx context.Context) error

Start performs the full startup reconciliation: list CRDs, sync with DS, update CRD statuses. Returns error on failure (fail-closed).

type WebhookAuditOpts

type WebhookAuditOpts struct {
	EventType    string
	Category     string
	Action       string
	Outcome      api.AuditEventRequestEventOutcome
	ResourceKind string
	ResourceID   string
	LoggerName   string
}

WebhookAuditOpts holds the parameters for constructing a webhook audit event envelope. The envelope fields are identical across all AW handlers; only the payload differs.

type WorkflowCatalogClient

type WorkflowCatalogClient interface {
	CreateWorkflowInline(ctx context.Context, content, source, registeredBy string) (*WorkflowRegistrationResult, error)
	DisableWorkflow(ctx context.Context, workflowID, reason, updatedBy string) error
}

WorkflowCatalogClient defines the DS REST API operations required by the AW handler to register and manage workflows on behalf of CRD lifecycle events. BR-WORKFLOW-006: Kubernetes-native workflow registration via CRD + AW bridge.

type WorkflowDSClient added in v1.2.0

type WorkflowDSClient interface {
	CreateWorkflowInline(ctx context.Context, content, source, registeredBy string) (*WorkflowRegistrationResult, error)
}

WorkflowDSClient abstracts the DS workflow operations needed by the startup reconciler.

type WorkflowExecutionAuthHandler

type WorkflowExecutionAuthHandler struct {
	// contains filtered or unexported fields
}

WorkflowExecutionAuthHandler handles authentication for WorkflowExecution block clearance BR-WE-013: Audit-Tracked Execution Block Clearing BR-AUTH-001: SOC2 CC8.1 Operator Attribution DD-WEBHOOK-003: Webhook-Complete Audit Pattern

This mutating webhook intercepts WorkflowExecution status updates and: 1. Populates status.BlockClearance.ClearedBy (operator email/username) 2. Populates status.BlockClearance.ClearedAt (timestamp) 3. Writes complete audit event (WHO + WHAT + ACTION)

func NewWorkflowExecutionAuthHandler

func NewWorkflowExecutionAuthHandler(auditStore audit.AuditStore) *WorkflowExecutionAuthHandler

NewWorkflowExecutionAuthHandler creates a new WorkflowExecution authentication handler

func (*WorkflowExecutionAuthHandler) Handle

Handle processes the admission request for WorkflowExecution Implements admission.Handler interface from controller-runtime

func (*WorkflowExecutionAuthHandler) InjectDecoder

InjectDecoder injects the decoder into the handler Required by controller-runtime admission webhook framework

type WorkflowRegistrationResult

type WorkflowRegistrationResult struct {
	WorkflowID        string
	WorkflowName      string
	Version           string
	Status            string
	PreviouslyExisted bool
	Superseded        bool   // true when an active workflow was superseded by a new spec (different ContentHash)
	SupersededID      string // UUID of the workflow that was superseded (for audit trail)
}

WorkflowRegistrationResult holds the DS response after registering or re-enabling a workflow.

Directories

Path Synopsis
Package config provides configuration types for the AuthWebhook admission controller.
Package config provides configuration types for the AuthWebhook admission controller.

Jump to

Keyboard shortcuts

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