Skip to content

Commit 464223e

Browse files
authored
Merge pull request #264 from aojea/refactor_alpha
Replace region semantics by opaque labels
2 parents d651550 + bb1ec6d commit 464223e

51 files changed

Lines changed: 1156 additions & 739 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/k8s/sam-control-plane-template.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ spec:
5252
- name: POSTGRES_USER
5353
value: sam
5454
- name: POSTGRES_PASSWORD
55-
value: sam-secret-password
55+
valueFrom:
56+
secretKeyRef:
57+
name: sam-control-plane-secret-${ENV_NAME}
58+
key: db-password
5659
- name: PGDATA
5760
value: /var/lib/postgresql/data/pgdata
5861
ports:
@@ -99,6 +102,11 @@ spec:
99102
name: sam-control-plane-secret-${ENV_NAME}
100103
key: admin-token
101104
optional: true
105+
- name: SAM_DB_DSN
106+
valueFrom:
107+
secretKeyRef:
108+
name: sam-control-plane-secret-${ENV_NAME}
109+
key: db-dsn
102110
ports:
103111
- containerPort: 8080
104112
protocol: TCP
@@ -120,7 +128,6 @@ spec:
120128
args:
121129
- "--bind-address=0.0.0.0:8080"
122130
- "--db-driver=postgres"
123-
- "--db-dsn=postgres://sam:sam-secret-password@sam-db-${ENV_NAME}:5432/sam_mesh?sslmode=disable"
124131
- "--issuer=https://auth.sam-mesh.dev,https://container.googleapis.com/v1/projects/${GCP_PROJECT_ID}/locations/${CLUSTER_REGION}/clusters/${CLUSTER_NAME}"
125132
- "--allowed-audiences=sam-mesh-audience,sam-control-plane-audience"
126133
- "--auto-approve-enrollment"

.github/workflows/deploy.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,11 +314,15 @@ jobs:
314314
export ENV_NAME="${VAR_ENV_NAME}"
315315
export NAMESPACE="sam-${ENV_NAME}"
316316
if ! kubectl get secret sam-control-plane-secret-${ENV_NAME} -n ${NAMESPACE} >/dev/null 2>&1; then
317-
echo "Generating a new random 32-byte hex key for admin-token..."
317+
echo "Generating new random secrets for admin-token and the DB..."
318318
ADMIN_TOKEN=$(openssl rand -hex 32)
319+
DB_PASSWORD=$(openssl rand -hex 32)
320+
DB_DSN="postgres://sam:${DB_PASSWORD}@sam-db-${ENV_NAME}:5432/sam_mesh?sslmode=disable"
319321
kubectl create secret generic sam-control-plane-secret-${ENV_NAME} \
320322
--namespace=${NAMESPACE} \
321-
--from-literal=admin-token="${ADMIN_TOKEN}"
323+
--from-literal=admin-token="${ADMIN_TOKEN}" \
324+
--from-literal=db-password="${DB_PASSWORD}" \
325+
--from-literal=db-dsn="${DB_DSN}"
322326
else
323327
echo "sam-control-plane-secret-${ENV_NAME} already exists in namespace ${NAMESPACE}. Skipping generation."
324328
fi

api/datalog.go

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -198,16 +198,17 @@ const (
198198
// Example Datalog: service("mcp", "calculator")
199199
FactService = "service"
200200

201-
// FactRegion is the control-plane-attested jurisdiction of the token's node,
202-
// following the hierarchical model of region.go. The control plane mints one
203-
// fact per hierarchy level (RegionPrefixes), so a requirement is a single
204-
// exact match: a node attested as "EU-DE" carries region("EU") and
205-
// region("EU-DE"), satisfying `check if region("EU")` but never a finer
206-
// requirement it cannot guarantee. Distinct from LabelRegion, the
207-
// unauthenticated gossip routing hint.
208-
// Contains: biscuit.String(regionPrefix)
209-
// Example Datalog: check if region("EU")
210-
FactRegion = "region"
201+
// FactLabel is a control-plane-attested key=value label on the token's
202+
// node (see api/labels.go). The control plane mints one fact per
203+
// declared label, so a requirement is a single exact match: a node
204+
// attested with region="us-east-1" carries label("region", "us-east-1"),
205+
// satisfying `check if label("region", "us-east-1")` only — composition
206+
// across values is left entirely to the operator (attest as many labels
207+
// as needed). Distinct from the unauthenticated gossip routing hint
208+
// carried in ServiceAnnounce.labels.
209+
// Contains: biscuit.String(key), biscuit.String(value)
210+
// Example Datalog: check if label("region", "us-east-1")
211+
FactLabel = "label"
211212

212213
// FactTime defines the current system time injected during evaluation.
213214
// Contains: biscuit.Date(currentTime)
@@ -431,36 +432,51 @@ func BuildTargetDatalogFact(targetStr string) biscuit.Fact {
431432
}}
432433
}
433434

