Ory Kratos -> Oathkeeper -> Trino/Starburst

1.1k Views Asked by At

I have deployed Ory Kratos, Ory Oathkeeper, Starburst and Apache Ranger. The goal is to allow users to create their accounts with Ory Kratos, then protect the Starburst UI with Ory Oathkeeper so that once the user is authenticated with Ory Kratos and hits the https://proxy.oathkeeper.mydomain.com/starburst/ we would send the user to the Starburst UI with a JWT in hand that can be verified against the jwks.json file that we have created with Ory Oathkeeper. Finally using the identity of the currently logged in user apply data access policies with Apache Ranger.

The Kratos UI is on https://kratos.mydomain.com

Kratos public is on https://public.kratos.mydomain.com

Kratos admin is on https://admin.kratos.mydomain.com

The jwks.json is on https://auth.mydomain.com/assets/well-known/jwks.json

The Ory Oathkeeper proxy is on https://proxy.oathkeeper.mydomain.com

The Ory Oathkeeper api is on https://api.oathkeeper.mydomain.com

Ory Kratos has the following configuration: (Terraform code to be yaml encoded to be used in the values file in a helm chart)

config = {
  version = "v0.10.1"
  dsn     = "postgres://USERNAME:PASSWORD@${local.postgres_host}/kratos?sslmode=disable"
  courier = {
    smtp = {
      connection_uri = "SOME_URI"
    }
  }
  cookies = {
    domain    = "mydomain.com"
    path      = "/"
    same_site = "Lax"
  }
  session = {
    lifespan = "2h"
    cookie = {
      name       = "ory_kratos_session"
      persistent = true
      path       = "/"
      same_site  = "Lax"
      domain     = "mydomain.com"
    }
  }
  selfservice = {
    default_browser_return_url = "https://${local.kratos_ui_dns}/"
    methods = {
      password = {
        enabled = true
      }
    }
    flows = {
      login = {
        ui_url   = "https://${local.kratos_ui_dns}/login"
        lifespan = "10m"
      }
      error = {
        ui_url = "https://${local.kratos_ui_dns}/error"
      }
      settings = {
        ui_url                     = "https://${local.kratos_ui_dns}/setting"
        privileged_session_max_age = "15m"
      }
      recovery = {
        ui_url  = "https://${local.kratos_ui_dns}/recovery"
        enabled = true
      }
      verification = {
        ui_url  = "https://${local.kratos_ui_dns}/verification"
        enabled = true
        after = {
          default_browser_return_url = "https://${local.kratos_ui_dns}/"
        }
      }
      logout = {
        after = {
          default_browser_return_url = "https://${local.kratos_ui_dns}/login"
        }
      }
      registration = {
        lifespan = "10m"
        ui_url   = "https://${local.kratos_ui_dns}/registration"
        after = {
          password = {
            hooks = [{ "hook" : "session" }]
          }
        }
      }
    }
  }
  log = {
    level  = "debug"
    format = "text"
  }
  secrets = {
    cookie = ["SOME-COOKIE-INSECURE-VALUE"]
    cipher = ["32-LONG-SECRET-NOT-SECURE-AT-ALL"]
  }
  ciphers = {
    algorithm = "noop"
  }
  hashers = {
    algorithm = "bcrypt"
    bcrypt = {
      cost = 4
    }
  }

  serve = {
    public = {
      base_url = "http://${local.kratos_public_dns}"
      host     = ""
      port     = 80
      request_log = {
        disable_for_health = true
      }
    }
    admin = {
      base_url = "https://kratos-admin.dev.svc.cluster.local"
      host     = ""
      port     = 4434
      request_log = {
        disable_for_health = true
      }
    }
  }
}

This results in a good csrf cookie and a ory_kratos_session cookie in the browser after logging in with Kratos. But no JWT token.

The Ory Oathkeeper configuration looks as follows: (Terraform code to be yaml encoded to be used in the values file in a helm chart)

config = {
  authenticators = {
    anonymous = {
      enabled = true
    }
    noop = {
      enabled = true
    }
    cookie_session = {
      enabled = true
      config = {
        check_session_url = "https://public.kratos.mydomain.com/sessions/whoami"
        preserve_path     = true
        extra_from : "@this"
        subject_from : "identity.id"
        only = [
          "ory_kratos_session"
        ]
      }
    }
    jwt = {
      enabled = true
      config = {
        jwks_urls = [
          "https://auth.mydomain.com/assets/well-known/jwks.json"
        ]
        scope_strategy = "none"
        target_audience = [
          "https://proxy.oathkeeper.mydomain.com/starburst/",
          "starburst-ui"
        ]
        trusted_issuers = [
          "https://proxy.oathkeeper.mydomain.com"
        ]
      }
    }
    oauth2_client_credentials = {
      enabled = false
      config = {
        token_url = "somesite/oath2/token"
      }
    }
  }
  authorizers = {
    allow = {
      enabled = true
    }
  }
  mutators = {
    noop = {
      enabled = true
    }
    id_token = {
      enabled = true
      config = {
        issuer_url = "https://proxy.oathkeeper.mydomain.com"
        jwks_url   = "https://auth.mydomain.com/assets/well-known/jwks.json"
        claims = jsonencode({
          aud = [
            "https://proxy.oathkeeper.mydomain.com/starburst",
            "starburst-ui"
          ],
          claims = {
            sub = "{{print .Subject}}"
          }
        })
      }
    }
  }

  serve = {
    proxy = {
      port = 4455
      cors = {
        enabled = false
      }
    }
    api = {
      port = 4456
      cors = {
        enabled = false
      }
    }
  }
}

