Configure

Everything is an environment variable. No configuration file, no hardcoded path.

  1. Environment variables
    1. On a runner
  2. Authentication
  3. Exposing it beyond this machine
  4. File ownership
    1. SELinux
  5. Paths inside the container
  6. Validator profiles

Environment variables

Variable Default Meaning
CRANE_DB ./data/crane.db Database file
CRANE_MIGRATIONS searched Migration directory
CRANE_EXPORTS ./exports Where reports and evidence packages are written
CRANE_RUNS ./runs Working directory for each executed tool run
CRANE_UPLOADS next to the database Where the import wizard stores uploaded documents
CRANE_REPORT_TEMPLATES ./report-templates Directory holding your own Markdown report templates
CRANE_API_TOKEN unset Token the web interface and API require. See below
CRANE_ALLOW_UNAUTHENTICATED_BIND unset Assert the port is restricted outside this process. See below
CRANE_OSSBOMER_BIN ossbomer on PATH The validator executable, when it is not on PATH
CRANE_OSSBOMER_PROFILE_PATH unset Directories holding your own validator profiles
CRANE_OSSBOMER_SECONDS_PER_MB 60 Validation time each megabyte of document adds to the job’s own 600 s limit, so a large document gets more time rather than the same timeout. See troubleshooting
CRANE_GITHUB_TOKEN unset Token the GitHub collector uses to see private repositories. Unset, public organizations still scan inside GitHub’s anonymous rate limit
CRANE_GITLAB_TOKEN unset Token the GitLab collector uses to see private projects. Unset, public projects still scan inside GitLab’s unauthenticated rate limit
CRANE_ADO_TOKEN unset Personal access token the Azure DevOps collector uses. Azure sends anonymous callers at a private organization to a sign-in page rather than refusing them, so without this a private organization reports a refusal naming this variable
CRANE_PURL2SRC_BIN purl2src on PATH The source resolver executable, when it is not on PATH
CRANE_SUPHM_BIN suphm on PATH The upstream health executable, when it is not on PATH
CRANE_AGENT_BIN the configured agent’s name on PATH The interpretation agent executable, when it is not on PATH. Read only where this server runs the agent — by default a runner does, and that machine names the executable in its own [commands] instead
SOURCES_DIR ./sources Host directory mounted read only at /sources. Compose only
CRANE_UID, CRANE_GID 10001 Uid and gid the container runs as. Compose only

The image sets CRANE_DB, CRANE_EXPORTS, CRANE_RUNS and CRANE_MIGRATIONS to their container paths already. Every path is named explicitly rather than left to a default, because a default would resolve relative to the installed package inside site-packages, which is not writable and is not data.

A test reads these names back out of the source and fails if one is missing from this table, because the table drifted once already and nothing was watching it.

On a runner

These are read by crane-runner, which runs on the machine executing a tool rather than on this one. None of them are read here, and nothing here is read there — a runner holds a runner token and has no database.

Every one of them can also come from the runner’s configuration file, and the file is where the token belongs: it can be mode 600, and it keeps the secret out of a shell history and out of ps. The environment wins over the file where both say something.

Variable Default Meaning
CRANE_RUNNER_CONFIG ~/.config/crane-runner/config.toml, then /etc/crane-runner/config.toml The configuration file. Named and missing is an error, not a fallback
CRANE_RUNNER_SERVER unset The CRAne to claim from, as an https:// URL
CRANE_RUNNER_TOKEN unset The runner’s own credential, from crane runner token issue. For a container with no persistent disk, so the token need not be in a file
CRANE_RUNNER_ID the hostname Which machine this is. Recorded against every run it claims
CRANE_RUNNER_CAPABILITIES none Comma separated. What this machine can execute. Narrows what it is offered and never widens it — the token is the ceiling
CRANE_RUNNER_MAX_OUTPUT 262144 Bytes of combined stdout and stderr the runner will carry back. Above this the job is refused, with the size and the ceiling recorded against the run, and none of the output is sent. There is no setting for unbounded
CRANE_RUNNER_MAX_ARTIFACT 33554432 Bytes of any single file the runner hands over as an artifact. Separate from the output ceiling and much larger, because a tool that writes an SBOM has not printed it. Above this the job is refused rather than the file being skipped, so a run never succeeds with its answer missing. The server enforces the same number; there is no setting for unbounded
CRANE_RUNNER_WAIT_SECONDS 20 How long the server may hold a claim open waiting for work before answering “nothing”. Replaces asking repeatedly: the fleet makes one request per wait rather than one per poll interval, and a job queued a moment after a runner asked is picked up straight away instead of after a backed-off sleep. 0 asks once and returns, which is what a runner did before this existed. The server clamps anything above 30
CRANE_RUNNER_LEASE_SECONDS 300 How long each claim asks to hold a job. Between 30 and 3600; the server refuses a claim outside that. Heartbeats extend it while a command runs, so this is how long a runner that dies strands a job for
CRANE_RUNNER_POLL_SECONDS 5 How often an idle runner asks for work
CRANE_RUNNER_MAX_POLL_SECONDS 60 The ceiling the idle poll backs off to
CRANE_RUNNER_MAX_TIMEOUT_SECONDS 3600 The longest any one command may run on this machine, whatever the job definition asks for. The smaller of the two wins
CRANE_RUNNER_WORK_DIR the system temporary directory Where each command’s scratch directory is made, and removed afterwards

