diff --git a/library/common-test/tests/rbac/data_test.yaml b/library/common-test/tests/rbac/data_test.yaml new file mode 100644 index 0000000000..644673ee03 --- /dev/null +++ b/library/common-test/tests/rbac/data_test.yaml @@ -0,0 +1,250 @@ +suite: rbac data test +templates: + - common.yaml +tests: + - it: should pass with rules and subjects added with tpl and primary rbac/sa + set: + some_verb: list + some_group: apps + some_resource: deployments + some_kind: some-kind + some_name: some-name + some_api_group: rbac.authorization.k8s.io + serviceAccount: + my-sa: + enabled: true + primary: true + my-other-sa: + enabled: true + primary: false + rbac: + my-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - apiGroups: + - "{{ .Values.some_group }}" + resources: + - "{{ .Values.some_resource }}" + verbs: + - "{{ .Values.some_verb }}" + subjects: + - kind: a-kind + name: a-name + apiGroup: rbac.authorization.k8s.io + - kind: "{{ .Values.some_kind }}" + name: "{{ .Values.some_name }}" + apiGroup: "{{ .Values.some_api_group }}" + + asserts: + - documentIndex: &roleDoc 2 + isKind: + of: Role + - documentIndex: *roleDoc + equal: + path: metadata.name + value: release-name-common-test + - documentIndex: *roleDoc + equal: + path: rules + value: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - apiGroups: + - apps + resources: + - deployments + verbs: + - list + - documentIndex: &roleBinding 3 + isKind: + of: RoleBinding + - documentIndex: *roleBinding + equal: + path: metadata.name + value: release-name-common-test + - documentIndex: *roleBinding + equal: + path: subjects + value: + - kind: ServiceAccount + name: release-name-common-test + namespace: NAMESPACE + - kind: a-kind + name: a-name + apiGroup: rbac.authorization.k8s.io + - kind: some-kind + name: some-name + apiGroup: rbac.authorization.k8s.io + + - it: should pass with rules and subjects added with tpl and allSA on clusterWide + set: + some_verb: list + some_group: apps + some_resource: deployments + some_kind: some-kind + some_name: some-name + some_api_group: rbac.authorization.k8s.io + serviceAccount: + my-sa: + enabled: true + primary: true + my-other-sa: + enabled: true + primary: false + rbac: + z-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + my-rbac2: + enabled: true + clusterWide: true + allServiceAccounts: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - apiGroups: + - "{{ .Values.some_group }}" + resources: + - "{{ .Values.some_resource }}" + verbs: + - "{{ .Values.some_verb }}" + subjects: + - kind: a-kind + name: a-name + apiGroup: rbac.authorization.k8s.io + - kind: "{{ .Values.some_kind }}" + name: "{{ .Values.some_name }}" + apiGroup: "{{ .Values.some_api_group }}" + asserts: + - documentIndex: &clusterRoleDoc 2 + isKind: + of: ClusterRole + - documentIndex: *clusterRoleDoc + equal: + path: metadata.name + value: release-name-common-test-my-rbac2 + - documentIndex: *clusterRoleDoc + equal: + path: rules + value: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - apiGroups: + - apps + resources: + - deployments + verbs: + - list + - documentIndex: &clusterRoleBinding 3 + isKind: + of: ClusterRoleBinding + - documentIndex: *clusterRoleBinding + equal: + path: metadata.name + value: release-name-common-test-my-rbac2 + - documentIndex: *clusterRoleBinding + equal: + path: subjects + value: + - kind: ServiceAccount + name: release-name-common-test-my-other-sa + namespace: NAMESPACE + - kind: ServiceAccount + name: release-name-common-test + namespace: NAMESPACE + - apiGroup: rbac.authorization.k8s.io + kind: a-kind + name: a-name + - apiGroup: rbac.authorization.k8s.io + kind: some-kind + name: some-name + + - it: should pass with serviceAccount selector + set: + serviceAccount: + my-sa: + enabled: true + primary: true + my-other-sa: + enabled: true + primary: false + rbac: + z-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + my-rbac3: + enabled: true + serviceAccounts: + - my-other-sa + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + asserts: + - documentIndex: &roleDoc 2 + isKind: + of: Role + - documentIndex: *roleDoc + equal: + path: metadata.name + value: release-name-common-test-my-rbac3 + - documentIndex: *roleDoc + equal: + path: rules + value: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - documentIndex: &roleBinding 3 + isKind: + of: RoleBinding + - documentIndex: *roleBinding + equal: + path: metadata.name + value: release-name-common-test-my-rbac3 + - documentIndex: *roleBinding + equal: + path: subjects + value: + - kind: ServiceAccount + name: release-name-common-test-my-other-sa + namespace: NAMESPACE diff --git a/library/common-test/tests/rbac/metadata_test.yaml b/library/common-test/tests/rbac/metadata_test.yaml new file mode 100644 index 0000000000..53bcd1dd2c --- /dev/null +++ b/library/common-test/tests/rbac/metadata_test.yaml @@ -0,0 +1,163 @@ +suite: rbac metadata test +templates: + - common.yaml +chart: + appVersion: &appVer v9.9.9 +tests: + - it: should pass with rbac created with labels and annotations + set: + label1: label1 + label2: global_label2 + annotation1: annotation1 + annotation2: global_annotation2 + global: + labels: + g_label1: global_label1 + g_label2: "{{ .Values.label2 }}" + annotations: + g_annotation1: global_annotation1 + g_annotation2: "{{ .Values.annotation2 }}" + serviceAccount: + my-sa1: + enabled: true + primary: true + rbac: + my-rbac1: + enabled: true + primary: true + labels: + label1: "{{ .Values.label1 }}" + label2: label2 + annotations: + annotation1: "{{ .Values.annotation1 }}" + annotation2: annotation2 + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch + my-rbac2: + enabled: true + primary: false + clusterWide: true + allServiceAccounts: true + labels: + label1: "{{ .Values.label1 }}" + label2: label2 + annotations: + annotation1: "{{ .Values.annotation1 }}" + annotation2: annotation2 + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch + asserts: + - documentIndex: &roleDoc 1 + isKind: + of: Role + - documentIndex: *roleDoc + equal: + path: metadata.annotations + value: + annotation1: annotation1 + annotation2: annotation2 + g_annotation1: global_annotation1 + g_annotation2: global_annotation2 + - documentIndex: *roleDoc + equal: + path: metadata.labels + value: + app: common-test-1.0.0 + release: release-name + helm-revision: 0 + helm.sh/chart: common-test-1.0.0 + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: *appVer + g_label1: global_label1 + g_label2: global_label2 + label1: label1 + label2: label2 + - documentIndex: &roleBindingDoc 2 + isKind: + of: RoleBinding + - documentIndex: *roleBindingDoc + equal: + path: metadata.annotations + value: + annotation1: annotation1 + annotation2: annotation2 + g_annotation1: global_annotation1 + g_annotation2: global_annotation2 + - documentIndex: *roleBindingDoc + equal: + path: metadata.labels + value: + app: common-test-1.0.0 + release: release-name + helm-revision: 0 + helm.sh/chart: common-test-1.0.0 + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: *appVer + g_label1: global_label1 + g_label2: global_label2 + label1: label1 + label2: label2 + - documentIndex: &clusterRoleDoc 3 + isKind: + of: ClusterRole + - documentIndex: *clusterRoleDoc + equal: + path: metadata.annotations + value: + annotation1: annotation1 + annotation2: annotation2 + g_annotation1: global_annotation1 + g_annotation2: global_annotation2 + - documentIndex: *clusterRoleDoc + equal: + path: metadata.labels + value: + app: common-test-1.0.0 + release: release-name + helm-revision: 0 + helm.sh/chart: common-test-1.0.0 + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: *appVer + g_label1: global_label1 + g_label2: global_label2 + label1: label1 + label2: label2 + - documentIndex: &clusterRoleBindingDoc 4 + isKind: + of: ClusterRoleBinding + - documentIndex: *clusterRoleBindingDoc + equal: + path: metadata.annotations + value: + annotation1: annotation1 + annotation2: annotation2 + g_annotation1: global_annotation1 + g_annotation2: global_annotation2 + - documentIndex: *clusterRoleBindingDoc + equal: + path: metadata.labels + value: + app: common-test-1.0.0 + release: release-name + helm-revision: 0 + helm.sh/chart: common-test-1.0.0 + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: *appVer + g_label1: global_label1 + g_label2: global_label2 + label1: label1 + label2: label2 diff --git a/library/common-test/tests/rbac/name_test.yaml b/library/common-test/tests/rbac/name_test.yaml new file mode 100644 index 0000000000..c0c9ab7b8a --- /dev/null +++ b/library/common-test/tests/rbac/name_test.yaml @@ -0,0 +1,105 @@ +suite: rbac name test +templates: + - common.yaml +tests: + - it: should generate correct name + set: + serviceAccount: + my-sa: + enabled: true + primary: true + rbac: + my-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + my-rbac2: + enabled: true + clusterWide: true + allServiceAccounts: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + my-rbac3: + enabled: true + allServiceAccounts: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + asserts: + - documentIndex: &roleDoc 1 + isKind: + of: Role + - documentIndex: *roleDoc + isAPIVersion: + of: rbac.authorization.k8s.io/v1 + - documentIndex: *roleDoc + equal: + path: metadata.name + value: release-name-common-test + - documentIndex: &roleBindingDoc 2 + isKind: + of: RoleBinding + - documentIndex: *roleBindingDoc + isAPIVersion: + of: rbac.authorization.k8s.io/v1 + - documentIndex: *roleBindingDoc + equal: + path: metadata.name + value: release-name-common-test + + - documentIndex: &clusterRoleDoc 3 + isKind: + of: ClusterRole + - documentIndex: *clusterRoleDoc + isAPIVersion: + of: rbac.authorization.k8s.io/v1 + - documentIndex: *clusterRoleDoc + equal: + path: metadata.name + value: release-name-common-test-my-rbac2 + - documentIndex: &clusterRoleBindingDoc 4 + isKind: + of: ClusterRoleBinding + - documentIndex: *clusterRoleBindingDoc + isAPIVersion: + of: rbac.authorization.k8s.io/v1 + - documentIndex: *clusterRoleBindingDoc + equal: + path: metadata.name + value: release-name-common-test-my-rbac2 + + - documentIndex: &otherRoleDoc 5 + isKind: + of: Role + - documentIndex: *otherRoleDoc + isAPIVersion: + of: rbac.authorization.k8s.io/v1 + - documentIndex: *otherRoleDoc + equal: + path: metadata.name + value: release-name-common-test-my-rbac3 + - documentIndex: &otherRoleBindingDoc 6 + isKind: + of: RoleBinding + - documentIndex: *otherRoleBindingDoc + isAPIVersion: + of: rbac.authorization.k8s.io/v1 + - documentIndex: *otherRoleBindingDoc + equal: + path: metadata.name + value: release-name-common-test-my-rbac3 diff --git a/library/common-test/tests/rbac/validation_test.yaml b/library/common-test/tests/rbac/validation_test.yaml new file mode 100644 index 0000000000..ec479132e8 --- /dev/null +++ b/library/common-test/tests/rbac/validation_test.yaml @@ -0,0 +1,250 @@ +suite: rbac validation test +templates: + - common.yaml +tests: + - it: should fail with name longer than 63 characters + set: + rbac: + zmy-rbac: + enabled: true + primary: true + my-rbac-has-super-long-name-that-is-longer-than-63-characters-too-bad: + enabled: true + primary: false + asserts: + - failedTemplate: + errorMessage: Name [release-name-common-test-my-rbac-has-super-long-name-that-is-longer-than-63-characters-too-bad] is not valid. Must start and end with an alphanumeric character. It can contain '-'. And must be at most 63 characters. + + - it: should fail with name starting with underscore + set: + rbac: + my-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + _my-rbac2: + enabled: true + primary: false + asserts: + - failedTemplate: + errorMessage: Name [release-name-common-test-_my-rbac2] is not valid. Must start and end with an alphanumeric character. It can contain '-'. And must be at most 63 characters. + + - it: should fail with labels not a dict + set: + rbac: + my-rbac: + enabled: true + primary: true + labels: "not a dict" + asserts: + - failedTemplate: + errorMessage: RBAC - Expected to be a dictionary, but got [string] + + - it: should fail with annotations not a dict + set: + rbac: + my-rbac: + enabled: true + primary: true + annotations: "not a dict" + asserts: + - failedTemplate: + errorMessage: RBAC - Expected to be a dictionary, but got [string] + + - it: should fail with more than 1 primary rbac + set: + rbac: + my-rbac: + enabled: true + primary: true + my-rbac2: + enabled: true + primary: true + asserts: + - failedTemplate: + errorMessage: RBAC - Only one rbac can be primary + + - it: should fail without any primary on enabled rbac + set: + rbac: + my-rbac: + enabled: true + primary: false + my-rbac2: + enabled: true + primary: false + asserts: + - failedTemplate: + errorMessage: RBAC - At least one enabled rbac must be primary + + - it: should fail without rules in rbac + set: + rbac: + my-rbac: + enabled: true + primary: true + asserts: + - failedTemplate: + errorMessage: RBAC - Expected non-empty + + - it: should fail without apiGroups in rules in rbac + set: + rbac: + my-rbac: + enabled: true + primary: true + rules: + - resources: + - pods + verbs: + - get + asserts: + - failedTemplate: + errorMessage: RBAC - Expected non-empty + + - it: should fail without resources in rules in rbac + set: + rbac: + my-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + verbs: + - get + asserts: + - failedTemplate: + errorMessage: RBAC - Expected non-empty + + - it: should fail without verbs in rules in rbac + set: + rbac: + my-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + resources: + - pods + asserts: + - failedTemplate: + errorMessage: RBAC - Expected non-empty + + - it: should fail with empty entry in resources in rules in rbac + set: + rbac: + my-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + resources: + - pods + - "" + verbs: + - get + asserts: + - failedTemplate: + errorMessage: RBAC - Expected non-empty entry in + + - it: should fail with empty entry in verbs in rules in rbac + set: + rbac: + my-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - "" + asserts: + - failedTemplate: + errorMessage: RBAC - Expected non-empty entry in + + - it: should fail with empty kind in subjects in rbac + set: + serviceAccount: + my-service-account: + enabled: true + primary: true + rbac: + my-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + subjects: + - kind: "" + name: my-name + apiGroup: my-apiGroup + asserts: + - failedTemplate: + errorMessage: RBAC - Expected non-empty + + - it: should fail with empty name in subjects in rbac + set: + serviceAccount: + my-service-account: + enabled: true + primary: true + rbac: + my-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + subjects: + - kind: my-kind + name: "" + apiGroup: my-apiGroup + asserts: + - failedTemplate: + errorMessage: RBAC - Expected non-empty + + - it: should fail with empty apiGroup in subjects in rbac + set: + serviceAccount: + my-service-account: + enabled: true + primary: true + rbac: + my-rbac: + enabled: true + primary: true + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - get + subjects: + - kind: my-kind + name: my-name + apiGroup: "" + asserts: + - failedTemplate: + errorMessage: RBAC - Expected non-empty diff --git a/library/common-test/tests/serviceAccount/name_test.yaml b/library/common-test/tests/serviceAccount/name_test.yaml index e05210471c..899b13e0b5 100644 --- a/library/common-test/tests/serviceAccount/name_test.yaml +++ b/library/common-test/tests/serviceAccount/name_test.yaml @@ -5,13 +5,13 @@ tests: - it: should generate correct name set: serviceAccount: + my-sa: + enabled: true + primary: true my-sa1: enabled: true my-sa2: enabled: true - main: - enabled: true - primary: true asserts: - documentIndex: &primaryServiceAccount 0 isKind: diff --git a/library/common/1.0.0/templates/classes/_rbac.tpl b/library/common/1.0.0/templates/classes/_rbac.tpl index e69de29bb2..89eb0f7054 100644 --- a/library/common/1.0.0/templates/classes/_rbac.tpl +++ b/library/common/1.0.0/templates/classes/_rbac.tpl @@ -0,0 +1,64 @@ +{{/* RBAC Class */}} +{{/* Call this template: +{{ include "ix.v1.common.class.rbac" (dict "rootCtx" $ "objectData" $objectData) }} + +rootCtx: The root context of the template. It is used to access the global context. +objectData: + name: The name of the rbac. + labels: The labels of the rbac. + annotations: The annotations of the rbac. + clusterWide: Whether the rbac is cluster wide or not. + rules: The rules of the rbac. + subjects: The subjects of the rbac. +*/}} + +{{- define "ix.v1.common.class.rbac" -}} + + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: {{ ternary "ClusterRole" "Role" $objectData.clusterWide }} +metadata: + name: {{ $objectData.name }} + {{- if not $objectData.clusterWide }} + namespace: {{ $rootCtx.Release.Namespace }} + {{- end }} + {{- $labels := (mustMerge ($objectData.labels | default dict) (include "ix.v1.common.lib.metadata.allLabels" $rootCtx | fromYaml)) -}} + {{- with (include "ix.v1.common.lib.metadata.render" (dict "rootCtx" $rootCtx "labels" $labels) | trim) }} + labels: + {{- . | nindent 4 }} + {{- end -}} + {{- $annotations := (mustMerge ($objectData.annotations | default dict) (include "ix.v1.common.lib.metadata.allAnnotations" $rootCtx | fromYaml)) -}} + {{- with (include "ix.v1.common.lib.metadata.render" (dict "rootCtx" $rootCtx "annotations" $annotations) | trim) }} + annotations: + {{- . | nindent 4 }} + {{- end }} +rules: + {{- include "ix.v1.common.lib.rbac.rules" (dict "rootCtx" $rootCtx "objectData" $objectData) | trim | nindent 2 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: {{ ternary "ClusterRoleBinding" "RoleBinding" $objectData.clusterWide }} +metadata: + name: {{ $objectData.name }} + {{- if not $objectData.clusterWide }} + namespace: {{ $rootCtx.Release.Namespace }} + {{- end }} + {{- $labels := (mustMerge ($objectData.labels | default dict) (include "ix.v1.common.lib.metadata.allLabels" $rootCtx | fromYaml)) -}} + {{- with (include "ix.v1.common.lib.metadata.render" (dict "rootCtx" $rootCtx "labels" $labels) | trim) }} + labels: + {{- . | nindent 4 }} + {{- end -}} + {{- $annotations := (mustMerge ($objectData.annotations | default dict) (include "ix.v1.common.lib.metadata.allAnnotations" $rootCtx | fromYaml)) -}} + {{- with (include "ix.v1.common.lib.metadata.render" (dict "rootCtx" $rootCtx "annotations" $annotations) | trim) }} + annotations: + {{- . | nindent 4 }} + {{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: {{ ternary "ClusterRole" "Role" $objectData.clusterWide }} + name: {{ $objectData.name }} +subjects: + {{- include "ix.v1.common.lib.rbac.serviceAccount" (dict "rootCtx" $rootCtx "objectData" $objectData) | trim | nindent 2 }} + {{- include "ix.v1.common.lib.rbac.subjects" (dict "rootCtx" $rootCtx "objectData" $objectData) | trim | nindent 2 }} +{{- end -}} diff --git a/library/common/1.0.0/templates/lib/rbac/_getServiceAccounts.tpl b/library/common/1.0.0/templates/lib/rbac/_getServiceAccounts.tpl new file mode 100644 index 0000000000..1fc75618cd --- /dev/null +++ b/library/common/1.0.0/templates/lib/rbac/_getServiceAccounts.tpl @@ -0,0 +1,52 @@ +{{/* Returns Service Account List for rbac */}} +{{/* Call this template: +{{ include "ix.v1.common.lib.rbac.serviceAccount" (dict "rootCtx" $ "objectData" $objectData) }} +rootCtx: The root context of the template. It is used to access the global context. +objectData: The object data to be used to render the RBAC. +*/}} +{{/* Parses service accounts, and checks if RBAC have selected any of them */}} +{{- define "ix.v1.common.lib.rbac.serviceAccount" -}} + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData -}} + + {{- $serviceAccounts := list -}} + + {{- range $name, $serviceAccount := $rootCtx.Values.serviceAccount -}} + {{- $saName := include "ix.v1.common.lib.chart.names.fullname" $rootCtx -}} + + {{- if $serviceAccount.enabled -}} + + {{- if not $serviceAccount.primary -}} + {{- $saName = (printf "%s-%s" (include "ix.v1.common.lib.chart.names.fullname" $rootCtx) $name) -}} + {{- end -}} + + {{/* If allServiceAccounts is true */}} + {{- if $objectData.allServiceAccounts -}} + {{- $serviceAccounts = mustAppend $serviceAccounts $saName -}} + + {{/* Else if serviceAccounts is a list */}} + {{- else if (kindIs "slice" $objectData.serviceAccounts) -}} + {{- if (mustHas $name $objectData.serviceAccounts) -}} + {{- $serviceAccounts = mustAppend $serviceAccounts $saName -}} + {{- end -}} + + {{/* If not "allServiceAccounts" or "serviceAccounts", assign the primary service account to rbac */}} + {{- else if $serviceAccount.primary -}} + {{- if $objectData.primary -}} + {{- $serviceAccounts = mustAppend $serviceAccounts $saName -}} + {{- end -}} + {{- end -}} + + {{- end -}} + {{- end -}} + + {{- if not $serviceAccounts -}} + {{- fail "RBAC - Expected at least one serviceAccount to be assigned. Assign one using [allServiceAccounts (boolean), serviceAccounts (list)]" -}} + {{- end -}} + + {{- range $serviceAccounts }} +- kind: ServiceAccount + name: {{ . }} + namespace: {{ $rootCtx.Release.Namespace }} + {{- end -}} +{{- end -}} diff --git a/library/common/1.0.0/templates/lib/rbac/_rules.tpl b/library/common/1.0.0/templates/lib/rbac/_rules.tpl new file mode 100644 index 0000000000..21e0e31fe4 --- /dev/null +++ b/library/common/1.0.0/templates/lib/rbac/_rules.tpl @@ -0,0 +1,50 @@ +{{/* Returns Rules for rbac */}} +{{/* Call this template: +{{ include "ix.v1.common.lib.rbac.rules" (dict "rootCtx" $ "objectData" $objectData) }} +rootCtx: The root context of the template. It is used to access the global context. +objectData: The object data to be used to render the RBAC. +*/}} +{{/* Parses service accounts, and checks if RBAC have selected any of them */}} +{{- define "ix.v1.common.lib.rbac.rules" -}} + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData -}} + + {{- if not $objectData.rules -}} + {{- fail "RBAC - Expected non-empty " -}} + {{- end -}} + + {{- range $objectData.rules -}} + {{- if not .apiGroups -}} + {{- fail "RBAC - Expected non-empty " -}} + {{- end -}} + {{- if not .resources -}} + {{- fail "RBAC - Expected non-empty " -}} + {{- end -}} + {{- if not .verbs -}} + {{- fail "RBAC - Expected non-empty " -}} + {{- end -}} + + {{- /* apiGroups */}} +- apiGroups: + {{- range .apiGroups }} + - {{ tpl . $rootCtx | quote }} + {{- end -}} + {{- /* resources */}} + resources: + {{- range .resources -}} + {{- if not . -}} + {{- fail "RBAC - Expected non-empty entry in " -}} + {{- end }} + - {{ tpl . $rootCtx | quote }} + {{- end -}} + {{- /* verbs */}} + verbs: + {{- range .verbs -}} + {{- if not . -}} + {{- fail "RBAC - Expected non-empty entry in " -}} + {{- end }} + - {{ tpl . $rootCtx | quote }} + {{- end -}} + {{- end -}} + +{{- end -}} diff --git a/library/common/1.0.0/templates/lib/rbac/_subjects.tpl b/library/common/1.0.0/templates/lib/rbac/_subjects.tpl new file mode 100644 index 0000000000..5befe800ed --- /dev/null +++ b/library/common/1.0.0/templates/lib/rbac/_subjects.tpl @@ -0,0 +1,17 @@ +{{/* Returns Subjects for rbac */}} +{{/* Call this template: +{{ include "ix.v1.common.lib.rbac.subjects" (dict "rootCtx" $ "objectData" $objectData) }} +rootCtx: The root context of the template. It is used to access the global context. +objectData: The object data to be used to render the RBAC. +*/}} +{{/* Parses service accounts, and checks if RBAC have selected any of them */}} +{{- define "ix.v1.common.lib.rbac.subjects" -}} + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData -}} + + {{- range $objectData.subjects }} +- kind: {{ tpl (required "RBAC - Expected non-empty " .kind) $rootCtx | quote }} + name: {{ tpl (required "RBAC - Expected non-empty " .name) $rootCtx | quote }} + apiGroup: {{ tpl (required "RBAC - Expected non-empty " .apiGroup) $rootCtx | quote }} + {{- end -}} +{{- end -}} diff --git a/library/common/1.0.0/templates/lib/rbac/_validation.tpl b/library/common/1.0.0/templates/lib/rbac/_validation.tpl new file mode 100644 index 0000000000..8e1dd458c4 --- /dev/null +++ b/library/common/1.0.0/templates/lib/rbac/_validation.tpl @@ -0,0 +1,63 @@ +{{/* RBAC Validation */}} +{{/* Call this template: +{{ include "ix.v1.common.lib.rbac.validation" (dict "objectData" $objectData) -}} +objectData: + labels: The labels of the rbac. + annotations: The annotations of the rbac. + data: The data of the rbac. +*/}} + +{{- define "ix.v1.common.lib.rbac.validation" -}} + {{- $objectData := .objectData -}} + + {{- if and $objectData.labels (not (kindIs "map" $objectData.labels)) -}} + {{- fail (printf "RBAC - Expected to be a dictionary, but got [%v]" (kindOf $objectData.labels)) -}} + {{- end -}} + + {{- if and $objectData.annotations (not (kindIs "map" $objectData.annotations)) -}} + {{- fail (printf "RBAC - Expected to be a dictionary, but got [%v]" (kindOf $objectData.annotations)) -}} + {{- end -}} + +{{- end -}} + +{{/* RBAC Primary Validation */}} +{{/* Call this template: +{{ include "ix.v1.common.lib.rbac.primaryValidation" (dict "objectData" $objectData) -}} +objectData: + labels: The labels of the rbac. + annotations: The annotations of the rbac. +*/}} + +{{- define "ix.v1.common.lib.rbac.primaryValidation" -}} + + {{/* Initialize values */}} + {{- $hasPrimary := false -}} + {{- $hasEnabled := false -}} + + {{- range $name, $rbac := .Values.rbac -}} + + {{/* If rbac is enabled */}} + {{- if $rbac.enabled -}} + {{- $hasEnabled = true -}} + + {{/* And rbac is primary */}} + {{- if and (hasKey $rbac "primary") ($rbac.primary) -}} + + {{/* Fail if there is already a primary rbac */}} + {{- if $hasPrimary -}} + {{- fail "RBAC - Only one rbac can be primary" -}} + {{- end -}} + + {{- $hasPrimary = true -}} + + {{- end -}} + + {{- end -}} + {{- end -}} + + {{/* Require at least one primary rbac, if any enabled */}} + {{- if and $hasEnabled (not $hasPrimary) -}} + {{- fail "RBAC - At least one enabled rbac must be primary" -}} + {{- end -}} + +{{- end -}} diff --git a/library/common/1.0.0/templates/loader/_apply.tpl b/library/common/1.0.0/templates/loader/_apply.tpl index 8439ce752c..49242991c3 100644 --- a/library/common/1.0.0/templates/loader/_apply.tpl +++ b/library/common/1.0.0/templates/loader/_apply.tpl @@ -13,6 +13,9 @@ {{/* Render Service Accounts(s) */}} {{- include "ix.v1.common.spawner.serviceAccount" . | nindent 0 -}} + {{/* Render RBAC(s) */}} + {{- include "ix.v1.common.spawner.rbac" . | nindent 0 -}} + {{/* Render Workload(s) */}} {{- include "ix.v1.common.spawner.workload" . | nindent 0 -}} diff --git a/library/common/1.0.0/templates/spawner/_rbac.tpl b/library/common/1.0.0/templates/spawner/_rbac.tpl index e69de29bb2..8b28bb9e20 100644 --- a/library/common/1.0.0/templates/spawner/_rbac.tpl +++ b/library/common/1.0.0/templates/spawner/_rbac.tpl @@ -0,0 +1,43 @@ +{{/* RBAC Spawner */}} +{{/* Call this template: +{{ include "ix.v1.common.spawner.rbac" $ -}} +*/}} + +{{- define "ix.v1.common.spawner.rbac" -}} + + {{/* Primary validation for enabled rbacs. */}} + {{- include "ix.v1.common.lib.rbac.primaryValidation" $ -}} + + {{- range $name, $rbac := .Values.rbac -}} + + {{- if $rbac.enabled -}} + + {{/* Create a copy of the configmap */}} + {{- $objectData := (mustDeepCopy $rbac) -}} + + {{- $objectName := include "ix.v1.common.lib.chart.names.fullname" $ -}} + {{- if not $objectData.primary -}} + {{- $objectName = (printf "%s-%s" (include "ix.v1.common.lib.chart.names.fullname" $) $name) -}} + {{- end -}} + + {{/* Perform validations */}} + {{- include "ix.v1.common.lib.chart.names.validation" (dict "name" $objectName) -}} + {{- include "ix.v1.common.lib.rbac.validation" (dict "objectData" $objectData) -}} + + {{/* Set the name of the rbac */}} + {{- $_ := set $objectData "name" $objectName -}} + {{- $_ := set $objectData "shortName" $name -}} + + {{/* If clusteWide key does not exist, assume false */}} + {{- if not (hasKey $objectData "clusterWide") -}} + {{- $_ := set $objectData "clusterWide" false -}} + {{- end -}} + + {{/* Call class to create the object */}} + {{- include "ix.v1.common.class.rbac" (dict "rootCtx" $ "objectData" $objectData) -}} + + {{- end -}} + + {{- end -}} + +{{- end -}} diff --git a/library/common/1.0.0/templates/spawner/_serviceAccount.tpl b/library/common/1.0.0/templates/spawner/_serviceAccount.tpl index 05c1cb6975..2ad1be6a37 100644 --- a/library/common/1.0.0/templates/spawner/_serviceAccount.tpl +++ b/library/common/1.0.0/templates/spawner/_serviceAccount.tpl @@ -26,6 +26,7 @@ {{/* Set the name of the service account */}} {{- $_ := set $objectData "name" $objectName -}} + {{- $_ := set $objectData "shortName" $name -}} {{/* Call class to create the object */}} {{- include "ix.v1.common.class.serviceAccount" (dict "rootCtx" $ "objectData" $objectData) -}}