The access rule looks as follows: (json)

[
  {
    "id": "starburst-rule.app",
    "upstream": {
      "url": "http://starburst.app:8080/",
      "preserve_host": true,
      "strip_path": "/starburst"
    },
    "match": {
      "url": "https://proxy.oathkeeper.mydomain.com/starburst/<.*>",
      "methods": [
        "GET",
        "POST",
        "PUT",
        "DELETE",
        "PATCH"
      ]
    },
    "authenticators": [
      {
        "handler": "jwt",
        "config": {
          "jwks_urls": [
            "https://auth.mydomain.com/assets/well-known/jwks.json"
          ],
          "scope_strategy": "none",
          "target_audience": [
            "https://proxy.oathkeeper.mydomain.com/starburst/",
            "starburst-ui"
          ]
        }
      }
    ],
    "authorizer": {
      "handler": "allow"
    },
    "mutators": [
      {
        "handler": "id_token"
      }
    ],
    "errors": {
      "fallback": [
        "json"
      ],
      "handlers": {
        "redirect": {
          "enabled": true,
          "config": {
            "to": "https://kratos.mydomain.com/login",
            "when": [
              {
                "error": [
                  "unauthorized",
                  "forbidden"
                ],
                "request": {
                  "header": {
                    "accept": [
                      "text/html"
                    ]
                  }
                }
              }
            ]
          }
        },
        "json": {
          "enabled": true,
          "config": {
            "verbose": true
          }
        }
      }
    }
  }
]

The Starburst/Trino configuration looks as follows:

config.properties: |
  coordinator=true
  node-scheduler.include-coordinator=false
  http-server.http.port=8080
  http-server.authentication.type=JWT
  http-server.authentication.jwt.key-file=https://auth.mydomain.com/assets/well-known/jwks.json
  http-server.authentication.jwt.required-issuer=https://proxy.oathkeeper.mydomain.com
  http-server.authentication.jwt.required-audience=https://proxy.oathkeeper.mydomain.com/starburst/

  discovery.uri=http://localhost:8080
  usage-metrics.gathering.initial-delay=1m
  usage-metrics.gathering.interval=10m
  usage-metrics.cluster-usage-resource.enabled=true

The ory_kratos_session cookie looks like this: (json)

{
  "id": "1df8ddb5-9628-4c05-97f5-f32b42119748",
  "active": true,
  "expires_at": "2022-11-23T15:42:25.323749Z",
  "authenticated_at": "2022-11-23T13:42:25.323749Z",
  "authenticator_assurance_level": "aal1",
  "authentication_methods": [
    {
      "method": "password",
      "aal": "aal1",
      "completed_at": "2022-11-23T13:42:25.323746742Z"
    }
  ],
  "issued_at": "2022-11-23T13:42:25.323749Z",
  "identity": {
    "id": "844bbe95-5e16-4e8d-abe4-0f19578e17a4",
    "schema_id": "default",
    "schema_url": "http://public.kratos.mydomain.com/schemas/ZGVmYXVsdA",
    "state": "active",
    "state_changed_at": "2022-11-23T11:33:55.700664Z",
    "traits": {
      "email": "[email protected]"
    },
    "metadata_public": null,
    "created_at": "2022-11-23T11:33:55.702463Z",
    "updated_at": "2022-11-23T11:33:55.702463Z"
  }
}

I do get a 404 response code when i try to access https://proxy.oathkeeper.mydomain.com/foobar as expected.

The problem is that i am getting a 401 when i try to access https://proxy.oathkeeper.mydomain.com/starburst/

With the following error message excerpt from the Ory Oathkeeper Pod:

time=2022-11-23T15:29:45Z level=warning msg=Access request denied audience=application error=map[debug: message:Access credentials are invalid reason: status:Unauthorized status_code:401] granted=false http_host=proxy.oathkeeper.mydomain.com http_method=GET http_url=https://proxy.oathkeeper.mydomain.com/starburst/ http_user_agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 service_name=ORY Oathkeeper service_version=v0.38.19-beta.1
time=2022-11-23T15:29:45Z level=error msg=An error occurred while handling a request code=401 debug= details=map[] error=The request could not be authorized reason= request-id= status=401 writer=JSON
time=2022-11-23T15:29:45Z level=info msg=completed handling request http_request=map[headers:map[accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 accept-encoding:gzip, deflate, br accept-language:en,sv;q=0.9 cache-control:no-cache cookie:Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 x-forwarded-for:10.7.223.164 x-forwarded-proto:https] host:proxy.oathkeeper.mydomain.com method:GET path:/starburst/ query:<nil> remote:172.29.44.94:8063 scheme:http] http_response=map[status:401 text_status:Unauthorized took:433.335µs]

And no trace of a Authorization: Bearer <JWT...> or related logs in Starburst.

I am not sure if i have missed to configure something of if the configuraion is wrong and was hoping someone could review this and point me in the right direction.

1

There are 1 best solutions below

0
On

Ok. I figured it out. Ory Kratos should be configured with Cookie Session, it doesn't provide JWT tokens. Ory Oathkeeper can translate the ory_kratos_session cookie to a jwt token, but it has to be configured for it.

Oathkeeper should have cookie authenticator enabled, noop authorizer (in my case) and id_token authenticator with an valid issuer url, jwks_url, ttl and the audience in the claims configured. As for the Oathkeeper rules, the cookie_session should be used, "allow" authorizer and the mutator of type id_token with the claims in its configuration, my mutator rule configuration contains the audience only, since the rest comes from the main configuration file.