APIZ guide
Partition One Shared API Across Clients
Use this pattern when several agents or applications need the same upstream API connection but must see different resources. APIZ keeps one API Instance and upstream credential, creates a separate Client for each consumer, and attaches a different mandatory Policy to each Client Binding.
For example, one S3 bucket can be divided by prefix:
one S3 API Instance
shared-artifacts + upstream key
|
+--------------------+--------------------+
| |
Client: Acme exporter Client: Globex exporter
mandatory Binding Policy mandatory Binding Policy
tenants/acme/* only tenants/globex/* only
| |
Temporary Credential A Temporary Credential B
APIZ does not introduce a Tenant, Organization, Workspace, or Project object for this pattern. The Team owns the shared API Instance; Clients and their Binding Policies define the separate access views.
When To Use This Pattern
- Several workloads use the same provider account or upstream credential.
- The adapter exposes stable normalized resource fields that Policy can check.
- Every consumer can have its own Client and Temporary Credentials.
- Logical isolation with redacted decision evidence meets the risk requirement.
Do not use this as a substitute for separate provider accounts, credentials, buckets, or networks when the upstream service or your compliance boundary requires hard infrastructure isolation.
Build The Shared S3 Connection
Create one S3 API Instance for the shared bucket. The upstream IAM credential
may cover the full bucket, but should still have no broader provider permission
than APIZ needs. Leave the API Instance default_prefix empty because each
Client Policy supplies a different enforced prefix.
In the Web Console, use API Instances → Create API Instance → S3. With the CLI, follow the credential-safe creation flow in the S3 Adapter guide:
apiz -o json api create \
--adapter s3 \
--name shared-artifacts \
--config-json '{"provider":"aws_s3","endpoint_url":"https://s3.us-west-2.amazonaws.com","region":"us-west-2","bucket":"shared-artifacts"}' \
--test-config-json '{"preset":"head_bucket","bucket":"shared-artifacts"}' \
--credential-stdin
The bucket name is synthetic. Replace the endpoint, region, bucket, and write-only credential with the values for your controlled environment.
Create One Client Per Access View
In the Web Console:
- Create Clients named Acme exporter and Globex exporter.
- Add the same
shared-artifactsAPI Instance to both Clients. - Use the same route alias, such as
artifacts, if that makes deployment configuration uniform. The alias does not provide isolation; Policy does.
With the CLI:
apiz -o json client create --name acme-exporter > acme-client.json
apiz -o json client create --name globex-exporter > globex-client.json
ACME_CLIENT_ID=$(jq -r '.client_id' acme-client.json)
GLOBEX_CLIENT_ID=$(jq -r '.client_id' globex-client.json)
apiz -o json client bind "$ACME_CLIENT_ID" \
--api-instance <shared-api-instance-id> --alias artifacts > acme-binding.json
apiz -o json client bind "$GLOBEX_CLIENT_ID" \
--api-instance <shared-api-instance-id> --alias artifacts > globex-binding.json
Define One Prefix Policy Per Client
Create two Structured Policies from the same definition. Change only the name,
reason, and allowed prefix. This Acme example allows object reads and prefix
listing under tenants/acme/; every other action or resource is denied.
{
"type": "structured",
"adapter_scope": "s3",
"when": {
"actions": ["s3:list_objects", "s3:get_object", "s3:head_object"],
"conditions": [
{"field": "resource.bucket", "op": "equals", "value": "shared-artifacts"},
{"field": "resource.prefix", "op": "prefix", "value": "tenants/acme/"}
]
},
"then": {"decision": "allow", "reason": "acme_prefix_read_allowed"},
"otherwise": {"decision": "deny", "reason": "outside_acme_partition"}
}
For Globex, replace tenants/acme/ with tenants/globex/ and use distinct
stable reasons. S3 normalization supplies the requested list prefix for list
operations and the object key for object operations as resource.prefix.
A bucket-wide list with no prefix therefore does not match either Policy.
In the Web Console, build these conditions in the Structured Policy form. With the CLI, use two Policy Project directories and follow the publish lifecycle in the Structured Policy tutorial.
Prove Both Sides Of The Boundary
Each Policy needs credential-free fixtures for its own prefix and the adjacent prefix. For Acme, test at least:
| Request | Expected result |
|---|---|
List shared-artifacts with prefix tenants/acme/ | allow: acme_prefix_read_allowed |
Get shared-artifacts/tenants/acme/report.json | allow: acme_prefix_read_allowed |
List with prefix tenants/globex/ | deny: outside_acme_partition |
Get shared-artifacts/tenants/globex/report.json | deny: outside_acme_partition |
| Delete an Acme object | deny: outside_acme_partition |
| List the bucket without a prefix | deny: outside_acme_partition |
Repeat the mirrored suite for Globex. Publish the exact definitions and
fixtures, run both published suites, enable the Policies, and attach each one
to only its matching Client Binding. Keep the bindings mandatory and at a
stable execution order such as 100.
ACME_BINDING_ID=$(jq -r '.client_binding_id' acme-binding.json)
GLOBEX_BINDING_ID=$(jq -r '.client_binding_id' globex-binding.json)
apiz client binding policy add "$ACME_BINDING_ID" \
--policy <acme-policy-id> --order 100
apiz client binding policy add "$GLOBEX_BINDING_ID" \
--policy <globex-policy-id> --order 100
Do not attach both tenant Policies to the same Binding: cumulative mandatory Policies would require both decisions to allow and would deny every partition.
Issue Separate Temporary Credentials
Issue credentials independently so one Client can be revoked without affecting the other:
apiz -o json client credentials create "$ACME_CLIENT_ID" \
--binding artifacts --ttl 1h --format aws-credentials > acme-setup.json
apiz -o json client credentials create "$GLOBEX_CLIENT_ID" \
--binding artifacts --ttl 1h --format aws-credentials > globex-setup.json
An Acme agent may use the generated AWS-shaped APIZ credential and endpoint:
aws --endpoint-url "$APIZ_S3_ENDPOINT" s3api list-objects-v2 \
--bucket shared-artifacts --prefix tenants/acme/
The same credential requesting tenants/globex/ is denied before Secret Broker
release and before S3 contact. It never receives the shared upstream access
key. Globex receives a separate Temporary Credential with the opposite view.
Verify Isolation And Evidence
Open Logs / Audit → Access Logs and compare an allowed in-prefix request with a denied cross-prefix request. Confirm:
- the credential resolved the expected Client and Binding;
- S3 normalized the expected action, bucket, key, and prefix;
- the matching Published Policy Revision produced the stable reason;
- denied cross-prefix traffic did not reach upstream; and
- evidence contains no APIZ credential or upstream access-key value.
Use Audit Events to confirm who changed either Policy or attached it to a Binding. Revoke only the affected Client's Credential Group when one consumer is compromised; disable the shared API Instance only when every partition must stop.
Safety Boundaries
- A configured
default_prefixis a routing/test convenience, not an authorization boundary. Enforce the prefix in Policy. - Require a prefix on list operations. Otherwise a bucket-wide listing can reveal object names from other partitions.
- Include read, write, multipart, tagging, copy, and delete actions explicitly when a tenant needs them; never assume one object action covers the others.
- Keep a global mandatory API Instance Policy for rules that every Client must obey, such as denying bucket administration. Client Binding Policies provide the per-consumer slice.
- A Policy misconfiguration can widen a logical partition. Maintain allow, adjacent-deny, dangerous-action, and no-prefix fixtures for every partition.
- Prefer separate upstream credentials or resources when compromise of one logical partition must be cryptographically or operationally independent.