Skip to content

AccessPolicy

Ajit Verma edited this page Feb 11, 2022 · 2 revisions

Overview

Enable OPA

One time setup to bring up OPA
  1. Configure global settings for OPA sidecar. Can be over-written on a per-service basis.
    Following provides a sample configuration
# values.yaml with global settings
global:
  titanSideCars:
    imageRegistry: gcr.io/cloud-tools-images
    opa:
      enabled: false
      imageName: opa
      imageTag: v1.4.0

  1. Configure OPA policy for token interpretation if token with custom format is used

By default, titan's opa implementation expects Authorization header with Bearer token where all claims are normal json attributes. If this is not true, then a custom opa policy must be supplied that parses the token and exposes all usable claims under a token object accesible via token. prefix.

For example, in following token payload, the 'custom_claim' is an stringified json. If a policy check is required on the 'privs' or 'scope' attributes in the stringified json, then a custom policy must be declared exposing them as first class json attributes.

{
  "custom_claim": "{\"scope\":\"system\",\"privs\":\"read_users create_users\"}",
  "iss": "https://example.com",
  "exp": 1628805673,
  "iat": 1628719273,
  "jti": "lTe3-KPMQGyYaSXTBpLbgQ"
}

Following sample policy handles above payload

# values.yaml with global settings
global:
  titanSideCars:
    opa:
      customPolicies:
        tokenSpec: |
          package authz.token
          import input.attributes.request.http as request
          default authz_header = ""
          authz_header = trim_space(request.headers.authorization)
          token_raw = claims {
            startswith(authz_header, "Bearer ")
            [_, encoded] := regex.split("[ ]+", authz_header)
            [_, claims, _] := io.jwt.decode(encoded) 
          } else = claims {
            [_, claims, _] := io.jwt.decode(authz_header) 
          } else = claims {
            claims := {"custom_claim": "{}", "jti": "null", "iss": "null"}
          }
          token = {
            "custom_claim": json.unmarshal(token_raw.custom_claim),
            "iss": token_raw.iss,
            "jti": token_raw.jti
          }

  1. Enable opa side-car on a per-service basis for services that require OPA
# service's values.yaml
titanSideCars:
  opa:
    enabled: true

Apart from authorization checks, OPA sidecar is also leveraged to enable ratelimiting on attributes from payload and token and request enrichment.

Config Reference

Click to expand!

OPA

# titanSideCars.opa.

    enabled:                    bool
    imageRegistry:              string
    imageName:                  string
    imageTag:                   string
    cpu:
      request:                  string
      limit:                    string
    memory:
      request:                  string
      limit:                    string
    ephemeralStorage:
      request:                  string
      limit:                    string
    livenessFailureThreshold:   integer
    readinessFailureThreshold:  integer

    customPolicies:
      tokenSpec:                string

enabled

(bool, default true) Set to false to disable opa sidecar

imageRegistry

(string, optional) Docker image registry path used for OPA sidecar. Overrides titanSideCars.imageRegistry

imageName

(string, default opa)

imageTag

(string, default latest)

cpu.request

(string, default 250m)

cpu.limit

(string, default 1)

memory.request

(string, default 256Mi)

memory.limit

(string, default 1Gi)

ephemeralStorage.request

(string, default 100Mi)

ephemeralStorage.limit

(string, default 500Mi)

livenessFailureThreshold

(integer, default 50)

readinessFailureThreshold

(integer, default 100)

customPolicies.tokenSpec

(string, optional)


AccessPolicy

# titanSideCars.ingress.
  accessPolicy:
    defaultAction:  enum
  routes:
  - match:          RouteMatch
    accessPolicy:   PerRouteAccessPolicy

accessPolicy.defaultAction

(enum, default ALLOW) Valid values are ALLOW or DENY.

If defaultAction is set to ALLOW then request is allowed by default. The request is denied if it matches one or more access policies defined on a per route basis.
If defaultAction is set to DENY then request is denied by default. The request is allowed if it matches one or more access policies defined on a per route basis.
An http status code 403 (Forbidden) is returned on denial.

routes[].match

(RouteMatch, required)

routes[].accessPolicy

(PerRouteAccessPolicy, optional) If supplied, triggers access policy enforcement for matching requests.


PerRouteAccessPolicy

# titanSideCars.ingress.routes[].accessPolicy
    enabled:  bool
    name:     string
    oneOf:    []AccessRuleSet  

enabled

(bool, default true) Controls enforecement of supplied access policy

name

(string, optional) Policy name to enhance readability of generated policy. If not supplied, a unique policy identifier gets auto-generated

oneOf

([]AccessRuleSet, optional) A list of rulesets that collectively define the access policy for requests that match corresponding route definiton. The route definition is implicitly part of each ruleset. If unspecified, the policy is generated from route defintion alone.

A request a said to match the policy if it matches atleast one-of the rulesets


AccessRuleSet

# titanSideCars.ingress.routes[].accessPolicy.oneOf[]

    allOf: []AccessRule

allOf

([]AccessRule, required) A set of rules that collectively define an access ruleset.

A request a said to match the ruleset if it matches all-of the rules in the ruleset


AcessRule

# titanSideCars.ingress.routes[].accessPolicy.oneOf[].allOf[]

      key:    string              # left operand

      # operator + right operand
      eq:     string              # equals
      sw:     string              # starts-with
      ew:     string              # ends-with 
      co:     string              # contains
      lk:     string              # like
      pr:     bool                # present (unary)
      neq:    string              # not equals
      nsw:    string              # not starts-with
      new:    string              # not ends-with
      nco:    string              # not contains
      nlk:    string              # not like
      npr:    bool                # not present (unary)

An access rule is a simple expression of the form 'left-operand operator right-operand' and follows standard rules of expression evaluation. The expression is capable of comparing headers, claim from token, json payload attributes, and raw text values.

A header can be referenced via header. notaion. Example: header.x-request-id
A token claim can be referenced via token. notation. Example: token.sub.scope or token.jti
A payload attribute can be referenced via payload. notation. Example: payload.userType

If operand has none of the special prefixes, it is treated as raw text

key

(string, required) Left hand operand. The left operand can be a header, a token claim, or a json payload attribute

eq | sw | ew | co | lk | pr | neq | nsw | new | nco | nlk | npr

(oneof required oneof) Comparison operator. For binary operators, the operator value indicates the right hand operand.

The supported operators are

  • eq/neq: Exact string match
  • sw/nsw: String prefix match
  • ew/new: String suffix match
  • co/nco: Substring match
  • lk/nlk: Regex match. Regex syntax is documented here
  • pr/npr: Unary operator. Indicates if key is present or not. Only true value is used. Use pr to test presence and npr to test non presence.

The right hand operand can be a header, a token claim, a json payload attribute, or raw text


Examples

Example 1

titanSideCars:
  ingress:
    accessPolicy:
      defaultAction: DENY
    routes:
    - match:
        path: /objects
        method: POST
      accessPolicy:
        oneOf:
        - allOf:
          - key: token.privs
            co: create_object
          - key: token.scope
            co: system
        - allOf:
          - key: token.privs
            co: create_object
          - key: token.scope
            co: customer
          - key: header.x-customer-id
            eq: token.customer_id
        - allOf:
          - key: token.privs
            co: create_object
          - key: header.x-customer-id
            eq: token.customer_id
          - key: header.x-domain-id
            eq: token.domain_id

The POST /objects request will be allowed if it matches one of the supplied policies. All other requests will be blocked.

Clone this wiki locally