Connect to the platform
Your first attestation signed and verified evidence entirely on your laptop with a local key. That's the whole loop, offline. Connecting to the TestifySec platform adds three things a local key can't:
- Keyless signing — sign against the platform's Fulcio with your identity, no key file to manage or leak.
- Storage + sharing — attestations upload to the platform's Archivista, so policy verification, dashboards, and teammates can find them by the artifact's digest.
- CI trust — let a GitHub/GitLab pipeline upload keylessly, without a long-lived secret.
The one thing to understand: signing needs no login, but uploading does. The platform derives Fulcio (signing), TSA (timestamping), and Archivista (storage) from a single --platform-url — but storing an attestation has to bind it to your tenant and product, and that binding is what a session carries.
0. Start where the first page ended
Work in the same demo-cilock directory. From Your first attestation you keep:
main.go,go.modand the builtmyapp: the build this page attests again.testkey.pemandtestpub.pem: a local scratch key. It stays useful for offline checks, but nothing on this page trusts it.build.attestation.json,policy.jsonandpolicy-signed.json: the local loop's evidence and policy. They trust onlytestpub.pem, so they cannot accept keyless evidence. This page writes a new policy beside them and does not change them.
Set the platform you are connecting to, and check it before you use it. Paste this into the shell you will use for the rest of the page:
PLATFORM=https://platform.testifysec.com # or your self-hosted platform's https:// URL
case "$PLATFORM" in
https://?*) echo "platform: $PLATFORM" ;;
*) echo "PLATFORM is not an https:// URL: '$PLATFORM'. Stopping before any command below runs." >&2; exit 2 ;;
esacIf the value is wrong, the check exits that shell, so no later command can run against the wrong platform. Open a new terminal, fix the URL and paste the block again. The check only proves the value looks like an https:// origin, not that it is a platform your organization approved.
Every command below passes --platform-url "$PLATFORM". Keep it on each one. Without it, login, whoami, use, doctor and trust talk to the hosted platform, not a self-hosted one you logged in to. With an empty value, run, sign and verify turn the platform off, as --offline did on the first page.
1. Log in
cilock login --platform-url "$PLATFORM"This opens a browser approve page and stores a session credential. The approve page binds a working tenant and product, creating a default tenant and product if you have none yet, so every attestation you upload is scoped to one.
Confirm it worked:
cilock whoami --platform-url "$PLATFORM"It prints the platform, the tenant, the bound product, your email: and the session expiry. That email is the identity in the certificate your keyless signatures carry; step 5 writes it into the policy.
Three identities can sign on this page and the next ones, and a policy trusts each one separately:
- You, signed in with
cilock login: a certificate naming your email. This page uses it. - An enrolled agent (
cilock enroll agent, approved by a human in the browser): a certificate naming the agent. Ifcilock whoamisays the machine signs as an enrolled agent, your runs sign as that agent, not as you, and the policy in step 5 must name the agent instead. - A CI workload (step 6): a certificate naming the pipeline.
The local testkey.pem from the first page is a fourth, and none of the three above is it.
2. (Optional) switch the working scope
If you belong to more than one tenant or want attestations under a different product, switch the binding — the cilock analog of kubectl config use-context:
# Pick or create a tenant/product interactively
cilock use --platform-url "$PLATFORM"
# Or bind a known product directly (no browser)
cilock use --platform-url "$PLATFORM" --product-id <uuid> --product-name acme-webcilock run then scopes every attestation to that product without re-prompting.
3. Preflight before a real run
Before a multi-minute build, confirm signing and upload will actually work — instead of discovering a misconfiguration afterward:
cilock doctor --platform-url "$PLATFORM"It prints a green/red checklist: logged in? platform reachable? Fulcio / TSA / Archivista resolved? upload authorized? Pass --json for a machine-readable report (report.ok) an agent can gate on.
4. Run with upload
Now the same cilock run you used locally, but keyless and uploaded:
cilock run \
--step build \
--platform-url "$PLATFORM" \
--enable-archivista \
-- go build -o myapp ./cilock signs keyless against the platform Fulcio, timestamps against its TSA, and uploads the signed DSSE envelope to your tenant's Archivista. The run summary prints the gitoid — the address other tools (and cilock verify --enable-archivista) use to retrieve the evidence by subject digest, no local bundle files required.
If the upload is rejected with a 401 / Invalid API credential, your session expired or the run targeted a platform you're not logged in to — re-run cilock login (or cilock doctor to see exactly which check fails).
5. Verify against a policy that trusts you
The first page's policy-signed.json trusts only the local key, so it refuses this keyless build. Write a policy whose functionary is you, as the platform's Fulcio certifies you, and leave its trust roots empty for the platform to fill:
{
"expires": "2030-12-17T23:57:40-05:00",
"steps": {
"build": {
"name": "build",
"attestations": [
{"type": "https://aflock.ai/attestations/material/v0.3", "regopolicies": []},
{"type": "https://aflock.ai/attestations/command-run/v0.2", "regopolicies": []},
{"type": "https://aflock.ai/attestations/product/v0.3", "regopolicies": []}
],
"functionaries": [{
"type": "root",
"certConstraint": {
"commonname": "*",
"dnsnames": ["*"],
"emails": ["__YOUR_EMAIL__"],
"organizations": ["*"],
"uris": ["*"],
"roots": ["fulcio-root"],
"extensions": {"issuer": "__PLATFORM__/fulcio/oidc"}
}
}]
}
},
"roots": {"fulcio-root": {"certificate": ""}},
"timestampauthorities": {"platform-tsa": {"certificate": ""}}
}Fill in your email (from cilock whoami) and the platform, and check both before writing:
EMAIL=$(cilock whoami --platform-url "$PLATFORM" | awk '$1 == "email:" {print $2}')
if printf '%s' "$EMAIL" | grep -Eq '^[^@ ]+@[^@ ]+