Skip to content

Commit

Permalink
Adding support for anyuid annotation in case of OCP
Browse files Browse the repository at this point in the history
  • Loading branch information
dbinnal-px committed Nov 15, 2024
1 parent 918b8ba commit 17f9198
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 14 deletions.
4 changes: 2 additions & 2 deletions pkg/drivers/kopiabackup/kopiabackup.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ func jobFor(
}
// Add security Context only if the PSA is enabled.
if jobOption.PodUserId != "" || jobOption.PodGroupId != "" {
job, err = utils.AddSecurityContextToJob(job, jobOption.PodUserId, jobOption.PodGroupId)
job, err = utils.AddSecurityContextToJob(job, jobOption.PodUserId, jobOption.PodGroupId, jobOption.SourcePVCName, jobOption.SourcePVCNamespace)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -537,7 +537,7 @@ func buildJob(jobName string, jobOptions drivers.JobOpts) (*batchv1.Job, error)
}
}
resourceNamespace = jobOptions.Namespace
if err := utils.SetupServiceAccount(jobName, resourceNamespace, roleFor()); err != nil {
if err := utils.SetupServiceAccount(jobName, resourceNamespace, jobOptions.SourcePVCName, roleFor()); err != nil {
errMsg := fmt.Sprintf("error creating service account %s/%s: %v", resourceNamespace, jobName, err)
logrus.Errorf("%s: %v", fn, errMsg)
return nil, fmt.Errorf(errMsg)
Expand Down
4 changes: 2 additions & 2 deletions pkg/drivers/kopiarestore/kopiarestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func jobFor(
return nil, err
}

if err := utils.SetupServiceAccount(jobName, jobOption.Namespace, roleFor()); err != nil {
if err := utils.SetupServiceAccount(jobName, jobOption.Namespace, jobOption.DestinationPVCName, roleFor()); err != nil {
return nil, err
}

Expand Down Expand Up @@ -297,7 +297,7 @@ func jobFor(
}
// Add security Context only if the PSA is enabled.
if jobOption.PodUserId != "" || jobOption.PodGroupId != "" {
job, err = utils.AddSecurityContextToJob(job, jobOption.PodUserId, jobOption.PodGroupId)
job, err = utils.AddSecurityContextToJob(job, jobOption.PodUserId, jobOption.PodGroupId, jobOption.DestinationPVCName, jobOption.Namespace)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/drivers/nfsbackup/nfsbackup.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func buildJob(
funct := "NfsbuildJob"
// Setup service account using same role permission as stork role
logrus.Infof("Inside %s function", funct)
if err := utils.SetupNFSServiceAccount(jobOptions.RestoreExportName, jobOptions.Namespace, roleFor()); err != nil {
if err := utils.SetupNFSServiceAccount(jobOptions.RestoreExportName, jobOptions.Namespace, jobOptions.SourcePVCName, roleFor()); err != nil {
errMsg := fmt.Sprintf("error creating service account %s/%s: %v", jobOptions.Namespace, jobOptions.RestoreExportName, err)
logrus.Errorf("%s: %v", funct, errMsg)
return nil, fmt.Errorf(errMsg)
Expand Down Expand Up @@ -300,7 +300,7 @@ func jobForBackupResource(
// Not passing the groupId as we do not want to set the RunAsGroup field in the securityContext
// This helps us in setting the primaryGroup ID to root for the user ID.
if uid != "" {
job, err = utils.AddSecurityContextToJob(job, uid, "")
job, err = utils.AddSecurityContextToJob(job, uid, "", jobOption.SourcePVCName, jobOption.SourcePVCNamespace)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/drivers/nfscsirestore/nfscsirestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func buildJob(
logrus.Infof("Inside %s function", funct)

jobName := utils.GetCsiRestoreJobName(drivers.NFSCSIRestore, jobOptions.DataExportName)
if err := utils.SetupNFSServiceAccount(jobName, jobOptions.Namespace, roleFor()); err != nil {
if err := utils.SetupNFSServiceAccount(jobName, jobOptions.Namespace, jobOptions.DestinationPVCName, roleFor()); err != nil {
errMsg := fmt.Sprintf("error creating service account %s/%s: %v", jobOptions.Namespace, jobOptions.DataExportName, err)
logrus.Errorf("%s: %v", funct, errMsg)
return nil, fmt.Errorf(errMsg)
Expand Down Expand Up @@ -277,7 +277,7 @@ func jobForRestoreCSISnapshot(
}
// Add security Context only if the PSA is enabled.
if jobOption.PodUserId != "" || jobOption.PodGroupId != "" {
job, err = utils.AddSecurityContextToJob(job, jobOption.PodUserId, jobOption.PodGroupId)
job, err = utils.AddSecurityContextToJob(job, jobOption.PodUserId, jobOption.PodGroupId, jobOption.DestinationPVCName, jobOption.Namespace)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/drivers/nfsrestore/nfsrestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func buildJob(
funct := "NfsbuildJob"
// Setup service account using same role permission as stork role
logrus.Infof("Inside %s function", funct)
if err := utils.SetupNFSServiceAccount(jobOptions.RestoreExportName, jobOptions.Namespace, roleFor()); err != nil {
if err := utils.SetupNFSServiceAccount(jobOptions.RestoreExportName, jobOptions.Namespace, jobOptions.DestinationPVCName, roleFor()); err != nil {
errMsg := fmt.Sprintf("error creating service account %s/%s: %v", jobOptions.Namespace, jobOptions.RestoreExportName, err)
logrus.Errorf("%s: %v", funct, errMsg)
return nil, fmt.Errorf(errMsg)
Expand Down Expand Up @@ -323,7 +323,7 @@ func jobForRestoreResource(
}
// Not passing the groupId as we do not want to set the RunAsGroup field in the securityContext
// This helps us in setting the primaryGroup ID to root for the user ID.
job, err = utils.AddSecurityContextToJob(job, utils.KdmpJobUid, "")
job, err = utils.AddSecurityContextToJob(job, utils.KdmpJobUid, "", jobOption.SourcePVCName, jobOption.SourcePVCNamespace)
if err != nil {
return nil, err
}
Expand Down
127 changes: 125 additions & 2 deletions pkg/drivers/utils/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const (
// BurstKey - configmap burst key name
BurstKey = "K8S_BURST"
k8sMinVersionSASecretTokenNotSupport = "1.24"
SccRoleBindingNameSuffix = "-scc"
AnyUidClusterRoleName = "system:openshift:scc:anyuid"
)

var (
Expand Down Expand Up @@ -81,7 +83,7 @@ func isServiceAccountSecretMissing() (bool, error) {
}

// SetupServiceAccount create a service account and bind it to a provided role.
func SetupServiceAccount(name, namespace string, role *rbacv1.Role) error {
func SetupServiceAccount(name, namespace, pvcName string, role *rbacv1.Role) error {
if role != nil {
role.Name, role.Namespace = name, namespace
role.Annotations = map[string]string{
Expand All @@ -93,6 +95,38 @@ func SetupServiceAccount(name, namespace string, role *rbacv1.Role) error {
if _, err := rbacops.Instance().CreateRoleBinding(roleBindingFor(name, namespace)); err != nil && !errors.IsAlreadyExists(err) {
return fmt.Errorf("create %s/%s rolebinding: %s", namespace, name, err)
}

_, _, isOCP, err := GetOcpNsUidGid(namespace, "", "")
if err != nil {
return fmt.Errorf("failed to check if cluster is OCP: %v", err)
}

// read the kdmp-config configmap to read a key named PROVISIONER_TO_USE_ANYUID
// If it is true only then exercise below code else return without doing anything
kdmpData, err := coreops.Instance().GetConfigMap(KdmpConfig, defaultPXNamespace)
if err != nil {
logrus.Tracef("error reading kdmp config map: %v", err)
return err
}

provisionerName, err := GetProvisionerNameFromPvc(pvcName, namespace)
if err != nil {
return fmt.Errorf("failed to get provisioner name from pvc: %v", err)
}

provisionersListToUseAnyUid, err := extractArrayFromConfigMap(kdmpData, provisionersToUseAnyUid)
if err != nil {
logrus.Errorf("failed to extract provisioners list from configmap: %v", err)
return err
}
if len(provisionersListToUseAnyUid) > 0 {
if isOCP && contains(provisionersListToUseAnyUid, provisionerName) {
failed, err := addRoleBindingForScc(name, namespace, AnyUidClusterRoleName)
if failed {
return err
}
}
}
}
var sa *corev1.ServiceAccount
var err error
Expand Down Expand Up @@ -136,6 +170,36 @@ func SetupServiceAccount(name, namespace string, role *rbacv1.Role) error {
return nil
}

// Check if corresponding SCC cluster role exists, then only create rolebinding for it.
// This way we will avoid creating rolebinding in non-ocp cluster.
func addRoleBindingForScc(name string, namespace string, sccClusterRoleName string) (bool, error) {
// read the kdmp-config configmap to read a key named KDMP_JOB_WITH_ANYUID
// If it is true only then exercise below code else return without doing anything
kdmpConfigMap, err := coreops.Instance().GetConfigMap(KdmpConfigmapName, KdmpConfigmapNamespace)
if err != nil {
if errors.IsNotFound(err) {
return false, nil
}
return true, fmt.Errorf("get %s/%s configmap: %s", KdmpConfigmapNamespace, KdmpConfigmapName, err)
}

provisioners, ok := kdmpConfigMap.Data[provisionersToUseAnyUid]
if !ok || len(provisioners) == 0 {
return false, nil
}
// Check if the cluster role exists for the given SCC
if _, err := rbacops.Instance().GetClusterRole(sccClusterRoleName); err == nil {
if _, err := rbacops.Instance().CreateRoleBinding(roleBindingForScc(name, namespace, sccClusterRoleName)); err != nil && !errors.IsAlreadyExists(err) {
return true, fmt.Errorf("create %s/%s rolebinding: %s", namespace, name+SccRoleBindingNameSuffix, err)
}
} else {
if !errors.IsNotFound(err) {
return true, fmt.Errorf("get anyuid clusterrole %s failed: %s", AnyUidClusterRoleName, err)
}
}
return false, nil
}

// CleanServiceAccount removes a service account with a corresponding role and rolebinding.
func CleanServiceAccount(name, namespace string) error {
if err := rbacops.Instance().DeleteRole(name, namespace); err != nil && !errors.IsNotFound(err) {
Expand All @@ -144,6 +208,9 @@ func CleanServiceAccount(name, namespace string) error {
if err := rbacops.Instance().DeleteRoleBinding(name, namespace); err != nil && !errors.IsNotFound(err) {
return fmt.Errorf("delete %s/%s rolebinding: %s", namespace, name, err)
}
if err := rbacops.Instance().DeleteRoleBinding(name+SccRoleBindingNameSuffix, namespace); err != nil && !errors.IsNotFound(err) {
return fmt.Errorf("delete %s/%s rolebinding: %s", namespace, name, err)
}
if err := coreops.Instance().DeleteServiceAccount(name, namespace); err != nil && !errors.IsNotFound(err) {
return fmt.Errorf("delete %s/%s serviceaccount: %s", namespace, name, err)
}
Expand All @@ -157,7 +224,7 @@ func CleanServiceAccount(name, namespace string) error {
}

// SetupNFSServiceAccount create a service account and bind it to a provided role.
func SetupNFSServiceAccount(name, namespace string, role *rbacv1.ClusterRole) error {
func SetupNFSServiceAccount(name, namespace, pvcName string, role *rbacv1.ClusterRole) error {
if role != nil {
role.Name, role.Namespace = name, namespace
role.Annotations = map[string]string{
Expand All @@ -169,6 +236,37 @@ func SetupNFSServiceAccount(name, namespace string, role *rbacv1.ClusterRole) er
if _, err := rbacops.Instance().CreateClusterRoleBinding(clusterRoleBindingFor(name, namespace)); err != nil && !errors.IsAlreadyExists(err) {
return fmt.Errorf("create %s/%s cluster rolebinding: %s", namespace, name, err)
}

_, _, isOCP, err := GetOcpNsUidGid(namespace, "", "")
if err != nil {
return fmt.Errorf("failed to check if cluster is OCP: %v", err)
}
// read the kdmp-config configmap to read a key named PROVISIONERS_TO_USE_ANYUID
kdmpData, err := coreops.Instance().GetConfigMap(KdmpConfig, defaultPXNamespace)
if err != nil {
logrus.Tracef("error reading kdmp config map: %v", err)
return err
}

provisionerName, err := GetProvisionerNameFromPvc(pvcName, namespace)
if err != nil {
return fmt.Errorf("failed to get provisioner name from pvc: %v", err)
}

// If PROVISIONERS_TO_USE_ANYUID is set in kdmp-config, then add rolebinding for anyuid SCC
provisionersListToUseAnyUid, err := extractArrayFromConfigMap(kdmpData, provisionersToUseAnyUid)
if err != nil {
logrus.Errorf("failed to extract provisioners list from configmap: %v", err)
return err
}
if len(provisionersListToUseAnyUid) > 0 {
if isOCP && contains(provisionersListToUseAnyUid, provisionerName) {
failed, err := addRoleBindingForScc(name, namespace, AnyUidClusterRoleName)
if failed {
return err
}
}
}
}
var sa *corev1.ServiceAccount
var err error
Expand Down Expand Up @@ -218,6 +316,31 @@ func SetupNFSServiceAccount(name, namespace string, role *rbacv1.ClusterRole) er
return nil
}

// In OCP standard scc cluster role name are predefined and one pod can adhere to one SCC at a time.
func roleBindingForScc(name, namespace string, sccClusterRoleName string) *rbacv1.RoleBinding {
return &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: name + SccRoleBindingNameSuffix,
Namespace: namespace,
Annotations: map[string]string{
SkipResourceAnnotation: "true",
},
},
Subjects: []rbacv1.Subject{
{
Kind: rbacv1.ServiceAccountKind,
Name: name,
Namespace: namespace,
},
},
RoleRef: rbacv1.RoleRef{
Name: sccClusterRoleName,
Kind: "ClusterRole",
APIGroup: rbacv1.GroupName,
},
}
}

func roleBindingFor(name, namespace string) *rbacv1.RoleBinding {
return &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Expand Down
78 changes: 76 additions & 2 deletions pkg/drivers/utils/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package utils

import (
"encoding/json"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -78,7 +79,9 @@ const (
OcpGidRangeAnnotationKey = "openshift.io/sa.scc.supplemental-groups"
kopiaBackupString = "kopiaexecutor backup"
// if providerType in node spec has this string then it is GCP hosted cluster
GCPBasedClusterString = "gce://"
GCPBasedClusterString = "gce://"
provisionersToUseAnyUid = "PROVISIONERS_TO_USE_ANYUID"
pvcStorageProvisionerKey = "volume.kubernetes.io/storage-provisioner"
)

var (
Expand Down Expand Up @@ -1020,7 +1023,7 @@ func GetShortUID(uid string) string {
// If static uids like kdmpJobUid or kdmpJobGid is used that means
// these are dummy UIDs used for backing up resources to backuplocation
// which doesn't need specific UID specific permission.
func AddSecurityContextToJob(job *batchv1.Job, podUserId, podGroupId string) (*batchv1.Job, error) {
func AddSecurityContextToJob(job *batchv1.Job, podUserId, podGroupId, pvcName, pvcNamespace string) (*batchv1.Job, error) {
if job == nil {
return job, fmt.Errorf("recieved a nil job object to add security context")
}
Expand All @@ -1034,6 +1037,41 @@ func AddSecurityContextToJob(job *batchv1.Job, podUserId, podGroupId string) (*b
if err != nil {
return nil, err
}

// read the kdmp-config configmap to read a key named PROVISIONER_TO_USE_ANYUID
kdmpData, err := core.Instance().GetConfigMap(KdmpConfig, defaultPXNamespace)
if err != nil {
logrus.Tracef("error reading kdmp config map: %v", err)
return nil, err
}

// If PROVISIONERS_TO_USE_ANYUID is set in kdmp-config, then add rolebinding for anyuid SCC
provisionersListToUseAnyUid, err := extractArrayFromConfigMap(kdmpData, provisionersToUseAnyUid)
if err != nil {
logrus.Errorf("failed to extract provisioners list from configmap: %v", err)
return nil, err
}

// Get provisioner name from the pvcName, pvcNamespace
provisionerName, err := GetProvisionerNameFromPvc(pvcName, pvcNamespace)
if err != nil {
logrus.Errorf("failed to get storage class name for pvc [%s/%s]: %v", pvcNamespace, pvcName, err)
return nil, err
}

if len(provisionersListToUseAnyUid) > 0 {
logrus.Infof("PROVISIONERS_TO_USE_ANYUID is set to use, running the job %v with anyuid SCC", job.Name)
// Add the annotation to force the pod to adopt anyuid scc in OCP
// It may not work if the pod's SA doesn't have permission to use anyuid SCC
if isOcp && contains(provisionersListToUseAnyUid, provisionerName) {
if job.Spec.Template.Annotations == nil {
job.Spec.Template.Annotations = make(map[string]string)
}
job.Spec.Template.Annotations["openshift.io/required-scc"] = "anyuid"
return job, nil
}
}

// if the namespace is OCP, then overwrite the UID and GID from the namespace annotation
if isOcp {
podUserId = ocpUid
Expand Down Expand Up @@ -1178,3 +1216,39 @@ func GetAccessModeFromPvc(srcPvcName, srcPvcNameSpace string) ([]corev1.Persiste
accessModes := srcPvc.Status.AccessModes
return accessModes, nil
}

func GetProvisionerNameFromPvc(pvcName, pvcNamespace string) (string, error) {
pvc, err := core.Instance().GetPersistentVolumeClaim(pvcName, pvcNamespace)
if err != nil {
return "", err
}
provisionerName := pvc.Annotations[pvcStorageProvisionerKey]
logrus.Info("Deepa provisionerName: ", provisionerName)
return provisionerName, nil
}

func extractArrayFromConfigMap(configMap *corev1.ConfigMap, key string) ([]string, error) {
// Retrieve the JSON string from the ConfigMap
jsonData, ok := configMap.Data[key]
if !ok {
return nil, fmt.Errorf("key %s not found in ConfigMap", key)
}

// Parse the JSON string into a Go slice
var arrayData []string
if err := json.Unmarshal([]byte(jsonData), &arrayData); err != nil {
return nil, fmt.Errorf("failed to parse JSON: %v", err)
}

return arrayData, nil
}

// Helper function to check if a slice contains a string
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}

0 comments on commit 17f9198

Please sign in to comment.