Skip to content

Commit

Permalink
Add helm and kustomize RBAC rules equivalence
Browse files Browse the repository at this point in the history
Default operator helm chart templates is compared with kustomize from
redpanda-operator repository. Kustomize could deploy combination of
cluster and namespace scoped operator. There are few exclusions and corss
checks between helm Role and kustomize Cluster role.
  • Loading branch information
RafalKorepta committed Nov 7, 2024
1 parent b56943d commit fc4250f
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 5 deletions.
191 changes: 191 additions & 0 deletions charts/operator/chart_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package operator

import (
"bytes"
"encoding/json"
"fmt"
"os"
Expand All @@ -11,18 +12,153 @@ import (

fuzz "github.com/google/gofuzz"
"github.com/redpanda-data/helm-charts/pkg/helm"
"github.com/redpanda-data/helm-charts/pkg/kube"
"github.com/redpanda-data/helm-charts/pkg/testutil"
"github.com/santhosh-tekuri/jsonschema/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/tools/txtar"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/ptr"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/kustomize/v5/commands/build"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/yaml"
)

func TestHelmKustomizeEquivalence(t *testing.T) {
ctx := testutil.Context(t)
client, err := helm.New(helm.Options{ConfigHome: testutil.TempDir(t)})
require.NoError(t, err)

values := PartialValues{FullnameOverride: ptr.To("redpanda")}

rendered, err := client.Template(ctx, ".", helm.TemplateOptions{
Name: "redpanda",
Namespace: "",
Values: values,
})
require.NoError(t, err)

fSys := filesys.MakeFsOnDisk()
buffy := new(bytes.Buffer)
cmd := build.NewCmdBuild(
fSys, build.MakeHelp(konfig.ProgramName, "build"), buffy)
require.NoError(t, cmd.RunE(cmd, []string{"https://github.com/redpanda-data/redpanda-operator//operator/config/default"}))

helmObjs, err := kube.DecodeYAML(rendered, Scheme)
require.NoError(t, err)

require.NoError(t, apiextensionsv1.AddToScheme(Scheme))
kustomizeObjs, err := kube.DecodeYAML(buffy.Bytes(), Scheme)
require.NoError(t, err)

helmClusterRoleRules, helmRoleRules := ExtractRules(helmObjs)
kClusterRoleRules, kRoleRules := ExtractRules(kustomizeObjs)

// Special 2 cases as namespaced operator deployment have only Role when cluster scoped deployment has
// ClusterRole rules
clusterKey := "redpanda.vectorized.io#clusters"
consoleKey := "redpanda.vectorized.io#consoles"

helmChart := helmRoleRules[clusterKey]
// As namespaced operator deployment does not have create and delete permissions for Cluster resource
helmChart = append(helmChart, "create", "delete")
slices.Sort(helmChart)
kustomize := kClusterRoleRules[clusterKey]
slices.Sort(kustomize)
assert.Equal(t, helmChart, kustomize, "for the api#resource %q", clusterKey)
// Delete cluster resource key as default helm base deployment only is defined only
// in Role when in kustomize the same Resource is defined in ClusterRole
delete(helmRoleRules, clusterKey)
delete(kClusterRoleRules, clusterKey)

helmChart = helmRoleRules[consoleKey]
// As namespaced operator deployment does not have create and delete permissions for Console resource
helmChart = append(helmChart, "create", "delete")
slices.Sort(helmChart)
kustomize = kClusterRoleRules[consoleKey]
slices.Sort(kustomize)
assert.Equal(t, helmChart, kustomize, "for the api#resource %q", consoleKey)
// Delete console resource key as default helm base deployment only is defined only
// in Role when in kustomize the same Resource is defined in ClusterRole
delete(helmRoleRules, consoleKey)
delete(kClusterRoleRules, consoleKey)

for k := range helmRoleRules {
helmChart := helmRoleRules[k]
slices.Sort(helmChart)
kustomize := kRoleRules[k]
slices.Sort(kustomize)
assert.Equal(t, helmChart, kustomize, "for the api#resource %q", k)
delete(kRoleRules, k)

kustomizeClusterRule := kClusterRoleRules[k]
switch k {
// Only defined in Role
case "coordination.k8s.io#leases":
continue
// flux resources are defined in Role
case "source.toolkit.fluxcd.io#gitrepository/status", "source.toolkit.fluxcd.io#gitrepository/finalizers", "helm.toolkit.fluxcd.io#helmreleases/status", "source.toolkit.fluxcd.io#helmrepositories/finalizers", "source.toolkit.fluxcd.io#helmcharts/status", "source.toolkit.fluxcd.io#helmcharts", "source.toolkit.fluxcd.io#helmrepositories", "source.toolkit.fluxcd.io#gitrepositories", "source.toolkit.fluxcd.io#helmrepositories/status", "source.toolkit.fluxcd.io#gitrepository", "source.toolkit.fluxcd.io#buckets", "helm.toolkit.fluxcd.io#helmreleases/finalizers", "source.toolkit.fluxcd.io#helmcharts/finalizers", "helm.toolkit.fluxcd.io#helmreleases":
continue
// Redpanda custom resource is build to be only namespaced scope so it should not be compared with ClusterRole
case "cluster.redpanda.com#redpandas", "cluster.redpanda.com#redpandas/status", "cluster.redpanda.com#redpandas/finalizers":
continue
// The secret in Kustomize ClusterRole does not have delete verb, but it is defined in Role
case "#secrets", "#services", "#serviceaccounts":
kustomizeClusterRule = append(kustomizeClusterRule, "delete")
case "apps#replicasets":
continue
case "monitoring.coreos.com#servicemonitors":
continue
case "rbac.authorization.k8s.io#roles":
continue
case "#events":
continue
case "#persistentvolumeclaims":
kustomizeClusterRule = append(kustomizeClusterRule, "patch", "update")
case "apps#statefulsets/status":
continue
case "batch#jobs":
continue
case "rbac.authorization.k8s.io#rolebindings":
continue
case "#pods":
kustomizeClusterRule = append(kustomizeClusterRule, "create")
}
slices.Sort(kustomizeClusterRule)
assert.Equal(t, helmChart, kustomizeClusterRule, "for the api#resource %q", k)
delete(kClusterRoleRules, k)
}
assert.Len(t, kRoleRules, 0)

// Drop few cluster roles as Kustomize have more Rules than helm deployment
delete(kClusterRoleRules, "rbac.authorization.k8s.io#clusterrolebindings")
delete(kClusterRoleRules, "#persistentvolumes")
delete(kClusterRoleRules, "#nodes")
delete(kClusterRoleRules, "cert-manager.io#clusterissuers")
delete(kClusterRoleRules, "#events")
delete(kClusterRoleRules, "#pods/finalizers")
delete(kClusterRoleRules, "rbac.authorization.k8s.io#clusterroles")
delete(kClusterRoleRules, "scheduling.k8s.io#priorityclasses")

for k := range kClusterRoleRules {
helmChart := helmClusterRoleRules[k]
slices.Sort(helmChart)
kustomize := kClusterRoleRules[k]
slices.Sort(kustomize)
assert.Equal(t, helmChart, kustomize, "for the api#resource %q", k)

delete(kClusterRoleRules, k)
}
assert.Len(t, kClusterRoleRules, 0)
}

// TestValues asserts that the chart's values.yaml file can be losslessly
// loaded into our type [Values] struct.
// NB: values.yaml should round trip through [Values], not [PartialValues], as
Expand Down Expand Up @@ -222,3 +358,58 @@ func makeSureTagIsNotEmptyString(values PartialValues, fuzzer *fuzz.Fuzzer) {
}
}
}

