diff --git a/docs/user-guide.md b/docs/user-guide.md index b7bb1f065a..a352045f79 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -201,6 +201,7 @@ The currently supported options are: | kompose.service.healthcheck.liveness.http_get_port | kubernetes liveness httpGet port | | kompose.service.healthcheck.liveness.tcp_port | kubernetes liveness tcpSocket port | | kompose.service.external-traffic-policy | 'cluster', 'local', '' | | +| kompose.security-context.fsgroup | kubernetes pod security group fsgroup | | **Note**: `kompose.service.type` label should be defined with `ports` only (except for headless service), otherwise `kompose` will fail. @@ -431,6 +432,20 @@ services: kompose.service.external-traffic-policy: local kompose.service.type: loadbalancer ``` + +- `kompose.security-context.fsgroup` defines Kubernetes Pod [security context FsGroup.](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + +For example: + +```yaml +version: '3.8' + +services: + pgadmin: + image: nginx + labels: + kompose.security-context.fsgroup: 1001 +``` ## Restart If you want to create normal pods without controller you can use `restart` construct of docker-compose to define that. Follow table below to see what happens on the `restart` value. diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 79e074bed3..50db8ee37b 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -149,6 +149,7 @@ type ServiceConfig struct { Dockerfile string `compose:"dockerfile"` Replicas int `compose:"replicas"` GroupAdd []int64 `compose:"group_add"` + FsGroup int64 `compose:"kompose.security-context.fsgroup"` Volumes []Volumes `compose:""` Secrets []types.ServiceSecretConfig HealthChecks HealthChecks `compose:""` diff --git a/pkg/loader/compose/compose.go b/pkg/loader/compose/compose.go index 511ce339d9..a406c0e465 100644 --- a/pkg/loader/compose/compose.go +++ b/pkg/loader/compose/compose.go @@ -705,6 +705,8 @@ func parseKomposeLabels(labels map[string]string, serviceConfig *kobject.Service } serviceConfig.ServiceExternalTrafficPolicy = serviceExternalTypeTrafficPolicy + case LabelSecurityContextFsGroup: + serviceConfig.FsGroup = cast.ToInt64(value) case LabelServiceExpose: serviceConfig.ExposeService = strings.Trim(strings.ToLower(value), " ,") case LabelNodePortPort: diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go index b797711417..ce31f3f3dc 100644 --- a/pkg/loader/compose/utils.go +++ b/pkg/loader/compose/utils.go @@ -79,6 +79,8 @@ const ( // ServiceTypeHeadless ... ServiceTypeHeadless = "Headless" + // LabelSecurityContextFsGroup defines the pod FsGroup + LabelSecurityContextFsGroup = "kompose.security-context.fsgroup" ) // load environment variables from compose file diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index d9d1799e63..90d12fed1f 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -549,6 +549,11 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic podSecurityContext.SupplementalGroups = service.GroupAdd } + //set Security Context FsGroup + if service.FsGroup != 0 { + podSecurityContext.FSGroup = &service.FsGroup + } + // Setup security context securityContext := &api.SecurityContext{} if service.Privileged { diff --git a/pkg/transformer/kubernetes/kubernetes_test.go b/pkg/transformer/kubernetes/kubernetes_test.go index 6b86d02588..f9ccd45cea 100644 --- a/pkg/transformer/kubernetes/kubernetes_test.go +++ b/pkg/transformer/kubernetes/kubernetes_test.go @@ -51,6 +51,7 @@ func newServiceConfig() kobject.ServiceConfig { VolList: []string{"/tmp/volume"}, Network: []string{"network1", "network2"}, // supported Labels: nil, + FsGroup: 1001, Annotations: map[string]string{"abc": "def"}, CPUQuota: 1, // not supported CapAdd: []string{"cap_add"}, @@ -209,6 +210,9 @@ func checkPodTemplate(config kobject.ServiceConfig, template api.PodTemplateSpec if config.Privileged == privilegedNilOrFalse(template) { return fmt.Errorf("Found different template privileged: %#v vs. %#v", config.Privileged, template.Spec.Containers[0].SecurityContext) } + if config.FsGroup != *template.Spec.SecurityContext.FSGroup { + return fmt.Errorf("Found different pod security context fs group values: %#v vs. %#v", config.FsGroup, *template.Spec.SecurityContext.FSGroup) + } if config.Stdin != template.Spec.Containers[0].Stdin { return fmt.Errorf("Found different values for stdin: %#v vs. %#v", config.Stdin, template.Spec.Containers[0].Stdin) } diff --git a/pkg/transformer/kubernetes/podspec.go b/pkg/transformer/kubernetes/podspec.go index d523784195..80aa5a78ed 100644 --- a/pkg/transformer/kubernetes/podspec.go +++ b/pkg/transformer/kubernetes/podspec.go @@ -128,6 +128,11 @@ func SecurityContext(name string, service kobject.ServiceConfig) PodSpecOption { podSecurityContext.SupplementalGroups = service.GroupAdd } + //set Pod FsGroup + if service.FsGroup != 0 { + podSecurityContext.FSGroup = &service.FsGroup + } + // Setup security context securityContext := &api.SecurityContext{} if service.Privileged { diff --git a/script/test/cmd/tests_new.sh b/script/test/cmd/tests_new.sh index 32432d7cf8..ca669b727c 100755 --- a/script/test/cmd/tests_new.sh +++ b/script/test/cmd/tests_new.sh @@ -239,3 +239,11 @@ os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/exter os_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-os-v2.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" convert::expect_success_and_warning "$os_cmd" "$os_output" + +# Test Pod security context fs group +k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/fsgroup/docker-compose.yaml convert --stdout --with-kompose-annotation=false" +k8s_output="$KOMPOSE_ROOT/script/test/fixtures/fsgroup/output-k8s.yaml" +os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/fsgroup/docker-compose.yaml convert --stdout --with-kompose-annotation=false" +os_output="$KOMPOSE_ROOT/script/test/fixtures/fsgroup/output-os.yaml" +convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" +convert::expect_success "$os_cmd" "$os_output" diff --git a/script/test/fixtures/fsgroup/docker-compose.yaml b/script/test/fixtures/fsgroup/docker-compose.yaml new file mode 100644 index 0000000000..63e1c8a9e5 --- /dev/null +++ b/script/test/fixtures/fsgroup/docker-compose.yaml @@ -0,0 +1,14 @@ +version: '3.8' +volumes: + pgadmin-data: + +services: + pgadmin: + labels: + kompose.security-context.fsgroup: 1001 + image: dpage/pgadmin4 + environment: + PGADMIN_DEFAULT_EMAIL: dumb_pgadmin_user@email.com + PGADMIN_DEFAULT_PASSWORD: pgadmin_password + volumes: + - pgadmin-data:/var/lib/pgadmin diff --git a/script/test/fixtures/fsgroup/output-k8s.yaml b/script/test/fixtures/fsgroup/output-k8s.yaml new file mode 100644 index 0000000000..95ad09ef27 --- /dev/null +++ b/script/test/fixtures/fsgroup/output-k8s.yaml @@ -0,0 +1,79 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.security-context.fsgroup: "1001" + creationTimestamp: null + labels: + io.kompose.service: pgadmin + name: pgadmin +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: pgadmin + strategy: + type: Recreate + template: + metadata: + annotations: + kompose.security-context.fsgroup: "1001" + creationTimestamp: null + labels: + io.kompose.network/fsgroup-default: "true" + io.kompose.service: pgadmin + spec: + containers: + - env: + - name: PGADMIN_DEFAULT_EMAIL + value: dumb_pgadmin_user@email.com + - name: PGADMIN_DEFAULT_PASSWORD + value: pgadmin_password + image: dpage/pgadmin4 + name: pgadmin + resources: {} + volumeMounts: + - mountPath: /var/lib/pgadmin + name: pgadmin-data + restartPolicy: Always + securityContext: + fsGroup: 1001 + volumes: + - name: pgadmin-data + persistentVolumeClaim: + claimName: pgadmin-data +status: {} + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + creationTimestamp: null + labels: + io.kompose.service: pgadmin-data + name: pgadmin-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +status: {} + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + creationTimestamp: null + name: fsgroup-default +spec: + ingress: + - from: + - podSelector: + matchLabels: + io.kompose.network/fsgroup-default: "true" + podSelector: + matchLabels: + io.kompose.network/fsgroup-default: "true" + diff --git a/script/test/fixtures/fsgroup/output-os.yaml b/script/test/fixtures/fsgroup/output-os.yaml new file mode 100644 index 0000000000..50b5eae32f --- /dev/null +++ b/script/test/fixtures/fsgroup/output-os.yaml @@ -0,0 +1,102 @@ +--- +apiVersion: v1 +kind: DeploymentConfig +metadata: + annotations: + kompose.security-context.fsgroup: "1001" + creationTimestamp: null + labels: + io.kompose.service: pgadmin + name: pgadmin +spec: + replicas: 1 + selector: + io.kompose.service: pgadmin + strategy: + resources: {} + type: Recreate + template: + metadata: + creationTimestamp: null + labels: + io.kompose.network/fsgroup-default: "true" + io.kompose.service: pgadmin + spec: + containers: + - env: + - name: PGADMIN_DEFAULT_EMAIL + value: dumb_pgadmin_user@email.com + - name: PGADMIN_DEFAULT_PASSWORD + value: pgadmin_password + image: ' ' + name: pgadmin + resources: {} + volumeMounts: + - mountPath: /var/lib/pgadmin + name: pgadmin-data + restartPolicy: Always + securityContext: + fsGroup: 1001 + volumes: + - name: pgadmin-data + persistentVolumeClaim: + claimName: pgadmin-data + test: false + triggers: + - type: ConfigChange + - imageChangeParams: + automatic: true + containerNames: + - pgadmin + from: + kind: ImageStreamTag + name: pgadmin:latest + type: ImageChange +status: + availableReplicas: 0 + latestVersion: 0 + observedGeneration: 0 + replicas: 0 + unavailableReplicas: 0 + updatedReplicas: 0 + +--- +apiVersion: v1 +kind: ImageStream +metadata: + creationTimestamp: null + labels: + io.kompose.service: pgadmin + name: pgadmin +spec: + lookupPolicy: + local: false + tags: + - annotations: null + from: + kind: DockerImage + name: dpage/pgadmin4 + generation: null + importPolicy: {} + name: latest + referencePolicy: + type: "" +status: + dockerImageRepository: "" + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + creationTimestamp: null + labels: + io.kompose.service: pgadmin-data + name: pgadmin-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +status: {} +