Salt Command-Line Reference
Salt Authentication
Before using salt make sure you have a valid Xygeni account as well as a Xygeni token. Please see Generate Xygeni Token for further info.
Once you have a valid Xygeni Token, provide the token as an env variable named XYGENI_TOKEN. Salt will read and use it to authenticate against the Xygeni platform.
Salt Command-Line Reference
Salt provides a command-line interface (Salt CLI) with commands for creating the attestation document from the inputs, interacting with the attestations registry, and verifying an attestation with respect to the referenced software product.
salt CMD, with CMD = attestation | keygen | registry | policy | digest | verify.
The main commands are:
salt keygen: Generate key pairs for signingsalt attestation init|add|run|status|commit|reset: Incremental attestation buildsalt attestation provenance: Provenance for pipeline, in a single shotsalt registry search | get | put: Operations with Attestation Registrysalt verify: Verify an attestation for a SW artifact, and hold it to a policysalt policy list | show | pull | push | delete | validate | infer: Attestation policy handlingsalt digest: Compute the digest of a file, directory, container image or string
Software attestations are typically created in the same pipeline that builds and/or deploys the software. One or more steps can run Salt CLI commands to either build the attestation either in one shot (salt attestation provenance) or incrementally at different steps along the pipeline (salt attestation init|add|run|commit).
The attestation is always a JSON file following a standard format, that could be distributed along with the software products, or registered in a specific Attestation Registry.
The -h | --help could be used with any command / subcommand:
Key Generation
Keys need to be generated before generating attestations (unless you use the keyless mode with --keyless). You may use your own tooling like openssl
Example: To generate an Ed25519 keypair and store in my_key.pub (public key) and my_key.key (private key), use the following:
The command generates the keypair of the requested type and stores its parts in two files. The password is read from the console, or from the SALT_PRIVATE_KEY_PASSWORD environment variable when it is set.
A password is required. An empty or blank password is rejected, and so is an empty SALT_PRIVATE_KEY_PASSWORD — which is what a declared-but-unset CI variable expands to. If you genuinely need an unprotected key, because it is held somewhere that protects it by other means, ask for it explicitly with --insecure-no-password.
The private key file is written readable only by its owner, and encrypted with a key derived from your password using PBKDF2-HMAC-SHA256. Keys written by earlier versions of Salt are still read, so existing pipelines keep working.
For generating on-the-fly, single-use keys, and if you have an OIDC provider available, it could be more convenient to use keyless signing.
Create attestations
Creating and verifying software attestations is the core of the SALT framework. Attestations in SALT follow the in-toto Attestations Framework. See Attestation Format for more details.
The command salt attestation is used for creating software attestations:
There are two modes: incremental (init … commit) and one-shot (provenance).
Notes:
(1) The working directory used for resolving file / directory paths is the -d | --basedir option, or the current working directory when not given.
(2) Key material is typically encoded in PEM format, and can be passed as an environment variable, local file path, or the PEM value itself. You may also use prefix-based syntax like env:YOUR_VARNAME_HERE or file:YOUR_PATH_HERE. File paths are resolved against the current working directory.
Please note that using env:VAR_NAME could be more convenient than passing $VAR_NAME substitution to the command option directly, as the value will not be shown in logfiles.
Generate Provenance
For generating attestation during the build of software in a single shot, you may use the attestation provenance command (alias: attestation slsa). SLSA Provenance is a common attestation format.
Example: Generate SLSA provenance for a container image.
With REPO_DIR = path to the software repository, and OUT_DIR = directory where the output files will be written.
The above command will:
compute the gitoid:sha256 digest on the
$REPO_DIR/srcdirectory as a subject named 'sources', the sha256 digest on the$REPO_DIR/build/my_cliexecutable (one product of the build), and the docker sha256 digest on the container image (as a second product).use keyless signing to generate and certify on-the-fly keys,
produce a signed SLSA v1 Provenance in
$OUT_DIR/provenance.signed.jsonand the unsigned in-toto Statement (easier to see the attestation contents) into$OUT_DIR/provenance.json.upload the signed attestation to the configured attestations registry, and the result from the build and attestation drafting to Xygeni platform.
Generate Custom Attestation
As an alternative to the attestation provenance command, you may incrementally generate a fully customized attestation using the attestation init | add | run | commit/reset subcommands. If you need to create a customized attestation.
You may consider different attestation formats, for different use cases:
"Know your container" - Feed into automated policy engines, like Google Cloud’s Binary Authorization.
Add statements about the current security position of the software: Typically, the statement reads like "I hereby certify that, to the best of my knowledge, the software does not have any critical security flaw…"
The idea is to integrate the usual software product security reports like static analysis' detected flaws, software composition and vulnerabilities in components, results from dynamic vulnerability scans. Formats like SCAI or the Vulnerabilities Predicate allow for this.
Attestation drafting
Attestation could be built incrementally, using multiple steps kept in a context, and a final step that signs the attestation and publishes it. This incremental process is called attestation drafting. The drafting process follows the commands in the developer’s possibly favorite tool, git: init / add / commit. An attestation run subcommand is added to capture the execution of a build command.
Example of attestation drafting: Assume that PIPELINE is the reference to the running pipeline where the commands are inserted, and that REPO_DIR points to the software repo directory.
attestation initcreates the initial draft with initial information (like system environment and git data) extracted by one or more predefined 'attestors'. Available attestors are currentlygitandenvironment. Materials (inputs) may also be added as files or directories.attestation addadds elements (material, subject, product or statement) to the current draft attestation. Typically, a digest (SHA-256) is computed on the element. Container images may be added.The image digest is the 'official' from the manifest, as extracted from
docker manifest inspect IMAGE -vorgcrane digest IMAGEattestation runruns a command and adds an attestation predicate for the command execution. Cryptographic digests for input artifacts (materials) will be computed before running the command, and for output artifacts (by-products) at the end of the command. This could be used to link the inputs and outputs of the step with other steps in the pipeline. The predicate has a type URI ofxygeni.io/attestations/command-run/v1, and includes the following information:
attestation commitbuilds the final attestation as in-toto Statement, serializes it as JSON, signs it with the passed key material, creates an in-toto Envelope with the statement as payload, the signature and the reference for the signing key, and publishes it in the attestation registryThe final statement is composed using the added predicates, input artifacts ('materials'), run command predicates, init attestors, and output artifacts ('products'), and then signed using the key materials passed as options.
As multiple predicates are typically added, a special collection predicate (with type URI
xygeni.io/attestations/collection-predicates/v1) is used to compound multiple predicates in the single predicate that goes as the predicate for the in-toto statement to be signed.
For full command syntax, use the --help | -h option on the attestation subcommand:
The wrapped command's result
attestation run is a build wrapper, so it returns the failure of the command it wrapped. If your mvn package or go build fails, the salt attestation run step fails too, with exit code 16, and your pipeline stops there instead of going on to commit an attestation for a build that produced nothing.
The attestation is still recorded either way: a predicate describing a step that failed is evidence, and the exit code of the wrapped command is part of it. Add --never-fail on the attestation command if a particular step must not break the pipeline.
What is captured, and secret redaction
attestation run records the command line, its exit code, and its standard output and error, all of which become part of the signed attestation. Because build logs and command lines carry credentials, output and arguments are redacted before they are signed: values matching well-known token formats are replaced with a [redacted:…] marker.
Redaction recognises common shapes, not every possible one. Three options let you bound what is captured when that is not enough for your build:
--max-out=<KB>
Cap the captured stdout. 0 ignores stdout entirely. Default 10 KB
--max-err=<KB>
Cap the captured stderr. 0 ignores stderr entirely. Default 10 KB
--skip-cmd-args
Record only the command name, omitting its arguments
Attestation Status
You may use the attestation status subcommand in the drafting process to show the current context for the drafted attestation. Example:
Attestation Reset
If the attestation drafting process should be aborted, due to an failure ini the build, the attestation reset command should be invoked for cleaning up the attestation context:
Run salt attestation reset -h for full command reference.
Verify Attestation
Attestation verification can be performed by the user of the software the attestation refers to. The verification process:
(1) Computes the digests of the subjects that are passed to the command, following the same syntax as with the attestation add | provenance commands. Emits a verification failure if any digest does not match with the digests present in the attestation. This could help with detecting post-build tampering.
(2) Verifies the signature of the attestation payload (statement included in the attestation envelope) with the public key or certificate passed.
(3) If a signer certificate is present, verifies the certificate itself: that its public key is the one that signed, that it was valid when it signed, and that any presented chain holds together. Optionally that it chains to a certificate authority you name, and that it carries the signer identity you expect. For a keyless (Fulcio) certificate, whose validity lasts only minutes, the signature can be dated from the transparency log so the certificate is judged at the instant it signed.
The attestation verify performs this validation:
When there is a failure in validation, the result shows what failed:
Use salt verify -h to display the full syntax of the command.
Subject digests. A subject is matched on every digest algorithm that the attestation and your artifact have in common, and at least one of them must be SHA-256 or stronger. MD5 and SHA-1 are not accepted on their own to identify an artifact, because collisions for both can be produced deliberately. If a digest you supply disagrees with the attestation, verification fails even when another algorithm agrees. Attest with sha256 — the default when Salt computes a digest for you — and this never comes up.
Attestations from other tools. salt verify reads plain DSSE envelopes and Sigstore bundles — the .sigstore.json format cosign produces by default and the GitHub attestation API serves — so an attestation from cosign, witness or GitHub Actions can be verified as published, with no unwrapping step. A keyid is optional: an envelope that carries none, as cosign's do, still verifies. RSA signatures verify whether they use PKCS#1 v1.5 or PSS, which is what sigstore and witness produce for an RSA key.
Certificate checks
When you verify with a certificate rather than a bare public key — -k file:signer.crt, or an attestation whose envelope carries one — the certificate is checked, not just read for its key. Verification fails if its public key is not the one that signed, if it was outside its validity window when it signed, or if a presented chain does not hold together. Verifying with a bare public key is unaffected.
Two things you can additionally require:
--trust-root=ROOT
The signer certificate must chain to this authority. sigstore uses the bundled public Sigstore (Fulcio) authority; otherwise env:VARNAME, file:PATH or inline PEM. Omit it and certificates are still checked, but not required to reach any authority
--cert-email=EMAIL
The certificate must carry this email. Repeatable; any one matching is enough
--cert-uri=URI
The certificate must carry this URI — how CI workload identity is expressed
--cert-common-name=CN
The common name the certificate must carry
--cert-organization=ORG
The organization the certificate must carry
--cert-dns=NAME
The DNS name the certificate must carry
--trust-root sigstore on its own only establishes that Fulcio issued the certificate, and Fulcio issues one to anyone who can log in with an OIDC identity. Pair it with an identity option, or you have checked almost nothing:
Keyless certificates expire in minutes. A Fulcio certificate is valid for about ten, so by the time anyone verifies the attestation it has long expired. The transparency log closes that gap: its entry proves the signature already existed when the log recorded it, and the certificate is then judged at the instant it signed rather than now.
You normally need to do nothing — when a certificate cannot be judged against the current time, the entry is looked up in the log automatically, by the digest of the attestation. Two options are there when you want to be explicit:
--rekor-uuid=UUID
Name the entry instead of searching for it
--rekor-entry=file:PATH
Supply an entry you already hold, so nothing is fetched
--rekor-entry is what makes verification work with no network at all: an entry carries its own inclusion proof and timestamp, so saving it beside the attestation lets both travel together — for an air-gapped consumer, or for anyone re-checking a release years later.
Certificate transparency. With --trust-root sigstore, the signer certificate must also carry a valid certificate transparency receipt, and Salt verifies it against the log's own key. A public authority such as Fulcio publishes everything it issues, so one of its certificates without a receipt did not come from it. A private authority owes nobody a receipt and is not held to one; use --ct-log-key to supply the key of a self-hosted log.
Verifying an attestation you did not produce
A keyless signer's private key exists for the ten minutes of the signing and is never published, so there is no --public-key you could pass for somebody else's attestation. The certificate inside the attestation is the only key that can verify it, and Salt will use it — provided you say what would make it trustworthy:
Both --trust-root and one of the --cert-* options are required here, and verification is refused without them, naming what is missing. This is deliberate: a certificate that arrives together with the artifact it vouches for is not evidence on its own — anyone can issue one and sign with it — and an authority alone is little better, since Fulcio issues a certificate to anyone who can log in.
Seeing what a verification rested on
ok is a claim rather than evidence: it reads the same whether the certificate chained to Sigstore and carried a receipt a public log signed, or whether no authority was named and nothing was required. Add --explain for the detail:
It reports the instant the signature was taken to have been made and where that came from — an unproven time says so rather than looking established — the log entry that dated it, the authorities trusted, and each certificate with its transparency receipts. The same detail is always included in the report written with --output, since an audit usually happens long after the run.
Whether the attestations are enough
Everything above answers whether an attestation is genuine. Whether it is enough — which attestations must be present at all, who must have signed them, and what they must say — is a separate question, and --policy is how you ask it:
The two verdicts stay apart in the exit code: 13 means the attestations do not verify, 17 means they verify and do not satisfy the policy. See Attestation Policy.
Policy Operations
A policy is the set of requirements an attestation must satisfy before salt verify will pass. Writing one is covered in Attestation Policy; the policy command is how policies are kept.
Two of the subcommands work on a local file and need neither network nor credentials:
The rest keep the policies stored on the Xygeni platform, so that many pipelines can be held to one definition that is changed in one place. A stored policy is referenced as --policy xygeni:<name>:
delete removes the policy for every pipeline referencing it, and those runs will then fail to load it. With no console to ask on — a pipeline, a redirected stdin — the command refuses rather than assuming yes.
Use salt policy -h for the full syntax of the commands.
Registry Operations
Attestation generation and verification may connect with the Attestations Registry to fetch the attestation. The registry command allows you to upload, search for, and download attestations.
Attestation entries in the registry have a digest (64 hexadecimal digits) for downloading.
Search typically uses the digest value of a subject in the attestation to list the attestations that refer to it (there could be multiple).
Use salt reg -h for the full syntax of the commands.
Exit Codes
Salt commands report their outcome in the exit code, so a pipeline can react to it. The ones you are most likely to act on:
0
Success
1
Generic error
2
Invalid command options
3
No password provided for the private key
4
Key pair could not be generated
5
Nothing matched: no such entry in the registry, or no policy of that name available to this account
6
Output could not be written
7
Timed out
8
No active attestation context for the pipeline (was attestation init run?)
9
An attestation item could not be built
10
Signing keys are not properly configured
11
Signature error
12
Attestations registry error
13
Attestation validation failed
14
A subject digest could not be computed
15
The result report could not be uploaded to Xygeni
16
The command wrapped by attestation run failed
17
The attestations verified, and a policy refused them anyway
Code 16 is a single code whatever the wrapped command returned, so it cannot collide with Salt's own codes; the command's real exit code is recorded in the attestation.
Codes 13 and 17 are kept apart on purpose: "these attestations are not valid" and "these attestations are valid and not sufficient" call for different responses from a pipeline, and one code for both would make them indistinguishable to the shell that has to decide.
--never-fail, available on the attestation and registry commands, returns 0 on every one of these, so a step cannot break the pipeline. Note that this also hides a failed upload: use it where you want the pipeline to continue regardless, not as a default.
Last updated