func CalculateRoleRules(rules []rbacv1.PolicyRule, computedRules map[string][]string) {
for _, rule := range rules {
for _, api := range rule.APIGroups {
for _, res := range rule.Resources {
key := fmt.Sprintf("%s#%s", api, res)

// There is differences between cluster scope and namespaced scope operator deployment
// By default namespaced scope does not includes Cluster and Console custom resource
switch key {
// Console status and finalizer is not defined in default helm namespaced operator deployment
case "redpanda.vectorized.io#consoles/finalizers", "redpanda.vectorized.io#consoles/status":
continue
// Cluster status and finalizer is not defined in default helm namespaced operator deployment
case "redpanda.vectorized.io#clusters/finalizers", "redpanda.vectorized.io#clusters/status":
continue
// Horizontal Pod Autoscalers is missing from kustomize
case "autoscaling#horizontalpodautoscalers":
continue
// Pod monitor is missing in kustomize
case "monitoring.coreos.com#podmonitors":
continue
}

verbs, exists := computedRules[key]
if !exists {
computedRules[key] = rule.Verbs
continue
}
for _, verb := range rule.Verbs {
if !slices.Contains(verbs, verb) {
verbs = append(verbs, verb)
}
}
computedRules[key] = verbs
}
}
}
}

func ExtractRules(objs []kube.Object) (map[string][]string, map[string][]string) {
clusterRoles := map[string][]string{}
roles := map[string][]string{}
for _, o := range objs {
switch o.GetObjectKind().GroupVersionKind().String() {
case "rbac.authorization.k8s.io/v1, Kind=ClusterRole":
cr := o.(*rbacv1.ClusterRole)
CalculateRoleRules(cr.Rules, clusterRoles)
case "rbac.authorization.k8s.io/v1, Kind=Role":
r := o.(*rbacv1.Role)
CalculateRoleRules(r.Rules, roles)
}
}
return clusterRoles, roles
}
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ require (
gopkg.in/yaml.v3 v3.0.1
helm.sh/helm/v3 v3.14.4
k8s.io/api v0.29.5
k8s.io/apiextensions-apiserver v0.29.5
k8s.io/apimachinery v0.29.5
k8s.io/client-go v0.29.5
k8s.io/utils v0.0.0-20240310230437-4693a0247e57
mvdan.cc/gofumpt v0.6.0
pgregory.net/rapid v1.1.0
sigs.k8s.io/controller-runtime v0.17.2
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3
sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3
sigs.k8s.io/yaml v1.4.0
)

