Skip to content

Commit

Permalink
pb-6681: Add support for PSA in kdmp job
Browse files Browse the repository at this point in the history
- if psa enabled then extracted the uid and gid used by the POD.
  here the pod is choosen based on whichever PVC is used by that pod
- applied those uid and GID to all relevant job pod spec
- for baseline and privilege mode no restriction on UID/GID and
  default setting of SElinux and secomp is adopted in the job POD spec

Signed-off-by: Lalatendu Das <[email protected]>
  • Loading branch information
lalat-das committed Jun 13, 2024
1 parent 8461dc1 commit a382276
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 2 deletions.
21 changes: 21 additions & 0 deletions pkg/controllers/dataexport/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -1872,13 +1872,19 @@ func startTransferJob(
nfsServerAddr string
nfsExportPath string
nfsMountOption string
psaJobUid string
psaJobGid string
)

if backupLocation != nil {
nfsServerAddr = backupLocation.Location.NFSConfig.ServerAddr
nfsExportPath = backupLocation.Location.NFSConfig.SubPath
nfsMountOption = backupLocation.Location.NFSConfig.MountOptions
}
if dataExport != nil {
psaJobUid = getAnnotationValue(dataExport, utils.PsaUIDKey)
psaJobGid = getAnnotationValue(dataExport, utils.PsaGIDKey)
}
switch drv.Name() {
case drivers.Rsync:
return drv.StartJob(
Expand Down Expand Up @@ -1927,6 +1933,8 @@ func startTransferJob(
drivers.WithNfsServer(nfsServerAddr),
drivers.WithNfsExportDir(nfsExportPath),
drivers.WithNfsMountOption(nfsMountOption),
drivers.WithPodUserId(psaJobUid),
drivers.WithPodGroupId(psaJobGid),
)
case drivers.KopiaRestore:
return drv.StartJob(
Expand All @@ -1945,6 +1953,8 @@ func startTransferJob(
drivers.WithJobConfigMapNs(jobConfigMapNs),
drivers.WithNfsServer(nfsServerAddr),
drivers.WithNfsExportDir(nfsExportPath),
drivers.WithPodUserId(psaJobUid),
drivers.WithPodGroupId(psaJobGid),
)
}

Expand Down Expand Up @@ -2353,6 +2363,15 @@ func startNfsCSIRestoreVolumeJob(
bl *storkapi.BackupLocation,
) (string, error) {

var (
psaJobUid string
psaJobGid string
)

if de != nil {
psaJobUid = getAnnotationValue(de, utils.PsaUIDKey)
psaJobGid = getAnnotationValue(de, utils.PsaGIDKey)
}
jobName := utils.GetCsiRestoreJobName(drivers.NFSCSIRestore, de.Name)
err := utils.CreateNfsSecret(utils.GetCredSecretName(jobName), bl, de.Namespace, nil)
if err != nil {
Expand All @@ -2371,6 +2390,8 @@ func startNfsCSIRestoreVolumeJob(
drivers.WithNfsExportDir(bl.Location.NFSConfig.SubPath),
drivers.WithNfsMountOption(bl.Location.NFSConfig.MountOptions),
drivers.WithNfsSubPath(bl.Location.Path),
drivers.WithPodUserId(psaJobUid),
drivers.WithPodGroupId(psaJobGid),
)
}
return "", fmt.Errorf("unknown driver for nfs csi volume restore: %s", drv.Name())
Expand Down
8 changes: 8 additions & 0 deletions pkg/controllers/resourceexport/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ type updateResourceExportFields struct {
LargeResourceEnabled bool
}

func getAnnotationValue(re *kdmpapi.ResourceExport, key string) string {
var val string
if _, ok := re.Annotations[key]; ok {
val = re.Annotations[key]
}
return val
}

func (c *Controller) process(ctx context.Context, in *kdmpapi.ResourceExport) (bool, error) {
funct := "resourceExport.process"
if in == nil {
Expand Down
8 changes: 8 additions & 0 deletions pkg/drivers/kopiabackup/kopiabackup.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ func jobFor(
logrus.Errorf("failed to get the toleration details: %v", err)
return nil, fmt.Errorf("failed to get the toleration details for job [%s/%s]", jobOption.Namespace, jobName)
}

job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: jobName,
Expand Down Expand Up @@ -384,6 +385,13 @@ 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)
if err != nil {
return nil, err
}
}

if len(nodeName) != 0 {
job.Spec.Template.Spec.NodeName = nodeName
Expand Down
9 changes: 8 additions & 1 deletion pkg/drivers/kopiarestore/kopiarestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ func jobFor(
logrus.Errorf("failed to get the toleration details: %v", err)
return nil, fmt.Errorf("failed to get the toleration details for job [%s/%s]", jobOption.Namespace, jobName)
}

job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: jobName,
Expand Down Expand Up @@ -285,7 +286,13 @@ 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)
if err != nil {
return nil, err
}
}
// Add the image secret in job spec only if it is present in the stork deployment.
if len(imageRegistrySecret) != 0 {
job.Spec.Template.Spec.ImagePullSecrets = utils.ToImagePullSecret(utils.GetImageSecretName(jobName))
Expand Down
11 changes: 10 additions & 1 deletion pkg/drivers/nfsbackup/nfsbackup.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ func jobForBackupResource(
}, " ")

labels := addJobLabels(jobOption)

nfsExecutorImage, imageRegistrySecret, err := utils.GetExecutorImageAndSecret(drivers.NfsExecutorImage,
jobOption.NfsImageExecutorSource,
jobOption.NfsImageExecutorSourceNs,
Expand All @@ -216,6 +215,7 @@ func jobForBackupResource(
logrus.Errorf("failed to get the toleration details: %v", err)
return nil, fmt.Errorf("failed to get the toleration details for job [%s/%s]", jobOption.Namespace, jobOption.RestoreExportName)
}

job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: jobOption.RestoreExportName,
Expand Down Expand Up @@ -270,6 +270,15 @@ func jobForBackupResource(
},
},
}

// The Job is intended to backup resources to NFS backuplocation
// and it doesn't need a specific JOB uid/gid since it will be sqaushed at NFS server
// hence used a global hardcoded UID/GID.
job, err = utils.AddSecurityContextToJob(job, utils.KdmpJobUid, utils.KdmpJobGid)
if err != nil {
return nil, err
}

// Add the image secret in job spec only if it is present in the stork deployment.
if len(imageRegistrySecret) != 0 {
job.Spec.Template.Spec.ImagePullSecrets = utils.ToImagePullSecret(utils.GetImageSecretName(jobOption.RestoreExportName))
Expand Down
7 changes: 7 additions & 0 deletions pkg/drivers/nfscsirestore/nfscsirestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ 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)
if err != nil {
return nil, err
}
}
// Add the image secret in job spec only if it is present in the stork deployment.
if len(imageRegistrySecret) != 0 {
job.Spec.Template.Spec.ImagePullSecrets = utils.ToImagePullSecret(utils.GetImageSecretName(jobName))
Expand Down
4 changes: 4 additions & 0 deletions pkg/drivers/nfsrestore/nfsrestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,10 @@ func jobForRestoreResource(
},
},
}
job, err = utils.AddSecurityContextToJob(job, utils.KdmpJobUid, utils.KdmpJobGid)
if err != nil {
return nil, err
}
// Add the image secret in job spec only if it is present in the stork deployment.
if len(imageRegistrySecret) != 0 {
job.Spec.Template.Spec.ImagePullSecrets = utils.ToImagePullSecret(utils.GetImageSecretName(jobOption.RestoreExportName))
Expand Down
19 changes: 19 additions & 0 deletions pkg/drivers/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ type JobOpts struct {
ResoureBackupName string
ResoureBackupNamespace string
S3DisableSSL bool
// psa specifc option to be used by job
PodUserId string
PodGroupId string
}

// WithS3DisableSSL is job parameter
Expand Down Expand Up @@ -517,3 +520,19 @@ func WithNodeAffinity(l map[string]string) JobOption {
return nil
}
}

