Add unmounted canonical signup scope codec
2 files changed,
+1036
−0
internal/console/signupscope.go+400 −0internal/console/signupscope_test.go+636 −0
CI passed — run details
diff --git a/internal/console/signupscope.go b/internal/console/signupscope.go| new file mode 100644 |
| index 000000000..e0244e792 |
| --- /dev/null |
| +++ b/internal/console/signupscope.go |
| @@ -0,0 +1,400 @@ |
| +package console |
| + |
| +import ( |
| + "bytes" |
| + "encoding/binary" |
| + "net/netip" |
| + "net/url" |
| + "strconv" |
| + "strings" |
| + "unicode" |
| + "unicode/utf8" |
| +) |
| + |
| +const ( |
| + signupScopeDomain = "carloku:signup-scope:v1\x00" |
| + signupScopeVersion = byte(1) |
| + signupScopeBackendS3 = "s3" |
| + signupScopeMaxBytes = 2048 |
| +) |
| + |
| +type signupScopeMode uint8 |
| + |
| +const ( |
| + signupScopeModeAWS signupScopeMode = 1 |
| + signupScopeModeCustom signupScopeMode = 2 |
| +) |
| + |
| +// signupScopeRefusal is deliberately numeric and has no Error method. The |
| +// codec is not a source of customer-facing explanations. |
| +type signupScopeRefusal uint8 |
| + |
| +const ( |
| + signupScopeNoRefusal signupScopeRefusal = 0 |
| + signupScopeRefuseSize signupScopeRefusal = 1 |
| + signupScopeRefuseHeader signupScopeRefusal = 2 |
| + signupScopeRefuseVersion signupScopeRefusal = 3 |
| + signupScopeRefuseMode signupScopeRefusal = 4 |
| + signupScopeRefuseLength signupScopeRefusal = 5 |
| + signupScopeRefuseField signupScopeRefusal = 6 |
| + signupScopeRefuseCanonical signupScopeRefusal = 7 |
| +) |
| + |
| +type signupScopeDescriptor struct { |
| + backendKind string |
| + mode signupScopeMode |
| + logicalAuthorityID string |
| + databaseLineageID string |
| + partition string |
| + region string |
| + customOrigin string |
| + namespaceKind string |
| + namespaceToken string |
| + addressingStyle string |
| + bucket string |
| + effectivePrefix string |
| + adapterConfigurationRevision string |
| +} |
| + |
| +func signupScopeEncode(d signupScopeDescriptor) ([]byte, signupScopeRefusal) { |
| + if code := signupScopeCheck(d); code != signupScopeNoRefusal { |
| + return nil, code |
| + } |
| + fields := signupScopeFields(d) |
| + total := len(signupScopeDomain) + 2 + 2*len(fields) |
| + for _, field := range fields { |
| + total += len(field) |
| + } |
| + if total > signupScopeMaxBytes { |
| + return nil, signupScopeRefuseSize |
| + } |
| + |
| + out := make([]byte, 0, total) |
| + out = append(out, signupScopeDomain...) |
| + out = append(out, signupScopeVersion, byte(d.mode)) |
| + var length [2]byte |
| + for _, field := range fields { |
| + binary.BigEndian.PutUint16(length[:], uint16(len(field))) |
| + out = append(out, length[:]...) |
| + out = append(out, field...) |
| + } |
| + return out, signupScopeNoRefusal |
| +} |
| + |
| +func signupScopeDecode(encoded []byte) (signupScopeDescriptor, signupScopeRefusal) { |
| + var d signupScopeDescriptor |
| + if len(encoded) > signupScopeMaxBytes { |
| + return d, signupScopeRefuseSize |
| + } |
| + if len(encoded) < len(signupScopeDomain)+2 || !bytes.Equal(encoded[:len(signupScopeDomain)], []byte(signupScopeDomain)) { |
| + return d, signupScopeRefuseHeader |
| + } |
| + offset := len(signupScopeDomain) |
| + if encoded[offset] != signupScopeVersion { |
| + return d, signupScopeRefuseVersion |
| + } |
| + offset++ |
| + d.mode = signupScopeMode(encoded[offset]) |
| + if d.mode != signupScopeModeAWS && d.mode != signupScopeModeCustom { |
| + return signupScopeDescriptor{}, signupScopeRefuseMode |
| + } |
| + offset++ |
| + |
| + var fields [11]string |
| + for i, cap := range signupScopeFieldCaps() { |
| + if len(encoded)-offset < 2 { |
| + return signupScopeDescriptor{}, signupScopeRefuseLength |
| + } |
| + length := int(binary.BigEndian.Uint16(encoded[offset : offset+2])) |
| + offset += 2 |
| + if length > cap || length > len(encoded)-offset { |
| + return signupScopeDescriptor{}, signupScopeRefuseLength |
| + } |
| + fields[i] = string(encoded[offset : offset+length]) |
| + offset += length |
| + } |
| + if offset != len(encoded) { |
| + return signupScopeDescriptor{}, signupScopeRefuseLength |
| + } |
| + |
| + d.backendKind = signupScopeBackendS3 |
| + d.logicalAuthorityID = fields[0] |
| + d.databaseLineageID = fields[1] |
| + d.partition = fields[2] |
| + d.region = fields[3] |
| + d.customOrigin = fields[4] |
| + d.namespaceKind = fields[5] |
| + d.namespaceToken = fields[6] |
| + d.addressingStyle = fields[7] |
| + d.bucket = fields[8] |
| + d.effectivePrefix = fields[9] |
| + d.adapterConfigurationRevision = fields[10] |
| + |
| + reencoded, code := signupScopeEncode(d) |
| + if code != signupScopeNoRefusal { |
| + return signupScopeDescriptor{}, code |
| + } |
| + if !bytes.Equal(encoded, reencoded) { |
| + return signupScopeDescriptor{}, signupScopeRefuseCanonical |
| + } |
| + return d, signupScopeNoRefusal |
| +} |
| + |
| +// Equality here means only that both inputs are canonical and byte-identical. |
| +// It says nothing about authority, freshness, capability, or physical namespace |
| +// separation. |
| +func signupScopeEqual(a, b []byte) (bool, signupScopeRefusal) { |
| + if _, code := signupScopeDecode(a); code != signupScopeNoRefusal { |
| + return false, code |
| + } |
| + if _, code := signupScopeDecode(b); code != signupScopeNoRefusal { |
| + return false, code |
| + } |
| + return bytes.Equal(a, b), signupScopeNoRefusal |
| +} |
| + |
| +func signupScopeFields(d signupScopeDescriptor) [11]string { |
| + return [11]string{ |
| + d.logicalAuthorityID, |
| + d.databaseLineageID, |
| + d.partition, |
| + d.region, |
| + d.customOrigin, |
| + d.namespaceKind, |
| + d.namespaceToken, |
| + d.addressingStyle, |
| + d.bucket, |
| + d.effectivePrefix, |
| + d.adapterConfigurationRevision, |
| + } |
| +} |
| + |
| +func signupScopeFieldCaps() [11]int { |
| + return [11]int{64, 64, 64, 64, 272, 64, 64, 64, 63, 511, 64} |
| +} |
| + |
| +func signupScopeCheck(d signupScopeDescriptor) signupScopeRefusal { |
| + if d.backendKind != signupScopeBackendS3 { |
| + return signupScopeRefuseField |
| + } |
| + if d.mode != signupScopeModeAWS && d.mode != signupScopeModeCustom { |
| + return signupScopeRefuseMode |
| + } |
| + fields := signupScopeFields(d) |
| + caps := signupScopeFieldCaps() |
| + for i, field := range fields { |
| + if !utf8.ValidString(field) || len(field) > caps[i] { |
| + return signupScopeRefuseField |
| + } |
| + } |
| + if !signupScopeID(d.logicalAuthorityID) || !signupScopeID(d.databaseLineageID) { |
| + return signupScopeRefuseField |
| + } |
| + if !signupScopeToken(d.region) || !signupScopeToken(d.adapterConfigurationRevision) { |
| + return signupScopeRefuseField |
| + } |
| + if d.addressingStyle != "path" && d.addressingStyle != "virtual" { |
| + return signupScopeRefuseField |
| + } |
| + if !signupScopeBucket(d.bucket) || !signupScopePrefix(d.effectivePrefix) { |
| + return signupScopeRefuseField |
| + } |
| + |
| + switch d.mode { |
| + case signupScopeModeAWS: |
| + if !signupScopeToken(d.partition) || d.customOrigin != "" || d.namespaceKind != "global" || d.namespaceToken != "" { |
| + return signupScopeRefuseField |
| + } |
| + case signupScopeModeCustom: |
| + if d.partition != "" || !signupScopeOrigin(d.customOrigin) { |
| + return signupScopeRefuseField |
| + } |
| + switch d.namespaceKind { |
| + case "global": |
| + if d.namespaceToken != "" { |
| + return signupScopeRefuseField |
| + } |
| + case "tenant": |
| + if !signupScopeToken(d.namespaceToken) { |
| + return signupScopeRefuseField |
| + } |
| + default: |
| + return signupScopeRefuseField |
| + } |
| + } |
| + return signupScopeNoRefusal |
| +} |
| + |
| +func signupScopeID(s string) bool { |
| + if len(s) != 64 { |
| + return false |
| + } |
| + nonzero := false |
| + for i := range len(s) { |
| + if (s[i] < '0' || s[i] > '9') && (s[i] < 'a' || s[i] > 'f') { |
| + return false |
| + } |
| + if s[i] != '0' { |
| + nonzero = true |
| + } |
| + } |
| + return nonzero |
| +} |
| + |
| +func signupScopeToken(s string) bool { |
| + if len(s) == 0 || len(s) > 64 { |
| + return false |
| + } |
| + for i := range len(s) { |
| + if (s[i] < 'a' || s[i] > 'z') && (s[i] < '0' || s[i] > '9') && s[i] != '-' { |
| + return false |
| + } |
| + } |
| + return true |
| +} |
| + |
| +func signupScopeBucket(s string) bool { |
| + if len(s) < 3 || len(s) > 63 || !signupScopeLowerAlnum(s[0]) || !signupScopeLowerAlnum(s[len(s)-1]) { |
| + return false |
| + } |
| + for i := range len(s) { |
| + if !signupScopeLowerAlnum(s[i]) && s[i] != '.' && s[i] != '-' { |
| + return false |
| + } |
| + } |
| + if strings.Contains(s, "..") || strings.HasPrefix(s, "xn--") || strings.HasPrefix(s, "sthree-") || |
| + strings.HasSuffix(s, "-s3alias") || strings.HasSuffix(s, "--ol-s3") || strings.HasSuffix(s, ".mrap") || strings.HasSuffix(s, "--x-s3") { |
| + return false |
| + } |
| + parts := strings.Split(s, ".") |
| + if len(parts) == 4 { |
| + allDecimal := true |
| + for _, part := range parts { |
| + if part == "" { |
| + allDecimal = false |
| + break |
| + } |
| + for i := range len(part) { |
| + if part[i] < '0' || part[i] > '9' { |
| + allDecimal = false |
| + break |
| + } |
| + } |
| + } |
| + if allDecimal { |
| + return false |
| + } |
| + } |
| + return true |
| +} |
| + |
| +func signupScopePrefix(s string) bool { |
| + if len(s) > 511 || !utf8.ValidString(s) || (s != "" && !strings.HasSuffix(s, "/")) { |
| + return false |
| + } |
| + for _, r := range s { |
| + if unicode.IsControl(r) { |
| + return false |
| + } |
| + } |
| + return true |
| +} |
| + |
| +func signupScopeOrigin(origin string) bool { |
| + if len(origin) == 0 || len(origin) > 272 || !utf8.ValidString(origin) { |
| + return false |
| + } |
| + u, err := url.Parse(origin) |
| + if err != nil || u.Scheme != "https" || u.Opaque != "" || u.User != nil || u.Path != "" || u.RawPath != "" || u.RawQuery != "" || u.ForceQuery || u.Fragment != "" { |
| + return false |
| + } |
| + hostname := u.Hostname() |
| + if hostname == "" || len(hostname) > 253 || strings.HasSuffix(hostname, ".") { |
| + return false |
| + } |
| + port := u.Port() |
| + if port != "" { |
| + if len(port) > 1 && port[0] == '0' { |
| + return false |
| + } |
| + for i := range len(port) { |
| + if port[i] < '0' || port[i] > '9' { |
| + return false |
| + } |
| + } |
| + n, err := strconv.Atoi(port) |
| + if err != nil || n < 1 || n > 65535 || n == 443 { |
| + return false |
| + } |
| + } |
| + |
| + host := hostname |
| + if addr, err := netip.ParseAddr(hostname); err == nil { |
| + if addr.Zone() != "" || hostname != addr.String() { |
| + return false |
| + } |
| + if addr.Is6() { |
| + host = "[" + hostname + "]" |
| + } else if !addr.Is4() { |
| + return false |
| + } |
| + } else { |
| + if signupScopeNumericIPv4(hostname) || !signupScopeDNSName(hostname) { |
| + return false |
| + } |
| + } |
| + if port != "" { |
| + host += ":" + port |
| + } |
| + return origin == "https://"+host |
| +} |
| + |
| +func signupScopeDNSName(s string) bool { |
| + if len(s) == 0 || len(s) > 253 || strings.HasSuffix(s, ".") { |
| + return false |
| + } |
| + for _, label := range strings.Split(s, ".") { |
| + if len(label) == 0 || len(label) > 63 || !signupScopeLowerAlnum(label[0]) || !signupScopeLowerAlnum(label[len(label)-1]) { |
| + return false |
| + } |
| + for i := range len(label) { |
| + if !signupScopeLowerAlnum(label[i]) && label[i] != '-' { |
| + return false |
| + } |
| + } |
| + } |
| + return true |
| +} |
| + |
| +func signupScopeNumericIPv4(s string) bool { |
| + parts := strings.Split(s, ".") |
| + if len(parts) > 4 { |
| + return false |
| + } |
| + for _, part := range parts { |
| + if part == "" { |
| + return false |
| + } |
| + if strings.HasPrefix(part, "0x") { |
| + if len(part) == 2 { |
| + return false |
| + } |
| + for i := 2; i < len(part); i++ { |
| + if !((part[i] >= '0' && part[i] <= '9') || (part[i] >= 'a' && part[i] <= 'f')) { |
| + return false |
| + } |
| + } |
| + continue |
| + } |
| + for i := range len(part) { |
| + if part[i] < '0' || part[i] > '9' { |
| + return false |
| + } |
| + } |
| + } |
| + return true |
| +} |
| + |
| +func signupScopeLowerAlnum(b byte) bool { |
| + return b >= 'a' && b <= 'z' || b >= '0' && b <= '9' |
| +} |
diff --git a/internal/console/signupscope_test.go b/internal/console/signupscope_test.go| new file mode 100644 |
| index 000000000..588dd96d7 |
| --- /dev/null |
| +++ b/internal/console/signupscope_test.go |
| @@ -0,0 +1,636 @@ |
| +package console |
| + |
| +import ( |
| + "bytes" |
| + "encoding/binary" |
| + "go/ast" |
| + "go/parser" |
| + "go/token" |
| + "path/filepath" |
| + "reflect" |
| + "strconv" |
| + "strings" |
| + "testing" |
| +) |
| + |
| +func testAWSSignupScope() signupScopeDescriptor { |
| + return signupScopeDescriptor{ |
| + backendKind: "s3", |
| + mode: signupScopeModeAWS, |
| + logicalAuthorityID: strings.Repeat("1", 64), |
| + databaseLineageID: strings.Repeat("2", 64), |
| + partition: "aws", |
| + region: "us-east-1", |
| + namespaceKind: "global", |
| + addressingStyle: "path", |
| + bucket: "example-bucket", |
| + effectivePrefix: "a//", |
| + adapterConfigurationRevision: "profile-1", |
| + } |
| +} |
| + |
| +func testCustomSignupScope() signupScopeDescriptor { |
| + d := testAWSSignupScope() |
| + d.mode = signupScopeModeCustom |
| + d.partition = "" |
| + d.customOrigin = "https://objects.example.test:9443" |
| + d.namespaceKind = "tenant" |
| + d.namespaceToken = "tenant-1" |
| + d.addressingStyle = "virtual" |
| + return d |
| +} |
| + |
| +// This fixture spells every length and field byte independently of the |
| +// encoder. A change to the wire format has to change this literal deliberately. |
| +func signupScopeGoldenBytes() []byte { |
| + return []byte("carloku:signup-scope:v1\x00" + |
| + "\x01\x01" + |
| + "\x00\x40" + "1111111111111111111111111111111111111111111111111111111111111111" + |
| + "\x00\x40" + "2222222222222222222222222222222222222222222222222222222222222222" + |
| + "\x00\x03" + "aws" + |
| + "\x00\x09" + "us-east-1" + |
| + "\x00\x00" + |
| + "\x00\x06" + "global" + |
| + "\x00\x00" + |
| + "\x00\x04" + "path" + |
| + "\x00\x0e" + "example-bucket" + |
| + "\x00\x03" + "a//" + |
| + "\x00\x09" + "profile-1") |
| +} |
| + |
| +func signupScopeCustomGoldenBytes() []byte { |
| + return []byte("carloku:signup-scope:v1\x00" + |
| + "\x01\x02" + |
| + "\x00\x40" + "1111111111111111111111111111111111111111111111111111111111111111" + |
| + "\x00\x40" + "2222222222222222222222222222222222222222222222222222222222222222" + |
| + "\x00\x00" + |
| + "\x00\x09" + "us-east-1" + |
| + "\x00\x21" + "https://objects.example.test:9443" + |
| + "\x00\x06" + "tenant" + |
| + "\x00\x08" + "tenant-1" + |
| + "\x00\x07" + "virtual" + |
| + "\x00\x0e" + "example-bucket" + |
| + "\x00\x03" + "a//" + |
| + "\x00\x09" + "profile-1") |
| +} |
| + |
| +func TestSignupScopeGoldenRoundTrip(t *testing.T) { |
| + want := signupScopeGoldenBytes() |
| + got, code := signupScopeEncode(testAWSSignupScope()) |
| + if code != signupScopeNoRefusal { |
| + t.Fatalf("encode refusal = %d", code) |
| + } |
| + if !bytes.Equal(got, want) { |
| + t.Fatalf("encoded bytes = %x\nwant = %x", got, want) |
| + } |
| + |
| + d, code := signupScopeDecode(want) |
| + if code != signupScopeNoRefusal { |
| + t.Fatalf("decode refusal = %d", code) |
| + } |
| + if !reflect.DeepEqual(d, testAWSSignupScope()) { |
| + t.Fatalf("decoded descriptor = %#v", d) |
| + } |
| + reencoded, code := signupScopeEncode(d) |
| + if code != signupScopeNoRefusal || !bytes.Equal(reencoded, want) { |
| + t.Fatalf("re-encode refusal = %d, bytes = %x", code, reencoded) |
| + } |
| + |
| + swapped := d |
| + swapped.logicalAuthorityID, swapped.databaseLineageID = swapped.databaseLineageID, swapped.logicalAuthorityID |
| + swappedBytes, code := signupScopeEncode(swapped) |
| + if code != signupScopeNoRefusal || bytes.Equal(swappedBytes, want) { |
| + t.Fatalf("changing the fixed field order did not change the fixture: refusal=%d", code) |
| + } |
| +} |
| + |
| +func TestSignupScopeCustomGoldenRoundTrip(t *testing.T) { |
| + want := signupScopeCustomGoldenBytes() |
| + wantDescriptor := testCustomSignupScope() |
| + got, code := signupScopeEncode(wantDescriptor) |
| + if code != signupScopeNoRefusal { |
| + t.Fatalf("encode refusal = %d", code) |
| + } |
| + if !bytes.Equal(got, want) { |
| + t.Fatalf("encoded bytes = %x\nwant = %x", got, want) |
| + } |
| + |
| + decoded, code := signupScopeDecode(want) |
| + if code != signupScopeNoRefusal { |
| + t.Fatalf("decode refusal = %d", code) |
| + } |
| + if !reflect.DeepEqual(decoded, wantDescriptor) { |
| + t.Fatalf("decoded descriptor = %#v\nwant = %#v", decoded, wantDescriptor) |
| + } |
| +} |
| + |
| +func TestSignupScopeEveryRepresentableFieldAffectsBytes(t *testing.T) { |
| + base := testAWSSignupScope() |
| + baseBytes := mustSignupScopeEncode(t, base) |
| + awsChanges := []struct { |
| + name string |
| + change func(*signupScopeDescriptor) |
| + }{ |
| + {"logical authority", func(d *signupScopeDescriptor) { d.logicalAuthorityID = strings.Repeat("3", 64) }}, |
| + {"database lineage", func(d *signupScopeDescriptor) { d.databaseLineageID = strings.Repeat("4", 64) }}, |
| + {"partition", func(d *signupScopeDescriptor) { d.partition = "aws-cn" }}, |
| + {"region", func(d *signupScopeDescriptor) { d.region = "eu-west-1" }}, |
| + {"addressing style", func(d *signupScopeDescriptor) { d.addressingStyle = "virtual" }}, |
| + {"bucket", func(d *signupScopeDescriptor) { d.bucket = "another-bucket" }}, |
| + {"prefix", func(d *signupScopeDescriptor) { d.effectivePrefix = "nested/" }}, |
| + {"adapter revision", func(d *signupScopeDescriptor) { d.adapterConfigurationRevision = "profile-2" }}, |
| + } |
| + for _, tc := range awsChanges { |
| + t.Run(tc.name, func(t *testing.T) { |
| + changed := base |
| + tc.change(&changed) |
| + if bytes.Equal(mustSignupScopeEncode(t, changed), baseBytes) { |
| + t.Fatal("field change left canonical bytes unchanged") |
| + } |
| + }) |
| + } |
| + |
| + custom := testCustomSignupScope() |
| + customBytes := mustSignupScopeEncode(t, custom) |
| + customChanges := []struct { |
| + name string |
| + change func(*signupScopeDescriptor) |
| + }{ |
| + {"mode and mode shape", func(d *signupScopeDescriptor) { |
| + d.mode = signupScopeModeAWS |
| + d.partition = "aws" |
| + d.customOrigin = "" |
| + d.namespaceKind = "global" |
| + d.namespaceToken = "" |
| + }}, |
| + {"custom origin", func(d *signupScopeDescriptor) { d.customOrigin = "https://objects-2.example.test:9443" }}, |
| + {"namespace qualifier", func(d *signupScopeDescriptor) { d.namespaceToken = "tenant-2" }}, |
| + {"namespace kind and shape", func(d *signupScopeDescriptor) { |
| + d.namespaceKind = "global" |
| + d.namespaceToken = "" |
| + }}, |
| + } |
| + for _, tc := range customChanges { |
| + t.Run(tc.name, func(t *testing.T) { |
| + changed := custom |
| + tc.change(&changed) |
| + if bytes.Equal(mustSignupScopeEncode(t, changed), customBytes) { |
| + t.Fatal("field change left canonical bytes unchanged") |
| + } |
| + }) |
| + } |
| + |
| + badBackend := base |
| + badBackend.backendKind = "dir" |
| + if _, code := signupScopeEncode(badBackend); code == signupScopeNoRefusal { |
| + t.Fatal("unsupported backend encoded") |
| + } |
| +} |
| + |
| +func TestSignupScopePrefixBytesStayExact(t *testing.T) { |
| + base := testAWSSignupScope() |
| + seen := map[string]string{} |
| + for _, prefix := range []string{"", "/", "a/", "a//", "/a/"} { |
| + d := base |
| + d.effectivePrefix = prefix |
| + encoded := mustSignupScopeEncode(t, d) |
| + key := string(encoded) |
| + if prior, exists := seen[key]; exists { |
| + t.Fatalf("prefixes %q and %q encoded alike", prior, prefix) |
| + } |
| + seen[key] = prefix |
| + decoded, code := signupScopeDecode(encoded) |
| + if code != signupScopeNoRefusal || decoded.effectivePrefix != prefix { |
| + t.Fatalf("prefix %q round trip = %q, refusal %d", prefix, decoded.effectivePrefix, code) |
| + } |
| + } |
| +} |
| + |
| +func TestSignupScopeCustomQualifierIsExplicit(t *testing.T) { |
| + tenant := testCustomSignupScope() |
| + tenantBytes := mustSignupScopeEncode(t, tenant) |
| + |
| + missing := tenant |
| + missing.namespaceToken = "" |
| + if _, code := signupScopeEncode(missing); code == signupScopeNoRefusal { |
| + t.Fatal("tenant namespace without a qualifier encoded") |
| + } |
| + |
| + different := tenant |
| + different.namespaceToken = "tenant-2" |
| + differentBytes := mustSignupScopeEncode(t, different) |
| + if bytes.Equal(tenantBytes, differentBytes) { |
| + t.Fatal("different tenant qualifiers encoded alike") |
| + } |
| + |
| + global := tenant |
| + global.namespaceKind = "global" |
| + global.namespaceToken = "" |
| + globalBytes := mustSignupScopeEncode(t, global) |
| + if bytes.Equal(tenantBytes, globalBytes) { |
| + t.Fatal("global and tenant namespace claims encoded alike") |
| + } |
| + |
| + globalWithToken := global |
| + globalWithToken.namespaceToken = "tenant-1" |
| + if _, code := signupScopeEncode(globalWithToken); code == signupScopeNoRefusal { |
| + t.Fatal("global namespace with a qualifier encoded") |
| + } |
| +} |
| + |
| +func TestSignupScopeRejectsInvalidDescriptors(t *testing.T) { |
| + tests := []struct { |
| + name string |
| + change func(*signupScopeDescriptor) |
| + }{ |
| + {"empty backend", func(d *signupScopeDescriptor) { d.backendKind = "" }}, |
| + {"unknown backend", func(d *signupScopeDescriptor) { d.backendKind = "fleet" }}, |
| + {"unknown mode", func(d *signupScopeDescriptor) { d.mode = 3 }}, |
| + {"missing authority id", func(d *signupScopeDescriptor) { d.logicalAuthorityID = "" }}, |
| + {"short authority id", func(d *signupScopeDescriptor) { d.logicalAuthorityID = strings.Repeat("1", 63) }}, |
| + {"uppercase authority id", func(d *signupScopeDescriptor) { d.logicalAuthorityID = strings.Repeat("A", 64) }}, |
| + {"zero authority id", func(d *signupScopeDescriptor) { d.logicalAuthorityID = strings.Repeat("0", 64) }}, |
| + {"missing lineage id", func(d *signupScopeDescriptor) { d.databaseLineageID = "" }}, |
| + {"zero lineage id", func(d *signupScopeDescriptor) { d.databaseLineageID = strings.Repeat("0", 64) }}, |
| + {"missing partition", func(d *signupScopeDescriptor) { d.partition = "" }}, |
| + {"invalid partition token", func(d *signupScopeDescriptor) { d.partition = "AWS" }}, |
| + {"oversize partition", func(d *signupScopeDescriptor) { d.partition = strings.Repeat("a", 65) }}, |
| + {"missing region", func(d *signupScopeDescriptor) { d.region = "" }}, |
| + {"invalid region token", func(d *signupScopeDescriptor) { d.region = "us_east_1" }}, |
| + {"invalid utf8 region", func(d *signupScopeDescriptor) { d.region = string([]byte{0xff}) }}, |
| + {"aws custom origin", func(d *signupScopeDescriptor) { d.customOrigin = "https://objects.example.test" }}, |
| + {"aws tenant namespace", func(d *signupScopeDescriptor) { d.namespaceKind = "tenant" }}, |
| + {"aws namespace token", func(d *signupScopeDescriptor) { d.namespaceToken = "tenant-1" }}, |
| + {"unknown addressing style", func(d *signupScopeDescriptor) { d.addressingStyle = "automatic" }}, |
| + {"missing adapter revision", func(d *signupScopeDescriptor) { d.adapterConfigurationRevision = "" }}, |
| + {"invalid adapter revision", func(d *signupScopeDescriptor) { d.adapterConfigurationRevision = "profile_1" }}, |
| + {"oversize adapter revision", func(d *signupScopeDescriptor) { d.adapterConfigurationRevision = strings.Repeat("a", 65) }}, |
| + {"prefix missing slash", func(d *signupScopeDescriptor) { d.effectivePrefix = "a" }}, |
| + {"prefix ascii control", func(d *signupScopeDescriptor) { d.effectivePrefix = "a\n/" }}, |
| + {"prefix unicode control", func(d *signupScopeDescriptor) { d.effectivePrefix = "a\u0085/" }}, |
| + {"prefix invalid utf8", func(d *signupScopeDescriptor) { d.effectivePrefix = string([]byte{0xff, '/'}) }}, |
| + {"prefix too long", func(d *signupScopeDescriptor) { d.effectivePrefix = strings.Repeat("a", 511) + "/" }}, |
| + } |
| + for _, tc := range tests { |
| + t.Run(tc.name, func(t *testing.T) { |
| + d := testAWSSignupScope() |
| + tc.change(&d) |
| + if encoded, code := signupScopeEncode(d); code == signupScopeNoRefusal || encoded != nil { |
| + t.Fatalf("encode = %x, refusal = %d", encoded, code) |
| + } |
| + }) |
| + } |
| + |
| + customTests := []struct { |
| + name string |
| + change func(*signupScopeDescriptor) |
| + }{ |
| + {"custom partition", func(d *signupScopeDescriptor) { d.partition = "aws" }}, |
| + {"custom missing origin", func(d *signupScopeDescriptor) { d.customOrigin = "" }}, |
| + {"custom unknown namespace", func(d *signupScopeDescriptor) { d.namespaceKind = "account" }}, |
| + {"custom tenant missing token", func(d *signupScopeDescriptor) { d.namespaceToken = "" }}, |
| + {"custom invalid tenant token", func(d *signupScopeDescriptor) { d.namespaceToken = "Tenant_1" }}, |
| + {"custom oversize tenant token", func(d *signupScopeDescriptor) { d.namespaceToken = strings.Repeat("a", 65) }}, |
| + } |
| + for _, tc := range customTests { |
| + t.Run(tc.name, func(t *testing.T) { |
| + d := testCustomSignupScope() |
| + tc.change(&d) |
| + if encoded, code := signupScopeEncode(d); code == signupScopeNoRefusal || encoded != nil { |
| + t.Fatalf("encode = %x, refusal = %d", encoded, code) |
| + } |
| + }) |
| + } |
| +} |
| + |
| +func TestSignupScopeBucketRefusals(t *testing.T) { |
| + invalid := map[string]string{ |
| + "too short": "ab", |
| + "too long": strings.Repeat("a", 64), |
| + "uppercase": "Bad-bucket", |
| + "underscore": "bad_bucket", |
| + "leading hyphen": "-bucket", |
| + "trailing hyphen": "bucket-", |
| + "adjacent dots": "bucket..name", |
| + "ip address shaped": "192.168.5.4", |
| + "out of range ip shaped": "999.999.999.999", |
| + "xn prefix": "xn--bucket", |
| + "sthree prefix": "sthree-bucket", |
| + "access point alias": "bucket-s3alias", |
| + "object lambda alias": "bucket--ol-s3", |
| + "mrap alias": "bucket.mrap", |
| + "directory bucket": "bucket--x-s3", |
| + } |
| + for name, bucket := range invalid { |
| + t.Run(name, func(t *testing.T) { |
| + d := testAWSSignupScope() |
| + d.bucket = bucket |
| + if _, code := signupScopeEncode(d); code == signupScopeNoRefusal { |
| + t.Fatalf("bucket %q encoded", bucket) |
| + } |
| + }) |
| + } |
| +} |
| + |
| +func TestSignupScopeCustomOrigins(t *testing.T) { |
| + valid := []string{ |
| + "https://objects.example.test", |
| + "https://objects.example.test:9443", |
| + "https://192.0.2.1", |
| + "https://[2001:db8::1]", |
| + "https://[2001:db8::1]:9443", |
| + "https://" + strings.Repeat("a", 63) + "." + strings.Repeat("b", 63) + "." + strings.Repeat("c", 63) + "." + strings.Repeat("d", 61), |
| + } |
| + for _, origin := range valid { |
| + t.Run(origin, func(t *testing.T) { |
| + d := testCustomSignupScope() |
| + d.customOrigin = origin |
| + mustSignupScopeEncode(t, d) |
| + }) |
| + } |
| + |
| + invalid := map[string]string{ |
| + "http": "http://objects.example.test", |
| + "uppercase scheme": "HTTPS://objects.example.test", |
| + "uppercase host": "https://Objects.example.test", |
| + "trailing hostname dot": "https://objects.example.test.", |
| + "userinfo": "https://user@objects.example.test", |
| + "path": "https://objects.example.test/a", |
| + "trailing slash": "https://objects.example.test/", |
| + "query": "https://objects.example.test?x=1", |
| + "empty query": "https://objects.example.test?", |
| + "fragment": "https://objects.example.test#x", |
| + "empty host": "https://", |
| + "underscore": "https://objects_bad.example.test", |
| + "leading label hyphen": "https://-objects.example.test", |
| + "long label": "https://" + strings.Repeat("a", 64) + ".example.test", |
| + "long hostname": "https://" + strings.Repeat("a", 63) + "." + strings.Repeat("b", 63) + "." + strings.Repeat("c", 63) + "." + strings.Repeat("d", 63), |
| + "short numeric ipv4": "https://127.1", |
| + "integer ipv4": "https://2130706433", |
| + "hex ipv4": "https://0x7f000001", |
| + "octal-like ipv4": "https://0177.0.0.1", |
| + "padded dotted ipv4": "https://127.000.000.001", |
| + "unbracketed ipv6": "https://2001:db8::1", |
| + "noncanonical ipv6": "https://[2001:0db8::1]", |
| + "ipv6 zone": "https://[fe80::1%25eth0]", |
| + "empty port": "https://objects.example.test:", |
| + "zero port": "https://objects.example.test:0", |
| + "default port": "https://objects.example.test:443", |
| + "leading-zero port": "https://objects.example.test:0443", |
| + "port too large": "https://objects.example.test:65536", |
| + "nondecimal port": "https://objects.example.test:abc", |
| + } |
| + for name, origin := range invalid { |
| + t.Run(name, func(t *testing.T) { |
| + d := testCustomSignupScope() |
| + d.customOrigin = origin |
| + if _, code := signupScopeEncode(d); code == signupScopeNoRefusal { |
| + t.Fatalf("origin %q encoded", origin) |
| + } |
| + }) |
| + } |
| +} |
| + |
| +func TestSignupScopeDecoderBoundsAndCanonicality(t *testing.T) { |
| + golden := signupScopeGoldenBytes() |
| + headerEnd := len(signupScopeDomain) |
| + tests := []struct { |
| + name string |
| + in []byte |
| + want signupScopeRefusal |
| + }{ |
| + {"oversize checked first", make([]byte, signupScopeMaxBytes+1), signupScopeRefuseSize}, |
| + {"truncated header", append([]byte(nil), golden[:headerEnd-1]...), signupScopeRefuseHeader}, |
| + {"truncated length", append([]byte(nil), golden[:headerEnd+3]...), signupScopeRefuseLength}, |
| + } |
| + badDomain := append([]byte(nil), golden...) |
| + badDomain[0] = 'C' |
| + tests = append(tests, struct { |
| + name string |
| + in []byte |
| + want signupScopeRefusal |
| + }{"domain", badDomain, signupScopeRefuseHeader}) |
| + badVersion := append([]byte(nil), golden...) |
| + badVersion[headerEnd] = 2 |
| + tests = append(tests, struct { |
| + name string |
| + in []byte |
| + want signupScopeRefusal |
| + }{"version", badVersion, signupScopeRefuseVersion}) |
| + badMode := append([]byte(nil), golden...) |
| + badMode[headerEnd+1] = 3 |
| + tests = append(tests, struct { |
| + name string |
| + in []byte |
| + want signupScopeRefusal |
| + }{"mode", badMode, signupScopeRefuseMode}) |
| + hugeLength := append([]byte(nil), golden...) |
| + binary.BigEndian.PutUint16(hugeLength[headerEnd+2:headerEnd+4], 0xffff) |
| + tests = append(tests, struct { |
| + name string |
| + in []byte |
| + want signupScopeRefusal |
| + }{"uint16 maximum length", hugeLength, signupScopeRefuseLength}) |
| + beyondRemaining := append([]byte(nil), golden[:headerEnd+4+10]...) |
| + tests = append(tests, struct { |
| + name string |
| + in []byte |
| + want signupScopeRefusal |
| + }{"declared length beyond remaining", beyondRemaining, signupScopeRefuseLength}) |
| + overOrigin := testAWSSignupScope() |
| + overOrigin.mode = signupScopeModeCustom |
| + overOrigin.partition = "" |
| + overOrigin.customOrigin = strings.Repeat("a", 273) |
| + overOrigin.namespaceKind = "global" |
| + overOriginRaw := signupScopeRawBytes(overOrigin) |
| + tests = append(tests, struct { |
| + name string |
| + in []byte |
| + want signupScopeRefusal |
| + }{"field cap before conversion", overOriginRaw, signupScopeRefuseLength}) |
| + trailing := append(append([]byte(nil), golden...), 0) |
| + tests = append(tests, struct { |
| + name string |
| + in []byte |
| + want signupScopeRefusal |
| + }{"trailing byte", trailing, signupScopeRefuseLength}) |
| + badField := append([]byte(nil), golden...) |
| + badField[headerEnd+4] = 'A' |
| + tests = append(tests, struct { |
| + name string |
| + in []byte |
| + want signupScopeRefusal |
| + }{"noncanonical field", badField, signupScopeRefuseField}) |
| + |
| + for _, tc := range tests { |
| + t.Run(tc.name, func(t *testing.T) { |
| + if d, code := signupScopeDecode(tc.in); code != tc.want || d != (signupScopeDescriptor{}) { |
| + t.Fatalf("decode = %#v, refusal = %d; want zero descriptor, %d", d, code, tc.want) |
| + } |
| + }) |
| + } |
| +} |
| + |
| +func TestSignupScopeCopiesInputsAndOutputs(t *testing.T) { |
| + original := testCustomSignupScope() |
| + encoded := mustSignupScopeEncode(t, original) |
| + want := append([]byte(nil), encoded...) |
| + encoded[0] ^= 0xff |
| + again := mustSignupScopeEncode(t, original) |
| + if !bytes.Equal(again, want) { |
| + t.Fatal("mutating an encoder result changed a later result") |
| + } |
| + |
| + input := append([]byte(nil), want...) |
| + decoded, code := signupScopeDecode(input) |
| + if code != signupScopeNoRefusal { |
| + t.Fatalf("decode refusal = %d", code) |
| + } |
| + input[len(input)-1] ^= 0xff |
| + afterMutation := mustSignupScopeEncode(t, decoded) |
| + if !bytes.Equal(afterMutation, want) { |
| + t.Fatal("mutating decoder input changed the decoded descriptor") |
| + } |
| + afterMutation[0] ^= 0xff |
| + if final := mustSignupScopeEncode(t, decoded); !bytes.Equal(final, want) { |
| + t.Fatal("mutating one re-encoding changed another") |
| + } |
| +} |
| + |
| +func TestSignupScopeEqualityHasOnlyCanonicalByteSemantics(t *testing.T) { |
| + a := mustSignupScopeEncode(t, testAWSSignupScope()) |
| + b := append([]byte(nil), a...) |
| + equal, code := signupScopeEqual(a, b) |
| + if code != signupScopeNoRefusal || !equal { |
| + t.Fatalf("equal canonical bytes = %t, refusal %d", equal, code) |
| + } |
| + |
| + different := testAWSSignupScope() |
| + different.effectivePrefix = "accounts/" |
| + equal, code = signupScopeEqual(a, mustSignupScopeEncode(t, different)) |
| + if code != signupScopeNoRefusal || equal { |
| + t.Fatalf("different canonical bytes = %t, refusal %d", equal, code) |
| + } |
| + |
| + invalid := append([]byte(nil), a...) |
| + invalid = append(invalid, 0) |
| + if equal, code = signupScopeEqual(a, invalid); code == signupScopeNoRefusal || equal { |
| + t.Fatalf("invalid comparison = %t, refusal %d", equal, code) |
| + } |
| +} |
| + |
| +func TestSignupScopeCodecStaysPureAndUnmounted(t *testing.T) { |
| + const codecFile = "signupscope.go" |
| + fset := token.NewFileSet() |
| + codec, err := parser.ParseFile(fset, codecFile, nil, 0) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + allowedImports := map[string]bool{ |
| + "bytes": true, "encoding/binary": true, "net/netip": true, "net/url": true, |
| + "strconv": true, "strings": true, "unicode": true, "unicode/utf8": true, |
| + } |
| + for _, spec := range codec.Imports { |
| + path, err := strconv.Unquote(spec.Path.Value) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if !allowedImports[path] { |
| + t.Errorf("%s imports %q; the codec must stay pure", codecFile, path) |
| + } |
| + } |
| + |
| + codecNames := map[string]bool{} |
| + for _, decl := range codec.Decls { |
| + switch decl := decl.(type) { |
| + case *ast.FuncDecl: |
| + if strings.HasPrefix(decl.Name.Name, "signupScope") { |
| + codecNames[decl.Name.Name] = true |
| + } |
| + case *ast.GenDecl: |
| + for _, spec := range decl.Specs { |
| + switch spec := spec.(type) { |
| + case *ast.TypeSpec: |
| + if strings.HasPrefix(spec.Name.Name, "signupScope") { |
| + codecNames[spec.Name.Name] = true |
| + } |
| + case *ast.ValueSpec: |
| + for _, name := range spec.Names { |
| + if strings.HasPrefix(name.Name, "signupScope") { |
| + codecNames[name.Name] = true |
| + } |
| + } |
| + } |
| + } |
| + } |
| + } |
| + if len(codecNames) == 0 { |
| + t.Fatal("codec symbol inventory is empty") |
| + } |
| + files, err := filepath.Glob("*.go") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + for _, name := range files { |
| + if name == codecFile || strings.HasSuffix(name, "_test.go") { |
| + continue |
| + } |
| + file, err := parser.ParseFile(fset, name, nil, 0) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + ast.Inspect(file, func(node ast.Node) bool { |
| + ident, ok := node.(*ast.Ident) |
| + if ok && codecNames[ident.Name] { |
| + t.Errorf("%s: production reference to %s; codec mounting needs a separate reviewed design", fset.Position(ident.Pos()), ident.Name) |
| + } |
| + return true |
| + }) |
| + } |
| + |
| + errorType := reflect.TypeOf((*error)(nil)).Elem() |
| + if reflect.TypeOf(signupScopeNoRefusal).Implements(errorType) { |
| + t.Fatal("numeric refusal codes must not become user-facing errors") |
| + } |
| +} |
| + |
| +func FuzzSignupScopeDecode(f *testing.F) { |
| + f.Add(signupScopeGoldenBytes()) |
| + f.Add(mustSignupScopeEncode(f, testCustomSignupScope())) |
| + f.Add([]byte{}) |
| + f.Add([]byte(signupScopeDomain)) |
| + f.Add(bytes.Repeat([]byte{0xff}, signupScopeMaxBytes+1)) |
| + f.Fuzz(func(t *testing.T, input []byte) { |
| + d, code := signupScopeDecode(input) |
| + if code != signupScopeNoRefusal { |
| + return |
| + } |
| + reencoded, code := signupScopeEncode(d) |
| + if code != signupScopeNoRefusal { |
| + t.Fatalf("accepted input refused on re-encode: %d", code) |
| + } |
| + if !bytes.Equal(reencoded, input) { |
| + t.Fatalf("accepted input changed on re-encode:\n got %x\nwant %x", reencoded, input) |
| + } |
| + }) |
| +} |
| + |
| +func mustSignupScopeEncode(t testing.TB, d signupScopeDescriptor) []byte { |
| + t.Helper() |
| + encoded, code := signupScopeEncode(d) |
| + if code != signupScopeNoRefusal { |
| + t.Fatalf("encode refusal = %d for %#v", code, d) |
| + } |
| + return encoded |
| +} |
| + |
| +func signupScopeRawBytes(d signupScopeDescriptor) []byte { |
| + encoded := append([]byte(signupScopeDomain), signupScopeVersion, byte(d.mode)) |
| + var length [2]byte |
| + for _, field := range signupScopeFields(d) { |
| + binary.BigEndian.PutUint16(length[:], uint16(len(field))) |
| + encoded = append(encoded, length[:]...) |
| + encoded = append(encoded, field...) |
| + } |
| + return encoded |
| +} |