×

AWS WAF is a web application firewall that lets you monitor the 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 (ROSA) workloads. Using an external solution protects ROSA resources from experiencing denial of service due to handling the WAF.

It is recommended that you use the CloudFront method unless you absolutely must use an ALB based solution.

Prerequisites

AWS ALBs require a multi-AZ cluster, as well as three public subnets split across three AZs in the same VPC as the cluster.

Environment setup

  • Prepare the environment variables:

    $ export AWS_PAGER=""
    $ export CLUSTER_NAME=$(oc get infrastructure cluster -o=jsonpath="{.status.infrastructureName}"  | sed 's/-[a-z0-9]\{5\}$//')
    $ 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_NAME}/alb-waf"
    $ mkdir -p ${SCRATCH}
    $ echo "Cluster: ${CLUSTER_NAME}, Region: ${REGION}, OIDC Endpoint: ${OIDC_ENDPOINT}, AWS Account ID: ${AWS_ACCOUNT_ID}"

AWS VPC and subnets

This section only applies to clusters that were deployed into existing VPCs. If you did not deploy your cluster into an existing VPC, skip this section and proceed to the installation section below.

  1. Set the below variables to the proper values for your ROSA deployment:

    $ export VPC_ID=<vpc-id>
    $ export PUBLIC_SUBNET_IDS=<public-subnets>
    $ export PRIVATE_SUBNET_IDS=<private-subnets>
  2. Add a tag to your cluster’s VPC with the cluster name:

    $ aws ec2 create-tags --resources ${VPC_ID} --tags Key=kubernetes.io/cluster/${CLUSTER_NAME},Value=owned --region ${REGION}
  3. Add a tag to your public subnets:

    $ aws ec2 create-tags \
         --resources ${PUBLIC_SUBNET_IDS} \
         --tags Key=kubernetes.io/role/elb,Value='' \
         --region ${REGION}
  4. Add a tag to your private subnets:

    $ aws ec2 create-tags \
         --resources "${PRIVATE_SUBNET_IDS}" \
         --tags Key=kubernetes.io/role/internal-elb,Value='' \
         --region ${REGION}

Deploy the AWS Load Balancer Operator

The AWS Load Balancer Operator is used to used to install, manage and configure an instance of aws-load-balancer-controller in a ROSA cluster. To deploy ALBs in ROSA, we need to first deploy the AWS Load Balancer Operator.

  1. Create an AWS IAM policy for the AWS Load Balancer Controller:

    The policy is sourced from the upstream AWS Load Balancer Controller policy plus permission to create tags on subnets. This is required by the operator to function.

    $ oc new-project aws-load-balancer-operator
    $ 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/rh-mobb/documentation/main/content/docs/rosa/aws-load-balancer-operator/load-balancer-operator-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")
    fi
    $ echo $POLICY_ARN
  2. Create 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"
     }
     ]
    }
    EOF
  3. Create an AWS IAM role for the AWS Load Balancer Operator:

    $ ROLE_ARN=$(aws iam create-role --role-name "${CLUSTER_NAME}-alb-operator" \
       --assume-role-policy-document "file://${SCRATCH}/trust-policy.json" \
       --query Role.Arn --output text)
    $ echo $ROLE_ARN
    
    $ aws iam attach-role-policy --role-name "${CLUSTER_NAME}-alb-operator" \
         --policy-arn $POLICY_ARN
  4. Create a secret for the AWS Load Balancer Operator to assume our 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
    EOF
  5. Install the Red Hat 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
    EOF
  6. Deploy an instance of the AWS Load Balancer Controller using the operator:

    If you get an error here wait a minute and try again, it means the Operator has not completed installing yet.

    $ 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
    EOF
  7. Check the that the operator and controller pods are both running:

    $ oc -n aws-load-balancer-operator get pods

    You should see the following, if not wait a moment and retry:

    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          2m4s

Deploy a sample application

  1. Create a new project for our sample application:

    $ oc new-project hello-world
  2. Deploy a hello world application:

    $ oc new-app -n hello-world --image=docker.io/openshift/hello-openshift
  3. Convert the pre-created service resource to a NodePort service type:

    $ oc -n hello-world patch service hello-openshift -p '{"spec":{"type":"NodePort"}}'
  4. 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
    EOF
  5. Curl the AWS ALB Ingress endpoint to verify the hello world application is accessible:

    AWS 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!

Configure the AWS WAF

The AWS WAF service is a web application firewall that lets you monitor, protect, and control the HTTP and HTTPS requests that are forwarded to your protected web application resources, like ROSA.

  1. Create a AWS WAF rules file to apply to our web 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"
          }
        }
    ]
    EOF

    This will enable the Core (Common) and SQL AWS Managed Rule Sets.

  2. Create an AWS WAF Web ACL using the rules we specified above:

    $ WAF_ARN=$(aws wafv2 create-web-acl \
      --name ${CLUSTER_NAME}-waf \
      --region ${REGION} \
      --default-action Allow={} \
      --scope REGIONAL \
      --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=${CLUSTER_NAME}-waf-metrics \
      --rules file://${SCRATCH}/waf-rules.json \
      --query 'Summary.ARN' \
      --output text)
  3. Annotate the Ingress resource with the AWS WAF Web ACL ARN:

    $ oc annotate -n hello-world ingress.networking.k8s.io/hello-openshift-alb \
      alb.ingress.kubernetes.io/wafv2-acl-arn=${WAF_ARN}
  4. Wait for 10 seconds for the rules to propagate and test that the app still works:

    $ curl "http://${INGRESS}"
    Example output
    Hello OpenShift!
  5. 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>
    </html

    The expected result is a 403 Forbidden error, which means the AWS WAF is protecting your application.