diff --git a/README.md b/README.md index 1f4d07f87..50157ece2 100644 --- a/README.md +++ b/README.md @@ -29,46 +29,117 @@ Pick one from the [Releases](https://github.com/up9inc/mizu/releases) page. ## Prerequisites 1. Set `KUBECONFIG` environment variable to your kubernetes configuration. If this is not set, mizu assumes that configuration is at `${HOME}/.kube/config` 2. mizu needs following permissions on your kubernetes cluster to run -``` + +```yaml - apiGroups: - "" - - apps resources: - pods - - services verbs: - list - - get + - watch - create - - delete - apiGroups: - "" + resources: + - services + verbs: + - create +- apiGroups: - apps resources: - daemonsets verbs: - - list - - get - create - patch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - list + - watch + - create - delete +- apiGroups: + - "" + resources: + - services/proxy + verbs: + - get ``` 3. Optionally, for resolving traffic ip to kubernetes service name, mizu needs below permissions -``` + +```yaml +- apiGroups: + - "" + resources: + - pods + verbs: + - get +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +- apiGroups: + - apps + - extensions + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - apps + - extensions + resources: + - services + verbs: + - get + - list + - watch - apiGroups: - "" - apps - - "rbac.authorization.k8s.io" + - extensions + resources: + - endpoints + verbs: + - get + - list + - watch +- apiGroups: + - "" resources: - - clusterroles - - clusterrolebindings - serviceaccounts verbs: - get - create +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterroles + verbs: + - list + - create + - delete +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + verbs: + - list + - create - delete ``` +See `examples/roles` for example `clusterroles`. + ## How to run 1. Find pod you'd like to tap to in your Kubernetes cluster diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 000000000..ba077a403 --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1 @@ +bin diff --git a/cli/cmd/tapRunner.go b/cli/cmd/tapRunner.go index dff90fbb8..709cee3db 100644 --- a/cli/cmd/tapRunner.go +++ b/cli/cmd/tapRunner.go @@ -9,6 +9,7 @@ import ( "github.com/up9inc/mizu/shared" "github.com/up9inc/mizu/shared/debounce" core "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/wait" "log" "net/http" "net/url" @@ -24,6 +25,7 @@ var aggregatorService *core.Service const ( updateTappersDelay = 5 * time.Second + cleanupTimeout = time.Minute ) var currentlyTappedPods []core.Pod @@ -42,6 +44,7 @@ func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) { targetNamespace := getNamespace(tappingOptions, kubernetesProvider) if matchingPods, err := kubernetesProvider.GetAllPodsMatchingRegex(ctx, podRegexQuery, targetNamespace); err != nil { + fmt.Printf("Error listing pods: %v", err) return } else { currentlyTappedPods = matchingPods @@ -82,6 +85,10 @@ func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) { } func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, tappingOptions *MizuTapOptions, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error { + if err := createMizuNamespace(ctx, kubernetesProvider); err != nil { + return err + } + if err := createMizuAggregator(ctx, kubernetesProvider, tappingOptions, mizuApiFilteringOptions); err != nil { return err } @@ -93,11 +100,26 @@ func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Pro return nil } +func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Provider) error { + _, err := kubernetesProvider.CreateNamespace(ctx, mizu.ResourcesNamespace) + if err != nil { + fmt.Printf("Error creating Namespace %s: %v\n", mizu.ResourcesNamespace, err) + } + + return err +} + func createMizuAggregator(ctx context.Context, kubernetesProvider *kubernetes.Provider, tappingOptions *MizuTapOptions, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error { var err error mizuServiceAccountExists = createRBACIfNecessary(ctx, kubernetesProvider) - _, err = kubernetesProvider.CreateMizuAggregatorPod(ctx, mizu.ResourcesNamespace, mizu.AggregatorPodName, tappingOptions.MizuImage, mizuServiceAccountExists, mizuApiFilteringOptions, tappingOptions.MaxEntriesDBSizeBytes) + var serviceAccountName string + if mizuServiceAccountExists { + serviceAccountName = mizu.ServiceAccountName + } else { + serviceAccountName = "" + } + _, err = kubernetesProvider.CreateMizuAggregatorPod(ctx, mizu.ResourcesNamespace, mizu.AggregatorPodName, tappingOptions.MizuImage, serviceAccountName, mizuApiFilteringOptions, tappingOptions.MaxEntriesDBSizeBytes) if err != nil { fmt.Printf("Error creating mizu collector pod: %v\n", err) return err @@ -132,6 +154,13 @@ func getMizuApiFilteringOptions(tappingOptions *MizuTapOptions) (*shared.Traffic func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, tappingOptions *MizuTapOptions) error { if len(nodeToTappedPodIPMap) > 0 { + var serviceAccountName string + if mizuServiceAccountExists { + serviceAccountName = mizu.ServiceAccountName + } else { + serviceAccountName = "" + } + if err := kubernetesProvider.ApplyMizuTapperDaemonSet( ctx, mizu.ResourcesNamespace, @@ -140,7 +169,7 @@ func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provi mizu.TapperPodName, fmt.Sprintf("%s.%s.svc.cluster.local", aggregatorService.Name, aggregatorService.Namespace), nodeToTappedPodIPMap, - mizuServiceAccountExists, + serviceAccountName, tappingOptions.TapOutgoing, ); err != nil { fmt.Printf("Error creating mizu tapper daemonset: %v\n", err) @@ -159,15 +188,35 @@ func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provi func cleanUpMizuResources(kubernetesProvider *kubernetes.Provider) { fmt.Printf("\nRemoving mizu resources\n") - removalCtx, _ := context.WithTimeout(context.Background(), 5*time.Second) - if err := kubernetesProvider.RemovePod(removalCtx, mizu.ResourcesNamespace, mizu.AggregatorPodName); err != nil { - fmt.Printf("Error removing Pod %s in namespace %s: %s (%v,%+v)\n", mizu.AggregatorPodName, mizu.ResourcesNamespace, err, err, err) + removalCtx, cancel := context.WithTimeout(context.Background(), cleanupTimeout) + defer cancel() + + if err := kubernetesProvider.RemoveNamespace(removalCtx, mizu.ResourcesNamespace); err != nil { + fmt.Printf("Error removing Namespace %s: %s (%v,%+v)\n", mizu.ResourcesNamespace, err, err, err) + return } - if err := kubernetesProvider.RemoveService(removalCtx, mizu.ResourcesNamespace, mizu.AggregatorPodName); err != nil { - fmt.Printf("Error removing Service %s in namespace %s: %s (%v,%+v)\n", mizu.AggregatorPodName, mizu.ResourcesNamespace, err, err, err) + + if mizuServiceAccountExists { + if err := kubernetesProvider.RemoveNonNamespacedResources(removalCtx, mizu.ClusterRoleName, mizu.ClusterRoleBindingName); err != nil { + fmt.Printf("Error removing non-namespaced resources: %s (%v,%+v)\n", err, err, err) + return + } } - if err := kubernetesProvider.RemoveDaemonSet(removalCtx, mizu.ResourcesNamespace, mizu.TapperDaemonSetName); err != nil { - fmt.Printf("Error removing DaemonSet %s in namespace %s: %s (%v,%+v)\n", mizu.TapperDaemonSetName, mizu.ResourcesNamespace, err, err, err) + + // Call cancel if a terminating signal was received. Allows user to skip the wait. + go func() { + waitForFinish(removalCtx, cancel) + }() + + if err := kubernetesProvider.WaitUtilNamespaceDeleted(removalCtx, mizu.ResourcesNamespace); err != nil { + switch { + case removalCtx.Err() == context.Canceled: + // Do nothing. User interrupted the wait. + case err == wait.ErrWaitTimeout: + fmt.Printf("Timeout while removing Namespace %s\n", mizu.ResourcesNamespace) + default: + fmt.Printf("Error while waiting for Namespace %s to be deleted: %s (%v,%+v)\n", mizu.ResourcesNamespace, err, err, err) + } } } @@ -234,6 +283,9 @@ func portForwardApiPod(ctx context.Context, kubernetesProvider *kubernetes.Provi timeAfter := time.After(25 * time.Second) for { select { + case <-ctx.Done(): + return + case <-added: continue case <-removed: @@ -279,21 +331,18 @@ func portForwardApiPod(ctx context.Context, kubernetesProvider *kubernetes.Provi case <-errorChan: cancel() - - case <-ctx.Done(): - return } } } func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider) bool { - mizuRBACExists, err := kubernetesProvider.DoesMizuRBACExist(ctx, mizu.ResourcesNamespace) + mizuRBACExists, err := kubernetesProvider.DoesServiceAccountExist(ctx, mizu.ResourcesNamespace, mizu.ServiceAccountName) if err != nil { fmt.Printf("warning: could not ensure mizu rbac resources exist %v\n", err) return false } if !mizuRBACExists { - err := kubernetesProvider.CreateMizuRBAC(ctx, mizu.ResourcesNamespace, mizu.RBACVersion) + err := kubernetesProvider.CreateMizuRBAC(ctx, mizu.ResourcesNamespace, mizu.ServiceAccountName, mizu.ClusterRoleName, mizu.ClusterRoleBindingName, mizu.RBACVersion) if err != nil { fmt.Printf("warning: could not create mizu rbac resources %v\n", err) return false diff --git a/cli/go.sum b/cli/go.sum index eef67ad62..de1e9bcd7 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -99,6 +99,7 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= @@ -174,6 +175,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -223,6 +225,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -250,6 +253,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -336,6 +340,7 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -710,6 +715,7 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts= k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= k8s.io/kubectl v0.21.2 h1:9XPCetvOMDqrIZZXb1Ei+g8t6KrIp9ENJaysQjUuLiE= k8s.io/kubectl v0.21.2/go.mod h1:PgeUclpG8VVmmQIl8zpLar3IQEpFc9mrmvlwY3CK1xo= diff --git a/cli/kubernetes/provider.go b/cli/kubernetes/provider.go index 42a157b64..ddf79dffe 100644 --- a/cli/kubernetes/provider.go +++ b/cli/kubernetes/provider.go @@ -17,6 +17,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" resource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/watch" applyconfapp "k8s.io/client-go/applyconfigurations/apps/v1" @@ -28,8 +29,10 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" restclient "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/clientcmd" _ "k8s.io/client-go/tools/portforward" + watchtools "k8s.io/client-go/tools/watch" "k8s.io/client-go/util/homedir" ) @@ -41,7 +44,6 @@ type Provider struct { } const ( - serviceAccountName = "mizu-service-account" fieldManagerName = "mizu-manager" ) @@ -68,6 +70,46 @@ func (provider *Provider) CurrentNamespace() string { return ns } +func (provider *Provider) WaitUtilNamespaceDeleted(ctx context.Context, name string) error { + fieldSelector := fmt.Sprintf("metadata.name=%s", name) + var limit int64 = 1 + lw := &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + options.FieldSelector = fieldSelector + options.Limit = limit + return provider.clientSet.CoreV1().Namespaces().List(ctx, options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + options.FieldSelector = fieldSelector + options.Limit = limit + return provider.clientSet.CoreV1().Namespaces().Watch(ctx, options) + }, + } + + var preconditionFunc watchtools.PreconditionFunc = func(store cache.Store) (bool, error) { + _, exists, err := store.Get(&core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}}) + if err != nil { + return false, err + } + if exists { + return false, nil + } + return true, nil + } + + conditionFunc := func(e watch.Event) (bool, error) { + if e.Type == watch.Deleted { + return true, nil + } + return false, nil + } + + obj := &core.Namespace{} + _, err := watchtools.UntilWithSync(ctx, lw, obj, preconditionFunc, conditionFunc) + + return err +} + func (provider *Provider) GetPodWatcher(ctx context.Context, namespace string) watch.Interface { watcher, err := provider.clientSet.CoreV1().Pods(namespace).Watch(ctx, metav1.ListOptions{Watch: true}) if err != nil { @@ -76,7 +118,16 @@ func (provider *Provider) GetPodWatcher(ctx context.Context, namespace string) w return watcher } -func (provider *Provider) CreateMizuAggregatorPod(ctx context.Context, namespace string, podName string, podImage string, linkServiceAccount bool, mizuApiFilteringOptions *shared.TrafficFilteringOptions, maxEntriesDBSizeBytes int64) (*core.Pod, error) { +func (provider *Provider) CreateNamespace(ctx context.Context, name string) (*core.Namespace, error) { + namespaceSpec := &core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + return provider.clientSet.CoreV1().Namespaces().Create(ctx, namespaceSpec, metav1.CreateOptions{}) +} + +func (provider *Provider) CreateMizuAggregatorPod(ctx context.Context, namespace string, podName string, podImage string, serviceAccountName string, mizuApiFilteringOptions *shared.TrafficFilteringOptions, maxEntriesDBSizeBytes int64) (*core.Pod, error) { marshaledFilteringOptions, err := json.Marshal(mizuApiFilteringOptions) if err != nil { return nil, err @@ -143,7 +194,7 @@ func (provider *Provider) CreateMizuAggregatorPod(ctx context.Context, namespace }, } //define the service account only when it exists to prevent pod crash - if linkServiceAccount { + if serviceAccountName != "" { pod.Spec.ServiceAccountName = serviceAccountName } return provider.clientSet.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{}) @@ -164,7 +215,7 @@ func (provider *Provider) CreateService(ctx context.Context, namespace string, s return provider.clientSet.CoreV1().Services(namespace).Create(ctx, &service, metav1.CreateOptions{}) } -func (provider *Provider) DoesMizuRBACExist(ctx context.Context, namespace string) (bool, error) { +func (provider *Provider) DoesServiceAccountExist(ctx context.Context, namespace string, serviceAccountName string) (bool, error) { serviceAccount, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Get(ctx, serviceAccountName, metav1.GetOptions{}) var statusError *k8serrors.StatusError @@ -195,9 +246,7 @@ func (provider *Provider) DoesServicesExist(ctx context.Context, namespace strin return service != nil, nil } -func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string, version string) error { - clusterRoleName := "mizu-cluster-role" - +func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string, serviceAccountName string, clusterRoleName string, clusterRoleBindingName string, version string) error { serviceAccount := &core.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: serviceAccountName, @@ -220,7 +269,7 @@ func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string, } clusterRoleBinding := &rbac.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: "mizu-cluster-role-binding", + Name: clusterRoleBindingName, Labels: map[string]string{"mizu-cli-version": version}, }, RoleRef: rbac.RoleRef{ @@ -251,6 +300,51 @@ func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string, return nil } +func (provider *Provider) RemoveNamespace(ctx context.Context, name string) error { + if isFound, err := provider.CheckNamespaceExists(ctx, name); + err != nil { + return err + } else if !isFound { + return nil + } + + return provider.clientSet.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{}) +} + +func (provider *Provider) RemoveNonNamespacedResources(ctx context.Context, clusterRoleName string, clusterRoleBindingName string) error { + if err := provider.RemoveClusterRole(ctx, clusterRoleName); err != nil { + return err + } + + if err := provider.RemoveClusterRoleBinding(ctx, clusterRoleBindingName); err != nil { + return err + } + + return nil +} + +func (provider *Provider) RemoveClusterRole(ctx context.Context, name string) error { + if isFound, err := provider.CheckClusterRoleExists(ctx, name); + err != nil { + return err + } else if !isFound { + return nil + } + + return provider.clientSet.RbacV1().ClusterRoles().Delete(ctx, name, metav1.DeleteOptions{}) +} + +func (provider *Provider) RemoveClusterRoleBinding(ctx context.Context, name string) error { + if isFound, err := provider.CheckClusterRoleBindingExists(ctx, name); + err != nil { + return err + } else if !isFound { + return nil + } + + return provider.clientSet.RbacV1().ClusterRoleBindings().Delete(ctx, name, metav1.DeleteOptions{}) +} + func (provider *Provider) RemovePod(ctx context.Context, namespace string, podName string) error { if isFound, err := provider.CheckPodExists(ctx, namespace, podName); err != nil { return err @@ -281,6 +375,57 @@ func (provider *Provider) RemoveDaemonSet(ctx context.Context, namespace string, return provider.clientSet.AppsV1().DaemonSets(namespace).Delete(ctx, daemonSetName, metav1.DeleteOptions{}) } +func (provider *Provider) CheckNamespaceExists(ctx context.Context, name string) (bool, error) { + listOptions := metav1.ListOptions{ + FieldSelector: fmt.Sprintf("metadata.name=%s", name), + Limit: 1, + } + resourceList, err := provider.clientSet.CoreV1().Namespaces().List(ctx, listOptions) + if err != nil { + return false, err + } + + if len(resourceList.Items) > 0 { + return true, nil + } + + return false, nil +} + +func (provider *Provider) CheckClusterRoleExists(ctx context.Context, name string) (bool, error) { + listOptions := metav1.ListOptions{ + FieldSelector: fmt.Sprintf("metadata.name=%s", name), + Limit: 1, + } + resourceList, err := provider.clientSet.RbacV1().ClusterRoles().List(ctx, listOptions) + if err != nil { + return false, err + } + + if len(resourceList.Items) > 0 { + return true, nil + } + + return false, nil +} + +func (provider *Provider) CheckClusterRoleBindingExists(ctx context.Context, name string) (bool, error) { + listOptions := metav1.ListOptions{ + FieldSelector: fmt.Sprintf("metadata.name=%s", name), + Limit: 1, + } + resourceList, err := provider.clientSet.RbacV1().ClusterRoleBindings().List(ctx, listOptions) + if err != nil { + return false, err + } + + if len(resourceList.Items) > 0 { + return true, nil + } + + return false, nil +} + func (provider *Provider) CheckPodExists(ctx context.Context, namespace string, name string) (bool, error) { listOptions := metav1.ListOptions{ FieldSelector: fmt.Sprintf("metadata.name=%s", name), @@ -332,7 +477,7 @@ func (provider *Provider) CheckDaemonSetExists(ctx context.Context, namespace st return false, nil } -func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, aggregatorPodIp string, nodeToTappedPodIPMap map[string][]string, linkServiceAccount bool, tapOutgoing bool) error { +func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, aggregatorPodIp string, nodeToTappedPodIPMap map[string][]string, serviceAccountName string, tapOutgoing bool) error { if len(nodeToTappedPodIPMap) == 0 { return fmt.Errorf("Daemon set %s must tap at least 1 pod", daemonSetName) } @@ -426,7 +571,7 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac podSpec.WithHostNetwork(true) podSpec.WithDNSPolicy(core.DNSClusterFirstWithHostNet) podSpec.WithTerminationGracePeriodSeconds(0) - if linkServiceAccount { + if serviceAccountName != "" { podSpec.WithServiceAccountName(serviceAccountName) } podSpec.WithContainers(agentContainer) diff --git a/cli/mizu/consts.go b/cli/mizu/consts.go index 1f960ead2..45d2a2246 100644 --- a/cli/mizu/consts.go +++ b/cli/mizu/consts.go @@ -9,11 +9,14 @@ var ( ) const ( - ResourcesNamespace = "default" - TapperDaemonSetName = "mizu-tapper-daemon-set" - AggregatorPodName = "mizu-collector" - TapperPodName = "mizu-tapper" - K8sAllNamespaces = "" + AggregatorPodName = "mizu-collector" + ClusterRoleBindingName = "mizu-cluster-role-binding" + ClusterRoleName = "mizu-cluster-role" + K8sAllNamespaces = "" + ResourcesNamespace = "mizu" + ServiceAccountName = "mizu-service-account" + TapperDaemonSetName = "mizu-tapper-daemon-set" + TapperPodName = "mizu-tapper" ) const ( diff --git a/examples/roles/permissions-all-namespaces-without-ip-resolution.yaml b/examples/roles/permissions-all-namespaces-without-ip-resolution.yaml new file mode 100644 index 000000000..3252abb89 --- /dev/null +++ b/examples/roles/permissions-all-namespaces-without-ip-resolution.yaml @@ -0,0 +1,35 @@ +# This example shows the roles required for a user to be able to use Mizu in all namespaces with IP resolution disabled. +# (Traffic will be recorded, but Mizu will not translate IP addresses to names) +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: mizu-runner-clusterrole +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["list", "watch", "create"] +- apiGroups: [""] + resources: ["services"] + verbs: ["create"] +- apiGroups: ["apps"] + resources: ["daemonsets"] + verbs: ["create", "patch"] +- apiGroups: [""] + resources: ["namespaces"] + verbs: ["list", "watch", "create", "delete"] +- apiGroups: [""] + resources: ["services/proxy"] + verbs: ["get"] +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: mizu-runner-clusterrolebindings +subjects: +- kind: User + name: user1 + apiGroup: rbac.authorization.k8s.io +roleRef: + kind: ClusterRole + name: mizu-runner-clusterrole + apiGroup: rbac.authorization.k8s.io diff --git a/examples/roles/permissions-all-namespaces.yaml b/examples/roles/permissions-all-namespaces.yaml new file mode 100644 index 000000000..0f3ba3dff --- /dev/null +++ b/examples/roles/permissions-all-namespaces.yaml @@ -0,0 +1,52 @@ +# This example shows the roles required for a user to be able to use Mizu in all namespaces. +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: mizu-runner-clusterrole +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch", "create"] +- apiGroups: [""] + resources: ["services"] + verbs: ["get", "list", "watch", "create"] +- apiGroups: ["apps"] + resources: ["daemonsets"] + verbs: ["create", "patch"] +- apiGroups: [""] + resources: ["namespaces"] + verbs: ["list", "watch", "create", "delete"] +- apiGroups: [""] + resources: ["services/proxy"] + verbs: ["get"] +- apiGroups: [""] + resources: ["serviceaccounts"] + verbs: ["get", "create"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["clusterroles"] + verbs: ["list", "create", "delete"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["clusterrolebindings"] + verbs: ["list", "create", "delete"] +- apiGroups: ["apps", "extensions"] + resources: ["pods"] + verbs: ["get", "list", "watch"] +- apiGroups: ["apps", "extensions"] + resources: ["services"] + verbs: ["get", "list", "watch"] +- apiGroups: ["", "apps", "extensions"] + resources: ["endpoints"] + verbs: ["get", "list", "watch"] +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: mizu-runner-clusterrolebindings +subjects: +- kind: User + name: user1 + apiGroup: rbac.authorization.k8s.io +roleRef: + kind: ClusterRole + name: mizu-runner-clusterrole + apiGroup: rbac.authorization.k8s.io