diff --git a/.github/workflows/full_kubeflow_integration_test.yaml b/.github/workflows/full_kubeflow_integration_test.yaml index bcda92a612..5a08f1b83e 100644 --- a/.github/workflows/full_kubeflow_integration_test.yaml +++ b/.github/workflows/full_kubeflow_integration_test.yaml @@ -152,7 +152,7 @@ jobs: - name: V2 Pipeline Test run: | - pip3 install -U "kfp>=2.16.0" + pip3 install -U "kfp>=2.16.0" kfp-kubernetes TOKEN="$(kubectl -n $KF_PROFILE create token default-editor)" python3 tests/pipeline_v2_test.py run_pipeline "${TOKEN}" "${KF_PROFILE}" diff --git a/.github/workflows/pipeline_test.yaml b/.github/workflows/pipeline_test.yaml index b337bc83ef..19609bdd47 100644 --- a/.github/workflows/pipeline_test.yaml +++ b/.github/workflows/pipeline_test.yaml @@ -82,7 +82,7 @@ jobs: - name: V2 Pipeline Test run: | - pip3 install "kfp>=2.16.0" + pip3 install "kfp>=2.16.0" kfp-kubernetes TOKEN="$(kubectl -n $KF_PROFILE create token default-editor)" python3 tests/pipeline_v2_test.py run_pipeline "${TOKEN}" "${KF_PROFILE}" diff --git a/applications/pipeline/overlays/kustomization.yaml b/applications/pipeline/overlays/kustomization.yaml new file mode 100644 index 0000000000..4e2f5c9659 --- /dev/null +++ b/applications/pipeline/overlays/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- ../upstream/env/cert-manager/platform-agnostic-multi-user + +patches: +- path: pipeline-install-config-securitycontext.yaml diff --git a/applications/pipeline/overlays/pipeline-install-config-securitycontext.yaml b/applications/pipeline/overlays/pipeline-install-config-securitycontext.yaml new file mode 100644 index 0000000000..68dabe07ce --- /dev/null +++ b/applications/pipeline/overlays/pipeline-install-config-securitycontext.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: pipeline-install-config +data: + defaultSecurityContextRunAsUser: "" # 1000 + defaultSecurityContextRunAsGroup: "" # 0 + defaultSecurityContextRunAsNonRoot: "" # TODO "true" for better security diff --git a/common/security/PSS/dynamic/baseline/kustomization.yaml b/common/security/PSS/dynamic/baseline/kustomization.yaml deleted file mode 100644 index 8d8ea77e00..0000000000 --- a/common/security/PSS/dynamic/baseline/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1alpha1 -kind: Component - -configMapGenerator: -- name: namespace-labels-data - namespace: kubeflow - behavior: merge - files: - - namespace-labels.yaml diff --git a/common/security/PSS/dynamic/baseline/namespace-labels.yaml b/common/security/PSS/dynamic/baseline/namespace-labels.yaml deleted file mode 100644 index 08f6690272..0000000000 --- a/common/security/PSS/dynamic/baseline/namespace-labels.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# This file is a duplicate of apps/profiles/upstream/base/namespace-labels.yaml -# as using only the required label to merge it with the existing config map of profiles -# deployment to enable PSS for profile namespaces, leads to creation of a new config map -# with just the PSS label and replaces the pre-exisiting labels in the deployed config map. -# Below is a list of labels to be set by default. -# -# To add a namespace label, use `key: 'value'`, for example: -# istio.io/rev: 'asm-191-1' -# -# To remove a namespace label, use `key: ''`. For example: -# istio-injection: '' -# -# Profile controller will not replace a namespace label if its key already -# exists. If you want to override the value of a previously applied label, you -# need to: -# 1. Remove the label by using `key: ''` and deploy. -# 2. Add the label by using `key: 'value'` and deploy. -# -katib.kubeflow.org/metrics-collector-injection: "enabled" -serving.kubeflow.org/inferenceservice: "enabled" -pipelines.kubeflow.org/enabled: "true" -app.kubernetes.io/part-of: "kubeflow-profile" -pod-security.kubernetes.io/enforce: "baseline" diff --git a/example/kustomization.yaml b/example/kustomization.yaml index 14952bd7b9..a1f650a21c 100644 --- a/example/kustomization.yaml +++ b/example/kustomization.yaml @@ -65,7 +65,7 @@ resources: # Kubeflow Istio Resources - ../common/istio/kubeflow-istio-resources/base # Kubeflow Pipelines (SeaweedFS as default S3 storage) -- ../applications/pipeline/upstream/env/cert-manager/platform-agnostic-multi-user # Pipeline definitions stored in the database +- ../applications/pipeline/overlays # Pipeline definitions stored in the database # - ../applications/pipeline/upstream/env/cert-manager/platform-agnostic-multi-user-k8s-native # Pipeline Definitions Stored as Kubernetes Resources # Katib - ../applications/katib/upstream/installs/katib-with-kubeflow @@ -103,9 +103,3 @@ resources: # Here is the documentation for Ray: https://docs.ray.io/en/latest/ # Here is the internal documentation for Ray: - ../experimental/ray/README.md # - ../experimental/ray/kuberay-operator/overlays/kubeflow - -components: -# https://kubernetes.io/docs/concepts/security/pod-security-standards/ -# For all static namespaces we already enforce PSS restricted -# This should be used together with Kubernetes 1.33+ user namespaces to block root user exploits -- ../common/security/PSS/dynamic/baseline diff --git a/tests/kubeflow_profile_install.sh b/tests/kubeflow_profile_install.sh index 4176836970..6b17901930 100755 --- a/tests/kubeflow_profile_install.sh +++ b/tests/kubeflow_profile_install.sh @@ -6,4 +6,4 @@ PROFILE_CONTROLLER_POD=$(kubectl get pods -n kubeflow -o json | jq -r '.items[] kubectl logs -n kubeflow "$PROFILE_CONTROLLER_POD" KF_PROFILE=kubeflow-user-example-com kubectl -n $KF_PROFILE get pods,configmaps,secrets -kubectl label namespace $KF_PROFILE pod-security.kubernetes.io/enforce=baseline --overwrite +kubectl label namespace $KF_PROFILE pod-security.kubernetes.io/enforce=restricted --overwrite diff --git a/tests/pipeline_v1_test.py b/tests/pipeline_v1_test.py index 4b9abbeedc..46837c41c4 100755 --- a/tests/pipeline_v1_test.py +++ b/tests/pipeline_v1_test.py @@ -3,31 +3,41 @@ import kfp import sys import time +from kubernetes.client.models import V1Capabilities, V1SeccompProfile, V1SecurityContext +from kfp.components import func_to_container_op + + +def hello_world(): + print("Hello World from Kubeflow Pipelines V1!") + return "Hello World" -def hello_world_op(): - from kfp.components import func_to_container_op - - def hello_world(): - print("Hello World from Kubeflow Pipelines V1!") - return "Hello World" - - return func_to_container_op(hello_world) def hello_world_pipeline(): - hello_op = hello_world_op() - hello_op() + hello_world_task = func_to_container_op(hello_world, base_image="python:3.12")() + hello_world_task.container.set_security_context( + V1SecurityContext( + allow_privilege_escalation=False, + capabilities=V1Capabilities(drop=["ALL"]), + privileged=False, + read_only_root_filesystem=False, + seccomp_profile=V1SeccompProfile(type="RuntimeDefault"), + run_as_user=1000, + run_as_group=0, + run_as_non_root=True, + ) + ) def run_v1_pipeline(token, namespace): client = kfp.Client(host="http://localhost:8080/pipeline", existing_token=token) experiment = client.create_experiment("v1-pipeline-test", namespace=namespace) - + pipeline_run = client.create_run_from_pipeline_func( hello_world_pipeline, experiment_name=experiment.name, run_name="v1-hello-world", namespace=namespace, - arguments={} + arguments={}, ) for iteration in range(15): @@ -43,7 +53,12 @@ def run_v1_pipeline(token, namespace): sys.exit(1) if __name__ == "__main__": + from kfp import compiler + compiler.Compiler().compile( + pipeline_func=hello_world_pipeline, + package_path="pipeline_v1.yaml", + ) if len(sys.argv) != 3: sys.exit(1) - run_v1_pipeline(sys.argv[1], sys.argv[2]) \ No newline at end of file + run_v1_pipeline(sys.argv[1], sys.argv[2]) diff --git a/tests/pipeline_v2_test.py b/tests/pipeline_v2_test.py index 9a1c7461f3..f17bb44912 100644 --- a/tests/pipeline_v2_test.py +++ b/tests/pipeline_v2_test.py @@ -4,11 +4,11 @@ import sys import time from kfp import dsl +from kfp import kubernetes from kfp_server_api.exceptions import ApiException - @dsl.component -def hello_world_op() -> str: +def hello_world_operation() -> str: print("Hello World from Kubeflow Pipelines V2!") return "Hello World" @@ -18,7 +18,10 @@ def hello_world_op() -> str: description="A very simple hello world pipeline" ) def hello_world_pipeline(): - hello_world_op() + hello_world_task = hello_world_operation() + kubernetes.set_security_context( + hello_world_task, run_as_user=1000, run_as_group=0, run_as_non_root=True + ) def run_pipeline(token, namespace): @@ -93,4 +96,4 @@ def test_unauthorized_access(token, namespace): elif action == "test_unauthorized_access": test_unauthorized_access(token, namespace) else: - sys.exit(1) \ No newline at end of file + sys.exit(1) diff --git a/tests/pipelines_install.sh b/tests/pipelines_install.sh index f9bf955c49..ccfef617b6 100755 --- a/tests/pipelines_install.sh +++ b/tests/pipelines_install.sh @@ -1,11 +1,11 @@ #!/bin/bash set -euo pipefail echo "Installing Pipelines ..." -cd applications/pipeline/upstream -kubectl apply -f third-party/metacontroller/base/crd.yaml +cd applications/pipeline +kubectl apply -f upstream/third-party/metacontroller/base/crd.yaml echo "Waiting for crd/compositecontrollers.metacontroller.k8s.io to be available ..." kubectl wait --for condition=established --timeout=30s crd/compositecontrollers.metacontroller.k8s.io -kustomize build env/cert-manager/platform-agnostic-multi-user | kubectl apply -f - +kustomize build overlays | kubectl apply -f - sleep 60 kubectl wait --for=condition=Ready pods --all --all-namespaces --timeout=600s \ --field-selector=status.phase!=Succeeded