Access policies in Aembit define the conditions under which a client workload is authorized to obtain credentials for a server workload. This post covers the policy model, the condition types available, how policies are evaluated, common patterns for real access control problems, and how to test policies before deploying them to production.
This is written for platform engineers configuring Aembit for their environment, not for evaluating whether to use it. If you're here, you've already set up the basic plumbing and are trying to understand how to write policies that actually reflect your intended access control model.
The Policy Model
An Aembit policy has four components:
- Client workload selector: Which workloads this policy applies to. Matched against the SPIFFE ID of the requesting workload, which can be an exact match or a path-prefix match.
- Server workload: Which server workload the policy governs access to. A server workload in Aembit corresponds to a credential definition — the thing that gets injected into the client's request.
- Conditions: Zero or more conditions that must all be true for the policy to allow access. Conditions evaluate properties of the requesting workload's attestation context.
- Credential policy: What credential to issue (the server workload's credential definition) and under what constraints (TTL override, scope restriction).
Policies are evaluated per-request: every time a client workload requests credentials, Aembit evaluates all policies that match the client's SPIFFE ID and the requested server workload. If any matching policy has all conditions satisfied, the request is allowed and credentials are issued. If no matching policy allows the request, it's denied.
This is a default-deny model. A workload with no matching policy for a server workload gets no credentials. You're explicitly granting access, not trying to prevent it after the fact.
Workload Selector Syntax
Client workload selectors match against SPIFFE IDs. The SPIFFE ID for a Kubernetes workload follows the pattern:
spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>
Selectors can be exact or prefix-based:
client_workload_selector:
spiffe_id_prefix: "spiffe://prod.cluster/ns/payments/"
This matches all service accounts in the payments namespace. Useful for namespace-level policies where you want all services in a namespace to have the same access to a shared credential (a shared external API key for example). For finer-grained control:
client_workload_selector:
spiffe_id: "spiffe://prod.cluster/ns/payments/sa/payment-processor"
Exact match on a specific service account. Use this for any credential that shouldn't be accessible by all services in a namespace.
Multiple selectors in a policy form a union — any matching selector grants consideration for the policy. This is useful when the same credential should be accessible by a specific service in multiple namespaces (staging and production):
client_workload_selectors:
- spiffe_id: "spiffe://prod.cluster/ns/payments/sa/payment-processor"
- spiffe_id: "spiffe://staging.cluster/ns/payments/sa/payment-processor"
Condition Types
Conditions add access control logic beyond "which workload is asking." They evaluate properties of the workload's attestation context — facts about the workload that are verifiable from the platform identity token.
Time-window conditions
Restrict access to specific time windows. Useful for maintenance windows, scheduled jobs that should only run at certain times, or temporary access grants:
conditions:
- type: time_window
time_zone: "America/New_York"
days_of_week: [MON, TUE, WED, THU, FRI]
start_time: "09:00"
end_time: "18:00"
A workload that requests credentials outside this window gets a policy denial. Useful for restricting a data export job to business hours, or ensuring that an analytics pipeline only runs during an allowed maintenance window.
Workload label conditions
Kubernetes workloads have labels. Conditions can require specific labels to be present in the workload's attestation context:
conditions:
- type: workload_label
key: "environment"
value: "production"
This requires the Kubernetes pod to have the label environment=production. Labels are attested through the Kubernetes API at the time the SPIFFE token is issued — a workload can't claim a label it doesn't have, because the label is part of the Kubernetes object definition that the SPIFFE issuer reads.
This condition type is particularly useful for distinguishing production from non-production access. A production-only database password can have a condition that the client workload must carry the environment=production label — staging deployments, even if they use the same service account, won't be able to obtain production credentials.
IP range conditions
Restrict access to requests originating from specific IP ranges. Useful for credentials that should only be accessible from specific network segments:
conditions:
- type: source_ip_range
cidr_blocks:
- "10.0.4.0/24"
- "10.0.5.0/24"
This evaluates the source IP of the credential request, which is the IP address of the Aembit client agent making the request. In a Kubernetes environment, this is typically the pod IP. Combine with network policies for defense in depth.
Trust level conditions
Aembit supports a trust level concept that allows you to define differentiated access based on the attestation strength of the workload's identity. A workload that was attested through a hardware-backed mechanism (a TPM-attested instance, a confidential computing environment) can be assigned a higher trust level than a workload attested only through platform metadata:
conditions:
- type: minimum_trust_level
level: "hardware_attested"
In practice, hardware attestation is most relevant for VM-based workloads with TPM support, not Kubernetes pods. But the condition type exists for environments where this distinction matters — for example, a credential that should only be accessible from confidential computing VMs processing particularly sensitive data.
Common Policy Patterns
Pattern 1: Environment-scoped credential access
You have a production database password and a staging database password. The production credential should be inaccessible to staging workloads even if a misconfigured deployment sends staging traffic to a production Kubernetes namespace.
name: "payments-prod-db-access"
client_workload_selectors:
- spiffe_id_prefix: "spiffe://prod.cluster/ns/payments/"
server_workload: "postgres-payments-prod"
conditions:
- type: workload_label
key: "environment"
value: "production"
- type: workload_label
key: "team"
value: "payments"
The double-condition here requires both the environment=production label and the team=payments label. The namespace prefix selector ensures only workloads in the payments namespace are even considered for this policy. A workload in the wrong namespace, or a workload without the correct labels, gets nothing.
Pattern 2: Scheduled job with time-window restriction
A nightly data export job needs access to an external API. You don't want that credential available to ad hoc job invocations during business hours.
name: "export-job-external-api"
client_workload_selectors:
- spiffe_id: "spiffe://prod.cluster/ns/data-pipeline/sa/nightly-export"
server_workload: "external-analytics-api"
conditions:
- type: time_window
time_zone: "UTC"
days_of_week: [MON, TUE, WED, THU, FRI, SAT, SUN]
start_time: "00:00"
end_time: "06:00"
If someone manually triggers this job outside the 00:00-06:00 UTC window, the credential request is denied. The job fails loudly rather than silently proceeding with broader-than-intended access.
Pattern 3: Progressive trust for sensitive credentials
A secrets manager access credential should require both network-level restriction and environment labeling:
name: "vault-production-access"
client_workload_selectors:
- spiffe_id_prefix: "spiffe://prod.cluster/ns/platform/"
server_workload: "vault-production"
conditions:
- type: source_ip_range
cidr_blocks: ["10.0.0.0/16"]
- type: workload_label
key: "vault_access"
value: "enabled"
- type: workload_label
key: "environment"
value: "production"
Three conditions, all must be satisfied. A workload outside the corporate network, or one that hasn't been explicitly labeled with vault_access=enabled, cannot obtain Vault credentials regardless of its SPIFFE ID.
Policy Evaluation Order and Conflicts
When multiple policies match a request (same client selector pattern and same server workload), Aembit evaluates them all. If any matching policy allows the request (all its conditions are satisfied), the credential is issued. The effective policy is the union of all matching policies — the most permissive matching policy wins for allow decisions.
This means you can't use a more-restrictive policy to override a less-restrictive one. If you have a broad policy that allows namespace-wide access and a narrower policy that tries to add conditions for a specific service account, the broad policy will still allow access for that service account. To restrict a specific workload that would otherwise match a broad policy, you need to either scope the broad policy more narrowly or use a dedicated policy structure that doesn't have conflicting allows.
For security-sensitive credentials, prefer one explicit policy per service account over broad prefix-based policies. The operational overhead of managing per-service-account policies is worth the clarity of knowing exactly who has access to what.
Testing Policies
Aembit's policy simulator allows you to test a policy against a synthetic workload identity without deploying the policy to a live environment. You provide a simulated SPIFFE ID, workload labels, source IP, and request time; the simulator tells you which policies match and whether the credential request would be allowed or denied.
aembit policy simulate \
--spiffe-id "spiffe://prod.cluster/ns/payments/sa/payment-processor" \
--label "environment=production" \
--label "team=payments" \
--server-workload "postgres-payments-prod" \
--source-ip "10.0.4.23" \
--time "2025-10-22T14:30:00Z"
The output shows which policies were evaluated, which conditions passed or failed for each policy, and the final allow/deny decision. This is the right tool for validating new policies before pushing them to production and for debugging unexpected denials in production (by simulating the exact context from a failed access log entry).
For policies with time-window conditions, test both the allowed window and the denied window explicitly. It's easy to set the wrong time zone or make an off-by-one error in boundary times.
Audit Log Integration
Every policy evaluation produces an audit log event. The event includes the client SPIFFE ID, server workload, matched policies, condition evaluation results, and allow/deny decision. For deny events, the log includes which specific condition failed.
Set up alerts on unexpected deny patterns: if a workload that previously had access starts generating denials, it could indicate a label change (the workload lost a required label after a deployment), a time-window miss (a job ran outside its allowed window), or a policy misconfiguration introduced during a recent change.
Don't alert on every denial — some workloads probe for credentials they're not authorized for, and that's normal noise. Alert on sustained denial bursts from workloads that have historically been allowed, or on deny patterns involving credentials that are in the critical or high sensitivity tier.