Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OADP-3189: do not remove labels from OADP namespace #1274

8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,15 @@ $(ENVTEST): ## Download envtest-setup locally if necessary.
.PHONY: envtest
envtest: $(ENVTEST)

# If test results in prow are different, it is because the environment used.
# You can simulate their env by running
# docker run --platform linux/amd64 -w $PWD -v $PWD:$PWD -it registry.ci.openshift.org/ocp/builder:rhel-8-golang-1.20-openshift-4.14 sh -c "make test"
# where the image corresponds to the prow config for the test job, https://github.com/openshift/release/blob/master/ci-operator/config/openshift/oadp-operator/openshift-oadp-operator-master.yaml#L1-L5
# to login to registry cluster follow https://docs.ci.openshift.org/docs/how-tos/use-registries-in-build-farm/#how-do-i-log-in-to-pull-images-that-require-authentication
# If bin/ contains binaries of different arch, you may remove them so the container can install their arch.
.PHONY: test
test: vet envtest ## Run Go linter and unit tests and check Go code format and if api and bundle folders are up to date.
KUBEBUILDER_ASSETS="$(ENVTESTPATH)" go test -mod=mod ./controllers/... ./pkg/... -coverprofile cover.out
KUBEBUILDER_ASSETS="$(ENVTESTPATH)" go test -mod=mod $(shell go list -mod=mod ./... | grep -v /tests/e2e) -coverprofile cover.out
@make fmt-isupdated
@make api-isupdated
@make bundle-isupdated
Expand Down
91 changes: 50 additions & 41 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,21 @@ package main

import (
"context"
"encoding/json"
"flag"
"fmt"
"os"

"github.com/openshift/oadp-operator/pkg/common"
monitor "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes"
"os"
"k8s.io/client-go/rest"
client "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
Expand Down Expand Up @@ -57,12 +60,22 @@ var (
setupLog = ctrl.Log.WithName("setup")
)

// WebIdentityTokenPath mount present on operator CSV
const WebIdentityTokenPath = "/var/run/secrets/openshift/serviceaccount/token"
const (
// WebIdentityTokenPath mount present on operator CSV
WebIdentityTokenPath = "/var/run/secrets/openshift/serviceaccount/token"

// CloudCredentials API constants
CloudCredentialGroupVersion = "cloudcredential.openshift.io/v1"
CloudCredentialsCRDName = "credentialsrequests"

// CloudCredentials API constants
const CloudCredentialGroupVersion = "cloudcredential.openshift.io/v1"
const CloudCredentialsCRDName = "credentialsrequests"
// Pod security admission (PSA) labels
psaLabelPrefix = "pod-security.kubernetes.io/"
enforceLabel = psaLabelPrefix + "enforce"
auditLabel = psaLabelPrefix + "audit"
warnLabel = psaLabelPrefix + "warn"

privileged = "privileged"
)

func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
Expand Down Expand Up @@ -94,8 +107,15 @@ func main() {
"the manager will watch and manage resources in all namespaces")
}

kubeconf := ctrl.GetConfigOrDie()
clientset, err := kubernetes.NewForConfig(kubeconf)
if err != nil {
setupLog.Error(err, "problem getting client")
os.Exit(1)
}

