Integrate an OPA engine with an ingress gateway

Updated at:

When dynamic access control is required, you can integrate an Open Policy Agent (OPA) engine in an ingress gateway to customize authorization policies based on user identities or request content and control communication between services in real time. This effectively prevents unauthorized access, reduces the risks of data breach, and enhances the security of applications in a Service Mesh (ASM) instance. This topic describes how to use an OPA engine to authenticate and authorize requests that are received by an ingress gateway. In this example, requests flow through the ingress gateway to access the HTTPBin application.

Prerequisites

Step 1: Deploy an OPA engine

  1. Create a file named asm-opa.yaml with the following content.

    This YAML file deploys an OPA Service, a Deployment, and a Secret.

    Kind

    Description

    Deployment

    • Replace the region ID in the image path registry-vpc.cn-hangzhou.aliyuncs.com/acs/opa:0.46.1-istio-3-static with the ID of the region where your cluster is located.

    • Decision logging is enabled by default (--set=decision_logs.console=true) for easier debugging.

    Secret

    The Secret defines an OPA policy with the following rules:

    • Allows requests to paths starting with health.

    • Allows HEAD requests.

    • Allows requests from the user alice.

      Note

      The username is derived from the request's Authorization header, which has the format Authorization: Basic <base64-encoded 'username:password'>.

    asm-opa.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: asm-opa
      labels:
        app: opa
    spec:
      ports:
        - name: grpc
          port: 9191
          targetPort: 9191
          protocol: TCP
        - name: http
          port: 8181
          targetPort: 8181
          protocol: TCP
      selector:
        app: opa
    ---
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: opa
      labels:
        app: opa
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: opa
      template:
        metadata:
          labels:
            app: opa
          annotations:
            sidecar.istio.io/inject: "false"
        spec:
          containers:
            - name: opa
              image: registry-vpc.cn-hangzhou.aliyuncs.com/acs/opa:0.46.1-istio-3-static
              securityContext:
                runAsUser: 1111
              volumeMounts:
                - readOnly: true
                  mountPath: /policy
                  name: opa-policy
              args:
                - "run"
                - "--server"
                - "--addr=0.0.0.0:8181"
                - "--diagnostic-addr=0.0.0.0:8282"
                - "--set=plugins.envoy_ext_authz_grpc.addr=:9191"
                - "--set=plugins.envoy_ext_authz_grpc.path=asm/authz/allow"
                - "--set=decision_logs.console=true"
                - "--ignore=.*"
                - "/policy/policy.rego"
              ports:
                - containerPort: 9191
                  protocol: TCP
              resources:
                limits:
                  cpu: "0"
                  memory: "0"
          volumes:
            - name: opa-policy
              secret:
                secretName: opa-policy
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: opa-policy
    type: Opaque
    stringData:
      policy.rego: |
        package asm.authz
    
        import future.keywords
    
        import input.attributes.request.http as http_request
        import input.parsed_path
    
        default allow := false
    
        allow if {
          parsed_path[0] == "health"
        }
    
        allow if {
          http_request.method == "HEAD"
        }
    
        allow if {
          user_name == "alice"
        }
    
        user_name := parsed if {
          [_, encoded] := split(http_request.headers.authorization, " ")
          [parsed, _] := split(base64url.decode(encoded), ":")
        }
    
  2. Connect to your ACK cluster and run the following command to deploy OPA:

    kubectl apply -f asm-opa.yaml

