docs: publish Preview 19 dogfood evidence

Export the reviewed allowlisted snapshot from private source commit 05928cebd01b586cf9e9d4b8c8537a7605a6068c. This records the exact candidate, bounded capacity result, stateful migration scratch requirement, authenticated batch identity proof, and immediate live acceptance evidence.

AI-Assisted: OpenAI Codex
Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
2026-08-18 21:47:08 -04:00
commit 92a66db3df
201 changed files with 38227 additions and 0 deletions
+431
View File
@@ -0,0 +1,431 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Package identity binds Observatory's application policy to the storage-neutral
// Gamertan Web Foundations authentication, organization, and access packages.
package identity
import (
"context"
"database/sql"
"errors"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"sort"
"strings"
"syscall"
"time"
"gamertan.com/web/access"
"gamertan.com/web/auth"
"gamertan.com/web/authsqlite"
"gamertan.com/web/organizations"
_ "modernc.org/sqlite"
)
const (
PlatformOperator = "platform.operator"
OrganizationOwner = "organization.owner"
OrganizationViewer = "organization.viewer"
IncidentResponder = "incident.responder"
PermissionPlatformOperate = "platform.operate"
PermissionTelemetryQuery = "telemetry.query"
PermissionTelemetryReadSensitive = "telemetry.sensitive"
PermissionSourcesManage = "sources.manage"
PermissionSchemaManage = "schema.manage"
PermissionDashboardsRead = "dashboards.read"
PermissionDashboardsManage = "dashboards.manage"
PermissionIncidentsRead = "incidents.read"
PermissionIncidentsManage = "incidents.manage"
PermissionOrganizationAudit = "organization.audit.read"
PermissionOrganizationManage = "organization.manage"
)
var (
ErrAlreadyBootstrapped = errors.New("identity: platform is already bootstrapped")
ErrResourceNotFound = errors.New("identity: resource scope not found")
)
type Services struct {
Store *authsqlite.Store
Auth *auth.Service
Organizations *organizations.Service
Access *access.Service
control *sql.DB
dataDir string
}
func Open(dataDir string) (*Services, error) {
if !filepath.IsAbs(dataDir) || filepath.Clean(dataDir) != dataDir {
return nil, errors.New("identity: data directory must be absolute and clean")
}
info, err := os.Lstat(dataDir)
if err != nil {
return nil, fmt.Errorf("identity: inspect data directory: %w", err)
}
if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 || info.Mode().Perm()&0o077 != 0 {
return nil, errors.New("identity: data directory must be a private non-symlink directory")
}
controlPath := filepath.Join(dataDir, "control.sqlite")
store, err := authsqlite.Open(controlPath)
if err != nil {
return nil, err
}
authService, err := auth.New(store, auth.Options{})
if err != nil {
store.Close()
return nil, err
}
organizationService, err := organizations.New(store, organizations.Options{})
if err != nil {
store.Close()
return nil, err
}
accessService, err := access.New(store, AccessPolicy(), access.Options{})
if err != nil {
store.Close()
return nil, err
}
control, err := sql.Open("sqlite", sqliteDSN(controlPath))
if err != nil {
store.Close()
return nil, err
}
control.SetMaxOpenConns(1)
services := &Services{Store: store, Auth: authService, Organizations: organizationService, Access: accessService, control: control, dataDir: dataDir}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err = services.seed(ctx); err != nil {
services.Close()
return nil, err
}
return services, nil
}
func (services *Services) Close() error {
var errs []error
if services.control != nil {
errs = append(errs, services.control.Close())
}
if services.Store != nil {
errs = append(errs, services.Store.Close())
}
return errors.Join(errs...)
}
func PlatformPolicy() auth.PolicySeed {
return auth.PolicySeed{
Roles: map[string]string{PlatformOperator: "Operate the Observatory service without implicit access to organization telemetry."},
Permissions: map[string]string{PermissionPlatformOperate: "Operate platform-level service and migration controls."},
RolePermissions: map[string][]string{PlatformOperator: {PermissionPlatformOperate}},
}
}
func AccessPolicy() access.Policy {
permissions := map[string]string{
PermissionTelemetryQuery: "Query telemetry in an explicitly authorized resource scope.",
PermissionTelemetryReadSensitive: "Read fields classified as sensitive.",
PermissionSourcesManage: "Enroll, rotate, and revoke ingestion sources.",
PermissionSchemaManage: "Review field descriptors and projection changes.",
PermissionDashboardsRead: "Read saved queries and dashboards.",
PermissionDashboardsManage: "Create and change saved queries and dashboards.",
PermissionIncidentsRead: "Read incidents for an authorized scope.",
PermissionIncidentsManage: "Acknowledge, silence, and resolve incidents.",
PermissionOrganizationAudit: "Read organization-visible security and access audit events.",
PermissionOrganizationManage: "Manage organization membership invitations and teams.",
}
return access.Policy{
Roles: map[string]string{
OrganizationOwner: "Manage an organization and its Observatory resources.",
OrganizationViewer: "Read ordinary telemetry, dashboards, and incidents.",
IncidentResponder: "Read telemetry and respond to incidents without managing sources or access.",
},
Permissions: permissions,
Grants: map[string][]string{
OrganizationOwner: {
PermissionTelemetryQuery, PermissionTelemetryReadSensitive,
PermissionSourcesManage, PermissionSchemaManage, PermissionDashboardsRead,
PermissionDashboardsManage, PermissionIncidentsRead,
PermissionIncidentsManage, PermissionOrganizationAudit,
PermissionOrganizationManage,
},
OrganizationViewer: {PermissionTelemetryQuery, PermissionDashboardsRead, PermissionIncidentsRead},
IncidentResponder: {PermissionTelemetryQuery, PermissionDashboardsRead, PermissionIncidentsRead, PermissionIncidentsManage},
},
}
}
func (services *Services) CancelUnusedInvitation(ctx context.Context, digest [32]byte) error {
result, err := services.control.ExecContext(ctx, `DELETE FROM gwf_organization_invitations WHERE token_hash=? AND used_at IS NULL`, digest[:])
if err != nil {
return errors.New("identity: cancel invitation")
}
if changed, _ := result.RowsAffected(); changed != 1 {
return errors.New("identity: unused invitation was not found")
}
return nil
}
func (services *Services) seed(ctx context.Context) error {
if err := services.Store.SeedPolicy(ctx, PlatformPolicy()); err != nil {
return fmt.Errorf("identity: seed platform policy: %w", err)
}
if err := services.Access.Seed(ctx); err != nil {
return fmt.Errorf("identity: seed organization access policy: %w", err)
}
return nil
}
func (services *Services) ValidateResourceScope(ctx context.Context, scope access.Scope) error {
if err := scope.Validate(); err != nil {
return err
}
queryText := `SELECT COUNT(*) FROM gwf_organizations WHERE id=?`
arguments := []any{scope.OrganizationID}
switch {
case scope.ServiceID != "":
queryText = `SELECT COUNT(*) FROM gwf_application_services WHERE id=? AND environment_id=? AND project_id=? AND organization_id=?`
arguments = []any{scope.ServiceID, scope.EnvironmentID, scope.ProjectID, scope.OrganizationID}
case scope.EnvironmentID != "":
queryText = `SELECT COUNT(*) FROM gwf_environments WHERE id=? AND project_id=? AND organization_id=?`
arguments = []any{scope.EnvironmentID, scope.ProjectID, scope.OrganizationID}
case scope.ProjectID != "":
queryText = `SELECT COUNT(*) FROM gwf_projects WHERE id=? AND organization_id=?`
arguments = []any{scope.ProjectID, scope.OrganizationID}
}
var count int
if err := services.control.QueryRowContext(ctx, queryText, arguments...).Scan(&count); err != nil {
return fmt.Errorf("identity: validate resource scope: %w", err)
}
if count != 1 {
return ErrResourceNotFound
}
return nil
}
// OrganizationsForUser returns only active organizations in which the user
// has a direct membership. Access grants remain the independent authority for
// every operation performed after selection.
func (services *Services) OrganizationsForUser(ctx context.Context, userID string) ([]organizations.Organization, error) {
memberships, err := services.Organizations.Memberships(ctx, userID)
if err != nil {
return nil, fmt.Errorf("identity: list organization memberships: %w", err)
}
result := make([]organizations.Organization, 0, len(memberships))
for _, membership := range memberships {
if membership.Status != "active" {
continue
}
var organization organizations.Organization
var personal int
var createdAt int64
err = services.control.QueryRowContext(ctx, `SELECT id,slug,name,personal,created_at FROM gwf_organizations WHERE id=?`, membership.OrganizationID).Scan(&organization.ID, &organization.Slug, &organization.Name, &personal, &createdAt)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrResourceNotFound
}
if err != nil {
return nil, fmt.Errorf("identity: read organization: %w", err)
}
organization.Personal = personal == 1
organization.CreatedAt = time.Unix(createdAt, 0).UTC()
result = append(result, organization)
}
sort.Slice(result, func(i, j int) bool {
if result[i].Name == result[j].Name {
return result[i].ID < result[j].ID
}
return result[i].Name < result[j].Name
})
return result, nil
}
type BootstrapInput struct {
Username, Email, DisplayName, Password string
RequirePasswordChange bool
}
type BootstrapResult struct {
User auth.User
Organization organizations.Organization
Binding access.Binding
}
type UserProvisionResult struct {
User auth.User
Organization organizations.Organization
Binding access.Binding
}
// ProvisionUser creates an active local user and the personal organization
// that owns their private work. Shared organization access still requires a
// separately authorized, expiring invitation.
func (services *Services) ProvisionUser(ctx context.Context, input auth.CreateUser) (UserProvisionResult, error) {
user, err := services.Auth.CreateUser(ctx, input)
if err != nil {
return UserProvisionResult{}, fmt.Errorf("identity: create user: %w", err)
}
organization, err := services.Organizations.CreatePersonalOrganization(ctx, user.ID, user.DisplayName)
if err != nil {
return UserProvisionResult{}, fmt.Errorf("identity: create personal organization: %w", err)
}
binding, err := services.Access.Grant(ctx, access.Grant{
SubjectKind: access.User, SubjectID: user.ID, Role: OrganizationOwner,
Scope: access.Scope{OrganizationID: organization.ID}, GrantedBy: user.ID,
})
if err != nil {
return UserProvisionResult{}, fmt.Errorf("identity: grant personal organization ownership: %w", err)
}
return UserProvisionResult{User: user, Organization: organization, Binding: binding}, nil
}
func (services *Services) Bootstrap(ctx context.Context, input BootstrapInput) (BootstrapResult, error) {
lock, err := openBootstrapLock(filepath.Join(services.dataDir, ".bootstrap.lock"))
if err != nil {
return BootstrapResult{}, err
}
defer lock.Close()
var users int
if err = services.control.QueryRowContext(ctx, `SELECT COUNT(*) FROM gwf_users`).Scan(&users); err != nil {
return BootstrapResult{}, fmt.Errorf("identity: inspect bootstrap state: %w", err)
}
if users != 0 {
return BootstrapResult{}, ErrAlreadyBootstrapped
}
provisioned, err := services.ProvisionUser(ctx, auth.CreateUser{
Username: input.Username, Email: input.Email,
DisplayName: input.DisplayName, Password: input.Password,
RequirePasswordChange: input.RequirePasswordChange,
})
if err != nil {
return BootstrapResult{}, fmt.Errorf("identity: create first operator: %w", err)
}
now := time.Now().UTC()
if err = services.Store.GrantRole(ctx, provisioned.User.ID, PlatformOperator, now); err != nil {
return BootstrapResult{}, fmt.Errorf("identity: grant platform operator: %w", err)
}
return BootstrapResult{User: provisioned.User, Organization: provisioned.Organization, Binding: provisioned.Binding}, nil
}
func openBootstrapLock(path string) (*os.File, error) {
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|syscall.O_NOFOLLOW, 0o600)
if err != nil {
return nil, fmt.Errorf("identity: open bootstrap lock: %w", err)
}
if err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
file.Close()
return nil, errors.New("identity: another bootstrap operation is active")
}
info, err := file.Stat()
if err != nil || !info.Mode().IsRegular() || info.Mode().Perm() != 0o600 {
file.Close()
return nil, errors.New("identity: bootstrap lock must be a private regular file")
}
return file, nil
}
func sqliteDSN(path string) string {
return (&url.URL{Scheme: "file", Path: filepath.ToSlash(path), RawQuery: "_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)&_pragma=synchronous(FULL)"}).String()
}
func ReadSecret(path string, requireRoot bool) (string, error) {
if !filepath.IsAbs(path) || filepath.Clean(path) != path {
return "", errors.New("identity: secret path must be absolute and clean")
}
info, err := os.Lstat(path)
if err != nil {
return "", fmt.Errorf("identity: inspect secret file: %w", err)
}
if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 || info.Mode().Perm() != 0o600 {
return "", errors.New("identity: secret must be a regular non-symlink file with mode 0600")
}
if requireRoot {
stat, ok := info.Sys().(*syscall.Stat_t)
if !ok || stat.Uid != 0 {
return "", errors.New("identity: secret must be owned by root")
}
}
value, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("identity: read secret file: %w", err)
}
secret := strings.TrimSuffix(string(value), "\n")
secret = strings.TrimSuffix(secret, "\r")
if secret == "" || strings.ContainsAny(secret, "\x00\r\n") {
return "", errors.New("identity: secret file must contain one non-empty line")
}
return secret, nil
}
func WriteSecret(path, secret string) error {
if !filepath.IsAbs(path) || filepath.Clean(path) != path || secret == "" || len(secret) > 1024 || strings.ContainsAny(secret, "\x00\r\n") {
return errors.New("identity: secret output is invalid")
}
parent := filepath.Dir(path)
info, err := os.Lstat(parent)
if err != nil || !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
return errors.New("identity: secret output directory must be an existing non-symlink directory")
}
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL|syscall.O_NOFOLLOW, 0o600)
if err != nil {
return fmt.Errorf("identity: create secret output: %w", err)
}
remove := true
defer func() {
_ = file.Close()
if remove {
_ = os.Remove(path)
}
}()
if err = file.Chmod(0o600); err == nil {
_, err = io.WriteString(file, secret+"\n")
}
if err == nil {
err = file.Sync()
}
if closeErr := file.Close(); err == nil {
err = closeErr
}
if err != nil {
return errors.New("identity: persist secret output")
}
directory, err := os.Open(parent)
if err != nil {
return errors.New("identity: open secret output directory")
}
if err = directory.Sync(); err != nil {
directory.Close()
return errors.New("identity: persist secret output directory")
}
if err = directory.Close(); err != nil {
return errors.New("identity: close secret output directory")
}
remove = false
return nil
}
// RemoveSecret removes only an exact private regular secret file and syncs its
// parent directory. It is used to clean up a generated bootstrap credential
// when bootstrap cannot commit an operator.
func RemoveSecret(path string, requireRoot bool) error {
if _, err := ReadSecret(path, requireRoot); err != nil {
return err
}
if err := os.Remove(path); err != nil {
return fmt.Errorf("identity: remove secret file: %w", err)
}
directory, err := os.Open(filepath.Dir(path))
if err != nil {
return errors.New("identity: open secret output directory")
}
if err = directory.Sync(); err != nil {
directory.Close()
return errors.New("identity: persist secret output directory")
}
if err = directory.Close(); err != nil {
return errors.New("identity: close secret output directory")
}
return nil
}
+344
View File
@@ -0,0 +1,344 @@
// SPDX-License-Identifier: AGPL-3.0-only
package identity
import (
"context"
"errors"
"os"
"path/filepath"
"runtime"
"testing"
"time"
"gamertan.com/web/access"
"gamertan.com/web/auth"
"gamertan.com/web/organizations"
)
func TestBootstrapSeparatesPlatformAndOrganizationAccess(t *testing.T) {
root := t.TempDir()
if err := os.Chmod(root, 0o700); err != nil {
t.Fatal(err)
}
services, err := Open(root)
if err != nil {
t.Fatal(err)
}
defer services.Close()
result, err := services.Bootstrap(context.Background(), BootstrapInput{
Username: "operator", Email: "operator@example.test",
DisplayName: "First Operator", Password: "correct horse battery staple",
})
if err != nil {
t.Fatal(err)
}
if !result.Organization.Personal || result.Binding.Role != OrganizationOwner {
t.Fatalf("result=%+v", result)
}
token, principal, err := services.Auth.Authenticate(context.Background(), "operator", "correct horse battery staple", time.Hour)
if err != nil || token == "" || !principal.Has(PermissionPlatformOperate) {
t.Fatalf("platform session: token_present=%t principal=%+v err=%v", token != "", principal, err)
}
decision, err := services.Access.Authorize(context.Background(), result.User.ID, access.Scope{OrganizationID: result.Organization.ID}, PermissionTelemetryQuery)
if err != nil || !decision.Allowed || decision.Role != OrganizationOwner {
t.Fatalf("query decision=%+v err=%v", decision, err)
}
decision, err = services.Access.Authorize(context.Background(), result.User.ID, access.Scope{OrganizationID: result.Organization.ID}, PermissionTelemetryReadSensitive)
if err != nil || !decision.Allowed {
t.Fatalf("sensitive decision=%+v err=%v", decision, err)
}
decision, err = services.Access.Authorize(context.Background(), result.User.ID, access.Scope{OrganizationID: result.Organization.ID}, PermissionSchemaManage)
if err != nil || !decision.Allowed {
t.Fatalf("schema decision=%+v err=%v", decision, err)
}
if _, err = services.Access.Authorize(context.Background(), result.User.ID, access.Scope{OrganizationID: result.Organization.ID}, PermissionPlatformOperate); err == nil {
t.Fatal("platform permission entered organization access policy")
}
project, err := services.Organizations.CreateProject(context.Background(), organizations.CreateProject{OrganizationID: result.Organization.ID, Slug: "eql-helper", Name: "EQL Helper"})
if err != nil {
t.Fatal(err)
}
environment, err := services.Organizations.CreateEnvironment(context.Background(), organizations.CreateEnvironment{OrganizationID: result.Organization.ID, ProjectID: project.ID, Slug: "production", Name: "Production"})
if err != nil {
t.Fatal(err)
}
application, err := services.Organizations.CreateApplicationService(context.Background(), organizations.CreateApplicationService{OrganizationID: result.Organization.ID, ProjectID: project.ID, EnvironmentID: environment.ID, Slug: "web", Name: "Web"})
if err != nil {
t.Fatal(err)
}
scope := access.Scope{OrganizationID: result.Organization.ID, ProjectID: project.ID, EnvironmentID: environment.ID, ServiceID: application.ID}
if err = services.ValidateResourceScope(context.Background(), scope); err != nil {
t.Fatal(err)
}
scope.ServiceID = "missing1"
if err = services.ValidateResourceScope(context.Background(), scope); !errors.Is(err, ErrResourceNotFound) {
t.Fatalf("missing scope err=%v", err)
}
}
func TestBootstrapIsSingleUse(t *testing.T) {
root := t.TempDir()
if err := os.Chmod(root, 0o700); err != nil {
t.Fatal(err)
}
services, err := Open(root)
if err != nil {
t.Fatal(err)
}
defer services.Close()
input := BootstrapInput{Username: "operator", Email: "operator@example.test", DisplayName: "First Operator", Password: "correct horse battery staple"}
if _, err = services.Bootstrap(context.Background(), input); err != nil {
t.Fatal(err)
}
if _, err = services.Bootstrap(context.Background(), BootstrapInput{Username: "second", Email: "second@example.test", DisplayName: "Second Operator", Password: "correct horse battery staple"}); !errors.Is(err, ErrAlreadyBootstrapped) {
t.Fatalf("second bootstrap err=%v", err)
}
}
func TestBootstrapCanRequirePasswordChange(t *testing.T) {
root := t.TempDir()
if err := os.Chmod(root, 0o700); err != nil {
t.Fatal(err)
}
services, err := Open(root)
if err != nil {
t.Fatal(err)
}
defer services.Close()
result, err := services.Bootstrap(t.Context(), BootstrapInput{
Username: "operator", Email: "operator@example.test", DisplayName: "First Operator",
Password: "temporary correct horse battery staple", RequirePasswordChange: true,
})
if err != nil || !result.User.PasswordChangeRequired {
t.Fatalf("result=%+v err=%v", result, err)
}
_, principal, err := services.Auth.Authenticate(t.Context(), "operator", "temporary correct horse battery staple", time.Hour)
if err != nil || !principal.User.PasswordChangeRequired {
t.Fatalf("principal=%+v err=%v", principal, err)
}
}
func TestRemoveSecretValidatesAndRemovesOnlyPrivateRegularFile(t *testing.T) {
root := t.TempDir()
path := filepath.Join(root, "bootstrap-password")
if err := WriteSecret(path, "temporary secret"); err != nil {
t.Fatal(err)
}
if err := RemoveSecret(path, false); err != nil {
t.Fatal(err)
}
if _, err := os.Lstat(path); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("removed path err=%v", err)
}
unsafe := filepath.Join(root, "unsafe")
if err := os.WriteFile(unsafe, []byte("secret\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := RemoveSecret(unsafe, false); err == nil {
t.Fatal("world-readable secret was removed")
}
if _, err := os.Stat(unsafe); err != nil {
t.Fatalf("unsafe file changed: %v", err)
}
}
func TestEvidenceRetentionPrunesOnlyExpiredAudit(t *testing.T) {
ctx := t.Context()
root := t.TempDir()
if err := os.Chmod(root, 0o700); err != nil {
t.Fatal(err)
}
services, err := Open(root)
if err != nil {
t.Fatal(err)
}
defer services.Close()
owner, err := services.Bootstrap(ctx, BootstrapInput{Username: "operator", Email: "operator@example.test", DisplayName: "First Operator", Password: "correct horse battery staple"})
if err != nil {
t.Fatal(err)
}
now := time.Date(2026, 8, 17, 5, 0, 0, 0, time.UTC)
for _, event := range []struct {
id string
created time.Time
}{{"audit-old", now.Add(-401 * 24 * time.Hour)}, {"audit-current", now.Add(-399 * 24 * time.Hour)}} {
if _, err = services.control.ExecContext(ctx, `INSERT INTO gwf_audit_events(id,actor_user_id,action,resource_type,resource_id,summary,created_at) VALUES(?,?,?,?,?,?,?)`, event.id, owner.User.ID, "session.test", "user", owner.User.ID, "Test event", event.created.Unix()); err != nil {
t.Fatal(err)
}
if _, err = services.control.ExecContext(ctx, `INSERT INTO gwf_access_audit_events(id,organization_id,actor_user_id,action,resource_type,resource_id,summary,created_at) VALUES(?,?,?,?,?,?,?,?)`, "access-"+event.id, owner.Organization.ID, owner.User.ID, "access.test", "organization", owner.Organization.ID, "Test event", event.created.Unix()); err != nil {
t.Fatal(err)
}
}
report, err := services.PruneEvidence(ctx, 400, now)
if err != nil {
t.Fatal(err)
}
if report.AuthenticationEvents != 1 || report.OrganizationEvents != 1 {
t.Fatalf("report=%+v", report)
}
for _, table := range []string{"gwf_audit_events", "gwf_access_audit_events"} {
var count int
if err = services.control.QueryRowContext(ctx, `SELECT COUNT(*) FROM `+table).Scan(&count); err != nil || count != 1 {
t.Fatalf("table=%s count=%d err=%v", table, count, err)
}
}
}
func TestTeamsInvitationsRevocationAndBreakGlassRemainOrganizationScoped(t *testing.T) {
ctx := context.Background()
root := t.TempDir()
if err := os.Chmod(root, 0o700); err != nil {
t.Fatal(err)
}
services, err := Open(root)
if err != nil {
t.Fatal(err)
}
defer services.Close()
owner, err := services.Bootstrap(ctx, BootstrapInput{Username: "operator", Email: "operator@example.test", DisplayName: "First Operator", Password: "correct horse battery staple"})
if err != nil {
t.Fatal(err)
}
member, err := services.Auth.CreateUser(ctx, auth.CreateUser{Username: "responder", Email: "responder@example.test", DisplayName: "Incident Responder", Password: "another correct horse battery staple"})
if err != nil {
t.Fatal(err)
}
rawInvitation, invitation, err := services.Organizations.Invite(ctx, owner.Organization.ID, member.Email, owner.User.ID, 15*time.Minute)
if err != nil || rawInvitation == "" || invitation.OrganizationID != owner.Organization.ID {
t.Fatalf("invitation=%+v token_present=%t err=%v", invitation, rawInvitation != "", err)
}
if err = services.Organizations.AcceptInvitation(ctx, rawInvitation, member.ID); err != nil {
t.Fatal(err)
}
team, err := services.Organizations.CreateTeam(ctx, organizations.CreateTeam{OrganizationID: owner.Organization.ID, Slug: "responders", Name: "Incident Responders"})
if err != nil {
t.Fatal(err)
}
if err = services.Organizations.AddTeamMember(ctx, team.ID, member.ID); err != nil {
t.Fatal(err)
}
binding, err := services.Access.Grant(ctx, access.Grant{SubjectKind: access.Team, SubjectID: team.ID, Role: IncidentResponder, Scope: access.Scope{OrganizationID: owner.Organization.ID}, GrantedBy: owner.User.ID})
if err != nil {
t.Fatal(err)
}
decision, err := services.Access.Authorize(ctx, member.ID, access.Scope{OrganizationID: owner.Organization.ID}, PermissionIncidentsManage)
if err != nil || !decision.Allowed || decision.Source != "role" || decision.Role != IncidentResponder {
t.Fatalf("team decision=%+v err=%v", decision, err)
}
decision, err = services.Access.Authorize(ctx, owner.User.ID, access.Scope{OrganizationID: owner.Organization.ID}, PermissionOrganizationManage)
if err != nil || !decision.Allowed || decision.Role != OrganizationOwner {
t.Fatalf("owner organization-management decision=%+v err=%v", decision, err)
}
decision, err = services.Access.Authorize(ctx, member.ID, access.Scope{OrganizationID: owner.Organization.ID}, PermissionOrganizationManage)
if err != nil || decision.Allowed {
t.Fatalf("member organization-management decision=%+v err=%v", decision, err)
}
cancelToken, cancelInvitation, err := services.Organizations.Invite(ctx, owner.Organization.ID, "cancelled@example.test", owner.User.ID, 15*time.Minute)
if err != nil || cancelToken == "" {
t.Fatalf("cancel invitation=%+v token_present=%t err=%v", cancelInvitation, cancelToken != "", err)
}
if err = services.CancelUnusedInvitation(ctx, cancelInvitation.Digest); err != nil {
t.Fatal(err)
}
if err = services.Organizations.AcceptInvitation(ctx, cancelToken, member.ID); err == nil {
t.Fatal("cancelled invitation remained usable")
}
other, err := services.Organizations.CreatePersonalOrganization(ctx, member.ID, member.DisplayName)
if err != nil {
t.Fatal(err)
}
decision, err = services.Access.Authorize(ctx, owner.User.ID, access.Scope{OrganizationID: other.ID}, PermissionTelemetryQuery)
if err != nil || decision.Allowed {
t.Fatalf("cross-organization decision=%+v err=%v", decision, err)
}
decision, err = services.Access.Authorize(ctx, member.ID, access.Scope{OrganizationID: owner.Organization.ID}, PermissionTelemetryReadSensitive)
if err != nil || decision.Allowed {
t.Fatalf("unexpected sensitive decision=%+v err=%v", decision, err)
}
breakGlass, err := services.Access.ActivateBreakGlass(ctx, owner.Organization.ID, member.ID, PermissionTelemetryReadSensitive, "Investigate an active incident", "request-12345678", 15*time.Minute)
if err != nil {
t.Fatal(err)
}
decision, err = services.Access.Authorize(ctx, member.ID, access.Scope{OrganizationID: owner.Organization.ID}, PermissionTelemetryReadSensitive)
if err != nil || !decision.Allowed || decision.Source != "break_glass" {
t.Fatalf("break-glass decision=%+v err=%v", decision, err)
}
audit, err := services.Access.Audit(ctx, owner.Organization.ID, 10)
if err != nil || len(audit) != 1 || audit[0].Action != "break_glass.activate" || audit[0].ResourceID != owner.Organization.ID {
t.Fatalf("audit=%+v err=%v", audit, err)
}
if _, err = services.control.ExecContext(ctx, `UPDATE gwf_break_glass SET expires_at=? WHERE id=?`, time.Now().Add(-time.Minute).Unix(), breakGlass.ID); err != nil {
t.Fatal(err)
}
decision, err = services.Access.Authorize(ctx, member.ID, access.Scope{OrganizationID: owner.Organization.ID}, PermissionTelemetryReadSensitive)
if err != nil || decision.Allowed {
t.Fatalf("expired break-glass decision=%+v err=%v", decision, err)
}
if err = services.Store.Revoke(ctx, binding.ID, owner.User.ID, time.Now().UTC()); err != nil {
t.Fatal(err)
}
decision, err = services.Access.Authorize(ctx, member.ID, access.Scope{OrganizationID: owner.Organization.ID}, PermissionIncidentsManage)
if err != nil || decision.Allowed {
t.Fatalf("revoked team decision=%+v err=%v", decision, err)
}
}
func TestReadSecretRejectsWeakFiles(t *testing.T) {
dir := t.TempDir()
valid := filepath.Join(dir, "password")
if err := os.WriteFile(valid, []byte("correct horse battery staple\n"), 0o600); err != nil {
t.Fatal(err)
}
secret, err := ReadSecret(valid, false)
if err != nil || secret != "correct horse battery staple" {
t.Fatalf("secret=%q err=%v", secret, err)
}
weak := filepath.Join(dir, "weak")
if err = os.WriteFile(weak, []byte("not private"), 0o644); err != nil {
t.Fatal(err)
}
if _, err = ReadSecret(weak, false); err == nil {
t.Fatal("world-readable secret accepted")
}
if runtime.GOOS != "windows" {
link := filepath.Join(dir, "link")
if err = os.Symlink(valid, link); err != nil {
t.Fatal(err)
}
if _, err = ReadSecret(link, false); err == nil {
t.Fatal("symlinked secret accepted")
}
}
}
func TestWriteSecretIsPrivateExclusiveAndReadable(t *testing.T) {
path := filepath.Join(t.TempDir(), "invitation")
const secret = "single-use-invitation-token"
if err := WriteSecret(path, secret); err != nil {
t.Fatal(err)
}
info, err := os.Lstat(path)
if err != nil || !info.Mode().IsRegular() || info.Mode().Perm() != 0o600 {
t.Fatalf("info=%v err=%v", info, err)
}
got, err := ReadSecret(path, false)
if err != nil || got != secret {
t.Fatalf("secret=%q err=%v", got, err)
}
if err = WriteSecret(path, "replacement"); err == nil {
t.Fatal("existing secret was overwritten")
}
if err = WriteSecret(filepath.Join(filepath.Dir(path), "multiline"), "first\nsecond"); err == nil {
t.Fatal("multiline secret was accepted")
}
if runtime.GOOS != "windows" {
linkedParent := filepath.Join(filepath.Dir(path), "linked-parent")
if err = os.Symlink(filepath.Dir(path), linkedParent); err != nil {
t.Fatal(err)
}
if err = WriteSecret(filepath.Join(linkedParent, "through-link"), "secret"); err == nil {
t.Fatal("symlinked secret output directory was accepted")
}
}
}
+93
View File
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: AGPL-3.0-only
package identity
import (
"context"
"errors"
"time"
)
type EvidencePruneReport struct {
AuthenticationEvents int64 `json:"authentication_events"`
OrganizationEvents int64 `json:"organization_events"`
ExpiredSessions int64 `json:"expired_sessions"`
ExpiredInvitations int64 `json:"expired_invitations"`
ExpiredBreakGlass int64 `json:"expired_break_glass"`
}
// PruneEvidence applies the server default to platform authentication audit
// events and each organization's approved retention override to its visible
// access audit. Operational credentials are removed only after expiration.
func (services *Services) PruneEvidence(ctx context.Context, defaultDays int, now time.Time) (EvidencePruneReport, error) {
if services == nil || services.control == nil || defaultDays < 1 || defaultDays > 3650 || now.IsZero() {
return EvidencePruneReport{}, errors.New("identity: evidence retention input is invalid")
}
tx, err := services.control.BeginTx(ctx, nil)
if err != nil {
return EvidencePruneReport{}, errors.New("identity: begin evidence retention")
}
defer tx.Rollback()
report := EvidencePruneReport{}
cutoff := now.UTC().Add(-time.Duration(defaultDays) * 24 * time.Hour).Unix()
result, err := tx.ExecContext(ctx, `DELETE FROM gwf_audit_events WHERE created_at<?`, cutoff)
if err != nil {
return report, errors.New("identity: prune authentication audit")
}
report.AuthenticationEvents, _ = result.RowsAffected()
var policyTable int
if err = tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='organization_retention_policies'`).Scan(&policyTable); err != nil {
return report, errors.New("identity: inspect organization retention policy")
}
organizationQuery := `SELECT DISTINCT organization_id,? FROM gwf_access_audit_events ORDER BY organization_id`
if policyTable == 1 {
organizationQuery = `SELECT DISTINCT audit.organization_id,COALESCE(policy.evidence_days,?) FROM gwf_access_audit_events audit LEFT JOIN organization_retention_policies policy ON policy.organization_id=audit.organization_id ORDER BY audit.organization_id`
}
rows, err := tx.QueryContext(ctx, organizationQuery, defaultDays)
if err != nil {
return report, errors.New("identity: list organization audit retention")
}
type organizationCutoff struct {
id string
days int
}
var organizations []organizationCutoff
for rows.Next() {
var organization organizationCutoff
if err = rows.Scan(&organization.id, &organization.days); err != nil || organization.days < 1 || organization.days > 3650 {
_ = rows.Close()
return report, errors.New("identity: organization audit retention is invalid")
}
organizations = append(organizations, organization)
}
if err = rows.Close(); err != nil {
return report, errors.New("identity: close organization audit retention")
}
for _, organization := range organizations {
organizationCutoff := now.UTC().Add(-time.Duration(organization.days) * 24 * time.Hour).Unix()
result, err = tx.ExecContext(ctx, `DELETE FROM gwf_access_audit_events WHERE organization_id=? AND created_at<?`, organization.id, organizationCutoff)
if err != nil {
return report, errors.New("identity: prune organization access audit")
}
removed, _ := result.RowsAffected()
report.OrganizationEvents += removed
}
for _, cleanup := range []struct {
statement string
destination *int64
}{
{`DELETE FROM gwf_auth_sessions WHERE expires_at<=?`, &report.ExpiredSessions},
{`DELETE FROM gwf_organization_invitations WHERE expires_at<=?`, &report.ExpiredInvitations},
{`DELETE FROM gwf_break_glass WHERE expires_at<=?`, &report.ExpiredBreakGlass},
} {
result, err = tx.ExecContext(ctx, cleanup.statement, now.UTC().Unix())
if err != nil {
return report, errors.New("identity: prune expired security state")
}
*cleanup.destination, _ = result.RowsAffected()
}
if err = tx.Commit(); err != nil {
return report, errors.New("identity: commit evidence retention")
}
return report, nil
}