diff --git a/main.go b/main.go index 0cccf4b4..da7df617 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,6 @@ import ( "syscall" "time" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/controller" authzpolicy "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/authorizationpolicy" adInformer "github.com/yahoo/k8s-athenz-syncer/pkg/client/informers/externalversions/athenz/v1" v1 "k8s.io/api/core/v1" @@ -37,8 +36,6 @@ import ( func main() { dnsSuffix := flag.String("dns-suffix", "svc.cluster.local", "dns suffix used for service role target services") kubeconfig := flag.String("kubeconfig", "", "(optional) absolute path to the kubeconfig file") - adResyncIntervalRaw := flag.String("ad-resync-interval", "1h", "athenz domain resync interval") - crcResyncIntervalRaw := flag.String("crc-resync-interval", "1h", "cluster rbac config resync interval") apResyncIntervalRaw := flag.String("ap-resync-interval", "1h", "authorization policy resync interval") enableOriginJwtSubject := flag.Bool("enable-origin-jwt-subject", true, "enable adding origin jwt subject to service role binding") logFile := flag.String("log-file", "/var/log/k8s-athenz-istio-auth/k8s-athenz-istio-auth.log", "log file location") @@ -47,7 +44,6 @@ func main() { authzPolicyEnabledList := flag.String("ap-enabled-list", "", "List of namespace/service that enabled authz policy, "+ "use format 'example-ns1/example-service1' to enable a single service, use format 'example-ns2/*' to enable all services in a namespace, and use '*' to enable all services in the cluster' ") combinationPolicyTag := flag.String("combo-policy-tag", "proxy-principals", "key of tag for proxy principals list") - authPolicyControllerOnlyMode := flag.Bool("auth-policy-only-mode", false, "only run authzpolicy controller") enableSpiffeTrustDomain := flag.Bool("enable-spiffe-trust-domain", true, "Allow new SPIFFE ID's") adminDomain := flag.String("admin-domain", "", "admin domain") systemNamespaces := flag.String("system-namespaces", "istio-system,kube-system", "list of cluster system namespaces") @@ -89,11 +85,8 @@ func main() { } } var configDescriptor collection.Schemas - if *authPolicyControllerOnlyMode { - configDescriptor = collection.SchemasFor(collections.IstioSecurityV1Beta1Authorizationpolicies) - } else { - configDescriptor = collection.SchemasFor(collections.IstioRbacV1Alpha1Serviceroles, collections.IstioRbacV1Alpha1Clusterrbacconfigs, collections.IstioRbacV1Alpha1Servicerolebindings, collections.IstioSecurityV1Beta1Authorizationpolicies) - } + configDescriptor = collection.SchemasFor(collections.IstioSecurityV1Beta1Authorizationpolicies) + // If kubeconfig arg is not passed-in, try user $HOME config only if it exists if *kubeconfig == "" { home := filepath.Join(homedir.HomeDir(), ".kube", "config") @@ -126,16 +119,6 @@ func main() { istioClientSet, err := versionedclient.NewForConfig(config) - adResyncInterval, err := time.ParseDuration(*adResyncIntervalRaw) - if err != nil { - log.Panicf("Error parsing ad-resync-interval duration: %s", err.Error()) - } - - crcResyncInterval, err := time.ParseDuration(*crcResyncIntervalRaw) - if err != nil { - log.Panicf("Error parsing crc-resync-interval duration: %s", err.Error()) - } - apResyncInterval, err := time.ParseDuration(*apResyncIntervalRaw) if err != nil { log.Panicf("Error parsing ap-resync-interval duration: %s", err.Error()) @@ -167,19 +150,13 @@ func main() { for _, domain := range strings.Split(*adminDomain, ",") { adminDomains = append(adminDomains, strings.TrimSpace(domain)) } - if *authPolicyControllerOnlyMode { - configStoreCache := crdController.NewController(istioClient, istioController.Options{}) - serviceListWatch := cache.NewListWatchFromClient(k8sClient.CoreV1().RESTClient(), "services", v1.NamespaceAll, fields.Everything()) - serviceIndexInformer := cache.NewSharedIndexInformer(serviceListWatch, &v1.Service{}, 0, nil) - adIndexInformer := adInformer.NewAthenzDomainInformer(adClient, 0, cache.Indexers{}) - - apController := authzpolicy.NewController(configStoreCache, serviceIndexInformer, adIndexInformer, istioClientSet, apResyncInterval, *enableOriginJwtSubject, componentsEnabledAuthzPolicy, *combinationPolicyTag, *authPolicyControllerOnlyMode, *enableSpiffeTrustDomain, namespaces, serviceAccountNamespaceMap, adminDomains) - configStoreCache.RegisterEventHandler(collections.IstioSecurityV1Beta1Authorizationpolicies.Resource().GroupVersionKind(), apController.EventHandler) - go apController.Run(stopCh) - } else { - c := controller.NewController(*dnsSuffix, istioClient, k8sClient, adClient, istioClientSet, adResyncInterval, crcResyncInterval, apResyncInterval, *enableOriginJwtSubject, *enableAuthzPolicyController, componentsEnabledAuthzPolicy, *combinationPolicyTag, *enableSpiffeTrustDomain, namespaces, serviceAccountNamespaceMap, adminDomains) - go c.Run(stopCh) - } + configStoreCache := crdController.NewController(istioClient, istioController.Options{}) + serviceListWatch := cache.NewListWatchFromClient(k8sClient.CoreV1().RESTClient(), "services", v1.NamespaceAll, fields.Everything()) + serviceIndexInformer := cache.NewSharedIndexInformer(serviceListWatch, &v1.Service{}, 0, nil) + adIndexInformer := adInformer.NewAthenzDomainInformer(adClient, 0, cache.Indexers{}) + + apController := authzpolicy.NewController(configStoreCache, serviceIndexInformer, adIndexInformer, istioClientSet, apResyncInterval, *enableOriginJwtSubject, componentsEnabledAuthzPolicy, *combinationPolicyTag, *enableSpiffeTrustDomain, namespaces, serviceAccountNamespaceMap, adminDomains) + go apController.Run(stopCh) signalCh := make(chan os.Signal, 1) signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM) diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go deleted file mode 100644 index 627b4441..00000000 --- a/pkg/controller/controller.go +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2019, Verizon Media Inc. -// Licensed under the terms of the 3-Clause BSD license. See LICENSE file in -// github.com/yahoo/k8s-athenz-istio-auth for terms. -package controller - -import ( - "errors" - "fmt" - "time" - - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/common" - "istio.io/client-go/pkg/clientset/versioned" - "istio.io/istio/pkg/config/schema/collections" - - crd "istio.io/istio/pilot/pkg/config/kube/crd/controller" - "istio.io/istio/pilot/pkg/model" - "istio.io/istio/pilot/pkg/serviceregistry/kube/controller" - v1 "k8s.io/api/core/v1" - apiErrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/util/workqueue" - - "github.com/yahoo/k8s-athenz-istio-auth/pkg/athenz" - m "github.com/yahoo/k8s-athenz-istio-auth/pkg/athenz" - authzpolicy "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/authorizationpolicy" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/onboarding" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/processor" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac" - rbacv1 "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/v1" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/log" - adv1 "github.com/yahoo/k8s-athenz-syncer/pkg/apis/athenz/v1" - adClientset "github.com/yahoo/k8s-athenz-syncer/pkg/client/clientset/versioned" - adInformer "github.com/yahoo/k8s-athenz-syncer/pkg/client/informers/externalversions/athenz/v1" -) - -const queueNumRetries = 3 - -type Controller struct { - configStoreCache model.ConfigStoreCache - crcController *onboarding.Controller - processor *processor.Controller - serviceIndexInformer cache.SharedIndexInformer - adIndexInformer cache.SharedIndexInformer - rbacProvider rbac.Provider - apController *authzpolicy.Controller - queue workqueue.RateLimitingInterface - adResyncInterval time.Duration - enableAuthzPolicyController bool -} - -// getCallbackHandler returns a error handler func that re-adds the athenz domain back to queue -// this explicit func definition takes in the key to avoid data race while accessing key -func (c *Controller) getCallbackHandler(key string) common.OnCompleteFunc { - return func(err error, item *common.Item) error { - - if err == nil { - return nil - } - if item != nil { - log.Errorf("Error performing %s on %s: %s", item.Operation, item.Resource.Key(), err.Error()) - } - if apiErrors.IsNotFound(err) || apiErrors.IsAlreadyExists(err) { - log.Infof("Error is non-retryable %s", err) - return nil - } - if !apiErrors.IsConflict(err) { - log.Infof("Retrying operation %s on %s due to processing error for %s", item.Operation, item.Resource.Key(), key) - return err - } - if c.queue.NumRequeues(key) >= queueNumRetries { - log.Errorf("Max number of retries reached for %s.", key) - return nil - } - if item != nil { - log.Infof("Retrying operation %s on %s due to processing error for %s", item.Operation, item.Resource.Key(), key) - } - c.queue.AddRateLimited(key) - return nil - } -} - -// sync will be ran for each key in the queue and will be responsible for the following: -// 1. Get the Athenz Domain from the cache for the queue key -// 2. Convert to Athenz Model to group domain members and policies by role -// 3. Convert Athenz Model to Service Role and Service Role Binding objects -// 4. Create / Update / Delete Service Role and Service Role Binding objects -func (c *Controller) sync(key string) error { - athenzDomainRaw, exists, err := c.adIndexInformer.GetIndexer().GetByKey(key) - if err != nil { - return err - } - - if !exists { - // TODO, add the non existing athenz domain to the istio custom resource - // processing controller to delete them - return fmt.Errorf("athenz domain %s does not exist in cache", key) - } - - athenzDomain, ok := athenzDomainRaw.(*adv1.AthenzDomain) - if !ok { - return errors.New("athenz domain cast failed") - } - - signedDomain := athenzDomain.Spec.SignedDomain - domainRBAC := m.ConvertAthenzPoliciesIntoRbacModel(signedDomain.Domain, &c.adIndexInformer) - desiredCRs := c.rbacProvider.ConvertAthenzModelIntoIstioRbac(domainRBAC, "", "", "") - currentCRs := c.rbacProvider.GetCurrentIstioRbac(domainRBAC, c.configStoreCache, "") - cbHandler := c.getCallbackHandler(key) - - changeList := common.ComputeChangeList(currentCRs, desiredCRs, cbHandler, nil) - - // If change list is empty, nothing to do - if len(changeList) == 0 { - log.Infof("Everything is up-to-date for key: %s", key) - c.queue.Forget(key) - return nil - } - - for _, item := range changeList { - log.Infof("Adding resource action to processor queue: %s on %s for key: %s", item.Operation, item.Resource.Key(), key) - c.processor.ProcessConfigChange(item) - } - - return nil -} - -// NewController is responsible for creating the main controller object and -// initializing all of its dependencies: -// 1. Rate limiting queue -// 2. Istio custom resource config store cache for service role, service role -// bindings, and cluster rbac config -// 3. Onboarding controller responsible for creating / updating / deleting the -// cluster rbac config object based on a service label -// 4. Service shared index informer -// 5. Athenz Domain shared index informer -// 6. Authorization Policy controller responsible for creating / updating / deleting -// the authorization policy object based on service annotation and athenz domain spec -func NewController(dnsSuffix string, istioClient *crd.Client, k8sClient kubernetes.Interface, adClient adClientset.Interface, - istioClientSet versioned.Interface, adResyncInterval, crcResyncInterval, apResyncInterval time.Duration, enableOriginJwtSubject bool, - enableAuthzPolicyController bool, componentsEnabledAuthzPolicy *common.ComponentEnabled, combinationPolicyTag string, enableSpiffeTrustDomain bool, - systemNamespaces []string, customServicetMap map[string]string, adminDomains []string) *Controller { - queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) - configStoreCache := crd.NewController(istioClient, controller.Options{}) - - serviceListWatch := cache.NewListWatchFromClient(k8sClient.CoreV1().RESTClient(), "services", v1.NamespaceAll, fields.Everything()) - serviceIndexInformer := cache.NewSharedIndexInformer(serviceListWatch, &v1.Service{}, 0, nil) - processor := processor.NewController(configStoreCache) - crcController := onboarding.NewController(configStoreCache, dnsSuffix, serviceIndexInformer, crcResyncInterval, processor) - adIndexInformer := adInformer.NewAthenzDomainInformer(adClient, 0, cache.Indexers{}) - - // If enableAuthzPolicyController is enabled start the authzpolicy controller - var apController *authzpolicy.Controller - if enableAuthzPolicyController { - apController = authzpolicy.NewController(configStoreCache, serviceIndexInformer, adIndexInformer, istioClientSet, apResyncInterval, enableOriginJwtSubject, componentsEnabledAuthzPolicy, combinationPolicyTag, false, enableSpiffeTrustDomain, systemNamespaces, customServicetMap, adminDomains) - configStoreCache.RegisterEventHandler(collections.IstioSecurityV1Beta1Authorizationpolicies.Resource().GroupVersionKind(), apController.EventHandler) - } - - c := &Controller{ - serviceIndexInformer: serviceIndexInformer, - adIndexInformer: adIndexInformer, - configStoreCache: configStoreCache, - crcController: crcController, - processor: processor, - apController: apController, - rbacProvider: rbacv1.NewProvider(enableOriginJwtSubject), - queue: queue, - adResyncInterval: adResyncInterval, - enableAuthzPolicyController: enableAuthzPolicyController, - } - - configStoreCache.RegisterEventHandler(collections.IstioRbacV1Alpha1Serviceroles.Resource().GroupVersionKind(), c.processConfigEvent) - configStoreCache.RegisterEventHandler(collections.IstioRbacV1Alpha1Servicerolebindings.Resource().GroupVersionKind(), c.processConfigEvent) - configStoreCache.RegisterEventHandler(collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().GroupVersionKind(), crcController.EventHandler) - - adIndexInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - c.processEvent(cache.MetaNamespaceKeyFunc, obj) - }, - UpdateFunc: func(_, obj interface{}) { - c.processEvent(cache.MetaNamespaceKeyFunc, obj) - }, - DeleteFunc: func(obj interface{}) { - c.processEvent(cache.DeletionHandlingMetaNamespaceKeyFunc, obj) - }, - }) - - return c -} - -// processEvent is responsible for calling the key function and adding the -// key of the item to the queue -func (c *Controller) processEvent(fn cache.KeyFunc, obj interface{}) { - key, err := fn(obj) - if err == nil { - c.queue.Add(key) - return - } - log.Errorf("Error calling key func: %s", err.Error()) -} - -// processConfigEvent is responsible for adding the key of the item to the queue -func (c *Controller) processConfigEvent(_ model.Config, config model.Config, e model.Event) { - domain := athenz.NamespaceToDomain(config.Namespace) - c.queue.Add(domain) -} - -// Run starts the main controller loop running sync at every poll interval. It -// also starts the following controller dependencies: -// 1. Service informer -// 2. Istio custom resource informer -// 3. Athenz Domain informer -func (c *Controller) Run(stopCh <-chan struct{}) { - go c.serviceIndexInformer.Run(stopCh) - go c.configStoreCache.Run(stopCh) - go c.adIndexInformer.Run(stopCh) - - if !cache.WaitForCacheSync(stopCh, c.configStoreCache.HasSynced, c.serviceIndexInformer.HasSynced, c.adIndexInformer.HasSynced) { - log.Panicln("Timed out waiting for namespace cache to sync.") - } - - // crc controller must wait for service informer to sync before starting - go c.processor.Run(stopCh) - go c.crcController.Run(stopCh) - if c.enableAuthzPolicyController { - go c.apController.Run(stopCh) - } - go c.resync(stopCh) - - defer c.queue.ShutDown() - wait.Until(c.runWorker, 0, stopCh) -} - -// runWorker calls processNextItem to process events of the work queue -func (c *Controller) runWorker() { - for c.processNextItem() { - } -} - -// processNextItem takes an item off the queue and calls the controllers sync -// function, handles the logic of requeuing in case any errors occur -func (c *Controller) processNextItem() bool { - keyRaw, quit := c.queue.Get() - if quit { - return false - } - - defer c.queue.Done(keyRaw) - key, ok := keyRaw.(string) - if !ok { - log.Errorf("String cast failed for key %v", key) - c.queue.Forget(keyRaw) - return true - } - - log.Infof("Processing key: %s", key) - err := c.sync(key) - if err != nil { - log.Errorf("Error syncing athenz state for key %s: %s", keyRaw, err) - if c.queue.NumRequeues(keyRaw) < queueNumRetries { - log.Infof("Retrying key %s due to sync error", keyRaw) - c.queue.AddRateLimited(keyRaw) - return true - } - } - - return true -} - -// resync will run as a periodic resync at a given interval, it will take all -// the current athenz domains in the cache and put them onto the queue -func (c *Controller) resync(stopCh <-chan struct{}) { - t := time.NewTicker(c.adResyncInterval) - defer t.Stop() - for { - select { - case <-t.C: - log.Infoln("Running resync for athenz domains...") - adListRaw := c.adIndexInformer.GetIndexer().List() - for _, adRaw := range adListRaw { - c.processEvent(cache.MetaNamespaceKeyFunc, adRaw) - } - case <-stopCh: - log.Infoln("Stopping athenz domain resync...") - return - } - } -} diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go deleted file mode 100644 index 5d9b47a2..00000000 --- a/pkg/controller/controller_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package controller - -import ( - "testing" - "time" - - "istio.io/istio/pilot/pkg/model" - - "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/util/workqueue" - - "github.com/yahoo/k8s-athenz-istio-auth/pkg/log" - adv1 "github.com/yahoo/k8s-athenz-syncer/pkg/apis/athenz/v1" - "github.com/yahoo/k8s-athenz-syncer/pkg/client/clientset/versioned/fake" - adInformer "github.com/yahoo/k8s-athenz-syncer/pkg/client/informers/externalversions/athenz/v1" - - "github.com/stretchr/testify/assert" -) - -var ad = &adv1.AthenzDomain{ - ObjectMeta: v1.ObjectMeta{ - Name: "test.namespace", - Namespace: "test-namespace", - }, -} - -func init() { - log.InitLogger("", "debug") -} - -func TestProcessEvent(t *testing.T) { - c := &Controller{ - queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), - } - - c.processEvent(cache.MetaNamespaceKeyFunc, ad.DeepCopy()) - - assert.Equal(t, 1, c.queue.Len(), "queue length should be 1") - item, shutdown := c.queue.Get() - assert.False(t, shutdown, "shutdown should be false") - assert.Equal(t, 0, c.queue.Len(), "queue length should be 0") - assert.Equal(t, "test-namespace/test.namespace", item, "key should be equal") -} - -func TestProcessConfigEvent(t *testing.T) { - c := &Controller{ - queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), - } - - config := model.Config{ - ConfigMeta: model.ConfigMeta{ - Name: "test", - Namespace: "test-namespace", - }, - } - - c.processConfigEvent(config, config, model.EventAdd) - - assert.Equal(t, 1, c.queue.Len(), "queue length should be 1") - item, shutdown := c.queue.Get() - assert.False(t, shutdown, "shutdown should be false") - assert.Equal(t, 0, c.queue.Len(), "queue length should be 0") - assert.Equal(t, "test.namespace", item, "key should be equal") -} - -func TestResync(t *testing.T) { - fakeClientset := fake.NewSimpleClientset() - adIndexInformer := adInformer.NewAthenzDomainInformer(fakeClientset, 0, cache.Indexers{}) - adIndexInformer.GetStore().Add(ad.DeepCopy()) - - c := &Controller{ - queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), - adIndexInformer: adIndexInformer, - adResyncInterval: time.Second * 1, - } - - stopCh := make(chan struct{}) - go c.resync(stopCh) - time.Sleep(time.Second * 2) - close(stopCh) - - assert.Equal(t, 1, c.queue.Len(), "queue length should be 1") - item, shutdown := c.queue.Get() - assert.False(t, shutdown, "shutdown should be false") - assert.Equal(t, 0, c.queue.Len(), "queue length should be 0") - assert.Equal(t, "test-namespace/test.namespace", item, "key should be equal") -} diff --git a/pkg/istio/authorizationpolicy/controller.go b/pkg/istio/authorizationpolicy/controller.go index 993fce70..f898b523 100644 --- a/pkg/istio/authorizationpolicy/controller.go +++ b/pkg/istio/authorizationpolicy/controller.go @@ -44,10 +44,9 @@ type Controller struct { dryRunHandler common.DryRunHandler apiHandler common.ApiHandler combinationPolicyTag string - standAloneMode bool } -func NewController(configStoreCache model.ConfigStoreCache, serviceIndexInformer cache.SharedIndexInformer, adIndexInformer cache.SharedIndexInformer, istioClientSet versioned.Interface, apResyncInterval time.Duration, enableOriginJwtSubject bool, componentEnabledAuthzPolicy *common.ComponentEnabled, combinationPolicyTag string, standAloneMode bool, enableSpiffeTrustDomain bool, systemNamespaces []string, customServiceMap map[string]string, adminDomains []string) *Controller { +func NewController(configStoreCache model.ConfigStoreCache, serviceIndexInformer cache.SharedIndexInformer, adIndexInformer cache.SharedIndexInformer, istioClientSet versioned.Interface, apResyncInterval time.Duration, enableOriginJwtSubject bool, componentEnabledAuthzPolicy *common.ComponentEnabled, combinationPolicyTag string, enableSpiffeTrustDomain bool, systemNamespaces []string, customServiceMap map[string]string, adminDomains []string) *Controller { queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) c := &Controller{ @@ -60,9 +59,8 @@ func NewController(configStoreCache model.ConfigStoreCache, serviceIndexInformer enableOriginJwtSubject: enableOriginJwtSubject, componentEnabledAuthzPolicy: componentEnabledAuthzPolicy, dryRunHandler: common.DryRunHandler{}, - standAloneMode: standAloneMode, } - + configStoreCache.RegisterEventHandler(collections.IstioSecurityV1Beta1Authorizationpolicies.Resource().GroupVersionKind(), c.EventHandler) c.apiHandler = common.ApiHandler{ ConfigStoreCache: c.configStoreCache, } @@ -113,11 +111,9 @@ func (c *Controller) processEvent(fn cache.KeyFunc, obj interface{}) { // Run starts the main controller loop running sync at every poll interval. func (c *Controller) Run(stopCh <-chan struct{}) { - if c.standAloneMode { - go c.serviceIndexInformer.Run(stopCh) - go c.configStoreCache.Run(stopCh) - go c.adIndexInformer.Run(stopCh) - } + go c.serviceIndexInformer.Run(stopCh) + go c.configStoreCache.Run(stopCh) + go c.adIndexInformer.Run(stopCh) if !cache.WaitForCacheSync(stopCh, c.configStoreCache.HasSynced, c.serviceIndexInformer.HasSynced, c.adIndexInformer.HasSynced) { log.Panicln("Timed out waiting for namespace cache to sync.") diff --git a/pkg/istio/authorizationpolicy/controller_test.go b/pkg/istio/authorizationpolicy/controller_test.go index c0ff7995..52d39e0f 100644 --- a/pkg/istio/authorizationpolicy/controller_test.go +++ b/pkg/istio/authorizationpolicy/controller_test.go @@ -157,7 +157,7 @@ func (cs *fakeConfigStore) Delete(typ resource.GroupVersionKind, name, namespace return cs.ConfigStore.Delete(typ, name, namespace) } -func newFakeController(athenzDomain *adv1.AthenzDomain, service *v1.Service, fake bool, apEnabledList string, stopCh <-chan struct{}, standaloneMode bool) *Controller { +func newFakeController(athenzDomain *adv1.AthenzDomain, service *v1.Service, fake bool, apEnabledList string, stopCh <-chan struct{}) *Controller { c := &Controller{} configDescriptor := collection.SchemasFor(collections.IstioSecurityV1Beta1Authorizationpolicies) @@ -207,7 +207,6 @@ func newFakeController(athenzDomain *adv1.AthenzDomain, service *v1.Service, fak c.apiHandler = common.ApiHandler{ ConfigStoreCache: c.configStoreCache, } - c.standAloneMode = standaloneMode return c } @@ -219,7 +218,6 @@ func TestSyncService(t *testing.T) { existingAuthzPolicy *model.Config expectedAuthzPolicy *model.Config item Item - standaloneMode bool }{ { name: "generate Authorization Policy spec for service with annotation set", @@ -228,7 +226,6 @@ func TestSyncService(t *testing.T) { existingAuthzPolicy: nil, expectedAuthzPolicy: getExpectedAuthzPolicy(), item: Item{Operation: model.EventAdd, Resource: onboardedService}, - standaloneMode: false, }, { name: "not generate Authorization Policy spec for service without annotation set", @@ -237,7 +234,6 @@ func TestSyncService(t *testing.T) { existingAuthzPolicy: nil, expectedAuthzPolicy: nil, item: Item{Operation: model.EventAdd, Resource: notOnboardedService}, - standaloneMode: false, }, { name: "delete Authorization Policy spec when there is deletion event of service with annotation set", @@ -246,7 +242,6 @@ func TestSyncService(t *testing.T) { existingAuthzPolicy: getExpectedAuthzPolicy(), expectedAuthzPolicy: nil, item: Item{Operation: model.EventDelete, Resource: onboardedService}, - standaloneMode: false, }, { name: "create Authorization Policy spec when there is update event of service from no annotation set to annotation set", @@ -255,7 +250,6 @@ func TestSyncService(t *testing.T) { existingAuthzPolicy: nil, expectedAuthzPolicy: getExpectedAuthzPolicy(), item: Item{Operation: model.EventUpdate, Resource: onboardedService}, - standaloneMode: false, }, { name: "create empty Authorization Policy spec when there is create event of service which doesn't have roles / policies defined", @@ -264,7 +258,6 @@ func TestSyncService(t *testing.T) { existingAuthzPolicy: nil, expectedAuthzPolicy: getExpectedEmptyAuthzPolicy(), item: Item{Operation: model.EventAdd, Resource: undefinedAthenzRulesServiceWithAnnotationTrue}, - standaloneMode: false, }, { name: "generate Authorization Policy spec for service with annotation set", @@ -273,7 +266,6 @@ func TestSyncService(t *testing.T) { existingAuthzPolicy: nil, expectedAuthzPolicy: getExpectedAuthzPolicy(), item: Item{Operation: model.EventAdd, Resource: onboardedService}, - standaloneMode: true, }, { name: "not generate Authorization Policy spec for service without annotation set", @@ -282,7 +274,6 @@ func TestSyncService(t *testing.T) { existingAuthzPolicy: nil, expectedAuthzPolicy: nil, item: Item{Operation: model.EventAdd, Resource: notOnboardedService}, - standaloneMode: true, }, { name: "delete Authorization Policy spec when there is deletion event of service with annotation set", @@ -291,7 +282,6 @@ func TestSyncService(t *testing.T) { existingAuthzPolicy: getExpectedAuthzPolicy(), expectedAuthzPolicy: nil, item: Item{Operation: model.EventDelete, Resource: onboardedService}, - standaloneMode: true, }, { name: "create Authorization Policy spec when there is update event of service from no annotation set to annotation set", @@ -300,7 +290,6 @@ func TestSyncService(t *testing.T) { existingAuthzPolicy: nil, expectedAuthzPolicy: getExpectedAuthzPolicy(), item: Item{Operation: model.EventUpdate, Resource: onboardedService}, - standaloneMode: true, }, { name: "create empty Authorization Policy spec when there is create event of service which doesn't have roles / policies defined", @@ -309,13 +298,12 @@ func TestSyncService(t *testing.T) { existingAuthzPolicy: nil, expectedAuthzPolicy: getExpectedEmptyAuthzPolicy(), item: Item{Operation: model.EventAdd, Resource: undefinedAthenzRulesServiceWithAnnotationTrue}, - standaloneMode: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newFakeController(tt.inputAthenzDomain, tt.inputService, true, "*", make(chan struct{}), tt.standaloneMode) + c := newFakeController(tt.inputAthenzDomain, tt.inputService, true, "*", make(chan struct{})) switch action := tt.item.Operation; action { case model.EventDelete: c.configStoreCache.Create(*tt.existingAuthzPolicy) @@ -360,45 +348,38 @@ func TestSyncAthenzDomain(t *testing.T) { expectedAuthzPolicy *model.Config item Item expErr error - standaloneMode bool }{ { name: "update existing authz policy spec when there is athenz domain crd update", expectedAuthzPolicy: getExpectedAuthzPolicy(), item: Item{Operation: model.EventUpdate, Resource: onboardedAthenzDomain}, - standaloneMode: false, }, { name: "no action on authz policy when athenz domain crd is deleted", expectedAuthzPolicy: getExpectedAuthzPolicy(), item: Item{Operation: model.EventDelete, Resource: onboardedAthenzDomain}, expErr: fmt.Errorf("athenz domain test.namespace.onboarded does not exist in cache"), - standaloneMode: false, }, { name: "when athenz domain crd is created, authz policy should be created", expectedAuthzPolicy: getExpectedAuthzPolicy(), item: Item{Operation: model.EventAdd, Resource: onboardedAthenzDomain}, - standaloneMode: false, }, { name: "update existing authz policy spec when there is athenz domain crd update", expectedAuthzPolicy: getExpectedAuthzPolicy(), item: Item{Operation: model.EventUpdate, Resource: onboardedAthenzDomain}, - standaloneMode: true, }, { name: "no action on authz policy when athenz domain crd is deleted", expectedAuthzPolicy: getExpectedAuthzPolicy(), item: Item{Operation: model.EventDelete, Resource: onboardedAthenzDomain}, expErr: fmt.Errorf("athenz domain test.namespace.onboarded does not exist in cache"), - standaloneMode: true, }, { name: "when athenz domain crd is created, authz policy should be created", expectedAuthzPolicy: getExpectedAuthzPolicy(), item: Item{Operation: model.EventAdd, Resource: onboardedAthenzDomain}, - standaloneMode: true, }, } @@ -408,7 +389,7 @@ func TestSyncAthenzDomain(t *testing.T) { switch action := tt.item.Operation; action { case model.EventUpdate: - c := newFakeController(onboardedAthenzDomain, onboardedService, true, "*", stopCh, tt.standaloneMode) + c := newFakeController(onboardedAthenzDomain, onboardedService, true, "*", stopCh) _, err := c.configStoreCache.Create(*getExistingAuthzPolicy()) assert.Nil(t, err, "configstore create resource should not return error") time.Sleep(100 * time.Millisecond) @@ -423,7 +404,7 @@ func TestSyncAthenzDomain(t *testing.T) { tt.expectedAuthzPolicy.ConfigMeta.ResourceVersion = genAuthzPolicy.ConfigMeta.ResourceVersion assert.Equal(t, *tt.expectedAuthzPolicy, *genAuthzPolicy, "updated authorization policy spec should be equal") case model.EventDelete: - c := newFakeController(onboardedAthenzDomain, onboardedService, true, "*", stopCh, tt.standaloneMode) + c := newFakeController(onboardedAthenzDomain, onboardedService, true, "*", stopCh) _, err := c.configStoreCache.Create(*getExpectedAuthzPolicy()) assert.Nil(t, err, "configstore create resource should not return error") time.Sleep(100 * time.Millisecond) @@ -440,7 +421,7 @@ func TestSyncAthenzDomain(t *testing.T) { tt.expectedAuthzPolicy.ConfigMeta.ResourceVersion = genAuthzPolicy.ConfigMeta.ResourceVersion assert.Equal(t, *tt.expectedAuthzPolicy, *genAuthzPolicy, "created authorization policy spec should be equal") case model.EventAdd: - c := newFakeController(&adv1.AthenzDomain{}, onboardedService, true, "*", stopCh, tt.standaloneMode) + c := newFakeController(&adv1.AthenzDomain{}, onboardedService, true, "*", stopCh) err := c.adIndexInformer.GetStore().Add(onboardedAthenzDomain) assert.Nil(t, err, "add athenz domain crd to cache should not return error") key, err := cache.MetaNamespaceKeyFunc(tt.item.Resource) @@ -467,7 +448,6 @@ func TestSyncAuthzPolicy(t *testing.T) { initAuthzPolicySpec *model.Config item Item expectedAuthzPolicy *model.Config - standaloneMode bool }{ { name: "when there is manual modification of authz policy resource, controller will revert back to spec matched with athenz domain crd", @@ -476,7 +456,6 @@ func TestSyncAuthzPolicy(t *testing.T) { initAuthzPolicySpec: getExistingAuthzPolicy(), item: Item{Operation: model.EventUpdate, Resource: getModifiedAuthzPolicy()}, expectedAuthzPolicy: getExpectedAuthzPolicy(), - standaloneMode: false, }, { name: "when there is deletion of authz policy resource, controller will recreate the authz policy", @@ -485,7 +464,6 @@ func TestSyncAuthzPolicy(t *testing.T) { initAuthzPolicySpec: getExistingAuthzPolicy(), item: Item{Operation: model.EventDelete, Resource: getModifiedAuthzPolicy()}, expectedAuthzPolicy: getExpectedAuthzPolicy(), - standaloneMode: false, }, { name: "when there is manual modification of authz policy resource with override annotation, controller should do nothing", @@ -494,7 +472,6 @@ func TestSyncAuthzPolicy(t *testing.T) { initAuthzPolicySpec: getModifiedAuthzPolicyCRWithOverrideAnnotation(), item: Item{Operation: model.EventUpdate, Resource: getModifiedAuthzPolicyWithOverrideAnnotation()}, expectedAuthzPolicy: getModifiedAuthzPolicyCRWithOverrideAnnotation(), - standaloneMode: false, }, { name: "when there is manual creation of authz policy without override annotation, controller should delete this create resource", @@ -503,7 +480,6 @@ func TestSyncAuthzPolicy(t *testing.T) { initAuthzPolicySpec: nil, item: Item{Operation: model.EventAdd, Resource: getModifiedAuthzPolicy()}, expectedAuthzPolicy: nil, - standaloneMode: false, }, { name: "when there is manual modification of authz policy resource, controller will revert back to spec matched with athenz domain crd", @@ -512,7 +488,6 @@ func TestSyncAuthzPolicy(t *testing.T) { initAuthzPolicySpec: getExistingAuthzPolicy(), item: Item{Operation: model.EventUpdate, Resource: getModifiedAuthzPolicy()}, expectedAuthzPolicy: getExpectedAuthzPolicy(), - standaloneMode: true, }, { name: "when there is deletion of authz policy resource, controller will recreate the authz policy", @@ -521,7 +496,6 @@ func TestSyncAuthzPolicy(t *testing.T) { initAuthzPolicySpec: getExistingAuthzPolicy(), item: Item{Operation: model.EventDelete, Resource: getModifiedAuthzPolicy()}, expectedAuthzPolicy: getExpectedAuthzPolicy(), - standaloneMode: true, }, { name: "when there is manual modification of authz policy resource with override annotation, controller should do nothing", @@ -530,7 +504,6 @@ func TestSyncAuthzPolicy(t *testing.T) { initAuthzPolicySpec: getModifiedAuthzPolicyCRWithOverrideAnnotation(), item: Item{Operation: model.EventUpdate, Resource: getModifiedAuthzPolicyWithOverrideAnnotation()}, expectedAuthzPolicy: getModifiedAuthzPolicyCRWithOverrideAnnotation(), - standaloneMode: true, }, { name: "when there is manual creation of authz policy without override annotation, controller should delete this create resource", @@ -539,7 +512,6 @@ func TestSyncAuthzPolicy(t *testing.T) { initAuthzPolicySpec: nil, item: Item{Operation: model.EventAdd, Resource: getModifiedAuthzPolicy()}, expectedAuthzPolicy: nil, - standaloneMode: true, }, } @@ -547,7 +519,7 @@ func TestSyncAuthzPolicy(t *testing.T) { t.Run(tt.name, func(t *testing.T) { switch action := tt.item.Operation; action { case model.EventUpdate: - c := newFakeController(tt.inputAthenzDomain, tt.inputService, true, "*", make(chan struct{}), tt.standaloneMode) + c := newFakeController(tt.inputAthenzDomain, tt.inputService, true, "*", make(chan struct{})) _, err := c.configStoreCache.Create(*tt.initAuthzPolicySpec) assert.Nil(t, err, "configstore create resource should not return error") time.Sleep(100 * time.Millisecond) @@ -562,7 +534,7 @@ func TestSyncAuthzPolicy(t *testing.T) { tt.expectedAuthzPolicy.ConfigMeta.ResourceVersion = genAuthzPolicy.ConfigMeta.ResourceVersion assert.Equal(t, *tt.expectedAuthzPolicy, *genAuthzPolicy, "created authorization policy spec should be equal") case model.EventDelete: - c := newFakeController(tt.inputAthenzDomain, tt.inputService, true, "*", make(chan struct{}), tt.standaloneMode) + c := newFakeController(tt.inputAthenzDomain, tt.inputService, true, "*", make(chan struct{})) key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(tt.item.Resource) assert.Nil(t, err, "function convert item interface to key should not return error") err = c.sync(key) @@ -574,7 +546,7 @@ func TestSyncAuthzPolicy(t *testing.T) { tt.expectedAuthzPolicy.ConfigMeta.ResourceVersion = genAuthzPolicy.ConfigMeta.ResourceVersion assert.Equal(t, *tt.expectedAuthzPolicy, *genAuthzPolicy, "created authorization policy spec should be equal") case model.EventAdd: - c := newFakeController(tt.inputAthenzDomain, tt.inputService, true, "*", make(chan struct{}), tt.standaloneMode) + c := newFakeController(tt.inputAthenzDomain, tt.inputService, true, "*", make(chan struct{})) key, err := cache.MetaNamespaceKeyFunc(tt.item.Resource) assert.Nil(t, err, "function convert item interface to key should not return error") err = c.sync(key) @@ -602,15 +574,13 @@ func TestNewController(t *testing.T) { apiHandler := common.ApiHandler{ ConfigStoreCache: configStoreCache, } - standAloneMode := true - c := NewController(configStoreCache, fakeIndexInformer, fakeAthenzInformer, istioClientSet, apResyncInterval, true, &common.ComponentEnabled{}, "proxy-principals", standAloneMode, true, []string{}, map[string]string{}, []string{""}) + c := NewController(configStoreCache, fakeIndexInformer, fakeAthenzInformer, istioClientSet, apResyncInterval, true, &common.ComponentEnabled{}, "proxy-principals", true, []string{}, map[string]string{}, []string{""}) assert.Equal(t, fakeIndexInformer, c.serviceIndexInformer, "service index informer pointer should be equal") assert.Equal(t, configStoreCache, c.configStoreCache, "config configStoreCache cache pointer should be equal") assert.Equal(t, fakeAthenzInformer, c.adIndexInformer, "athenz index informer cache should be equal") assert.Equal(t, true, c.enableOriginJwtSubject, "enableOriginJwtSubject bool should be equal") assert.Equal(t, common.DryRunHandler{}, c.dryRunHandler, "dryRun handler should be equal") assert.Equal(t, apiHandler, c.apiHandler, "api handler should be equal") - assert.Equal(t, standAloneMode, c.standAloneMode, "stand alone mode should be equal") } func TestCleanUpStaleAP(t *testing.T) { @@ -621,7 +591,6 @@ func TestCleanUpStaleAP(t *testing.T) { existingAuthzPolicy *model.Config expectedAuthzPolicy *model.Config apEnabledList string - standaloneMode bool }{ { name: "delete all authorization policies as apEnabledList has a different namespace than onboarded namespace", @@ -630,7 +599,6 @@ func TestCleanUpStaleAP(t *testing.T) { existingAuthzPolicy: getExpectedAuthzPolicy(), expectedAuthzPolicy: nil, apEnabledList: "foobar/*", - standaloneMode: false, }, { name: "existing authorization policy is not deleted as all namespaces are part of apEnabledList", @@ -639,7 +607,6 @@ func TestCleanUpStaleAP(t *testing.T) { existingAuthzPolicy: getExpectedAuthzPolicy(), expectedAuthzPolicy: getExpectedAuthzPolicy(), apEnabledList: "*", - standaloneMode: false, }, { name: "existing authorization policy is not deleted as the same namespace as the existing service is part of apEnabledList", @@ -648,7 +615,6 @@ func TestCleanUpStaleAP(t *testing.T) { existingAuthzPolicy: getExpectedAuthzPolicy(), expectedAuthzPolicy: getExpectedAuthzPolicy(), apEnabledList: "test-namespace-onboarded/*", - standaloneMode: false, }, { name: "existing authorization policy is not deleted as the override annotation is enabled even when not in the same namespace", @@ -657,7 +623,6 @@ func TestCleanUpStaleAP(t *testing.T) { existingAuthzPolicy: getModifiedAuthzPolicyCRWithOverrideAnnotation(), expectedAuthzPolicy: getModifiedAuthzPolicyCRWithOverrideAnnotation(), apEnabledList: "foobar/*", - standaloneMode: true, }, { name: "delete all authorization policies as apEnabledList has a different namespace than onboarded namespace", @@ -666,7 +631,6 @@ func TestCleanUpStaleAP(t *testing.T) { existingAuthzPolicy: getExpectedAuthzPolicy(), expectedAuthzPolicy: nil, apEnabledList: "foobar/*", - standaloneMode: true, }, { name: "existing authorization policy is not deleted as all namespaces are part of apEnabledList", @@ -675,7 +639,6 @@ func TestCleanUpStaleAP(t *testing.T) { existingAuthzPolicy: getExpectedAuthzPolicy(), expectedAuthzPolicy: getExpectedAuthzPolicy(), apEnabledList: "*", - standaloneMode: true, }, { name: "existing authorization policy is not deleted as the same namespace as the existing service is part of apEnabledList", @@ -684,7 +647,6 @@ func TestCleanUpStaleAP(t *testing.T) { existingAuthzPolicy: getExpectedAuthzPolicy(), expectedAuthzPolicy: getExpectedAuthzPolicy(), apEnabledList: "test-namespace-onboarded/*", - standaloneMode: true, }, { name: "existing authorization policy is not deleted as the override annotation is enabled even when not in the same namespace", @@ -693,13 +655,12 @@ func TestCleanUpStaleAP(t *testing.T) { existingAuthzPolicy: getModifiedAuthzPolicyCRWithOverrideAnnotation(), expectedAuthzPolicy: getModifiedAuthzPolicyCRWithOverrideAnnotation(), apEnabledList: "foobar/*", - standaloneMode: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newFakeController(tt.inputAthenzDomain, tt.inputService, true, tt.apEnabledList, make(chan struct{}), tt.standaloneMode) + c := newFakeController(tt.inputAthenzDomain, tt.inputService, true, tt.apEnabledList, make(chan struct{})) c.configStoreCache.Create(*tt.existingAuthzPolicy) time.Sleep(100 * time.Millisecond) diff --git a/pkg/istio/onboarding/controller.go b/pkg/istio/onboarding/controller.go deleted file mode 100644 index 53a9fa8a..00000000 --- a/pkg/istio/onboarding/controller.go +++ /dev/null @@ -1,355 +0,0 @@ -// Copyright 2019, Verizon Media Inc. -// Licensed under the terms of the 3-Clause BSD license. See LICENSE file in -// github.com/yahoo/k8s-athenz-istio-auth for terms. -package onboarding - -import ( - "errors" - "time" - - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/common" - "istio.io/istio/pkg/config/schema/collections" - - v1 "k8s.io/api/core/v1" - apiErrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/util/workqueue" - - "istio.io/api/rbac/v1alpha1" - "istio.io/istio/pilot/pkg/model" - "istio.io/istio/pkg/config/constants" - - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/processor" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/log" -) - -const ( - queueNumRetries = 3 - authzEnabled = "true" - authzEnabledAnnotation = "authz.istio.io/enabled" - queueKey = v1.NamespaceDefault + "/" + constants.DefaultRbacConfigName -) - -type Controller struct { - configStoreCache model.ConfigStoreCache - dnsSuffix string - serviceIndexInformer cache.SharedIndexInformer - processor *processor.Controller - queue workqueue.RateLimitingInterface - crcResyncInterval time.Duration -} - -// NewController initializes the Controller object and its dependencies -func NewController(configStoreCache model.ConfigStoreCache, dnsSuffix string, serviceIndexInformer cache.SharedIndexInformer, crcResyncInterval time.Duration, processor *processor.Controller) *Controller { - queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) - - c := &Controller{ - configStoreCache: configStoreCache, - dnsSuffix: dnsSuffix, - serviceIndexInformer: serviceIndexInformer, - processor: processor, - queue: queue, - crcResyncInterval: crcResyncInterval, - } - - serviceIndexInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(_ interface{}) { - c.queue.Add(queueKey) - }, - UpdateFunc: func(_ interface{}, _ interface{}) { - c.queue.Add(queueKey) - }, - DeleteFunc: func(_ interface{}) { - c.queue.Add(queueKey) - }, - }) - - return c -} - -// Run starts the worker thread -func (c *Controller) Run(stopCh <-chan struct{}) { - go c.resync(stopCh) - - defer c.queue.ShutDown() - wait.Until(c.runWorker, 0, stopCh) -} - -// runWorker calls processNextItem to process events of the work queue -func (c *Controller) runWorker() { - for c.processNextItem() { - } -} - -// processNextItem takes an item off the queue and calls the controllers sync -// function, handles the logic of requeuing in case any errors occur -func (c *Controller) processNextItem() bool { - key, quit := c.queue.Get() - if quit { - return false - } - - defer c.queue.Done(key) - - err := c.sync() - if err != nil { - log.Errorf("Error syncing cluster rbac config for key %s: %s", key, err.Error()) - if c.queue.NumRequeues(key) < queueNumRetries { - log.Infof("Retrying key %s due to sync error", key) - c.queue.AddRateLimited(key) - return true - } - } - - return true -} - -// addService will add a service to the ClusterRbacConfig object -func addServices(services []string, clusterRbacConfig *v1alpha1.RbacConfig) { - if clusterRbacConfig == nil || clusterRbacConfig.Inclusion == nil { - return - } - - for _, service := range services { - clusterRbacConfig.Inclusion.Services = append(clusterRbacConfig.Inclusion.Services, service) - } -} - -// deleteService will delete a service from the ClusterRbacConfig object -func deleteServices(services []string, clusterRbacConfig *v1alpha1.RbacConfig) { - if clusterRbacConfig == nil || clusterRbacConfig.Inclusion == nil { - return - } - - for _, service := range services { - var indexToRemove = -1 - for i, svc := range clusterRbacConfig.Inclusion.Services { - if svc == service { - indexToRemove = i - break - } - } - - if indexToRemove != -1 { - clusterRbacConfig.Inclusion.Services = removeIndexElement(clusterRbacConfig.Inclusion.Services, indexToRemove) - } - } -} - -// newClusterRbacSpec creates the rbac config object with the inclusion field -func newClusterRbacSpec(services []string) *v1alpha1.RbacConfig { - return &v1alpha1.RbacConfig{ - Mode: v1alpha1.RbacConfig_ON_WITH_INCLUSION, - Inclusion: &v1alpha1.RbacConfig_Target{ - Services: services, - }, - Exclusion: nil, - } -} - -// newClusterRbacConfig creates the ClusterRbacConfig model config object -func newClusterRbacConfig(services []string) model.Config { - return model.Config{ - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Kind(), - Name: constants.DefaultRbacConfigName, - Group: collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Version(), - }, - Spec: newClusterRbacSpec(services), - } -} - -// getOnboardedServiceList extracts all services from the indexer with the authz -// annotation set to true. -func (c *Controller) getOnboardedServiceList() []string { - cacheServiceList := c.serviceIndexInformer.GetIndexer().List() - serviceList := make([]string, 0) - - for _, service := range cacheServiceList { - svc, ok := service.(*v1.Service) - if !ok { - log.Errorln("Could not cast to service object, skipping service list addition...") - continue - } - - key, exists := svc.Annotations[authzEnabledAnnotation] - if exists && key == authzEnabled { - serviceName := svc.Name + "." + svc.Namespace + "." + c.dnsSuffix - serviceList = append(serviceList, serviceName) - } - } - - return serviceList -} - -// callbackHandler re-adds the key for a failed processor.sync operation -func (c *Controller) callbackHandler(err error, item *common.Item) error { - if err == nil { - return nil - } - if item != nil { - log.Errorf("Error performing %s on %s: %s", item.Operation, item.Resource.Key(), err) - } - if apiErrors.IsNotFound(err) || apiErrors.IsAlreadyExists(err) { - log.Infof("Error is non-retryable %s", err) - return nil - } - if !apiErrors.IsConflict(err) { - log.Infof("Retrying operation %s on %s due to processing error for %s", item.Operation, item.Resource.Key(), queueKey) - return err - } - if c.queue.NumRequeues(queueKey) >= queueNumRetries { - log.Errorf("Max number of retries reached for %s.", queueKey) - return nil - } - if item != nil { - log.Infof("Retrying operation %s on %s due to processing error for %s", item.Operation, item.Resource.Key(), queueKey) - } - c.queue.AddRateLimited(queueKey) - return nil -} - -// sync decides whether to create / update / delete the ClusterRbacConfig -// object based on the current onboarded services in the cluster -func (c *Controller) sync() error { - serviceList := c.getOnboardedServiceList() - config := c.configStoreCache.Get(collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().GroupVersionKind(), constants.DefaultRbacConfigName, "") - if config == nil && len(serviceList) == 0 { - log.Infoln("Service list is empty and cluster rbac config does not exist, skipping sync...") - c.queue.Forget(queueKey) - return nil - } - - if config == nil { - log.Infoln("Creating cluster rbac config...") - item := common.Item{ - Operation: model.EventAdd, - Resource: newClusterRbacConfig(serviceList), - CallbackHandler: c.callbackHandler, - } - c.processor.ProcessConfigChange(&item) - return nil - } - - if len(serviceList) == 0 { - log.Infoln("Deleting cluster rbac config...") - item := common.Item{ - Operation: model.EventDelete, - Resource: newClusterRbacConfig(serviceList), - CallbackHandler: c.callbackHandler, - } - c.processor.ProcessConfigChange(&item) - return nil - } - - clusterRbacConfig, ok := config.Spec.(*v1alpha1.RbacConfig) - if !ok { - return errors.New("Could not cast to cluster rbac config") - } - - if clusterRbacConfig.Inclusion == nil || clusterRbacConfig.Mode != v1alpha1.RbacConfig_ON_WITH_INCLUSION { - log.Infoln("Cluster rbac config inclusion field is nil or ON_WITH_INCLUSION mode is not set, syncing...") - config := model.Config{ - ConfigMeta: config.ConfigMeta, - Spec: newClusterRbacSpec(serviceList), - } - item := common.Item{ - Operation: model.EventUpdate, - Resource: config, - CallbackHandler: c.callbackHandler, - } - c.processor.ProcessConfigChange(&item) - return nil - } - - newServices := compareServiceLists(serviceList, clusterRbacConfig.Inclusion.Services) - if len(newServices) > 0 { - addServices(newServices, clusterRbacConfig) - } - - oldServices := compareServiceLists(clusterRbacConfig.Inclusion.Services, serviceList) - if len(oldServices) > 0 { - deleteServices(oldServices, clusterRbacConfig) - } - - if len(newServices) > 0 || len(oldServices) > 0 { - log.Infoln("Updating cluster rbac config...") - config := model.Config{ - ConfigMeta: config.ConfigMeta, - Spec: clusterRbacConfig, - } - item := common.Item{ - Operation: model.EventUpdate, - Resource: config, - CallbackHandler: c.callbackHandler, - } - // TODO: investigate: when dns-suffix is changed and results in full service list update, this update is likely to fail and controller will be stuck in fail and retry cycles. - // How to reproduce: 1. do not mention dns-suffix as part of controller, use default setting - // 2. start the controller, at the beginning, controller will perform a full service list update - // 3. update will fail with message like - // ``` - // INFO[2021-02-04T20:00:45Z] [istio/processor/controller.go] [processNextItem] Processing update for resource: ClusterRbacConfig//default - // INFO[2021-02-04T20:00:45Z] [istio/onboarding/controller.go] [sync] Sync state is current, no changes needed... - // INFO[2021-02-04T20:00:45Z] [istio/onboarding/controller.go] [sync] Updating cluster rbac config... - // ``` - // It will stuck in this loop and won't proceed. - c.processor.ProcessConfigChange(&item) - return nil - } - - log.Infoln("Sync state is current, no changes needed...") - c.queue.Forget(queueKey) - return nil -} - -func (c *Controller) EventHandler(config model.Config, _ model.Config, e model.Event) { - c.queue.Add(queueKey) -} - -// resync will run as a periodic resync at a given interval, it will put the -// cluster rbac config key onto the queue -func (c *Controller) resync(stopCh <-chan struct{}) { - t := time.NewTicker(c.crcResyncInterval) - defer t.Stop() - for { - select { - case <-t.C: - log.Infoln("Running resync for cluster rbac config...") - c.queue.Add(queueKey) - case <-stopCh: - log.Infoln("Stopping cluster rbac config resync...") - return - } - } -} - -// compareServices returns a list of which items in list A are not in list B -func compareServiceLists(serviceListA, serviceListB []string) []string { - serviceMapB := make(map[string]bool, len(serviceListB)) - for _, item := range serviceListB { - serviceMapB[item] = true - } - - serviceListDiff := make([]string, 0) - for _, item := range serviceListA { - if _, exists := serviceMapB[item]; !exists { - serviceListDiff = append(serviceListDiff, item) - } - } - - return serviceListDiff -} - -// removeIndexElement removes an element from an array at the given index -func removeIndexElement(serviceList []string, indexToRemove int) []string { - if indexToRemove > len(serviceList) || indexToRemove < 0 { - return serviceList - } - - serviceList[len(serviceList)-1], - serviceList[indexToRemove] = serviceList[indexToRemove], - serviceList[len(serviceList)-1] - return serviceList[:len(serviceList)-1] -} diff --git a/pkg/istio/onboarding/controller_test.go b/pkg/istio/onboarding/controller_test.go deleted file mode 100644 index 53dd20c3..00000000 --- a/pkg/istio/onboarding/controller_test.go +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright 2019, Verizon Media Inc. -// Licensed under the terms of the 3-Clause BSD license. See LICENSE file in -// github.com/yahoo/k8s-athenz-istio-auth for terms. -package onboarding - -import ( - "fmt" - "istio.io/istio/pkg/config/schema/collection" - "istio.io/istio/pkg/config/schema/collections" - "istio.io/istio/pkg/config/schema/resource" - "sync" - "testing" - "time" - - "istio.io/api/rbac/v1alpha1" - "istio.io/istio/pilot/pkg/config/memory" - "istio.io/istio/pilot/pkg/model" - "istio.io/istio/pkg/config/constants" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/cache" - fcache "k8s.io/client-go/tools/cache/testing" - "k8s.io/client-go/util/workqueue" - - "github.com/stretchr/testify/assert" - - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/processor" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/log" -) - -var ( - onboardedService = &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "onboarded-service", - Namespace: "test-namespace", - Annotations: map[string]string{ - authzEnabledAnnotation: "true", - }, - }, - } - notOnboardedService = &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "not-onboarded-service", - Namespace: "test-namespace", - }, - } - - onboardedServiceName = "onboarded-service.test-namespace.svc.cluster.local" - existingServiceName = "existing-service.test-namespace.svc.cluster.local" - notOnboardedServiceName = "not-onboarded-service.test-namespace.svc.cluster.local" - dnsSuffix = "svc.cluster.local" -) - -func init() { - log.InitLogger("", "debug") -} - -// fakeConfigStore a wrapper around a passed-in config store that does mutex lock on all store operations -type fakeConfigStore struct { - model.ConfigStore - m sync.Mutex -} - -func (cs *fakeConfigStore) ConfigDescriptor() collection.Schemas { - return collection.SchemasFor(collections.IstioRbacV1Alpha1Clusterrbacconfigs) -} - -func (cs *fakeConfigStore) Get(typ resource.GroupVersionKind, name, namespace string) *model.Config { - cs.m.Lock() - defer cs.m.Unlock() - return cs.ConfigStore.Get(typ, name, namespace) -} - -func (cs *fakeConfigStore) List(typ resource.GroupVersionKind, namespace string) ([]model.Config, error) { - cs.m.Lock() - defer cs.m.Unlock() - return cs.ConfigStore.List(typ, namespace) -} - -func (cs *fakeConfigStore) Create(cfg model.Config) (string, error) { - cs.m.Lock() - defer cs.m.Unlock() - return cs.ConfigStore.Create(cfg) -} - -func (cs *fakeConfigStore) Update(cfg model.Config) (string, error) { - cs.m.Lock() - defer cs.m.Unlock() - return cs.ConfigStore.Update(cfg) -} - -func (cs *fakeConfigStore) Delete(typ resource.GroupVersionKind, name, namespace string) error { - cs.m.Lock() - defer cs.m.Unlock() - return cs.ConfigStore.Delete(typ, name, namespace) -} - -func getClusterRbacConfig(c *Controller) (*v1alpha1.RbacConfig, error) { - config := c.configStoreCache.Get(collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().GroupVersionKind(), constants.DefaultRbacConfigName, "") - if config == nil { - return nil, fmt.Errorf("config store returned nil for ClusterRbacConfig resource") - } - - clusterRbacConfig, ok := config.Spec.(*v1alpha1.RbacConfig) - if !ok { - log.Panicln("cannot cast to rbac config") - } - return clusterRbacConfig, nil -} - -func newFakeController(services []*v1.Service, fake bool, stopCh <-chan struct{}) *Controller { - c := &Controller{} - - configDescriptor := collection.SchemasFor(collections.IstioRbacV1Alpha1Clusterrbacconfigs) - - configStore := memory.Make(configDescriptor) - - if fake { - configStore = &fakeConfigStore{ - configStore, - sync.Mutex{}, - } - } - c.configStoreCache = memory.NewController(configStore) - c.processor = processor.NewController(c.configStoreCache) - go c.processor.Run(stopCh) - - source := fcache.NewFakeControllerSource() - for _, service := range services { - source.Add(service) - } - fakeIndexInformer := cache.NewSharedIndexInformer(source, &v1.Service{}, 0, nil) - go fakeIndexInformer.Run(stopCh) - - if !cache.WaitForCacheSync(stopCh, fakeIndexInformer.HasSynced) { - log.Panicln("timed out waiting for cache to sync") - } - c.serviceIndexInformer = fakeIndexInformer - c.dnsSuffix = dnsSuffix - - return c -} - -func TestNewController(t *testing.T) { - configDescriptor := collection.SchemasFor(collections.IstioRbacV1Alpha1Clusterrbacconfigs) - - source := fcache.NewFakeControllerSource() - fakeIndexInformer := cache.NewSharedIndexInformer(source, &v1.Service{}, 0, nil) - configStore := memory.Make(configDescriptor) - configStoreCache := memory.NewController(configStore) - processor := processor.NewController(configStoreCache) - stopCh := make(chan struct{}) - go processor.Run(stopCh) - - c := NewController(configStoreCache, dnsSuffix, fakeIndexInformer, time.Second, processor) - assert.Equal(t, dnsSuffix, c.dnsSuffix, "dns suffix should be equal") - assert.Equal(t, fakeIndexInformer, c.serviceIndexInformer, "service index informer pointer should be equal") - assert.Equal(t, configStoreCache, c.configStoreCache, "config configStoreCache cache pointer should be equal") - assert.Equal(t, time.Second, c.crcResyncInterval, "crc resync interval should be equal") - assert.Equal(t, processor, c.processor, "processor controller pointer should be equal") - -} - -func TestAddService(t *testing.T) { - tests := []struct { - name string - inputServices []string - clusterRbacConfig *v1alpha1.RbacConfig - expectedArray []string - }{ - { - name: "test adding new service to ClusterRbacConfig", - inputServices: []string{onboardedServiceName}, - clusterRbacConfig: newClusterRbacSpec(nil), - expectedArray: []string{onboardedServiceName}, - }, - { - name: "test adding existing service to ClusterRbacConfig", - inputServices: []string{onboardedServiceName}, - clusterRbacConfig: newClusterRbacSpec([]string{existingServiceName}), - expectedArray: []string{existingServiceName, onboardedServiceName}, - }, - { - name: "test adding nil ClusterRbacConfig", - inputServices: []string{onboardedServiceName}, - clusterRbacConfig: nil, - expectedArray: []string{}, - }, - { - name: "test adding with empty ClusterRbacConfig", - inputServices: []string{onboardedServiceName}, - clusterRbacConfig: newClusterRbacSpec(nil), - expectedArray: []string{onboardedServiceName}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - addServices(tt.inputServices, tt.clusterRbacConfig) - if tt.clusterRbacConfig != nil { - assert.Equal(t, tt.expectedArray, tt.clusterRbacConfig.Inclusion.Services, "ClusterRbacConfig service list should contain expected services") - } - }) - } -} - -func TestDeleteService(t *testing.T) { - tests := []struct { - name string - clusterRbacConfig *v1alpha1.RbacConfig - inputArray []string - expectedArray []string - }{ - { - name: "test deleting service from ClusterRbacConfig", - clusterRbacConfig: newClusterRbacSpec([]string{onboardedServiceName}), - inputArray: []string{onboardedServiceName}, - expectedArray: []string{}, - }, - { - name: "test deleting existing service from ClusterRbacConfig", - clusterRbacConfig: newClusterRbacSpec([]string{onboardedServiceName, existingServiceName}), - inputArray: []string{onboardedServiceName}, - expectedArray: []string{existingServiceName}, - }, - { - name: "test deleting empty input array to ClusterRbacConfig", - clusterRbacConfig: newClusterRbacSpec([]string{existingServiceName}), - inputArray: []string{}, - expectedArray: []string{existingServiceName}, - }, - { - name: "test deleting empty service which does not exist in the ClusterRbacConfig", - clusterRbacConfig: newClusterRbacSpec([]string{existingServiceName}), - inputArray: []string{onboardedServiceName}, - expectedArray: []string{existingServiceName}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - deleteServices(tt.inputArray, tt.clusterRbacConfig) - assert.Equal(t, tt.expectedArray, tt.clusterRbacConfig.Inclusion.Services, "ClusterRbacConfig service list should contain expected services") - }) - } -} - -func TestCreateClusterRbacConfig(t *testing.T) { - config := newClusterRbacConfig([]string{onboardedServiceName, existingServiceName}) - clusterRbacConfig, ok := config.Spec.(*v1alpha1.RbacConfig) - if !ok { - log.Panicln("cannot cast to rbac config") - } - assert.Equal(t, collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Kind(), config.Type, "ClusterRbacConfig type should be equal") - assert.Equal(t, constants.DefaultRbacConfigName, config.Name, "ClusterRbacConfig name should be equal") - assert.Equal(t, collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Group(), config.Group, "ClusterRbacConfig group should be equal") - assert.Equal(t, collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Version(), config.Version, "ClusterRbacConfig version should be equal") - assert.Equal(t, []string{onboardedServiceName, existingServiceName}, clusterRbacConfig.Inclusion.Services, "ClusterRbacConfig service list should be equal to expected") -} - -func TestGetServiceList(t *testing.T) { - onboardedServiceCopy := onboardedService.DeepCopy() - onboardedServiceCopy.Name = "onboarded-service-copy" - onboardedServiceCopyName := "onboarded-service-copy.test-namespace.svc.cluster.local" - - tests := []struct { - name string - inputServiceList []*v1.Service - expectedServiceArray []string - }{ - { - name: "test getting onboarded services", - inputServiceList: []*v1.Service{onboardedService}, - expectedServiceArray: []string{onboardedServiceName}, - }, - { - name: "test getting mix of onboarded and not onboarded services", - inputServiceList: []*v1.Service{onboardedService, onboardedServiceCopy, notOnboardedService}, - expectedServiceArray: []string{onboardedServiceName, onboardedServiceCopyName}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := newFakeController(tt.inputServiceList, false, make(chan struct{})) - ret := c.getOnboardedServiceList() - diff := compareServiceLists(tt.expectedServiceArray, ret) - assert.Equal(t, []string{}, diff, "list should be equal to expected") - }) - } -} - -func createClusterRbacExclusionConfig(services []string) model.Config { - return model.Config{ - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Kind(), - Name: constants.DefaultRbacConfigName, - Group: collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Version(), - }, - Spec: &v1alpha1.RbacConfig{ - Mode: v1alpha1.RbacConfig_ON_WITH_EXCLUSION, - Exclusion: &v1alpha1.RbacConfig_Target{ - Services: services, - }, - Inclusion: nil, - }, - } -} - -func TestSyncService(t *testing.T) { - onboardedServiceCopy := onboardedService.DeepCopy() - onboardedServiceCopy.Name = "onboarded-service-copy" - onboardedServiceCopyName := "onboarded-service-copy.test-namespace.svc.cluster.local" - - notOnboardedServiceCopy := notOnboardedService.DeepCopy() - notOnboardedServiceCopy.Name = "not-onboarded-service-copy" - notOnboardedServiceCopy.Annotations = make(map[string]string) - - tests := []struct { - name string - inputServiceList []*v1.Service - inputClusterRbacConfig model.Config - expectedServiceList []string - }{ - { - name: "Create: create ClusterRbacConfig when it does not exist with multiple new services", - inputServiceList: []*v1.Service{onboardedService, onboardedServiceCopy, notOnboardedService, notOnboardedServiceCopy}, - expectedServiceList: []string{onboardedServiceName, onboardedServiceCopyName}, - }, - { - name: "Update: update ClusterRbacConfig when it exists with multiple services", - inputServiceList: []*v1.Service{onboardedService, onboardedServiceCopy, notOnboardedService, notOnboardedServiceCopy}, - inputClusterRbacConfig: newClusterRbacConfig([]string{onboardedServiceCopyName}), - expectedServiceList: []string{onboardedServiceCopyName, onboardedServiceName}, - }, - { - name: "Update: update ClusterRbacConfig when it exists without an inclusion field", - inputServiceList: []*v1.Service{onboardedService, onboardedServiceCopy, notOnboardedService, notOnboardedServiceCopy}, - inputClusterRbacConfig: createClusterRbacExclusionConfig([]string{onboardedServiceCopyName}), - expectedServiceList: []string{onboardedServiceCopyName, onboardedServiceName}, - }, - { - name: "Update: update ClusterRbacConfig when not onboarded service exists", - inputServiceList: []*v1.Service{onboardedService, notOnboardedService}, - inputClusterRbacConfig: newClusterRbacConfig([]string{onboardedServiceName, notOnboardedServiceName}), - expectedServiceList: []string{onboardedServiceName}, - }, - { - name: "Delete: delete cluster rbacconfig if service is no longer onboarded", - inputServiceList: []*v1.Service{notOnboardedService}, - inputClusterRbacConfig: newClusterRbacConfig([]string{notOnboardedServiceName}), - expectedServiceList: []string{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - stopCh := make(chan struct{}) - c := newFakeController(tt.inputServiceList, true, stopCh) - - if tt.inputClusterRbacConfig.Spec != nil { - _, err := c.configStoreCache.Create(tt.inputClusterRbacConfig) - assert.Nil(t, err, "creating the ClusterRbacConfig should return nil") - } - - err := c.sync() - assert.Nil(t, err, "sync error should be nil") - - // Add a sleep for processing controller to work on the queue - time.Sleep(100 * time.Millisecond) - - clusterRbacConfig, err := getClusterRbacConfig(c) - if len(tt.expectedServiceList) == 0 { - assert.NotNil(t, err, fmt.Sprintf("error should not be nil for getClusterRbacConfig: %s", err)) - assert.Nil(t, clusterRbacConfig, "ClusterRbacConfig resource should be nil") - } else { - assert.Nil(t, err, fmt.Sprintf("error should be nil for getClusterRbacConfig: %s", err)) - assert.Equal(t, len(tt.expectedServiceList), len(clusterRbacConfig.Inclusion.Services), "number of onboarded services should match expected") - diff := compareServiceLists(tt.expectedServiceList, clusterRbacConfig.Inclusion.Services) - assert.Equal(t, []string{}, diff, "ClusterRbacConfig inclusion service list should be equal to the expected service list") - } - }) - } -} - -func TestResync(t *testing.T) { - c := &Controller{ - queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), - crcResyncInterval: time.Second * 1, - } - - stopCh := make(chan struct{}) - go c.resync(stopCh) - time.Sleep(time.Second * 2) - close(stopCh) - - assert.Equal(t, 1, c.queue.Len(), "queue length should be 1") - item, shutdown := c.queue.Get() - assert.False(t, shutdown, "shutdown should be false") - assert.Equal(t, 0, c.queue.Len(), "queue length should be 0") - assert.Equal(t, queueKey, item, "key should be equal") -} - -func TestCompareServiceLists(t *testing.T) { - tests := []struct { - name string - inputListA []string - inputListB []string - expectedList []string - }{ - { - name: "test one item difference between arrays", - inputListA: []string{"one", "two", "three"}, - inputListB: []string{"one", "three"}, - expectedList: []string{"two"}, - }, - { - name: "test array equality", - inputListA: []string{"one", "two", "three"}, - inputListB: []string{"one", "two", "three"}, - expectedList: []string{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - list := compareServiceLists(tt.inputListA, tt.inputListB) - assert.Equal(t, tt.expectedList, list, "expected array to equal expected") - }) - } -} - -func TestRemoveIndexElement(t *testing.T) { - tests := []struct { - name string - inputList []string - indexToRemove int - expectedList []string - }{ - { - name: "test removing index from array", - inputList: []string{"one", "two", "three"}, - indexToRemove: 1, - expectedList: []string{"one", "three"}, - }, - { - name: "test removing from empty array", - inputList: []string{}, - indexToRemove: 1, - expectedList: []string{}, - }, - { - name: "test removing negative index", - inputList: []string{"one"}, - indexToRemove: -1, - expectedList: []string{"one"}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - list := removeIndexElement(tt.inputList, tt.indexToRemove) - assert.Equal(t, tt.expectedList, list, "expected array to equal expected") - }) - } -} diff --git a/pkg/istio/processor/controller.go b/pkg/istio/processor/controller.go deleted file mode 100644 index c7d48307..00000000 --- a/pkg/istio/processor/controller.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2019, Verizon Media Inc. -// Licensed under the terms of the 3-Clause BSD license. See LICENSE file in -// github.com/yahoo/k8s-athenz-istio-auth for terms. -package processor - -import ( - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/common" - "istio.io/istio/pilot/pkg/model" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/util/workqueue" - - "github.com/yahoo/k8s-athenz-istio-auth/pkg/log" -) - -const queueNumRetries = 3 - -type Controller struct { - configStoreCache model.ConfigStoreCache - queue workqueue.RateLimitingInterface -} - -// NewController is responsible for creating the processing controller workqueue -func NewController(configStoreCache model.ConfigStoreCache) *Controller { - queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) - - c := &Controller{ - configStoreCache: configStoreCache, - queue: queue, - } - - return c -} - -// ProcessConfigChange is responsible for adding the key of the item to the queue -func (c *Controller) ProcessConfigChange(item *common.Item) { - log.Infof("Item added to queue Resource: %s, Action: %s", item.Resource.Key(), item.Operation) - c.queue.Add(item) -} - -// Run starts the main controller loop running sync at every poll interval. -func (c *Controller) Run(stopCh <-chan struct{}) { - defer c.queue.ShutDown() - wait.Until(c.runWorker, 0, stopCh) -} - -// runWorker calls processNextItem to process events of the work queue -func (c *Controller) runWorker() { - for c.processNextItem() { - } -} - -// processNextItem takes an item off the queue and calls the controllers sync -// function, handles the logic of re-queuing in case any errors occur -func (c *Controller) processNextItem() bool { - itemRaw, quit := c.queue.Get() - if quit { - return false - } - - defer c.queue.Done(itemRaw) - - item, ok := itemRaw.(*common.Item) - if !ok { - log.Errorf("Item cast failed for resource %v", item) - return true - } - - log.Infof("Processing %s for resource: %s", item.Operation, item.Resource.Key()) - err := c.sync(item) - if err != nil { - log.Errorf("Error performing %s for resource: %s: %s", item.Operation, item.Resource.Key(), err) - } - if item.CallbackHandler == nil { - c.queue.Forget(itemRaw) - return true - } - - // All errors/successes should be handled by the CallbackHandler() - err = item.CallbackHandler(err, item) - if err == nil { - c.queue.Forget(itemRaw) - return true - } - // If callback returns an error, retry if within limit - if c.queue.NumRequeues(itemRaw) < queueNumRetries { - log.Infof("Retrying %s for resource: %s due to sync error", item.Operation, item.Resource.Key()) - c.queue.AddRateLimited(itemRaw) - return true - } - log.Errorf("Max number of retries reached for operation %s on %s", item.Operation, item.Resource.Key()) - return true -} - -// sync is responsible for invoking the appropriate API operation on the model.Config resource -func (c *Controller) sync(item *common.Item) error { - if item == nil { - return nil - } - - var err error - switch item.Operation { - case model.EventAdd: - _, err = c.configStoreCache.Create(item.Resource) - case model.EventUpdate: - _, err = c.configStoreCache.Update(item.Resource) - case model.EventDelete: - res := item.Resource - err = c.configStoreCache.Delete(res.GroupVersionKind(), res.Name, res.Namespace) - } - - return err -} diff --git a/pkg/istio/processor/controller_test.go b/pkg/istio/processor/controller_test.go deleted file mode 100644 index 3b4d384d..00000000 --- a/pkg/istio/processor/controller_test.go +++ /dev/null @@ -1,226 +0,0 @@ -package processor - -import ( - "fmt" - "istio.io/istio/pkg/config/schema/collection" - "istio.io/istio/pkg/config/schema/collections" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/common" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/log" - - "istio.io/api/rbac/v1alpha1" - "istio.io/istio/pilot/pkg/config/memory" - "istio.io/istio/pilot/pkg/model" - - "k8s.io/api/core/v1" -) - -func init() { - log.InitLogger("", "debug") -} - -func newSrSpec() *v1alpha1.ServiceRole { - return &v1alpha1.ServiceRole{ - Rules: []*v1alpha1.AccessRule{ - { - Services: []string{common.WildCardAll}, - Methods: []string{"GET"}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{"test-svc"}, - }, - }, - }, - }, - } -} - -func newSr(ns, role string) model.Config { - return common.NewConfig(collections.IstioRbacV1Alpha1Serviceroles, ns, role, newSrSpec()) -} - -func newSrbSpec(role string) *v1alpha1.ServiceRoleBinding { - return &v1alpha1.ServiceRoleBinding{ - RoleRef: &v1alpha1.RoleRef{ - Kind: common.ServiceRoleKind, - Name: role, - }, - Subjects: []*v1alpha1.Subject{ - { - User: "test-user", - }, - }, - } -} - -func newSrb(ns, role string) model.Config { - return common.NewConfig(collections.IstioRbacV1Alpha1Servicerolebindings, ns, role, newSrbSpec(role)) -} - -func cacheWithItems() (model.ConfigStoreCache, error) { - configDescriptor := collection.SchemasFor(collections.IstioRbacV1Alpha1Serviceroles, collections.IstioRbacV1Alpha1Clusterrbacconfigs, collections.IstioRbacV1Alpha1Servicerolebindings) - - c := memory.NewController(memory.Make(configDescriptor)) - _, err := c.Create(newSr("test-ns", "test-svc")) - if err != nil { - return nil, err - } - _, err = c.Create(newSrb("test-ns", "test-svc")) - if err != nil { - return nil, err - } - return c, nil -} - -func TestSync(t *testing.T) { - - configDescriptor := collection.SchemasFor(collections.IstioRbacV1Alpha1Serviceroles, collections.IstioRbacV1Alpha1Clusterrbacconfigs, collections.IstioRbacV1Alpha1Servicerolebindings) - - cbHandler := func(err error, i *common.Item) error { - assert.Fail(t, "CallbackHandler should not be called") - return nil - } - - updateTestCache, err := cacheWithItems() - assert.Nil(t, err, "error should be nil while setting up cache") - - tests := []struct { - name string - input *common.Item - startingCache model.ConfigStoreCache - expectedCache model.ConfigStoreCache - expectedErr error - }{ - { - name: "should not perform any cache op on nil item", - input: nil, - startingCache: memory.NewController(memory.Make(configDescriptor)), - expectedCache: memory.NewController(memory.Make(configDescriptor)), - expectedErr: nil, - }, - { - name: "should perfom valid create operation", - input: &common.Item{ - Operation: model.EventAdd, - Resource: newSr("test-ns", "test-role"), - CallbackHandler: cbHandler, - }, - startingCache: memory.NewController(memory.Make(configDescriptor)), - expectedCache: func() model.ConfigStoreCache { - c := memory.NewController(memory.Make(configDescriptor)) - _, err := c.Create(newSr("test-ns", "test-role")) - assert.Nil(t, err, fmt.Sprintf("unexpected error while setting up expectedCache: %s", err)) - return c - }(), - expectedErr: nil, - }, - { - name: "should perform valid update operation", - input: &common.Item{ - Operation: model.EventUpdate, - Resource: func() model.Config { - obj := updateTestCache.Get(collections.IstioRbacV1Alpha1Serviceroles.Resource().GroupVersionKind(), "test-svc", "test-ns") - assert.NotNil(t, obj, "cache should return the ServiceRole resource") - srSpec, ok := (obj.Spec).(*v1alpha1.ServiceRole) - assert.True(t, ok, "cache should return a ServiceRole resource") - srSpec.Rules = append(srSpec.Rules, &v1alpha1.AccessRule{ - Services: []string{common.WildCardAll}, - Methods: []string{"POST"}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{"test-svc"}, - }, - }, - }) - return *obj - }(), - CallbackHandler: cbHandler, - }, - startingCache: updateTestCache, - expectedCache: func() model.ConfigStoreCache { - c := memory.NewController(memory.Make(configDescriptor)) - srSpec := newSrSpec() - srSpec.Rules = append(srSpec.Rules, &v1alpha1.AccessRule{ - Services: []string{common.WildCardAll}, - Methods: []string{"POST"}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{"test-svc"}, - }, - }, - }) - _, err := c.Create(common.NewConfig(collections.IstioRbacV1Alpha1Serviceroles, "test-ns", "test-svc", srSpec)) - assert.Nil(t, err, fmt.Sprintf("unexpected error while setting up expectedCache: %s", err)) - _, err = c.Create(newSrb("test-ns", "test-svc")) - assert.Nil(t, err, fmt.Sprintf("unexpected error while setting up expectedCache: %s", err)) - - return c - }(), - expectedErr: nil, - }, - { - name: "should perfom valid delete operation", - input: &common.Item{ - Operation: model.EventDelete, - Resource: newSr("test-ns", "test-svc"), - CallbackHandler: cbHandler, - }, - startingCache: func() model.ConfigStoreCache { - c, err := cacheWithItems() - assert.Nil(t, err, "error should be nil while setting up cache") - return c - }(), - expectedCache: func() model.ConfigStoreCache { - c := memory.NewController(memory.Make(configDescriptor)) - _, err := c.Create(newSrb("test-ns", "test-svc")) - assert.Nil(t, err, fmt.Sprintf("unexpected error while setting up expectedCache: %s", err)) - return c - }(), - expectedErr: nil, - }, - { - name: "should return error if valid update operation", - input: &common.Item{ - Operation: model.EventUpdate, - Resource: newSr("test-ns", "test-svc"), - CallbackHandler: cbHandler, - }, - startingCache: updateTestCache, - expectedCache: updateTestCache, - expectedErr: fmt.Errorf("old revision"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - configStoreCache := tt.startingCache - c := NewController(configStoreCache) - - err := c.sync(tt.input) - assert.Equal(t, tt.expectedErr, err, "sync err should match expected error") - - for _, typ := range configDescriptor.All() { - actualItemsT, err := configStoreCache.List(typ.Resource().GroupVersionKind(), v1.NamespaceAll) - assert.Nil(t, err, fmt.Sprintf("error should be nil while fetching %s resources: %s", typ, err)) - - expectedItemsT, err := tt.expectedCache.List(typ.Resource().GroupVersionKind(), v1.NamespaceAll) - assert.Nil(t, err, fmt.Sprintf("error should be nil whil efetching %s resources: %s", typ, err)) - - assert.Equal(t, len(expectedItemsT), len(actualItemsT), fmt.Sprintf("len(list) of %s resources on the cache should match", typ)) - if len(expectedItemsT) == len(actualItemsT) { - for i, expItem := range expectedItemsT { - assert.Equal(t, expItem.Type, actualItemsT[i].Type, "type should match") - assert.Equal(t, expItem.Namespace, actualItemsT[i].Namespace, "namespace should match") - assert.Equal(t, expItem.Name, actualItemsT[i].Name, "name should match") - assert.Equal(t, expItem.Spec, actualItemsT[i].Spec, "spec should match") - } - } - } - }) - } -} diff --git a/pkg/istio/rbac/v1/provider.go b/pkg/istio/rbac/v1/provider.go deleted file mode 100644 index c9747893..00000000 --- a/pkg/istio/rbac/v1/provider.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2019, Verizon Media Inc. -// Licensed under the terms of the 3-Clause BSD license. See LICENSE file in -// github.com/yahoo/k8s-athenz-istio-auth for terms. -package v1 - -import ( - "github.com/yahoo/k8s-athenz-istio-auth/pkg/athenz" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/common" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/log" - "istio.io/istio/pilot/pkg/model" - "istio.io/istio/pkg/config/schema/collections" - "istio.io/istio/pkg/config/validation" -) - -// implements github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/Provider interface -type v1 struct { - enableOriginJwtSubject bool -} - -func NewProvider(enableOriginJwtSubject bool) rbac.Provider { - return &v1{ - enableOriginJwtSubject: enableOriginJwtSubject, - } -} - -// ConvertAthenzModelIntoIstioRbac converts the Athenz RBAC model into the list of Istio Authorization V1 specific -// RBAC custom resources (ServiceRoles, ServiceRoleBindings) -// The idea is that with a given input model, the function should always return the same output list of resources -func (p *v1) ConvertAthenzModelIntoIstioRbac(m athenz.Model, _ string, _ string, _ string) []model.Config { - - out := make([]model.Config, 0) - - // Process all the roles in the same order as defined in the Athenz domain - for _, roleFQDN := range m.Roles { - - // Check if there are any policies/assertions defined for this role - assertions, exists := m.Rules[roleFQDN] - if !exists { - log.Debugf("Policies/assertions not defined for role %s", roleFQDN) - continue - } - - // Extract only the role name from the :role. format - roleName, err := common.ParseRoleFQDN(m.Name, string(roleFQDN)) - if err != nil { - log.Debugln(err.Error()) - continue - } - - // Transform the assertions for an Athenz Role into a ServiceRole spec - srSpec, err := common.GetServiceRoleSpec(m.Name, roleName, assertions) - if err != nil { - log.Debugf("Error converting the assertions for role: %s to a ServiceRole: %s", roleName, err.Error()) - continue - } - - // Validate the ServiceRole spec - err = validation.ValidateServiceRole(roleName, m.Namespace, srSpec) - if err != nil { - log.Warningf("Error validating the converted ServiceRole spec: %s for role: %s", err.Error(), roleName) - continue - } - - k8sRoleName := common.ConvertAthenzRoleNameToK8sName(roleName) - sr := common.NewConfig(collections.IstioRbacV1Alpha1Serviceroles, m.Namespace, k8sRoleName, srSpec) - out = append(out, sr) - - // Transform the members for an Athenz Role into a ServiceRoleBinding spec - roleMembers, exists := m.Members[roleFQDN] - if !exists { - log.Debugf("Cannot find members for the role: %s while creating a ServiceRoleBinding", roleName) - continue - } - - srbSpec, err := common.GetServiceRoleBindingSpec(string(m.Name), roleName, k8sRoleName, roleMembers, p.enableOriginJwtSubject) - if err != nil { - log.Debugf("Error converting the members for role: %s to a ServiceRoleBinding: %s", roleName, err.Error()) - continue - } - - // Validate the ServiceRoleBinding spec - err = validation.ValidateServiceRoleBinding(roleName, m.Namespace, srbSpec) - if err != nil { - log.Warningf("Error validating the converted ServiceRoleBinding spec: %s for role: %s", err.Error(), roleName) - continue - } - - srb := common.NewConfig(collections.IstioRbacV1Alpha1Servicerolebindings, m.Namespace, k8sRoleName, srbSpec) - out = append(out, srb) - } - - return out -} - -// GetCurrentIstioRbac returns the ServiceRole and ServiceRoleBinding resources for the specified model's namespace -func (p *v1) GetCurrentIstioRbac(m athenz.Model, csc model.ConfigStoreCache, _ string) []model.Config { - - sr, err := csc.List(collections.IstioRbacV1Alpha1Serviceroles.Resource().GroupVersionKind(), m.Namespace) - if err != nil { - log.Errorf("Error listing the ServiceRole resources in the namespace: %s", m.Namespace) - } - - srb, err := csc.List(collections.IstioRbacV1Alpha1Servicerolebindings.Resource().GroupVersionKind(), m.Namespace) - if err != nil { - log.Errorf("Error listing the ServiceRoleBinding resources in the namespace: %s", m.Namespace) - } - - return append(sr, srb...) -} diff --git a/pkg/istio/rbac/v1/provider_test.go b/pkg/istio/rbac/v1/provider_test.go deleted file mode 100644 index 3a9bde98..00000000 --- a/pkg/istio/rbac/v1/provider_test.go +++ /dev/null @@ -1,866 +0,0 @@ -// Copyright 2019, Verizon Media Inc. -// Licensed under the terms of the 3-Clause BSD license. See LICENSE file in -// github.com/yahoo/k8s-athenz-istio-auth for terms. -package v1 - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/yahoo/athenz/clients/go/zms" - "istio.io/istio/pkg/config/schema/collection" - "istio.io/istio/pkg/config/schema/collections" - - "github.com/yahoo/k8s-athenz-istio-auth/pkg/athenz" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/common" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/log" - - "istio.io/api/rbac/v1alpha1" - "istio.io/istio/pilot/pkg/config/memory" - "istio.io/istio/pilot/pkg/model" -) - -func init() { - log.InitLogger("", "debug") -} - -func TestConvertAthenzModelIntoIstioRbac(t *testing.T) { - - allow := zms.ALLOW - cases := []struct { - test string - model athenz.Model - enableOriginJwtSubject bool - expectedConfigs []model.Config - }{ - { - test: "empty model", - model: athenz.Model{}, - enableOriginJwtSubject: true, - expectedConfigs: []model.Config{}, - }, - { - test: "valid model with policies and members", - model: athenz.Model{ - Name: "athenz.domain", - Namespace: "athenz-domain", - Roles: []zms.ResourceName{ - zms.ResourceName("athenz.domain:role.client-reader-role"), - zms.ResourceName("different-domain:role.trust-role"), - zms.ResourceName("athenz.domain:role.client-writer-role"), - zms.ResourceName("athenz.domain:role.identity-provider-role"), - zms.ResourceName("athenz.domain:role.client-no-policies-role"), - zms.ResourceName("athenz.domain:role.no-members-role"), - }, - Rules: map[zms.ResourceName][]*zms.Assertion{ - zms.ResourceName("different-domain:role.trust-role"): { - { - Effect: &allow, - Action: "PUT", - Role: "different-domain:role.trust-role", - Resource: "athenz.domain:svc.my-service-name:/another/sub/path", - }, - { - Effect: &allow, - Action: "post", - Role: "different-domain:role.trust-role", - Resource: "athenz.domain:svc.some-other-service-name:*", - }, - }, - zms.ResourceName("athenz.domain:role.client-writer-role"): { - { - Effect: &allow, - Action: "PUT", - Role: "athenz.domain:role.client-writer-role", - Resource: "athenz.domain:svc.my-service-name:/another/sub/path", - }, - { - Effect: &allow, - Action: "post", - Role: "athenz.domain:role.client-writer-role", - Resource: "athenz.domain:svc.some-other-service-name:*", - }, - }, - zms.ResourceName("athenz.domain:role.identity-provider-role"): { - { - Effect: &allow, - Action: "LAUNCH", - Role: "athenz.domain:role.identity-provider-role", - Resource: "athenz.domain:svc.my-service-name", - }, - }, - zms.ResourceName("athenz.domain:role.client-reader-role"): { - { - Effect: &allow, - Action: "get", - Role: "athenz.domain:role.client-reader-role", - Resource: "athenz.domain:svc.my-service-name:/protected/path", - }, - { - Effect: &allow, - Action: "HEAD", - Role: "athenz.domain:role.client-reader-role", - Resource: "athenz.domain:svc.my-another-service-name", - }, - }, - }, - Members: map[zms.ResourceName][]*zms.RoleMember{ - zms.ResourceName("athenz.domain:role.client-reader-role"): { - { - MemberName: "some-client.domain.client-serviceA", - }, - { - MemberName: "user.athenzuser", - }, - }, - zms.ResourceName("different-domain:trust-role"): { - { - MemberName: "user.trusted", - }, - }, - zms.ResourceName("athenz.domain:role.client-writer-role"): { - { - MemberName: "writer-domain.client-power-service", - }, - { - MemberName: "user.developer", - }, - }, - zms.ResourceName("athenz.domain:role.identity-provider-role"): { - { - MemberName: "k8s.cluster.prod", - }, - { - MemberName: "k8s.cluster.canary", - }, - }, - zms.ResourceName("athenz.domain:role.client-no-policies-role"): { - { - MemberName: "another-domain.client-service", - }, - { - MemberName: "user.engineer", - }, - }, - }, - }, - enableOriginJwtSubject: true, - expectedConfigs: []model.Config{ - { - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Serviceroles.Resource().Kind(), - Group: collections.IstioRbacV1Alpha1Serviceroles.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Serviceroles.Resource().Version(), - Namespace: "athenz-domain", - Name: "client-reader-role", - }, - Spec: &v1alpha1.ServiceRole{ - Rules: []*v1alpha1.AccessRule{ - { - Methods: []string{ - "GET", - }, - Paths: []string{ - "/protected/path", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-service-name", - }, - }, - }, - }, - { - Methods: []string{ - "HEAD", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-another-service-name", - }, - }, - }, - }, - }, - }, - }, - { - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Kind(), - Group: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Version(), - Namespace: "athenz-domain", - Name: "client-reader-role", - }, - Spec: &v1alpha1.ServiceRoleBinding{ - RoleRef: &v1alpha1.RoleRef{ - Name: "client-reader-role", - Kind: common.ServiceRoleKind, - }, - Subjects: []*v1alpha1.Subject{ - { - User: "some-client.domain/sa/client-serviceA", - }, - { - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "some-client.domain.client-serviceA", - }, - }, - { - User: "user/sa/athenzuser", - }, - { - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "user.athenzuser", - }, - }, - { - User: "athenz.domain/ra/client-reader-role", - }, - }, - }, - }, - { - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Serviceroles.Resource().Kind(), - Group: collections.IstioRbacV1Alpha1Serviceroles.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Serviceroles.Resource().Version(), - Namespace: "athenz-domain", - Name: "client-writer-role", - }, - Spec: &v1alpha1.ServiceRole{ - Rules: []*v1alpha1.AccessRule{ - { - Methods: []string{ - "PUT", - }, - Paths: []string{ - "/another/sub/path", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-service-name", - }, - }, - }, - }, - { - Methods: []string{ - "POST", - }, - Paths: []string{ - "*", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "some-other-service-name", - }, - }, - }, - }, - }, - }, - }, - { - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Kind(), - Group: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Version(), - Namespace: "athenz-domain", - Name: "client-writer-role", - }, - Spec: &v1alpha1.ServiceRoleBinding{ - RoleRef: &v1alpha1.RoleRef{ - Name: "client-writer-role", - Kind: common.ServiceRoleKind, - }, - Subjects: []*v1alpha1.Subject{ - { - User: "writer-domain/sa/client-power-service", - }, - { - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "writer-domain.client-power-service", - }, - }, - { - User: "user/sa/developer", - }, - { - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "user.developer", - }, - }, - { - User: "athenz.domain/ra/client-writer-role", - }, - }, - }, - }, - }, - }, - { - test: "valid model with policies and members and role name with underscore", - model: athenz.Model{ - Name: "athenz.domain", - Namespace: "athenz-domain", - Roles: []zms.ResourceName{ - zms.ResourceName("athenz.domain:role.client_reader_role"), - }, - Rules: map[zms.ResourceName][]*zms.Assertion{ - zms.ResourceName("athenz.domain:role.client_reader_role"): { - { - Effect: &allow, - Action: "get", - Role: "athenz.domain:role.client_reader_role", - Resource: "athenz.domain:svc.my-service-name:/protected/path", - }, - { - Effect: &allow, - Action: "HEAD", - Role: "athenz.domain:role.client_reader_role", - Resource: "athenz.domain:svc.my-another-service-name", - }, - }, - }, - Members: map[zms.ResourceName][]*zms.RoleMember{ - zms.ResourceName("athenz.domain:role.client_reader_role"): { - { - MemberName: "some-client.domain.client-serviceA", - }, - { - MemberName: "user.athenzuser", - }, - }, - }, - }, - enableOriginJwtSubject: true, - expectedConfigs: []model.Config{ - { - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Serviceroles.Resource().Kind(), - Group: collections.IstioRbacV1Alpha1Serviceroles.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Serviceroles.Resource().Version(), - Namespace: "athenz-domain", - Name: "client--reader--role", - }, - Spec: &v1alpha1.ServiceRole{ - Rules: []*v1alpha1.AccessRule{ - { - Methods: []string{ - "GET", - }, - Paths: []string{ - "/protected/path", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-service-name", - }, - }, - }, - }, - { - Methods: []string{ - "HEAD", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-another-service-name", - }, - }, - }, - }, - }, - }, - }, - { - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Kind(), - Group: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Version(), - Namespace: "athenz-domain", - Name: "client--reader--role", - }, - Spec: &v1alpha1.ServiceRoleBinding{ - RoleRef: &v1alpha1.RoleRef{ - Name: "client--reader--role", - Kind: common.ServiceRoleKind, - }, - Subjects: []*v1alpha1.Subject{ - { - User: "some-client.domain/sa/client-serviceA", - }, - { - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "some-client.domain.client-serviceA", - }, - }, - { - User: "user/sa/athenzuser", - }, - { - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "user.athenzuser", - }, - }, - { - User: "athenz.domain/ra/client_reader_role", - }, - }, - }, - }, - }, - }, - { - test: "valid model with policies and members and role name with underscore with enableOriginJwtSubject flag set to false", - model: athenz.Model{ - Name: "athenz.domain", - Namespace: "athenz-domain", - Roles: []zms.ResourceName{ - zms.ResourceName("athenz.domain:role.client_reader_role"), - }, - Rules: map[zms.ResourceName][]*zms.Assertion{ - zms.ResourceName("athenz.domain:role.client_reader_role"): { - { - Effect: &allow, - Action: "get", - Role: "athenz.domain:role.client_reader_role", - Resource: "athenz.domain:svc.my-service-name:/protected/path", - }, - { - Effect: &allow, - Action: "HEAD", - Role: "athenz.domain:role.client_reader_role", - Resource: "athenz.domain:svc.my-another-service-name", - }, - }, - }, - Members: map[zms.ResourceName][]*zms.RoleMember{ - zms.ResourceName("athenz.domain:role.client_reader_role"): { - { - MemberName: "some-client.domain.client-serviceA", - }, - { - MemberName: "user.athenzuser", - }, - }, - }, - }, - enableOriginJwtSubject: false, - expectedConfigs: []model.Config{ - { - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Serviceroles.Resource().Kind(), - Group: collections.IstioRbacV1Alpha1Serviceroles.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Serviceroles.Resource().Version(), - Namespace: "athenz-domain", - Name: "client--reader--role", - }, - Spec: &v1alpha1.ServiceRole{ - Rules: []*v1alpha1.AccessRule{ - { - Methods: []string{ - "GET", - }, - Paths: []string{ - "/protected/path", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-service-name", - }, - }, - }, - }, - { - Methods: []string{ - "HEAD", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-another-service-name", - }, - }, - }, - }, - }, - }, - }, - { - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Kind(), - Group: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Version(), - Namespace: "athenz-domain", - Name: "client--reader--role", - }, - Spec: &v1alpha1.ServiceRoleBinding{ - RoleRef: &v1alpha1.RoleRef{ - Name: "client--reader--role", - Kind: common.ServiceRoleKind, - }, - Subjects: []*v1alpha1.Subject{ - { - User: "some-client.domain/sa/client-serviceA", - }, - { - User: "user/sa/athenzuser", - }, - { - User: "athenz.domain/ra/client_reader_role", - }, - }, - }, - }, - }, - }, - { - test: "model with invalid members that results in empty ServiceRoleBindingSpec", - model: athenz.Model{ - Name: "athenz.domain", - Namespace: "athenz-domain", - Roles: []zms.ResourceName{ - zms.ResourceName("athenz.domain:role.client-reader-role"), - zms.ResourceName("athenz.domain:role.client-writer-role"), - }, - Rules: map[zms.ResourceName][]*zms.Assertion{ - zms.ResourceName("athenz.domain:role.client-reader-role"): { - { - Effect: &allow, - Action: "get", - Role: "athenz.domain:role.client-reader-role", - Resource: "athenz.domain:svc.my-service-name:/protected/path", - }, - { - Effect: &allow, - Action: "HEAD", - Role: "athenz.domain:role.client-reader-role", - Resource: "athenz.domain:svc.my-another-service-name", - }, - }, - }, - Members: map[zms.ResourceName][]*zms.RoleMember{ - zms.ResourceName("athenz.domain:role.client-reader-role"): { - { - MemberName: "invalid-principal", - }, - }, - }, - }, - enableOriginJwtSubject: true, - expectedConfigs: []model.Config{ - { - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Serviceroles.Resource().Kind(), - Group: collections.IstioRbacV1Alpha1Serviceroles.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Serviceroles.Resource().Version(), - Namespace: "athenz-domain", - Name: "client-reader-role", - }, - Spec: &v1alpha1.ServiceRole{ - Rules: []*v1alpha1.AccessRule{ - { - Methods: []string{ - "GET", - }, - Paths: []string{ - "/protected/path", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-service-name", - }, - }, - }, - }, - { - Methods: []string{ - "HEAD", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-another-service-name", - }, - }, - }, - }, - }, - }, - }, - { - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Kind(), - Group: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Version(), - Namespace: "athenz-domain", - Name: "client-reader-role", - }, - Spec: &v1alpha1.ServiceRoleBinding{ - RoleRef: &v1alpha1.RoleRef{ - Name: "client-reader-role", - Kind: common.ServiceRoleKind, - }, - Subjects: []*v1alpha1.Subject{ - { - User: "athenz.domain/ra/client-reader-role", - }, - }, - }, - }, - }, - }, - { - test: "model that has no members for a role resulting in unable to create ServiceRoleBindingSpec", - model: athenz.Model{ - Name: "athenz.domain", - Namespace: "athenz-domain", - Roles: []zms.ResourceName{ - zms.ResourceName("athenz.domain:role.client-reader-role"), - }, - Rules: map[zms.ResourceName][]*zms.Assertion{ - zms.ResourceName("athenz.domain:role.client-reader-role"): { - { - Effect: &allow, - Action: "get", - Role: "athenz.domain:role.client-reader-role", - Resource: "athenz.domain:svc.my-service-name:/protected/path", - }, - { - Effect: &allow, - Action: "HEAD", - Role: "athenz.domain:role.client-reader-role", - Resource: "athenz.domain:svc.my-another-service-name", - }, - }, - }, - Members: map[zms.ResourceName][]*zms.RoleMember{}, - }, - enableOriginJwtSubject: true, - expectedConfigs: []model.Config{ - { - ConfigMeta: model.ConfigMeta{ - Type: collections.IstioRbacV1Alpha1Serviceroles.Resource().Kind(), - Group: collections.IstioRbacV1Alpha1Serviceroles.Resource().Group(), - Version: collections.IstioRbacV1Alpha1Serviceroles.Resource().Version(), - Namespace: "athenz-domain", - Name: "client-reader-role", - }, - Spec: &v1alpha1.ServiceRole{ - Rules: []*v1alpha1.AccessRule{ - { - Methods: []string{ - "GET", - }, - Paths: []string{ - "/protected/path", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-service-name", - }, - }, - }, - }, - { - Methods: []string{ - "HEAD", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-another-service-name", - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - test: "model with invalid assertions that results in no rules for ServiceRole", - model: athenz.Model{ - Name: "athenz.domain", - Namespace: "athenz-domain", - Roles: []zms.ResourceName{ - zms.ResourceName("athenz.domain:role.client-reader-role"), - zms.ResourceName("athenz.domain:role.client-writer-role"), - }, - Rules: map[zms.ResourceName][]*zms.Assertion{ - zms.ResourceName("athenz.domain:role.client-reader-role"): { - { - Effect: &allow, - Action: "assume_role", - Role: "athenz.domain:role.client-reader-role", - Resource: "athenz.domain:svc.my-service-name:/protected/path", - }, - { - Effect: &allow, - Action: "HEAD", - Role: "athenz.domain:role.client-reader-role", - Resource: "my-another-service-name:*", - }, - }, - }, - Members: map[zms.ResourceName][]*zms.RoleMember{ - zms.ResourceName("athenz.domain:role.client-reader-role"): { - { - MemberName: "some-client.domain.client-serviceA", - }, - { - MemberName: "user.athenzuser", - }, - }, - }, - }, - enableOriginJwtSubject: true, - expectedConfigs: []model.Config{}, - }, - } - - for _, c := range cases { - t.Run(c.test, func(t *testing.T) { - p := NewProvider(c.enableOriginJwtSubject) - gotConfigs := p.ConvertAthenzModelIntoIstioRbac(c.model, "", "", "") - assert.EqualValues(t, c.expectedConfigs, gotConfigs, c.test) - }) - } -} - -func newCache() model.ConfigStoreCache { - configDescriptor := collection.SchemasFor(collections.IstioRbacV1Alpha1Serviceroles, collections.IstioRbacV1Alpha1Clusterrbacconfigs, collections.IstioRbacV1Alpha1Servicerolebindings) - - return memory.NewController(memory.Make(configDescriptor)) -} - -func newSr(ns, role string) model.Config { - srSpec := &v1alpha1.ServiceRole{ - Rules: []*v1alpha1.AccessRule{ - { - Services: []string{common.WildCardAll}, - Methods: []string{"GET"}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{"test-svc"}, - }, - }, - }, - }, - } - return common.NewConfig(collections.IstioRbacV1Alpha1Serviceroles, ns, role, srSpec) -} - -func newSrb(ns, role string) model.Config { - srbSpec := &v1alpha1.ServiceRoleBinding{ - RoleRef: &v1alpha1.RoleRef{ - Kind: common.ServiceRoleKind, - Name: role, - }, - Subjects: []*v1alpha1.Subject{ - { - User: "test-user", - }, - { - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "user.test-user", - }, - }, - }, - } - return common.NewConfig(collections.IstioRbacV1Alpha1Servicerolebindings, ns, role, srbSpec) -} - -func updatedCache() (model.ConfigStoreCache, error) { - c := newCache() - _, err := c.Create(newSr("test-ns", "svc-role")) - if err != nil { - return nil, err - } - _, err = c.Create(newSrb("test-ns", "svc-role")) - if err != nil { - return nil, err - } - return c, nil -} - -func TestGetCurrentIstioRbac(t *testing.T) { - cacheWithItems, err := updatedCache() - assert.Nil(t, err, "error should be nil") - assert.NotNil(t, cacheWithItems, "cache should not be nil") - - type input struct { - m athenz.Model - csc model.ConfigStoreCache - } - cases := []struct { - test string - input input - expected []model.Config - }{ - { - test: "should return empty list for empty cache", - input: input{ - m: athenz.Model{}, - csc: newCache(), - }, - expected: []model.Config{}, - }, - { - test: "should return list of servicerole and servicerolebindings", - input: input{ - m: athenz.Model{}, - csc: cacheWithItems, - }, - expected: []model.Config{ - *cacheWithItems.Get(collections.IstioRbacV1Alpha1Serviceroles.Resource().GroupVersionKind(), "svc-role", "test-ns"), - *cacheWithItems.Get(collections.IstioRbacV1Alpha1Servicerolebindings.Resource().GroupVersionKind(), "svc-role", "test-ns"), - }, - }, - } - - for _, c := range cases { - t.Run(c.test, func(t *testing.T) { - p := NewProvider(true) - gotConfigs := p.GetCurrentIstioRbac(c.input.m, c.input.csc, "") - assert.EqualValues(t, c.expected, gotConfigs, c.test) - }) - } -} diff --git a/screwdriver.yaml b/screwdriver.yaml index 61c7766c..8b6e6098 100644 --- a/screwdriver.yaml +++ b/screwdriver.yaml @@ -21,7 +21,7 @@ jobs: cd $GOPATH/src mkdir -p k8s.io cd k8s.io - git clone https://github.com/kubernetes/kubernetes.git + git clone https://github.com/kubernetes/kubernetes.git --branch release-1.17 --depth 3 cd kubernetes apt-get update && apt-get install rsync -y git checkout release-1.17 @@ -30,4 +30,5 @@ jobs: cd $GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/kube-openapi go mod init cd $GOPATH/src/github.com/AthenZ/k8s-athenz-istio-auth/test/integration - go test -v -p 1 ./... \ No newline at end of file + go test -v -p 1 ./... + \ No newline at end of file diff --git a/test/integration/authz_v1_test.go b/test/integration/authz_v1_test.go deleted file mode 100644 index 4e20fcc3..00000000 --- a/test/integration/authz_v1_test.go +++ /dev/null @@ -1,467 +0,0 @@ -package integration - -import ( - "reflect" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/yahoo/athenz/clients/go/zms" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/athenz" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/common" - "github.com/yahoo/k8s-athenz-istio-auth/test/integration/fixtures" - "github.com/yahoo/k8s-athenz-istio-auth/test/integration/framework" - - "istio.io/api/rbac/v1alpha1" - "istio.io/istio/pilot/pkg/model" - "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" -) - -type action int - -const ( - create action = iota - update - delete - noop -) - -// rolloutAndValidateRbac will create / update / delete / noop the athenz domain resource and wait for the -// associated service role / service role bindings to be created. Once these are rolled out, -// they are validated against the expected output. -func rolloutAndValidateRbac(t *testing.T, r *fixtures.ExpectedRbac, a action) { - switch a { - case create: - _, err := framework.Global.AthenzDomainClientset.AthenzV1().AthenzDomains().Create(r.AD, v1.CreateOptions{}) - assert.Nil(t, err, "athenz domain create error should be nil") - case update: - currentAD, err := framework.Global.AthenzDomainClientset.AthenzV1().AthenzDomains().Get(r.AD.Name, v1.GetOptions{}) - assert.Nil(t, err, "athenz domain get error should be nil") - r.AD.ResourceVersion = currentAD.ResourceVersion - _, err = framework.Global.AthenzDomainClientset.AthenzV1().AthenzDomains().Update(r.AD, v1.UpdateOptions{}) - assert.Nil(t, err, "athenz domain update error should be nil") - case delete: - err := framework.Global.AthenzDomainClientset.AthenzV1().AthenzDomains().Delete(r.AD.Name, v1.DeleteOptions{}) - assert.Nil(t, err, "athenz domain delete error should be nil") - } - - err := wait.PollImmediate(time.Second, time.Second*5, func() (bool, error) { - for _, curr := range r.ModelConfigs { - got := framework.Global.IstioClientset.Get(curr.GroupVersionKind(), curr.Name, curr.Namespace) - if got == nil { - return false, nil - } - - if !reflect.DeepEqual(curr.Spec, got.Spec) { - return false, nil - } - } - - return true, nil - }) - - assert.Nil(t, err, "time out waiting for rollout for ad "+r.AD.Name+" with error") - - namespace := athenz.DomainToNamespace(r.AD.Name) - modelConfigTypes := make(map[string]bool) - modelConfigs := make([]model.Config, 0) - for _, modelConfig := range r.ModelConfigs { - _, exists := modelConfigTypes[modelConfig.Type] - if !exists { - list, err := framework.Global.IstioClientset.List(modelConfig.GroupVersionKind(), namespace) - assert.Nil(t, err, "istio custom resource list error should be nil") - modelConfigTypes[modelConfig.Type] = true - for _, curr := range list { - curr.ResourceVersion = "" - curr.CreationTimestamp = time.Time{} - modelConfigs = append(modelConfigs, curr) - } - } - } - - assert.ElementsMatch(t, modelConfigs, r.ModelConfigs, "expected list must match the configs on the cluster") -} - -// cleanupRbac will clean up the athenz domain and service role / service role binding objects on the cluster -func cleanupRbac(t *testing.T, r *fixtures.ExpectedRbac) { - err := framework.Global.AthenzDomainClientset.AthenzV1().AthenzDomains().Delete(r.AD.Name, v1.DeleteOptions{}) - assert.Nil(t, err, "athenz domain delete error should be nil") - - modelConfigTypes := make(map[string]bool) - for _, modelConfig := range r.ModelConfigs { - modelConfigTypes[modelConfig.Type] = true - err := framework.Global.IstioClientset.Delete(modelConfig.GroupVersionKind(), modelConfig.Name, modelConfig.Namespace) - assert.Nil(t, err, "istio custom resource delete error should be nil") - } - - namespace := athenz.DomainToNamespace(r.AD.Name) - for _, modelConfigType := range r.ModelConfigs { - list, err := framework.Global.IstioClientset.List(modelConfigType.GroupVersionKind(), namespace) - assert.Nil(t, err, "istio custom resource list error should be nil") - assert.Empty(t, list, "all configs must be deleted, list not empty") - } -} - -// 1.0 Create SR / SRB with valid AD -func TestCreateServiceRoleAndBinding(t *testing.T) { - r := fixtures.GetExpectedRbac(nil) - rolloutAndValidateRbac(t, r, create) - cleanupRbac(t, r) -} - -// 1.1 Create SR and SRB with role cert spiffe only if there are no role members -func TestCreateServiceRoleAndBindingsWhenNoMembersInRole(t *testing.T) { - o := &fixtures.OverrideRbac{ - ModifyAD: func(signedDomain *zms.SignedDomain) { - signedDomain.Domain.Roles[0].RoleMembers = []*zms.RoleMember{} - signedDomain.Domain.Roles[0].Members = []zms.MemberName{} - }, - } - r := fixtures.GetExpectedRbac(o) - rolloutAndValidateRbac(t, r, create) - cleanupRbac(t, r) -} - -// 2.0 Update existing domain with new role / policy -func TestUpdateRoleAndPolicy(t *testing.T) { - r := fixtures.GetExpectedRbac(nil) - rolloutAndValidateRbac(t, r, create) - - o := &fixtures.OverrideRbac{ - ModifyAD: func(signedDomain *zms.SignedDomain) { - allow := zms.ALLOW - domainName := "athenz.domain" - policy := &zms.Policy{ - Assertions: []*zms.Assertion{ - { - Effect: &allow, - Action: "GET", - Role: domainName + ":role.client-reader-role", - Resource: domainName + ":svc.my-service-name", - }, - }, - Name: zms.ResourceName(domainName + ":policy.admin"), - } - signedDomain.Domain.Policies.Contents.Policies = append(signedDomain.Domain.Policies.Contents.Policies, policy) - role := &zms.Role{ - Members: []zms.MemberName{zms.MemberName("user.bar")}, - Name: zms.ResourceName(domainName + ":role.client-reader-role"), - RoleMembers: []*zms.RoleMember{ - { - MemberName: zms.MemberName("user.bar"), - }, - }, - } - signedDomain.Domain.Roles = append(signedDomain.Domain.Roles, role) - }, - ModifySRAndSRBPair: []func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding){ - func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding) { - }, - func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding) { - sr.Rules = []*v1alpha1.AccessRule{ - { - Methods: []string{ - "GET", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-service-name", - }, - }, - }, - }, - } - srb.Subjects = []*v1alpha1.Subject{ - { - User: "user/sa/bar", - }, - { - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "user.bar", - }, - }, - } - }, - }, - } - - r = fixtures.GetExpectedRbac(o) - rolloutAndValidateRbac(t, r, update) - cleanupRbac(t, r) -} - -// 2.1 Update existing assertion / role member / action -func TestUpdateAssertionActionAndRoleMember(t *testing.T) { - r := fixtures.GetExpectedRbac(nil) - rolloutAndValidateRbac(t, r, create) - - o := &fixtures.OverrideRbac{ - ModifyAD: func(signedDomain *zms.SignedDomain) { - allow := zms.ALLOW - domainName := "athenz.domain" - policy := &zms.Policy{ - Assertions: []*zms.Assertion{ - { - Effect: &allow, - Action: "POST", - Role: domainName + ":role.client-writer-role", - Resource: domainName + ":svc.my-service-name-two", - }, - }, - Name: zms.ResourceName(domainName + ":policy.admin"), - } - signedDomain.Domain.Policies.Contents.Policies = []*zms.Policy{policy} - - roleMember := &zms.RoleMember{ - MemberName: zms.MemberName("user.bar"), - } - signedDomain.Domain.Roles[0].RoleMembers = []*zms.RoleMember{roleMember} - signedDomain.Domain.Roles[0].Members = []zms.MemberName{zms.MemberName("user.bar")} - }, - ModifySRAndSRBPair: []func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding){ - func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding) { - sr.Rules = []*v1alpha1.AccessRule{ - { - Methods: []string{ - "POST", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-service-name-two", - }, - }, - }, - }, - } - srb.Subjects = []*v1alpha1.Subject{ - { - User: "user/sa/bar", - }, - { - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "user.bar", - }, - }, - } - }, - }, - } - - r = fixtures.GetExpectedRbac(o) - rolloutAndValidateRbac(t, r, update) - cleanupRbac(t, r) -} - -// 2.2 Delete existing roleMember / assertion -func TestUpdateDeleteRoleMemberAndAssertion(t *testing.T) { - o := &fixtures.OverrideRbac{ - ModifyAD: func(signedDomain *zms.SignedDomain) { - allow := zms.ALLOW - domainName := "athenz.domain" - policy := &zms.Policy{ - Assertions: []*zms.Assertion{ - { - Effect: &allow, - Action: "GET", - Role: domainName + ":role.client-writer-role", - Resource: domainName + ":svc.my-service-name", - }, - }, - Name: zms.ResourceName(domainName + ":policy.admin"), - } - signedDomain.Domain.Policies.Contents.Policies = append(signedDomain.Domain.Policies.Contents.Policies, policy) - - roleMember := &zms.RoleMember{ - MemberName: zms.MemberName("user.bar"), - } - signedDomain.Domain.Roles[0].RoleMembers = append(signedDomain.Domain.Roles[0].RoleMembers, roleMember) - }, - ModifySRAndSRBPair: []func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding){ - func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding) { - sr.Rules = append(sr.Rules, &v1alpha1.AccessRule{ - Methods: []string{ - "GET", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-service-name", - }, - }, - }, - }) - srb.Subjects = append(srb.Subjects, &v1alpha1.Subject{ - User: "user/sa/bar", - }, &v1alpha1.Subject{ - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "user.bar", - }, - }) - }, - }, - } - - r := fixtures.GetExpectedRbac(o) - rolloutAndValidateRbac(t, r, create) - - r = fixtures.GetExpectedRbac(nil) - rolloutAndValidateRbac(t, r, update) - cleanupRbac(t, r) -} - -// 2.3 Add unrelated AD changes (not conformant to RBAC) -func TestUpdateUnrelatedAthenzDomainField(t *testing.T) { - r := fixtures.GetExpectedRbac(nil) - rolloutAndValidateRbac(t, r, create) - - o := &fixtures.OverrideRbac{ - ModifyAD: func(signedDomain *zms.SignedDomain) { - signedDomain.Domain.Policies.KeyId = "col-env-1.2" - signedDomain.KeyId = "col-env-1.2" - }, - } - - updatedR := fixtures.GetExpectedRbac(o) - rolloutAndValidateRbac(t, updatedR, update) - cleanupRbac(t, r) -} - -// 2.4 Update role name and expect the old SR/SRB to be deleted -func TestUpdateRoleName(t *testing.T) { - r := fixtures.GetExpectedRbac(nil) - rolloutAndValidateRbac(t, r, create) - - o := &fixtures.OverrideRbac{ - ModifyAD: func(signedDomain *zms.SignedDomain) { - signedDomain.Domain.Policies.Contents.Policies[0].Assertions[0].Role = "athenz.domain:role.client-reader-role" - signedDomain.Domain.Roles[0].Name = "athenz.domain:role.client-reader-role" - }, - } - - updatedR := fixtures.GetExpectedRbac(o) - rolloutAndValidateRbac(t, updatedR, update) - for _, config := range r.ModelConfigs { - c := framework.Global.IstioClientset.Get(config.GroupVersionKind(), config.Name, config.Namespace) - assert.Nil(t, c, "istio custom resource get should return nil") - } - cleanupRbac(t, updatedR) -} - -// 2.5 Test updates with multiple namespaces / AD -func TestMultipleAthenzDomain(t *testing.T) { - rOne := fixtures.GetExpectedRbac(nil) - rolloutAndValidateRbac(t, rOne, create) - - oTwo := &fixtures.OverrideRbac{ - ModifyAD: func(signedDomain *zms.SignedDomain) { - signedDomain.Domain.Name = "athenz.domain.one" - signedDomain.Domain.Policies.Contents.Policies[0].Assertions[0].Role = "athenz.domain.one:role.client-reader-role" - signedDomain.Domain.Policies.Contents.Policies[0].Assertions[0].Resource = "athenz.domain.one:svc.my-service-name" - signedDomain.Domain.Roles[0].Name = "athenz.domain.one:role.client-reader-role" - }, - } - - rTwo := fixtures.GetExpectedRbac(oTwo) - rolloutAndValidateRbac(t, rTwo, create) - - oThree := &fixtures.OverrideRbac{ - ModifyAD: func(signedDomain *zms.SignedDomain) { - signedDomain.Domain.Name = "athenz.domain.two" - signedDomain.Domain.Policies.Contents.Policies[0].Assertions[0].Role = "athenz.domain.two:role.client-reader-role" - signedDomain.Domain.Policies.Contents.Policies[0].Assertions[0].Resource = "athenz.domain.two:svc.my-service-name" - signedDomain.Domain.Roles[0].Name = "athenz.domain.two:role.client-reader-role" - }, - } - - rThree := fixtures.GetExpectedRbac(oThree) - rolloutAndValidateRbac(t, rThree, create) - - oOne := &fixtures.OverrideRbac{ - ModifyAD: func(signedDomain *zms.SignedDomain) { - allow := zms.ALLOW - domainName := "athenz.domain" - policy := &zms.Policy{ - Assertions: []*zms.Assertion{ - { - Effect: &allow, - Action: "GET", - Role: domainName + ":role.client-writer-role", - Resource: domainName + ":svc.my-service-name", - }, - }, - Name: zms.ResourceName(domainName + ":policy.admin"), - } - signedDomain.Domain.Policies.Contents.Policies = append(signedDomain.Domain.Policies.Contents.Policies, policy) - - roleMember := &zms.RoleMember{ - MemberName: zms.MemberName("user.bar"), - } - signedDomain.Domain.Roles[0].RoleMembers = append(signedDomain.Domain.Roles[0].RoleMembers, roleMember) - }, - ModifySRAndSRBPair: []func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding){ - func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding) { - sr.Rules = append(sr.Rules, &v1alpha1.AccessRule{ - Methods: []string{ - "GET", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-service-name", - }, - }, - }, - }) - srb.Subjects = append(srb.Subjects, &v1alpha1.Subject{ - User: "user/sa/bar", - }, &v1alpha1.Subject{ - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "user.bar", - }, - }) - }, - }, - } - - rOne = fixtures.GetExpectedRbac(oOne) - rolloutAndValidateRbac(t, rOne, update) - - cleanupRbac(t, rOne) - cleanupRbac(t, rTwo) - cleanupRbac(t, rThree) -} - -// 3.0 Delete athenz domain -// TODO: currently the controller does not delete the service role / binding -// due to checking for the existence of the athenz domain. This test assumes -// these still exist. Update this test once the controller core logic changes. -func TestAthenzDomainDelete(t *testing.T) { - r := fixtures.GetExpectedRbac(nil) - rolloutAndValidateRbac(t, r, create) - rolloutAndValidateRbac(t, r, delete) -} - -// 3.1 Delete SR / SRB if AD still exists, expect the controller to sync it back -func TestDeleteSRAndSRB(t *testing.T) { - r := fixtures.GetExpectedRbac(nil) - rolloutAndValidateRbac(t, r, create) - - for _, curr := range r.ModelConfigs { - err := framework.Global.IstioClientset.Delete(curr.GroupVersionKind(), curr.Name, curr.Namespace) - assert.Nil(t, err, "istio custom resource delete error should be nil") - } - - rolloutAndValidateRbac(t, r, noop) - cleanupRbac(t, r) -} diff --git a/test/integration/authz_v2_test.go b/test/integration/authz_v2_test.go index ac88a322..3a7b1183 100644 --- a/test/integration/authz_v2_test.go +++ b/test/integration/authz_v2_test.go @@ -18,6 +18,15 @@ import ( "k8s.io/apimachinery/pkg/util/wait" ) +type action int + +const ( + create action = iota + update + delete + noop +) + // rolloutAndValidateAuthorizationPolicyScenario will perform the specified actions for the Athenz Domain and k8s services // and then validate that the AuthroizationPolicy's Spec created is same as the expected func rolloutAndValidateAuthorizationPolicyScenario(t *testing.T, e *fixtures.ExpectedV2Rbac, athenzAction action, serviceAction action) { diff --git a/test/integration/fixtures/fixtures.go b/test/integration/fixtures/fixtures.go index aafcc8a4..0d629d61 100644 --- a/test/integration/fixtures/fixtures.go +++ b/test/integration/fixtures/fixtures.go @@ -5,21 +5,15 @@ package fixtures import ( - "fmt" - "strings" - "github.com/ardielle/ardielle-go/rdl" "github.com/yahoo/athenz/clients/go/zms" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/athenz" "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/common" - "istio.io/api/rbac/v1alpha1" + athenzdomain "github.com/yahoo/k8s-athenz-syncer/pkg/apis/athenz/v1" securityV1beta1 "istio.io/api/security/v1beta1" istioTypeV1beta1 "istio.io/api/type/v1beta1" "istio.io/istio/pilot/pkg/model" "istio.io/istio/pkg/config/schema/collections" v1 "k8s.io/api/core/v1" - - athenzdomain "github.com/yahoo/k8s-athenz-syncer/pkg/apis/athenz/v1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -225,77 +219,6 @@ func CreateNamespaces(clientset kubernetes.Interface) error { return nil } -type ExpectedRbac struct { - AD *athenzdomain.AthenzDomain - ModelConfigs []model.Config -} - -type OverrideRbac struct { - ModifyAD func(signedDomain *zms.SignedDomain) - ModifySRAndSRBPair []func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding) -} - -// GetExpectedRbac returns an expected resources object which contains the -// athenz domain along with its service roles / bindings objects -func GetExpectedRbac(o *OverrideRbac) *ExpectedRbac { - signedDomain := getDefaultSignedDomain() - - if o == nil { - o = &OverrideRbac{} - } - - if o.ModifyAD != nil { - o.ModifyAD(&signedDomain) - } - - domainName := string(signedDomain.Domain.Name) - ns := athenz.DomainToNamespace(domainName) - - ad := &athenzdomain.AthenzDomain{ - ObjectMeta: metav1.ObjectMeta{ - Name: domainName, - }, - Spec: athenzdomain.AthenzDomainSpec{ - SignedDomain: signedDomain, - }, - } - - if len(o.ModifySRAndSRBPair) == 0 { - o.ModifySRAndSRBPair = []func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding){ - func(sr *v1alpha1.ServiceRole, srb *v1alpha1.ServiceRoleBinding) { - }, - } - } - - var modelConfig []model.Config - for i, fn := range o.ModifySRAndSRBPair { - srSpec := getDefaultServiceRole() - srbSpec := getDefaultServiceRoleBinding() - fn(srSpec, srbSpec) - - roleFQDN := string(signedDomain.Domain.Roles[i].Name) - roleName := strings.TrimPrefix(roleFQDN, fmt.Sprintf("%s:role.", domainName)) - sr := common.NewConfig(collections.IstioRbacV1Alpha1Serviceroles, ns, roleName, srSpec) - modelConfig = append(modelConfig, sr) - - // Every Role has SRB with Spiffe URI - if len(signedDomain.Domain.Roles[i].RoleMembers) == 0 { - srbSpec.Subjects = []*v1alpha1.Subject{} - } - srbSpec.Subjects = append(srbSpec.Subjects, &v1alpha1.Subject{ - User: fmt.Sprintf("%s/ra/%s", signedDomain.Domain.Name, sr.Name), - }) - srbSpec.RoleRef.Name = sr.Name - srb := common.NewConfig(collections.IstioRbacV1Alpha1Servicerolebindings, ns, roleName, srbSpec) - modelConfig = append(modelConfig, srb) - } - - return &ExpectedRbac{ - AD: ad, - ModelConfigs: modelConfig, - } -} - // getDefaultSignedDomain returns a default testing spec for a signed domain object func getDefaultSignedDomain() zms.SignedDomain { allow := zms.ALLOW @@ -349,49 +272,6 @@ func getDefaultSignedDomain() zms.SignedDomain { } } -// getDefaultServiceRole returns a default testing spec for a service role object -func getDefaultServiceRole() *v1alpha1.ServiceRole { - return &v1alpha1.ServiceRole{ - Rules: []*v1alpha1.AccessRule{ - { - Methods: []string{ - "PUT", - }, - Services: []string{common.WildCardAll}, - Constraints: []*v1alpha1.AccessRule_Constraint{ - { - Key: common.ConstraintSvcKey, - Values: []string{ - "my-service-name", - }, - }, - }, - }, - }, - } -} - -// getDefaultServiceRoleBinding returns a default testing spec for a service -// role binding object -func getDefaultServiceRoleBinding() *v1alpha1.ServiceRoleBinding { - return &v1alpha1.ServiceRoleBinding{ - RoleRef: &v1alpha1.RoleRef{ - Name: "client-writer-role", - Kind: common.ServiceRoleKind, - }, - Subjects: []*v1alpha1.Subject{ - { - User: "user/sa/foo", - }, - { - Properties: map[string]string{ - common.RequestAuthPrincipalProperty: common.AthenzJwtPrefix + "user.foo", - }, - }, - }, - } -} - type ExpectedServices struct { Services []*v1.Service ServiceDNS []string diff --git a/test/integration/framework/framework.go b/test/integration/framework/framework.go index d3a48f17..a92f82f7 100644 --- a/test/integration/framework/framework.go +++ b/test/integration/framework/framework.go @@ -11,23 +11,27 @@ import ( "os" "time" - versionedclient "istio.io/client-go/pkg/clientset/versioned" - "istio.io/istio/pkg/config/schema/collection" - "istio.io/istio/pkg/config/schema/collections" - "istio.io/pkg/ledger" - apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - - "github.com/yahoo/k8s-athenz-istio-auth/pkg/controller" - "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/common" "github.com/yahoo/k8s-athenz-istio-auth/pkg/log" "github.com/yahoo/k8s-athenz-istio-auth/test/integration/fixtures" + adInformer "github.com/yahoo/k8s-athenz-syncer/pkg/client/informers/externalversions/athenz/v1" "go.etcd.io/etcd/embed" + versionedclient "istio.io/client-go/pkg/clientset/versioned" crd "istio.io/istio/pilot/pkg/config/kube/crd/controller" + istioController "istio.io/istio/pilot/pkg/serviceregistry/kube/controller" + "istio.io/istio/pkg/config/schema/collection" + "istio.io/istio/pkg/config/schema/collections" + apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + authzpolicy "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/authorizationpolicy" + "github.com/yahoo/k8s-athenz-istio-auth/pkg/istio/rbac/common" athenzdomainclientset "github.com/yahoo/k8s-athenz-syncer/pkg/client/clientset/versioned" + "istio.io/pkg/ledger" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/client-go/tools/cache" "k8s.io/kubernetes/cmd/kube-apiserver/app" "k8s.io/kubernetes/cmd/kube-apiserver/app/options" ) @@ -38,7 +42,7 @@ type Framework struct { K8sClientset kubernetes.Interface AthenzDomainClientset athenzdomainclientset.Interface IstioClientset *crd.Client - Controller *controller.Controller + Controller *authzpolicy.Controller etcd *embed.Etcd stopCh chan struct{} } @@ -100,6 +104,9 @@ func Setup() error { if !flag.Parsed() { flag.Parse() } + enableOriginJwtSubject := true + combinationPolicyTag := "" + enableSpiffeTrustDomain := true etcd, err := runEtcd() if err != nil { @@ -136,7 +143,7 @@ func Setup() error { return err } - configDescriptor := collection.SchemasFor(collections.IstioRbacV1Alpha1Serviceroles, collections.IstioRbacV1Alpha1Clusterrbacconfigs, collections.IstioRbacV1Alpha1Servicerolebindings, collections.IstioSecurityV1Beta1Authorizationpolicies) + configDescriptor := collection.SchemasFor(collections.IstioSecurityV1Beta1Authorizationpolicies) ledgerValue := ledger.Make(time.Hour) istioClient, err := crd.NewClient("", "", configDescriptor, "", ledgerValue, "") if err != nil { @@ -144,7 +151,7 @@ func Setup() error { } log.InitLogger("", "debug") - componentsEnabled, err := common.ParseComponentsEnabledAuthzPolicy("*") + componentsEnabledAuthzPolicy, err := common.ParseComponentsEnabledAuthzPolicy("*") if err != nil { return err } @@ -154,14 +161,19 @@ func Setup() error { return err } - c := controller.NewController("svc.cluster.local", istioClient, k8sClientset, athenzDomainClientset, istioClientSet, time.Minute, time.Minute, time.Minute, true, true, componentsEnabled, "proxy-principals", true, []string{"istio-system", "kube-yahoo"}, map[string]string{"istio-ingressgateway": "istio-system"}, []string{"k8s.omega.stage"}) - go c.Run(stopCh) + configStoreCache := crd.NewController(istioClient, istioController.Options{}) + serviceListWatch := cache.NewListWatchFromClient(k8sClientset.CoreV1().RESTClient(), "services", v1.NamespaceAll, fields.Everything()) + serviceIndexInformer := cache.NewSharedIndexInformer(serviceListWatch, &v1.Service{}, 0, nil) + adIndexInformer := adInformer.NewAthenzDomainInformer(athenzDomainClientset, 0, cache.Indexers{}) + + apController := authzpolicy.NewController(configStoreCache, serviceIndexInformer, adIndexInformer, istioClientSet, time.Minute, enableOriginJwtSubject, componentsEnabledAuthzPolicy, combinationPolicyTag, enableSpiffeTrustDomain, []string{"istio-system", "kube-yahoo"}, map[string]string{"istio-ingressgateway": "istio-system"}, []string{"k8s.omega.stage"}) + go apController.Run(stopCh) Global = &Framework{ K8sClientset: k8sClientset, AthenzDomainClientset: athenzDomainClientset, IstioClientset: istioClient, - Controller: c, + Controller: apController, etcd: etcd, stopCh: stopCh, } diff --git a/test/integration/onboarding_test.go b/test/integration/onboarding_test.go deleted file mode 100644 index 370cc672..00000000 --- a/test/integration/onboarding_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package integration - -import ( - "istio.io/istio/pkg/config/schema/collections" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/yahoo/k8s-athenz-istio-auth/test/integration/fixtures" - "github.com/yahoo/k8s-athenz-istio-auth/test/integration/framework" - - "istio.io/api/rbac/v1alpha1" - "istio.io/istio/pkg/config/constants" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" -) - -// rolloutAndValidateOnboarding will create / update / delete / noop the service resource and wait for the -// associated cluster rbac config to be created. It will then be validated against the -// expected output. -func rolloutAndValidateOnboarding(t *testing.T, s *fixtures.ExpectedServices, a action) { - switch a { - case create: - createServices(t, s) - case update: - updateServices(t, s) - case delete: - deleteServices(t, s) - } - - err := wait.PollImmediate(time.Second, time.Second*10, func() (bool, error) { - config := framework.Global.IstioClientset.Get(collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().GroupVersionKind(), constants.DefaultRbacConfigName, "") - if config == nil && len(s.ServiceDNS) == 0 { - return true, nil - } else if config == nil { - return false, nil - } - - clusterRbacConfig, ok := config.Spec.(*v1alpha1.RbacConfig) - if !ok { - return false, nil - } - - if len(clusterRbacConfig.Inclusion.Services) == len(s.ServiceDNS) { - return true, nil - } - - return false, nil - }) - - assert.Nil(t, err, "time out waiting for rollout for crc with error") - - crc := framework.Global.IstioClientset.Get(collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().GroupVersionKind(), constants.DefaultRbacConfigName, "") - if crc == nil && len(s.ServiceDNS) == 0 { - return - } - - clusterRbacConfig, ok := crc.Spec.(*v1alpha1.RbacConfig) - assert.True(t, ok, "cluster rbac config cast should pass") - assert.Equal(t, clusterRbacConfig.Mode, v1alpha1.RbacConfig_ON_WITH_INCLUSION, "cluster rbac config inclusion field should be set") - assert.Nil(t, clusterRbacConfig.Exclusion, "cluster rbac config exclusion field should be nil") - assert.ElementsMatch(t, s.ServiceDNS, clusterRbacConfig.Inclusion.Services, "cluster rbac config service list should be equal to expected") -} - -// createServices will iterate through the service list and create each object -func createServices(t *testing.T, services *fixtures.ExpectedServices) { - for _, service := range services.Services { - _, err := framework.Global.K8sClientset.CoreV1().Services(service.Namespace).Create(service) - assert.Nil(t, err, "service create error should be nil") - } -} - -// updateServices will iterate through the service list and update each object -func updateServices(t *testing.T, services *fixtures.ExpectedServices) { - for _, service := range services.Services { - currentService, err := framework.Global.K8sClientset.CoreV1().Services(service.Namespace).Get(service.Name, metav1.GetOptions{}) - assert.Nil(t, err, "service get error should be nil") - service.ResourceVersion = currentService.ResourceVersion - service.Spec.ClusterIP = currentService.Spec.ClusterIP - _, err = framework.Global.K8sClientset.CoreV1().Services(service.Namespace).Update(service) - assert.Nil(t, err, "service update error should be nil") - } -} - -// deleteServices will iterate through the service list and delete each object -func deleteServices(t *testing.T, s *fixtures.ExpectedServices) { - for _, service := range s.Services { - err := framework.Global.K8sClientset.CoreV1().Services(service.Namespace).Delete(service.Name, &metav1.DeleteOptions{}) - assert.Nil(t, err, "service delete error should be nil") - } - s.Services = []*v1.Service{} - s.ServiceDNS = []string{} -} - -// 1.0 Create CRC with valid service annotation -func TestCreateCRC(t *testing.T) { - s := fixtures.GetExpectedServices(nil) - rolloutAndValidateOnboarding(t, s, create) - deleteServices(t, s) -} - -// 2.0 Update CRC with new service -func TestUpdateCRC(t *testing.T) { - o := []func(*v1.Service){ - func(s *v1.Service) { - }, - } - - sOne := fixtures.GetExpectedServices(o) - rolloutAndValidateOnboarding(t, sOne, create) - - o = []func(*v1.Service){ - func(s *v1.Service) { - s.Name = "test-service-two" - }, - } - - sTwo := fixtures.GetExpectedServices(o) - sTwo.ServiceDNS = append(sTwo.ServiceDNS, sOne.ServiceDNS...) - rolloutAndValidateOnboarding(t, sTwo, create) - - deleteServices(t, sOne) - deleteServices(t, sTwo) -} - -// 2.1 Test services in different namespace -func TestMultipleServices(t *testing.T) { - o := []func(*v1.Service){ - func(s *v1.Service) { - }, - func(s *v1.Service) { - s.Name = "test-service-two" - s.Namespace = "athenz-domain-one" - }, - func(s *v1.Service) { - s.Name = "test-service-three" - s.Namespace = "athenz-domain-two" - }, - } - - s := fixtures.GetExpectedServices(o) - rolloutAndValidateOnboarding(t, s, create) - deleteServices(t, s) -} - -// 2.2 Test enable/disable annotation combinations -func TestEnableDisableAnnotation(t *testing.T) { - o := []func(*v1.Service){ - func(s *v1.Service) { - }, - func(s *v1.Service) { - s.Name = "test-service-two" - s.Namespace = "athenz-domain-one" - s.Annotations = make(map[string]string) - }, - } - s := fixtures.GetExpectedServices(o) - rolloutAndValidateOnboarding(t, s, create) - - o = []func(*v1.Service){ - func(s *v1.Service) { - }, - func(s *v1.Service) { - s.Name = "test-service-two" - s.Namespace = "athenz-domain-one" - }, - } - s = fixtures.GetExpectedServices(o) - rolloutAndValidateOnboarding(t, s, update) - deleteServices(t, s) -} - -// 3.0 Delete crc if there are no more onboarded services -func TestDeleteCRC(t *testing.T) { - s := fixtures.GetExpectedServices(nil) - rolloutAndValidateOnboarding(t, s, create) - rolloutAndValidateOnboarding(t, s, delete) -} - -// 3.1 Delete CRC if onboarded services still exist, expect the controller to sync it back -func TestDeleteCRCIfServiceExists(t *testing.T) { - s := fixtures.GetExpectedServices(nil) - rolloutAndValidateOnboarding(t, s, create) - - err := framework.Global.IstioClientset.Delete(collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().GroupVersionKind(), constants.DefaultRbacConfigName, "") - assert.Nil(t, err, "cluster rbac config delete error should be nil") - - rolloutAndValidateOnboarding(t, s, noop) - deleteServices(t, s) -}