Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
659aeb934a | ||
|
|
148259040c |
56
documents/N_OS_TR_AGENT_CHANGES.md
Normal file
56
documents/N_OS_TR_AGENT_CHANGES.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# n_OS_tr Agent Guide: Handling n_signer v0.0.16 Changes
|
||||
|
||||
This is a short operational guide for the n_OS_tr agent after the approval-model update in `n_signer`.
|
||||
|
||||
## What changed
|
||||
|
||||
- `n_signer` is now **deny-by-default**.
|
||||
- First-time caller/index combinations require approval unless preapproved.
|
||||
- `nostr_index` values can be auto-derived on approval.
|
||||
- Policy decisions are visible in activity labels: `:preapprove`, `:session-grant`, `:prompt`, `:no-match`.
|
||||
|
||||
## What the agent should do
|
||||
|
||||
1. **Always send explicit selector options** in the last `params` item:
|
||||
- Prefer `{"nostr_index": N}` for service identities.
|
||||
2. **Expect `policy_denied` on first contact** if caller/index is not preapproved.
|
||||
3. **Do not retry blindly** on `policy_denied`.
|
||||
- Surface a clear status: "approval needed".
|
||||
4. **Treat `unknown_role` as actionable**:
|
||||
- If using `nostr_index`, request again only after human approval or valid preapprove config.
|
||||
5. **Use stable caller identity** (service name / VM identity) so preapprove rules match reliably.
|
||||
|
||||
## Required startup pattern for n_OS_tr services
|
||||
|
||||
Launch `n_signer` with explicit `--preapprove` entries for each boot service that must sign without prompts.
|
||||
|
||||
Example:
|
||||
|
||||
```ini
|
||||
ExecStart=/usr/bin/n_signer \
|
||||
--preapprove caller=qubes:nostr-relay,role=main \
|
||||
--preapprove caller=qubes:dm-handler,nostr_index=1 \
|
||||
--preapprove caller=qubes:contacts,nostr_index=2 \
|
||||
--preapprove caller=qubes:zaps,nostr_index=3
|
||||
```
|
||||
|
||||
## Agent error-handling policy (recommended)
|
||||
|
||||
- `policy_denied` → stop, mark as "awaiting approval/preapprove".
|
||||
- `unknown_role` with `nostr_index` → stop, mark as "identity not yet approved/created".
|
||||
- `purpose_mismatch` / `curve_mismatch` → configuration bug; fail hard and alert.
|
||||
- `ambiguous_role_selector` → client bug; send exactly one selector field.
|
||||
|
||||
## Observability checks
|
||||
|
||||
When troubleshooting, inspect n_signer activity lines:
|
||||
|
||||
- `ALLOWED:preapprove` → expected for boot services.
|
||||
- `ALLOWED:session-grant` → interactive approval happened earlier this session.
|
||||
- `ALLOWED:prompt` → just approved now.
|
||||
- `DENIED:no-match` → no policy entry matched caller/index.
|
||||
|
||||
## Operational rule of thumb
|
||||
|
||||
For n_OS_tr system services, rely on `--preapprove` at startup.
|
||||
For ad-hoc/manual clients, rely on interactive prompt approvals.
|
||||
@@ -468,8 +468,8 @@ int socket_name_random(char *out, size_t out_len);
|
||||
/* Version information (auto-updated by build/version tooling) */
|
||||
#define NSIGNER_VERSION_MAJOR 0
|
||||
#define NSIGNER_VERSION_MINOR 0
|
||||
#define NSIGNER_VERSION_PATCH 16
|
||||
#define NSIGNER_VERSION "v0.0.16"
|
||||
#define NSIGNER_VERSION_PATCH 18
|
||||
#define NSIGNER_VERSION "v0.0.18"
|
||||
|
||||
|
||||
/* NSIGNER_HEADERLESS_DECLS_END */
|
||||
|
||||
17
src/server.c
17
src/server.c
@@ -271,7 +271,8 @@ typedef struct {
|
||||
|
||||
/* Policy check result */
|
||||
#define POLICY_ALLOW 0
|
||||
#define POLICY_ALLOW_SESSION 3 /* allow + save as session grant */
|
||||
#define POLICY_ALLOW_SESSION_VERB 3 /* allow + save session grant for this caller+role+verb */
|
||||
#define POLICY_ALLOW_SESSION_ALL 4 /* allow + save session grant for this caller+role (all verbs) */
|
||||
#define POLICY_DENY -1
|
||||
#define POLICY_PROMPT -2 /* would need user confirmation */
|
||||
#define POLICY_NO_MATCH -3 /* no policy entry matched (fail-closed = deny) */
|
||||
@@ -691,7 +692,8 @@ static int prompt_for_policy_decision(const caller_identity_t *caller,
|
||||
if (pending_derivation) {
|
||||
printf(" ** NEW IDENTITY — will be derived if approved **\n");
|
||||
}
|
||||
printf("[y] allow once [n] deny [a] allow this caller+role for session\n> ");
|
||||
printf("[y] allow once [n] deny [e] allow this caller+role+verb for session\n");
|
||||
printf("[a] allow this caller+role for session (all verbs)\n> ");
|
||||
fflush(stdout);
|
||||
|
||||
ch = getchar();
|
||||
@@ -704,7 +706,10 @@ static int prompt_for_policy_decision(const caller_identity_t *caller,
|
||||
|
||||
ch = tolower(ch);
|
||||
if (ch == 'a') {
|
||||
return POLICY_ALLOW_SESSION;
|
||||
return POLICY_ALLOW_SESSION_ALL;
|
||||
}
|
||||
if (ch == 'e') {
|
||||
return POLICY_ALLOW_SESSION_VERB;
|
||||
}
|
||||
if (ch == 'y') {
|
||||
return POLICY_ALLOW;
|
||||
@@ -1284,13 +1289,17 @@ int server_handle_one(server_ctx_t *ctx, server_activity_cb cb, void *cb_data) {
|
||||
|
||||
if (pchk == POLICY_PROMPT) {
|
||||
pchk = prompt_for_policy_decision(&caller, method, role_name, purpose, pending_derivation);
|
||||
if (pchk == POLICY_ALLOW_SESSION) {
|
||||
if (pchk == POLICY_ALLOW_SESSION_VERB || pchk == POLICY_ALLOW_SESSION_ALL) {
|
||||
policy_entry_t grant;
|
||||
|
||||
memset(&grant, 0, sizeof(grant));
|
||||
strncpy(grant.caller, caller.caller_id, sizeof(grant.caller) - 1);
|
||||
strncpy(grant.roles[0], role_name, ROLE_NAME_MAX - 1);
|
||||
grant.role_count = 1;
|
||||
if (pchk == POLICY_ALLOW_SESSION_VERB) {
|
||||
strncpy(grant.verbs[0], method, POLICY_VERB_MAX_LEN - 1);
|
||||
grant.verb_count = 1;
|
||||
}
|
||||
grant.prompt = PROMPT_NEVER;
|
||||
grant.source = POLICY_SOURCE_SESSION_GRANT;
|
||||
if (policy_table_insert_before_last(ctx->policy, &grant) != 0) {
|
||||
|
||||
Reference in New Issue
Block a user