Skip to content
This repository was archived by the owner on Aug 19, 2024. It is now read-only.

Commit 1f8875b

Browse files
Security mitigation: remove secret get from RBAC (#160)
* Security mitigation: remove secret get from RBAC * Security migtigation: update the description for the custom image and extraFile secrets in the CRD * Security compliance: remove create and update from RBAC for PV and PVC
1 parent fc37ff4 commit 1f8875b

15 files changed

+120
-113
lines changed

.rhdh/bundle/manifests/rhdh-operator.csv.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ metadata:
2525
It comes with pre-built plug-ins, configuration settings, and deployment mechanisms,
2626
which can help streamline the process of setting up a self-managed internal
2727
developer portal for adopters who are just starting out.
28-
operatorframework.io/suggested-namespace: openshift-operators
28+
operatorframework.io/suggested-namespace: openshift-rhdh-operator
2929
operators.openshift.io/valid-subscription: '["OpenShift Container Platform", "OpenShift
3030
Platform Plus"]'
3131
operators.operatorframework.io/builder: operator-sdk-v1.33.0

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ undeploy-olm: ## Un-deploy the operator with OLM
359359
DEFAULT_OLM_NAMESPACE ?= openshift-marketplace
360360
.PHONY: catalog-update
361361
catalog-update: ## Update catalog source in the default namespace for catalogsource
362-
kubectl delete catalogsource backstage-operator -n $(DEFAULT_OLM_NAMESPACE)
362+
-kubectl delete catalogsource backstage-operator -n $(DEFAULT_OLM_NAMESPACE)
363363
sed "s/{{CATALOG_IMG}}/$(subst /,\/,$(CATALOG_IMG))/g" config/samples/catalog-source-template.yaml | kubectl apply -n $(DEFAULT_OLM_NAMESPACE) -f -
364364

365365
.PHONY: deploy-openshift

api/v1alpha1/backstage_types.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ type Application struct {
9797
//+kubebuilder:default=1
9898
Replicas *int32 `json:"replicas,omitempty"`
9999

100-
// Image to use in all containers (including Init Containers)
100+
// Custom image to use in all containers (including Init Containers).
101+
// It is your responsibility to make sure the image is from trusted sources and has been validated for security compliance
101102
// +optional
102103
Image *string `json:"image,omitempty"`
103104

@@ -138,8 +139,7 @@ type ExtraFiles struct {
138139
ConfigMaps []ObjectKeyRef `json:"configMaps,omitempty"`
139140

140141
// List of references to Secrets objects mounted as extra files under the MountPath specified.
141-
// For each item in this array, if a key is not specified, it means that all keys in the Secret will be mounted as files.
142-
// Otherwise, only the specified key will be mounted as a file.
142+
// For each item in this array, a key must be specified that will be mounted as a file.
143143
// +optional
144144
Secrets []ObjectKeyRef `json:"secrets,omitempty"`
145145
}

bundle/manifests/backstage-operator.clusterserviceversion.yaml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ metadata:
2020
"spec": null
2121
}
2222
]
23-
capabilities: Basic Install
24-
createdAt: "2024-01-19T01:12:25Z"
23+
capabilities: Seamless Upgrades
24+
createdAt: "2024-01-29T20:18:14Z"
25+
operatorframework.io/suggested-namespace: backstage-system
2526
operators.operatorframework.io/builder: operator-sdk-v1.33.0
2627
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
2728
name: backstage-operator.v0.0.1
@@ -48,9 +49,6 @@ spec:
4849
- ""
4950
resources:
5051
- configmaps
51-
- persistentvolumeclaims
52-
- persistentvolumes
53-
- secrets
5452
- services
5553
verbs:
5654
- create
@@ -59,6 +57,22 @@ spec:
5957
- list
6058
- update
6159
- watch
60+
- apiGroups:
61+
- ""
62+
resources:
63+
- persistentvolumeclaims
64+
- persistentvolumes
65+
verbs:
66+
- get
67+
- list
68+
- watch
69+
- apiGroups:
70+
- ""
71+
resources:
72+
- secrets
73+
verbs:
74+
- create
75+
- delete
6276
- apiGroups:
6377
- apps
6478
resources:
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
1-
# permissions for end users to view backstages.
21
apiVersion: rbac.authorization.k8s.io/v1
32
kind: ClusterRole
43
metadata:
4+
creationTimestamp: null
55
labels:
6-
app.kubernetes.io/name: clusterrole
7-
app.kubernetes.io/instance: backstage-viewer-role
86
app.kubernetes.io/component: rbac
97
app.kubernetes.io/created-by: backstage-operator
10-
app.kubernetes.io/part-of: backstage-operator
8+
app.kubernetes.io/instance: backstage-secret-viewer-role
119
app.kubernetes.io/managed-by: kustomize
12-
name: backstage-viewer-role
10+
app.kubernetes.io/name: clusterrole
11+
app.kubernetes.io/part-of: backstage-operator
12+
name: backstage-secret-viewer-role
1313
rules:
1414
- apiGroups:
15-
- janus-idp.io
15+
- ""
1616
resources:
17-
- backstages
17+
- secrets
1818
verbs:
1919
- get
2020
- list
2121
- watch
22-
- apiGroups:
23-
- janus-idp.io
24-
resources:
25-
- backstages/status
26-
verbs:
27-
- get

