Chapter 3. Tutorial: Retrofit Bring Your Own (BYO) firewall rules for OpenShift Dedicated on Google Cloud
Google Cloud provisions OpenShift Dedicated clusters with platform-managed firewall rules that use Google Cloud network tags to target instances. The bring your own (BYO) firewall feature replaces these with rules that target Workload Identity Federation (WIF) service accounts instead. Targeting service accounts gives you full ownership of your firewall rules while maintaining the same network security posture.
In this tutorial, you replace the tag-targeted firewall rules on an existing OpenShift Dedicated on Google Cloud cluster with service-account-targeted BYO rules. You then disable Cloud Controller Manager (CCM) firewall management and validate Day 2 operations.
This content is authored by Red Hat experts but has not yet been tested on every supported configuration.
3.1. Overview of the bring your own (BYO) firewall retrofit procedure Copy linkLink copied to clipboard!
The retrofit procedure replaces platform-managed firewall rules that use Google Cloud network tags with customer-managed rules that target Workload Identity Federation (WIF) service accounts.
The retrofit consists of the following stages:
- Create service-account-targeted bring your own (BYO) rules alongside the existing tag-targeted rules.
- Verify coexistence, ensuring both rule sets are active with no conflicts.
- Delete the platform-managed tag-targeted rules.
- Disable firewall management in the Cloud Controller Manager (CCM) so that the CCM does not re-create rules.
- Optionally, validate Day 2 operations such as scale-up and upgrades with BYO rules only.
3.2. Set environment variables Copy linkLink copied to clipboard!
Set the cluster name and Google Cloud project manually. The procedure derives all other values dynamically.
Prerequisites
- You have an existing OpenShift Dedicated on Google Cloud cluster deployed with Workload Identity Federation (WIF).
-
You have the
gcloudCLI installed with permissions to manage firewall rules in the Google Cloud project. -
You have the
ocCLI authenticated to the cluster withcluster-adminprivileges. -
You have the
ocmCLI logged in to OpenShift Cluster Manager. -
You have the
jqCLI installed.
The remaining procedures in this tutorial use the environment variables that you set here. Shell variables do not persist across terminal sessions. Run the remaining procedures in the same terminal session, or repeat this procedure in each new session.
Procedure
Set the cluster name and Google Cloud project:
$ export CLUSTER_NAME="<your_cluster_name>" $ export GCP_PROJECT="<your_gcp_project>"Derive the infrastructure ID from OpenShift Cluster Manager:
$ export INFRA_ID=$(ocm describe cluster ${CLUSTER_NAME} --json | jq -r '.infra_id') $ echo "INFRA_ID: ${INFRA_ID}"Derive the virtual private cloud (VPC) network and bring your own (BYO) rule prefix:
$ export VPC_NETWORK="${INFRA_ID}-network" $ export BYO_PREFIX="${CLUSTER_NAME}-byo"Derive the WIF service accounts from the cluster compute instances:
$ export CP_SA=$(gcloud compute instances list \ --project=${GCP_PROJECT} \ --filter="name~${INFRA_ID}-master" \ --format="value(serviceAccounts.email)" \ --limit=1) $ export WORKER_SA=$(gcloud compute instances list \ --project=${GCP_PROJECT} \ --filter="name~${INFRA_ID}-worker" \ --format="value(serviceAccounts.email)" \ --limit=1) $ echo "CP_SA: ${CP_SA}" $ echo "WORKER_SA: ${WORKER_SA}"NoteThe
--limit=1option returns the service account of a single instance. All control plane instances in a cluster share one service account, and all worker instances share another, so a single instance is representative of its machine type.Set the Google Cloud health check probe IP ranges. These ranges are the Google Cloud global health check source ranges that all Google Cloud load balancers use:
$ export LB_CIDRS="35.191.0.0/16,130.211.0.0/22,209.85.152.0/22,209.85.204.0/22"
Verification
Verify that each variable has a value:
$ echo "CLUSTER_NAME: ${CLUSTER_NAME}" $ echo "GCP_PROJECT: ${GCP_PROJECT}" $ echo "INFRA_ID: ${INFRA_ID}" $ echo "VPC_NETWORK: ${VPC_NETWORK}" $ echo "BYO_PREFIX: ${BYO_PREFIX}" $ echo "CP_SA: ${CP_SA}" $ echo "WORKER_SA: ${WORKER_SA}" $ echo "LB_CIDRS: ${LB_CIDRS}"Example output
CLUSTER_NAME: my-cluster GCP_PROJECT: my-gcp-project INFRA_ID: my-cluster-abcde VPC_NETWORK: my-cluster-abcde-network BYO_PREFIX: my-cluster-byo CP_SA: my-cluster-abcde-m@my-gcp-project.iam.gserviceaccount.com WORKER_SA: my-cluster-abcde-w@my-gcp-project.iam.gserviceaccount.com LB_CIDRS: 35.191.0.0/16,130.211.0.0/22,209.85.152.0/22,209.85.204.0/22Each variable must have a non-empty value before you continue.
3.3. Record the existing platform-managed firewall rules Copy linkLink copied to clipboard!
Before making any changes, capture a snapshot of the current firewall rules.
Procedure
List all firewall rules in the cluster virtual private cloud (VPC):
$ gcloud compute firewall-rules list \ --project=${GCP_PROJECT} \ --filter="network=${VPC_NETWORK}" \ --format="table( name, direction, allowed[].map().firewall_rule().list():label=ALLOWED, sourceRanges.list():label=SOURCE_RANGES, targetTags.list():label=TARGET_TAGS, targetServiceAccounts.list():label=TARGET_SVC_ACCT )"NoteAny additional
k8s-*rules are Google Cloud firewall rules that Kubernetes creates automatically when you provision aLoadBalancerservice or an ingress on your cluster.Save the output to a file so that you can review it later:
$ gcloud compute firewall-rules list \ --project=${GCP_PROJECT} \ --filter="network=${VPC_NETWORK}" \ --format=json > firewall-rules-backup.json
Verification
Verify that the snapshot file has the current rules:
$ jq -r '.[].name' firewall-rules-backup.jsonExample output
my-cluster-abcde-api my-cluster-abcde-control-plane my-cluster-abcde-etcd my-cluster-abcde-health-checks my-cluster-abcde-internal-cluster my-cluster-abcde-internal-network
3.4. Platform-managed firewall rules reference Copy linkLink copied to clipboard!
A standard OpenShift Dedicated on Google Cloud cluster has six platform-managed rules, all of which use network tags as their targeting mechanism.
| Rule name pattern | Ports | Targeting |
|---|---|---|
|
|
| Network tags |
|
|
| Network tags |
|
|
| Network tags |
|
|
| Network tags |
|
|
| Network tags |
|
|
| Network tags |
3.5. Establishing a baseline (optional) Copy linkLink copied to clipboard!
Deploy a test workload to verify networking before, during, and after the migration.
Procedure
Create a test project and deploy a sample application:
$ oc new-project byo-fw-test $ oc create deployment hello --image=quay.io/openshift/origin-hello-openshift $ oc expose deployment hello --port=8080 $ oc expose svc helloCreate a
LoadBalancerservice to test Cloud Controller Manager (CCM) behavior:$ oc create -f - <<EOF apiVersion: v1 kind: Service metadata: name: test-lb namespace: byo-fw-test spec: type: LoadBalancer ports: - port: 80 targetPort: 8080 selector: app: hello EOFWait for Google Cloud to assign an external IP address to the load balancer. Assignment can take up to 90 seconds:
$ oc get svc test-lb -n byo-fw-test -wExample output
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE test-lb LoadBalancer 172.30.100.10 34.72.100.200 80:31234/TCP 75s
Verification
Verify that the API server is healthy:
$ oc get --raw /healthzExample output
okVerify that the ingress route is responding:
$ curl -s http://$(oc get route hello -n byo-fw-test -o jsonpath='{.spec.host}')Example output
Hello OpenShift!
3.6. Create bring your own (BYO) firewall rules Copy linkLink copied to clipboard!
Create eight service-account-targeted rules that mirror the platform-managed rules and cover ingress. These rules coexist safely with the existing tag-targeted rules.
Procedure
Create the API server rule for external access:
NoteThe
--source-ranges=0.0.0.0/0value allows access from any IP address. If you restrict API access to specific networks, replace this value with a comma-separated list of your CIDR ranges.$ gcloud compute firewall-rules create ${BYO_PREFIX}-api \ --project=${GCP_PROJECT} \ --network=${VPC_NETWORK} \ --direction=INGRESS \ --action=ALLOW \ --rules=tcp:6443 \ --source-ranges=0.0.0.0/0 \ --target-service-accounts=${CP_SA} \ --priority=1000Create the etcd rule for control plane internal communication:
$ gcloud compute firewall-rules create ${BYO_PREFIX}-etcd \ --project=${GCP_PROJECT} \ --network=${VPC_NETWORK} \ --direction=INGRESS \ --action=ALLOW \ --rules=tcp:2379-2380 \ --source-service-accounts=${CP_SA} \ --target-service-accounts=${CP_SA} \ --priority=1000Create the Google Cloud load balancer health checks rule for the control plane:
$ gcloud compute firewall-rules create ${BYO_PREFIX}-health-checks \ --project=${GCP_PROJECT} \ --network=${VPC_NETWORK} \ --direction=INGRESS \ --action=ALLOW \ --rules=tcp:6080,tcp:6443,tcp:22624 \ --source-ranges=${LB_CIDRS} \ --target-service-accounts=${CP_SA} \ --priority=1000Create the control plane services rule:
$ gcloud compute firewall-rules create ${BYO_PREFIX}-control-plane \ --project=${GCP_PROJECT} \ --network=${VPC_NETWORK} \ --direction=INGRESS \ --action=ALLOW \ --rules=tcp:2379-2380,tcp:6443,tcp:9258-9259,tcp:10257-10259,tcp:22623-22624 \ --source-service-accounts=${CP_SA} \ --target-service-accounts=${CP_SA} \ --priority=1000Create the internal network rule for node-to-node services:
$ gcloud compute firewall-rules create ${BYO_PREFIX}-internal-network \ --project=${GCP_PROJECT} \ --network=${VPC_NETWORK} \ --direction=INGRESS \ --action=ALLOW \ --rules=tcp:9000-9999,tcp:10250-10259,tcp:22623-22624,tcp:30000-32767,udp:4789,udp:6081,udp:9000-9999,udp:500,udp:4500 \ --source-service-accounts=${CP_SA},${WORKER_SA} \ --target-service-accounts=${CP_SA},${WORKER_SA} \ --priority=1000Create the internal cluster rule for all traffic between nodes:
$ gcloud compute firewall-rules create ${BYO_PREFIX}-internal-cluster \ --project=${GCP_PROJECT} \ --network=${VPC_NETWORK} \ --direction=INGRESS \ --action=ALLOW \ --rules=tcp,udp,icmp \ --source-service-accounts=${CP_SA},${WORKER_SA} \ --target-service-accounts=${CP_SA},${WORKER_SA} \ --priority=1000Create the ingress rule for external HTTP and HTTPS traffic to workers:
$ gcloud compute firewall-rules create ${BYO_PREFIX}-ingress-k8s-fw \ --project=${GCP_PROJECT} \ --network=${VPC_NETWORK} \ --direction=INGRESS \ --action=ALLOW \ --rules=tcp:80,tcp:443 \ --source-ranges=0.0.0.0/0 \ --target-service-accounts=${WORKER_SA} \ --priority=1000Create the ingress health checks rule for Google Cloud load balancer to worker node ports:
$ gcloud compute firewall-rules create ${BYO_PREFIX}-ingress-k8s-http-hc \ --project=${GCP_PROJECT} \ --network=${VPC_NETWORK} \ --direction=INGRESS \ --action=ALLOW \ --rules=tcp:30000-32767 \ --source-ranges=${LB_CIDRS} \ --target-service-accounts=${WORKER_SA} \ --priority=1000
Verification
Confirm that all eight rules exist:
$ gcloud compute firewall-rules list \ --project=${GCP_PROJECT} \ --filter="network=${VPC_NETWORK} AND name~${BYO_PREFIX}" \ --format="table( name, direction, allowed[].map().firewall_rule().list():label=ALLOWED, targetServiceAccounts.list():label=TARGET_SVC_ACCT )"Example output
NAME DIRECTION ALLOWED TARGET_SVC_ACCT my-cluster-byo-api INGRESS tcp:6443 my-cluster-abcde-m@my-gcp-project.iam.gserviceaccount.com my-cluster-byo-control-plane INGRESS tcp:2379-2380,tcp:6443,tcp:9258-9259,tcp:10257-10259,tcp:22623-22624 my-cluster-abcde-m@my-gcp-project.iam.gserviceaccount.com my-cluster-byo-etcd INGRESS tcp:2379-2380 my-cluster-abcde-m@my-gcp-project.iam.gserviceaccount.com my-cluster-byo-health-checks INGRESS tcp:6080,tcp:6443,tcp:22624 my-cluster-abcde-m@my-gcp-project.iam.gserviceaccount.com my-cluster-byo-ingress-k8s-fw INGRESS tcp:80,tcp:443 my-cluster-abcde-w@my-gcp-project.iam.gserviceaccount.com my-cluster-byo-ingress-k8s-http-hc INGRESS tcp:30000-32767 my-cluster-abcde-w@my-gcp-project.iam.gserviceaccount.com my-cluster-byo-internal-cluster INGRESS tcp,udp,icmp my-cluster-abcde-m@my-gcp-project.iam.gserviceaccount.com,my-cluster-abcde-w@my-gcp-project.iam.gserviceaccount.com my-cluster-byo-internal-network INGRESS tcp:9000-9999,udp:4789,udp:6081 my-cluster-abcde-m@my-gcp-project.iam.gserviceaccount.com,my-cluster-abcde-w@my-gcp-project.iam.gserviceaccount.comVerify that the cluster is still healthy:
$ oc get --raw /healthzExample output
ok
3.7. Bring your own (BYO) firewall rules reference Copy linkLink copied to clipboard!
The following table summarizes the eight bring your own (BYO) firewall rules that you create in Creating bring your own (BYO) firewall rules.
| Number | Rule | Ports | Source | Target service account |
|---|---|---|---|---|
| 1 |
|
|
| Control plane |
| 2 |
|
| Control plane service account | Control plane |
| 3 |
|
| Google Cloud load balancer CIDR ranges | Control plane |
| 4 |
|
| Control plane service account | Control plane |
| 5 |
|
| Control plane and worker service accounts | Control plane and worker |
| 6 |
|
| Control plane and worker service accounts | Control plane and worker |
| 7 |
|
|
| Worker |
| 8 |
|
| Google Cloud load balancer CIDR ranges | Worker |
3.8. Delete platform-managed firewall rules Copy linkLink copied to clipboard!
With bring your own (BYO) rules in place, remove the original tag-targeted rules. Leave the k8s-* rules that the Cloud Controller Manager (CCM) manages for existing LoadBalancer services in place.
Verify that the BYO firewall rules are active and the cluster is healthy before deleting the platform-managed rules. Deleting these rules without BYO replacements in place disrupts cluster networking.
Procedure
Delete the six platform-managed firewall rules:
$ gcloud compute firewall-rules delete \ ${INFRA_ID}-api \ ${INFRA_ID}-control-plane \ ${INFRA_ID}-etcd \ ${INFRA_ID}-health-checks \ ${INFRA_ID}-internal-cluster \ ${INFRA_ID}-internal-network \ --project=${GCP_PROJECT} --quiet
Verification
Verify that the API server is healthy:
$ oc get --raw /healthzExample output
okVerify that all nodes are in the
Readystate:$ oc get nodesExample output
NAME STATUS ROLES AGE VERSION my-cluster-abcde-master-0.c.example.internal Ready control-plane,master 3d v1.31.5 my-cluster-abcde-master-1.c.example.internal Ready control-plane,master 3d v1.31.5 my-cluster-abcde-master-2.c.example.internal Ready control-plane,master 3d v1.31.5 my-cluster-abcde-worker-a-xxxxx.c.example.internal Ready worker 3d v1.31.5 my-cluster-abcde-worker-b-yyyyy.c.example.internal Ready worker 3d v1.31.5Verify that all cluster Operators are healthy:
$ oc get coIf you deployed the baseline workload, verify that the ingress route is responding:
$ curl -s http://$(oc get route hello -n byo-fw-test -o jsonpath='{.spec.host}')Example output
Hello OpenShift!
3.9. Disable Cloud Controller Manager firewall management Copy linkLink copied to clipboard!
With bring your own (BYO) firewall rules, you own every firewall rule for your cluster. Configure the Cloud Controller Manager (CCM) so that it no longer creates or deletes firewall rules when you create or delete LoadBalancer services. The CCM continues to manage load balancer resources such as forwarding rules and target pools, so this change affects firewall operations only.
The CCM reads its cloud config from the cloud-conf config map in the openshift-cloud-controller-manager namespace. A sync controller manages this config map. If you patch the config map directly, the sync controller reverts your change within seconds.
Patch the source config map instead. Changes propagate automatically through the following three-stage sync chain:
-
openshift-config/cloud-provider-config(key:config) -
openshift-config-managed/kube-cloud-config(key:cloud.conf) -
openshift-cloud-controller-manager/cloud-conf(key:cloud.conf)
Prerequisites
- You deleted the platform-managed firewall rules.
-
You have the
jqCLI installed.
Procedure
Read the current cloud config:
$ CURRENT_CONFIG=$(oc get configmap cloud-provider-config -n openshift-config \ -o jsonpath='{.data.config}')Set
firewall-rules-managementtoDisabled. The following command removes any existingfirewall-rules-managemententry and appends a disabled entry:$ NEW_CONFIG=$(printf '%s\n firewall-rules-management = Disabled\n' \ "$(echo "$CURRENT_CONFIG" | grep -v 'firewall-rules-management')")Patch the new config back into the source config map:
$ oc patch configmap cloud-provider-config -n openshift-config \ --type merge \ --patch "$(jq -n --arg config "$NEW_CONFIG" '{data: {config: $config}}')"- Wait about 30 seconds for the sync controllers to propagate the change.
Restart the CCM pods to pick up the new config:
$ oc delete pods -n openshift-cloud-controller-manager \ -l k8s-app=gcp-cloud-controller-manager
Verification
Verify that the flag propagated to the source config map:
$ oc get cm cloud-provider-config -n openshift-config \ -o jsonpath='{.data.config}' | grep firewallExample output
firewall-rules-management = DisabledVerify that the flag propagated to the intermediate config map:
$ oc get cm kube-cloud-config -n openshift-config-managed \ -o jsonpath='{.data.cloud\.conf}' | grep firewallExample output
firewall-rules-management = DisabledVerify that the flag propagated to the CCM config map:
$ oc get cm cloud-conf -n openshift-cloud-controller-manager \ -o jsonpath='{.data.cloud\.conf}' | grep firewallExample output
firewall-rules-management = DisabledCheck the CCM logs for the disabled flag:
$ oc logs -n openshift-cloud-controller-manager \ -l k8s-app=gcp-cloud-controller-manager --tail=50 | grep -i firewallLog entries that contain
firewall rules are unmanagedconfirm that the CCM skips firewall operations. The exact function names and message format can vary across OpenShift Dedicated versions.
3.10. Validate Day 2 operations (optional) Copy linkLink copied to clipboard!
Optionally validate that bring your own (BYO) rules and the firewall-rules-management flag survive worker scale-up and z-stream upgrades.
Prerequisites
- You set the environment variables for this tutorial in the same terminal session.
- You disabled Cloud Controller Manager (CCM) firewall management.
Procedure
Scale a machine pool to verify that BYO rules cover new nodes. The following command scales the
workermachine pool from the default of 2 replicas to 3 replicas:$ ocm edit machinepool worker --cluster=${CLUSTER_NAME} --replicas=3Service-account-targeted rules apply to any instance that runs with the matching Workload Identity Federation (WIF) service account, so new nodes require no firewall changes.
Watch for the new node to reach the
Readystate:$ oc get nodes -wExample output
NAME STATUS ROLES AGE VERSION my-cluster-abcde-worker-c-zzzzz.c.example.internal Ready worker 92s v1.31.5Scale the machine pool back down when the validation is complete:
$ ocm edit machinepool worker --cluster=${CLUSTER_NAME} --replicas=2To validate a z-stream upgrade, list the available upgrade versions:
$ ocm describe cluster ${CLUSTER_NAME} --json | jq -r '.version.available_upgrades[]'Schedule an upgrade, replacing
<target_version>with a version from the earlier command:ImportantOn OpenShift Dedicated, you must start upgrades through OpenShift Cluster Manager rather than by using
oc adm upgrade. TheUpgradeable=Falsegate that the cloud-credential annotation sets blocks minor version upgrades only. It does not block z-stream upgrades.$ CLUSTER_ID=$(ocm describe cluster ${CLUSTER_NAME} --json | jq -r '.id') $ TARGET_VERSION="<target_version>" $ NEXT_TS=$(date -u -d '+10 minutes' '+%Y-%m-%dT%H:%M:%SZ') $ echo '{"version":"'${TARGET_VERSION}'","schedule_type":"manual","next_run":"'${NEXT_TS}'"}' \ > /tmp/upgrade-policy.json $ ocm post /api/clusters_mgmt/v1/clusters/${CLUSTER_ID}/upgrade_policies \ --body=/tmp/upgrade-policy.json $ rm /tmp/upgrade-policy.jsonNoteOn Linux, use
date -u -d '\+10 minutes' '\+%Y-%m-%dT%H:%M:%SZ'instead.Check the upgrade progress:
$ ocm list upgradepolicies --cluster=${CLUSTER_NAME}
Verification
After the upgrade completes, perform the following checks:
Verify the cluster version:
$ oc get clusterversionExample output
NAME VERSION AVAILABLE PROGRESSING SINCE STATUS version 4.18.2 True False 12m Cluster version is 4.18.2Verify that all nodes are in the
Readystate:$ oc get nodesExample output
NAME STATUS ROLES AGE VERSION my-cluster-abcde-master-0.c.example.internal Ready control-plane,master 3d v1.31.6 my-cluster-abcde-master-1.c.example.internal Ready control-plane,master 3d v1.31.6 my-cluster-abcde-master-2.c.example.internal Ready control-plane,master 3d v1.31.6 my-cluster-abcde-worker-a-xxxxx.c.example.internal Ready worker 3d v1.31.6 my-cluster-abcde-worker-b-yyyyy.c.example.internal Ready worker 3d v1.31.6Verify that all cluster Operators are healthy:
$ oc get coExample output
NAME VERSION AVAILABLE PROGRESSING DEGRADED SINCE authentication 4.18.2 True False False 10m cloud-controller-manager 4.18.2 True False False 11m dns 4.18.2 True False False 10m etcd 4.18.2 True False False 11m ingress 4.18.2 True False False 10m kube-apiserver 4.18.2 True False False 9m network 4.18.2 True False False 11mConfirm that the BYO firewall rules are still present:
$ gcloud compute firewall-rules list \ --project=${GCP_PROJECT} \ --filter="network=${VPC_NETWORK} AND name~${BYO_PREFIX}" \ --format="table(name)"Example output
NAME my-cluster-byo-api my-cluster-byo-control-plane my-cluster-byo-etcd my-cluster-byo-health-checks my-cluster-byo-ingress-k8s-fw my-cluster-byo-ingress-k8s-http-hc my-cluster-byo-internal-cluster my-cluster-byo-internal-networkConfirm that the platform-managed rules are still not present:
$ gcloud compute firewall-rules list \ --project=${GCP_PROJECT} \ --filter="network=${VPC_NETWORK} AND name~${INFRA_ID}" \ --format="table(name)"The command returns no platform-managed rules.
Confirm that the flag persisted through the upgrade:
$ oc get cm cloud-conf -n openshift-cloud-controller-manager \ -o jsonpath='{.data.cloud\.conf}' | grep firewallExample output
firewall-rules-management = Disabled
3.11. Clean up the baseline test workload (optional) Copy linkLink copied to clipboard!
If you deployed the baseline test workload earlier in this tutorial, remove it.
Procedure
Delete the test project:
$ oc delete project byo-fw-test
Verification
Verify that the test project no longer exists:
$ oc get project byo-fw-testExample output
Error from server (NotFound): namespaces "byo-fw-test" not found