434-
// RegionFacts materializes a region claim as one Datalog fact per hierarchy
435-
// level (see FactRegion and RegionPrefixes). An empty region returns nil.
436-
func RegionFacts(region string) []biscuit.Fact {
437-
prefixes := RegionPrefixes(region)
438-
if len(prefixes) == 0 {
435+
// LabelFacts materializes a label set as one Datalog fact per key=value
436+
// pair (see FactLabel). Keys are sorted for deterministic fact ordering. An
437+
// empty set returns nil.
438+
func LabelFacts(labels map[string]string) []biscuit.Fact {
439+
if len(labels) == 0 {
439440
return nil
440441
}
441-
facts := make([]biscuit.Fact, 0, len(prefixes))
442-
for _, p := range prefixes {
442+
keys := make([]string, 0, len(labels))
443+
for k := range labels {
444+
keys = append(keys, k)
445+
}
446+
sort.Strings(keys)
447+
facts := make([]biscuit.Fact, 0, len(labels))
448+
for _, k := range keys {
443449
facts = append(facts, biscuit.Fact{Predicate: biscuit.Predicate{
444-
Name: FactRegion,
445-
IDs: []biscuit.Term{biscuit.String(p)},
450+
Name: FactLabel,
451+
IDs: []biscuit.Term{biscuit.String(k), biscuit.String(labels[k])},
446452
}})
447453
}
448454
return facts
449455
}
450456

451-
// RegionCheck compiles required regions (canonical, pre-validated with
452-
// ValidateRegion) into a single fail-closed check satisfied when the token
453-
// carries any of them: `check if region("EU") or region("NA-US")`.
454-
func RegionCheck(required []string) (biscuit.Check, error) {
457+
// LabelCheck compiles a required label set (canonical, pre-validated with
458+
// ValidateLabels) into a single fail-closed check satisfied when the token
459+
// carries any of them: `check if label("region", "us-east-1") or
460+
// label("team", "platform")`.
461+
func LabelCheck(required map[string]string) (biscuit.Check, error) {
455462
if len(required) == 0 {
456-
return biscuit.Check{}, fmt.Errorf("no required regions")
463+
return biscuit.Check{}, fmt.Errorf("no required labels")
464+
}
465+
keys := make([]string, 0, len(required))
466+
for k := range required {
467+
keys = append(keys, k)
457468
}
469+
sort.Strings(keys)
458470
clauses := make([]string, 0, len(required))
459-
for _, r := range required {
460-
if err := ValidateRegion(r); err != nil {
471+
for _, k := range keys {
472+
if err := ValidateLabelKey(k); err != nil {
473+
return biscuit.Check{}, err
474+
}
475+
v := required[k]
476+
if err := ValidateLabelValue(v); err != nil {
461477
return biscuit.Check{}, err
462478
}
463-
clauses = append(clauses, fmt.Sprintf("%s(%q)", FactRegion, NormalizeRegion(r)))
479+
clauses = append(clauses, fmt.Sprintf("%s(%q, %q)", FactLabel, k, v))
464480
}
465481
return parser.FromStringCheck("check if " + strings.Join(clauses, " or "))
466482
}

api/datalog_test.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -557,40 +557,40 @@ func TestBuildTargetDatalogFacts(t *testing.T) {
557557
}
558558
}
559559

560-
func TestRegionFactsAndCheck(t *testing.T) {
560+
func TestLabelFactsAndCheck(t *testing.T) {
561561
pub, priv := makeKeyPair(t)
562562

563-
if facts := RegionFacts(""); facts != nil {
564-
t.Errorf("RegionFacts(\"\") = %v, want nil", facts)
563+
if facts := LabelFacts(nil); facts != nil {
564+
t.Errorf("LabelFacts(nil) = %v, want nil", facts)
565565
}
566-
if _, err := RegionCheck(nil); err == nil {
567-
t.Error("RegionCheck(nil): expected error, got nil")
566+
if _, err := LabelCheck(nil); err == nil {
567+
t.Error("LabelCheck(nil): expected error, got nil")
568568
}
569-
if _, err := RegionCheck([]string{"MARS"}); err == nil {
570-
t.Error("RegionCheck(MARS): expected error, got nil")
569+
if _, err := LabelCheck(map[string]string{"region": "bad,value"}); err == nil {
570+
t.Error("LabelCheck(invalid value): expected error, got nil")
571571
}
572572

573573
tests := []struct {
574574
name string
575-
claimed string // minted into the token via RegionFacts
576-
required []string
575+
claimed map[string]string // minted into the token via LabelFacts
576+
required map[string]string
577577
expectAllow bool
578578
}{
579-
{"finer claim satisfies coarser requirement", "EU-DE-BY", []string{"EU"}, true},
580-
{"exact level match", "EU-DE", []string{"EU-DE"}, true},
581-
{"any-of requirement", "NA-US", []string{"EU", "NA-US"}, true},
582-
{"lowercase requirement is normalized", "EU-DE", []string{"eu"}, true},
583-
{"coarser claim never satisfies finer requirement", "EU", []string{"EU-DE"}, false},
584-
{"disjoint region", "NA-US", []string{"EU"}, false},
585-
{"unattested token fails closed", "", []string{"EU"}, false},
579+
{"exact match", map[string]string{"region": "us-east-1"}, map[string]string{"region": "us-east-1"}, true},
580+
{"any-of requirement", map[string]string{"region": "us-east-1"}, map[string]string{"region": "eu", "team": "us-east-1"}, false},
581+
{"any-of requirement matches one key", map[string]string{"region": "us-east-1", "team": "platform"}, map[string]string{"region": "eu", "team": "platform"}, true},
582+
{"case-sensitive value mismatch", map[string]string{"region": "us-east-1"}, map[string]string{"region": "US-EAST-1"}, false},
583+
{"no built-in hierarchy: coarser requirement does not match a finer claim", map[string]string{"region": "us-east-1"}, map[string]string{"region": "us"}, false},
584+
{"disjoint labels", map[string]string{"region": "us-east-1"}, map[string]string{"region": "eu-west-1"}, false},
585+
{"unattested token fails closed", nil, map[string]string{"region": "us-east-1"}, false},
586586
}
587587

588588
for _, tt := range tests {
589589
t.Run(tt.name, func(t *testing.T) {
590590
builder := biscuit.NewBuilder(priv)
591-
for _, fact := range RegionFacts(tt.claimed) {
591+
for _, fact := range LabelFacts(tt.claimed) {
592592
if err := builder.AddAuthorityFact(fact); err != nil {
593-
t.Fatalf("failed to add region fact: %v", err)
593+
t.Fatalf("failed to add label fact: %v", err)
594594
}
595595
}
596596
tok, err := builder.Build()
@@ -602,9 +602,9 @@ func TestRegionFactsAndCheck(t *testing.T) {
602602
if err != nil {
603603
t.Fatalf("failed to create authorizer: %v", err)
604604
}
605-
check, err := RegionCheck(tt.required)
605+
check, err := LabelCheck(tt.required)
606606
if err != nil {
607-
t.Fatalf("RegionCheck(%v): %v", tt.required, err)
607+
t.Fatalf("LabelCheck(%v): %v", tt.required, err)
608608
}
609609
authorizer.AddCheck(check)
610610
authorizer.AddPolicy(AllowIfTruePolicy)

api/labels.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package api
16+
17+
import (
18+
"fmt"
19+
"regexp"
20+
"strings"
21+
)
22+
23+
// Labels are free-form, control-plane-attested key=value metadata a node's
24+
// identity carries (e.g. region="us-east-1", team="platform"). Cloud
25+
// providers, on-prem operators, and countries all name things differently,
26+
// so SAM imposes no taxonomy or hierarchy on keys or values: composition
27+
// (e.g. attesting both a precise and a coarser value so a coarser
28+
// requirement also matches) is entirely up to the operator. Matching is
29+
// exact and case-sensitive on both key and value (see LabelCheck).
30+
//
31+
// Keys are plain conventions agreed by operators (e.g. "region"); SAM does
32+
// not reserve or interpret any key beyond ValidateLabelKey/ValidateLabelValue.
33+
34+
// labelKeySyntax bounds label keys to a safe, portable charset.
35+
var labelKeySyntax = regexp.MustCompile(`^[a-zA-Z0-9_.-]{1,63}$`)
36+
37+
// maxLabelValueLen bounds label values defensively; well within any
38+
// realistic cloud region/zone or on-prem naming convention.
39+
const maxLabelValueLen = 255
40+
41+
// ValidateLabelKey checks that a label key is well-formed: 1-63 characters
42+
// from [a-zA-Z0-9_.-].
43+
func ValidateLabelKey(key string) error {
44+
if !labelKeySyntax.MatchString(key) {
45+
return fmt.Errorf("invalid label key %q: must be 1-63 chars of [a-zA-Z0-9_.-]", key)
46+
}
47+
return nil
48+
}
49+
50+
// ValidateLabelValue checks that a label value is well-formed: non-empty,
51+
// bounded length, and free of characters that collide with the wire-format
52+
// separators (comma-separated key=value pairs) or control characters.
53+
func ValidateLabelValue(value string) error {
54+
if value == "" {
55+
return fmt.Errorf("label value must not be empty")
56+
}
57+
if len(value) > maxLabelValueLen {
58+
return fmt.Errorf("label value %q exceeds %d characters", value, maxLabelValueLen)
59+
}
60+
if strings.ContainsAny(value, ",=\n\r\t") {
61+
return fmt.Errorf("label value %q must not contain ',', '=', or control characters", value)
62+
}
63+
return nil
64+
}
65+
66+
// ValidateLabels checks every key and value in a label set.
67+
func ValidateLabels(labels map[string]string) error {
68+
for k, v := range labels {
69+
if err := ValidateLabelKey(k); err != nil {
70+
return err
71+
}
72+
if err := ValidateLabelValue(v); err != nil {
73+
return err
74+
}
75+
}
76+
return nil
77+
}

api/labels_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package api
16+
17+
import (
18+
"strings"
19+
"testing"
20+
)
21+
22+
func TestValidateLabelKey(t *testing.T) {
23+
valid := []string{"region", "team", "cloud-region", "on_prem.zone", "A1"}
24+
for _, k := range valid {
25+
if err := ValidateLabelKey(k); err != nil {
26+
t.Errorf("ValidateLabelKey(%q): unexpected error: %v", k, err)
27+
}
28+
}
29+
30+
invalid := []string{"", "has space", "has,comma", "has=equals", strings.Repeat("a", 64)}
31+
for _, k := range invalid {
32+
if err := ValidateLabelKey(k); err == nil {
33+
t.Errorf("ValidateLabelKey(%q): expected error, got nil", k)
34+
}
35+
}
36+
}
37+
38+
func TestValidateLabelValue(t *testing.T) {
39+
valid := []string{"us-east-1", "EU-DE", "office-berlin", "rack.3", "123"}
40+
for _, v := range valid {
41+
if err := ValidateLabelValue(v); err != nil {
42+
t.Errorf("ValidateLabelValue(%q): unexpected error: %v", v, err)
43+
}
44+
}
45+
46+
invalid := []string{"", "has,comma", "has=equals", "has\ttab", strings.Repeat("a", maxLabelValueLen+1)}
47+
for _, v := range invalid {
48+
if err := ValidateLabelValue(v); err == nil {
49+
t.Errorf("ValidateLabelValue(%q): expected error, got nil", v)
50+
}
51+
}
52+
}
53+
54+
func TestValidateLabels(t *testing.T) {
55+
if err := ValidateLabels(nil); err != nil {
56+
t.Errorf("ValidateLabels(nil): unexpected error: %v", err)
57+
}
58+
if err := ValidateLabels(map[string]string{"region": "us-east-1", "team": "platform"}); err != nil {
59+
t.Errorf("ValidateLabels(valid): unexpected error: %v", err)
60+
}
61+
if err := ValidateLabels(map[string]string{"region": ""}); err == nil {
62+
t.Error("ValidateLabels(empty value): expected error, got nil")
63+
}
64+
if err := ValidateLabels(map[string]string{"bad key!": "v"}); err == nil {
65+
t.Error("ValidateLabels(bad key): expected error, got nil")
66+
}
67+
}

0 commit comments

Comments
 (0)