Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

spindle: switch to rbac2

This commit won't work without following spindle rewrite to use tap and
introduce backfill because repos table is empty yet.

Signed-off-by: Seongmin Lee <git@boltless.me>

+42 -135
+11 -11
spindle/config/config.go
··· 9 9 ) 10 10 11 11 type Server struct { 12 - ListenAddr string `env:"LISTEN_ADDR, default=0.0.0.0:6555"` 13 - DBPath string `env:"DB_PATH, default=spindle.db"` 14 - Hostname string `env:"HOSTNAME, required"` 15 - JetstreamEndpoint string `env:"JETSTREAM_ENDPOINT, default=wss://jetstream1.us-west.bsky.network/subscribe"` 16 - PlcUrl string `env:"PLC_URL, default=https://plc.directory"` 17 - Dev bool `env:"DEV, default=false"` 18 - Owner string `env:"OWNER, required"` 19 - Secrets Secrets `env:",prefix=SECRETS_"` 20 - LogDir string `env:"LOG_DIR, default=/var/log/spindle"` 21 - QueueSize int `env:"QUEUE_SIZE, default=100"` 22 - MaxJobCount int `env:"MAX_JOB_COUNT, default=2"` // max number of jobs that run at a time 12 + ListenAddr string `env:"LISTEN_ADDR, default=0.0.0.0:6555"` 13 + DBPath string `env:"DB_PATH, default=spindle.db"` 14 + Hostname string `env:"HOSTNAME, required"` 15 + JetstreamEndpoint string `env:"JETSTREAM_ENDPOINT, default=wss://jetstream1.us-west.bsky.network/subscribe"` 16 + PlcUrl string `env:"PLC_URL, default=https://plc.directory"` 17 + Dev bool `env:"DEV, default=false"` 18 + Owner syntax.DID `env:"OWNER, required"` 19 + Secrets Secrets `env:",prefix=SECRETS_"` 20 + LogDir string `env:"LOG_DIR, default=/var/log/spindle"` 21 + QueueSize int `env:"QUEUE_SIZE, default=100"` 22 + MaxJobCount int `env:"MAX_JOB_COUNT, default=2"` // max number of jobs that run at a time 23 23 } 24 24 25 25 func (s Server) Did() syntax.DID {
+18 -42
spindle/ingester.go
··· 9 9 10 10 "tangled.org/core/api/tangled" 11 11 "tangled.org/core/eventconsumer" 12 - "tangled.org/core/rbac" 13 12 "tangled.org/core/spindle/db" 14 13 15 14 comatproto "github.com/bluesky-social/indigo/api/atproto" 16 - "github.com/bluesky-social/indigo/atproto/identity" 17 15 "github.com/bluesky-social/indigo/atproto/syntax" 18 16 "github.com/bluesky-social/indigo/xrpc" 19 17 "github.com/bluesky-social/jetstream/pkg/models" 20 - securejoin "github.com/cyphar/filepath-securejoin" 21 18 ) 22 19 23 20 type Ingester func(ctx context.Context, e *models.Event) error ··· 76 79 return fmt.Errorf("domain mismatch: %s != %s", record.Instance, domain) 77 80 } 78 81 79 - ok, err := s.e.IsSpindleInviteAllowed(did, rbacDomain) 82 + ok, err := s.e.IsSpindleMemberInviteAllowed(syntax.DID(did), s.cfg.Server.Did()) 80 83 if err != nil || !ok { 81 84 l.Error("failed to add member", "did", did, "error", err) 82 85 return fmt.Errorf("failed to enforce permissions: %w", err) ··· 93 96 return fmt.Errorf("failed to add member: %w", err) 94 97 } 95 98 96 - if err := s.e.AddSpindleMember(rbacDomain, record.Subject); err != nil { 99 + if err := s.e.AddSpindleMember(syntax.DID(record.Subject), s.cfg.Server.Did()); err != nil { 97 100 l.Error("failed to add member", "error", err) 98 101 return fmt.Errorf("failed to add member: %w", err) 99 102 } ··· 119 122 return fmt.Errorf("failed to remove member: %w", err) 120 123 } 121 124 122 - if err := s.e.RemoveSpindleMember(rbacDomain, record.Subject.String()); err != nil { 125 + if err := s.e.RemoveSpindleMember(record.Subject, s.cfg.Server.Did()); err != nil { 123 126 l.Error("failed to add member", "error", err) 124 127 return fmt.Errorf("failed to add member: %w", err) 125 128 } ··· 173 176 return fmt.Errorf("failed to add repo: %w", err) 174 177 } 175 178 176 - didSlashRepo, err := securejoin.SecureJoin(did, record.Name) 177 - if err != nil { 178 - return err 179 - } 179 + repoAt := syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", did, e.Commit.Collection, e.Commit.RKey)) 180 180 181 181 // add repo to rbac 182 - if err := s.e.AddRepo(did, rbac.ThisServer, didSlashRepo); err != nil { 182 + if err := s.e.AddRepo(repoAt); err != nil { 183 183 l.Error("failed to add repo to enforcer", "error", err) 184 184 return fmt.Errorf("failed to add repo: %w", err) 185 185 } 186 186 187 187 // add collaborators to rbac 188 - owner, err := s.res.ResolveIdent(ctx, did) 189 - if err != nil || owner.Handle.IsInvalidHandle() { 190 - return err 191 - } 192 - if err := s.fetchAndAddCollaborators(ctx, owner, didSlashRepo); err != nil { 188 + if err := s.fetchAndAddCollaborators(ctx, repoAt); err != nil { 193 189 return err 194 190 } 195 191 ··· 224 234 return nil 225 235 } 226 236 227 - // TODO: get rid of this entirely 228 - // resolve this aturi to extract the repo record 229 - owner, err := s.res.ResolveIdent(ctx, repoAt.Authority().String()) 230 - if err != nil || owner.Handle.IsInvalidHandle() { 231 - return fmt.Errorf("failed to resolve handle: %w", err) 232 - } 233 - 234 - xrpcc := xrpc.Client{ 235 - Host: owner.PDSEndpoint(), 236 - } 237 - 238 - resp, err := comatproto.RepoGetRecord(ctx, &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String()) 239 - if err != nil { 240 - return err 241 - } 242 - 243 - repo := resp.Value.Val.(*tangled.Repo) 244 - didSlashRepo, _ := securejoin.SecureJoin(owner.DID.String(), repo.Name) 245 - 246 237 // check perms for this user 247 - if ok, err := s.e.IsCollaboratorInviteAllowed(owner.DID.String(), rbac.ThisServer, didSlashRepo); !ok || err != nil { 238 + if ok, err := s.e.IsRepoCollaboratorInviteAllowed(syntax.DID(e.Did), repoAt); !ok || err != nil { 248 239 return fmt.Errorf("insufficient permissions: %w", err) 249 240 } 250 241 251 242 // add collaborator to rbac 252 - if err := s.e.AddCollaborator(record.Subject, rbac.ThisServer, didSlashRepo); err != nil { 243 + if err := s.e.AddRepoCollaborator(syntax.DID(record.Subject), repoAt); err != nil { 253 244 l.Error("failed to add repo to enforcer", "error", err) 254 245 return fmt.Errorf("failed to add repo: %w", err) 255 246 } ··· 240 269 return nil 241 270 } 242 271 243 - func (s *Spindle) fetchAndAddCollaborators(ctx context.Context, owner *identity.Identity, didSlashRepo string) error { 272 + func (s *Spindle) fetchAndAddCollaborators(ctx context.Context, repo syntax.ATURI) error { 244 273 l := s.l.With("component", "ingester", "handler", "fetchAndAddCollaborators") 245 274 246 275 l.Info("fetching and adding existing collaborators") 247 276 248 - xrpcc := xrpc.Client{ 249 - Host: owner.PDSEndpoint(), 277 + ident, err := s.res.ResolveIdent(ctx, repo.Authority().String()) 278 + if err != nil || ident.Handle.IsInvalidHandle() { 279 + return fmt.Errorf("failed to resolve handle: %w", err) 250 280 } 251 281 252 - resp, err := comatproto.RepoListRecords(ctx, &xrpcc, tangled.RepoCollaboratorNSID, "", 50, owner.DID.String(), false) 282 + xrpcc := xrpc.Client{ 283 + Host: ident.PDSEndpoint(), 284 + } 285 + 286 + resp, err := comatproto.RepoListRecords(ctx, &xrpcc, tangled.RepoCollaboratorNSID, "", 50, ident.DID.String(), false) 253 287 if err != nil { 254 288 return err 255 289 } ··· 266 290 } 267 291 record := r.Value.Val.(*tangled.RepoCollaborator) 268 292 269 - if err := s.e.AddCollaborator(record.Subject, rbac.ThisServer, didSlashRepo); err != nil { 293 + if err := s.e.AddRepoCollaborator(syntax.DID(record.Subject), syntax.ATURI(record.Repo)); err != nil { 270 294 l.Error("failed to add repo to enforcer", "error", err) 271 295 errors.Join(errs, fmt.Errorf("failed to add repo: %w", err)) 272 296 }
+6 -47
spindle/server.go
··· 18 18 "tangled.org/core/jetstream" 19 19 "tangled.org/core/log" 20 20 "tangled.org/core/notifier" 21 - "tangled.org/core/rbac" 21 + "tangled.org/core/rbac2" 22 22 "tangled.org/core/spindle/config" 23 23 "tangled.org/core/spindle/db" 24 24 "tangled.org/core/spindle/engine" ··· 33 33 //go:embed motd 34 34 var defaultMotd []byte 35 35 36 - const ( 37 - rbacDomain = "thisserver" 38 - ) 39 - 40 36 type Spindle struct { 41 37 jc *jetstream.JetstreamClient 42 38 db *db.DB 43 - e *rbac.Enforcer 39 + e *rbac2.Enforcer 44 40 l *slog.Logger 45 41 n *notifier.Notifier 46 42 engs map[string]models.Engine ··· 58 62 return nil, fmt.Errorf("failed to setup db: %w", err) 59 63 } 60 64 61 - e, err := rbac.NewEnforcer(cfg.Server.DBPath) 65 + e, err := rbac2.NewEnforcer(cfg.Server.DBPath) 62 66 if err != nil { 63 67 return nil, fmt.Errorf("failed to setup rbac enforcer: %w", err) 64 68 } 65 - e.E.EnableAutoSave(true) 66 69 67 70 n := notifier.New() 68 71 ··· 102 107 if err != nil { 103 108 return nil, fmt.Errorf("failed to setup jetstream client: %w", err) 104 109 } 105 - jc.AddDid(cfg.Server.Owner) 110 + jc.AddDid(cfg.Server.Owner.String()) 106 111 107 112 // Check if the spindle knows about any Dids; 108 113 dids, err := d.GetAllDids() ··· 129 134 motd: defaultMotd, 130 135 } 131 136 132 - err = e.AddSpindle(rbacDomain) 133 - if err != nil { 134 - return nil, fmt.Errorf("failed to set rbac domain: %w", err) 135 - } 136 - err = spindle.configureOwner() 137 + err = e.SetSpindleOwner(spindle.cfg.Server.Owner, spindle.cfg.Server.Did()) 137 138 if err != nil { 138 139 return nil, err 139 140 } ··· 192 201 } 193 202 194 203 // Enforcer returns the RBAC enforcer instance. 195 - func (s *Spindle) Enforcer() *rbac.Enforcer { 204 + func (s *Spindle) Enforcer() *rbac2.Enforcer { 196 205 return s.e 197 206 } 198 207 ··· 390 399 } 391 400 392 401 return nil 393 - } 394 - 395 - func (s *Spindle) configureOwner() error { 396 - cfgOwner := s.cfg.Server.Owner 397 - 398 - existing, err := s.e.GetSpindleUsersByRole("server:owner", rbacDomain) 399 - if err != nil { 400 - return err 401 - } 402 - 403 - switch len(existing) { 404 - case 0: 405 - // no owner configured, continue 406 - case 1: 407 - // find existing owner 408 - existingOwner := existing[0] 409 - 410 - // no ownership change, this is okay 411 - if existingOwner == s.cfg.Server.Owner { 412 - break 413 - } 414 - 415 - // remove existing owner 416 - err = s.e.RemoveSpindleOwner(rbacDomain, existingOwner) 417 - if err != nil { 418 - return nil 419 - } 420 - default: 421 - return fmt.Errorf("more than one owner in DB, try deleting %q and starting over", s.cfg.Server.DBPath) 422 - } 423 - 424 - return s.e.AddSpindleOwner(rbacDomain, cfgOwner) 425 402 }
+1 -2
spindle/xrpc/add_secret.go
··· 11 11 "github.com/bluesky-social/indigo/xrpc" 12 12 securejoin "github.com/cyphar/filepath-securejoin" 13 13 "tangled.org/core/api/tangled" 14 - "tangled.org/core/rbac" 15 14 "tangled.org/core/spindle/secrets" 16 15 xrpcerr "tangled.org/core/xrpc/errors" 17 16 ) ··· 67 68 return 68 69 } 69 70 70 - if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 71 + if ok, err := x.Enforcer.IsRepoSettingsWriteAllowed(actorDid, repoAt); !ok || err != nil { 71 72 l.Error("insufficent permissions", "did", actorDid.String()) 72 73 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 73 74 return
+1 -2
spindle/xrpc/list_secrets.go
··· 11 11 "github.com/bluesky-social/indigo/xrpc" 12 12 securejoin "github.com/cyphar/filepath-securejoin" 13 13 "tangled.org/core/api/tangled" 14 - "tangled.org/core/rbac" 15 14 "tangled.org/core/spindle/secrets" 16 15 xrpcerr "tangled.org/core/xrpc/errors" 17 16 ) ··· 62 63 return 63 64 } 64 65 65 - if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 66 + if ok, err := x.Enforcer.IsRepoSettingsWriteAllowed(actorDid, repoAt); !ok || err != nil { 66 67 l.Error("insufficent permissions", "did", actorDid.String()) 67 68 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 68 69 return
+1 -1
spindle/xrpc/owner.go
··· 9 9 ) 10 10 11 11 func (x *Xrpc) Owner(w http.ResponseWriter, r *http.Request) { 12 - owner := x.Config.Server.Owner 12 + owner := x.Config.Server.Owner.String() 13 13 if owner == "" { 14 14 writeError(w, xrpcerr.OwnerNotFoundError, http.StatusInternalServerError) 15 15 return
+1 -26
spindle/xrpc/pipeline_cancelPipeline.go
··· 6 6 "net/http" 7 7 "strings" 8 8 9 - "github.com/bluesky-social/indigo/api/atproto" 10 9 "github.com/bluesky-social/indigo/atproto/syntax" 11 - "github.com/bluesky-social/indigo/xrpc" 12 - securejoin "github.com/cyphar/filepath-securejoin" 13 10 "tangled.org/core/api/tangled" 14 - "tangled.org/core/rbac" 15 11 "tangled.org/core/spindle/models" 16 12 xrpcerr "tangled.org/core/xrpc/errors" 17 13 ) ··· 49 53 return 50 54 } 51 55 52 - ident, err := x.Resolver.ResolveIdent(r.Context(), repoAt.Authority().String()) 53 - if err != nil || ident.Handle.IsInvalidHandle() { 54 - fail(xrpcerr.GenericError(fmt.Errorf("failed to resolve handle: %w", err))) 55 - return 56 - } 57 - 58 - xrpcc := xrpc.Client{Host: ident.PDSEndpoint()} 59 - resp, err := atproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String()) 60 - if err != nil { 61 - fail(xrpcerr.GenericError(err)) 62 - return 63 - } 64 - 65 - repo := resp.Value.Val.(*tangled.Repo) 66 - didSlashRepo, err := securejoin.SecureJoin(ident.DID.String(), repo.Name) 67 - if err != nil { 68 - fail(xrpcerr.GenericError(err)) 69 - return 70 - } 71 - 72 - // TODO: fine-grained role based control 73 - isRepoOwner, err := x.Enforcer.IsRepoOwner(actorDid.String(), rbac.ThisServer, didSlashRepo) 56 + isRepoOwner, err := x.Enforcer.IsRepoOwner(actorDid, repoAt) 74 57 if err != nil || !isRepoOwner { 75 58 fail(xrpcerr.AccessControlError(actorDid.String())) 76 59 return
+1 -2
spindle/xrpc/remove_secret.go
··· 10 10 "github.com/bluesky-social/indigo/xrpc" 11 11 securejoin "github.com/cyphar/filepath-securejoin" 12 12 "tangled.org/core/api/tangled" 13 - "tangled.org/core/rbac" 14 13 "tangled.org/core/spindle/secrets" 15 14 xrpcerr "tangled.org/core/xrpc/errors" 16 15 ) ··· 61 62 return 62 63 } 63 64 64 - if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 65 + if ok, err := x.Enforcer.IsRepoSettingsWriteAllowed(actorDid, repoAt); !ok || err != nil { 65 66 l.Error("insufficent permissions", "did", actorDid.String()) 66 67 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 67 68 return
+2 -2
spindle/xrpc/xrpc.go
··· 11 11 "tangled.org/core/api/tangled" 12 12 "tangled.org/core/idresolver" 13 13 "tangled.org/core/notifier" 14 - "tangled.org/core/rbac" 14 + "tangled.org/core/rbac2" 15 15 "tangled.org/core/spindle/config" 16 16 "tangled.org/core/spindle/db" 17 17 "tangled.org/core/spindle/models" ··· 25 25 type Xrpc struct { 26 26 Logger *slog.Logger 27 27 Db *db.DB 28 - Enforcer *rbac.Enforcer 28 + Enforcer *rbac2.Enforcer 29 29 Engines map[string]models.Engine 30 30 Config *config.Config 31 31 Resolver *idresolver.Resolver