// setting privileged pod security labels to operator ns
err = addPodSecurityPrivilegedLabels(watchNamespace)
err = addPodSecurityPrivilegedLabels(watchNamespace, clientset)
if err != nil {
setupLog.Error(err, "error setting privileged pod security labels to operator namespace")
os.Exit(1)
Expand All @@ -110,7 +130,7 @@ func main() {

// check if cred request API exists in the cluster before creating a cred request
setupLog.Info("Checking if credentialsrequest CRD exists in the cluster")
credReqCRDExists, err := DoesCRDExist(CloudCredentialGroupVersion, CloudCredentialsCRDName)
credReqCRDExists, err := DoesCRDExist(CloudCredentialGroupVersion, CloudCredentialsCRDName, kubeconf)
if err != nil {
setupLog.Error(err, "problem checking the existence of CredentialRequests CRD")
os.Exit(1)
Expand All @@ -119,7 +139,7 @@ func main() {
if credReqCRDExists {
// create cred request
setupLog.Info(fmt.Sprintf("Creating credentials request for role: %s, and WebIdentityTokenPath: %s", roleARN, WebIdentityTokenPath))
if err := CreateCredRequest(roleARN, WebIdentityTokenPath, watchNamespace); err != nil {
if err := CreateCredRequest(roleARN, WebIdentityTokenPath, watchNamespace, kubeconf); err != nil {
if !errors.IsAlreadyExists(err) {
setupLog.Error(err, "unable to create credRequest")
os.Exit(1)
Expand All @@ -128,7 +148,7 @@ func main() {
}
}

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
mgr, err := ctrl.NewManager(kubeconf, ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
Expand Down Expand Up @@ -222,46 +242,36 @@ func getWatchNamespace() (string, error) {
return ns, nil
}

// setting privileged pod security labels to OADP operator namespace
func addPodSecurityPrivilegedLabels(watchNamespaceName string) error {
setupLog.Info("patching operator namespace with PSA labels")
// setting Pod security admission (PSA) labels to privileged in OADP operator namespace
func addPodSecurityPrivilegedLabels(watchNamespaceName string, clientset kubernetes.Interface) error {
setupLog.Info("patching operator namespace with Pod security admission (PSA) labels to privileged")

if len(watchNamespaceName) == 0 {
return fmt.Errorf("cannot add privileged pod security labels, watchNamespaceName is empty")
return fmt.Errorf("cannot patch operator namespace with PSA labels to privileged, watchNamespaceName is empty")
}

kubeconf := ctrl.GetConfigOrDie()
clientset, err := kubernetes.NewForConfig(kubeconf)
if err != nil {
setupLog.Error(err, "problem getting client")
return err
}

operatorNamespace, err := clientset.CoreV1().Namespaces().Get(context.TODO(), watchNamespaceName, metav1.GetOptions{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One get call eliminated :D

nsPatch, err := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]string{
enforceLabel: privileged,
auditLabel: privileged,
warnLabel: privileged,
},
},
})
if err != nil {
setupLog.Error(err, "problem getting operator namespace")
setupLog.Error(err, "problem marshalling patches")
return err
}

privilegedLabels := map[string]string{
"pod-security.kubernetes.io/enforce": "privileged",
"pod-security.kubernetes.io/audit": "privileged",
"pod-security.kubernetes.io/warn": "privileged",
}

operatorNamespace.SetLabels(privilegedLabels)

_, err = clientset.CoreV1().Namespaces().Update(context.TODO(), operatorNamespace, metav1.UpdateOptions{})
_, err = clientset.CoreV1().Namespaces().Patch(context.TODO(), watchNamespaceName, types.StrategicMergePatchType, nsPatch, metav1.PatchOptions{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StrategicMergePatchType is naturally additive and should not remove existing labels as confirmed by the unit test.

if err != nil {
setupLog.Error(err, "problem patching operator namespace for privileged pod security labels")
setupLog.Error(err, "problem patching operator namespace with PSA labels to privileged")
return err
}
return nil
}

func DoesCRDExist(CRDGroupVersion, CRDName string) (bool, error) {
kubeconf := ctrl.GetConfigOrDie()

func DoesCRDExist(CRDGroupVersion, CRDName string, kubeconf *rest.Config) (bool, error) {
discoveryClient, err := discovery.NewDiscoveryClientForConfig(kubeconf)
if err != nil {
return false, err
Expand All @@ -287,9 +297,8 @@ func DoesCRDExist(CRDGroupVersion, CRDName string) (bool, error) {
}

// CreateCredRequest WITP : WebIdentityTokenPath
func CreateCredRequest(roleARN string, WITP string, secretNS string) error {
cfg := config.GetConfigOrDie()
client, err := client.New(cfg, client.Options{})
func CreateCredRequest(roleARN string, WITP string, secretNS string, kubeconf *rest.Config) error {
client, err := client.New(kubeconf, client.Options{})
if err != nil {
setupLog.Error(err, "unable to create client")
}
Expand Down
114 changes: 114 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
Copyright 2021.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"context"
"testing"

coreV1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)

// Tests that addPodSecurityPrivilegedLabels do not override the existing labels in OADP namespace
func TestAddPodSecurityPrivilegedLabels(t *testing.T) {
var testNamespaceName = "openshift-adp"
tests := []struct {
name string
existingLabels map[string]string
expectedLabels map[string]string
}{
{
name: "PSA labels do not exist in the namespace",
existingLabels: map[string]string{
"existing-label": "existing-value",
},
expectedLabels: map[string]string{
"existing-label": "existing-value",
enforceLabel: privileged,
auditLabel: privileged,
warnLabel: privileged,
},
},
{
name: "PSA labels exist in the namespace, but are not set to privileged",
existingLabels: map[string]string{
"user-label": "user-value",
enforceLabel: "baseline",
auditLabel: "baseline",
warnLabel: "baseline",
},
expectedLabels: map[string]string{
"user-label": "user-value",
enforceLabel: privileged,
auditLabel: privileged,
warnLabel: privileged,
},
},
{
name: "PSA labels exist in the namespace, and are set to privileged",
existingLabels: map[string]string{
"another-label": "another-value",
enforceLabel: privileged,
auditLabel: privileged,
warnLabel: privileged,
},
expectedLabels: map[string]string{
"another-label": "another-value",
enforceLabel: privileged,
auditLabel: privileged,
warnLabel: privileged,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a new namespace with the existing labels
namespace := coreV1.Namespace{
ObjectMeta: v1.ObjectMeta{
Name: testNamespaceName,
Labels: tt.existingLabels,
},
}
testClient := fake.NewSimpleClientset(&namespace)
err := addPodSecurityPrivilegedLabels(testNamespaceName, testClient)
if err != nil {
t.Errorf("addPodSecurityPrivilegedLabels() error = %v", err)
}
testNamespace, err := testClient.CoreV1().Namespaces().Get(context.TODO(), testNamespaceName, v1.GetOptions{})
if err != nil {
t.Errorf("Get test namespace error = %v", err)
}
// assert that existing labels are not overridden
for key, value := range tt.existingLabels {
if testNamespace.Labels[key] != value {
// only error if changing non PSA labels
if key != enforceLabel && key != auditLabel && key != warnLabel {
t.Errorf("namespace label %v has value %v, instead of %v", key, testNamespace.Labels[key], value)
}
}
}
for key, value := range tt.expectedLabels {
if testNamespace.Labels[key] != value {
t.Errorf("namespace label %v has value %v, instead of %v", key, testNamespace.Labels[key], value)
}
}
})
}
}