github.com/cilium/cilium@v1.16.2/clustermesh-apiserver/clustermesh/users_mgmt.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package clustermesh
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"sync"
    11  
    12  	"github.com/cilium/hive/cell"
    13  	"github.com/spf13/pflag"
    14  	"gopkg.in/yaml.v3"
    15  
    16  	cmtypes "github.com/cilium/cilium/pkg/clustermesh/types"
    17  	"github.com/cilium/cilium/pkg/controller"
    18  	"github.com/cilium/cilium/pkg/fswatcher"
    19  	"github.com/cilium/cilium/pkg/kvstore"
    20  	"github.com/cilium/cilium/pkg/logging/logfields"
    21  	"github.com/cilium/cilium/pkg/promise"
    22  )
    23  
    24  const (
    25  	usersMgmtCtrl = "clustermesh-users-management"
    26  )
    27  
    28  var usersManagementCell = cell.Module(
    29  	"clustermesh-users-management",
    30  	"ClusterMesh Etcd Users Management",
    31  
    32  	kvstore.GlobalUserMgmtClientPromiseCell,
    33  	cell.Config(UsersManagementConfig{}),
    34  	cell.Invoke(registerUsersManager),
    35  )
    36  
    37  var usersManagementControllerGroup = controller.NewGroup("clustermesh-users-management")
    38  
    39  type UsersManagementConfig struct {
    40  	ClusterUsersEnabled    bool
    41  	ClusterUsersConfigPath string
    42  }
    43  
    44  func (UsersManagementConfig) Flags(flags *pflag.FlagSet) {
    45  	flags.Bool("cluster-users-enabled", false,
    46  		"Enable the management of etcd users for remote clusters")
    47  	flags.String("cluster-users-config-path", "/var/lib/cilium/etcd-config/users.yaml",
    48  		"The path of the config file with the list of remote cluster users")
    49  }
    50  
    51  type usersConfigFile struct {
    52  	Users []struct {
    53  		Name string `yaml:"name"`
    54  		Role string `yaml:"role"`
    55  	} `yaml:"users"`
    56  }
    57  
    58  type usersManager struct {
    59  	UsersManagementConfig
    60  	clusterInfo cmtypes.ClusterInfo
    61  
    62  	client        kvstore.BackendOperationsUserMgmt
    63  	clientPromise promise.Promise[kvstore.BackendOperationsUserMgmt]
    64  
    65  	manager *controller.Manager
    66  	users   map[string]string
    67  
    68  	stop chan struct{}
    69  	wg   sync.WaitGroup
    70  }
    71  
    72  func registerUsersManager(
    73  	lc cell.Lifecycle,
    74  	cfg UsersManagementConfig,
    75  	cinfo cmtypes.ClusterInfo,
    76  	clientPromise promise.Promise[kvstore.BackendOperationsUserMgmt],
    77  ) error {
    78  	if !cfg.ClusterUsersEnabled {
    79  		log.Info("etcd users management disabled")
    80  		return nil
    81  	}
    82  
    83  	manager := usersManager{
    84  		UsersManagementConfig: cfg,
    85  		clientPromise:         clientPromise,
    86  
    87  		manager: controller.NewManager(),
    88  		users:   make(map[string]string),
    89  
    90  		stop: make(chan struct{}),
    91  	}
    92  
    93  	lc.Append(&manager)
    94  	return nil
    95  }
    96  
    97  func (us *usersManager) Start(cell.HookContext) error {
    98  	log.WithField(logfields.Path, us.ClusterUsersConfigPath).
    99  		Info("Starting managing etcd users based on configuration")
   100  
   101  	configWatcher, err := fswatcher.New([]string{us.ClusterUsersConfigPath})
   102  	if err != nil {
   103  		log.WithError(err).Error("Unable to setup config watcher")
   104  		return fmt.Errorf("unable to setup config watcher: %w", err)
   105  	}
   106  
   107  	us.manager.UpdateController(usersMgmtCtrl, controller.ControllerParams{
   108  		Group:   usersManagementControllerGroup,
   109  		Context: context.Background(),
   110  		DoFunc:  us.sync,
   111  	})
   112  
   113  	us.wg.Add(1)
   114  	go func() {
   115  		defer us.wg.Done()
   116  
   117  		for {
   118  			select {
   119  			case <-configWatcher.Events:
   120  				us.manager.TriggerController(usersMgmtCtrl)
   121  			case err := <-configWatcher.Errors:
   122  				log.WithError(err).WithField(logfields.Path, us.ClusterUsersConfigPath).
   123  					Warning("Error encountered while watching file with fsnotify")
   124  			case <-us.stop:
   125  				log.Info("Closing")
   126  				configWatcher.Close()
   127  				return
   128  			}
   129  		}
   130  	}()
   131  
   132  	return nil
   133  }
   134  
   135  func (us *usersManager) Stop(cell.HookContext) error {
   136  	log.WithField(logfields.Path, us.ClusterUsersConfigPath).
   137  		Info("Stopping managing etcd users based on configuration")
   138  
   139  	us.manager.RemoveAllAndWait()
   140  	close(us.stop)
   141  	us.wg.Wait()
   142  	return nil
   143  }
   144  
   145  func (us *usersManager) sync(ctx context.Context) error {
   146  	if us.client == nil {
   147  		client, err := us.clientPromise.Await(ctx)
   148  		if err != nil {
   149  			log.WithError(err).Error("Unable to retrieve the kvstore client")
   150  			return err
   151  		}
   152  		us.client = client
   153  	}
   154  
   155  	config, err := os.ReadFile(us.ClusterUsersConfigPath)
   156  	if err != nil {
   157  		log.WithError(err).WithField(logfields.Path, us.ClusterUsersConfigPath).
   158  			Error("Failed reading users configuration file")
   159  		return err
   160  	}
   161  
   162  	var users usersConfigFile
   163  	if err := yaml.Unmarshal(config, &users); err != nil {
   164  		log.WithError(err).WithField(logfields.Path, us.ClusterUsersConfigPath).
   165  			Error("Failed un-marshalling users configuration file")
   166  		return err
   167  	}
   168  
   169  	// Mark all users as stale
   170  	stale := make(map[string]struct{}, len(us.users))
   171  	for user := range us.users {
   172  		stale[user] = struct{}{}
   173  	}
   174  
   175  	for _, user := range users.Users {
   176  		if user.Name == us.clusterInfo.Name {
   177  			continue
   178  		}
   179  
   180  		role, found := us.users[user.Name]
   181  		if !found || role != user.Role {
   182  			if err := us.client.UserEnforcePresence(ctx, user.Name, []string{user.Role}); err != nil {
   183  				log.WithError(err).WithField(logfields.User, user.Name).Error("Failed configuring user")
   184  				return err
   185  			}
   186  
   187  			log.WithField(logfields.User, user.Name).Info("User successfully configured")
   188  		}
   189  
   190  		us.users[user.Name] = user.Role
   191  		delete(stale, user.Name)
   192  	}
   193  
   194  	// Delete all stale users
   195  	for user := range stale {
   196  		if err := us.client.UserEnforceAbsence(ctx, user); err != nil {
   197  			log.WithError(err).WithField(logfields.User, user).Error("Failed removing user")
   198  			return err
   199  		}
   200  
   201  		log.WithField(logfields.User, user).Info("User successfully removed")
   202  		delete(us.users, user)
   203  	}
   204  
   205  	return nil
   206  }