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/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

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

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 54m ago 4b8c9e1d
history blame raw 2.7 KiB
1package agent
2
3// The environment a job acting as a bot gets.
4//
5// The credential is a file in the run directory, and git and the `vole` CLI both
6// already honour an ssh command, so a step needs no special handling: `git push` and
7// `vole issue comment` authenticate as the account because the environment points at
8// the key. The names are reserved in the workflow format, so a workflow cannot
9// replace them.
10
11import (
12 "fmt"
13 "net/url"
14 "strings"
15)
16
17// RunCredentialPath is where the key is written inside the job's container.
18const RunCredentialPath = "/run/vole/id_bot"
19
20// RunKnownHostsPath is where the instance's host key is written.
21const RunKnownHostsPath = "/run/vole/known_hosts"
22
23// withCredentialEnv returns a copy of the job's environment with the variables that
24// point git and the CLI at the run credential. It copies rather than editing the
25// pinned map, which belongs to the run record.
26func withCredentialEnv(env map[string]string, remote string, job *runnerJob) (map[string]string, error) {
27 sshCommand, err := credentialSSHCommand(remote)
28 if err != nil {
29 return nil, err
30 }
31 out := make(map[string]string, len(env)+5)
32 for name, value := range env {
33 out[name] = value
34 }
35 out["VOLE_RUN_ID"] = job.ID
36 out["VOLE_REPO"] = job.Repo
37 out["VOLE_SSH_COMMAND"] = sshCommand
38 out["GIT_SSH_COMMAND"] = sshCommand
39 out["VOLE_KNOWN_HOSTS"] = RunKnownHostsPath
40 return out, nil
41}
42
43// credentialSSHCommand pins the key and the known_hosts file. IdentitiesOnly keeps
44// ssh from offering an agent key that happens to be in the container, so the run
45// credential is the only identity a step can present.
46func credentialSSHCommand(remote string) (string, error) {
47 if _, _, err := splitRemote(remote); err != nil {
48 return "", err
49 }
50 return "ssh -i " + RunCredentialPath + " -o IdentitiesOnly=yes -o UserKnownHostsFile=" + RunKnownHostsPath, nil
51}
52
53// knownHostsLine is the entry that lets the job verify the instance it pushes to.
54// OpenSSH brackets a host whose port is not the default.
55func knownHostsLine(remote, hostKey string) (string, error) {
56 host, port, err := splitRemote(remote)
57 if err != nil {
58 return "", err
59 }
60 if strings.TrimSpace(hostKey) == "" {
61 return "", fmt.Errorf("the instance sent no host key with the job, so the credential has nothing to verify it against")
62 }
63 if port == "" || port == "22" {
64 return host + " " + strings.TrimSpace(hostKey) + "\n", nil
65 }
66 return "[" + host + "]:" + port + " " + strings.TrimSpace(hostKey) + "\n", nil
67}
68
69func splitRemote(remote string) (host, port string, err error) {
70 u, err := url.Parse(remote)
71 if err != nil || u.Scheme != "ssh" || u.Hostname() == "" {
72 return "", "", fmt.Errorf("%q is not an ssh URL, so the job cannot be told how to reach the instance", remote)
73 }
74 return u.Hostname(), u.Port(), nil
75}
76