2019-10-18 17:38:46 -07:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"reflect"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
2020-03-17 11:05:20 -07:00
|
|
|
"github.com/go-logr/logr"
|
2019-10-18 17:38:46 -07:00
|
|
|
"github.com/minio/minio/pkg/wildcard"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
|
|
informers "k8s.io/client-go/informers/core/v1"
|
|
|
|
"k8s.io/client-go/kubernetes"
|
|
|
|
"k8s.io/client-go/tools/cache"
|
|
|
|
)
|
|
|
|
|
|
|
|
// read the conifgMap with name in env:INIT_CONFIG
|
|
|
|
// this configmap stores the resources that are to be filtered
|
|
|
|
const cmNameEnv string = "INIT_CONFIG"
|
|
|
|
|
2020-08-14 12:21:06 -07:00
|
|
|
var defaultExcludeGroupRole []string = []string{"system:serviceaccounts:kube-system", "system:nodes", "system:kube-scheduler"}
|
2020-08-14 03:00:25 +05:30
|
|
|
|
2020-01-24 12:05:53 -08:00
|
|
|
// ConfigData stores the configuration
|
2019-10-18 17:38:46 -07:00
|
|
|
type ConfigData struct {
|
2020-12-16 12:29:16 -08:00
|
|
|
client kubernetes.Interface
|
|
|
|
cmName string
|
|
|
|
mux sync.RWMutex
|
|
|
|
filters []k8Resource
|
|
|
|
excludeGroupRole []string
|
|
|
|
excludeUsername []string
|
2020-08-07 17:09:24 -07:00
|
|
|
restrictDevelopmentUsername []string
|
2020-12-16 12:29:16 -08:00
|
|
|
cmSycned cache.InformerSynced
|
2021-03-25 12:28:03 -07:00
|
|
|
reconcilePolicyReport chan<- bool
|
2020-12-16 12:29:16 -08:00
|
|
|
log logr.Logger
|
2019-10-18 17:38:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// ToFilter checks if the given resource is set to be filtered in the configuration
|
|
|
|
func (cd *ConfigData) ToFilter(kind, namespace, name string) bool {
|
|
|
|
cd.mux.RLock()
|
|
|
|
defer cd.mux.RUnlock()
|
|
|
|
for _, f := range cd.filters {
|
|
|
|
if wildcard.Match(f.Kind, kind) && wildcard.Match(f.Namespace, namespace) && wildcard.Match(f.Name, name) {
|
|
|
|
return true
|
|
|
|
}
|
2020-12-21 11:04:19 -08:00
|
|
|
|
|
|
|
if kind == "Namespace" {
|
|
|
|
// [Namespace,kube-system,*] || [*,kube-system,*]
|
|
|
|
if (f.Kind == "Namespace" || f.Kind == "*") && wildcard.Match(f.Namespace, name) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2019-10-18 17:38:46 -07:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-08-07 17:09:24 -07:00
|
|
|
// GetExcludeGroupRole return exclude roles
|
|
|
|
func (cd *ConfigData) GetExcludeGroupRole() []string {
|
|
|
|
cd.mux.RLock()
|
|
|
|
defer cd.mux.RUnlock()
|
|
|
|
return cd.excludeGroupRole
|
|
|
|
}
|
|
|
|
|
|
|
|
// RestrictDevelopmentUsername return exclude development username
|
|
|
|
func (cd *ConfigData) RestrictDevelopmentUsername() []string {
|
|
|
|
cd.mux.RLock()
|
|
|
|
defer cd.mux.RUnlock()
|
|
|
|
return cd.restrictDevelopmentUsername
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetExcludeUsername return exclude username
|
|
|
|
func (cd *ConfigData) GetExcludeUsername() []string {
|
|
|
|
cd.mux.RLock()
|
|
|
|
defer cd.mux.RUnlock()
|
|
|
|
return cd.excludeUsername
|
|
|
|
}
|
|
|
|
|
2021-01-21 18:58:53 -08:00
|
|
|
// FilterNamespaces filters exclude namespace
|
|
|
|
func (cd *ConfigData) FilterNamespaces(namespaces []string) []string {
|
|
|
|
var results []string
|
|
|
|
|
|
|
|
for _, ns := range namespaces {
|
|
|
|
if !cd.ToFilter("", ns, "") {
|
|
|
|
results = append(results, ns)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results
|
|
|
|
}
|
|
|
|
|
2019-10-18 17:38:46 -07:00
|
|
|
// Interface to be used by consumer to check filters
|
|
|
|
type Interface interface {
|
|
|
|
ToFilter(kind, namespace, name string) bool
|
2020-08-07 17:09:24 -07:00
|
|
|
GetExcludeGroupRole() []string
|
|
|
|
GetExcludeUsername() []string
|
|
|
|
RestrictDevelopmentUsername() []string
|
2021-01-21 18:58:53 -08:00
|
|
|
FilterNamespaces(namespaces []string) []string
|
2019-10-18 17:38:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewConfigData ...
|
2021-03-25 12:28:03 -07:00
|
|
|
func NewConfigData(rclient kubernetes.Interface, cmInformer informers.ConfigMapInformer, filterK8sResources, excludeGroupRole, excludeUsername string, reconcilePolicyReport chan<- bool, log logr.Logger) *ConfigData {
|
2019-10-18 17:38:46 -07:00
|
|
|
// environment var is read at start only
|
|
|
|
if cmNameEnv == "" {
|
2020-03-17 11:05:20 -07:00
|
|
|
log.Info("ConfigMap name not defined in env:INIT_CONFIG: loading no default configuration")
|
2019-10-18 17:38:46 -07:00
|
|
|
}
|
2020-12-16 12:29:16 -08:00
|
|
|
|
2019-10-18 17:38:46 -07:00
|
|
|
cd := ConfigData{
|
2021-03-25 12:28:03 -07:00
|
|
|
client: rclient,
|
|
|
|
cmName: os.Getenv(cmNameEnv),
|
|
|
|
cmSycned: cmInformer.Informer().HasSynced,
|
|
|
|
reconcilePolicyReport: reconcilePolicyReport,
|
|
|
|
log: log,
|
2019-10-18 17:38:46 -07:00
|
|
|
}
|
2020-12-16 12:29:16 -08:00
|
|
|
|
2020-08-07 17:09:24 -07:00
|
|
|
cd.restrictDevelopmentUsername = []string{"minikube-user", "kubernetes-admin"}
|
|
|
|
|
2019-10-29 10:56:28 -07:00
|
|
|
//TODO: this has been added to backward support command line arguments
|
|
|
|
// will be removed in future and the configuration will be set only via configmaps
|
2021-01-07 11:27:50 -08:00
|
|
|
if filterK8sResources != "" {
|
|
|
|
cd.log.Info("init configuration from commandline arguments for filterK8sResources")
|
|
|
|
cd.initFilters(filterK8sResources)
|
2019-10-29 10:56:28 -07:00
|
|
|
}
|
2019-10-18 17:38:46 -07:00
|
|
|
|
2020-08-07 17:09:24 -07:00
|
|
|
if excludeGroupRole != "" {
|
|
|
|
cd.log.Info("init configuration from commandline arguments for excludeGroupRole")
|
2020-08-14 12:21:06 -07:00
|
|
|
cd.initRbac("excludeRoles", excludeGroupRole)
|
2020-09-01 21:42:05 +05:30
|
|
|
} else {
|
2020-08-19 13:46:08 +05:30
|
|
|
cd.initRbac("excludeRoles", "")
|
2020-08-07 17:09:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if excludeUsername != "" {
|
|
|
|
cd.log.Info("init configuration from commandline arguments for excludeUsername")
|
2020-08-14 12:21:06 -07:00
|
|
|
cd.initRbac("excludeUsername", excludeUsername)
|
2020-08-07 17:09:24 -07:00
|
|
|
}
|
|
|
|
|
2019-10-18 17:38:46 -07:00
|
|
|
cmInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
|
|
AddFunc: cd.addCM,
|
|
|
|
UpdateFunc: cd.updateCM,
|
|
|
|
DeleteFunc: cd.deleteCM,
|
|
|
|
})
|
2020-12-16 12:29:16 -08:00
|
|
|
|
2019-10-25 18:49:26 -05:00
|
|
|
return &cd
|
|
|
|
}
|
|
|
|
|
2019-11-15 15:59:37 -08:00
|
|
|
//Run checks syncing
|
|
|
|
func (cd *ConfigData) Run(stopCh <-chan struct{}) {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger := cd.log
|
2019-10-18 17:38:46 -07:00
|
|
|
// wait for cache to populate first time
|
2019-11-15 15:59:37 -08:00
|
|
|
if !cache.WaitForCacheSync(stopCh, cd.cmSycned) {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.Info("configuration: failed to sync informer cache")
|
2019-10-18 17:38:46 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cd *ConfigData) addCM(obj interface{}) {
|
|
|
|
cm := obj.(*v1.ConfigMap)
|
|
|
|
if cm.Name != cd.cmName {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cd.load(*cm)
|
|
|
|
// else load the configuration
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cd *ConfigData) updateCM(old, cur interface{}) {
|
|
|
|
cm := cur.(*v1.ConfigMap)
|
|
|
|
if cm.Name != cd.cmName {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// if data has not changed then dont load configmap
|
2021-03-25 12:28:03 -07:00
|
|
|
changed := cd.load(*cm)
|
|
|
|
if changed {
|
|
|
|
cd.log.Info("resource filters changed, sending reconcile signal to the policy controller")
|
|
|
|
cd.reconcilePolicyReport <- true
|
|
|
|
}
|
2019-10-18 17:38:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (cd *ConfigData) deleteCM(obj interface{}) {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger := cd.log
|
2019-10-18 17:38:46 -07:00
|
|
|
cm, ok := obj.(*v1.ConfigMap)
|
|
|
|
if !ok {
|
|
|
|
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
|
|
|
if !ok {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.Info("failed to get object from tombstone")
|
2019-10-18 17:38:46 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
_, ok = tombstone.Obj.(*v1.ConfigMap)
|
|
|
|
if !ok {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.Info("Tombstone contained object that is not a ConfigMap", "object", obj)
|
2019-10-18 17:38:46 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if cm.Name != cd.cmName {
|
|
|
|
return
|
|
|
|
}
|
2020-01-24 12:05:53 -08:00
|
|
|
// remove the configuration parameters
|
2019-10-18 17:38:46 -07:00
|
|
|
cd.unload(*cm)
|
|
|
|
}
|
|
|
|
|
2021-03-25 12:28:03 -07:00
|
|
|
func (cd *ConfigData) load(cm v1.ConfigMap) (changed bool) {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger := cd.log.WithValues("name", cm.Name, "namespace", cm.Namespace)
|
2019-10-18 17:38:46 -07:00
|
|
|
if cm.Data == nil {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.V(4).Info("configuration: No data defined in ConfigMap")
|
2019-10-18 17:38:46 -07:00
|
|
|
return
|
|
|
|
}
|
2021-03-25 12:28:03 -07:00
|
|
|
|
2020-08-19 13:46:08 +05:30
|
|
|
cd.mux.Lock()
|
|
|
|
defer cd.mux.Unlock()
|
2021-03-25 12:28:03 -07:00
|
|
|
|
2019-10-18 17:38:46 -07:00
|
|
|
filters, ok := cm.Data["resourceFilters"]
|
2020-09-01 21:42:05 +05:30
|
|
|
if !ok {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.V(4).Info("configuration: No resourceFilters defined in ConfigMap")
|
2020-09-01 21:42:05 +05:30
|
|
|
} else {
|
2020-08-19 13:46:08 +05:30
|
|
|
newFilters := parseKinds(filters)
|
|
|
|
if reflect.DeepEqual(newFilters, cd.filters) {
|
|
|
|
logger.V(4).Info("resourceFilters did not change")
|
|
|
|
} else {
|
|
|
|
logger.V(2).Info("Updated resource filters", "oldFilters", cd.filters, "newFilters", newFilters)
|
|
|
|
cd.filters = newFilters
|
2021-03-25 12:28:03 -07:00
|
|
|
changed = true
|
2020-08-19 13:46:08 +05:30
|
|
|
}
|
2019-10-18 17:38:46 -07:00
|
|
|
}
|
2020-08-07 17:09:24 -07:00
|
|
|
|
|
|
|
excludeGroupRole, ok := cm.Data["excludeGroupRole"]
|
2020-09-01 21:42:05 +05:30
|
|
|
if !ok {
|
2020-08-07 17:09:24 -07:00
|
|
|
logger.V(4).Info("configuration: No excludeGroupRole defined in ConfigMap")
|
2019-10-18 17:38:46 -07:00
|
|
|
}
|
2020-08-14 03:00:25 +05:30
|
|
|
newExcludeGroupRoles := parseRbac(excludeGroupRole)
|
2020-08-14 12:21:06 -07:00
|
|
|
newExcludeGroupRoles = append(newExcludeGroupRoles, defaultExcludeGroupRole...)
|
2020-08-14 03:00:25 +05:30
|
|
|
if reflect.DeepEqual(newExcludeGroupRoles, cd.excludeGroupRole) {
|
2020-08-07 17:09:24 -07:00
|
|
|
logger.V(4).Info("excludeGroupRole did not change")
|
2020-08-14 12:21:06 -07:00
|
|
|
} else {
|
2020-08-14 03:00:25 +05:30
|
|
|
logger.V(2).Info("Updated resource excludeGroupRoles", "oldExcludeGroupRole", cd.excludeGroupRole, "newExcludeGroupRole", newExcludeGroupRoles)
|
2020-08-14 12:21:06 -07:00
|
|
|
cd.excludeGroupRole = newExcludeGroupRoles
|
2021-03-25 12:28:03 -07:00
|
|
|
changed = true
|
2020-08-07 17:09:24 -07:00
|
|
|
}
|
|
|
|
|
2020-08-19 13:46:08 +05:30
|
|
|
excludeUsername, ok := cm.Data["excludeUsername"]
|
2020-09-01 21:42:05 +05:30
|
|
|
if !ok {
|
2020-08-19 13:46:08 +05:30
|
|
|
logger.V(4).Info("configuration: No excludeUsername defined in ConfigMap")
|
2020-09-01 21:42:05 +05:30
|
|
|
} else {
|
2020-08-19 13:46:08 +05:30
|
|
|
excludeUsernames := parseRbac(excludeUsername)
|
|
|
|
if reflect.DeepEqual(excludeUsernames, cd.excludeUsername) {
|
|
|
|
logger.V(4).Info("excludeGroupRole did not change")
|
|
|
|
} else {
|
|
|
|
logger.V(2).Info("Updated resource excludeUsernames", "oldExcludeUsername", cd.excludeUsername, "newExcludeUsername", excludeUsernames)
|
|
|
|
cd.excludeUsername = excludeUsernames
|
2021-03-25 12:28:03 -07:00
|
|
|
changed = true
|
2020-08-19 13:46:08 +05:30
|
|
|
}
|
2020-08-07 17:09:24 -07:00
|
|
|
}
|
|
|
|
|
2021-03-25 12:28:03 -07:00
|
|
|
return changed
|
2019-10-18 17:38:46 -07:00
|
|
|
}
|
|
|
|
|
2019-10-29 10:56:28 -07:00
|
|
|
//TODO: this has been added to backward support command line arguments
|
|
|
|
// will be removed in future and the configuration will be set only via configmaps
|
|
|
|
func (cd *ConfigData) initFilters(filters string) {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger := cd.log
|
2019-10-29 10:56:28 -07:00
|
|
|
// parse and load the configuration
|
|
|
|
cd.mux.Lock()
|
|
|
|
defer cd.mux.Unlock()
|
|
|
|
|
|
|
|
newFilters := parseKinds(filters)
|
2020-07-02 12:49:10 -07:00
|
|
|
logger.V(2).Info("Init resource filters", "filters", newFilters)
|
2019-10-29 10:56:28 -07:00
|
|
|
// update filters
|
|
|
|
cd.filters = newFilters
|
|
|
|
}
|
|
|
|
|
2020-08-14 12:21:06 -07:00
|
|
|
func (cd *ConfigData) initRbac(action, exclude string) {
|
2020-08-07 17:09:24 -07:00
|
|
|
logger := cd.log
|
|
|
|
// parse and load the configuration
|
|
|
|
cd.mux.Lock()
|
|
|
|
defer cd.mux.Unlock()
|
|
|
|
rbac := parseRbac(exclude)
|
|
|
|
logger.V(2).Info("Init resource ", action, exclude)
|
|
|
|
// update filters
|
|
|
|
if action == "excludeRoles" {
|
|
|
|
cd.excludeGroupRole = rbac
|
2020-08-14 12:21:06 -07:00
|
|
|
cd.excludeGroupRole = append(cd.excludeGroupRole, defaultExcludeGroupRole...)
|
|
|
|
} else {
|
2020-08-07 17:09:24 -07:00
|
|
|
cd.excludeUsername = rbac
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-10-18 17:38:46 -07:00
|
|
|
func (cd *ConfigData) unload(cm v1.ConfigMap) {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger := cd.log
|
|
|
|
logger.Info("ConfigMap deleted, removing configuration filters", "name", cm.Name, "namespace", cm.Namespace)
|
2019-10-18 17:38:46 -07:00
|
|
|
cd.mux.Lock()
|
|
|
|
defer cd.mux.Unlock()
|
|
|
|
cd.filters = []k8Resource{}
|
2020-08-07 17:09:24 -07:00
|
|
|
cd.excludeGroupRole = []string{}
|
2020-08-14 12:21:06 -07:00
|
|
|
cd.excludeGroupRole = append(cd.excludeGroupRole, defaultExcludeGroupRole...)
|
2020-08-07 17:09:24 -07:00
|
|
|
cd.excludeUsername = []string{}
|
2019-10-18 17:38:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
type k8Resource struct {
|
|
|
|
Kind string //TODO: as we currently only support one GVK version, we use the kind only. But if we support multiple GVK, then GV need to be added
|
|
|
|
Namespace string
|
|
|
|
Name string
|
|
|
|
}
|
|
|
|
|
2020-01-24 12:05:53 -08:00
|
|
|
//ParseKinds parses the kinds if a single string contains comma separated kinds
|
2019-10-18 17:38:46 -07:00
|
|
|
// {"1,2,3","4","5"} => {"1","2","3","4","5"}
|
|
|
|
func parseKinds(list string) []k8Resource {
|
|
|
|
resources := []k8Resource{}
|
|
|
|
var resource k8Resource
|
|
|
|
re := regexp.MustCompile(`\[([^\[\]]*)\]`)
|
|
|
|
submatchall := re.FindAllString(list, -1)
|
|
|
|
for _, element := range submatchall {
|
|
|
|
element = strings.Trim(element, "[")
|
|
|
|
element = strings.Trim(element, "]")
|
|
|
|
elements := strings.Split(element, ",")
|
|
|
|
//TODO: wildcards for namespace and name
|
|
|
|
if len(elements) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if len(elements) == 3 {
|
|
|
|
resource = k8Resource{Kind: elements[0], Namespace: elements[1], Name: elements[2]}
|
|
|
|
}
|
|
|
|
if len(elements) == 2 {
|
|
|
|
resource = k8Resource{Kind: elements[0], Namespace: elements[1]}
|
|
|
|
}
|
|
|
|
if len(elements) == 1 {
|
|
|
|
resource = k8Resource{Kind: elements[0]}
|
|
|
|
}
|
|
|
|
resources = append(resources, resource)
|
|
|
|
}
|
|
|
|
return resources
|
|
|
|
}
|
2020-08-07 17:09:24 -07:00
|
|
|
|
|
|
|
func parseRbac(list string) []string {
|
2020-08-14 03:00:25 +05:30
|
|
|
return strings.Split(list, ",")
|
2020-08-14 12:21:06 -07:00
|
|
|
}
|