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>
55 lines
1.3 KiB
Go
55 lines
1.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// readProcEnviron reads /proc/{pid}/environ (NUL-delimited KEY=VALUE pairs)
|
|
// and returns the environment as a map.
|
|
func readProcEnviron(procRoot string, pid int32) (map[string]string, error) {
|
|
path := fmt.Sprintf("%s/%d/environ", procRoot, pid)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
env := make(map[string]string)
|
|
for _, entry := range bytes.Split(data, []byte{0}) {
|
|
if len(entry) == 0 {
|
|
continue
|
|
}
|
|
parts := bytes.SplitN(entry, []byte("="), 2)
|
|
if len(parts) == 2 {
|
|
env[string(parts[0])] = string(parts[1])
|
|
}
|
|
}
|
|
return env, nil
|
|
}
|
|
|
|
// getParentPid reads /proc/{pid}/status and extracts the PPid field.
|
|
// Returns 0 if pid 1 (init) is reached or the field cannot be parsed.
|
|
func getParentPid(procRoot string, pid int32) (int32, error) {
|
|
path := fmt.Sprintf("%s/%d/status", procRoot, pid)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
if strings.HasPrefix(line, "PPid:") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) >= 2 {
|
|
ppid, err := strconv.ParseInt(fields[1], 10, 32)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("parse PPid in %s: %w", path, err)
|
|
}
|
|
return int32(ppid), nil
|
|
}
|
|
}
|
|
}
|
|
return 0, fmt.Errorf("PPid not found in %s", path)
|
|
}
|