Step 2: Use the external authorization feature of the ingress gateway to integrate the OPA engine with the ingress gateway

  1. Log on to the ASM console. In the left-side navigation pane, choose Service Mesh > Mesh Management.

  2. On the Mesh Management page, click the name of the ASM instance. In the left-side navigation pane, choose ASM Gateways > Ingress Gateway.

  3. On the Ingress Gateway page, find the gateway that you want to integrate with OPA and click Gateway Security.

  4. In the left-side navigation pane of the gateway, choose Gateway Security > External Authorization Service.

  5. Configure a custom authorization service.

    1. In the Custom Authorization Service Configuration wizard, configure OPA as the custom authorization service for the gateway, and then click Next.

      Turn on the Enable Gateway Custom Authorization Service switch, select the Custom authorization service based on envoy.ext_authz (HTTP or gRPC) tab, set Protocol to GRPC, service address to asm-opa.default.svc.cluster.local, service port to 9191, and timeout to 10 seconds.

    2. In the Matching Rules wizard, configure matching rules to specify which requests require OPA authorization, and then click Submit.

      Set Matching Mode to Selected requests must be authorized, select Custom matching rules, turn on HTTP Path (Path), and enter /status/*.

      A message appears, confirming that the Gateway Custom Authorization Service was created successfully and listing the created Istio native resources: an ASMExtensionProvider (resource name: grpcextauth-asmsecuritypolicy-ingressgateway-extauthz) and an AuthorizationPolicy (resource name: ingressgateway-extauthz-ap-wg-gateway-istio-system-gateway-ingressgateway). You can click View YAML to view the resource configuration or click Edit to modify the authorization service.

Step 3: Test httpbin access

  1. Run the following command to access the / path.

    curl ${ASM_INGRESS_GATEWAY_IP}/ -I -X GET

    Expected output:

    HTTP/1.1 200 OK
    server: istio-envoy
    date: Tue, 25 Jul 2023 08:30:58 GMT
    content-type: text/html; charset=utf-8
    content-length: 9593
    access-control-allow-origin: *
    access-control-allow-credentials: true
    x-envoy-upstream-service-time: 2

    The output indicates that the request is allowed because this path does not require authorization.

  2. Run the following command to access the /status/201 path without valid credentials.

    curl ${ASM_INGRESS_GATEWAY_IP}/status/201 -I -X GET

    Expected output:

    HTTP/1.1 403 Forbidden
    date: Tue, 25 Jul 2023 08:31:18 GMT
    server: istio-envoy
    content-length: 0
    x-envoy-upstream-service-time: 1

    The output indicates that the request is denied because it lacks valid credentials.

  3. Run the following command to access the /status/201 path with valid credentials.

    curl ${ASM_INGRESS_GATEWAY_IP}/status/201 -I -X GET --user alice:testpassword

    Expected output:

    HTTP/1.1 201 Created
    server: istio-envoy
    date: Tue, 25 Jul 2023 08:31:38 GMT
    content-type: text/html; charset=utf-8
    access-control-allow-origin: *
    access-control-allow-credentials: true
    content-length: 0
    x-envoy-upstream-service-time: 3

    The output indicates that the request is allowed because it includes valid credentials.

Step 4: Update the OPA policy

You can update the OPA policy at runtime by calling the OPA engine's HTTP API. This lets you change rules without redeploying the OPA service.

  1. Run the following command to update the policy to allow access only for the user bob.

    kubectl exec deployment/httpbin -c istio-proxy -- curl asm-opa:8181/v1/policies/policy/policy.rego -XPUT --data-binary 'package asm.authz
    
    import future.keywords
    import input.attributes.request.http as http_request
    import input.parsed_path
    
    default allow := false
    
    allow if {
      parsed_path[0] == "health"
    }
    
    allow if {
      http_request.method == "HEAD"
    }
    
    allow if {
      user_name == "bob"
    }
    
    user_name := parsed if {
      [_, encoded] := split(http_request.headers.authorization, " ")
      [parsed, _] := split(base64url.decode(encoded), ":")
    }'
  2. Run the following command to access httpbin as the user bob.

    curl ${ASM_INGRESS_GATEWAY_IP}/status/201 -I -X GET --user bob:testpassword

    Expected output:

    HTTP/1.1 201 Created
    server: istio-envoy
    date: Tue, 25 Jul 2023 08:32:16 GMT
    content-type: text/html; charset=utf-8
    access-control-allow-origin: *
    access-control-allow-credentials: true
    content-length: 0
    x-envoy-upstream-service-time: 3

    The output indicates that access is granted for the user bob.

  3. Run the following command to access httpbin as the user alice.

    curl ${ASM_INGRESS_GATEWAY_IP}/status/201 -I -X GET --user alice:testpassword  

    Expected output:

    HTTP/1.1 403 Forbidden
    date: Tue, 25 Jul 2023 08:32:49 GMT
    server: istio-envoy
    content-length: 0
    x-envoy-upstream-service-time: 1

    The output indicates that access is denied for the user alice.