v0.28.1 cc7ba64

ci_oidc.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain
4
5import "time"
6
7// OIDC ID tokens of CI jobs (CI-4, D5): a job asks the server for an RS256 JWT
8// with GitHub's claims, which a relying party (Vault, a cloud) verifies against
9// the server's JWKS instead of holding a long-lived secret.
10
11// OIDCRequestTokenPrefix starts the secret a job presents to the token
12// endpoint (ACTIONS_ID_TOKEN_REQUEST_TOKEN). It is a job repository token
13// (CIRepoToken.OIDC) that authenticates nothing else.
14const OIDCRequestTokenPrefix = "ermiteoidc_"
15
16// IsOIDCRequestToken reports whether s has the shape of an ID token request
17// token.
18func IsOIDCRequestToken(s string) bool { return hasTokenShape(s, OIDCRequestTokenPrefix) }
19
20// PermIDToken is the permission that lets a job request ID tokens.
21const PermIDToken = "id-token"
22
23// IDTokenAllowed reports whether a job with the permissions stored for it may
24// request ID tokens: the run is trusted and the job has id-token: write. The
25// permissions are lowered by the trust rule first (TokenPermissions), so an
26// untrusted run never qualifies, whatever it asks.
27func IDTokenAllowed(stored map[string]string, trusted bool) bool {
28	return trusted && TokenPermissions(stored, trusted)[PermIDToken] == PermissionWrite
29}
30
31// OIDCKeyRetention is how long a replaced signing key stays in the JWKS: far
32// longer than the life of the tokens it signed (OIDCTokenLifetime).
33const OIDCKeyRetention = 24 * time.Hour
34
35// OIDCTokenLifetime is how long an ID token is valid after it is issued, and
36// OIDCNotBeforeSkew how far before its issue it is already valid (GitHub's
37// values: exp = iat + 5 min, nbf = iat - 10 min).
38const (
39	OIDCTokenLifetime = 5 * time.Minute
40	OIDCNotBeforeSkew = 10 * time.Minute
41)
42
43// OIDCRunnerEnvironment is the runner_environment claim: ermite's runners are
44// the owner's machines, which GitHub calls self-hosted.
45const OIDCRunnerEnvironment = "self-hosted"
46
47// MaxOIDCAudienceLen bounds the audience a job may ask for.
48const MaxOIDCAudienceLen = 1024
49
50// OIDCKey is a signing key of the ID tokens as stored: the private key sealed
51// with the secrets key, bound to its path (OIDCKeyPath). A key without
52// RetiredAt signs; a retired one is only published until RetiredAt plus
53// OIDCKeyRetention.
54type OIDCKey struct {
55	// ID is the kid: the RFC 7638 thumbprint of the public key.
56	ID string
57	// Sealed is the PKCS #8 DER of the private key, sealed.
58	Sealed    []byte
59	CreatedAt time.Time
60	RetiredAt time.Time
61}
62
63// OIDCKeyPath is where the key with that id is stored, and the additional data
64// it is sealed with, so that a sealed key moved to another id does not open.
65func OIDCKeyPath(kid string) string { return "oidc/key/" + kid }
66
67// Published reports whether the key is still in the JWKS at now.
68func (k OIDCKey) Published(now time.Time) bool {
69	return k.RetiredAt.IsZero() || now.Before(k.RetiredAt.Add(OIDCKeyRetention))
70}
71
72// OIDCSubject is the sub claim of a job's ID token in GitHub's default format:
73// the environment of the job when it has one, else "pull_request" for the
74// events of merge requests, else the ref. repository is "owner/name".
75//
76// GitHub gives pull_request_target runs the pull_request context too, though
77// they run the workflow of the default branch: a relying party that trusts
78// "repo:o/r:pull_request" trusts both.
79func OIDCSubject(repository, eventName, environment, ref string) string {
80	prefix := "repo:" + repository + ":"
81	switch {
82	case environment != "":
83		return prefix + "environment:" + environment
84	case eventName == "pull_request" || eventName == "pull_request_target":
85		return prefix + "pull_request"
86	}
87	return prefix + "ref:" + ref
88}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 44

↑↓ moveenter openesc close