From f0d8cd9eefddd6c519fbb4fd7baa73a0151cf5c8 Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Thu, 23 Jul 2026 06:09:10 +0900 Subject: [PATCH] spindle: remove spindle owner Signed-off-by: Seongmin Lee --- contrib/Tiltfile | 1 - docs/DOCS.md | 5 +- localinfra/spindle.Dockerfile | 4 - nix/modules/spindle.nix | 10 +- nix/vm.nix | 1 - spindle/config/config.go | 1 - spindle/config/config_test.go | 1 - spindle/ingester_test.go | 206 ---------------------------------- spindle/server.go | 44 -------- spindle/xrpc/owner.go | 31 ----- spindle/xrpc/xrpc.go | 1 - 11 files changed, 4 insertions(+), 301 deletions(-) delete mode 100644 spindle/xrpc/owner.go diff --git a/contrib/Tiltfile b/contrib/Tiltfile index a9af4f2ba..94d148603 100644 --- a/contrib/Tiltfile +++ b/contrib/Tiltfile @@ -1,5 +1,4 @@ common_env = { - "TANGLED_VM_SPINDLE_OWNER": os.getenv("TANGLED_VM_SPINDLE_OWNER", default=""), "TANGLED_VM_KNOT_OWNER": os.getenv("TANGLED_VM_KNOT_OWNER", default=""), "TANGLED_DB_PATH": os.getenv("TANGLED_DB_PATH", default="dev.db"), "TANGLED_DEV": os.getenv("TANGLED_DEV", default="true"), diff --git a/docs/DOCS.md b/docs/DOCS.md index f71540440..dab585df4 100644 --- a/docs/DOCS.md +++ b/docs/DOCS.md @@ -1453,7 +1453,6 @@ Spindle is configured using environment variables. The following environment var - `SPINDLE_SERVER_HOSTNAME`: The hostname of the server (required). - `SPINDLE_SERVER_JETSTREAM_ENDPOINT`: The endpoint of the Jetstream server (default: `"wss://jetstream1.us-west.bsky.network/subscribe"`). - `SPINDLE_SERVER_DEV`: A boolean indicating whether the server is running in development mode (default: `false`). -- `SPINDLE_SERVER_OWNER`: The DID of the owner (required). - `SPINDLE_SERVER_LOG_DIR`: The directory to store workflow logs (default: `"/var/log/spindle"`). - `SPINDLE_SERVER_DOCKER_SOCKET`: Path to Docker socket to expose to invoked Spindle containers (default: `""`). - `SPINDLE_NIXERY_PIPELINES_NIXERY`: The Nixery URL (default: `"nixery.tangled.sh"`). @@ -1516,7 +1515,6 @@ cache (and read from it), configure the cache (prefix ```shell export SPINDLE_SERVER_HOSTNAME="your-hostname" - export SPINDLE_SERVER_OWNER="your-did" ``` 2. **Build the Spindle binary.** @@ -2637,8 +2635,7 @@ ssh` works can be tricky. It seems to be [possible with To begin, grab your DID from http://localhost:3000/settings. -Then, set `TANGLED_VM_KNOT_OWNER` and -`TANGLED_VM_SPINDLE_OWNER` to your DID. You can now start a +Then, set `TANGLED_VM_KNOT_OWNER` to your DID. You can now start a lightweight NixOS VM like so: ```bash diff --git a/localinfra/spindle.Dockerfile b/localinfra/spindle.Dockerfile index 007e7d847..b9ec4388f 100644 --- a/localinfra/spindle.Dockerfile +++ b/localinfra/spindle.Dockerfile @@ -45,10 +45,6 @@ COPY <<'EOF' /usr/local/bin/spindle-entrypoint.sh #!/bin/sh set -eu -[ -z "${SPINDLE_SERVER_OWNER:-}" ] && [ -r /shared/owner-did ] && \ - export SPINDLE_SERVER_OWNER="$(cat /shared/owner-did)" -: "${SPINDLE_SERVER_OWNER:?set via env or /shared/owner-did}" - mkdir -p /var/lib/spindle /var/lib/spindle/overlays /var/log/spindle if [ -f /usr/local/share/ca-certificates/caddy.crt ]; then diff --git a/nix/modules/spindle.nix b/nix/modules/spindle.nix index 8ba2487b8..6e56cdbbe 100644 --- a/nix/modules/spindle.nix +++ b/nix/modules/spindle.nix @@ -7,6 +7,9 @@ cfg = config.services.tangled.spindle; in with lib; { + imports = [ + (lib.mkRemovedOptionModule [ "services" "tangled" "spindle" "server" "owner" ] "The spindle owner setting is deprecated.") + ]; options = { services.tangled.spindle = { enable = mkOption { @@ -62,12 +65,6 @@ in description = "Enable development mode (disables signature verification)"; }; - owner = mkOption { - type = types.str; - example = "did:plc:qfpnj4og54vl56wngdriaxug"; - description = "DID of owner (required)"; - }; - maxJobCount = mkOption { type = types.int; default = 2; @@ -353,7 +350,6 @@ in "SPINDLE_SERVER_PLC_URL=${cfg.server.plcUrl}" "SPINDLE_SERVER_JETSTREAM_ENDPOINT=${cfg.server.jetstreamEndpoint}" "SPINDLE_SERVER_DEV=${lib.boolToString cfg.server.dev}" - "SPINDLE_SERVER_OWNER=${cfg.server.owner}" "SPINDLE_SERVER_MAX_JOB_COUNT=${toString cfg.server.maxJobCount}" "SPINDLE_SERVER_QUEUE_SIZE=${toString cfg.server.queueSize}" "SPINDLE_SERVER_SECRETS_PROVIDER=${cfg.server.secrets.provider}" diff --git a/nix/vm.nix b/nix/vm.nix index 044a2a9fd..a1cfce0a6 100644 --- a/nix/vm.nix +++ b/nix/vm.nix @@ -149,7 +149,6 @@ in services.tangled.spindle = { enable = true; server = { - owner = envVar "TANGLED_VM_SPINDLE_OWNER"; hostname = envVarOr "TANGLED_VM_SPINDLE_HOST" "localhost:6555"; plcUrl = plcUrl; jetstreamEndpoint = jetstream; diff --git a/spindle/config/config.go b/spindle/config/config.go index 661e35f79..634c0f7c2 100644 --- a/spindle/config/config.go +++ b/spindle/config/config.go @@ -19,7 +19,6 @@ type Server struct { PlcUrl string `env:"PLC_URL, default=https://plc.directory"` Dev bool `env:"DEV, default=false"` DevExtraHosts []string `env:"DEV_EXTRA_HOSTS"` - Owner string `env:"OWNER, required"` Secrets Secrets `env:",prefix=SECRETS_"` LogDir string `env:"LOG_DIR, default=/var/log/spindle"` QueueSize int `env:"QUEUE_SIZE, default=100"` diff --git a/spindle/config/config_test.go b/spindle/config/config_test.go index 12a42e397..78efd66cf 100644 --- a/spindle/config/config_test.go +++ b/spindle/config/config_test.go @@ -7,7 +7,6 @@ import ( func TestLoadAllowsUnconfiguredMicroVMEngine(t *testing.T) { t.Setenv("SPINDLE_SERVER_HOSTNAME", "spindle.example.com") - t.Setenv("SPINDLE_SERVER_OWNER", "did:web:spindle.example.com") t.Setenv("SPINDLE_MICROVM_PIPELINES_IMAGE_DIR", "") cfg, err := Load(context.Background()) diff --git a/spindle/ingester_test.go b/spindle/ingester_test.go index 99a309ece..2a3890bdb 100644 --- a/spindle/ingester_test.go +++ b/spindle/ingester_test.go @@ -3,16 +3,12 @@ package spindle import ( "context" "encoding/json" - "log/slog" - "strings" - "tangled.org/core/jetstream" "testing" "github.com/bluesky-social/indigo/atproto/syntax" "github.com/bluesky-social/jetstream/pkg/models" "tangled.org/core/api/tangled" - "tangled.org/core/rbac" "tangled.org/core/spindle/config" "tangled.org/core/tapc" ) @@ -89,205 +85,3 @@ func TestEmbeddedTapDoesNotSubscribeToPullRecords(t *testing.T) { } } } - -func TestIngestMember_RBAC(t *testing.T) { - d, e := newTestSpindleDB(t) - - cfg := &config.Config{} - cfg.Server.Hostname = "spindle.test" - - jc, jcerr := jetstream.NewJetstreamClient("", "", nil, nil, slog.Default(), nil, false, false) - if jcerr != nil { - t.Fatalf("NewJetstreamClient: %v", jcerr) - } - - s := &Spindle{ - db: d, - e: e, - l: slog.Default(), - cfg: cfg, - jc: jc, - rootCtx: context.Background(), - } - - actorDid := "did:plc:adminactor" - subjectDid := "did:plc:newmember" - rbacDomain := rbac.ThisServer - - memberRecord := tangled.SpindleMember{ - Instance: "spindle.test", - Subject: subjectDid, - } - memberRecordJson, _ := json.Marshal(memberRecord) - - evt := &models.Event{ - Did: actorDid, - Kind: models.EventKindCommit, - Commit: &models.Commit{ - Operation: models.CommitOperationCreate, - Collection: tangled.SpindleMemberNSID, - RKey: "member-rkey-1", - Record: memberRecordJson, - }, - } - - err := s.ingestMember(context.Background(), evt) - if err == nil { - t.Fatal("expected permission denied error, got nil") - } - if !strings.Contains(err.Error(), "permission denied") { - t.Fatalf("expected permission denied, got error: %v", err) - } - - var dbCount int - err = d.QueryRow(`select count(*) from spindle_members where subject = ?`, subjectDid).Scan(&dbCount) - if err != nil { - t.Fatalf("DB query error: %v", err) - } - if dbCount > 0 { - t.Fatal("spindle member was registered in DB on failed auth") - } - - err = e.AddSpindle(rbacDomain) - if err != nil { - t.Fatalf("AddSpindle: %v", err) - } - err = e.AddSpindleOwner(rbacDomain, actorDid) - if err != nil { - t.Fatalf("AddSpindleOwner: %v", err) - } - - err = s.ingestMember(context.Background(), evt) - if err != nil { - t.Fatalf("ingestMember failed for authorized actor: %v", err) - } - - err = d.QueryRow(`select count(*) from spindle_members where subject = ?`, subjectDid).Scan(&dbCount) - if err != nil || dbCount != 1 { - t.Fatalf("expected exactly 1 member in DB, got: %d (err: %v)", dbCount, err) - } - - isMember, err := e.IsSpindleMember(subjectDid, rbacDomain) - if err != nil || !isMember { - t.Fatalf("expected subject to be spindle member in Casbin, got: %t (err: %v)", isMember, err) - } - - deleteEvt := &models.Event{ - Did: actorDid, - Kind: models.EventKindCommit, - Commit: &models.Commit{ - Operation: models.CommitOperationDelete, - Collection: tangled.SpindleMemberNSID, - RKey: "member-rkey-1", - }, - } - - err = s.ingestMember(context.Background(), deleteEvt) - if err != nil { - t.Fatalf("ingestMember delete failed: %v", err) - } - - err = d.QueryRow(`select count(*) from spindle_members where subject = ?`, subjectDid).Scan(&dbCount) - if err != nil || dbCount != 0 { - t.Fatalf("expected 0 members in DB after delete, got: %d (err: %v)", dbCount, err) - } - - isMember, err = e.IsSpindleMember(subjectDid, rbacDomain) - if err != nil || isMember { - t.Fatalf("expected subject to NOT be spindle member in Casbin, got: %t (err: %v)", isMember, err) - } -} - -func TestIngestMember_ForgeDeleteRejection(t *testing.T) { - d, e := newTestSpindleDB(t) - - cfg := &config.Config{} - cfg.Server.Hostname = "spindle.test" - - jc, jcerr := jetstream.NewJetstreamClient("", "", nil, nil, slog.Default(), nil, false, false) - if jcerr != nil { - t.Fatalf("NewJetstreamClient: %v", jcerr) - } - - s := &Spindle{ - db: d, - e: e, - l: slog.Default(), - cfg: cfg, - jc: jc, - rootCtx: context.Background(), - } - - adminDid := "did:plc:adminactor" - bobDid := "did:plc:bobactor" - subjectDid := "did:plc:newmember" - rbacDomain := rbac.ThisServer - - err := e.AddSpindle(rbacDomain) - if err != nil { - t.Fatalf("AddSpindle: %v", err) - } - err = e.AddSpindleOwner(rbacDomain, adminDid) - if err != nil { - t.Fatalf("AddSpindleOwner: %v", err) - } - - memberRecord := tangled.SpindleMember{ - Instance: "spindle.test", - Subject: subjectDid, - } - memberRecordJson, _ := json.Marshal(memberRecord) - - evt := &models.Event{ - Did: adminDid, - Kind: models.EventKindCommit, - Commit: &models.Commit{ - Operation: models.CommitOperationCreate, - Collection: tangled.SpindleMemberNSID, - RKey: "member-rkey-1", - Record: memberRecordJson, - }, - } - - err = s.ingestMember(context.Background(), evt) - if err != nil { - t.Fatalf("ingestMember failed for admin: %v", err) - } - - var dbCount int - err = d.QueryRow(`select count(*) from spindle_members where subject = ?`, subjectDid).Scan(&dbCount) - if err != nil || dbCount != 1 { - t.Fatalf("expected member in DB, got: %d (err: %v)", dbCount, err) - } - - isMember, err := e.IsSpindleMember(subjectDid, rbacDomain) - if err != nil || !isMember { - t.Fatalf("expected subject to be spindle member, got %t (err: %v)", isMember, err) - } - - // bob tries to delete alice's spindle member record, must reject forged delete - deleteEvt := &models.Event{ - Did: bobDid, // Bob is the actor - Kind: models.EventKindCommit, - Commit: &models.Commit{ - Operation: models.CommitOperationDelete, - Collection: tangled.SpindleMemberNSID, - RKey: "member-rkey-1", - }, - } - - err = s.ingestMember(context.Background(), deleteEvt) - if err != nil { - t.Fatalf("ingestMember delete returned error: %v", err) - } - - err = d.QueryRow(`select count(*) from spindle_members where subject = ?`, subjectDid).Scan(&dbCount) - if err != nil || dbCount != 1 { - t.Fatalf("member was deleted from DB, expected remaining, count: %d (err: %v)", dbCount, err) - } - - isMember, err = e.IsSpindleMember(subjectDid, rbacDomain) - if err != nil || !isMember { - t.Fatal("member policy was removed from Casbin by forged delete") - } -} diff --git a/spindle/server.go b/spindle/server.go index 939a254b4..c1e6b0809 100644 --- a/spindle/server.go +++ b/spindle/server.go @@ -113,7 +113,6 @@ func New(ctx context.Context, cfg *config.Config, d *db.DB, engines map[string]m } collections := []string{ - tangled.SpindleMemberNSID, tangled.RepoNSID, tangled.RepoCollaboratorNSID, tangled.RepoPullNSID, @@ -122,7 +121,6 @@ func New(ctx context.Context, cfg *config.Config, d *db.DB, engines map[string]m if err != nil { return nil, fmt.Errorf("failed to setup jetstream client: %w", err) } - jc.AddDid(cfg.Server.Owner) // pull records are created by arbitrary users too, same hack as in tap jc.ExemptCollection(tangled.RepoPullNSID) @@ -163,16 +161,6 @@ func New(ctx context.Context, cfg *config.Config, d *db.DB, engines map[string]m jobWake: make(chan struct{}, 1), } - err = e.AddSpindle(rbacDomain) - if err != nil { - return nil, fmt.Errorf("failed to set rbac domain: %w", err) - } - err = spindle.configureOwner() - if err != nil { - return nil, err - } - logger.Info("owner set", "did", cfg.Server.Owner) - cursorStore, err := cursor.NewSQLiteStore(cfg.Server.DBPath) if err != nil { return nil, fmt.Errorf("failed to setup sqlite3 cursor store: %w", err) @@ -885,35 +873,3 @@ func (s *Spindle) resolvePipelineRepoDid(repo *tangled.Pipeline_TriggerRepo) (sy } return repoDid, nil } - -func (s *Spindle) configureOwner() error { - cfgOwner := s.cfg.Server.Owner - - existing, err := s.e.GetSpindleUsersByRole("server:owner", rbacDomain) - if err != nil { - return err - } - - switch len(existing) { - case 0: - // no owner configured, continue - case 1: - // find existing owner - existingOwner := existing[0] - - // no ownership change, this is okay - if existingOwner == s.cfg.Server.Owner { - break - } - - // remove existing owner - err = s.e.RemoveSpindleOwner(rbacDomain, existingOwner) - if err != nil { - return nil - } - default: - return fmt.Errorf("more than one owner in DB, try deleting %q and starting over", s.cfg.Server.DBPath) - } - - return s.e.AddSpindleOwner(rbacDomain, cfgOwner) -} diff --git a/spindle/xrpc/owner.go b/spindle/xrpc/owner.go deleted file mode 100644 index 6d72a60cd..000000000 --- a/spindle/xrpc/owner.go +++ /dev/null @@ -1,31 +0,0 @@ -package xrpc - -import ( - "encoding/json" - "net/http" - - "tangled.org/core/api/tangled" - xrpcerr "tangled.org/core/xrpc/errors" -) - -func (x *Xrpc) Owner(w http.ResponseWriter, r *http.Request) { - owner := x.Config.Server.Owner - if owner == "" { - writeError(w, xrpcerr.OwnerNotFoundError, http.StatusInternalServerError) - return - } - - response := tangled.Owner_Output{ - Owner: owner, - } - - w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(response); err != nil { - x.Logger.Error("failed to encode response", "error", err) - writeError(w, xrpcerr.NewXrpcError( - xrpcerr.WithTag("InternalServerError"), - xrpcerr.WithMessage("failed to encode response"), - ), http.StatusInternalServerError) - return - } -} diff --git a/spindle/xrpc/xrpc.go b/spindle/xrpc/xrpc.go index d8232be4b..e558deeb4 100644 --- a/spindle/xrpc/xrpc.go +++ b/spindle/xrpc/xrpc.go @@ -67,7 +67,6 @@ func (x *Xrpc) Router() http.Handler { }) // service query endpoints (no auth required) - r.Get("/"+tangled.OwnerNSID, x.Owner) r.Get("/"+tangled.CiSubscribePipelineLogsNSID, x.HandleCiSubscribePipelineLogs) r.Get("/"+tangled.CiQueryPipelinesNSID, x.HandleCiQueryPipelines) r.Get("/"+tangled.CiGetPipelineNSID, x.HandleCiGetPipeline) -- 2.51.2