package hostactors import ( "context" "crypto" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/json" "github.com/carlosframework/platform/internal/boxid" "testing" ) func TestEnrollmentUsesSignedCloudAndRefusesRebinding(t *testing.T) { d, sg, o := fixture(t) key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatal(err) } cert := &x509.Certificate{PublicKey: &key.PublicKey, PublicKeyAlgorithm: x509.RSA} document := []byte(`{"accountId":"123","region":"eu-west-1","instanceId":"i-new"}`) sum := sha256.Sum256(document) signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, sum[:]) if err != nil { t.Fatal(err) } rec := boxid.Record{V: 1, Box: "new-label", IdentityDocument: base64.StdEncoding.EncodeToString(document), IdentitySignature: base64.StdEncoding.EncodeToString(signature)} raw, _ := json.Marshal(rec) v := boxid.Verifier{Account: "123", Certs: map[string]*x509.Certificate{"eu-west-1": cert}} in := EnrollmentRequest{Reference: rec.Box, Namespace: "platform", Label: rec.Box, Region: "ie", Pool: "default", AWSRegion: "eu-west-1", Operator: "operator", ExistingReference: true, TofuInstanceID: "i-new"} for _, wrong := range []string{"region", "tofu", "missing-tofu", "operator", "signature"} { request := in body := raw switch wrong { case "region": request.AWSRegion = "us-east-1" case "tofu": request.TofuInstanceID = "i-other" case "missing-tofu": request.TofuInstanceID = "" case "operator": request.Operator = "" case "signature": bad := rec bad.IdentitySignature = base64.StdEncoding.EncodeToString([]byte("wrong")) body, _ = json.Marshal(bad) } if _, err = d.Enroll(context.Background(), sg, o, v, request, body); err == nil { t.Fatal(wrong) } } if _, err = d.Invite(context.Background(), sg, in.Reference, raw); err != nil { t.Fatal(err) } next, err := d.Enroll(context.Background(), sg, o, v, in, raw) if err != nil { t.Fatal(err) } got := next.Roster.Boxes[rec.Box] if got.Cloud.InstanceID != "i-new" || got.BoxID != rec.Box || got.Enrollment == "" { t.Fatal(got) } duplicate := rec duplicate.Box = "second-label" duplicateRaw, _ := json.Marshal(duplicate) duplicateRequest := in duplicateRequest.Reference = duplicate.Box duplicateRequest.Label = duplicate.Box if _, err := d.Invite(context.Background(), sg, duplicate.Box, duplicateRaw); err != nil { t.Fatal(err) } if _, err := d.Enroll(context.Background(), sg, next, v, duplicateRequest, duplicateRaw); err == nil { t.Fatal("one instance enrolled under a second reference") } in.Reference = "box" rec.Box = "box" raw, _ = json.Marshal(rec) if _, err = d.Enroll(context.Background(), sg, next, v, in, raw); err == nil { t.Fatal("reference rebound") } } func TestFleetInvitationBindsReferenceToEnrollment(t *testing.T) { d, sg, o := fixture(t) key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatal(err) } cert := &x509.Certificate{PublicKey: &key.PublicKey, PublicKeyAlgorithm: x509.RSA} document := []byte(`{"accountId":"123","region":"eu-west-1","instanceId":"i-fleet"}`) sum := sha256.Sum256(document) signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, sum[:]) if err != nil { t.Fatal(err) } rec := boxid.Record{V: 1, IdentityDocument: base64.StdEncoding.EncodeToString(document), IdentitySignature: base64.StdEncoding.EncodeToString(signature)} raw, _ := json.Marshal(rec) invite, err := d.Invite(context.Background(), sg, "", raw) if err != nil { t.Fatal(err) } if invite.Reference != "fleet-"+invite.Enrollment { t.Fatalf("reference = %q, enrollment = %q", invite.Reference, invite.Enrollment) } rec.Box = invite.Reference bound, _ := json.Marshal(rec) retry, err := d.Invite(context.Background(), sg, invite.Reference, bound) if err != nil || retry != invite { t.Fatalf("retry = %+v, %v", retry, err) } if _, err := d.Invite(context.Background(), sg, "fleet-00000000000000000000000000000000", bound); err == nil { t.Fatal("caller-chosen fleet reference accepted") } v := boxid.Verifier{Account: "123", Certs: map[string]*x509.Certificate{"eu-west-1": cert}} ordinary := rec ordinary.Box = "ordinary-label" ordinaryRaw, _ := json.Marshal(ordinary) if _, err := d.Invite(context.Background(), sg, ordinary.Box, ordinaryRaw); err != nil { t.Fatal(err) } badFleet := EnrollmentRequest{Reference: ordinary.Box, Namespace: "fleet:workers", Label: "fleet", Region: "ie", Pool: "default", AWSRegion: "eu-west-1", Operator: "operator"} if _, err := d.Enroll(context.Background(), sg, o, v, badFleet, ordinaryRaw); err == nil { t.Fatal("fleet namespace accepted arbitrary reference") } in := EnrollmentRequest{Reference: invite.Reference, Namespace: "fleet:workers", Label: "fleet", Region: "ie", Pool: "default", AWSRegion: "eu-west-1", Operator: "operator"} next, err := d.Enroll(context.Background(), sg, o, v, in, bound) if err != nil { t.Fatal(err) } if got := next.Roster.Boxes[invite.Reference]; got.Enrollment != invite.Enrollment || got.BoxID != invite.Reference { t.Fatal(got) } rec.Box = "fleet-ffffffffffffffffffffffffffffffff" mismatched, _ := json.Marshal(rec) if _, err := d.Enroll(context.Background(), sg, o, v, in, mismatched); err == nil { t.Fatal("mismatched fleet evidence accepted") } }