Configure
Everything is an environment variable. No configuration file, no hardcoded path.
- Environment variables
- Authentication
- Exposing it beyond this machine
- File ownership
- Paths inside the container
- 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=1is 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 the127.0.0.1:8000:8000mapping. Change that mapping to8000:8000and 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.