Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

395 commit(s) on main

update readme Keenan Nemetz · 29m ago 32290114
history blame raw 3.4 KiB
1package ssh
2
3// The key a job acts with.
4//
5// A workflow that names `acts-as: <account>` gets a credential for that account
6// when the job is leased: an ed25519 pair generated here, the public half stored in
7// the queue with the run, repository and expiry, and the private half sent on the
8// lease channel. The runner writes it into the job's run directory, so a step's
9// `git push` or `vole issue comment` authenticates as the account without the
10// repository holding a long-lived key.
11//
12// Two checks decide whether the job can have it: the account must exist, be active,
13// and be a bot account. The role it may act with is not decided here — the hook
14// applies the same role rules to a credential session that it applies to the
15// account's own key, so a job cannot act as more than its account may.
16
17import (
18 "crypto/ed25519"
19 "crypto/rand"
20 "encoding/pem"
21 "fmt"
22 "time"
23
24 xssh "golang.org/x/crypto/ssh"
25 "rodent.software/vole/ci/runs"
26)
27
28const (
29 // credentialGrace is how long a run's key remains valid after the job's own
30 // budget ends. A step may need to push its last commit after the last step, and
31 // the runner's finish is what normally ends the key; this is the backstop for a
32 // runner that dies without one.
33 credentialGrace = 30 * time.Minute
34 // credentialMin and credentialMax bound the backstop. A run with no budget gets
35 // the minimum; a long one is still capped, so a key cannot outlive its job by a
36 // working day.
37 credentialMin = time.Hour
38 credentialMax = 6 * time.Hour
39)
40
41// mintCredential issues a key for one run and returns the private half and the
42// instance's host key line. A non-empty reason means the run asked for an account it
43// cannot have, and the job fails with that line instead of running without a key.
44func (s *Server) mintCredential(run *runs.Run, now time.Time) (*runnerCredential, string, string) {
45 acct, err := s.cfg.Identity.Find(run.Bot)
46 if err != nil || acct == nil {
47 return nil, "", fmt.Sprintf("this job acts as %s, which is not an account on this instance", run.Bot)
48 }
49 if !acct.Active() {
50 return nil, "", fmt.Sprintf("this job acts as %s, which is retired", run.Bot)
51 }
52 if !acct.Bot() {
53 return nil, "", fmt.Sprintf("this job acts as %s, which is not a bot account", run.Bot)
54 }
55 pub, priv, err := ed25519.GenerateKey(rand.Reader)
56 if err != nil {
57 return nil, "", "cannot generate a credential: " + err.Error()
58 }
59 publicKey, err := xssh.NewPublicKey(pub)
60 if err != nil {
61 return nil, "", "cannot prepare a credential: " + err.Error()
62 }
63 block, err := xssh.MarshalPrivateKey(priv, "vole run "+run.ID)
64 if err != nil {
65 return nil, "", "cannot encode a credential: " + err.Error()
66 }
67 fingerprint := xssh.FingerprintSHA256(publicKey)
68 if err := s.cfg.Runs.SetRunCredential(runs.RunCredential{
69 Fingerprint: fingerprint,
70 Account: run.Bot,
71 Repo: run.Repo,
72 RunID: run.ID,
73 Expires: now.Add(credentialTTL(run.Timeout)),
74 }, now); err != nil {
75 return nil, "", "cannot record a credential: " + err.Error()
76 }
77 return &runnerCredential{
78 PrivateKey: string(pem.EncodeToMemory(block)),
79 Fingerprint: fingerprint,
80 }, s.hostKey, ""
81}
82
83// credentialTTL is how long a run's key may live: its own budget plus a grace, so a
84// step that pushes at the end of the budget is not cut off, bounded at both ends.
85func credentialTTL(budget string) time.Duration {
86 ttl := credentialMin
87 if d, err := time.ParseDuration(budget); err == nil && d > 0 {
88 ttl = d
89 }
90 return min(ttl+credentialGrace, credentialMax)
91}
92