Chapter 6. Tutorial: Use AWS WAF and AWS ALBs to protect Red Hat OpenShift Service on AWS workloads
You can use AWS Web Application Firewall (WAF) to monitor HTTP and HTTPS requests that are forwarded to your protected web application resources.
You can use an AWS Application Load Balancer (ALB) to add a Web Application Firewall (WAF) to your Red Hat OpenShift Service on AWS workloads. Using an external solution protects Red Hat OpenShift Service on AWS resources from experiencing denial of service due to handling the WAF.
It is recommended that you use the more flexible CloudFront method unless you absolutely must use an ALB-based solution. For more information, see Additional resources.
6.1. Set up your environment Copy linkLink copied to clipboard!
You can use environment variables to ensure consistency across the commands within this lab.
Prerequisites
You have created a multiple availability zone (AZ) Red Hat OpenShift Service on AWS cluster.
NoteAWS ALBs require at least two public subnets across AZs, per the AWS documentation. For this reason, only multiple AZ Red Hat OpenShift Service on AWS clusters can be used with ALBs.
-
You have access to the OpenShift CLI (
oc). -
You have access to the AWS CLI (
aws).
Procedure
Configure the following environment variables:
$ export AWS_PAGER="" $ export CLUSTER=$(oc get infrastructure cluster -o=jsonpath="{.status.infrastructureName}") $ export REGION=$(oc get infrastructure cluster -o=jsonpath="{.status.platformStatus.aws.region}") $ export OIDC_ENDPOINT=$(oc get authentication.config.openshift.io cluster -o jsonpath='{.spec.serviceAccountIssuer}' | sed 's|^https://||') $ export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) $ export SCRATCH="/tmp/${CLUSTER}/alb-waf" $ mkdir -p ${SCRATCH} $ echo "Cluster: $(echo ${CLUSTER} | sed 's/-[a-z0-9]\{5\}$//'), Region: ${REGION}, OIDC Endpoint: ${OIDC_ENDPOINT}, AWS Account ID: ${AWS_ACCOUNT_ID}"
6.1.1. Tag your AWS VPC and subnets Copy linkLink copied to clipboard!
You need to tag your subnets and Virtual Private Cloud (VPC) before using these resources.
These steps only apply to clusters that were deployed into existing VPCs. If you did not deploy your cluster into an existing VPC, proceed to the installation steps.
Procedure
Set the following variables to the proper values for your Red Hat OpenShift Service on AWS deployment:
$ export VPC_ID=<vpc-id> $ export PUBLIC_SUBNET_IDS=(<space-separated-list-of-ids>) $ export PRIVATE_SUBNET_IDS=(<space-separated-list-of-ids>)where:
VPC_ID=<vpc-id>-
Replace with the VPC ID of the cluster, for example:
export VPC_ID=vpc-04c429b7dbc4680ba. PUBLIC_SUBNET_IDS=(<space-separated-list-of-ids>)-
Replace with a space-separated list of the public subnet IDs of the cluster, making sure to preserve the
(). For example:export PUBLIC_SUBNET_IDS=(subnet-056fd6861ad332ba2 subnet-08ce3b4ec753fe74c subnet-071aa28228664972f). PRIVATE_SUBNET_IDS=(<space-separated-list-of-ids>)-
Replace with a space-separated list of the private subnet IDs of the cluster, making sure to preserve the
(). For example:export PRIVATE_SUBNET_IDS=(subnet-0b933d72a8d72c36a subnet-0817eb72070f1d3c2 subnet-0806e64159b66665a).
Add a tag to your cluster’s VPC with the cluster identifier:
$ aws ec2 create-tags --resources ${VPC_ID} \ --tags Key=kubernetes.io/cluster/${CLUSTER},Value=shared --region ${REGION}Add a tag to your public subnets:
$ aws ec2 create-tags \ --resources ${PUBLIC_SUBNET_IDS} \ --tags Key=kubernetes.io/role/elb,Value='1' \ Key=kubernetes.io/cluster/${CLUSTER},Value=shared \ --region ${REGION}Add a tag to your private subnets:
$ aws ec2 create-tags \ --resources ${PRIVATE_SUBNET_IDS} \ --tags Key=kubernetes.io/role/internal-elb,Value='1' \ Key=kubernetes.io/cluster/${CLUSTER},Value=shared \ --region ${REGION}
6.2. Deploy the AWS Load Balancer Operator Copy linkLink copied to clipboard!
The AWS Load Balancer Operator is used to install, manage, and configure an instance of aws-load-balancer-controller in a Red Hat OpenShift Service on AWS cluster. To deploy Application Load Balancers (ALBs) in Red Hat OpenShift Service on AWS, you must first deploy the AWS Load Balancer Operator.
Procedure
Create a new project to deploy the AWS Load Balancer Operator into by running the following command:
$ oc new-project aws-load-balancer-operatorCreate an AWS IAM policy for the AWS Load Balancer Controller if one does not already exist by running the following command:
NoteThe policy is sourced from the upstream AWS Load Balancer Controller policy. This is required by the Operator to function.
$ POLICY_ARN=$(aws iam list-policies --query \ "Policies[?PolicyName=='aws-load-balancer-operator-policy'].{ARN:Arn}" \ --output text)$ if [[ -z "${POLICY_ARN}" ]]; then wget -O "${SCRATCH}/load-balancer-operator-policy.json" \ https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/install/iam_policy.json POLICY_ARN=$(aws --region "$REGION" --query Policy.Arn \ --output text iam create-policy \ --policy-name aws-load-balancer-operator-policy \ --policy-document "file://${SCRATCH}/load-balancer-operator-policy.json") fiCreate an AWS IAM trust policy for AWS Load Balancer Operator:
$ cat <<EOF > "${SCRATCH}/trust-policy.json" { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Condition": { "StringEquals" : { "${OIDC_ENDPOINT}:sub": ["system:serviceaccount:aws-load-balancer-operator:aws-load-balancer-operator-controller-manager", "system:serviceaccount:aws-load-balancer-operator:aws-load-balancer-controller-cluster"] } }, "Principal": { "Federated": "arn:aws:iam::$AWS_ACCOUNT_ID:oidc-provider/${OIDC_ENDPOINT}" }, "Action": "sts:AssumeRoleWithWebIdentity" } ] } EOFCreate an AWS IAM role for the AWS Load Balancer Operator:
$ ROLE_ARN=$(aws iam create-role --role-name "${CLUSTER}-alb-operator" \ --assume-role-policy-document "file://${SCRATCH}/trust-policy.json" \ --query Role.Arn --output text)Attach the AWS Load Balancer Operator policy to the IAM role we created previously by running the following command:
$ aws iam attach-role-policy --role-name "${CLUSTER}-alb-operator" \ --policy-arn ${POLICY_ARN}Create a secret for the AWS Load Balancer Operator to assume the newly created AWS IAM role:
$ cat << EOF | oc apply -f - apiVersion: v1 kind: Secret metadata: name: aws-load-balancer-operator namespace: aws-load-balancer-operator stringData: credentials: | [default] role_arn = ${ROLE_ARN} web_identity_token_file = /var/run/secrets/openshift/serviceaccount/token EOFInstall the AWS Load Balancer Operator:
$ cat << EOF | oc apply -f - apiVersion: operators.coreos.com/v1 kind: OperatorGroup metadata: name: aws-load-balancer-operator namespace: aws-load-balancer-operator spec: upgradeStrategy: Default --- apiVersion: operators.coreos.com/v1alpha1 kind: Subscription metadata: name: aws-load-balancer-operator namespace: aws-load-balancer-operator spec: channel: stable-v1.0 installPlanApproval: Automatic name: aws-load-balancer-operator source: redhat-operators sourceNamespace: openshift-marketplace startingCSV: aws-load-balancer-operator.v1.0.0 EOFDeploy an instance of the AWS Load Balancer Controller using the Operator:
$ cat << EOF | oc apply -f - apiVersion: networking.olm.openshift.io/v1 kind: AWSLoadBalancerController metadata: name: cluster spec: credentials: name: aws-load-balancer-operator enabledAddons: - AWSWAFv2 EOFIf you get an error, the Operator has not completed installing yet. Wait briefly and retry the step.
Check that the Operator and controller pods are both running:
$ oc -n aws-load-balancer-operator get podsExample output
NAME READY STATUS RESTARTS AGE aws-load-balancer-controller-cluster-6ddf658785-pdp5d 1/1 Running 0 99s aws-load-balancer-operator-controller-manager-577d9ffcb9-w6zqn 2/2 Running 0 2m4sIf the pods aren’t running yet, wait briefly and retry the step.
6.3. Deploy a sample application Copy linkLink copied to clipboard!
Deploy a sample application with an AWS Application Load Balancer (ALB) Ingress to verify your load balancer configuration.
Procedure
Create a new project for the sample application:
$ oc new-project hello-worldDeploy a
hello-worldapplication:$ oc new-app -n hello-world --image=docker.io/openshift/hello-openshiftConvert the pre-created service resource to a NodePort service type:
$ oc -n hello-world patch service hello-openshift -p '{"spec":{"type":"NodePort"}}'Deploy an AWS ALB using the AWS Load Balancer Operator:
$ cat << EOF | oc apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: hello-openshift-alb namespace: hello-world annotations: alb.ingress.kubernetes.io/scheme: internet-facing spec: ingressClassName: alb rules: - http: paths: - path: / pathType: Exact backend: service: name: hello-openshift port: number: 8080 EOFCurl the AWS ALB Ingress endpoint to verify the
hello-worldapplication is accessible:NoteAWS ALB provisioning takes a few minutes. If you receive an error that says
curl: (6) Could not resolve host, please wait and try again.$ INGRESS=$(oc -n hello-world get ingress hello-openshift-alb -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') $ curl "http://${INGRESS}"Example output
Hello OpenShift!
6.4. Configure the AWS WAF Copy linkLink copied to clipboard!
You can use the AWS Web Application Firewall (WAF) service to monitor, protect, and control the HTTP and HTTPS requests that are forwarded to your protected web application resources, such as Red Hat OpenShift Service on AWS.
Procedure
Create an AWS WAF rules file to apply to your web access control list (ACL):
$ cat << EOF > ${SCRATCH}/waf-rules.json [ { "Name": "AWS-AWSManagedRulesCommonRuleSet", "Priority": 0, "Statement": { "ManagedRuleGroupStatement": { "VendorName": "AWS", "Name": "AWSManagedRulesCommonRuleSet" } }, "OverrideAction": { "None": {} }, "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "AWS-AWSManagedRulesCommonRuleSet" } }, { "Name": "AWS-AWSManagedRulesSQLiRuleSet", "Priority": 1, "Statement": { "ManagedRuleGroupStatement": { "VendorName": "AWS", "Name": "AWSManagedRulesSQLiRuleSet" } }, "OverrideAction": { "None": {} }, "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "AWS-AWSManagedRulesSQLiRuleSet" } } ] EOFThis enables the Core (Common) and SQL AWS Managed Rule Sets.
Create an AWS WAF Web ACL using the rules you specified in the previous steps:
$ WAF_ARN=$(aws wafv2 create-web-acl \ --name ${CLUSTER}-waf \ --region ${REGION} \ --default-action Allow={} \ --scope REGIONAL \ --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=${CLUSTER}-waf-metrics \ --rules file://${SCRATCH}/waf-rules.json \ --query 'Summary.ARN' \ --output text)Annotate the Ingress resource with the AWS WAF Web ACL (Amazon Resource Name) ARN:
$ oc annotate -n hello-world ingress.networking.k8s.io/hello-openshift-alb \ alb.ingress.kubernetes.io/wafv2-acl-arn=${WAF_ARN}
Verification
Wait 10 seconds for the rules to propagate and test that the application still works:
$ curl "http://${INGRESS}"Example output
Hello OpenShift!Test that the WAF denies a bad request:
$ curl -X POST "http://${INGRESS}" \ -F "user='<script><alert>Hello></alert></script>'"Example output
<html> <head><title>403 Forbidden</title></head> <body> <center><h1>403 Forbidden</h1></center> </body> </htmlNoteActivation of the AWS WAF integration can sometimes take several minutes. If you do not receive a
403 Forbiddenerror, please wait a few seconds and try again.The expected result is a
403 Forbiddenerror, which means the AWS WAF is protecting your application.