feat: publish Tend v0.2 preview source

Publish the reviewed allowlisted snapshot whose exact binary completed maintenance deployment, rollback, and reactivation exercises for Gamertan and Sandwich Hime.

Private-Source-Commit: 4d7094c8b7c61991bfb67b11fc1558724c874eb2

Private-Source-Tree: 54a2f74804f7acddf3755d7d4da5b97f5fc28381

AI-Assistance: OpenAI Codex assisted implementation, testing, security review, and release verification.
Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
2026-08-16 19:02:08 -04:00
parent b2cc4482f6
commit 00d1dd4209
47 changed files with 1590 additions and 124 deletions
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: AGPL-3.0-only
//go:build linux
package serverpolicy
import (
"os"
"syscall"
)
func rootOwned(info os.FileInfo) bool {
stat, ok := info.Sys().(*syscall.Stat_t)
return ok && stat.Uid == 0
}
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
//go:build !linux
package serverpolicy
import "os"
func rootOwned(os.FileInfo) bool { return false }
+300
View File
@@ -0,0 +1,300 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Package serverpolicy validates the root-owned allowlist used by Tend's
// restricted SSH receiver. It contains service names and paths, never secrets.
package serverpolicy
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"gamertan.com/tend/internal/config"
)
const SchemaVersion = 1
var servicePattern = regexp.MustCompile(`^[a-z][a-z0-9-]{1,62}$`)
type Policy struct {
SchemaVersion int `json:"schema_version"`
ConfigRoot string `json:"config_root"`
IncomingRoot string `json:"incoming_root"`
SharedLockFile string `json:"shared_lock_file"`
Services map[string]ServicePolicy `json:"services"`
}
type ServicePolicy struct {
Config string `json:"config"`
MaxArtifactBytes int64 `json:"max_artifact_bytes"`
}
type CheckedService struct {
Name string
Config config.Config
Policy ServicePolicy
}
func Parse(reader io.Reader) (Policy, error) {
limited := io.LimitReader(reader, 1<<20+1)
body, err := io.ReadAll(limited)
if err != nil {
return Policy{}, err
}
if len(body) > 1<<20 {
return Policy{}, errors.New("server policy exceeds 1 MiB")
}
decoder := json.NewDecoder(bytes.NewReader(body))
decoder.DisallowUnknownFields()
var policy Policy
if err = decoder.Decode(&policy); err != nil {
return Policy{}, fmt.Errorf("decode server policy: %w", err)
}
var trailing any
if err = decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
return Policy{}, errors.New("server policy contains trailing data")
}
if err = policy.Validate(); err != nil {
return Policy{}, err
}
return policy, nil
}
func Load(path string) (Policy, error) {
if err := secureDirectory(filepath.Dir(path), 0); err != nil {
return Policy{}, fmt.Errorf("server policy directory: %w", err)
}
if err := secureFile(path, 0o600); err != nil {
return Policy{}, fmt.Errorf("server policy: %w", err)
}
file, err := os.Open(path)
if err != nil {
return Policy{}, err
}
defer file.Close()
return Parse(file)
}
func (policy Policy) Validate() error {
if policy.SchemaVersion != SchemaVersion {
return fmt.Errorf("server policy schema_version must be %d", SchemaVersion)
}
if policy.ConfigRoot != "/etc/tend/services" {
return errors.New("server policy config_root must be /etc/tend/services")
}
if policy.IncomingRoot != "/var/lib/tend/incoming" {
return errors.New("server policy incoming_root must be /var/lib/tend/incoming")
}
if policy.SharedLockFile != config.SharedLockFile {
return fmt.Errorf("server policy shared_lock_file must be %s", config.SharedLockFile)
}
if len(policy.Services) == 0 || len(policy.Services) > 128 {
return errors.New("server policy must allow 1 to 128 services")
}
for name, service := range policy.Services {
if !servicePattern.MatchString(name) {
return fmt.Errorf("invalid service name %q", name)
}
expected := filepath.Join(policy.ConfigRoot, name+".json")
if service.Config != expected {
return fmt.Errorf("service %s config must be %s", name, expected)
}
if service.MaxArtifactBytes < 1<<20 || service.MaxArtifactBytes > 512<<20 {
return fmt.Errorf("service %s artifact limit is invalid", name)
}
}
return nil
}
func (policy Policy) CheckFiles() ([]CheckedService, error) {
if err := policy.CheckDirectories(); err != nil {
return nil, err
}
names := make([]string, 0, len(policy.Services))
for name := range policy.Services {
names = append(names, name)
}
sort.Strings(names)
checked := make([]CheckedService, 0, len(names))
for _, name := range names {
service, err := policy.CheckService(name)
if err != nil {
return nil, err
}
checked = append(checked, service)
}
return checked, nil
}
func (policy Policy) CheckDirectories() error {
if err := secureDirectory(policy.ConfigRoot, 0); err != nil {
return fmt.Errorf("config root: %w", err)
}
if err := secureDirectory(policy.IncomingRoot, 0o700); err != nil {
return fmt.Errorf("incoming root: %w", err)
}
if err := secureDirectory("/etc/tend/environment", 0o700); err != nil {
return fmt.Errorf("environment root: %w", err)
}
return nil
}
func (policy Policy) CheckService(name string) (CheckedService, error) {
entry, ok := policy.Services[name]
if !ok {
return CheckedService{}, errors.New("service is not allowed by server policy")
}
if err := secureFile(entry.Config, 0); err != nil {
return CheckedService{}, fmt.Errorf("service %s config: %w", name, err)
}
cfg, err := config.Load(entry.Config)
if err != nil {
return CheckedService{}, fmt.Errorf("service %s config: %w", name, err)
}
if cfg.Service.Name != name {
return CheckedService{}, fmt.Errorf("service %s config identity does not match", name)
}
if cfg.Deployment.LockFile != policy.SharedLockFile {
return CheckedService{}, fmt.Errorf("service %s does not use the host-wide lock", name)
}
if err = secureFile(cfg.Service.EnvironmentFile, 0o600); err != nil {
return CheckedService{}, fmt.Errorf("service %s environment file: %w", name, err)
}
if cfg.Deployment.Singleton != nil {
if err = rejectEnvironmentKey(cfg.Service.EnvironmentFile, cfg.Deployment.Singleton.ListenEnv); err != nil {
return CheckedService{}, fmt.Errorf("service %s environment file: %w", name, err)
}
}
return CheckedService{Name: name, Config: cfg, Policy: entry}, nil
}
func CheckConfig(path string, cfg config.Config) error {
expected := filepath.Join("/etc/tend/services", cfg.Service.Name+".json")
if path != expected {
return fmt.Errorf("production config must be %s", expected)
}
if err := secureDirectory("/etc/tend/services", 0); err != nil {
return fmt.Errorf("config root: %w", err)
}
if err := secureDirectory("/etc/tend/environment", 0o700); err != nil {
return fmt.Errorf("environment root: %w", err)
}
if err := secureFile(path, 0); err != nil {
return fmt.Errorf("production config: %w", err)
}
if cfg.Deployment.LockFile != config.SharedLockFile {
return fmt.Errorf("deployment lock must be %s", config.SharedLockFile)
}
if err := secureFile(cfg.Service.EnvironmentFile, 0o600); err != nil {
return fmt.Errorf("environment file: %w", err)
}
if cfg.Deployment.Singleton != nil {
if err := rejectEnvironmentKey(cfg.Service.EnvironmentFile, cfg.Deployment.Singleton.ListenEnv); err != nil {
return fmt.Errorf("environment file: %w", err)
}
}
return nil
}
func rejectEnvironmentKey(path, key string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
body, err := io.ReadAll(io.LimitReader(file, 1<<20+1))
if err != nil {
return err
}
if len(body) > 1<<20 {
return errors.New("environment file exceeds 1 MiB")
}
for _, raw := range bytes.Split(body, []byte{'\n'}) {
line := strings.TrimSpace(string(raw))
if !strings.HasPrefix(line, key) {
continue
}
remainder := strings.TrimSpace(strings.TrimPrefix(line, key))
if strings.HasPrefix(remainder, "=") {
return errors.New("singleton candidate listen key must not be set in the shared environment file")
}
}
return nil
}
func secureFile(path string, exactMode os.FileMode) error {
if !filepath.IsAbs(path) || filepath.Clean(path) != path || strings.ContainsRune(path, '\x00') {
return errors.New("path must be clean and absolute")
}
if err := rejectSymlinkAncestors(filepath.Dir(path)); err != nil {
return err
}
info, err := os.Lstat(path)
if err != nil {
return err
}
if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
return errors.New("must be a regular non-symlink file")
}
if !rootOwned(info) {
return errors.New("must be owned by root")
}
if exactMode != 0 && info.Mode().Perm() != exactMode {
return fmt.Errorf("mode must be %04o", exactMode)
}
if exactMode == 0 && info.Mode().Perm()&0o022 != 0 {
return errors.New("must not be group- or world-writable")
}
return nil
}
func secureDirectory(path string, exactMode os.FileMode) error {
if !filepath.IsAbs(path) || filepath.Clean(path) != path || strings.ContainsRune(path, '\x00') {
return errors.New("path must be clean and absolute")
}
if err := rejectSymlinkAncestors(filepath.Dir(path)); err != nil {
return err
}
info, err := os.Lstat(path)
if err != nil {
return err
}
if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
return errors.New("must be a real directory")
}
if !rootOwned(info) {
return errors.New("must be owned by root")
}
if exactMode != 0 && info.Mode().Perm() != exactMode {
return fmt.Errorf("mode must be %04o", exactMode)
}
if exactMode == 0 && info.Mode().Perm()&0o022 != 0 {
return errors.New("must not be group- or world-writable")
}
return nil
}
func rejectSymlinkAncestors(path string) error {
current := string(filepath.Separator)
for _, part := range strings.Split(strings.TrimPrefix(filepath.Clean(path), string(filepath.Separator)), string(filepath.Separator)) {
if part == "" {
continue
}
current = filepath.Join(current, part)
info, err := os.Lstat(current)
if err != nil {
return err
}
if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
return fmt.Errorf("symlink or non-directory ancestor refused: %s", current)
}
}
return nil
}
+59
View File
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: AGPL-3.0-only
package serverpolicy
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestParseAcceptsStrictServiceMap(t *testing.T) {
body := `{"schema_version":1,"config_root":"/etc/tend/services","incoming_root":"/var/lib/tend/incoming","shared_lock_file":"/run/lock/tend-deploy.lock","services":{"example-site":{"config":"/etc/tend/services/example-site.json","max_artifact_bytes":1048576}}}`
policy, err := Parse(strings.NewReader(body))
if err != nil {
t.Fatal(err)
}
if policy.Services["example-site"].MaxArtifactBytes != 1<<20 {
t.Fatalf("policy=%+v", policy)
}
}
func TestRejectEnvironmentKeyProtectsSingletonCandidateOverride(t *testing.T) {
directory := t.TempDir()
path := filepath.Join(directory, "service.env")
for _, test := range []struct {
name string
body string
wantErr bool
}{
{name: "shared values only", body: "APP_SECRET=private\n"},
{name: "exact listen key", body: "EXAMPLE_LISTEN=127.0.0.1:8092\n", wantErr: true},
{name: "spaced listen key", body: " EXAMPLE_LISTEN = 127.0.0.1:8092\n", wantErr: true},
{name: "commented listen key", body: "# EXAMPLE_LISTEN=127.0.0.1:8092\n"},
{name: "longer key", body: "EXAMPLE_LISTENER=safe\n"},
} {
t.Run(test.name, func(t *testing.T) {
if err := os.WriteFile(path, []byte(test.body), 0o600); err != nil {
t.Fatal(err)
}
err := rejectEnvironmentKey(path, "EXAMPLE_LISTEN")
if (err != nil) != test.wantErr {
t.Fatalf("error=%v wantErr=%v", err, test.wantErr)
}
})
}
}
func TestParseRejectsUnknownAndForgedPaths(t *testing.T) {
tests := []string{
`{"schema_version":1,"config_root":"/etc/tend/services","incoming_root":"/var/lib/tend/incoming","shared_lock_file":"/run/lock/tend-deploy.lock","services":{"example-site":{"config":"/tmp/example.json","max_artifact_bytes":1048576}}}`,
`{"schema_version":1,"config_root":"/etc/tend/services","incoming_root":"/var/lib/tend/incoming","shared_lock_file":"/run/lock/tend-deploy.lock","services":{},"surprise":true}`,
}
for _, body := range tests {
if _, err := Parse(strings.NewReader(body)); err == nil {
t.Fatalf("accepted %s", body)
}
}
}