| 1 | package hostactors |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "crypto" |
| 6 | "crypto/rand" |
| 7 | "crypto/rsa" |
| 8 | "crypto/sha256" |
| 9 | "crypto/x509" |
| 10 | "encoding/base64" |
| 11 | "encoding/json" |
| 12 | "github.com/carlosframework/platform/internal/boxid" |
| 13 | "testing" |
| 14 | "time" |
| 15 | ) |
| 16 | |
| 17 | func TestEnrollmentRejectsSupersededAndConsumedAttempts(t *testing.T) { |
| 18 | ctx := context.Background() |
| 19 | d, sg, o := fixture(t) |
| 20 | key, err := rsa.GenerateKey(rand.Reader, 2048) |
| 21 | if err != nil { |
| 22 | t.Fatal(err) |
| 23 | } |
| 24 | doc := []byte(`{"accountId":"123","region":"eu-west-1","instanceId":"i-new"}`) |
| 25 | sum := sha256.Sum256(doc) |
| 26 | sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, sum[:]) |
| 27 | if err != nil { |
| 28 | t.Fatal(err) |
| 29 | } |
| 30 | rec := boxid.Record{V: 1, Box: newRef, IdentityDocument: base64.StdEncoding.EncodeToString(doc), IdentitySignature: base64.StdEncoding.EncodeToString(sig)} |
| 31 | raw, _ := json.Marshal(rec) |
| 32 | v := boxid.Verifier{Account: "123", Certs: map[string]*x509.Certificate{"eu-west-1": {PublicKey: &key.PublicKey, PublicKeyAlgorithm: x509.RSA}}} |
| 33 | in := EnrollmentRequest{Reference: newRef, Namespace: "platform", Label: newRef, Region: "ie", Pool: "default", AWSRegion: "eu-west-1"} |
| 34 | next := Clone(o.Roster) |
| 35 | next.Regions = map[string]Region{"ie": {Account: "123", AWSRegion: "eu-west-1", GroupName: "routers", GroupARN: "group-arn", Namespaces: []string{"platform"}, Pools: []string{"default"}}} |
| 36 | o, err = d.Replace(ctx, sg, o, next) |
| 37 | if err != nil { |
| 38 | t.Fatal(err) |
| 39 | } |
| 40 | if _, err = d.Invite(ctx, sg, in.Reference, raw); err != nil { |
| 41 | t.Fatal(err) |
| 42 | } |
| 43 | o, first, err := d.BeginEnrollmentAttempt(ctx, sg, o, in, raw, "i-new") |
| 44 | if err != nil { |
| 45 | t.Fatal(err) |
| 46 | } |
| 47 | // The console's own read, which says the instance is a member. |
| 48 | cloud := &fakeMembership{members: map[string]Membership{"i-new": member("routers")}} |
| 49 | now := func() time.Time { return time.Now().UTC() } |
| 50 | staleRoster := o |
| 51 | o, second, err := d.BeginEnrollmentAttempt(ctx, sg, o, in, raw, "i-new") |
| 52 | if err != nil { |
| 53 | t.Fatal(err) |
| 54 | } |
| 55 | if first.AttemptID == second.AttemptID || first.ConfirmationID == second.ConfirmationID { |
| 56 | t.Fatal("immutable identity reused") |
| 57 | } |
| 58 | in.AttemptID, in.ConfirmationID = first.AttemptID, first.ConfirmationID |
| 59 | if _, _, err = d.EnrollAutomatic(ctx, sg, o, v, in, raw, "i-new", cloud, now); err == nil { |
| 60 | t.Fatal("superseded request admitted") |
| 61 | } |
| 62 | // The superseded roster snapshot loses its CAS even with its own |
| 63 | // attempt's ids. |
| 64 | if _, _, err = d.EnrollAutomatic(ctx, sg, staleRoster, v, in, raw, "i-new", cloud, now); err == nil { |
| 65 | t.Fatal("superseded snapshot admitted") |
| 66 | } |
| 67 | in.AttemptID, in.ConfirmationID = second.AttemptID, second.ConfirmationID |
| 68 | admitted, _, err := d.EnrollAutomatic(ctx, sg, o, v, in, raw, "i-new", cloud, now) |
| 69 | if err != nil { |
| 70 | t.Fatal(err) |
| 71 | } |
| 72 | if !admitted.Roster.EnrollmentAttempts[in.Reference].Consumed { |
| 73 | t.Fatal("attempt not consumed with roster admission") |
| 74 | } |
| 75 | if _, _, err = d.EnrollAutomatic(ctx, sg, admitted, v, in, raw, "i-new", cloud, now); err == nil { |
| 76 | t.Fatal("consumed attempt replayed") |
| 77 | } |
| 78 | } |
| 79 | |