carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostactors
2
3import (
4 "context"
5 "crypto/rand"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "os"
10 "strings"
11
12 "github.com/carlosframework/platform/internal/boxid"
13 "github.com/carlosframework/platform/internal/hostfloor"
14 "github.com/carlosframework/platform/internal/release"
15)
16
17type Invitation struct {
18 V int `json:"v"`
19 Bucket string `json:"bucket"`
20 Reference string `json:"reference"`
21 Enrollment string `json:"enrollment"`
22 EvidenceSHA256 string `json:"evidence_sha256"`
23}
24
25// Invite grants no membership. It gives an enrolled-identity candidate a
26// stable nonce with which to install its root guard and publish readiness.
27// A fleet caller passes an empty reference, then changes only boxid.Record.Box
28// to the returned Reference and marshals it again before publishing or Enroll.
29// That field is outside the AWS-signed identity document and signature.
30func (d Directory) Invite(ctx context.Context, sg release.Signer, reference string, evidence []byte) (Invitation, error) {
31 fleet := reference == ""
32 if !fleet && !segment(reference) {
33 return Invitation{}, ErrRoster
34 }
35 if !fleet {
36 key := "control/host-actor-invitations/" + reference + ".json"
37 raw, _, err := d.Store.Get(ctx, key)
38 if err == nil {
39 var invite Invitation
40 if release.VerifyRootEnvelope(d.PublicKey, raw, &invite) != nil || invite.V != 1 || invite.Bucket != d.Bucket || invite.Reference != reference || !token.MatchString(invite.Enrollment) || invite.EvidenceSHA256 != hostfloor.Digest(evidence) || (strings.HasPrefix(reference, "fleet-") && reference != "fleet-"+invite.Enrollment) {
41 return Invitation{}, ErrRoster
42 }
43 return invite, nil
44 }
45 if !errors.Is(err, os.ErrNotExist) {
46 return Invitation{}, err
47 }
48 // Fleet references are minted from their enrollment nonce. A caller may
49 // retry one returned by this function, but cannot choose a fresh one.
50 if strings.HasPrefix(reference, "fleet-") {
51 return Invitation{}, ErrRoster
52 }
53 }
54 var id [16]byte
55 if _, err := rand.Read(id[:]); err != nil {
56 return Invitation{}, err
57 }
58 enrollment := hex.EncodeToString(id[:])
59 if fleet {
60 reference = "fleet-" + enrollment
61 var rec boxid.Record
62 if json.Unmarshal(evidence, &rec) != nil || rec.V != 1 {
63 return Invitation{}, ErrRoster
64 }
65 rec.Box = reference
66 var err error
67 evidence, err = json.Marshal(rec)
68 if err != nil {
69 return Invitation{}, err
70 }
71 }
72 invite := Invitation{1, d.Bucket, reference, enrollment, hostfloor.Digest(evidence)}
73 raw, err := release.SignRootEnvelope(sg, invite)
74 if err != nil {
75 return Invitation{}, err
76 }
77 key := "control/host-actor-invitations/" + reference + ".json"
78 if _, err = d.Store.Put(ctx, key, raw, ""); err != nil {
79 return Invitation{}, err
80 }
81 return invite, nil
82}
83func (d Directory) enrollmentNonce(ctx context.Context, in EnrollmentRequest, evidence []byte) (string, error) {
84 key := "control/host-actor-invitations/" + in.Reference + ".json"
85 raw, _, err := d.Store.Get(ctx, key)
86 if err != nil {
87 return "", err
88 }
89 var invite Invitation
90 if release.VerifyRootEnvelope(d.PublicKey, raw, &invite) != nil || invite.V != 1 || invite.Bucket != d.Bucket || invite.Reference != in.Reference || !token.MatchString(invite.Enrollment) || ((strings.HasPrefix(in.Namespace, "fleet:") || strings.HasPrefix(in.Reference, "fleet-")) && in.Reference != "fleet-"+invite.Enrollment) || invite.EvidenceSHA256 != hostfloor.Digest(evidence) {
91 return "", ErrRoster
92 }
93 return invite.Enrollment, nil
94}
95