bundle/manifests/janus-idp.io_backstages.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,8 @@ spec:
179179
secrets:
180180
description: List of references to Secrets objects mounted
181181
as extra files under the MountPath specified. For each item
182-
in this array, if a key is not specified, it means that
183-
all keys in the Secret will be mounted as files. Otherwise,
184-
only the specified key will be mounted as a file.
182+
in this array, a key must be specified that will be mounted
183+
as a file.
185184
items:
186185
properties:
187186
key:
@@ -197,7 +196,10 @@ spec:
197196
type: array
198197
type: object
199198
image:
200-
description: Image to use in all containers (including Init Containers)
199+
description: Custom image to use in all containers (including
200+
Init Containers). It is your responsibility to make sure the
201+
image is from trusted sources and has been validated for security
202+
compliance
201203
type: string
202204
imagePullSecrets:
203205
description: Image Pull Secrets to use in all containers (including

config/crd/bases/janus-idp.io_backstages.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,8 @@ spec:
180180
secrets:
181181
description: List of references to Secrets objects mounted
182182
as extra files under the MountPath specified. For each item
183-
in this array, if a key is not specified, it means that
184-
all keys in the Secret will be mounted as files. Otherwise,
185-
only the specified key will be mounted as a file.
183+
in this array, a key must be specified that will be mounted
184+
as a file.
186185
items:
187186
properties:
188187
key:
@@ -198,7 +197,10 @@ spec:
198197
type: array
199198
type: object
200199
image:
201-
description: Image to use in all containers (including Init Containers)
200+
description: Custom image to use in all containers (including
201+
Init Containers). It is your responsibility to make sure the
202+
image is from trusted sources and has been validated for security
203+
compliance
202204
type: string
203205
imagePullSecrets:
204206
description: Image Pull Secrets to use in all containers (including

config/manifests/bases/backstage-operator.clusterserviceversion.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ kind: ClusterServiceVersion
33
metadata:
44
annotations:
55
alm-examples: '[]'
6-
capabilities: Basic Install
6+
capabilities: Seamless Upgrades
7+
operatorframework.io/suggested-namespace: backstage-system
78
name: backstage-operator.v0.0.0
89
namespace: placeholder
910
spec:

config/rbac/role.yaml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ rules:
99
- ""
1010
resources:
1111
- configmaps
12-
- persistentvolumeclaims
13-
- persistentvolumes
14-
- secrets
1512
- services
1613
verbs:
1714
- create
@@ -20,6 +17,22 @@ rules:
2017
- list
2118
- update
2219
- watch
20+
- apiGroups:
21+
- ""
22+
resources:
23+
- persistentvolumeclaims
24+
- persistentvolumes
25+
verbs:
26+
- get
27+
- list
28+
- watch
29+
- apiGroups:
30+
- ""
31+
resources:
32+
- secrets
33+
verbs:
34+
- create
35+
- delete
2336
- apiGroups:
2437
- apps
2538
resources:

controllers/backstage_controller.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"k8s.io/apimachinery/pkg/runtime"
3232
"k8s.io/apimachinery/pkg/types"
3333
"k8s.io/apimachinery/pkg/util/yaml"
34+
"k8s.io/client-go/kubernetes"
3435
"k8s.io/utils/pointer"
3536
ctrl "sigs.k8s.io/controller-runtime"
3637
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -44,6 +45,9 @@ const (
4445
// BackstageReconciler reconciles a Backstage object
4546
type BackstageReconciler struct {
4647
client.Client
48+
49+
Clientset *kubernetes.Clientset
50+
4751
Scheme *runtime.Scheme
4852
// If true, Backstage Controller always sync the state of runtime objects created
4953
// otherwise, runtime objects can be re-configured independently
@@ -64,10 +68,12 @@ type BackstageReconciler struct {
6468
//+kubebuilder:rbac:groups=janus-idp.io,resources=backstages,verbs=get;list;watch;create;update;patch;delete
6569
//+kubebuilder:rbac:groups=janus-idp.io,resources=backstages/status,verbs=get;update;patch
6670
//+kubebuilder:rbac:groups=janus-idp.io,resources=backstages/finalizers,verbs=update
67-
//+kubebuilder:rbac:groups="",resources=configmaps;secrets;persistentvolumes;persistentvolumeclaims;services,verbs=get;watch;create;update;list;delete
68-
//+kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;watch;create;update;list;delete
69-
//+kubebuilder:rbac:groups="apps",resources=statefulsets,verbs=get;watch;create;update;list;delete
70-
//+kubebuilder:rbac:groups="route.openshift.io",resources=routes;routes/custom-host,verbs=get;watch;create;update;list;delete
71+
//+kubebuilder:rbac:groups="",resources=configmaps;services,verbs=get;list;watch;create;update;delete
72+
//+kubebuilder:rbac:groups="",resources=persistentvolumes;persistentvolumeclaims,verbs=get;list;watch
73+
//+kubebuilder:rbac:groups="",resources=secrets,verbs=create;delete
74+
//+kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;delete
75+
//+kubebuilder:rbac:groups="apps",resources=statefulsets,verbs=get;list;watch;create;update;delete
76+
//+kubebuilder:rbac:groups="route.openshift.io",resources=routes;routes/custom-host,verbs=get;list;watch;create;update;delete
7177

7278
// Reconcile is part of the main kubernetes reconciliation loop which aims to
7379
// move the current state of the cluster closer to the desired state.
@@ -293,6 +299,13 @@ func (r *BackstageReconciler) labels(meta *v1.ObjectMeta, backstage bs.Backstage
293299
// SetupWithManager sets up the controller with the Manager.
294300
func (r *BackstageReconciler) SetupWithManager(mgr ctrl.Manager, log logr.Logger) error {
295301

302+
clientset, err := kubernetes.NewForConfig(mgr.GetConfig())
303+
if err != nil {
304+
log.Error(err, "unable to create clientset")
305+
return err
306+
}
307+
r.Clientset = clientset
308+
296309
if len(r.PsqlImage) == 0 {
297310
r.PsqlImage = "quay.io/fedora/postgresql-15:latest"
298311
log.Info("Enviroment variable is not set, default is used", bs.EnvPostGresImage, r.PsqlImage)

controllers/backstage_controller_test.go

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ var _ = Describe("Backstage controller", func() {
6262

6363
backstageReconciler = &BackstageReconciler{
6464
Client: k8sClient,
65+
Clientset: k8sClientset,
6566
Scheme: k8sClient.Scheme(),
6667
Namespace: ns,
6768
OwnsRuntime: true,
@@ -852,9 +853,19 @@ plugins: []
852853
Context("Extra Files", func() {
853854
for _, kind := range []string{"ConfigMap", "Secret"} {
854855
kind := kind
855-
When(fmt.Sprintf("referencing non-existing %s as extra-file", kind), func() {
856+
name := "a-" + strings.ToLower(kind)
857+
title := ""
858+
errExpected := ""
859+
switch kind {
860+
case "ConfigMap":
861+
title = fmt.Sprintf("referencing non-existing %s as extra-file without key", kind)
862+
errExpected = fmt.Sprintf("configmaps \"%s\" not found", name)
863+
case "Secret":
864+
title = fmt.Sprintf("referencing %s as extra-file without key", kind)
865+
errExpected = fmt.Sprintf("key is required to mount extra file with secret %s", name)
866+
}
867+
When(title, func() {
856868
var backstage *bsv1alpha1.Backstage
857-
name := "a-non-existing-" + strings.ToLower(kind)
858869

859870
BeforeEach(func() {
860871
var (
@@ -891,9 +902,8 @@ plugins: []
891902
NamespacedName: types.NamespacedName{Name: backstageName, Namespace: ns},
892903
})
893904
Expect(err).To(HaveOccurred())
894-
errStr := fmt.Sprintf("failed to add volume mounts to Backstage deployment, reason: %ss \"%s\" not found", strings.ToLower(kind), name)
895-
Expect(err.Error()).Should(ContainSubstring(errStr))
896-
verifyBackstageInstanceError(ctx, errStr)
905+
Expect(err.Error()).Should(ContainSubstring(errExpected))
906+
verifyBackstageInstanceError(ctx, errExpected)
897907

898908
By("Not creating a Backstage Deployment")
899909
Consistently(func() error {
@@ -903,13 +913,11 @@ plugins: []
903913
})
904914
})
905915
}
906-
907916
for _, mountPath := range []string{"", "/some/path/for/extra/config"} {
908917
mountPath := mountPath
909918
When("referencing ConfigMaps and Secrets for extra files - mountPath="+mountPath, func() {
910919
const (
911920
extraConfig1CmNameAll = "my-extra-config-1-cm-all"
912-
extraConfig2SecretNameAll = "my-extra-config-2-secret-all"
913921
extraConfig1CmNameSingle = "my-extra-config-1-cm-single"
914922
extraConfig2SecretNameSingle = "my-extra-config-2-secret-single"
915923
)
@@ -928,17 +936,6 @@ plugins: []
928936
err := k8sClient.Create(ctx, extraConfig1CmAll)
929937
Expect(err).To(Not(HaveOccurred()))
930938

931-
extraConfig2SecretAll := buildSecret(extraConfig2SecretNameAll, map[string][]byte{
932-
"my-extra-config-21.yaml": []byte(`
933-
# my-extra-config-21.yaml
934-
`),
935-
"my-extra-config-22.yaml": []byte(`
936-
# my-extra-config-22.yaml
937-
`),
938-
})
939-
err = k8sClient.Create(ctx, extraConfig2SecretAll)
940-
Expect(err).To(Not(HaveOccurred()))
941-
942939
extraConfig1CmSingle := buildConfigMap(extraConfig1CmNameSingle, map[string]string{
943940
"my-extra-file-11-single.yaml": `
944941
# my-extra-file-11-single.yaml
@@ -970,7 +967,6 @@ plugins: []
970967
{Name: extraConfig1CmNameSingle, Key: "my-extra-file-12-single.yaml"},
971968
},
972969
Secrets: []bsv1alpha1.ObjectKeyRef{
973-
{Name: extraConfig2SecretNameAll},
974970
{Name: extraConfig2SecretNameSingle, Key: "my-extra-file-22-single.yaml"},
975971
},
976972
},
@@ -1012,7 +1008,7 @@ plugins: []
10121008
})
10131009

10141010
By("Checking the Volumes in the Backstage Deployment", func() {
1015-
Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(8))
1011+
Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(7))
10161012

10171013
backendAuthAppConfigVol, ok := findVolume(found.Spec.Template.Spec.Volumes, backendAuthConfigName)
10181014
Expect(ok).To(BeTrue(), "No volume found with name: %s", backendAuthConfigName)
@@ -1026,12 +1022,6 @@ plugins: []
10261022
Expect(extraConfig1CmVol.VolumeSource.ConfigMap.DefaultMode).To(HaveValue(Equal(int32(420))))
10271023
Expect(extraConfig1CmVol.VolumeSource.ConfigMap.LocalObjectReference.Name).To(Equal(extraConfig1CmNameAll))
10281024

1029-
extraConfig2SecretVol, ok := findVolume(found.Spec.Template.Spec.Volumes, extraConfig2SecretNameAll)
1030-
Expect(ok).To(BeTrue(), "No volume found with name: %s", extraConfig2SecretNameAll)
1031-
Expect(extraConfig2SecretVol.VolumeSource.ConfigMap).To(BeNil())
1032-
Expect(extraConfig2SecretVol.VolumeSource.Secret.DefaultMode).To(HaveValue(Equal(int32(420))))
1033-
Expect(extraConfig2SecretVol.VolumeSource.Secret.SecretName).To(Equal(extraConfig2SecretNameAll))
1034-
10351025
extraConfig1SingleCmVol, ok := findVolume(found.Spec.Template.Spec.Volumes, extraConfig1CmNameSingle)
10361026
Expect(ok).To(BeTrue(), "No volume found with name: %s", extraConfig1CmNameSingle)
10371027
Expect(extraConfig1SingleCmVol.VolumeSource.Secret).To(BeNil())
@@ -1051,13 +1041,12 @@ plugins: []
10511041

10521042
// Extra config mounted in the main container
10531043
Expect(findVolumeMounts(initCont.VolumeMounts, extraConfig1CmNameAll)).Should(HaveLen(0))
1054-
Expect(findVolumeMounts(initCont.VolumeMounts, extraConfig2SecretNameAll)).Should(HaveLen(0))
10551044
})
10561045

10571046
mainCont := found.Spec.Template.Spec.Containers[0]
10581047

10591048
By("Checking the main container Volume Mounts in the Backstage Deployment", func() {
1060-
Expect(mainCont.VolumeMounts).To(HaveLen(8))
1049+
Expect(mainCont.VolumeMounts).To(HaveLen(6))
10611050

10621051
expectedMountPath := mountPath
10631052
if expectedMountPath == "" {
@@ -1083,20 +1072,6 @@ plugins: []
10831072
Equal("my-extra-config-12.yaml")))
10841073
}
10851074

1086-
extraConfig2SecretMounts := findVolumeMounts(mainCont.VolumeMounts, extraConfig2SecretNameAll)
1087-
Expect(extraConfig2SecretMounts).To(HaveLen(2), "No volume mounts found with name: %s", extraConfig2SecretNameAll)
1088-
Expect(extraConfig2SecretMounts[0].MountPath).ToNot(Equal(extraConfig2SecretMounts[1].MountPath))
1089-
for i := 0; i <= 1; i++ {
1090-
Expect(extraConfig2SecretMounts[i].MountPath).To(
1091-
SatisfyAny(
1092-
Equal(expectedMountPath+"/my-extra-config-21.yaml"),
1093-
Equal(expectedMountPath+"/my-extra-config-22.yaml")))
1094-
Expect(extraConfig2SecretMounts[i].SubPath).To(
1095-
SatisfyAny(
1096-
Equal("my-extra-config-21.yaml"),
1097-
Equal("my-extra-config-22.yaml")))
1098-
}
1099-
11001075
extraConfig1CmSingleMounts := findVolumeMounts(mainCont.VolumeMounts, extraConfig1CmNameSingle)
11011076
Expect(extraConfig1CmSingleMounts).To(HaveLen(1), "No volume mounts found with name: %s", extraConfig1CmNameSingle)
11021077
Expect(extraConfig1CmSingleMounts[0].MountPath).To(Equal(expectedMountPath + "/my-extra-file-12-single.yaml"))

0 commit comments

Comments
 (0)