SwitchTenderSwitchTender Docs

Set a secret

Secrets live in credentials. Each is sealed at rest and never returned by the API. A run gets a credential's value only while it executes, in the environment or a temporary file created mode 0600 and deleted when the run ends. If a tool prints a credential's value, SwitchTender redacts it from the run's log, live stream, and events, so the output shows *** instead of the secret.

Store a credential

  1. Open Credentials and add one.

  2. Pick the kind:

    Kind What it is
    ssh_key an SSH private key, to reach hosts and clone private git projects.
    ssh_password a machine login, injected as ansible_user and ansible_password through a file, off the command line.
    vault_password an Ansible Vault password.
    become_password a privilege escalation password, kept off the command line.
    become privilege escalation with an optional method and user, injected as the ansible_become_* variables through a file.
    network a network device login, injected as the ansible_user, ansible_password, ansible_network_os, and ansible_connection variables.
    env KEY=VALUE lines injected into the run, how cloud SDK tokens reach a tool.
    token a single API token or JWT, exposed to the run as the SWITCHTENDER_TOKEN environment variable.
    registry a container registry login, to pull a pinned execution image.
    aws an AWS access key, injected as the standard AWS_* environment variables.
    azure an Azure service principal, injected as the ARM_* variables Terraform reads and the AZURE_* variables the Ansible azure collection reads.
    gcp a Google Cloud service account JSON, written to a private file bound to GOOGLE_APPLICATION_CREDENTIALS.
    vmware a vCenter login, injected as the VMWARE_* environment variables the community.vmware modules read.
    openstack an OpenStack login, injected as the OS_* environment variables openstacksdk and the openstack.cloud collection read.
    kubeconfig a Kubernetes kubeconfig, written to a private file bound to KUBECONFIG, K8S_AUTH_KUBECONFIG, and KUBE_CONFIG_PATH.
  3. Paste the secret and save. Attach it to a project, a template, or a run.

Resolve it from an external store

You do not have to paste the secret at all. Set the credential's source to a command, and SwitchTender runs that command at launch and uses its standard output as the secret. That keeps the value in your existing store, fetched fresh each run:

vault kv get -field=token secret/ci

That prints one bare value, which is what a token credential holds, and it reaches the run as SWITCHTENDER_TOKEN. An env credential holds KEY=VALUE lines, so its command has to print them, for example printf 'CI_TOKEN=%s\n' "$(vault kv get -field=token secret/ci)". An env credential whose command prints anything else stops the run before it starts and says which line.

Any CLI works the same way, so a credential can pull from Vault, AWS Secrets Manager, GCP Secret Manager, or 1Password with no extra integration.

For Vault there is also a native source that needs no vault CLI on the runner: set the source to Vault and give it the address, path, and field as JSON. SwitchTender reads the secret over Vault's HTTP API at launch, handling both KV v2 and KV v1. The token comes from the config, or from the server's VAULT_TOKEN environment when the server also has VAULT_ADDR set to the same Vault as addr. The server hands its own token only to the Vault it was pointed at, so a credential cannot name another address and collect it. With neither, the run stops and says a token is missing:

{"addr":"https://vault:8200","path":"secret/data/ci","field":"token"}

Google Secret Manager has a native source too: set the source to Google Secret Manager and give it the project, secret, and version as JSON. When SwitchTender runs on GCP it reads as its attached service account through the metadata server, so no key is stored. Off GCP, put an access token in the config.

{"project":"my-project","secret":"ci-token","version":"latest"}

AWS Secrets Manager and Azure Key Vault work the same way. For AWS, give the secret id and region as JSON, along with an access key. The key comes from the config or from AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in the server's environment; the instance metadata service is not read, so an EC2 instance role alone does not authenticate it.

{"secret_id":"prod/db-password","region":"us-east-1",
 "access_key_id":"AKIA...","secret_access_key":"..."}

For Azure, give the vault name and secret as JSON. On Azure it reads as the attached managed identity with no stored key. Off Azure, add a service principal's tenant_id, client_id, and client_secret, or a bearer token.

{"vault":"prod-kv","secret":"db-password"}

