carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package runs
2
3import (
4 "crypto/sha256"
5 "encoding/binary"
6 "encoding/hex"
7 "hash"
8 "strings"
9 "time"
10)
11
12// A keyed create's bounds (service runs spec § 3). Each admission prunes the
13// record's run_keys to entries younger than KeyTTL, then keeps the newest
14// MaxRunKeysPerCaller entries of each caller. So a key is honoured for
15// KeyTTL, or until that same caller makes MaxRunKeysPerCaller newer keyed
16// admissions on this instance, whichever comes first. One caller's
17// admissions never evict another's key. A caller must not retry a key past
18// that: it is a fresh run then.
19const (
20 KeyTTL = 24 * time.Hour
21 MaxRunKeysPerCaller = 32
22 // MaxKeyBytes is the longest Idempotency-Key a caller may send.
23 MaxKeyBytes = 128
24)
25
26// ValidKey reports whether k is an Idempotency-Key: 1 to 128 bytes of
27// [A-Za-z0-9._:-].
28func ValidKey(k string) bool {
29 if len(k) == 0 || len(k) > MaxKeyBytes {
30 return false
31 }
32 for i := 0; i < len(k); i++ {
33 switch c := k[i]; {
34 case 'a' <= c && c <= 'z', 'A' <= c && c <= 'Z', '0' <= c && c <= '9':
35 case c == '.', c == '_', c == ':', c == '-':
36 default:
37 return false
38 }
39 }
40 return true
41}
42
43// ScopedKey is the key as the record stores it: hex(sha256(scope, NUL,
44// key)). scope names the caller ("svc:" and a credential id, or "user:"
45// and an address), so one caller's key can never find, probe or collide
46// with another's, and the record never holds the caller's own words.
47func ScopedKey(scope, key string) string {
48 h := sha256.Sum256([]byte(scope + "\x00" + key))
49 return hex.EncodeToString(h[:])
50}
51
52// CallerOf is the caller's name as a run_keys entry stores it:
53// hex(sha256("caller", NUL, scope)). It groups a caller's entries for the
54// per-caller cap without the record holding the caller's identity.
55func CallerOf(scope string) string {
56 h := sha256.Sum256([]byte("caller\x00" + scope))
57 return hex.EncodeToString(h[:])
58}
59
60// ValidFileHash reports whether h is a sha256 as a create may declare it: 64
61// hex digits, in either case. The console lowercases it (NormalFileHash)
62// before comparing it or digesting it.
63func ValidFileHash(h string) bool {
64 if len(h) != 64 {
65 return false
66 }
67 for i := 0; i < len(h); i++ {
68 if c := h[i]; !('0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F') {
69 return false
70 }
71 }
72 return true
73}
74
75// NormalFileHash is a declared sha256 in the lowercase form the console
76// computes.
77func NormalFileHash(h string) string { return strings.ToLower(h) }
78
79// MetaDigest is the hex sha256 of a canonical encoding of what a create
80// asks for: argv, timeout_s, on_behalf_of, and each file's name, size and
81// sha256 (lowercased), in meta's order. Every string and count is
82// length-prefixed, so no two different requests share an encoding. A retry
83// with the same key must have the same digest.
84func MetaDigest(m Meta) string {
85 h := sha256.New()
86 str(h, "carlos-run-meta-v1")
87 num(h, uint64(len(m.Argv)))
88 for _, a := range m.Argv {
89 str(h, a)
90 }
91 num(h, uint64(m.TimeoutS))
92 str(h, m.OnBehalfOf)
93 num(h, uint64(len(m.Files)))
94 for _, f := range m.Files {
95 str(h, f.Name)
96 num(h, uint64(f.Size))
97 str(h, NormalFileHash(f.SHA256))
98 }
99 return hex.EncodeToString(h.Sum(nil))
100}
101
102func num(h hash.Hash, n uint64) {
103 var b [8]byte
104 binary.BigEndian.PutUint64(b[:], n)
105 h.Write(b[:])
106}
107
108func str(h hash.Hash, s string) {
109 num(h, uint64(len(s)))
110 h.Write([]byte(s))
111}
112