diff --git a/docs/posts/2024/0015-ory-oathkeeper.md b/docs/posts/2024/0015-ory-oathkeeper.md index b6e1377..2829e91 100644 --- a/docs/posts/2024/0015-ory-oathkeeper.md +++ b/docs/posts/2024/0015-ory-oathkeeper.md @@ -15,6 +15,7 @@ categories: - Jaeger - OpenTelemetry links: + - ./posts/2024/0012-ory-kratos.md - Source Code: https://github.com/developer-friendly/ory/ image: assets/images/social/2024/06/10/ory-oathkeeper-identity-and-access-proxy-server.png --- @@ -41,7 +42,7 @@ whether deployed into the internet and exposed publicly, or gated behind private network and only accessible to a certain privileged users. That is what Ory Oathkeeper is good at, making sure that requests won't make -it to the upstream unless they are explicitly allowed. +it to the upstream server unless they are explicitly allowed. It enforces protective measures to ensure unauthorized requests are denied. It does that by sitting at the frontier of your infrastructure, receiving @@ -52,10 +53,11 @@ In this blog post, we will explore what Ory Oathkeeper can do & deploy and configure it in a way that will protect our upstream server. This use-case is very common and you have likely encountered it or implemented -a custom solution for you application. +a custom solution for you application before. -Stick around till the end to find out how to leverage the opensource solution -to your advantage so that you won't ever have to reinvent the wheel. +Hold your breath till the end to find out how to leverage this opensource +solution to your advantage so that you won't ever have to reinvent the wheel +again. ## Why Ory Oathkeeper? @@ -64,16 +66,16 @@ are some of the highlights you should be aware of: - [x] **Proxy Server**: One of the superpower of Oathkeeper is its ability to sit at the forefront of your infrastructure and denying unauthorized requests. - :rocket: + :shield: - [x] **Decision Maker**: Another mode of running Ory Oathkeeper is to use it as a policy enforcer, making decisions on whether or not a request should - be granted access based on the defined rules. :shield: + be granted access based on the defined rules. :face_with_monocle: - [x] **Open Source**: Ory Oathkeeper is open source with a permissive license, meaning you can inspect the source code, contribute to it, and even fork it if you want to. :flag_white: - [x] **Stateless**: Ory Oathkeeper is stateless, meaning it doesn't store any session data. This is a good thing because it makes it horizontally scalable - and easy to deploy in a distributed environment. :airplane: + and easy to deploy in a distributed environment. :eagle: - [x] **Pluggable**: Ory products are adhering to plugin architecture; you can use all of them, some of them, or only one of them. This allows a lot of flexibility when migrating from a current solution or integrating with a @@ -81,19 +83,23 @@ are some of the highlights you should be aware of: very appealing to me.** :electric_plug: - [x] **Full Featured**: It comes with batteries included, providing experimental support for gRPC middleware (if you're into Golang), and also - stable support for WebSockets. :battery: + stable support for WebSockets[^websocket-support]. :battery: - [x] **Community**: Ory has a great community of developers and users. If you ever get stuck, you can always ask for help in the community Slack channel[^ory-slack]. :handshake: -In short and more accurately put, Ory Oathkeeper is a Identity and Access -Proxy (IAP for short)[^oathkeeper-intro]. You will see later in this post how that comes into -play. +In short and more accurately put, Ory Oathkeeper is an Identity and Access +Proxy (IAP for short)[^oathkeeper-intro]. You will see later in this post how +that comes into play. !!! note "Disclaimer" - This blog post is not sponsored by Ory. I'm just a happy user of their + This blog post is not sponsored by Ory(1). I'm just a happy user of their products and I want to share my experience with you. + { .annotate } + + 1. Though, I definitely wouldn't mind seeing some dollars. + :money_mouth_face: ## How Does Ory Oathkeeper Work? @@ -128,31 +134,34 @@ as you see below: [Kubernetes], heavingly relying on the operator pattern and the CRDs used to deploy our infrastructure as well as the Oathkeeper rules. - [x] **cert-manager**: We will need internet-accessible host to our cluster - with TLS certificate from a trusted CA. That is where [cert-manager] is - lending a generous hand. :handshake: Take a look at + with TLS certificate from a trusted CA. That is where [cert-manager] and + [Gateway API] are lending a generous hand. :handshake: Take a look at [cert-manager: All-in-One Kubernetes TLS Certificate Manager] if you don't have it set up yet. - [x] **External Secrets**: For fetching the TLS certificates, we need access to our DNS provider (Cloudflare in this case). That's where we need the [ESO] to provide the API token for the cert-manager. Our earlier - guide is a perfect place to set it up, if you haven't already: + guide is a perfect place to set it up if you haven't already: [External Secrets Operator: Fetching AWS SSM Parameters into Azure AKS]. - [x] **Ory Kratos**: The main and most important part of this guide is the integration of Oathkeeper with Kratos. We will use Kratos to authenticate - the users and Oathkeeper to authorize them. Ultimately the requests - reaching the upstream server will be authenticated by two of [Ory] - products. If you need help setting up Kratos, refer to + the users. Ultimately the requests reaching the upstream server will be + authenticated and granted access by two of [Ory]'s products. If you need + help setting up Kratos, refer to [Ory Kratos: Headless Authentication, Identity and User Management] - [ ] **FluxCD**: This is our technology of choice when it comes to Kubernetes deployments. You are free to pick simpler tools such as Helm CLI. FluxCD is a great tool that requires a bit of learning. Check out our guide on - [GitOps Demystified: Introduction to FluxCD for Kubernetes] if haven't - already. + [GitOps Demystified: Introduction to FluxCD for Kubernetes] if you need + a starting point or + [GitOps Continuous Deployment: FluxCD Advanced CRDs] if you are an + advanced user. - [ ] **Jaeger**: For the observation of traces between Oathkeeper and Kratos we get to configure both to send their traces to a custom endpoint. That's where [Jaeger] comes into play. -- [ ] **Azure account**: Optionally, a Azure VM having system assigned identity - attached to it. We have covered this specific case two weeks ago in +- [ ] **Azure account**: Optionally, an [Azure] VM having system assigned + identity attached to it. We have covered this specific case two weeks + ago in [How to Access AWS From Azure VM Using OpenID Connect]. We use this in the last part of this guide to send authenticated requests to Oathkeeper from an Azure VM. @@ -160,16 +169,16 @@ as you see below: ## Deploying Ory Oathkeeper Being an stateless application by nature, Oathkeeper is a perfect fit for -Kubernetes Deployment. This allows us to horizontally scale it on demand. +[Kubernetes] Deployment. This allows us to horizontally scale it on demand. -Let's write the server configuration and then deploy it in our Kubernetes +Let's write the server configuration and then deploy it to our Kubernetes cluster using [FluxCD]. ### Oathkeeper Server Configuration -As other Ory products, Oathkeeper relies heavily on its configuration file. -This is, as usual, written in YAML format, making it easy to read and maintain -as the complexity grows. +As with every other of [Ory] products, Oathkeeper relies heavily on its +configuration file. This is, as usual, written in YAML format, making it easy +to read and maintain as the complexity grows. The following configuration is fetched in its entirety from the configuration reference[^oathkeeper-configuration] and customized to fit our need. @@ -182,6 +191,59 @@ complete reference can be found in the official documentation[^oathkeeper-config -8<- "docs/codes/2024/0015/oathkeeper/oathkeeper-server-config.yml" ``` +There are a lot to unpack. We may not be able to cover all, but let's explain +some of the highlights. + +#### Allowed Authentication Methods + +Oathkeeper accepts in its configuration, the methods allowed for authentication +and authorization. If you wish to use OAuth2 authentication, before using it +in a Oathkeeper rule, you have to enable it in the configuration. + +```yaml title="oathkeeper/oathkeeper-server-config.yml" linenums="5" +-8<- "docs/codes/2024/0015/oathkeeper/oathkeeper-server-config.yml:5:18" +``` + +You can customize each method further with specific values. However, we will +leave the customization to the Oathkeeper rule later in this blog. The URLs, +however, are a required field and must be specified at the configuration level. + +#### Tracing Endoints + +In all of the [Ory] products, you can specify where to ship your traces to. + +That is possible through the same configuration over all the (currently) four +products as below: + +```yaml title="oathkeeper/oathkeeper-server-config.yml" linenums="50" +-8<- "docs/codes/2024/0015/oathkeeper/oathkeeper-server-config.yml:50:58" +``` + +#### CORS Configuration + +If you're access the Oathkeeper from the browser, you have to set the allowed +origin addresses in the configurations. + +Those "allowed" URLs are the hostnames that is in the address bar of a browser. +If you specify a wildcard, Oathkeeper will intelligently allow the concrete +value coming from the `Origin` header of the request. + +For example, if `*.developer-friendly.blog` is in the allowed origins and the +browser sends the request with `Origin: example.developer-friendly.blog`, the +Oathkeeper will respond with +`Access-Control-Allow-Origin: example.developer-friendly.blog`. Same goes with +other subdomains. + +The `allow_credentials: true` is perhaps the second most important part of this +configuration. Without it your browser will not forward the cookies to +the Oathkeeper server and you will always get a `401 Unauthorized` response. + +That part that makes it possible is in these configuration lines: + +```yaml title="oathkeeper/oathkeeper-server-config.yml" linenums="59" +-8<- "docs/codes/2024/0015/oathkeeper/oathkeeper-server-config.yml:59:66" +``` + ### Kubernetes Deployment Resources There are different ways to deploy Oathkeeper[^oathkeeper-installation] and it @@ -203,9 +265,13 @@ new to them. deployment tools such as Helm. However, the upstream Helm chart seems to be quite inflexible and due to - the lack of customization allowed, we had to resort to Kustomization. + the lack of customizations allowed, we had to resort to Kustomization. -As of writing this blog post, Ory Oathkeeper has native Kubernetes support for + Opting for the Helm installation would require us to do a lot of + post-render Kustomization, both ugly and unmaintainable. + +As of writing this blog post, with the help of Oathkeeper +Maester[^maester-repo], Ory Oathkeeper has native Kubernetes support for *rules*, i.e., you can create Kubernetes resources to have Oathkeeper rules. :muscle: @@ -235,7 +301,7 @@ The following two Deployment resources are the core of our Kustomization stack. ``` We won't need to expose Oathkeeper Maester, but we require the Oathkeeper to -be accessible to the cluster. +be accessible to the cluster. Hence the Services below. ```yaml title="oathkeeper/service-oathkeeper-api.yml" -8<- "docs/codes/2024/0015/oathkeeper/service-oathkeeper-api.yml" @@ -283,12 +349,14 @@ Of course there is. There is _almost_ always a way to less complexity, especially those complexities that are accidental! -The following Helm command does all the things we worked so hard to achieve +The following Helm commands do all the things we worked so hard to achieve in the previous sections[^helm-installation]. ```bash title="" linenums="0" helm repo add ory https://k8s.ory.sh/helm/charts -helm install oathkeeper ory/oathkeeper --version 0.43.x +helm install oathkeeper ory/oathkeeper \ + --set oathkeeper.managedAccessRules=false \ + --version 0.43.x ``` Pick whichever you like. The decision between extensive customization and @@ -355,7 +423,7 @@ make this come to life: 2. The Kubernetes HTTPRoute resource that will accept the incoming traffics. We will skip the first part as that is something we have covered multiple times -in this blog. +in this blog and it is tailored to your DNS provider. The second part is as you see below. @@ -363,6 +431,13 @@ Notice that this HTTPRoute has to be in the same namespace as the Oathkeeper. The reason is that the Gateway will only route the traffics to the same namespace as the HTTPRoute[^httproute-doc]. +In short, we send the internet traffics to the Oathkeeper, and if all looks OK, +it will forward the request to the upstream server. + +Otherwise, the user will get the proper error message from Oathkeeper before +even a single byte reaches the upstream server. That is the true power of +Oathkeeper. + ```yaml title="echo-server-rule/httproute.yml" hl_lines="7 15-18" -8<- "docs/codes/2024/0015/echo-server-rule/httproute.yml" ``` @@ -400,15 +475,15 @@ header (`guest` for anonymous). 4. What if error happens before reaching upstream? Return the error as JSON. The flow you see above is the most important part of how Ory Oathkeeper works. -If you master this flow, you can create any kind of rules you want. +If you master this flow, you can create any kind of rule you want. Notice that in our Rule definition, we are specifying the `upstream_url` as the `.`. This is due to the fact that the Oathkeeper stack and our echo-server are in separate namespaces. Also note that the `match.url` is using regex format. This is only possible -if you have `access_rules.matching_strategy: regexp` in your Oathkeeper server -configuration. +if you have `access_rules.matching_strategy: regexp` in your +[Oathkeeper server configuration]. Let's apply this stack: @@ -416,13 +491,26 @@ Let's apply this stack: -8<- "docs/codes/2024/0015/echo-server-rule/kustomization.yml" ``` +Let's send an anonymous request to verify it worked. + +```bash title="" linenums="0" +curl https://echo.developer-friendly.blog +``` + +The response is as below. + +```json title="" hl_lines="33" +-8<- "docs/codes/2024/0015/junk/anon-response.json" +``` + + ### Play 2: Authenticated by Ory Kratos At this stage, we should be able to use our [previously deployed Ory Kratos server]. Let's modify this rule so that the authenticated users and the identities of -Kratos can send their request to this upstream server. +Kratos can send their request to this upstream server[^kratos-whoami]. ```yaml title="echo-server-rule/rule.yml" hl_lines="7-14" -8<- "docs/codes/2024/0015/junk/kratos-rule.yml" @@ -431,7 +519,7 @@ Kratos can send their request to this upstream server. If we authenticate to Kratos first and send an HTTP request to the echo-server, this is what we get. -```json title="" +```json title="" hl_lines="42" -8<- "docs/codes/2024/0015/junk/kratos-response.json" ``` @@ -445,9 +533,9 @@ information from the Kratos itself. ### Play 3: Azure VM Access -The idea in this scenario is that the virtual machine in the Azure cloud with +The idea in this scenario is that the virtual machine in the [Azure] cloud with system assigned identity can send authenticated requests to the echo-server, -while Oathkeeper verifies the authenticity of the request using the Azure AD +while Oathkeeper verifying the authenticity of the request using the Azure AD JWKs endpoint. ```yaml title="echo-server-rule/rule.yml" hl_lines="17-22" @@ -467,7 +555,7 @@ curl https://echo.developer-friendly.blog -H "Authorization $token" The response will be as you see below. The `x-user-id` is the subject or the identity ID that the Identity Provider (Azure AD) knows the VM by. -```json title="" +```json title="" hl_lines="31" -8<- "docs/codes/2024/0015/junk/azure-vm-response.json" ``` @@ -487,17 +575,33 @@ will be denied with a `401 Unauthorized` status code. -8<- "docs/codes/2024/0015/junk/unauthorized-response.json" ``` -That will help you sleep tight at night, knowing that your application is -safely gaurded by a production grade and robust proxy server, consulting the -proper authentication server before handing it to the upstream backend. +That is to say, the order in which Oathkeeper processes the rules is important. +It will start from the top, and any authenticator that **can** handle the +authentication process will be used and the rest are ignored, even if the +matched authentication denies the request! + +From the official documentation[^api-access-rules-order]: -I will use the Oathkeeper even for my admin pages; even the ones not publicly -accessible and only exposed to the private network. This helps secure the -backend from unauthorized access. +> `authenticators`: A list of authentication handlers that authenticate the + provided credentials. Authenticators are checked iteratively from index `0` + to `n` and the first authenticator to return a positive result will be the + one used. If you want the rule to first check a specific authenticator before + "falling back" to others, have that authenticator as the first item in the + array. + +All in all, these will help you sleep tight at night, knowing that your +application is safely guarded by a production grade and robust proxy server, +consulting the proper authentication server before handing it to the upstream +backend. + +These days, I use the Oathkeeper even for my admin pages; even the ones not +publicly accessible and only exposed to the private network. This helps secure +the backend from unauthorized access. There are other types of examples we can provide here, but with the ones you see here, you should have a good idea on what's possible and what you can do -more with Ory Oathkeeper. +more with Ory Oathkeeper. Even so, we will have more examples of this topic +in the future for other practical and production use-cases. ## Conclusion @@ -529,7 +633,7 @@ Before investing many of your engineering efforts building something from scratch, I highly recommend trying Ory's products in the tenth of that time. :clock: -Ory is only one of the many solutions out there, but at the very least, you +[Ory] is only one of the many solutions out there, but at the very least, you should have a basic understanding of what is already available to you around the industry before going all in on a custom solution. @@ -537,7 +641,6 @@ Make your decisions wisely, do the right things before doing things right. Happy hacking and until next time :saluting_face:, _ciao_. :penguin: :crab: - [Kubernetes]: /category/kubernetes/ [FluxCD]: /category/fluxcd/ [cert-manager]: /category/cert-manager/ @@ -553,18 +656,25 @@ Happy hacking and until next time :saluting_face:, _ciao_. :penguin: :crab: [GitOps Demystified: Introduction to FluxCD for Kubernetes]: ./0006-gettings-started-with-gitops-and-fluxcd.md [our previous _echo server_ example]: ./0010-cert-manager.md#step-4-https-application [previously deployed Ory Kratos server]: ./0012-ory-kratos.md#kratos-deployment +[Gateway API]: /category/gateway-api/ +[GitOps Continuous Deployment: FluxCD Advanced CRDs]: ./0011-fluxcd-advanced-topics.md +[Oathkeeper server configuration]: #oathkeeper-server-configuration +[^websocket-support]: https://www.ory.sh/docs/oathkeeper/guides/proxy-websockets [^ory-slack]: https://slack.ory.sh/ [^oathkeeper-intro]: https://www.ory.sh/docs/oathkeeper/ [^decision-api]: https://github.com/ory/oathkeeper/blob/6d628fbcc6de9428491add8ab3862e9ed2ba5936/api/decision.go#L56:L121 [^api-access-rules]: https://www.ory.sh/docs/oathkeeper/api-access-rules +[^oathkeeper-configuration]: https://www.ory.sh/docs/oathkeeper/reference/configuration [^oathkeeper-installation]: https://www.ory.sh/docs/oathkeeper/install [^oathkeeper-repository]: https://github.com/ory/oathkeeper/tree/v0.40.7 [^ory-examples]: https://github.com/ory/examples/tree/a085b65d21d6d31c1cb728a6b8b28f281f074066 -[^oathkeeper-configuration]: https://www.ory.sh/docs/oathkeeper/reference/configuration +[^maester-repo]: https://github.com/ory/oathkeeper-maester/tree/v0.1.10 [^vscode-extension]: https://github.com/weaveworks/vscode-gitops-tools/tree/0.27.0 [^helm-installation]: https://artifacthub.io/packages/helm/ory/oathkeeper/0.43.1 [^echo-server]: https://github.com/Ealenn/Echo-Server/tree/0.9.2 [^httproute-doc]: https://gateway-api.sigs.k8s.io/api-types/httproute/ [^oathkeeper-proxy-flow]: https://www.ory.sh/docs/oathkeeper/#reverse-proxy +[^kratos-whoami]: https://www.ory.sh/docs/kratos/reference/api#tag/frontend/operation/listMySessions [^az-vm-token]: https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-curl +[^api-access-rules-order]: https://www.ory.sh/docs/oathkeeper/api-access-rules#access-rule-format