Configuring Tyk Gateway with Authentik OAuth2 for Granular API Access Control in Kubernetes

Hi there!

I’ve successfully deploy Tyk Gateway, Tyk Operator and Tyk Pump through the OSS Helm chart. I’ve also managed to protect a few APIs using JWT (integrated with Authentik working as an OAuth2/OpenID Connect identity provider) by using this in the API definition (I’m using the CRDs, btw)

  jwt_signing_method: rsa
  jwt_source: "https://my.authentik.domain/application/o/my-provider/jwks/"
  jwt_identity_base_field: sub

But now I want to take it one step further and I can’t seem to find enough resources to make it, thus I ended up here and I hope you can help me.

For instance, my JWT payload looks like this:

{
    "iss": "https://my.authentik.domain/application/o/my-application/",
    "sub": "some_value",
    "aud": "some_value",
    "exp": 00000000,
    "iat": 00000000,
    "auth_time": 00000000,
    "acr": "goauthentik.io/providers/oauth2/default",
    "amr": [
        "pwd"
    ],
    "matricula": "0000000",
    "orgao": "Não Informado",
    "setor": "Não Informado",
    "cpf": "00000000000",
    "email": "[email protected]",
    "email_verified": true,
    "name": "My Name",
    "given_name": "My Name",
    "preferred_username": "my_nickname",
    "nickname": "",
    "groups": [
        "group1",
        "group2"
    ]
}

Now, I want to achieve more granular access control within Tyk. Specifically, I need to:

  • Grant Access Based on Groups: I want to restrict access to certain APIs and even specific endpoints based on the user’s group membership (groups claim in the JWT). For example, users in group1 should have access to certain APIs, while group2 members might have access to different ones.
  • Granular Endpoint Control: Within a single API, I need to allow access to only specific endpoints for certain groups. For instance, members of group1 might only access /api/v1/resource1, while group2 can access /api/v1/resource2.

I’m using the Tyk CRDs (ApiDefinition and SecurityPolicy) to define these configurations, but I’m struggling with how to properly set up the policies and API definitions to enforce these access controls.

Questions:

  1. How can I configure the SecurityPolicy CRD to enforce access controls based on the groups claim in the JWT?
  2. What’s the best way to structure the ApiDefinition CRDs to ensure that group-based access restrictions are applied at both the API and endpoint levels?
  3. Are there any specific examples or documentation that can guide me through this setup?

Any guidance or examples from those who have implemented similar setups would be incredibly helpful. Thanks in advance for your assistance!

@gabriel-milan Hello and welcome to the community :tada:

For security policies, you can use allowed_urls field to restrict API definition paths (path-based permissions). However, I don’t know if we have an example for path-based permissions via operator. There might also be some caveats there. I would have to ask internally for confirmation.

You can use scope to policy mapping to ensure access to the right API definition and endpoint levels. Depending on your use case, this could be simple or complex.

I don’t think we have one, especially for via operator but the scope to policy mapping is a good starter.

Depending on the complexity you could set up a default policy that doesn’t give access to any of the required APIs. This is because it’s not may not possible to create an empty policy without an access rights object ( I can’t remember vividly), so a dummy internal API can be set to act as the default. The default policy is triggered once a matching scope isn’t found

Also, I think scope checks for policies are single-level. So for very complex setups you may need to setup multiple APIs with the following structure

User → Tyk JWT access API (which reads scope claim, and only controls access rules) → Tyk JWT rate API (reads scope claim and enforces rates/limits).

The JWT properties would be

"jwt_policy_field_name": "pol",
"jwt_default_policies": [
  "<tyk-policy-id>"
],
// Scope Claims
// Scope Claims - Pre v5.0.0
"jwt_scope_claim_name": "<scope-name>",
"jwt_scope_to_policy_mapping": {
  "<scope-map>": "<tyk-policy-id>",
},
// Scope Claims - Post v5.0.0
"scopes": {
  "jwt": {
	"scope_claim_name": "<scope-name>",
	"scope_to_policy": {
	  "<scope-map>": "<tyk-policy-id>"
	}
  }
}

Hope this helps.

Hello there! Thank you for the “scope to policy” hint, that was an awesome starter! I’ll share pieces of the YAML for a proof of concept I’m building here. With that, I was able to achieve group-based access control for my API definitions, which is awesome! There are some things that I wanted to point out too, I’ll do this after the YAML.

---
# Token API
apiVersion: tyk.tyk.io/v1alpha1
kind: ApiDefinition
metadata:
  name: token-endpoint
  namespace: tyk