// WithPodUserId is job parameter.
func WithPodUserId(podUserId string) JobOption {
return func(opts *JobOpts) error {
opts.PodUserId = podUserId
return nil
}
}

// WithPodGroupId is job parameter.
func WithPodGroupId(PodGroupId string) JobOption {
return func(opts *JobOpts) error {
opts.PodGroupId = PodGroupId
return nil
}
}
96 changes: 96 additions & 0 deletions pkg/drivers/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/utils/ptr"
)

const (
Expand Down Expand Up @@ -67,6 +68,13 @@ const (
IstioInjectLabel = "sidecar.istio.io/inject"
// ProcessVMResourceSuccessMsg - vm resources processed successfully
ProcessVMResourceSuccessMsg = "vm resources processed successfully"
PsaUIDKey = "portworx.io/psa-uid"
PsaGIDKey = "portworx.io/psa-gid"
KdmpJobUid = "1013"
KdmpJobGid = "1013"
OcpUidRangeAnnotationKey = "openshift.io/sa.scc.uid-range"
OcpGidRangeAnnotationKey = "openshift.io/sa.scc.supplemental-groups"
kopiaBackupString = "kopiaexecutor backup"
)

var (
Expand Down Expand Up @@ -966,3 +974,91 @@ func GetShortUID(uid string) string {
}
return uid[:8]
}

// Add container security Context to job pod if the PSA is enabled.
// 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) {
if job == nil {
return job, fmt.Errorf("recieved a nil job object to add security context")
}
if job.Spec.Template.Spec.Containers[0].SecurityContext == nil {
job.Spec.Template.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{}
}
// call GetOcpNsUidGid to get the UID and GID from the namespace annotation if it is an OCP cluster.
// In case of OCP we cannot run with hardcoded UID and GID or backup CR preserved UID and GID.
// We need to run with the UID and GID from the namespace annotation.
ocpUid, ocpGid, isOcp, err := GetOcpNsUidGid(job.Namespace, podUserId, podGroupId)
if err != nil {
return nil, err
}
// if the namespace is OCP, then overwrite the UID and GID from the namespace annotation
if isOcp {
podUserId = ocpUid
podGroupId = ocpGid
}

if podUserId != "" {
uid, err := strconv.ParseInt(podUserId, 10, 64)
if err != nil {
logrus.Errorf("failed to convert the UID to int: %v", err)
return nil, fmt.Errorf("failed to convert the UID to int: %v", err)
}
job.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser = &uid

// Add fsgroup in Pod security context with the same UID as RunAsUser
// But we shouldn't add fsgroup if it is a kopia backup because it will alter the permission
// of the backup pod filesystem.
if !strings.Contains(job.Spec.Template.Spec.Containers[0].Command[0], kopiaBackupString) {
job.Spec.Template.Spec.SecurityContext = &corev1.PodSecurityContext{
FSGroup: &uid,
}
}
}
if podGroupId != "" {
gid, err := strconv.ParseInt(podGroupId, 10, 64)
if err != nil {
logrus.Errorf("failed to convert the GID to int: %v", err)
return nil, fmt.Errorf("failed to convert the GID to int: %v", err)
}
job.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup = &gid
}
// Add RunAsNonRoot to true and drop all capabilities and seccomp profile and allowPrivilegeEscalation to false
job.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot = ptr.To(true)
job.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation = ptr.To(false)
job.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile = &corev1.SeccompProfile{
Type: "RuntimeDefault",
}
job.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities = &corev1.Capabilities{
Drop: []corev1.Capability{
"ALL",
},
}
return job, nil
}

// read if destination namespace has annotion like openshift.io/sa.scc.uid-range or openshift.io/sa.scc.supplemental-groups
// if yes then read the first value and pass it to the restore job for both uid and gid and use it as fsgroup too.
func GetOcpNsUidGid(nsName string, psaJobUid string, psaJobGid string) (string, string, bool, error) {
isOcp := false
if nsName == "" {
return "", "", false, fmt.Errorf("namespace name is empty")
}

ns, err := core.Instance().GetNamespace(nsName)
if err != nil {
return "", "", false, fmt.Errorf("failed to get namespace %s: %v", nsName, err)
}
if ns.Annotations != nil {
if ns.Annotations[OcpUidRangeAnnotationKey] != "" {
psaJobUid = strings.Split(ns.Annotations[OcpUidRangeAnnotationKey], "/")[0]
isOcp = true
}
if ns.Annotations[OcpGidRangeAnnotationKey] != "" {
psaJobGid = strings.Split(ns.Annotations[OcpGidRangeAnnotationKey], "/")[0]
isOcp = true
}
}
return psaJobUid, psaJobGid, isOcp, nil
}

0 comments on commit a382276

Please sign in to comment.