diff --git a/bundle/backstage.io/manifests/backstage-operator.clusterserviceversion.yaml b/bundle/backstage.io/manifests/backstage-operator.clusterserviceversion.yaml index a0cc9b570..5ce2bbe61 100644 --- a/bundle/backstage.io/manifests/backstage-operator.clusterserviceversion.yaml +++ b/bundle/backstage.io/manifests/backstage-operator.clusterserviceversion.yaml @@ -25,7 +25,7 @@ metadata: } } ] - createdAt: "2026-08-24T09:48:04Z" + createdAt: "2026-08-25T11:27:07Z" description: Backstage Operator operators.operatorframework.io/builder: operator-sdk-v1.42.2 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 @@ -128,6 +128,18 @@ spec: - patch - update - watch + - apiGroups: + - networking.k8s.io + resources: + - networkpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - rhdh.redhat.com resources: diff --git a/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml b/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml index 2d42edc68..68238fb43 100644 --- a/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml +++ b/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml @@ -29,7 +29,7 @@ metadata: categories: Developer Tools certified: "true" containerImage: registry.redhat.io/rhdh/rhdh-rhel9-operator:2.0 - createdAt: "2026-08-24T09:48:00Z" + createdAt: "2026-08-25T11:27:08Z" description: Red Hat Developer Hub is a Red Hat supported version of Backstage. It comes with pre-built plug-ins and configuration settings, supports use of an external database, and can help streamline the process of setting up a self-managed @@ -169,6 +169,18 @@ spec: - patch - update - watch + - apiGroups: + - networking.k8s.io + resources: + - networkpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - rhdh.redhat.com resources: @@ -221,18 +233,6 @@ spec: - patch - update - watch - - apiGroups: - - networking.k8s.io - resources: - - networkpolicies - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - tekton.dev resources: diff --git a/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml b/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml index ef5b934e8..15bd5b743 100644 --- a/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml +++ b/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml @@ -10,6 +10,58 @@ data: your own app-config ConfigMap\n # and reference it in your CR.\n # See https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.4/html/configuring/provisioning-and-using-your-custom-configuration#provisioning-your-custom-configuration\n \ # for more details.\n ###########################################################################################################\n \ auth:\n environment: development\n providers: \n {}\n" + db-networkpolicy.yaml: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: default-deny + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Ingress + - Egress + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-dns-egress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Egress + egress: + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - port: 5353 + protocol: UDP + - port: 5353 + protocol: TCP + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-backend-ingress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + ports: + - port: 5432 + protocol: TCP db-secret.yaml: |- apiVersion: v1 kind: Secret @@ -375,6 +427,111 @@ data: dynamic-plugins.yaml: | includes: - dynamic-plugins.default.yaml + networkpolicy.yaml: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: default-deny + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + - Egress + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-dns-egress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - port: 5353 + protocol: UDP + - port: 5353 + protocol: TCP + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-https-egress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - ports: + - port: 443 + protocol: TCP + - port: 6443 + protocol: TCP + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-psql-egress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - to: + - podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + ports: + - port: 5432 + protocol: TCP + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-metrics-ingress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-monitoring + ports: + - port: 9464 + protocol: TCP + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-router-ingress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: {} # on OCP, replaced with policy-group.network.openshift.io/ingress selector + ports: + - port: 7007 + protocol: TCP route.yaml: |- apiVersion: route.openshift.io/v1 kind: Route diff --git a/bundle/rhdh/manifests/rhdh-flavour-orchestrator-config_v1_configmap.yaml b/bundle/rhdh/manifests/rhdh-flavour-orchestrator-config_v1_configmap.yaml index f68c9f697..cf7102212 100644 --- a/bundle/rhdh/manifests/rhdh-flavour-orchestrator-config_v1_configmap.yaml +++ b/bundle/rhdh/manifests/rhdh-flavour-orchestrator-config_v1_configmap.yaml @@ -23,6 +23,78 @@ data: # This flavour is disabled by default due to heavy infrastructure dependencies # (requires Tekton, ArgoCD, Serverless Workflow Operator) enabledByDefault: false + networkpolicy.yaml: | + # TODO(asoro): these Orchestrator policies use podSelector: {} (namespace-wide) and conflict with + # the ADR's "default deny with selective allow" and "label-scoped policies" principles. + # They weaken per-component ingress isolation for RHDH backend and DB pods in the same namespace. + # Review and make stricter by scoping to SonataFlow-specific labels where possible. + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-infra-ns-to-workflow-ns + spec: + podSelector: {} + ingress: + - from: + - namespaceSelector: + matchLabels: + # Allow knative events to be delivered to workflows. + kubernetes.io/metadata.name: knative-eventing + - namespaceSelector: + matchLabels: + # Allow auxiliary knative function for workflow (such as m2k-save-transformation) + kubernetes.io/metadata.name: knative-serving + - namespaceSelector: + matchLabels: + # Allow openshift serverless logic operator controller pod to access all pods in sonataflow + kubernetes.io/metadata.name: openshift-serverless-logic + --- + # NetworkPolicy to unblock incoming traffic to the namespace + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-external-communication + spec: + podSelector: {} + ingress: + - from: + - namespaceSelector: + matchLabels: + # Allow knative events to be delivered to workflows. + policy-group.network.openshift.io/ingress: "" + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-intra-network + spec: + # Apply this policy to all pods in the namespace + podSelector: {} + # Specify policy type as 'Ingress' to control incoming traffic rules + policyTypes: + - Ingress + ingress: + - from: + # Allow ingress from any pod within the same namespace + - podSelector: {} + --- + # NetworkPolicy to allow openshift-user-workload-monitoring pods to access all pods within the workflow's namespace + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-monitoring-to-sonataflow-and-workflows + spec: + # Apply this policy to all pods in the namespace + podSelector: {} + # Specify policy type as 'Ingress' to control incoming traffic rules + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + # Allow openshift-user-workload-monitoring pods to access the workflow. + kubernetes.io/metadata.name: openshift-user-workload-monitoring kind: ConfigMap metadata: name: rhdh-flavour-orchestrator-config diff --git a/bundle/rhdh/manifests/rhdh-plugin-deps_v1_configmap.yaml b/bundle/rhdh/manifests/rhdh-plugin-deps_v1_configmap.yaml index bff70f0d4..74f6091f5 100644 --- a/bundle/rhdh/manifests/rhdh-plugin-deps_v1_configmap.yaml +++ b/bundle/rhdh/manifests/rhdh-plugin-deps_v1_configmap.yaml @@ -14,74 +14,6 @@ data: sourceRepos: - '*' sonataflow.yaml: | - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: allow-infra-ns-to-workflow-ns # hardcoded - spec: - podSelector: {} - ingress: - - from: - - namespaceSelector: - matchLabels: - # Allow knative events to be delivered to workflows. - kubernetes.io/metadata.name: knative-eventing - - namespaceSelector: - matchLabels: - # Allow auxiliary knative function for workflow (such as m2k-save-transformation) - kubernetes.io/metadata.name: knative-serving - - namespaceSelector: - matchLabels: - # Allow openshift serverless logic operator controller pod to access all pods in sonataflow - kubernetes.io/metadata.name: openshift-serverless-logic - --- - # NetworkPolicy to unblock incoming traffic to the namespace - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: allow-external-communication # hardcoded - spec: - podSelector: {} - ingress: - - from: - - namespaceSelector: - matchLabels: - # Allow knative events to be delivered to workflows. - policy-group.network.openshift.io/ingress: "" - --- - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: allow-intra-network # hardcoded - spec: - # Apply this policy to all pods in the namespace - podSelector: {} - # Specify policy type as 'Ingress' to control incoming traffic rules - policyTypes: - - Ingress - ingress: - - from: - # Allow ingress from any pod within the same namespace - - podSelector: {} - --- - # NetworkPolicy to allow openshift-user-workload-monitoring pods to access all pods within the workflow's namespace - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: allow-monitoring-to-sonataflow-and-workflows # hardcoded - spec: - # Apply this policy to all pods in the namespace - podSelector: {} - # Specify policy type as 'Ingress' to control incoming traffic rules - policyTypes: - - Ingress - ingress: - - from: - - namespaceSelector: - matchLabels: - # Allow openshift-user-workload-monitoring pods to access the workflow. - kubernetes.io/metadata.name: openshift-user-workload-monitoring - --- apiVersion: batch/v1 kind: Job metadata: diff --git a/config/profile/rhdh/default-config/db-networkpolicy.yaml b/config/profile/rhdh/default-config/db-networkpolicy.yaml new file mode 100644 index 000000000..3c02c8911 --- /dev/null +++ b/config/profile/rhdh/default-config/db-networkpolicy.yaml @@ -0,0 +1,51 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Ingress + - Egress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-dns-egress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Egress + egress: + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - port: 5353 + protocol: UDP + - port: 5353 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-backend-ingress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + ports: + - port: 5432 + protocol: TCP diff --git a/config/profile/rhdh/default-config/flavours/orchestrator/networkpolicy.yaml b/config/profile/rhdh/default-config/flavours/orchestrator/networkpolicy.yaml new file mode 100644 index 000000000..72d588145 --- /dev/null +++ b/config/profile/rhdh/default-config/flavours/orchestrator/networkpolicy.yaml @@ -0,0 +1,71 @@ +# TODO(asoro): these Orchestrator policies use podSelector: {} (namespace-wide) and conflict with +# the ADR's "default deny with selective allow" and "label-scoped policies" principles. +# They weaken per-component ingress isolation for RHDH backend and DB pods in the same namespace. +# Review and make stricter by scoping to SonataFlow-specific labels where possible. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-infra-ns-to-workflow-ns +spec: + podSelector: {} + ingress: + - from: + - namespaceSelector: + matchLabels: + # Allow knative events to be delivered to workflows. + kubernetes.io/metadata.name: knative-eventing + - namespaceSelector: + matchLabels: + # Allow auxiliary knative function for workflow (such as m2k-save-transformation) + kubernetes.io/metadata.name: knative-serving + - namespaceSelector: + matchLabels: + # Allow openshift serverless logic operator controller pod to access all pods in sonataflow + kubernetes.io/metadata.name: openshift-serverless-logic +--- +# NetworkPolicy to unblock incoming traffic to the namespace +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-external-communication +spec: + podSelector: {} + ingress: + - from: + - namespaceSelector: + matchLabels: + # Allow knative events to be delivered to workflows. + policy-group.network.openshift.io/ingress: "" +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-intra-network +spec: + # Apply this policy to all pods in the namespace + podSelector: {} + # Specify policy type as 'Ingress' to control incoming traffic rules + policyTypes: + - Ingress + ingress: + - from: + # Allow ingress from any pod within the same namespace + - podSelector: {} +--- +# NetworkPolicy to allow openshift-user-workload-monitoring pods to access all pods within the workflow's namespace +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-monitoring-to-sonataflow-and-workflows +spec: + # Apply this policy to all pods in the namespace + podSelector: {} + # Specify policy type as 'Ingress' to control incoming traffic rules + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + # Allow openshift-user-workload-monitoring pods to access the workflow. + kubernetes.io/metadata.name: openshift-user-workload-monitoring diff --git a/config/profile/rhdh/default-config/networkpolicy.yaml b/config/profile/rhdh/default-config/networkpolicy.yaml new file mode 100644 index 000000000..5d4c48be2 --- /dev/null +++ b/config/profile/rhdh/default-config/networkpolicy.yaml @@ -0,0 +1,104 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + - Egress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-dns-egress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - port: 5353 + protocol: UDP + - port: 5353 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-https-egress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - ports: + - port: 443 + protocol: TCP + - port: 6443 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-psql-egress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - to: + - podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + ports: + - port: 5432 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-metrics-ingress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-monitoring + ports: + - port: 9464 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-router-ingress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: {} # on OCP, replaced with policy-group.network.openshift.io/ingress selector + ports: + - port: 7007 + protocol: TCP diff --git a/config/profile/rhdh/kustomization.yaml b/config/profile/rhdh/kustomization.yaml index e5dbde1e0..7169244fc 100644 --- a/config/profile/rhdh/kustomization.yaml +++ b/config/profile/rhdh/kustomization.yaml @@ -47,6 +47,8 @@ configMapGenerator: - default-config/db-statefulset.yaml.k8s - default-config/service.yaml.k8s - default-config/secret-files.yaml + - default-config/db-networkpolicy.yaml + - default-config/networkpolicy.yaml name: default-config - files: - default-config/flavours/lightspeed/metadata.yaml @@ -57,6 +59,7 @@ configMapGenerator: - files: - default-config/flavours/orchestrator/metadata.yaml - default-config/flavours/orchestrator/dynamic-plugins.yaml + - default-config/flavours/orchestrator/networkpolicy.yaml name: flavour-orchestrator-config - files: - plugin-deps/sonataflow.yaml diff --git a/config/profile/rhdh/plugin-deps/sonataflow.yaml b/config/profile/rhdh/plugin-deps/sonataflow.yaml index f0f4fbd78..5fffa3b9c 100644 --- a/config/profile/rhdh/plugin-deps/sonataflow.yaml +++ b/config/profile/rhdh/plugin-deps/sonataflow.yaml @@ -1,71 +1,3 @@ -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: allow-infra-ns-to-workflow-ns # hardcoded -spec: - podSelector: {} - ingress: - - from: - - namespaceSelector: - matchLabels: - # Allow knative events to be delivered to workflows. - kubernetes.io/metadata.name: knative-eventing - - namespaceSelector: - matchLabels: - # Allow auxiliary knative function for workflow (such as m2k-save-transformation) - kubernetes.io/metadata.name: knative-serving - - namespaceSelector: - matchLabels: - # Allow openshift serverless logic operator controller pod to access all pods in sonataflow - kubernetes.io/metadata.name: openshift-serverless-logic ---- -# NetworkPolicy to unblock incoming traffic to the namespace -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: allow-external-communication # hardcoded -spec: - podSelector: {} - ingress: - - from: - - namespaceSelector: - matchLabels: - # Allow knative events to be delivered to workflows. - policy-group.network.openshift.io/ingress: "" ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: allow-intra-network # hardcoded -spec: - # Apply this policy to all pods in the namespace - podSelector: {} - # Specify policy type as 'Ingress' to control incoming traffic rules - policyTypes: - - Ingress - ingress: - - from: - # Allow ingress from any pod within the same namespace - - podSelector: {} ---- -# NetworkPolicy to allow openshift-user-workload-monitoring pods to access all pods within the workflow's namespace -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: allow-monitoring-to-sonataflow-and-workflows # hardcoded -spec: - # Apply this policy to all pods in the namespace - podSelector: {} - # Specify policy type as 'Ingress' to control incoming traffic rules - policyTypes: - - Ingress - ingress: - - from: - - namespaceSelector: - matchLabels: - # Allow openshift-user-workload-monitoring pods to access the workflow. - kubernetes.io/metadata.name: openshift-user-workload-monitoring ---- apiVersion: batch/v1 kind: Job metadata: diff --git a/config/profile/rhdh/plugin-rbac/rbac-sonataflow.yaml b/config/profile/rhdh/plugin-rbac/rbac-sonataflow.yaml index 9689f12d3..96018f7f6 100644 --- a/config/profile/rhdh/plugin-rbac/rbac-sonataflow.yaml +++ b/config/profile/rhdh/plugin-rbac/rbac-sonataflow.yaml @@ -16,18 +16,6 @@ rules: - patch - update - watch - - apiGroups: - - "networking.k8s.io" - resources: - - networkpolicies - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - tekton.dev resources: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index aea779798..7b4a9aefb 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -71,6 +71,18 @@ rules: - patch - update - watch +- apiGroups: + - networking.k8s.io + resources: + - networkpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - rhdh.redhat.com resources: diff --git a/dist/backstage.io/install.yaml b/dist/backstage.io/install.yaml index 6c8423698..e4737effa 100644 --- a/dist/backstage.io/install.yaml +++ b/dist/backstage.io/install.yaml @@ -2374,6 +2374,18 @@ rules: - patch - update - watch +- apiGroups: + - networking.k8s.io + resources: + - networkpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - rhdh.redhat.com resources: diff --git a/dist/rhdh/install.yaml b/dist/rhdh/install.yaml index 63589203f..5fa3adfd8 100644 --- a/dist/rhdh/install.yaml +++ b/dist/rhdh/install.yaml @@ -2374,6 +2374,18 @@ rules: - patch - update - watch +- apiGroups: + - networking.k8s.io + resources: + - networkpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - rhdh.redhat.com resources: @@ -2432,18 +2444,6 @@ rules: - patch - update - watch -- apiGroups: - - networking.k8s.io - resources: - - networkpolicies - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - tekton.dev resources: @@ -2603,6 +2603,58 @@ data: your own app-config ConfigMap\n # and reference it in your CR.\n # See https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.4/html/configuring/provisioning-and-using-your-custom-configuration#provisioning-your-custom-configuration\n \ # for more details.\n ###########################################################################################################\n \ auth:\n environment: development\n providers: \n {}\n" + db-networkpolicy.yaml: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: default-deny + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Ingress + - Egress + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-dns-egress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Egress + egress: + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - port: 5353 + protocol: UDP + - port: 5353 + protocol: TCP + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-backend-ingress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + ports: + - port: 5432 + protocol: TCP db-secret.yaml: |- apiVersion: v1 kind: Secret @@ -2968,6 +3020,111 @@ data: dynamic-plugins.yaml: | includes: - dynamic-plugins.default.yaml + networkpolicy.yaml: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: default-deny + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + - Egress + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-dns-egress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - port: 5353 + protocol: UDP + - port: 5353 + protocol: TCP + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-https-egress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - ports: + - port: 443 + protocol: TCP + - port: 6443 + protocol: TCP + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-psql-egress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - to: + - podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + ports: + - port: 5432 + protocol: TCP + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-metrics-ingress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-monitoring + ports: + - port: 9464 + protocol: TCP + --- + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-router-ingress + spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: {} # on OCP, replaced with policy-group.network.openshift.io/ingress selector + ports: + - port: 7007 + protocol: TCP route.yaml: |- apiVersion: route.openshift.io/v1 kind: Route @@ -3394,31 +3551,15 @@ data: # This flavour is disabled by default due to heavy infrastructure dependencies # (requires Tekton, ArgoCD, Serverless Workflow Operator) enabledByDefault: false -kind: ConfigMap -metadata: - name: rhdh-flavour-orchestrator-config - namespace: rhdh-operator ---- -apiVersion: v1 -data: - argocd.yaml: |- - --- - apiVersion: argoproj.io/v1alpha1 - kind: AppProject - metadata: - name: argocd-app-project - spec: - destinations: - - name: '*' - namespace: '*' - server: '*' - sourceRepos: - - '*' - sonataflow.yaml: | + networkpolicy.yaml: | + # TODO(asoro): these Orchestrator policies use podSelector: {} (namespace-wide) and conflict with + # the ADR's "default deny with selective allow" and "label-scoped policies" principles. + # They weaken per-component ingress isolation for RHDH backend and DB pods in the same namespace. + # Review and make stricter by scoping to SonataFlow-specific labels where possible. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: - name: allow-infra-ns-to-workflow-ns # hardcoded + name: allow-infra-ns-to-workflow-ns spec: podSelector: {} ingress: @@ -3440,7 +3581,7 @@ data: apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: - name: allow-external-communication # hardcoded + name: allow-external-communication spec: podSelector: {} ingress: @@ -3453,7 +3594,7 @@ data: apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: - name: allow-intra-network # hardcoded + name: allow-intra-network spec: # Apply this policy to all pods in the namespace podSelector: {} @@ -3469,7 +3610,7 @@ data: apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: - name: allow-monitoring-to-sonataflow-and-workflows # hardcoded + name: allow-monitoring-to-sonataflow-and-workflows spec: # Apply this policy to all pods in the namespace podSelector: {} @@ -3482,7 +3623,27 @@ data: matchLabels: # Allow openshift-user-workload-monitoring pods to access the workflow. kubernetes.io/metadata.name: openshift-user-workload-monitoring +kind: ConfigMap +metadata: + name: rhdh-flavour-orchestrator-config + namespace: rhdh-operator +--- +apiVersion: v1 +data: + argocd.yaml: |- --- + apiVersion: argoproj.io/v1alpha1 + kind: AppProject + metadata: + name: argocd-app-project + spec: + destinations: + - name: '*' + namespace: '*' + server: '*' + sourceRepos: + - '*' + sonataflow.yaml: | apiVersion: batch/v1 kind: Job metadata: diff --git a/docs/configuration.md b/docs/configuration.md index 4f6d60ee9..907254588 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -55,6 +55,8 @@ The Default Configuration defines the structure of all Backstage instances withi | secret-envs.yaml | []corev1.Secret | backstage-envs- | No | Yes | >=0.2.x | Backstage environment variables from Secret | | [dynamic-plugins.yaml](#dynamic-plugins) | corev1.ConfigMap | backstage-dynamic-plugins- | No | No | >=0.2.x | Dynamic plugins configuration | | pvcs.yaml | []corev1.PersistentVolumeClaim | backstage-- | No | Yes | >=0.4.x | List of PVC objects to be mounted to containers | +| networkpolicy.yaml | []networkingv1.NetworkPolicy | backstage-netpol--| No | Yes | >=2.0.x | NetworkPolicies for backend pods | +| db-networkpolicy.yaml | []networkingv1.NetworkPolicy | backstage-db-netpol--| For local DB | Yes| >=2.0.x | NetworkPolicies for PostgreSQL pods | **Meanings of "Mandatory" Column:** - **Yes** - Must be configured; deployment will fail otherwise. @@ -222,6 +224,13 @@ For example, Backstage CR named **mybackstage** will create K8s Deployment resou - `spec.template.metadata.labels[rhdh.redhat.com/app] = backstage-psql-` * db-service.yaml - `spec.selector[rhdh.redhat.com/app] = backstage-psql-` +* networkpolicy.yaml + - `metadata.labels[rhdh.redhat.com/app] = backstage-` + - `spec.podSelector.matchLabels[rhdh.redhat.com/app] = backstage-` + - On OpenShift, the `allow-router-ingress` policy's `namespaceSelector` is set to `policy-group.network.openshift.io/ingress: ""` +* db-networkpolicy.yaml + - `metadata.labels[rhdh.redhat.com/app] = backstage-psql-` + - `spec.podSelector.matchLabels[rhdh.redhat.com/app] = backstage-psql-` ### Multi objects @@ -233,7 +242,9 @@ The following configuration files support multi-object definitions: - **configmap-envs.yaml** (since 0.10.0) - **secret-files.yaml** - **secret-envs.yaml** -- **pvcs.yaml** +- **pvcs.yaml** +- **networkpolicy.yaml** (since 2.0.0) +- **db-networkpolicy.yaml** (since 2.0.0) For example, adding the following to **pvcs.yaml** will create 2 PVCs and mount them to the Backstage container: diff --git a/integration_tests/rhdh-config_test.go b/integration_tests/rhdh-config_test.go index e81a4519a..71a1739f0 100644 --- a/integration_tests/rhdh-config_test.go +++ b/integration_tests/rhdh-config_test.go @@ -7,6 +7,7 @@ import ( "github.com/redhat-developer/rhdh-operator/pkg/utils" appsv1 "k8s.io/api/apps/v1" + networkingv1 "k8s.io/api/networking/v1" "github.com/redhat-developer/rhdh-operator/pkg/model" @@ -206,6 +207,20 @@ var _ = When("create default rhdh", func() { }) } + By("creating NetworkPolicies for the backend") + npList := &networkingv1.NetworkPolicyList{} + err = k8sClient.List(ctx, npList, client.InNamespace(ns), + client.MatchingLabels{model.BackstageAppLabel: utils.BackstageAppLabelValue(backstageName)}) + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(len(npList.Items)).To(BeNumerically(">=", 6)) + + By("creating NetworkPolicies for the local database") + dbNpList := &networkingv1.NetworkPolicyList{} + err = k8sClient.List(ctx, dbNpList, client.InNamespace(ns), + client.MatchingLabels{model.BackstageAppLabel: utils.BackstageDbAppLabelValue(backstageName)}) + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(len(dbNpList.Items)).To(BeNumerically(">=", 3)) + }, 20*time.Second, time.Second).Should(Succeed()) deleteNamespace(ctx, ns) diff --git a/internal/controller/backstage_controller.go b/internal/controller/backstage_controller.go index 3a95da370..43aa95c75 100644 --- a/internal/controller/backstage_controller.go +++ b/internal/controller/backstage_controller.go @@ -50,6 +50,7 @@ type BackstageReconciler struct { // +kubebuilder:rbac:groups="",resources=configmaps;secrets;services;persistentvolumeclaims,verbs=get;watch;create;update;list;delete;patch // +kubebuilder:rbac:groups="",resources=pods;persistentvolumes,verbs=get;list;watch // +kubebuilder:rbac:groups="apps",resources=deployments;statefulsets,verbs=get;watch;create;update;list;delete;patch +// +kubebuilder:rbac:groups="networking.k8s.io",resources=networkpolicies,verbs=get;watch;create;update;list;delete;patch // +kubebuilder:rbac:groups="route.openshift.io",resources=routes;routes/custom-host,verbs=get;watch;create;update;list;delete;patch // +kubebuilder:rbac:groups="config.openshift.io",resources=ingresses,verbs=get;list;watch // +kubebuilder:rbac:groups="config.openshift.io",resources=apiservers,verbs=get;list;watch diff --git a/pkg/model/db-networkpolicy.go b/pkg/model/db-networkpolicy.go new file mode 100644 index 000000000..04901901e --- /dev/null +++ b/pkg/model/db-networkpolicy.go @@ -0,0 +1,83 @@ +package model + +import ( + "k8s.io/apimachinery/pkg/runtime" + + networkingv1 "k8s.io/api/networking/v1" + + "github.com/redhat-developer/rhdh-operator/api" + "github.com/redhat-developer/rhdh-operator/pkg/model/multiobject" + "github.com/redhat-developer/rhdh-operator/pkg/utils" +) + +type DbNetworkPolicyFactory struct{} + +func (f DbNetworkPolicyFactory) newBackstageObject() RuntimeObject { + return &DbNetworkPolicy{} +} + +type DbNetworkPolicy struct { + networkPolicies *multiobject.MultiObject + model *BackstageModel +} + +func init() { + registerConfig(DbNetworkPolicyKey, DbNetworkPolicyFactory{}, true, nil) +} + +func (b *DbNetworkPolicy) Object() runtime.Object { + if b.networkPolicies != nil && len(b.networkPolicies.Items) > 0 { + return b.networkPolicies + } + return nil +} + +func (b *DbNetworkPolicy) GetKey() string { + return DbNetworkPolicyKey +} + +func (b *DbNetworkPolicy) addToModel(model *BackstageModel, backstage api.Backstage, config runtime.Object, scheme *runtime.Scheme) error { + b.model = model + + if model.localDbEnabled && config != nil { + b.networkPolicies = config.(*multiobject.MultiObject) + } + + model.setRuntimeObject(b) + + if b.networkPolicies != nil && len(b.networkPolicies.Items) > 0 { + b.setMetaInfo(backstage, scheme) + } + + return nil +} + +func (b *DbNetworkPolicy) updateAndValidate(_ api.Backstage, _ *runtime.Scheme) error { + return nil +} + +func (b *DbNetworkPolicy) setMetaInfo(backstage api.Backstage, scheme *runtime.Scheme) { + dbLabel := utils.BackstageDbAppLabelValue(backstage.Name) + backendLabel := utils.BackstageAppLabelValue(backstage.Name) + + for _, item := range b.networkPolicies.Items { + np := item.(*networkingv1.NetworkPolicy) + + np.Spec.PodSelector.MatchLabels[BackstageAppLabel] = dbLabel + + for i := range np.Spec.Ingress { + for j := range np.Spec.Ingress[i].From { + if np.Spec.Ingress[i].From[j].PodSelector != nil { + if _, ok := np.Spec.Ingress[i].From[j].PodSelector.MatchLabels[BackstageAppLabel]; ok { + np.Spec.Ingress[i].From[j].PodSelector.MatchLabels[BackstageAppLabel] = backendLabel + } + } + } + } + + utils.GenerateLabel(&np.Labels, BackstageAppLabel, dbLabel) + utils.AddAnnotation(item, ConfiguredNameAnnotation, item.GetName()) + item.SetName(DefaultMultiObjectName("db-netpol", backstage.Name, item.GetName())) + setMetaInfo(item, backstage, scheme) + } +} diff --git a/pkg/model/db-networkpolicy_test.go b/pkg/model/db-networkpolicy_test.go new file mode 100644 index 000000000..7e8e508fd --- /dev/null +++ b/pkg/model/db-networkpolicy_test.go @@ -0,0 +1,95 @@ +package model + +import ( + "context" + "testing" + + "github.com/redhat-developer/rhdh-operator/api" + "github.com/redhat-developer/rhdh-operator/pkg/model/multiobject" + "github.com/redhat-developer/rhdh-operator/pkg/platform" + "github.com/redhat-developer/rhdh-operator/pkg/utils" + + "github.com/stretchr/testify/assert" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var dbNetworkPolicyTestBackstage = api.Backstage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-db-netpol", + Namespace: "test-ns", + }, +} + +func TestDefaultDbNetworkPolicies(t *testing.T) { + bs := *dbNetworkPolicyTestBackstage.DeepCopy() + testObj := createBackstageTest(bs).withDefaultConfig(true).withLocalDb(true) + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, platform.OpenShift, testObj.scheme) + assert.NoError(t, err) + assert.NotNil(t, model) + + obj := model.GetRuntimeObject(DbNetworkPolicyKey) + assert.NotNil(t, obj) + + mo := obj.Object().(*multiobject.MultiObject) + assert.Equal(t, 3, len(mo.Items)) + + dbLabel := utils.BackstageDbAppLabelValue(bs.Name) + for _, item := range mo.Items { + np := item.(*networkingv1.NetworkPolicy) + assert.Equal(t, dbLabel, np.Spec.PodSelector.MatchLabels[BackstageAppLabel]) + assert.Equal(t, bs.Namespace, np.GetNamespace()) + } +} + +func TestDbNetworkPolicyDisabledWhenNoLocalDb(t *testing.T) { + bs := *dbNetworkPolicyTestBackstage.DeepCopy() + testObj := createBackstageTest(bs).withDefaultConfig(true).withLocalDb(false) + + model, err := InitObjects(context.TODO(), testObj.backstage, testObj.externalConfig, platform.OpenShift, testObj.scheme) + assert.NoError(t, err) + assert.NotNil(t, model) + + obj := model.GetRuntimeObject(DbNetworkPolicyKey) + assert.Nil(t, obj, "DB NetworkPolicies should not be created when localDb is disabled") +} + +func TestDbNetworkPolicyPodSelectors(t *testing.T) { + bs := *dbNetworkPolicyTestBackstage.DeepCopy() + testObj := createBackstageTest(bs).withDefaultConfig(true).withLocalDb(true) + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, platform.OpenShift, testObj.scheme) + assert.NoError(t, err) + + mo := model.GetRuntimeObject(DbNetworkPolicyKey).Object().(*multiobject.MultiObject) + backendLabel := utils.BackstageAppLabelValue(bs.Name) + + found := false + for _, item := range mo.Items { + np := item.(*networkingv1.NetworkPolicy) + for _, ingress := range np.Spec.Ingress { + for _, from := range ingress.From { + if from.PodSelector != nil { + if val, ok := from.PodSelector.MatchLabels[BackstageAppLabel]; ok { + assert.Equal(t, backendLabel, val) + found = true + } + } + } + } + } + assert.True(t, found, "expected to find a backend ingress rule in DB policies") +} + +func TestDbNetworkPolicyNaming(t *testing.T) { + bs := *dbNetworkPolicyTestBackstage.DeepCopy() + testObj := createBackstageTest(bs).withDefaultConfig(true).withLocalDb(true) + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, platform.OpenShift, testObj.scheme) + assert.NoError(t, err) + + mo := model.GetRuntimeObject(DbNetworkPolicyKey).Object().(*multiobject.MultiObject) + assert.Equal(t, DefaultMultiObjectName("db-netpol", bs.Name, "default-deny"), mo.Items[0].GetName()) + assert.Equal(t, "default-deny", mo.Items[0].GetAnnotations()[ConfiguredNameAnnotation]) +} diff --git a/pkg/model/networkpolicy.go b/pkg/model/networkpolicy.go new file mode 100644 index 000000000..200e350db --- /dev/null +++ b/pkg/model/networkpolicy.go @@ -0,0 +1,107 @@ +package model + +import ( + "k8s.io/apimachinery/pkg/runtime" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/redhat-developer/rhdh-operator/api" + "github.com/redhat-developer/rhdh-operator/pkg/model/multiobject" + "github.com/redhat-developer/rhdh-operator/pkg/utils" +) + +type BackstageNetworkPolicyFactory struct{} + +func (f BackstageNetworkPolicyFactory) newBackstageObject() RuntimeObject { + return &BackstageNetworkPolicy{} +} + +type BackstageNetworkPolicy struct { + networkPolicies *multiobject.MultiObject + model *BackstageModel +} + +func init() { + registerConfig(NetworkPolicyKey, BackstageNetworkPolicyFactory{}, true, mergeMultiObjectConfigs) +} + +func (b *BackstageNetworkPolicy) Object() runtime.Object { + if b.networkPolicies != nil && len(b.networkPolicies.Items) > 0 { + return b.networkPolicies + } + return nil +} + +func (b *BackstageNetworkPolicy) GetKey() string { + return NetworkPolicyKey +} + +func (b *BackstageNetworkPolicy) addToModel(model *BackstageModel, backstage api.Backstage, config runtime.Object, scheme *runtime.Scheme) error { + b.model = model + + if config != nil { + b.networkPolicies = config.(*multiobject.MultiObject) + } + + model.setRuntimeObject(b) + + if b.networkPolicies != nil && len(b.networkPolicies.Items) > 0 { + b.setMetaInfo(backstage, scheme) + } + + return nil +} + +func (b *BackstageNetworkPolicy) updateAndValidate(_ api.Backstage, _ *runtime.Scheme) error { + if b.networkPolicies == nil || !b.model.isOpenshift { + return nil + } + for _, item := range b.networkPolicies.Items { + np := item.(*networkingv1.NetworkPolicy) + if np.GetAnnotations()[ConfiguredNameAnnotation] != "allow-router-ingress" { + continue + } + for i := range np.Spec.Ingress { + for j := range np.Spec.Ingress[i].From { + if np.Spec.Ingress[i].From[j].NamespaceSelector != nil { + np.Spec.Ingress[i].From[j].NamespaceSelector = &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "policy-group.network.openshift.io/ingress": "", + }, + } + } + } + } + break + } + return nil +} + +func (b *BackstageNetworkPolicy) setMetaInfo(backstage api.Backstage, scheme *runtime.Scheme) { + backendLabel := utils.BackstageAppLabelValue(backstage.Name) + dbLabel := utils.BackstageDbAppLabelValue(backstage.Name) + + for _, item := range b.networkPolicies.Items { + np := item.(*networkingv1.NetworkPolicy) + + if _, ok := np.Spec.PodSelector.MatchLabels[BackstageAppLabel]; ok { + np.Spec.PodSelector.MatchLabels[BackstageAppLabel] = backendLabel + } + + for i := range np.Spec.Egress { + for j := range np.Spec.Egress[i].To { + if np.Spec.Egress[i].To[j].PodSelector != nil { + if _, ok := np.Spec.Egress[i].To[j].PodSelector.MatchLabels[BackstageAppLabel]; ok { + np.Spec.Egress[i].To[j].PodSelector.MatchLabels[BackstageAppLabel] = dbLabel + } + } + } + } + + utils.GenerateLabel(&np.Labels, BackstageAppLabel, backendLabel) + utils.AddAnnotation(item, ConfiguredNameAnnotation, item.GetName()) + item.SetName(DefaultMultiObjectName("netpol", backstage.Name, item.GetName())) + setMetaInfo(item, backstage, scheme) + } +} diff --git a/pkg/model/networkpolicy_test.go b/pkg/model/networkpolicy_test.go new file mode 100644 index 000000000..172e97fb8 --- /dev/null +++ b/pkg/model/networkpolicy_test.go @@ -0,0 +1,181 @@ +package model + +import ( + "context" + "testing" + + "github.com/redhat-developer/rhdh-operator/api" + "github.com/redhat-developer/rhdh-operator/pkg/model/multiobject" + "github.com/redhat-developer/rhdh-operator/pkg/platform" + "github.com/redhat-developer/rhdh-operator/pkg/utils" + + "github.com/stretchr/testify/assert" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var networkPolicyTestBackstage = api.Backstage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-netpol", + Namespace: "test-ns", + }, +} + +func TestDefaultNetworkPolicies(t *testing.T) { + bs := *networkPolicyTestBackstage.DeepCopy() + testObj := createBackstageTest(bs).withDefaultConfig(true) + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, platform.OpenShift, testObj.scheme) + assert.NoError(t, err) + assert.NotNil(t, model) + + obj := model.GetRuntimeObject(NetworkPolicyKey) + assert.NotNil(t, obj) + + mo := obj.Object().(*multiobject.MultiObject) + assert.Equal(t, 6, len(mo.Items)) + + backendLabel := utils.BackstageAppLabelValue(bs.Name) + for _, item := range mo.Items { + np := item.(*networkingv1.NetworkPolicy) + assert.Equal(t, backendLabel, np.Spec.PodSelector.MatchLabels[BackstageAppLabel]) + assert.Equal(t, bs.Namespace, np.GetNamespace()) + } +} + +func TestNetworkPolicyPodSelectors(t *testing.T) { + bs := *networkPolicyTestBackstage.DeepCopy() + testObj := createBackstageTest(bs).withDefaultConfig(true) + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, platform.OpenShift, testObj.scheme) + assert.NoError(t, err) + + mo := model.GetRuntimeObject(NetworkPolicyKey).Object().(*multiobject.MultiObject) + dbLabel := utils.BackstageDbAppLabelValue(bs.Name) + + found := false + for _, item := range mo.Items { + np := item.(*networkingv1.NetworkPolicy) + for _, egress := range np.Spec.Egress { + for _, to := range egress.To { + if to.PodSelector != nil { + if val, ok := to.PodSelector.MatchLabels[BackstageAppLabel]; ok { + assert.Equal(t, dbLabel, val) + found = true + } + } + } + } + } + assert.True(t, found, "expected to find a psql egress rule targeting DB-labeled pods") +} + +func TestNetworkPolicyNaming(t *testing.T) { + bs := *networkPolicyTestBackstage.DeepCopy() + testObj := createBackstageTest(bs).withDefaultConfig(true) + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, platform.OpenShift, testObj.scheme) + assert.NoError(t, err) + + mo := model.GetRuntimeObject(NetworkPolicyKey).Object().(*multiobject.MultiObject) + assert.Equal(t, DefaultMultiObjectName("netpol", bs.Name, "default-deny"), mo.Items[0].GetName()) + assert.Equal(t, "default-deny", mo.Items[0].GetAnnotations()[ConfiguredNameAnnotation]) +} + +func TestNetworkPolicyRouterIngressOnOpenShift(t *testing.T) { + bs := *networkPolicyTestBackstage.DeepCopy() + testObj := createBackstageTest(bs).withDefaultConfig(true) + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, platform.OpenShift, testObj.scheme) + assert.NoError(t, err) + + mo := model.GetRuntimeObject(NetworkPolicyKey).Object().(*multiobject.MultiObject) + + var routerPolicy *networkingv1.NetworkPolicy + for _, item := range mo.Items { + np := item.(*networkingv1.NetworkPolicy) + if np.GetAnnotations()[ConfiguredNameAnnotation] == "allow-router-ingress" { + routerPolicy = np + break + } + } + assert.NotNil(t, routerPolicy, "expected allow-router-ingress policy") + assert.Len(t, routerPolicy.Spec.Ingress, 1) + assert.Len(t, routerPolicy.Spec.Ingress[0].From, 1) + assert.NotNil(t, routerPolicy.Spec.Ingress[0].From[0].NamespaceSelector) + assert.Equal(t, "", routerPolicy.Spec.Ingress[0].From[0].NamespaceSelector.MatchLabels["policy-group.network.openshift.io/ingress"]) +} + +func TestNetworkPolicyRouterIngressOnKubernetes(t *testing.T) { + bs := *networkPolicyTestBackstage.DeepCopy() + testObj := createBackstageTest(bs).withDefaultConfig(true) + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, platform.Default, testObj.scheme) + assert.NoError(t, err) + + mo := model.GetRuntimeObject(NetworkPolicyKey).Object().(*multiobject.MultiObject) + + var routerPolicy *networkingv1.NetworkPolicy + for _, item := range mo.Items { + np := item.(*networkingv1.NetworkPolicy) + if np.GetAnnotations()[ConfiguredNameAnnotation] == "allow-router-ingress" { + routerPolicy = np + break + } + } + assert.NotNil(t, routerPolicy, "expected allow-router-ingress policy") + assert.Len(t, routerPolicy.Spec.Ingress, 1) + assert.Len(t, routerPolicy.Spec.Ingress[0].From, 1) + ns := routerPolicy.Spec.Ingress[0].From[0].NamespaceSelector + assert.NotNil(t, ns) + assert.Empty(t, ns.MatchLabels, "non-OCP should use empty namespaceSelector (match all)") +} + +func TestNetworkPolicyFlavourMergePreservesNamespaceWidePodSelector(t *testing.T) { + bs := api.Backstage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-flavour-np", + Namespace: "test-ns", + }, + } + testObj := createBackstageTest(bs).withConfigPath("./testdata/testflavours").withLocalDb(false) + + model, err := InitObjects(context.TODO(), testObj.backstage, testObj.externalConfig, platform.OpenShift, testObj.scheme) + assert.NoError(t, err) + + obj := model.GetRuntimeObject(NetworkPolicyKey) + assert.NotNil(t, obj) + + mo := obj.Object().(*multiobject.MultiObject) + assert.Equal(t, 7, len(mo.Items), "expected 6 base + 1 flavour NP") + + backendLabel := utils.BackstageAppLabelValue(testObj.backstage.Name) + var foundNamespaceWide bool + for _, item := range mo.Items { + np := item.(*networkingv1.NetworkPolicy) + if np.GetAnnotations()[ConfiguredNameAnnotation] == "allow-intra-network" && + np.GetAnnotations()[SourceAnnotation] == "flavour-flavor1" { + foundNamespaceWide = true + _, hasLabel := np.Spec.PodSelector.MatchLabels[BackstageAppLabel] + assert.False(t, hasLabel, "flavour NP with podSelector: {} should NOT have rhdh.redhat.com/app in podSelector") + } else { + assert.Equal(t, backendLabel, np.Spec.PodSelector.MatchLabels[BackstageAppLabel], + "base NP %s should have backend label in podSelector", np.GetAnnotations()[ConfiguredNameAnnotation]) + } + } + assert.True(t, foundNamespaceWide, "expected to find the namespace-wide flavour NP") +} + +func TestNetworkPolicyWithOverlay(t *testing.T) { + bs := *networkPolicyTestBackstage.DeepCopy() + testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig("networkpolicy.yaml", "raw-networkpolicy.yaml") + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, platform.OpenShift, testObj.scheme) + assert.NoError(t, err) + + obj := model.GetRuntimeObject(NetworkPolicyKey) + assert.NotNil(t, obj) + + mo := obj.Object().(*multiobject.MultiObject) + assert.Equal(t, 1, len(mo.Items)) +} diff --git a/pkg/model/runtime.go b/pkg/model/runtime.go index 828fcbefd..6da8625f5 100644 --- a/pkg/model/runtime.go +++ b/pkg/model/runtime.go @@ -33,19 +33,21 @@ const SourceAnnotation = "rhdh.redhat.com/source" // Runtime object keys used to store and retrieve objects from BackstageModel const ( - DeploymentKey = "deployment.yaml" - ServiceKey = "service.yaml" - RouteKey = "route.yaml" - AppConfigKey = "app-config.yaml" - DynamicPluginsKey = "dynamic-plugins.yaml" - DbStatefulSetKey = "db-statefulset.yaml" - DbServiceKey = "db-service.yaml" - DbSecretKey = "db-secret.yaml" - SecretEnvsKey = "secret-envs.yaml" - SecretFilesKey = "secret-files.yaml" - ConfigMapEnvsKey = "configmap-envs.yaml" - ConfigMapFilesKey = "configmap-files.yaml" - PvcsKey = "pvcs.yaml" + DeploymentKey = "deployment.yaml" + ServiceKey = "service.yaml" + RouteKey = "route.yaml" + AppConfigKey = "app-config.yaml" + DynamicPluginsKey = "dynamic-plugins.yaml" + DbStatefulSetKey = "db-statefulset.yaml" + DbServiceKey = "db-service.yaml" + DbSecretKey = "db-secret.yaml" + SecretEnvsKey = "secret-envs.yaml" + SecretFilesKey = "secret-files.yaml" + ConfigMapEnvsKey = "configmap-envs.yaml" + ConfigMapFilesKey = "configmap-files.yaml" + PvcsKey = "pvcs.yaml" + NetworkPolicyKey = "networkpolicy.yaml" + DbNetworkPolicyKey = "db-networkpolicy.yaml" ) // Backstage configuration scaffolding with empty BackstageObjects. diff --git a/pkg/model/runtime_test.go b/pkg/model/runtime_test.go index 987d2d6b4..cde0c6cc4 100644 --- a/pkg/model/runtime_test.go +++ b/pkg/model/runtime_test.go @@ -100,7 +100,7 @@ func TestIfEmptyObjectIsValid(t *testing.T) { t.Logf("DbService wrapper exists, service: %v, Object(): %v", dbSvc.(*DbService).service, dbSvc.Object()) } - assert.Equal(t, 2, len(objs), "Should have 2 objects to apply (deployment + service)") + assert.Equal(t, 3, len(objs), "Should have 3 objects to apply (deployment + networkpolicy + service)") } @@ -125,7 +125,7 @@ func TestAddToModel(t *testing.T) { assert.NotNil(t, model.GetRuntimeObjects()) // With the new map-based API, only objects to be applied are in the map - assert.Equal(t, 2, len(model.GetRuntimeObjects()), "Should have 2 objects to apply (deployment + service)") + assert.Equal(t, 3, len(model.GetRuntimeObjects()), "Should have 3 objects to apply (deployment + networkpolicy + service)") // Verify deployment is in the map deployment := model.GetRuntimeObject(DeploymentKey) diff --git a/pkg/model/testdata/default-config/db-networkpolicy.yaml b/pkg/model/testdata/default-config/db-networkpolicy.yaml new file mode 100644 index 000000000..3c02c8911 --- /dev/null +++ b/pkg/model/testdata/default-config/db-networkpolicy.yaml @@ -0,0 +1,51 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Ingress + - Egress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-dns-egress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Egress + egress: + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - port: 5353 + protocol: UDP + - port: 5353 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-backend-ingress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + ports: + - port: 5432 + protocol: TCP diff --git a/pkg/model/testdata/default-config/networkpolicy.yaml b/pkg/model/testdata/default-config/networkpolicy.yaml new file mode 100644 index 000000000..5d4c48be2 --- /dev/null +++ b/pkg/model/testdata/default-config/networkpolicy.yaml @@ -0,0 +1,104 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + - Egress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-dns-egress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - port: 5353 + protocol: UDP + - port: 5353 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-https-egress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - ports: + - port: 443 + protocol: TCP + - port: 6443 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-psql-egress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - to: + - podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + ports: + - port: 5432 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-metrics-ingress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-monitoring + ports: + - port: 9464 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-router-ingress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: {} # on OCP, replaced with policy-group.network.openshift.io/ingress selector + ports: + - port: 7007 + protocol: TCP diff --git a/pkg/model/testdata/raw-networkpolicy.yaml b/pkg/model/testdata/raw-networkpolicy.yaml new file mode 100644 index 000000000..50f0c48ac --- /dev/null +++ b/pkg/model/testdata/raw-networkpolicy.yaml @@ -0,0 +1,11 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: custom-deny +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" + policyTypes: + - Ingress + - Egress diff --git a/pkg/model/testdata/testflavours/default-config/flavours/flavor1/networkpolicy.yaml b/pkg/model/testdata/testflavours/default-config/flavours/flavor1/networkpolicy.yaml new file mode 100644 index 000000000..44675190d --- /dev/null +++ b/pkg/model/testdata/testflavours/default-config/flavours/flavor1/networkpolicy.yaml @@ -0,0 +1,11 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-intra-network +spec: + podSelector: {} + policyTypes: + - Ingress + ingress: + - from: + - podSelector: {} diff --git a/pkg/model/testdata/testflavours/default-config/networkpolicy.yaml b/pkg/model/testdata/testflavours/default-config/networkpolicy.yaml new file mode 100644 index 000000000..5d4c48be2 --- /dev/null +++ b/pkg/model/testdata/testflavours/default-config/networkpolicy.yaml @@ -0,0 +1,104 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + - Egress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-dns-egress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - port: 5353 + protocol: UDP + - port: 5353 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-https-egress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - ports: + - port: 443 + protocol: TCP + - port: 6443 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-psql-egress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Egress + egress: + - to: + - podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-psql-' + ports: + - port: 5432 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-metrics-ingress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-monitoring + ports: + - port: 9464 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-router-ingress +spec: + podSelector: + matchLabels: + rhdh.redhat.com/app: "" # placeholder for 'backstage-' + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: {} # on OCP, replaced with policy-group.network.openshift.io/ingress selector + ports: + - port: 7007 + protocol: TCP