spec:
  id: 66db352b97034031ca0b146b
  name: token-endpoint
  use_keyless: true
  protocol: http
  active: true
  proxy:
    target_url: ...
    listen_path: /auth/token
    strip_listen_path: true
  global_rate_limit:
    rate: 1
    per: 1
  # This is for allowing POST requests only. I don't know if this is the best way to do it.
  version_data:
    default_version: Default
    not_versioned: true
    versions:
      Default:
        name: Default
        use_extended_paths: true
        extended_paths:
          white_list:
            - ignore_case: true
              method_actions:
                POST:
                  action: no_action
                  headers: {}
              path: /auth/token

---
# Default policy: blocks everything but things that any logged in user can access
apiVersion: tyk.tyk.io/v1alpha1
kind: SecurityPolicy
metadata:
  name: default-policy
  namespace: tyk
spec:
  id: 66db35398e5d7d828a472d07
  access_rights_array:
    - name: token-endpoint
      namespace: tyk
      versions:
        - Default
  active: true
  name: default-policy
  state: active

---
# httpbin policy: allows httpbin API only
apiVersion: tyk.tyk.io/v1alpha1
kind: SecurityPolicy
metadata:
  name: httpbin-policy
  namespace: tyk
spec:
  id: 66db354a796291f60300b1cd
  access_rights_array:
    - name: httpbin
      namespace: tyk
      versions:
        - Default
  active: true
  name: httpbin-policy
  state: active

---
# httpbin as an example API that's secured by group "tyk-httpbin"
apiVersion: tyk.tyk.io/v1alpha1
kind: ApiDefinition
metadata:
  name: httpbin
  namespace: tyk
spec:
  id: 66db356daa761f15a9d5dd13
  name: httpbin
  enable_jwt: true
  protocol: http
  active: true
  proxy:
    target_url: ...
    listen_path: /httpbin
    strip_listen_path: true

  strip_auth_data: true

  jwt_signing_method: rsa
  jwt_source: ...
  jwt_identity_base_field: sub
  jwt_policy_field_name: pol
  jwt_default_policies:
    - tyk/default-policy
  jwt_scope_claim_name: groups
  jwt_scope_to_policy_mapping:
    tyk-httpbin: 66db354a796291f60300b1cd

A few notes:

  • The /auth/token endpoint is just a simple endpoint that authenticates with Authentik and returns the JWT token that has the payload as I’ve mentioned before
  • I wanted to grant access to 2 APIs based on 2 Authentik groups that the user might belong to. That worked great!
  • I’ve forced IDs so it was easier for me to manage those things through YAML only

You’re right! I’ve tested it. Actually I was able to create it, but it just broke it.

Now for the questions:

  • Is this approach with version_data a recommended way of restricting the API for POST requests only? The whole idea on token-endpoint was to only allow POST requests to /auth/token and nothing else (no other method, no other endpoint)

  • I wasn’t able to do granular endpoint control. I guess it’s related to this:

I have tried adding the allowed_urls field in my YAMLs in some places and it complained that the key wasn’t recognized. Would you have any example at all that could use it? The versions of the images that are running in my cluster are docker.tyk.io/tyk-gateway/tyk-gateway:v5.5.0 and tykio/tyk-operator:v0.18.0, if that helps in anything. Also, were you able to check this out internally?

If the goal is to restrict any type of authenticated/non-authenticated request then using the version_data field is fine. The field is scoped to an API or an API definition version. So whatever is configured gets set regardless of the user/session

How did you add the allowed_urls field?

Here’s a sample. Let us know how it goes

apiVersion: tyk.tyk.io/v1alpha1
kind: SecurityPolicy            # SecurityPolicy CRD
metadata:
  name: httpbin                 # Unique k8s name
spec:
  name: Httpbin Security Policy # Generic Name
  state: active                 # View securitypolicy_types for more info
  active: true                  # View securitypolicy_types for more info
  access_rights_array:          # Adding APIs to the Policy. More info just below
    - name: test # Metadata name of API
      namespace: default
      versions:
        - Default               # Mandatory, Default is created automatically
      allowed_urls:
        - url: /get
          methods:
            - GET
  quota_max: 10
  quota_renewal_rate: 60
  rate: 5
  per: 5
  throttle_interval: 2
  throttle_retry_limit: 2
  tags:
    - Hello
    - World

I was clearly doing this wrong. Now that you’ve sent the sample, things just work as expected! Thank you very much!

I think that I just have one more question regarding this topic: is there such a thing like disallowed_urls for the access rights array or some way of achieving this? I’ve looked for it in code and also have tinkered a little bit with regex, something like using ^/(?!example(/|$)).*$ to allow all endpoints except /example/* but I haven’t been successful. Would you have any ideas on how to achieve this?

You are very welcome.Glad I could help.

No, there isn’t. This topic suggests what you could do. I see you are already on the right path

That solves it! Again, thank you very much