CyberArk Conjur has a native source too: give the Conjur URL, account, and variable as JSON. SwitchTender exchanges the login and api_key for a short-lived access token, then reads the variable. Supply a pre-issued token instead to skip the exchange.

{"url":"https://conjur.example.com","account":"prod","login":"host/app","api_key":"...","variable":"db/password"}

CyberArk's Central Credential Provider is the other native CyberArk source: give the CCP URL, app id, and the safe and object that locate the account, or a raw query. The application authenticates with a client_cert and client_key for mutual TLS, or through a CCP allowed-machine rule when neither is set.

{"url":"https://ccp.example.com","app_id":"switchtender","safe":"Prod","object":"db-prod","client_cert":"-----BEGIN CERTIFICATE-----\n...","client_key":"-----BEGIN EC PRIVATE KEY-----\n..."}

For 1Password, run a Connect server and give its URL, an API token, and the vault and item to read, each a name or an id. The field defaults to the item's password; set it to pull a different labeled field, such as a credential or an API key.

{"url":"https://connect.example.com","token":"...","vault":"Prod","item":"Database","field":"password"}

Mint a short-lived secret per run

For strong secret hygiene, SwitchTender can mint a fresh credential for each run and revoke it when the run ends, so a leaked value is useless minutes later. Set the source to Vault dynamic and give it a dynamic secrets path, such as a database or cloud role, as JSON:

{"addr":"https://vault:8200","path":"database/creds/app","field":"password"}

At launch SwitchTender reads that path, which mints a new credential, injects the chosen field into the run, and records the Vault lease. When the run reaches a terminal state the lease is revoked, so the credential lives only as long as the run. If the process dies before it can revoke, any control node revokes the lease once the run is marked interrupted, from a sealed handle the database keeps for it. The minted value is masked in the run's output like any other secret.

AWS STS mints short-lived role credentials the same way. Set the source to AWS STS and give it an IAM role_arn to assume, with an optional region, duration_seconds, and external_id. Use it with an env credential, since it injects AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN.

{"role_arn":"arn:aws:iam::123456789012:role/deploy","region":"us-east-1","duration_seconds":3600}

The base credentials that sign the assume-role call come from the config or the standard AWS environment. STS credentials cannot be revoked early, so they expire on their own lifetime rather than on a lease.

From the API

curl -s -X POST localhost:8080/v1/credentials \
  -H "Authorization: Bearer $ST_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"name":"ci-token","kind":"token","source":"command","secret":"vault kv get -field=token secret/ci"}'

For a pasted value, omit source and put the value in secret. Imported credentials arrive without secrets, since exports never contain them. This is the one-time step to fill them in.

Scope a secret to an inventory

Attach a credential to a stored inventory and every run that targets that inventory receives it, so a fleet can carry its own secrets in one place. Open Inventories, edit the inventory, and pick the credentials under Credentials. An env credential's KEY=VALUE lines become environment variables in each of those runs, so a playbook reads one with lookup('env', 'KEY') and a script reads $KEY. They are not Ansible variables, so {{ KEY }} is undefined. A token credential arrives as SWITCHTENDER_TOKEN. You need use access on a credential to attach it.

Source an inventory's hosts

The host list itself can live in an external store, the same way a secret does. When you add or edit an inventory, pick a content source:

The form takes the address, path, and field for Vault, or the project and secret for Google Secret Manager, and assembles and seals the source configuration for you, so the host list stays in your existing store and is fetched fresh for every run. A command source keeps a generated inventory current without a dedicated dynamic source plugin:

./bin/hosts --env prod

Content sources need encryption, so set SWITCHTENDER_ENCRYPTION_KEY and a stable SWITCHTENDER_ENCRYPTION_SALT first. The sealed configuration is never returned by the API. When you edit an inventory, leave the source fields blank to keep the stored one.

From the API

curl -s -X POST localhost:8080/v1/inventories \
  -H "Authorization: Bearer $ST_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"name":"prod-fleet","content_source":"command","content_config":"./bin/hosts --env prod"}'

For a Vault or Google Secret Manager source, send the same JSON configuration the matching credential source uses as content_config.