Workspaces
Storage connections
A storage connection is your own S3 bucket or Azure Blob container a new workspace can keep its files in, instead of Nace's managed storage. See Use your own storage for the concept, and the API reference for every field.
- A connection names one destination: an S3 bucket and region, or an Azure storage account and container, with an optional base prefix. Each workspace gets its own folder beneath it. The destination can't change.
- Who can do what: any member of the account lists and reads connections and binds a ready one to a new workspace. Only a connection's creator, or an owner or admin of the account, changes it; anyone else fails with
403 access_denied. - Cost: managing connections costs nothing and works at any balance.
- Availability: a deployment that doesn't offer customer storage fails every command with
404 storage_connection_unavailable.
A connection's status is one of:
| State | What you can do |
|---|---|
draft | Set it up, save credentials and validate it. It can't hold a workspace yet. |
ready | Give it to a new workspace. Validated: Nace can write, read and delete in the destination. |
degraded | A later check failed. Fix the access in your cloud and validate it again. |
revoked | Nace stopped using it, and workspaces in it stop reading and writing. Restore one you revoked with nace storage validate --reactivate. |
A connection is set up in five steps:
nace storage createmakes adraft.nace storage setupprints what to create in your cloud: for S3, an IAM role trust policy carrying the connection's external id, its permissions policy, a CloudFormation template and a quick-create link; for Azure, theazcommands that give a service principal access to the container. Pass your AWS account id and role name, or your Azure subscription and resource group, and the setup comes back filled in.- Create the role or service principal in your cloud.
nace storage credentialssaves the role's ARN, or the service principal and its secret.nace storage validate --waitchecks that Nace can write, read and delete in the destination. Once it succeeds, the connection isready.
A new IAM role or Azure role assignment can take a few minutes to apply, so a fresh one may fail its first validation: wait and validate again. Then pass the connection's id to nace workspace-create --storage; see Create a workspace.
nace storage create --name "Audit evidence" --provider s3 --bucket acme-audit-evidence --region us-east-1 --prefix nace
nace storage setup --id <connection_id> --aws-account-id 444455556666 --role-name nace-workspaces
# open the launch url it prints, and create the stack
nace storage credentials --id <connection_id> --role-arn arn:aws:iam::444455556666:role/nace-workspaces
nace storage validate --id <connection_id> --wait
nace workspace-create fy25-audit --storage <connection_id>Every storage command is nace storage followed by an action, and takes its inputs as flags: the connection's id as --id, never a bare argument. nace storage alone exits 2 and lists the actions.
| Command | What it does |
|---|---|
storage list | List your account's connections. |
storage get --id CONNECTION_ID | Print a connection and its state. |
storage create --name NAME --provider s3 | Create a draft connection. |
storage setup --id CONNECTION_ID | Print what to create in your cloud, saving the setup inputs you pass first. |
storage credentials --id CONNECTION_ID | Save the role or service principal Nace uses. |
storage validate --id CONNECTION_ID | Check that Nace can use the connection. |
storage revoke --id CONNECTION_ID | Stop Nace using the connection. |
storage delete --id CONNECTION_ID | Delete a connection no workspace uses. |
List storage connections
nace storage list
# storage connections (2):
# <connection_id> ready s3://acme-audit-evidence/nace Audit evidence
# <connection_id> draft azure://acmeaudit/evidence Audit evidence containernace storage list prints storage connections (<n>):, the number of connections listed, then one line per connection, indented two spaces: <id> <status> <destination> <name>. The destination is s3://<bucket>/<prefix> or azure://<account>/<container>/<prefix>, without the prefix when it has none, and the status is one of those above. When more pages follow, the last line is next: <cursor>. Pass it as --cursor for the next page. An empty list prints storage connections (0):.
| Flag | What it does |
|---|---|
--limit N | Connections per page, 1 to 200 (default 50). A larger number exits 2. |
--cursor CURSOR | Start at this page: the next_cursor of the page before. |
--all | Every connection, across every page. Not with --cursor. |
--json | Print {"items": [...], "next_cursor": ...}; next_cursor is null with --all. |
Read a storage connection
nace storage get --id <connection_id>
# <connection_id> ready s3://acme-audit-evidence/nace Audit evidence
# external id: nace-6b1f0c9e2d4a4e7b
# trusted role: arn:aws:iam::111122223333:role/ndi-api
# validated at: 2026-10-26T10:05:00Z
# ready for a workspace: yesnace storage get prints the connection's line, as nace storage list prints it, then external id: and the id your IAM role's trust policy must require, a trusted role: line for each Nace role it must allow (none for Azure), a line for each of credentials pending, validation running and reactivation requires approval that is true, validated at: and revoked at: with the reason when they are set, and last ready for a workspace: yes or no. Only a connection that says yes can be given to nace workspace-create --storage. With --json, it prints the whole connection object. A connection your account doesn't have exits 1 with 404 storage_connection_unavailable.
| Flag | What it does |
|---|---|
--id CONNECTION_ID | Required. The connection's id, as nace storage list lists it. |
Create a storage connection
nace storage create --name "Audit evidence container" --provider azure_blob --account acmeaudit --container evidence
# <connection_id> draft azure://acmeaudit/evidence Audit evidence containernace storage create creates a draft connection and prints its line, or the whole connection with --json, and next: nace storage setup --id <connection_id> on stderr. An S3 connection needs --bucket and --region, and an Azure one --account and --container; a missing flag, or one of the other provider's, exits 2 before any request. The request carries an idempotency key, so a retry after a timeout returns the same connection.
| Flag | What it does |
|---|---|
--name NAME | Required. The name people see. Up to 200 characters; a blank or longer one exits 2 before any request. |
--provider {s3,azure_blob} | Required. An S3 bucket, or an Azure Blob container. |
--bucket BUCKET | S3: your bucket. |
--region REGION | S3: the bucket's AWS region, such as us-east-1. |
--account ACCOUNT | Azure: the storage account name. |
--container CONTAINER | Azure: your container. |
--prefix PREFIX | A base prefix inside the bucket or container; leading and trailing / are dropped. Each workspace gets its own folder beneath it. Default: none. |
Set up access
nace storage setup --id <connection_id> --aws-account-id 444455556666 --role-name nace-workspaces
# options: s3 access_method=auto aws_account_id=444455556666 role_name=nace-workspaces
# role arn: arn:aws:iam::444455556666:role/nace-workspaces
# launch url: https://console.aws.amazon.com/cloudformation/home#/stacks/quickcreate?...
#
# trust policy:
# {
# "Version": "2012-10-17",
# ...nace storage setup prints what to create in your cloud, generated from the connection's saved setup inputs. With no option flags it only reads the setup. With any, it reads the saved inputs, puts the ones you pass over them, and saves them first; flags you leave out keep their saved values. An option flag of the other provider (an S3 one for an Azure connection, or --subscription-id, --resource-group, --tenant-id or --client-id for an S3 one) exits 2 after that read, before anything is saved.
It prints options: and the saved inputs, then for S3 role arn: and launch url: once the account id and role name are saved, then each of trust policy:, permissions policy: and cloudformation template: (S3) or azure cli: (Azure) on a line of its own after a blank line, followed by its text. A policy or template is printed as indented JSON. With --json, it prints the whole setup object.
| Flag | What it does |
|---|---|
--id CONNECTION_ID | Required. The connection's id. |
--access-method {auto,manual} | S3: auto for the CloudFormation stack, manual to write the role yourself. Drex's default is auto. |
--aws-account-id ACCOUNT_ID | S3: the 12-digit AWS account the role is created in. |
--role-name NAME | S3: the name of the role the stack creates. |
--kms-key-arn ARN | S3: a KMS key the role may use; the bucket's settings decide encryption. |
--subscription-id ID | Azure: the subscription the storage account is in. |
--resource-group NAME | Azure: the resource group the storage account is in. |
--tenant-id ID | Azure: your Microsoft Entra tenant. |
--client-id ID | Azure: the application (client) id to give access to. |
Save credentials
nace storage credentials --id <connection_id> --role-arn arn:aws:iam::444455556666:role/nace-workspaces
nace storage credentials --id <connection_id> --tenant-id <tenant_id> --client-id <client_id> --client-secret-file secret.txt
AZURE_CLIENT_SECRET="..." nace storage credentials --id <connection_id> --tenant-id <tenant_id> --client-id <client_id>nace storage credentials saves the IAM role Nace assumes, or the Azure service principal and its secret, and prints the connection's line, or the whole connection with --json, and saved; next: nace storage validate --id <connection_id> --wait on stderr. They are never returned, and the connection has credentials pending until a validation passes. Pass --role-arn for an S3 connection, or --tenant-id and --client-id for an Azure one; with neither, both kinds, or only one of the Azure ids, it exits 2 before any request.
The Azure secret is never a flag's value, so it stays out of your shell history and process list. It comes from --client-secret-file, a file holding it (one trailing newline is dropped), or - to read stdin, asking at a hidden prompt at a terminal. Without that flag it comes from $AZURE_CLIENT_SECRET. With neither, or an empty secret, the command exits 2 before any request. The CLI never prints the secret, and debug logs show it as ***. Credentials the cloud refuses exit 1 with 422 storage_credentials_refused, whose message never quotes them.
| Flag | What it does |
|---|---|
--id CONNECTION_ID | Required. The connection's id. |
--role-arn ARN | S3: your IAM role, arn:aws:iam::<account>:role/<name>, that trusts Nace's roles with the connection's external id. |
--tenant-id ID | Azure: your Microsoft Entra tenant. |
--client-id ID | Azure: the application (client) id, with Storage Blob Data Contributor on the container. |
--client-secret-file FILE | Azure: a file holding the application's secret, or - for stdin. Default: $AZURE_CLIENT_SECRET. |
Validate a connection
nace storage validate --id <connection_id> --wait
# succeeded storage_validation <job_id>nace storage validate starts a storage_validation job and prints its line, <status> storage_validation <job_id>, or the whole job with --json. With --wait, it prints the started job's line on stderr, waits until the job is succeeded, failed or cancelled, and prints that job; a job that didn't succeed exits 1 with error: validation failed: <message> on stderr, the job's error saying what Nace could not do. Still running after --wait-timeout seconds, it exits 1 and the validation keeps running: nace storage get shows validation running until it settles, and ready for a workspace: yes once it passed. A connection Nace revoked can't be validated and fails with 409. The command isn't retried automatically, since a retry after one that went through starts a second validation.
| Flag | What it does |
|---|---|
--id CONNECTION_ID | Required. The connection's id. |
--reactivate | Restore a connection you revoked once the validation passes. |
--wait | Wait until the validation finishes, and exit 1 if it failed. |
--wait-timeout SECONDS | With --wait, how long to wait (default 180). A negative number exits 2 before any request. |
Revoke a connection
nace storage revoke --id <connection_id>
# <connection_id> revoked s3://acme-audit-evidence/nace Audit evidencenace storage revoke stops Nace using the connection and prints its line, or the whole connection with --json. Workspaces bound to it stop reading and writing until nace storage validate --reactivate passes; their files stay in your bucket.
| Flag | What it does |
|---|---|
--id CONNECTION_ID | Required. The connection's id. |
Delete a connection
nace storage delete --id <connection_id>nace storage delete deletes a connection no workspace uses and prints deleted <connection_id> on stderr, and nothing on stdout, with or without --json. Nothing in the bucket is touched. A connection a workspace keeps its files in fails with 409. The command isn't retried automatically, since a retry after a delete that went through would fail with 404.
| Flag | What it does |
|---|---|
--id CONNECTION_ID | Required. The connection's id. |