PROCESS GATE
Run a program from a plan¶
Some of the work you want an agent to do has no API: a release script, a build, a report generator, an on-premises tool. EKKA runs that program under governance, and the plan never gets to write a command line.
This capability is on the Developer Edition
ekka org edition prints the edition your organization is on and what it
carries.
A plan names a recipe, never a program¶
There is no field in a plan step that holds a program, a flag, or a command. A step names a recipe and supplies values for the parameters that recipe declares, and that is the whole surface.
A recipe is registered by a person, on the machine where the program lives. It has four parts:
- One program, by absolute path. It is never looked up on
PATH, so which binary runs cannot change with the environment a process happens to start in. - A fixed argument list. Written once, in the recipe. A plan cannot add an argument, reorder one, or remove one.
- Named parameters, each restricted. Every parameter declares what its values may be. A parameter with no restriction is refused at registration, because a parameter that accepts anything is an open argument under another name.
- Limits on the run: a time limit, output caps, whether the network is reachable, and which environment variables survive.
EKKA starts the program directly. There is no shell in this path, which is why a value can never be re-read as a second command.
Write a recipe¶
A recipe is one JSON file, and it carries its own name, so the same file registers the same recipe on your laptop or from CI.
{
"name": "disk-report",
"program": "/bin/df",
"argv": ["-h", "{mount}"],
"params": {
"mount": {
"description": "Which filesystem to report on",
"restrict": { "kind": "enum", "values": ["/", "/var"] }
}
},
"limits": {
"timeout_ms": 10000,
"stdout_max_bytes": 65536,
"stderr_max_bytes": 65536
}
}
Check it before you register it. validate applies every rule that add
applies, reads the program so the digest is the one a real registration would
pin, and registers nothing:
✓ disk-report would be registered. Nothing was registered.
IT WOULD RUN
• /bin/df
• -h
• {mount}
The program is pinned by its contents: editing it stops every grant for this
recipe until somebody registers and grants again.
fingerprint sha256:a2ff32d800dac54fa1fad015f22b0e0264132145b421a79c23e6c7c264d3e1aa
register it ekka process recipe add <file.json>
The fingerprint covers the whole recipe: the program, its content digest, every argument, every parameter restriction, and the limits. Change any of them and the fingerprint changes. Raising the time limit in that file from 10000 to 20000 is enough:
What a parameter may accept¶
Every parameter declares one restriction.
restrict.kind |
Accepts |
|---|---|
enum |
One of a fixed list of values written in the recipe. The only kind whose values may begin with -, because a person wrote the list and can read all of it before granting. |
integer |
Digits, inside the min and max the recipe sets. |
segment |
One path segment: letters, digits, dot, dash, underscore. No separator, so a value cannot climb into another folder. |
relative-path |
Letters, digits, dot, dash, underscore and /, with no .. segment and no leading /. |
text |
Any characters except control characters, up to the max_len the recipe sets. |
url |
An http:// or https:// URL, with no whitespace and no user:password in front of the host. |
A value that fails its restriction is refused. It is never trimmed, quoted or escaped into something acceptable, and the refusal does not print the value, because a parameter can carry a credential:
ekka process test: parameter `mount` is not one of the values this recipe allows
what this recipe accepts ekka process recipe show disk-report
One parameter is one argument¶
A value containing spaces stays one argument. It cannot split into two, and it cannot become a flag:
That is a property of how the program is started, not a filter that has to recognize dangerous input. One argument goes to the program as one word, exactly as written.
Two programs a recipe cannot name¶
A shell, or a program whose own first argument is another program, hands back
everything the recipe grammar withholds. Registration refuses both, on the
program's name, so a copy of bash under another directory is refused too:
ekka process recipe validate: `program` is `bash`, which is a shell or takes a program as an argument. Its first argument would be a new program, which is what the argv template exists to prevent
what a recipe looks like ekka process recipe show <an existing recipe>
A program named without a full path is refused for the same family of reason:
ekka process recipe validate: `program` must be an absolute path, so which binary runs cannot depend on PATH
Register it, then grant it¶
✓ Registered recipe disk-report
IT RUNS
• /bin/df
• -h
• {mount}
The program is pinned by its contents: editing it stops every grant for this
recipe until somebody registers and grants again.
fingerprint sha256:a2ff32d800dac54fa1fad015f22b0e0264132145b421a79c23e6c7c264d3e1aa
Nothing can invoke this yet. Granting is a separate decision, and the grant
covers exactly what is printed above.
Registering a recipe permits nothing. Granting is a second command, and that gap is where a person reads what they are about to allow:
ekka gate list # find the process gate in your Enclave
ekka process recipe show disk-report # the whole of what a grant would cover
ekka process test disk-report --param mount=/
ekka process test resolves the recipe with real values and prints the program
and every argument, one per line, so the boundary between arguments is visible.
It starts nothing.
Then grant it:
ekka gate grant add --type process --instance <the process gate> \
--resource recipes/disk-report --capability process.invoke \
--no-fingerprint
--instance is the process gate as ekka gate list prints it, for example
process/enclave3a7e592fProcess. --no-fingerprint states that the resource is
a recipe name on that machine rather than an alias held in a gate directory.
Call it from a plan¶
The step names the recipe and the declared parameters, and nothing else:
{
"id": "runIt",
"action_ref": "ekka.gate.process.v1",
"executor_type": "process_gate",
"op": "invoke",
"feature": "process.invoke",
"resource": "recipes/disk-report",
"target": "enclave3a7e592fProcess",
"identity": { "requires_user_context": false },
"inputs": { "params": { "mount": "/" } },
"step_input_contract": {
"type": "object",
"properties": { "params": { "type": "object" } },
"required": ["params"]
},
"step_output_contract": {
"type": "object",
"properties": { "exit_code": { "type": "number" } },
"required": ["exit_code"]
}
}
target is the process gate inside your Enclave, as ekka gate list prints it.
resource is the recipe, always as recipes/<name>. Every parameter value is a
string. Check the whole file the way plan create would, creating nothing:
✓ Valid: [email protected]. Nothing was created.
It would run as agent core-c31f485e.
See Multi-step plans and schedules for the rest of the
plan file, and CLI reference for
every ekka process command.
What the receipt records¶
A run of a recipe produces a signed receipt like every other governed operation. It records:
- the recipe name and its fingerprint,
- the outcome and the exit code,
- how long the program ran,
- how many bytes it wrote to output and error, and a sha256 digest of each,
- whether output was cut at the recipe's cap,
- the names of the parameters that were supplied,
- whether the program was pinned by its contents for that run.
It does not record the program path, the arguments as they were resolved, the environment, the text the program wrote, or any parameter value. The recipe name plus its fingerprint is better evidence than a copied command line: a fingerprint can be checked against the registered recipe, while a copied argument list is only a claim about it.
ekka receipts show <receipt-id> -v # the recorded facts, in full
ekka receipts verify # the chain those facts sit in
Limits¶
- A recipe bounds how a program starts. Once running, it has the access to the machine that the account running your Enclave has.
- Programs run in the data folder your Enclave was started with, stated by the Enclave. A plan does not choose the working directory.
- The environment is empty unless the recipe lists variable names to pass through, and the network is closed unless the recipe opens it.
- Removing a recipe stops every grant naming it. Registering that name again with different contents produces a different fingerprint.
- Output above the recipe's cap is discarded, and the receipt records that the output was cut.
A recipe whose program must be confined further needs confinement from the operating system around it, the same way an API alias whose query surface must be bounded needs an endpoint built for the purpose.