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

Adding support for anyuid annotation in case of OCP #401

Merged
merged 1 commit into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion 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 @@ -542,6 +542,12 @@ func buildJob(jobName string, jobOptions drivers.JobOpts) (*batchv1.Job, error)
logrus.Errorf("%s: %v", fn, errMsg)
return nil, fmt.Errorf(errMsg)
}

if err := utils.SetupRoleBindingForSCC(jobName, resourceNamespace, jobOptions.SourcePVCName); err != nil {
errMsg := fmt.Sprintf("error creating role binding %s/%s: %v", resourceNamespace, jobName, err)
logrus.Errorf("%s: %v", fn, errMsg)
return nil, fmt.Errorf(errMsg)
}
return jobFor(
jobOptions,
jobName,
Expand Down
8 changes: 7 additions & 1 deletion pkg/drivers/kopiarestore/kopiarestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ func jobFor(
return nil, err
}

if err := utils.SetupRoleBindingForSCC(jobName, jobOption.Namespace, jobOption.DestinationPVCName); err != nil {
errMsg := fmt.Sprintf("error creating role binding %s/%s: %v", jobOption.Namespace, jobName, err)
logrus.Errorf(errMsg)
return nil, fmt.Errorf(errMsg)
}

cmd := strings.Join([]string{
"/kopiaexecutor",
"restore",
Expand Down Expand Up @@ -297,7 +303,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
8 changes: 7 additions & 1 deletion pkg/drivers/nfsbackup/nfsbackup.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ func buildJob(
return nil, fmt.Errorf(errMsg)
}

if err := utils.SetupRoleBindingForSCC(jobOptions.RestoreExportName, jobOptions.Namespace, jobOptions.SourcePVCName); err != nil {
errMsg := fmt.Sprintf("error creating role binding %s/%s: %v", jobOptions.Namespace, jobOptions.RestoreExportName, err)
logrus.Errorf("%s: %v", funct, errMsg)
return nil, fmt.Errorf(errMsg)
}

resources, err := utils.NFSResourceRequirements(jobOptions.JobConfigMap, jobOptions.JobConfigMapNs)
if err != nil {
return nil, err
Expand Down Expand Up @@ -300,7 +306,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
8 changes: 7 additions & 1 deletion pkg/drivers/nfscsirestore/nfscsirestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ func buildJob(
return nil, fmt.Errorf(errMsg)
}

if err := utils.SetupRoleBindingForSCC(jobName, jobOptions.Namespace, jobOptions.DestinationPVCName); err != nil {
errMsg := fmt.Sprintf("error creating role binding %s/%s: %v", jobOptions.Namespace, jobName, err)
logrus.Errorf("%s: %v", funct, errMsg)
return nil, fmt.Errorf(errMsg)
}

resources, err := utils.NFSResourceRequirements(jobOptions.JobConfigMap, jobOptions.JobConfigMapNs)
if err != nil {
return nil, err
Expand Down Expand Up @@ -277,7 +283,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
8 changes: 7 additions & 1 deletion pkg/drivers/nfsrestore/nfsrestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ func buildJob(
return nil, fmt.Errorf(errMsg)
}

if err := utils.SetupRoleBindingForSCC(jobOptions.RestoreExportName, jobOptions.Namespace, jobOptions.DestinationPVCName); err != nil {
errMsg := fmt.Sprintf("error creating role binding %s/%s: %v", jobOptions.Namespace, jobOptions.RestoreExportName, err)
logrus.Errorf("%s: %v", funct, errMsg)
return nil, fmt.Errorf(errMsg)
}

resources, err := utils.NFSResourceRequirements(jobOptions.JobConfigMap, jobOptions.JobConfigMapNs)
if err != nil {
return nil, err
Expand Down Expand Up @@ -323,7 +329,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.DestinationPVCName, jobOption.Namespace)
if err != nil {
return nil, err
}
Expand Down
73 changes: 73 additions & 0 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 @@ -136,6 +138,49 @@ func SetupServiceAccount(name, namespace string, role *rbacv1.Role) error {
return nil
}

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

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

provisionersListToUseAnyUid, err := GetArrayConfigValue(KdmpConfigmapName, KdmpConfigmapNamespace, 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
}
}
}
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) {
// 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 +189,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 Down Expand Up @@ -218,6 +266,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
67 changes: 66 additions & 1 deletion pkg/drivers/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ const (
PxbJobFailureRetryTimeoutKey = "MOUNT_FAILURE_RETRY_TIMEOUT"
// PxbDefaultJobFailureRetryTimeout default timeout after job failure due to mount failure
PxbDefaultJobFailureRetryTimeout = "30"
provisionersToUseAnyUid = "PROVISIONERS_TO_USE_ANYUID"
pvcStorageProvisionerKey = "volume.kubernetes.io/storage-provisioner"
)

var (
Expand Down Expand Up @@ -1037,7 +1039,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 @@ -1051,6 +1053,36 @@ func AddSecurityContextToJob(job *batchv1.Job, podUserId, podGroupId string) (*b
if err != nil {
return nil, err
}

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

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

if len(provisionersListToUseAnyUid) > 0 {
if isOcp && contains(provisionersListToUseAnyUid, provisionerName) {
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 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 @@ -1219,3 +1251,36 @@ func UpdateJobFailureTimeOut(jobConfigMap, jobConfigMapNs string) {
logrus.Debugf("%v: failed to parse the failure timeout set %v: %v", fn, JobFailureRetryTimeout, err)
}
}

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

// GetArrayConfigValue reads the configMap and returns the an array value of requested parameter
// If error in reading from configmap, we try reading from env variable
// Ex: provisionersToUseAnyUid: provisioner1,provisioner2 will return ["provisioner1", "provisioner2"]
func GetArrayConfigValue(cm, ns, key string) ([]string, error) {
arrayDataAsString := GetConfigValue(cm, ns, key)

arrayData := strings.Split(arrayDataAsString, ",")
for k, str := range arrayData {
arrayData[k] = strings.TrimSpace(str)
}

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
}
Loading