SPIRE WorkloadAttestor that reads governance env vars from /proc/{pid}/environ
(walking up the process tree to find gsh) and emits gsap: selectors on workload
SVIDs. Maps BASCULE_* vars set by bascule-shell and future GSH_* vars to the
11-selector vocabulary defined in gsap-types/src/selectors.rs.
- pkg/gsap/selectors.go: shared Go constants mirroring Rust vocabulary
- cmd/gsap-attestor/: plugin implementation with /proc reading, process tree
walking, capability ceiling translation, and fail-open for non-governed processes
- 28 tests covering selector extraction, proc parsing, tree walking, and depth limits
- Makefile, Dockerfile, deploy config updated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
101 lines
2.7 KiB
Go
101 lines
2.7 KiB
Go
package gsap
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestAllSelectorKeysCount(t *testing.T) {
|
|
if got := len(AllSelectorKeys); got != 11 {
|
|
t.Errorf("expected 11 selector keys, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestSelectorType(t *testing.T) {
|
|
if SelectorType != "gsap" {
|
|
t.Errorf("expected SelectorType %q, got %q", "gsap", SelectorType)
|
|
}
|
|
}
|
|
|
|
func TestSelectorKeysNoWhitespaceOrColons(t *testing.T) {
|
|
for _, key := range AllSelectorKeys {
|
|
if strings.ContainsAny(key, " \t\n:") {
|
|
t.Errorf("selector key %q contains whitespace or colon", key)
|
|
}
|
|
if key == "" {
|
|
t.Error("selector key is empty")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatSelector(t *testing.T) {
|
|
tests := []struct {
|
|
key, value, want string
|
|
}{
|
|
{"context_id", "abc-123", "gsap:context_id:abc-123"},
|
|
{"capability_mask", "0x07", "gsap:capability_mask:0x07"},
|
|
{"principal_did", "did:web:example.com/alice", "gsap:principal_did:did:web:example.com/alice"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := FormatSelector(tt.key, tt.value)
|
|
if got != tt.want {
|
|
t.Errorf("FormatSelector(%q, %q) = %q, want %q", tt.key, tt.value, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCapabilityCeilingToHex(t *testing.T) {
|
|
expected := map[string]string{
|
|
"CAP_NONE": "0x00",
|
|
"CAP_READ": "0x01",
|
|
"CAP_PROPOSE": "0x03",
|
|
"CAP_MUTATE": "0x07",
|
|
"CAP_GOVERN": "0x0f",
|
|
}
|
|
for name, wantHex := range expected {
|
|
got, ok := CapabilityCeilingToHex[name]
|
|
if !ok {
|
|
t.Errorf("CapabilityCeilingToHex missing entry for %q", name)
|
|
continue
|
|
}
|
|
if got != wantHex {
|
|
t.Errorf("CapabilityCeilingToHex[%q] = %q, want %q", name, got, wantHex)
|
|
}
|
|
}
|
|
if len(CapabilityCeilingToHex) != len(expected) {
|
|
t.Errorf("CapabilityCeilingToHex has %d entries, expected %d", len(CapabilityCeilingToHex), len(expected))
|
|
}
|
|
}
|
|
|
|
func TestCapabilityCeilingToHex_UnknownAbsent(t *testing.T) {
|
|
if _, ok := CapabilityCeilingToHex["CAP_UNKNOWN"]; ok {
|
|
t.Error("CapabilityCeilingToHex should not contain CAP_UNKNOWN")
|
|
}
|
|
}
|
|
|
|
func TestVocabularyMatchesRust(t *testing.T) {
|
|
// Contract test: these exact strings must match gsap-types/src/selectors.rs.
|
|
// If this test fails, the Go and Rust vocabularies have drifted.
|
|
rustKeys := map[string]bool{
|
|
"context_id": true,
|
|
"capability_mask": true,
|
|
"corpus_cid": true,
|
|
"parameters_cid": true,
|
|
"accord_template": true,
|
|
"playbook": true,
|
|
"principal_did": true,
|
|
"driver_id": true,
|
|
"session_mode": true,
|
|
"shell_class": true,
|
|
"posture_level": true,
|
|
}
|
|
for _, key := range AllSelectorKeys {
|
|
if !rustKeys[key] {
|
|
t.Errorf("Go selector key %q not in Rust vocabulary", key)
|
|
}
|
|
delete(rustKeys, key)
|
|
}
|
|
for key := range rustKeys {
|
|
t.Errorf("Rust selector key %q missing from Go vocabulary", key)
|
|
}
|
|
}
|