Skip to content

Commit

Permalink
Merge pull request #6321 from weaviate/rbac-raft-init
Browse files Browse the repository at this point in the history
raft(rbac): persist RBCA changes in all nodes
  • Loading branch information
moogacs authored Nov 13, 2024
2 parents ed3db4b 02c755f commit 05e793d
Show file tree
Hide file tree
Showing 13 changed files with 692 additions and 175 deletions.
4 changes: 3 additions & 1 deletion adapters/handlers/rest/configure_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 467,7 @@ func MakeAppState(ctx context.Context, options *swag.CommandLineOptionsGroup) *s
FQDNResolverTLD: appState.ServerConfig.Config.Raft.FQDNResolverTLD,
SentryEnabled: appState.ServerConfig.Config.Sentry.Enabled,
ClassTenantDataEvents: classTenantDataEvents,
AuthzController: appState.AuthzController,
}
for _, name := range appState.ServerConfig.Config.Raft.Join[:rConfig.BootstrapExpect] {
if strings.Contains(name, rConfig.NodeID) {
Expand All @@ -476,6 477,7 @@ func MakeAppState(ctx context.Context, options *swag.CommandLineOptionsGroup) *s
}

appState.ClusterService = rCluster.New(appState.Cluster, rConfig)

migrator.SetCluster(appState.ClusterService.Raft)

executor := schema.NewExecutor(migrator,
Expand Down Expand Up @@ -658,7 660,7 @@ func configureAPI(api *operations.WeaviateAPI) http.Handler {
appState.Authorizer,
appState.Logger, appState.Modules)

setupAuthZHandlers(api, appState.AuthzController, appState.Metrics, appState.Authorizer, appState.Logger)
setupAuthZHandlers(api, appState.ClusterService.Raft, appState.Metrics, appState.Authorizer, appState.Logger)
setupSchemaHandlers(api, appState.SchemaManager, appState.Metrics, appState.Logger)
objectsManager := objects.NewManager(appState.Locks,
appState.SchemaManager, appState.ServerConfig, appState.Logger,
Expand Down
320 changes: 172 additions & 148 deletions cluster/proto/api/message.pb.go

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions cluster/proto/api/message.proto
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 53,13 @@ message ApplyRequest {
TYPE_DELETE_TENANT = 18;
TYPE_TENANT_PROCESS = 19;


TYPE_UPSERT_ROLES = 60;
TYPE_DELETE_ROLES = 61;
TYPE_REMOVE_PERMISSIONS = 62;
TYPE_ADD_ROLES_FOR_USER = 63;
TYPE_REVOKE_ROLES_FOR_USER = 64;

TYPE_STORE_SCHEMA_V1 = 99;
}
Type type = 1;
Expand Down
39 changes: 39 additions & 0 deletions cluster/proto/api/rbac_requests.go
Original file line number Diff line number Diff line change
@@ -0,0 1,39 @@
// _ _
// __ _____ __ ___ ___ __ _| |_ ___
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
// \ V V / __/ (_| |\ V /| | (_| | || __/
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
//
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
//
// CONTACT: [email protected]
//

package api

import (
"github.com/weaviate/weaviate/entities/models"
)

type CreateRolesRequest struct {
Roles []*models.Role
}

type DeleteRolesRequest struct {
Roles []string
}

type RemovePermissionsRequest struct {
Role string
Permissions []*models.Permission
}

type AddRolesForUsersRequest struct {
User string
Roles []string
}

type RevokeRolesForUserRequest struct {
User string
Roles []string
}
File renamed without changes.
118 changes: 118 additions & 0 deletions cluster/raft_rbac_apply_endpoints.go
Original file line number Diff line number Diff line change
@@ -0,0 1,118 @@
// _ _
// __ _____ __ ___ ___ __ _| |_ ___
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
// \ V V / __/ (_| |\ V /| | (_| | || __/
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
//
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
//
// CONTACT: [email protected]
//

package cluster

import (
"context"
"encoding/json"
"fmt"

cmd "github.com/weaviate/weaviate/cluster/proto/api"
"github.com/weaviate/weaviate/cluster/schema"
"github.com/weaviate/weaviate/entities/models"
)

func (s *Raft) UpsertRoles(roles ...*models.Role) error {
if len(roles) == 0 {
return fmt.Errorf("no roles to create: %w", schema.ErrBadRequest)
}

req := cmd.CreateRolesRequest{Roles: roles}
subCommand, err := json.Marshal(&req)
if err != nil {
return fmt.Errorf("marshal request: %w", err)
}
command := &cmd.ApplyRequest{
Type: cmd.ApplyRequest_TYPE_UPSERT_ROLES,
SubCommand: subCommand,
}
if _, err := s.Execute(context.Background(), command); err != nil {
return err
}
return nil
}

func (s *Raft) DeleteRoles(names ...string) error {
if len(names) == 0 {
return fmt.Errorf("no roles to delete: %w", schema.ErrBadRequest)
}
req := cmd.DeleteRolesRequest{Roles: names}
subCommand, err := json.Marshal(&req)
if err != nil {
return fmt.Errorf("marshal request: %w", err)
}
command := &cmd.ApplyRequest{
Type: cmd.ApplyRequest_TYPE_DELETE_ROLES,
SubCommand: subCommand,
}
if _, err := s.Execute(context.Background(), command); err != nil {
return err
}
return nil
}

func (s *Raft) RemovePermissions(role string, permissions []*models.Permission) error {
if role == "" {
return fmt.Errorf("no roles to remove permissions from: %w", schema.ErrBadRequest)
}
req := cmd.RemovePermissionsRequest{Role: role, Permissions: permissions}
subCommand, err := json.Marshal(&req)
if err != nil {
return fmt.Errorf("marshal request: %w", err)
}
command := &cmd.ApplyRequest{
Type: cmd.ApplyRequest_TYPE_REMOVE_PERMISSIONS,
SubCommand: subCommand,
}
if _, err := s.Execute(context.Background(), command); err != nil {
return err
}
return nil
}

func (s *Raft) AddRolesForUser(user string, roles []string) error {
if len(roles) == 0 {
return fmt.Errorf("no roles to assign: %w", schema.ErrBadRequest)
}
req := cmd.AddRolesForUsersRequest{User: user, Roles: roles}
subCommand, err := json.Marshal(&req)
if err != nil {
return fmt.Errorf("marshal request: %w", err)
}
command := &cmd.ApplyRequest{
Type: cmd.ApplyRequest_TYPE_ADD_ROLES_FOR_USER,
SubCommand: subCommand,
}
if _, err := s.Execute(context.Background(), command); err != nil {
return err
}
return nil
}

func (s *Raft) RevokeRolesForUser(user string, roles ...string) error {
if len(roles) == 0 {
return fmt.Errorf("no roles to revoke: %w", schema.ErrBadRequest)
}
req := cmd.RevokeRolesForUserRequest{User: user, Roles: roles}
subCommand, err := json.Marshal(&req)
if err != nil {
return fmt.Errorf("marshal request: %w", err)
}
command := &cmd.ApplyRequest{
Type: cmd.ApplyRequest_TYPE_REVOKE_ROLES_FOR_USER,
SubCommand: subCommand,
}
if _, err := s.Execute(context.Background(), command); err != nil {
return err
}
return nil
}
28 changes: 28 additions & 0 deletions cluster/raft_rbac_query_endpoints.go
Original file line number Diff line number Diff line change
@@ -0,0 1,28 @@
// _ _
// __ _____ __ ___ ___ __ _| |_ ___
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
// \ V V / __/ (_| |\ V /| | (_| | || __/
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
//
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
//
// CONTACT: [email protected]
//

package cluster

import (
"github.com/weaviate/weaviate/entities/models"
)

func (s *Raft) GetRoles(names ...string) ([]*models.Role, error) {
return s.store.authZManager.GetRoles(names...)
}

func (s *Raft) GetRolesForUser(user string) ([]*models.Role, error) {
return s.store.authZManager.GetRolesForUser(user)
}

func (s *Raft) GetUsersForRole(role string) ([]string, error) {
return s.store.authZManager.GetUsersForRole(role)
}
91 changes: 91 additions & 0 deletions cluster/rbac/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 1,91 @@
// _ _
// __ _____ __ ___ ___ __ _| |_ ___
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
// \ V V / __/ (_| |\ V /| | (_| | || __/
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
//
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
//
// CONTACT: [email protected]
//

package rbac

import (
"encoding/json"
"errors"
"fmt"

"github.com/sirupsen/logrus"
cmd "github.com/weaviate/weaviate/cluster/proto/api"
"github.com/weaviate/weaviate/entities/models"
"github.com/weaviate/weaviate/usecases/auth/authorization"
)

var ErrBadRequest = errors.New("bad request")

type Manager struct {
authZ authorization.Controller
logger logrus.FieldLogger
}

func NewManager(authZ authorization.Controller, logger logrus.FieldLogger) *Manager {
return &Manager{authZ: authZ, logger: logger}
}

func (m *Manager) GetRoles(names ...string) ([]*models.Role, error) {
return m.authZ.GetRoles(names...)
}

func (m *Manager) GetRolesForUser(user string) ([]*models.Role, error) {
return m.authZ.GetRolesForUser(user)
}

func (m *Manager) GetUsersForRole(role string) ([]string, error) {
return m.authZ.GetUsersForRole(role)
}

func (m *Manager) UpsertRoles(c *cmd.ApplyRequest) error {
req := &cmd.CreateRolesRequest{}
if err := json.Unmarshal(c.SubCommand, req); err != nil {
return fmt.Errorf("%w: %w", ErrBadRequest, err)
}

return m.authZ.UpsertRoles(req.Roles...)
}

func (m *Manager) DeleteRoles(c *cmd.ApplyRequest) error {
req := &cmd.DeleteRolesRequest{}
if err := json.Unmarshal(c.SubCommand, req); err != nil {
return fmt.Errorf("%w: %w", ErrBadRequest, err)
}

return m.authZ.DeleteRoles(req.Roles...)
}

func (m *Manager) AddRolesForUser(c *cmd.ApplyRequest) error {
req := &cmd.AddRolesForUsersRequest{}
if err := json.Unmarshal(c.SubCommand, req); err != nil {
return fmt.Errorf("%w: %w", ErrBadRequest, err)
}

return m.authZ.AddRolesForUser(req.User, req.Roles)
}

func (m *Manager) RemovePermissions(c *cmd.ApplyRequest) error {
req := &cmd.RemovePermissionsRequest{}
if err := json.Unmarshal(c.SubCommand, req); err != nil {
return fmt.Errorf("%w: %w", ErrBadRequest, err)
}

return m.authZ.RemovePermissions(req.Role, req.Permissions)
}

func (m *Manager) RevokeRolesForUser(c *cmd.ApplyRequest) error {
req := &cmd.RevokeRolesForUserRequest{}
if err := json.Unmarshal(c.SubCommand, req); err != nil {
return fmt.Errorf("%w: %w", ErrBadRequest, err)
}

return m.authZ.RevokeRolesForUser(req.User, req.Roles...)
}
9 changes: 9 additions & 0 deletions cluster/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 24,13 @@ import (

enterrors "github.com/weaviate/weaviate/entities/errors"
"github.com/weaviate/weaviate/exp/metadata"
"github.com/weaviate/weaviate/usecases/auth/authorization"

"github.com/hashicorp/raft"
raftbolt "github.com/hashicorp/raft-boltdb/v2"
"github.com/sirupsen/logrus"
"github.com/weaviate/weaviate/cluster/log"
"github.com/weaviate/weaviate/cluster/rbac"
"github.com/weaviate/weaviate/cluster/resolver"
"github.com/weaviate/weaviate/cluster/schema"
"github.com/weaviate/weaviate/cluster/types"
Expand Down Expand Up @@ -142,6 144,8 @@ type Config struct {
// being frozen happen, with the goal of being able to alert the metadata nodes. This
// channel will be nil if the metadata server is not enabled.
ClassTenantDataEvents chan metadata.ClassTenant

AuthzController authorization.Controller
}

// Store is the implementation of RAFT on this local node. It will handle the local schema and RAFT operations (startup,
Expand Down Expand Up @@ -184,6 188,10 @@ type Store struct {
// schemaManager is responsible for applying changes committed by RAFT to the schema representation & querying the
// schema
schemaManager *schema.SchemaManager

// authZManager is responsible for applying/querying changes committed by RAFT to the rbac representation
authZManager *rbac.Manager

// lastAppliedIndexToDB represents the index of the last applied command when the store is opened.
lastAppliedIndexToDB atomic.Uint64
// / lastAppliedIndex index of latest update to the store
Expand Down Expand Up @@ -223,6 231,7 @@ func NewFSM(cfg Config) Store {
applyTimeout: time.Second * 20,
raftResolver: raftResolver,
schemaManager: schemaManager,
authZManager: rbac.NewManager(cfg.AuthzController, cfg.Logger),
}
}

Expand Down
21 changes: 21 additions & 0 deletions cluster/store_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 192,27 @@ func (st *Store) Apply(l *raft.Log) interface{} {
ret.Error = st.StoreSchemaV1()
}

case api.ApplyRequest_TYPE_UPSERT_ROLES:
f = func() {
ret.Error = st.authZManager.UpsertRoles(&cmd)
}
case api.ApplyRequest_TYPE_DELETE_ROLES:
f = func() {
ret.Error = st.authZManager.DeleteRoles(&cmd)
}
case api.ApplyRequest_TYPE_REMOVE_PERMISSIONS:
f = func() {
ret.Error = st.authZManager.RemovePermissions(&cmd)
}
case api.ApplyRequest_TYPE_ADD_ROLES_FOR_USER:
f = func() {
ret.Error = st.authZManager.AddRolesForUser(&cmd)
}
case api.ApplyRequest_TYPE_REVOKE_ROLES_FOR_USER:
f = func() {
ret.Error = st.authZManager.RevokeRolesForUser(&cmd)
}

default:
// This could occur when a new command has been introduced in a later app version
// At this point, we need to panic so that the app undergo an upgrade during restart
Expand Down
Loading

0 comments on commit 05e793d

Please sign in to comment.