[commands] — which argv runs for which job definition — is file-only, and deliberately. It is the allowlist of what the machine will execute, and a mapping squeezed into an environment variable is one nobody can read. CRAne never sends a command: it sends a definition key and parameters, and the parameters reach the command on its stdin as JSON.

Authentication

There are no accounts and no roles. Authentication is one shared token, and every authenticated user is the same user.

Set CRANE_API_TOKEN and the whole interface and API require it:

docker run --rm -p 127.0.0.1:8000:8000 -v "$PWD/data:/data" \
    --user "$(id -u):$(id -g)" -e CRANE_API_TOKEN="$(openssl rand -hex 32)" \
    ghcr.io/oscarvalenzuelab/crane:latest serve --host 0.0.0.0 --port 8000 --worker

From a program, send it as either header:

curl -H "Authorization: Bearer $CRANE_API_TOKEN" http://127.0.0.1:8000/api/v1/status
curl -H "X-API-Token: $CRANE_API_TOKEN"          http://127.0.0.1:8000/api/v1/status

Without one, every route answers 401.

From a browser, go to /login and paste the token. That sets a session cookie and the rest of the interface works normally. /login itself is reachable without the token, for obvious reasons; nothing else is. Note that a browser hitting / directly gets a bare 401 rather than a redirect, so /login is the URL to hand people.

/healthz is public on purpose, even with a token set. The thing that restarts the container has no token, and “which build is actually running” is the first question after a deploy.

There is no sign-out control, because the session cookie is not what lets anybody in: the token is, and revoking it means changing CRANE_API_TOKEN and restarting.

Exposing it beyond this machine

Inside a container the server must bind 0.0.0.0 for a published port to reach it at all. It cannot see how the port was published, so it refuses the combination it cannot verify:

error: refusing to bind to 0.0.0.0 without CRANE_API_TOKEN. There is no authentication,
so this would put the whole inventory on the network.
  Set CRANE_API_TOKEN, which is the right answer almost always.
  Or bind to 127.0.0.1 if this is your own machine.
  Or set CRANE_ALLOW_UNAUTHENTICATED_BIND=1 if the port is already restricted by
  something outside this process.

Three configurations, and only the first two are safe to publish:

Situation What to set
Reachable by other people CRANE_API_TOKEN. Nothing else
Your own machine, port mapped to 127.0.0.1 CRANE_ALLOW_UNAUTHENTICATED_BIND=1
Your own machine, no container Bind 127.0.0.1, set nothing

CRANE_ALLOW_UNAUTHENTICATED_BIND=1 is an assertion you are making, and CRAne cannot check it. It says “something outside this process restricts who can reach the port”. In the published compose file that something is the 127.0.0.1:8000:8000 mapping. Change that mapping to 8000:8000 and the assertion becomes false, and the entire portfolio is readable by anyone who can route to the host. Set a token and delete the override.

Put a reverse proxy in front of it for TLS. CRAne serves plain HTTP and does not terminate TLS itself.

File ownership

The image runs as uid 10001. A bind mount keeps the host’s ownership, so the container’s user has to be able to write it:

export CRANE_UID=$(id -u) CRANE_GID=$(id -g)     # compose
docker run --user "$(id -u):$(id -g)" ...        # plain docker

Create the directories before the first run. Docker creates a missing bind mount source as root, and the container is not root, so its first write fails with a permission error that looks like a bug and is not.

Named volumes avoid all of this, at the cost of the database no longer being a file you can open with a SQLite browser. That is usually the wrong trade for this tool.

On Docker Desktop for macOS the file sharing layer maps ownership for you and --user can be omitted. On Linux it cannot.

SELinux

On Fedora, RHEL and derivatives, add :z to bind mounts or the container cannot read them:

docker run -v "$PWD/data:/data:z" ...

Paths inside the container

CRAne resolves every path on its own filesystem. A path that exists on your host does not exist inside the container unless you mounted it there.

You want to Do this
Import a file Put it under data/, refer to it as /data/<name>
Import from a source system Mount it at /sources, read only
Get a report out Write it to /exports
Register SBOM evidence Register the container path, not the host path

This is the single most common mistake, and it has its own section.

Validator profiles

export CRANE_OSSBOMER_BIN=/usr/local/bin/ossbomer   # only if not on PATH
export CRANE_OSSBOMER_PROFILE_PATH=/opt/our-profiles
crane validate profiles                             # confirms the wiring

Covered in full under The validator.


Back to top

CRAne. The database is one SQLite file and it is yours.

This site uses Just the Docs, a documentation theme for Jekyll.