Repository navigation
feat: scheduled grants: approve now, use later, with an end time and a number of uses #108
Description
Why
A job that runs later (a timer, a job runner) cannot tap a button. Today there are two ways:
- a "never ask" rule in
broker.toml(DESIGN.md decision 3 B): it needs a config edit, and it stays open until the owner removes it; - a tap at run time, often at night, when the owner sleeps. The job waits or fails.
The owner often knows hours ahead that a job will need a secret: at the time the job is planned. A tap at that time, while the owner is awake, should still be valid when the job runs, and end by itself.
#103 step 9 already plans a one-use pre-approval for a login that lasts about 60 s. This issue is the same idea for any secret or SSH key, with a start time in the future, an absolute end time and a number of uses.
Idea (decide the design when the work starts)
-
Request. A new client command, for example:
nodkey preapprove --secret backup-token \ --from 2026-10-28T03:00 --until 2026-10-28T04:00 --uses 1 \ --reason "job nightly-backup"It hands out nothing. It only asks for a tap. Times need a UTC offset, or are read as broker local time.
-
Message. For example: "approve 1 use of
backup-tokenfrom laptop, between 03:00 and 04:00 on 2026-10-28. Reason (claimed): job nightly-backup". Buttons: Approve and Deny. After Approve, the message keeps a Revoke button until the grant is used up or ends. -
Scheduled grant. The broker keeps (client, secret, not before, not after, uses left). For an SSH key: (client, key, host key, remote user), as
nodkey ssh-grantdoes. Times are absolute, so summer time does not change them. The message shows broker local time. -
Use. A request from the same client, for that secret, inside the time, takes one use without a tap. Each use sends the silent notice, as a "never ask" use does (section 12). When no uses are left, or the time ends, the grant is gone.
-
Not used. A grant that ends with uses left sends a notice ("not used"). The job did not run, or ran late.
-
Limits (config, with safe defaults): how far ahead a grant may start (for example 7 days), how long it may be open (for example 8 hours), the most uses (for example 10), and the most pending grants per client.
-
Rules. A secret allows it only when its rule says so (
preapprove = true; off by default). As for any grant, it starts only after itsdecisionentry is on disk (sections 8 and 9). If the notice of a use cannot be sent, the use is denied (section 12), and these notices count toward the limits of 6 per client and 20 in all, in any 60 seconds. -
Audit log. New events: the request, the decision, each use, the end with uses left, and revoke.
-
Job identity (later, feat: job identity: tokens signed by a job runner, so rules and grants can name one job #109). With signed job tokens, a scheduled grant can also name a job (name and hash of its definition), or one run. Then only a request with a matching token uses it.
The honest limit
With the client as the only identity, anything on that client can use the grant inside its time. This is decision 3 B again, but smaller: it is made by a tap and not by a config edit, it ends by itself, and its uses are counted. If something else uses it up, the real job fails, and that is a visible sign.
Two more limits:
- The agent can ask for a pre-approval at any time, with any reason, such as "job nightly-backup". The reason is claimed. The approver cannot check that such a job exists. A tap must be read as "this client may use this secret in this time", nothing more.
- After a use, the secret stays on the agent machine (THREAT-MODEL.md section 5, item 1).
Open questions
- Broker restart. A restart ends all grants today. Keep scheduled grants across a restart (saved state, checked against the audit log) or drop them, so the job asks as usual? Proposed: drop them, and send one notice that lists the dropped grants.
- Several secrets in one run (
nodkey run --secret A_VAR=A --secret B_VAR=B): one pre-approval for all of them, as one window tap makes one grant for each secret? - Cancel by the client without a tap, when the job is removed. Proposed: yes, because a cancel can only take rights away.
- The broker clock set back inside the time of a grant: the uses already made still count, as for
max_per_day. - A later Deny. Section 8 says a Deny also stops a grant from an earlier tap that has not started yet. A scheduled grant is such a grant. Does a later Deny for the same client and secret cancel it? Proposed: yes, and the message says so.
- The
windowof a rule. Section 8 says a grant is never longer than the rule's window. Is a scheduled grant's open time also capped bywindow, or only by the new limits in step 6?
Done when
- DESIGN.md has scheduled grants: the request, the message, what the broker keeps, use, end, revoke and the audit events. THREAT-MODEL.md has the new threat: a planned grant used by something else on the same client.
- A scheduled grant is used only by the same client, for the named secret or key, inside its time, and at most N times. A test for each, and a test for the exact end time.
- Tests for the limits: how far ahead, how long open, uses, pending grants per client. Tests for what is decided about a later Deny and a broker restart.
- Revoke on the phone and cancel by the client work. Tests.
- A grant that ends with uses left sends a notice. A test with the fake Telegram server.
- docs/setup.md shows a job that asks for its pre-approval when it is planned.
- It gets the security review, as every change does.
Related: #103 (one-use pre-approval for a login, step 9), #29 (reload the config without a restart).
Activity
groupsky commentedon Oct 27, 2026
One grant core with #103 step 9 and standing grants (#110): see the comment on #103. Open question 1 (broker restart) must match #110, where standing grants survive a restart.
groupsky commentedon Oct 27, 2026
Grants that survive a broker restart: #111 (for later). It answers the restart open question here.
feat: scheduled grants with nodkey preapprove (part 1 of #108) (#112)
feat: revoke button and notices for scheduled grants (part 2 of #108) (…
groupsky commentedon Oct 28, 2026
Done in two pull requests: #112 (part 1,
102d0bf) and #114 (part 2,89d07e9). CI was green on both.Done when, box by box:
internal/approval/preapprove_test.go:TestScheduledGrantScope: same client and named secrets only.TestScheduledGrantKey: named hosts and the one remote user; each login takes one use.TestScheduledGrantTime: the first moment is inside, and the exact end time is outside.TestScheduledGrantUses: at most N uses.TestScheduledGrantSeveralSecrets,TestScheduledGrantDoesNotMix,TestScheduledUseConcurrent,TestScheduledUseClockSetBack,TestScheduledUseWallClockForward.TestPreapproveLimits: max_ahead, max_open, max_uses, at the border and one over.TestPreapproveMaxGrants: grants per client.TestPreapproveLimitsFromConfig,TestLoadBrokerPreapproveLimits,TestPreapproveOffByDefault,TestPreapproveRules.TestScheduledGrantDeny,TestScheduledGrantDenyBeforeStart,TestGrantEndChangesMessage,TestCoreGrantEndMessages.TestScheduledGrantStops,TestStopNoticeListsGrants,TestStopNoticeNoGrants,TestStopNoticeTelegramSilent,TestStopDeniesBeforeTheNoticeGoesOut.TestRevokeEndsGrant,TestRevokeRefused,TestRevokeBeforeStart,TestPreapproveRevoke.TestCoreRevoke,TestCoreRevokeRefused,TestCoreRevokeSkipsEditedCheck,TestCoreRevokeOnWaitingMessage.TestScheduledGrantCancel,TestPreapproveCancel,TestPreapproveCancelCommand,TestPreapproveCancelLeftover.TestCoreNotUsedNotice(internal/telegram/revoke_test.go), plusTestNotUsedNoticeandTestNotUsedNoticeLimits.nodkey preapprove --secret backup-token ...). It also says that each SSH connection takes one use, so a tool with many connections needsControlPersistor more uses.security-revieweragent ran on each pull request and on each round of fixes. There were no critical or high findings. Every finding was fixed; the lists are in the descriptions of feat: scheduled grants with nodkey preapprove (part 1 of #108) #112 and feat: revoke button and notices for scheduled grants (part 2 of #108) #114.Two notes, not boxes:
editMufix in feat: revoke button and notices for scheduled grants (part 2 of #108) #114 (item 8) has no test.docs/manual-telegram-test.md(Revoke and the notices) has not yet been run with a real bot.Later work: #110 (standing grants), #111 (grants that survive a restart).