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 #0
+11 -11
spindle/config/config.go
··· 9 ) 10 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 23 } 24 25 func (s Server) Did() syntax.DID {
··· 9 ) 10 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 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 } 24 25 func (s Server) Did() syntax.DID {
+17 -41
spindle/ingester.go
··· 9 10 "tangled.org/core/api/tangled" 11 "tangled.org/core/eventconsumer" 12 - "tangled.org/core/rbac" 13 "tangled.org/core/spindle/db" 14 15 comatproto "github.com/bluesky-social/indigo/api/atproto" 16 - "github.com/bluesky-social/indigo/atproto/identity" 17 "github.com/bluesky-social/indigo/atproto/syntax" 18 "github.com/bluesky-social/indigo/xrpc" 19 "github.com/bluesky-social/jetstream/pkg/models" 20 - securejoin "github.com/cyphar/filepath-securejoin" 21 ) 22 23 type Ingester func(ctx context.Context, e *models.Event) error ··· 79 return fmt.Errorf("domain mismatch: %s != %s", record.Instance, domain) 80 } 81 82 - ok, err := s.e.IsSpindleInviteAllowed(did, rbacDomain) 83 if err != nil || !ok { 84 l.Error("failed to add member", "did", did, "error", err) 85 return fmt.Errorf("failed to enforce permissions: %w", err) ··· 96 return fmt.Errorf("failed to add member: %w", err) 97 } 98 99 - if err := s.e.AddSpindleMember(rbacDomain, record.Subject); err != nil { 100 l.Error("failed to add member", "error", err) 101 return fmt.Errorf("failed to add member: %w", err) 102 } ··· 122 return fmt.Errorf("failed to remove member: %w", err) 123 } 124 125 - if err := s.e.RemoveSpindleMember(rbacDomain, record.Subject.String()); err != nil { 126 l.Error("failed to add member", "error", err) 127 return fmt.Errorf("failed to add member: %w", err) 128 } ··· 176 return fmt.Errorf("failed to add repo: %w", err) 177 } 178 179 - didSlashRepo, err := securejoin.SecureJoin(did, record.Name) 180 - if err != nil { 181 - return err 182 - } 183 184 // add repo to rbac 185 - if err := s.e.AddRepo(did, rbac.ThisServer, didSlashRepo); err != nil { 186 l.Error("failed to add repo to enforcer", "error", err) 187 return fmt.Errorf("failed to add repo: %w", err) 188 } 189 190 // 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 { 196 return err 197 } 198 ··· 234 return nil 235 } 236 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 // check perms for this user 257 - if ok, err := s.e.IsCollaboratorInviteAllowed(owner.DID.String(), rbac.ThisServer, didSlashRepo); !ok || err != nil { 258 return fmt.Errorf("insufficient permissions: %w", err) 259 } 260 261 // add collaborator to rbac 262 - if err := s.e.AddCollaborator(record.Subject, rbac.ThisServer, didSlashRepo); err != nil { 263 l.Error("failed to add repo to enforcer", "error", err) 264 return fmt.Errorf("failed to add repo: %w", err) 265 } ··· 269 return nil 270 } 271 272 - func (s *Spindle) fetchAndAddCollaborators(ctx context.Context, owner *identity.Identity, didSlashRepo string) error { 273 l := s.l.With("component", "ingester", "handler", "fetchAndAddCollaborators") 274 275 l.Info("fetching and adding existing collaborators") 276 277 xrpcc := xrpc.Client{ 278 - Host: owner.PDSEndpoint(), 279 } 280 281 - resp, err := comatproto.RepoListRecords(ctx, &xrpcc, tangled.RepoCollaboratorNSID, "", 50, owner.DID.String(), false) 282 if err != nil { 283 return err 284 } ··· 290 } 291 record := r.Value.Val.(*tangled.RepoCollaborator) 292 293 - if err := s.e.AddCollaborator(record.Subject, rbac.ThisServer, didSlashRepo); err != nil { 294 l.Error("failed to add repo to enforcer", "error", err) 295 errors.Join(errs, fmt.Errorf("failed to add repo: %w", err)) 296 }
··· 9 10 "tangled.org/core/api/tangled" 11 "tangled.org/core/eventconsumer" 12 "tangled.org/core/spindle/db" 13 14 comatproto "github.com/bluesky-social/indigo/api/atproto" 15 "github.com/bluesky-social/indigo/atproto/syntax" 16 "github.com/bluesky-social/indigo/xrpc" 17 "github.com/bluesky-social/jetstream/pkg/models" 18 ) 19 20 type Ingester func(ctx context.Context, e *models.Event) error ··· 76 return fmt.Errorf("domain mismatch: %s != %s", record.Instance, domain) 77 } 78 79 + ok, err := s.e.IsSpindleMemberInviteAllowed(syntax.DID(did), s.cfg.Server.Did()) 80 if err != nil || !ok { 81 l.Error("failed to add member", "did", did, "error", err) 82 return fmt.Errorf("failed to enforce permissions: %w", err) ··· 93 return fmt.Errorf("failed to add member: %w", err) 94 } 95 96 + if err := s.e.AddSpindleMember(syntax.DID(record.Subject), s.cfg.Server.Did()); err != nil { 97 l.Error("failed to add member", "error", err) 98 return fmt.Errorf("failed to add member: %w", err) 99 } ··· 119 return fmt.Errorf("failed to remove member: %w", err) 120 } 121 122 + if err := s.e.RemoveSpindleMember(record.Subject, s.cfg.Server.Did()); err != nil { 123 l.Error("failed to add member", "error", err) 124 return fmt.Errorf("failed to add member: %w", err) 125 } ··· 173 return fmt.Errorf("failed to add repo: %w", err) 174 } 175 176 + repoAt := syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", did, e.Commit.Collection, e.Commit.RKey)) 177 178 // add repo to rbac 179 + if err := s.e.AddRepo(repoAt); err != nil { 180 l.Error("failed to add repo to enforcer", "error", err) 181 return fmt.Errorf("failed to add repo: %w", err) 182 } 183 184 // add collaborators to rbac 185 + if err := s.fetchAndAddCollaborators(ctx, repoAt); err != nil { 186 return err 187 } 188 ··· 224 return nil 225 } 226 227 // check perms for this user 228 + if ok, err := s.e.IsRepoCollaboratorInviteAllowed(syntax.DID(e.Did), repoAt); !ok || err != nil { 229 return fmt.Errorf("insufficient permissions: %w", err) 230 } 231 232 // add collaborator to rbac 233 + if err := s.e.AddRepoCollaborator(syntax.DID(record.Subject), repoAt); err != nil { 234 l.Error("failed to add repo to enforcer", "error", err) 235 return fmt.Errorf("failed to add repo: %w", err) 236 } ··· 240 return nil 241 } 242 243 + func (s *Spindle) fetchAndAddCollaborators(ctx context.Context, repo syntax.ATURI) error { 244 l := s.l.With("component", "ingester", "handler", "fetchAndAddCollaborators") 245 246 l.Info("fetching and adding existing collaborators") 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 + 253 xrpcc := xrpc.Client{ 254 + Host: ident.PDSEndpoint(), 255 } 256 257 + resp, err := comatproto.RepoListRecords(ctx, &xrpcc, tangled.RepoCollaboratorNSID, "", 50, ident.DID.String(), false) 258 if err != nil { 259 return err 260 } ··· 266 } 267 record := r.Value.Val.(*tangled.RepoCollaborator) 268 269 + if err := s.e.AddRepoCollaborator(syntax.DID(record.Subject), syntax.ATURI(record.Repo)); err != nil { 270 l.Error("failed to add repo to enforcer", "error", err) 271 errors.Join(errs, fmt.Errorf("failed to add repo: %w", err)) 272 }
+6 -47
spindle/server.go
··· 18 "tangled.org/core/jetstream" 19 "tangled.org/core/log" 20 "tangled.org/core/notifier" 21 - "tangled.org/core/rbac" 22 "tangled.org/core/spindle/config" 23 "tangled.org/core/spindle/db" 24 "tangled.org/core/spindle/engine" ··· 33 //go:embed motd 34 var defaultMotd []byte 35 36 - const ( 37 - rbacDomain = "thisserver" 38 - ) 39 - 40 type Spindle struct { 41 jc *jetstream.JetstreamClient 42 db *db.DB 43 - e *rbac.Enforcer 44 l *slog.Logger 45 n *notifier.Notifier 46 engs map[string]models.Engine ··· 62 return nil, fmt.Errorf("failed to setup db: %w", err) 63 } 64 65 - e, err := rbac.NewEnforcer(cfg.Server.DBPath) 66 if err != nil { 67 return nil, fmt.Errorf("failed to setup rbac enforcer: %w", err) 68 } 69 - e.E.EnableAutoSave(true) 70 71 n := notifier.New() 72 ··· 107 if err != nil { 108 return nil, fmt.Errorf("failed to setup jetstream client: %w", err) 109 } 110 - jc.AddDid(cfg.Server.Owner) 111 112 // Check if the spindle knows about any Dids; 113 dids, err := d.GetAllDids() ··· 134 motd: defaultMotd, 135 } 136 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() 142 if err != nil { 143 return nil, err 144 } ··· 201 } 202 203 // Enforcer returns the RBAC enforcer instance. 204 - func (s *Spindle) Enforcer() *rbac.Enforcer { 205 return s.e 206 } 207 ··· 400 401 return nil 402 } 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 - }
··· 18 "tangled.org/core/jetstream" 19 "tangled.org/core/log" 20 "tangled.org/core/notifier" 21 + "tangled.org/core/rbac2" 22 "tangled.org/core/spindle/config" 23 "tangled.org/core/spindle/db" 24 "tangled.org/core/spindle/engine" ··· 33 //go:embed motd 34 var defaultMotd []byte 35 36 type Spindle struct { 37 jc *jetstream.JetstreamClient 38 db *db.DB 39 + e *rbac2.Enforcer 40 l *slog.Logger 41 n *notifier.Notifier 42 engs map[string]models.Engine ··· 58 return nil, fmt.Errorf("failed to setup db: %w", err) 59 } 60 61 + e, err := rbac2.NewEnforcer(cfg.Server.DBPath) 62 if err != nil { 63 return nil, fmt.Errorf("failed to setup rbac enforcer: %w", err) 64 } 65 66 n := notifier.New() 67 ··· 102 if err != nil { 103 return nil, fmt.Errorf("failed to setup jetstream client: %w", err) 104 } 105 + jc.AddDid(cfg.Server.Owner.String()) 106 107 // Check if the spindle knows about any Dids; 108 dids, err := d.GetAllDids() ··· 129 motd: defaultMotd, 130 } 131 132 + err = e.SetSpindleOwner(spindle.cfg.Server.Owner, spindle.cfg.Server.Did()) 133 if err != nil { 134 return nil, err 135 } ··· 192 } 193 194 // Enforcer returns the RBAC enforcer instance. 195 + func (s *Spindle) Enforcer() *rbac2.Enforcer { 196 return s.e 197 } 198 ··· 391 392 return nil 393 }
+1 -2
spindle/xrpc/add_secret.go
··· 11 "github.com/bluesky-social/indigo/xrpc" 12 securejoin "github.com/cyphar/filepath-securejoin" 13 "tangled.org/core/api/tangled" 14 - "tangled.org/core/rbac" 15 "tangled.org/core/spindle/secrets" 16 xrpcerr "tangled.org/core/xrpc/errors" 17 ) ··· 68 return 69 } 70 71 - if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 72 l.Error("insufficent permissions", "did", actorDid.String()) 73 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 74 return
··· 11 "github.com/bluesky-social/indigo/xrpc" 12 securejoin "github.com/cyphar/filepath-securejoin" 13 "tangled.org/core/api/tangled" 14 "tangled.org/core/spindle/secrets" 15 xrpcerr "tangled.org/core/xrpc/errors" 16 ) ··· 67 return 68 } 69 70 + if ok, err := x.Enforcer.IsRepoSettingsWriteAllowed(actorDid, repoAt); !ok || err != nil { 71 l.Error("insufficent permissions", "did", actorDid.String()) 72 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 73 return
+1 -2
spindle/xrpc/list_secrets.go
··· 11 "github.com/bluesky-social/indigo/xrpc" 12 securejoin "github.com/cyphar/filepath-securejoin" 13 "tangled.org/core/api/tangled" 14 - "tangled.org/core/rbac" 15 "tangled.org/core/spindle/secrets" 16 xrpcerr "tangled.org/core/xrpc/errors" 17 ) ··· 63 return 64 } 65 66 - if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 67 l.Error("insufficent permissions", "did", actorDid.String()) 68 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 69 return
··· 11 "github.com/bluesky-social/indigo/xrpc" 12 securejoin "github.com/cyphar/filepath-securejoin" 13 "tangled.org/core/api/tangled" 14 "tangled.org/core/spindle/secrets" 15 xrpcerr "tangled.org/core/xrpc/errors" 16 ) ··· 62 return 63 } 64 65 + if ok, err := x.Enforcer.IsRepoSettingsWriteAllowed(actorDid, repoAt); !ok || err != nil { 66 l.Error("insufficent permissions", "did", actorDid.String()) 67 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 68 return
+1 -1
spindle/xrpc/owner.go
··· 9 ) 10 11 func (x *Xrpc) Owner(w http.ResponseWriter, r *http.Request) { 12 - owner := x.Config.Server.Owner 13 if owner == "" { 14 writeError(w, xrpcerr.OwnerNotFoundError, http.StatusInternalServerError) 15 return
··· 9 ) 10 11 func (x *Xrpc) Owner(w http.ResponseWriter, r *http.Request) { 12 + owner := x.Config.Server.Owner.String() 13 if owner == "" { 14 writeError(w, xrpcerr.OwnerNotFoundError, http.StatusInternalServerError) 15 return
+1 -26
spindle/xrpc/pipeline_cancelPipeline.go
··· 6 "net/http" 7 "strings" 8 9 - "github.com/bluesky-social/indigo/api/atproto" 10 "github.com/bluesky-social/indigo/atproto/syntax" 11 - "github.com/bluesky-social/indigo/xrpc" 12 - securejoin "github.com/cyphar/filepath-securejoin" 13 "tangled.org/core/api/tangled" 14 - "tangled.org/core/rbac" 15 "tangled.org/core/spindle/models" 16 xrpcerr "tangled.org/core/xrpc/errors" 17 ) ··· 53 return 54 } 55 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) 78 if err != nil || !isRepoOwner { 79 fail(xrpcerr.AccessControlError(actorDid.String())) 80 return
··· 6 "net/http" 7 "strings" 8 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 "tangled.org/core/api/tangled" 11 "tangled.org/core/spindle/models" 12 xrpcerr "tangled.org/core/xrpc/errors" 13 ) ··· 49 return 50 } 51 52 + isRepoOwner, err := x.Enforcer.IsRepoOwner(actorDid, repoAt) 53 if err != nil || !isRepoOwner { 54 fail(xrpcerr.AccessControlError(actorDid.String())) 55 return
+1 -2
spindle/xrpc/remove_secret.go
··· 10 "github.com/bluesky-social/indigo/xrpc" 11 securejoin "github.com/cyphar/filepath-securejoin" 12 "tangled.org/core/api/tangled" 13 - "tangled.org/core/rbac" 14 "tangled.org/core/spindle/secrets" 15 xrpcerr "tangled.org/core/xrpc/errors" 16 ) ··· 62 return 63 } 64 65 - if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 66 l.Error("insufficent permissions", "did", actorDid.String()) 67 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 68 return
··· 10 "github.com/bluesky-social/indigo/xrpc" 11 securejoin "github.com/cyphar/filepath-securejoin" 12 "tangled.org/core/api/tangled" 13 "tangled.org/core/spindle/secrets" 14 xrpcerr "tangled.org/core/xrpc/errors" 15 ) ··· 61 return 62 } 63 64 + if ok, err := x.Enforcer.IsRepoSettingsWriteAllowed(actorDid, repoAt); !ok || err != nil { 65 l.Error("insufficent permissions", "did", actorDid.String()) 66 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 67 return
+2 -2
spindle/xrpc/xrpc.go
··· 11 "tangled.org/core/api/tangled" 12 "tangled.org/core/idresolver" 13 "tangled.org/core/notifier" 14 - "tangled.org/core/rbac" 15 "tangled.org/core/spindle/config" 16 "tangled.org/core/spindle/db" 17 "tangled.org/core/spindle/models" ··· 25 type Xrpc struct { 26 Logger *slog.Logger 27 Db *db.DB 28 - Enforcer *rbac.Enforcer 29 Engines map[string]models.Engine 30 Config *config.Config 31 Resolver *idresolver.Resolver
··· 11 "tangled.org/core/api/tangled" 12 "tangled.org/core/idresolver" 13 "tangled.org/core/notifier" 14 + "tangled.org/core/rbac2" 15 "tangled.org/core/spindle/config" 16 "tangled.org/core/spindle/db" 17 "tangled.org/core/spindle/models" ··· 25 type Xrpc struct { 26 Logger *slog.Logger 27 Db *db.DB 28 + Enforcer *rbac2.Enforcer 29 Engines map[string]models.Engine 30 Config *config.Config 31 Resolver *idresolver.Resolver

History

4 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
boltless.me submitted #0
1 commit
expand
spindle: switch to rbac2
1/3 failed, 2/3 success
expand
expand 0 comments