Monorepo for Tangled tangled.org

spindle: switch to rbac2 #977

open opened by boltless.me targeting master from sl/spindle-rewrite

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

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:xasnlahkri4ewmbuzly2rlc5/sh.tangled.repo.pull/3mckguakek622
+41 -134
Diff #2
+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 {
+17 -41
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 ··· 79 76 return fmt.Errorf("domain mismatch: %s != %s", record.Instance, domain) 80 77 } 81 78 82 - ok, err := s.e.IsSpindleInviteAllowed(did, rbacDomain) 79 + ok, err := s.e.IsSpindleMemberInviteAllowed(syntax.DID(did), s.cfg.Server.Did()) 83 80 if err != nil || !ok { 84 81 l.Error("failed to add member", "did", did, "error", err) 85 82 return fmt.Errorf("failed to enforce permissions: %w", err) ··· 96 93 return fmt.Errorf("failed to add member: %w", err) 97 94 } 98 95 99 - if err := s.e.AddSpindleMember(rbacDomain, record.Subject); err != nil { 96 + if err := s.e.AddSpindleMember(syntax.DID(record.Subject), s.cfg.Server.Did()); err != nil { 100 97 l.Error("failed to add member", "error", err) 101 98 return fmt.Errorf("failed to add member: %w", err) 102 99 } ··· 122 119 return fmt.Errorf("failed to remove member: %w", err) 123 120 } 124 121 125 - if err := s.e.RemoveSpindleMember(rbacDomain, record.Subject.String()); err != nil { 122 + if err := s.e.RemoveSpindleMember(record.Subject, s.cfg.Server.Did()); err != nil { 126 123 l.Error("failed to add member", "error", err) 127 124 return fmt.Errorf("failed to add member: %w", err) 128 125 } ··· 176 173 return fmt.Errorf("failed to add repo: %w", err) 177 174 } 178 175 179 - didSlashRepo, err := securejoin.SecureJoin(did, record.Name) 180 - if err != nil { 181 - return err 182 - } 176 + repoAt := syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", did, e.Commit.Collection, e.Commit.RKey)) 183 177 184 178 // add repo to rbac 185 - if err := s.e.AddRepo(did, rbac.ThisServer, didSlashRepo); err != nil { 179 + if err := s.e.AddRepo(repoAt); err != nil { 186 180 l.Error("failed to add repo to enforcer", "error", err) 187 181 return fmt.Errorf("failed to add repo: %w", err) 188 182 } 189 183 190 184 // add collaborators to rbac 191 - owner, err := s.res.ResolveIdent(ctx, did) 192 - if err != nil || owner.Handle.IsInvalidHandle() { 193 - return err 194 - } 195 - if err := s.fetchAndAddCollaborators(ctx, owner, didSlashRepo); err != nil { 185 + if err := s.fetchAndAddCollaborators(ctx, repoAt); err != nil { 196 186 return err 197 187 } 198 188 ··· 234 224 return nil 235 225 } 236 226 237 - // TODO: get rid of this entirely 238 - // resolve this aturi to extract the repo record 239 - owner, err := s.res.ResolveIdent(ctx, repoAt.Authority().String()) 240 - if err != nil || owner.Handle.IsInvalidHandle() { 241 - return fmt.Errorf("failed to resolve handle: %w", err) 242 - } 243 - 244 - xrpcc := xrpc.Client{ 245 - Host: owner.PDSEndpoint(), 246 - } 247 - 248 - resp, err := comatproto.RepoGetRecord(ctx, &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String()) 249 - if err != nil { 250 - return err 251 - } 252 - 253 - repo := resp.Value.Val.(*tangled.Repo) 254 - didSlashRepo, _ := securejoin.SecureJoin(owner.DID.String(), repo.Name) 255 - 256 227 // check perms for this user 257 - if ok, err := s.e.IsCollaboratorInviteAllowed(owner.DID.String(), rbac.ThisServer, didSlashRepo); !ok || err != nil { 228 + if ok, err := s.e.IsRepoCollaboratorInviteAllowed(syntax.DID(e.Did), repoAt); !ok || err != nil { 258 229 return fmt.Errorf("insufficient permissions: %w", err) 259 230 } 260 231 261 232 // add collaborator to rbac 262 - if err := s.e.AddCollaborator(record.Subject, rbac.ThisServer, didSlashRepo); err != nil { 233 + if err := s.e.AddRepoCollaborator(syntax.DID(record.Subject), repoAt); err != nil { 263 234 l.Error("failed to add repo to enforcer", "error", err) 264 235 return fmt.Errorf("failed to add repo: %w", err) 265 236 } ··· 269 240 return nil 270 241 } 271 242 272 - func (s *Spindle) fetchAndAddCollaborators(ctx context.Context, owner *identity.Identity, didSlashRepo string) error { 243 + func (s *Spindle) fetchAndAddCollaborators(ctx context.Context, repo syntax.ATURI) error { 273 244 l := s.l.With("component", "ingester", "handler", "fetchAndAddCollaborators") 274 245 275 246 l.Info("fetching and adding existing collaborators") 276 247 248 + ident, err := s.res.ResolveIdent(ctx, repo.Authority().String()) 249 + if err != nil || ident.Handle.IsInvalidHandle() { 250 + return fmt.Errorf("failed to resolve handle: %w", err) 251 + } 252 + 277 253 xrpcc := xrpc.Client{ 278 - Host: owner.PDSEndpoint(), 254 + Host: ident.PDSEndpoint(), 279 255 } 280 256 281 - resp, err := comatproto.RepoListRecords(ctx, &xrpcc, tangled.RepoCollaboratorNSID, "", 50, owner.DID.String(), false) 257 + resp, err := comatproto.RepoListRecords(ctx, &xrpcc, tangled.RepoCollaboratorNSID, "", 50, ident.DID.String(), false) 282 258 if err != nil { 283 259 return err 284 260 } ··· 290 266 } 291 267 record := r.Value.Val.(*tangled.RepoCollaborator) 292 268 293 - if err := s.e.AddCollaborator(record.Subject, rbac.ThisServer, didSlashRepo); err != nil { 269 + if err := s.e.AddRepoCollaborator(syntax.DID(record.Subject), syntax.ATURI(record.Repo)); err != nil { 294 270 l.Error("failed to add repo to enforcer", "error", err) 295 271 errors.Join(errs, fmt.Errorf("failed to add repo: %w", err)) 296 272 }
+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 ··· 62 58 return nil, fmt.Errorf("failed to setup db: %w", err) 63 59 } 64 60 65 - e, err := rbac.NewEnforcer(cfg.Server.DBPath) 61 + e, err := rbac2.NewEnforcer(cfg.Server.DBPath) 66 62 if err != nil { 67 63 return nil, fmt.Errorf("failed to setup rbac enforcer: %w", err) 68 64 } 69 - e.E.EnableAutoSave(true) 70 65 71 66 n := notifier.New() 72 67 ··· 107 102 if err != nil { 108 103 return nil, fmt.Errorf("failed to setup jetstream client: %w", err) 109 104 } 110 - jc.AddDid(cfg.Server.Owner) 105 + jc.AddDid(cfg.Server.Owner.String()) 111 106 112 107 // Check if the spindle knows about any Dids; 113 108 dids, err := d.GetAllDids() ··· 134 129 motd: defaultMotd, 135 130 } 136 131 137 - err = e.AddSpindle(rbacDomain) 138 - if err != nil { 139 - return nil, fmt.Errorf("failed to set rbac domain: %w", err) 140 - } 141 - err = spindle.configureOwner() 132 + err = e.SetSpindleOwner(spindle.cfg.Server.Owner, spindle.cfg.Server.Did()) 142 133 if err != nil { 143 134 return nil, err 144 135 } ··· 201 192 } 202 193 203 194 // Enforcer returns the RBAC enforcer instance. 204 - func (s *Spindle) Enforcer() *rbac.Enforcer { 195 + func (s *Spindle) Enforcer() *rbac2.Enforcer { 205 196 return s.e 206 197 } 207 198 ··· 400 391 401 392 return nil 402 393 } 403 - 404 - func (s *Spindle) configureOwner() error { 405 - cfgOwner := s.cfg.Server.Owner 406 - 407 - existing, err := s.e.GetSpindleUsersByRole("server:owner", rbacDomain) 408 - if err != nil { 409 - return err 410 - } 411 - 412 - switch len(existing) { 413 - case 0: 414 - // no owner configured, continue 415 - case 1: 416 - // find existing owner 417 - existingOwner := existing[0] 418 - 419 - // no ownership change, this is okay 420 - if existingOwner == s.cfg.Server.Owner { 421 - break 422 - } 423 - 424 - // remove existing owner 425 - err = s.e.RemoveSpindleOwner(rbacDomain, existingOwner) 426 - if err != nil { 427 - return nil 428 - } 429 - default: 430 - return fmt.Errorf("more than one owner in DB, try deleting %q and starting over", s.cfg.Server.DBPath) 431 - } 432 - 433 - return s.e.AddSpindleOwner(rbacDomain, cfgOwner) 434 - }
+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 ) ··· 68 67 return 69 68 } 70 69 71 - if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 70 + if ok, err := x.Enforcer.IsRepoSettingsWriteAllowed(actorDid, repoAt); !ok || err != nil { 72 71 l.Error("insufficent permissions", "did", actorDid.String()) 73 72 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 74 73 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 ) ··· 63 62 return 64 63 } 65 64 66 - 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 { 67 66 l.Error("insufficent permissions", "did", actorDid.String()) 68 67 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 69 68 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 ) ··· 53 49 return 54 50 } 55 51 56 - ident, err := x.Resolver.ResolveIdent(r.Context(), repoAt.Authority().String()) 57 - if err != nil || ident.Handle.IsInvalidHandle() { 58 - fail(xrpcerr.GenericError(fmt.Errorf("failed to resolve handle: %w", err))) 59 - return 60 - } 61 - 62 - xrpcc := xrpc.Client{Host: ident.PDSEndpoint()} 63 - resp, err := atproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String()) 64 - if err != nil { 65 - fail(xrpcerr.GenericError(err)) 66 - return 67 - } 68 - 69 - repo := resp.Value.Val.(*tangled.Repo) 70 - didSlashRepo, err := securejoin.SecureJoin(ident.DID.String(), repo.Name) 71 - if err != nil { 72 - fail(xrpcerr.GenericError(err)) 73 - return 74 - } 75 - 76 - // TODO: fine-grained role based control 77 - isRepoOwner, err := x.Enforcer.IsRepoOwner(actorDid.String(), rbac.ThisServer, didSlashRepo) 52 + isRepoOwner, err := x.Enforcer.IsRepoOwner(actorDid, repoAt) 78 53 if err != nil || !isRepoOwner { 79 54 fail(xrpcerr.AccessControlError(actorDid.String())) 80 55 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 ) ··· 62 61 return 63 62 } 64 63 65 - if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 64 + if ok, err := x.Enforcer.IsRepoSettingsWriteAllowed(actorDid, repoAt); !ok || err != nil { 66 65 l.Error("insufficent permissions", "did", actorDid.String()) 67 66 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 68 67 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

History

3 rounds 0 comments
sign up or login to add to the discussion
1 commit
expand
spindle: switch to rbac2
1/3 failed, 2/3 success
expand
no conflicts, ready to merge
expand 0 comments
1 commit
expand
spindle: switch to rbac2
1/3 failed, 2/3 success
expand
expand 0 comments
1 commit
expand
spindle: switch to rbac2
1/3 failed, 2/3 success
expand
expand 0 comments