Expand Down Expand Up @@ -208,7 +212,6 @@ require (
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apiextensions-apiserver v0.29.5 // indirect
k8s.io/apiserver v0.29.5 // indirect
k8s.io/cli-runtime v0.29.0 // indirect
k8s.io/component-base v0.29.5 // indirect
Expand All @@ -218,7 +221,5 @@ require (
oras.land/oras-go v1.2.5 // indirect
sigs.k8s.io/gateway-api v1.0.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,8 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0=
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY=
sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3 h1:vq2TtoDcQomhy7OxXLUOzSbHMuMYq0Bjn93cDtJEdKw=
sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3/go.mod h1:/d88dHCvoy7d0AKFT0yytezSGZKjsZBVs9YTkBHSGFk=
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U=
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
Expand Down
4 changes: 2 additions & 2 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1374,8 +1374,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RCh
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4=
sigs.k8s.io/controller-tools v0.13.0 h1:NfrvuZ4bxyolhDBt/rCZhDnx3M2hzlhgo5n3Iv2RykI=
sigs.k8s.io/controller-tools v0.13.0/go.mod h1:5vw3En2NazbejQGCeWKRrE7q4P+CW8/klfVqP8QZkgA=
sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3 h1:vq2TtoDcQomhy7OxXLUOzSbHMuMYq0Bjn93cDtJEdKw=
sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3/go.mod h1:/d88dHCvoy7d0AKFT0yytezSGZKjsZBVs9YTkBHSGFk=
sigs.k8s.io/kustomize/cmd/config v0.11.2 h1:YyoHHbxxsLUts/gWLGgIQkdT82ekp3zautbpcml54vc=
sigs.k8s.io/kustomize/cmd/config v0.11.2/go.mod h1:PCpHxyu10daTnbMfn3xhH1vppn7L8jsS3qpRKXb7Lkc=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
tags.cncf.io/container-device-interface v0.7.2 h1:MLqGnWfOr1wB7m08ieI4YJ3IoLKKozEnnNYBtacDPQU=
Expand Down

0 comments on commit fc4250f

Please sign in to comment.