<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>HTTP Callback on Digi Hunch</title><link>https://www.digihunch.com/tag/http-callback/</link><description>Recent content in HTTP Callback on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 02 Apr 2025 13:02:31 -0400</lastBuildDate><atom:link href="https://www.digihunch.com/tag/http-callback/index.xml" rel="self" type="application/rss+xml"/><item><title>Test Open ID Connect Flows Locally</title><link>https://www.digihunch.com/2024/08/test-open-id-connect-flows-locally/</link><pubDate>Mon, 05 Aug 2024 13:24:34 -0400</pubDate><guid>https://www.digihunch.com/2024/08/test-open-id-connect-flows-locally/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-local-oidc-test.webp" alt="Featured image of post Test Open ID Connect Flows Locally" /&gt;&lt;p class="wp-block-paragraph"&gt;Earlier this year, I had to integrate an application with an identity provider. Both claim to be compliant with &lt;a href="https://www.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/"&gt;Open ID Connect&lt;/a&gt;. But when they don&amp;#8217;t get along, I must find out where it breaks to determine which party isn&amp;#8217;t compliant. Therefore, I had to really get to the transaction-level details. As a result, I was eager to find out a way to test Open ID Connect Flows locally. Considering that I discussed OIDC in several past &lt;a href="https://www.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/"&gt;articles&lt;/a&gt; and use cases (&lt;a href="https://www.digihunch.com/2023/07/authenticate-kube-apiserver-via-oidc/"&gt;authenticating to kube-api server&lt;/a&gt;, Kubernetes &lt;a href="https://www.digihunch.com/2023/12/workload-identity-on-kubernetes-1-of-2-aks/"&gt;workload&lt;/a&gt;, &lt;a href="https://www.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/"&gt;ROSA&lt;/a&gt;), I feel it is important to be able to test Open ID Connect flows locally.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Architecture&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;What makes local testing difficult is that there are several parties involved and they act in different roles. Take &lt;a href="https://www.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/"&gt;Authorization Code Flow&lt;/a&gt; as an example, the main actors are:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;User Agent: the browser;&lt;/li&gt;&#10;&lt;li&gt;Authorization Server: an identity store, also referred to as identity provider;&lt;/li&gt;&#10;&lt;li&gt;Resource server: an HTTP server that returns protected resource;&lt;/li&gt;&#10;&lt;li&gt;OIDC client application: the component that communicates with identity store on behalf of the resource server;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from the bowser (obviously running on my laptop), we can group the the other actors in different patterns. Here I illustrate some options below:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="751px" viewBox="-0.5 -0.5 751 731" style="max-width:100%;max-height:731px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;g data-cell-id="0"&gt;&lt;g data-cell-id="1"&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-68"&gt;&lt;g&gt;&lt;rect x="0" y="0" width="750" height="730" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-62"&gt;&lt;g&gt;&lt;rect x="370" y="580" width="370" height="110" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 368px; height: 1px; padding-top: 587px; margin-left: 370px;"&gt;&lt;div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: right;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Server Pattern&lt;div&gt;Example #4&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="738" y="599" fill="#333333" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="end"&gt;Server Pattern&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-61"&gt;&lt;g&gt;&lt;rect x="370" y="232.5" width="260" height="80" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 258px; height: 1px; padding-top: 239px; margin-left: 370px;"&gt;&lt;div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: right;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Server Pattern&lt;div&gt;Example #2&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="628" y="251" fill="#333333" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="end"&gt;Server Pattern&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-58"&gt;&lt;g&gt;&lt;rect x="370" y="80" width="260" height="80" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 258px; height: 1px; padding-top: 87px; margin-left: 370px;"&gt;&lt;div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: right;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Server Pattern&lt;div&gt;Example #1&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="628" y="99" fill="#333333" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="end"&gt;Server Pattern&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-48"&gt;&lt;g&gt;&lt;rect x="10" y="490" width="220" height="225" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 218px; height: 1px; padding-top: 497px; margin-left: 12px;"&gt;&lt;div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: left;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Client&amp;nbsp;&lt;span style="background-color: initial;"&gt;Pattern Ex.#3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="12" y="509" fill="#333333" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px"&gt;Client&amp;nbsp;Pattern Ex.#3&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-47"&gt;&lt;g&gt;&lt;rect x="10" y="250" width="220" height="220" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 218px; height: 1px; padding-top: 257px; margin-left: 12px;"&gt;&lt;div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: left;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Client Pattern&lt;div&gt;Example #2&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="12" y="269" fill="#333333" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px"&gt;Client Pattern&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-46"&gt;&lt;g&gt;&lt;rect x="10" y="60" width="220" height="180" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 218px; height: 1px; padding-top: 67px; margin-left: 12px;"&gt;&lt;div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: left;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Client Pattern&lt;div&gt;Example #1&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="12" y="79" fill="#333333" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px"&gt;Client Pattern&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-23"&gt;&lt;g&gt;&lt;rect x="25" y="520" width="200" height="90" rx="13.5" ry="13.5" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 527px; margin-left: 26px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Advanced Reverse Proxy&lt;div&gt;(e.g. Nginx Plus, Traefik Enterprise)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="125" y="539" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;Advanced Reverse Proxy&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-3"&gt;&lt;g&gt;&lt;rect x="40" y="150" width="180" height="80" rx="12" ry="12" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 178px; height: 1px; padding-top: 157px; margin-left: 41px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;&lt;div&gt;&lt;span style="background-color: initial;"&gt;App (with built-in&amp;nbsp;&lt;/span&gt;&lt;span style="background-color: initial;"&gt;OIDC support)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="130" y="169" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;App (with built-in&amp;nbsp;OIDC suppor&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-4"&gt;&lt;g&gt;&lt;rect x="600" y="620" width="130" height="60" rx="9" ry="9" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 650px; margin-left: 601px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Identity Provider with&lt;div&gt;OIDC support&amp;nbsp;&lt;/div&gt;&lt;div&gt;(e.g. Active Directory)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="665" y="654" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;Identity Provider wit&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-5"&gt;&lt;g&gt;&lt;path d="M 240 720 L 240 20" fill="none" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-6"&gt;&lt;g&gt;&lt;rect x="35" y="10" width="160" height="30" fill="none" stroke="none" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 25px; margin-left: 36px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;"&gt;OIDC Client App Patterns&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="115" y="29" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle" font-weight="bold"&gt;OIDC Client App Patterns&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-7"&gt;&lt;g&gt;&lt;rect x="390" y="10" width="220" height="30" fill="none" stroke="none" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 218px; height: 1px; padding-top: 25px; margin-left: 391px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;"&gt;OIDC Authorization Server Patterns&lt;div&gt;(including Identity Provider)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="500" y="29" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle" font-weight="bold"&gt;OIDC Authorization Server Patterns&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-8"&gt;&lt;g&gt;&lt;path d="M 360 720 L 360 20" fill="none" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-9"&gt;&lt;g&gt;&lt;ellipse cx="85" cy="200" rx="35" ry="25" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 200px; margin-left: 51px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;HTTP&lt;div&gt;Resource&lt;/div&gt;&lt;div&gt;&lt;span style="background-color: initial;"&gt;Server&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="204" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;HTTP&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-15"&gt;&lt;g&gt;&lt;ellipse cx="135" cy="345" rx="50" ry="15" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 345px; margin-left: 86px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;oauth2-proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="135" y="349" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;oauth2-proxy&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-16"&gt;&lt;g&gt;&lt;rect x="380" y="90" width="160" height="60" rx="9" ry="9" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 120px; margin-left: 381px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Self-hosted OIDC-capable&lt;div&gt;Identity Providers&lt;/div&gt;&lt;div&gt;(e.g. KeyCloak as IdP)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="460" y="124" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;Self-hosted OIDC-capable&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-17"&gt;&lt;g&gt;&lt;rect x="50" y="380" width="170" height="80" rx="12" ry="12" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 387px; margin-left: 51px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;&lt;div&gt;&lt;span style="background-color: initial;"&gt;App (without&amp;nbsp;&lt;/span&gt;&lt;span style="background-color: initial;"&gt;OIDC capability)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="135" y="399" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;App (without&amp;nbsp;OIDC capability)&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-18"&gt;&lt;g&gt;&lt;ellipse cx="175" cy="200" rx="35" ry="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 200px; margin-left: 141px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;OIDC&lt;div&gt;library&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="175" y="204" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;OIDC&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-20"&gt;&lt;g&gt;&lt;ellipse cx="82.5" cy="580" rx="47.5" ry="20" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 93px; height: 1px; padding-top: 580px; margin-left: 36px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;reverse&amp;nbsp;&lt;span style="background-color: initial;"&gt;proxy&lt;/span&gt;&lt;div&gt;capability&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="83" y="584" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;reverse&amp;nbsp;proxy&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-24"&gt;&lt;g&gt;&lt;ellipse cx="175" cy="580" rx="40" ry="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 78px; height: 1px; padding-top: 580px; margin-left: 136px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;oidc client&lt;div&gt;capability&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="175" y="584" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;oidc client&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-27"&gt;&lt;g&gt;&lt;rect x="380" y="242.5" width="160" height="60" rx="9" ry="9" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 272px; margin-left: 381px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;&lt;div&gt;Commercial OIDC-capable&lt;/div&gt;Identity Providers&lt;div&gt;(e.g. MS Entra-ID, Google)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="460" y="276" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;Commercial OIDC-capableIde&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-35"&gt;&lt;g&gt;&lt;rect x="85" y="90" width="90" height="45" rx="6.75" ry="6.75" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 113px; margin-left: 86px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;(optional)&lt;div&gt;reverse proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="130" y="116" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;(optional)&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-36"&gt;&lt;g&gt;&lt;ellipse cx="135" cy="425" rx="35" ry="25" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 425px; margin-left: 101px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;HTTP&lt;div&gt;Resource&lt;/div&gt;&lt;div&gt;&lt;span style="background-color: initial;"&gt;Server&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="135" y="429" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;HTTP&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-37"&gt;&lt;g&gt;&lt;rect x="90" y="275" width="90" height="37.5" rx="5.63" ry="5.63" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 294px; margin-left: 91px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;(optional)&lt;div&gt;reverse proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="135" y="297" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;(optional)&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-38"&gt;&lt;g&gt;&lt;rect x="25" y="630" width="200" height="80" rx="12" ry="12" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 637px; margin-left: 26px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;&lt;div&gt;&lt;span style="background-color: initial;"&gt;App (without&amp;nbsp;&lt;/span&gt;&lt;span style="background-color: initial;"&gt;OIDC capability)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="125" y="649" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;App (without&amp;nbsp;OIDC capability)&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-39"&gt;&lt;g&gt;&lt;ellipse cx="125" cy="680" rx="35" ry="25" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 680px; margin-left: 91px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;HTTP&lt;div&gt;Resource&lt;/div&gt;&lt;div&gt;&lt;span style="background-color: initial;"&gt;Server&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="125" y="684" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;HTTP&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-40"&gt;&lt;g&gt;&lt;path d="M 135 312.5 L 135 323.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 135 328.88 L 131.5 321.88 L 135 323.63 L 138.5 321.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-41"&gt;&lt;g&gt;&lt;path d="M 135 373.63 L 135 360" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 135 378.88 L 131.5 371.88 L 135 373.63 L 138.5 371.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-42"&gt;&lt;g&gt;&lt;path d="M 130 143.63 L 130 135" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 130 148.88 L 126.5 141.88 L 130 143.63 L 133.5 141.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-50"&gt;&lt;g&gt;&lt;path d="M 125 623.63 L 125 610" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 125 628.88 L 121.5 621.88 L 125 623.63 L 128.5 621.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-52"&gt;&lt;g&gt;&lt;path d="M 130 70 L 130 83.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 130 88.88 L 126.5 81.88 L 130 83.63 L 133.5 81.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-54"&gt;&lt;g&gt;&lt;path d="M 124.73 500 L 124.73 513.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 124.73 518.88 L 121.23 511.88 L 124.73 513.63 L 128.23 511.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-55"&gt;&lt;g&gt;&lt;path d="M 135 255 L 135 268.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 135 273.88 L 131.5 266.88 L 135 268.63 L 138.5 266.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-57"&gt;&lt;g&gt;&lt;rect x="380" y="620" width="120" height="60" rx="9" ry="9" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 650px; margin-left: 381px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;&lt;div&gt;Any OIDC-capable&lt;/div&gt;Identity Broker&amp;nbsp;&lt;div&gt;&lt;span style="background-color: initial;"&gt;e.g. Dex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="440" y="654" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;Any OIDC-capableIden&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-59"&gt;&lt;g&gt;&lt;path d="M 519.45 654.19 L 519.34 664.69 L 500.5 649 L 519.65 633.7 L 519.55 644.2 L 580.55 644.81 L 580.66 634.31 L 599.5 650 L 580.35 665.3 L 580.45 654.8 Z" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-60"&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 621px; margin-left: 551px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"&gt;Other Protocol&lt;div&gt;(e.g. LDAP, SAML)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="551" y="624" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="11px" text-anchor="middle"&gt;Other Protocol&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-34"&gt;&lt;g&gt;&lt;path d="M 275 380 C 255 380 250 400 266 404 C 250 412.8 268 432 281 424 C 290 440 320 440 330 424 C 350 424 350 408 337.5 400 C 350 384 330 368 312.5 376 C 300 364 280 364 275 380 Z" fill="#d5e8d4" stroke="#82b366" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 400px; margin-left: 251px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Standard&lt;div&gt;OIDC Flow&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="300" y="404" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;Standard&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-63"&gt;&lt;g&gt;&lt;rect x="370" y="390" width="370" height="110" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 368px; height: 1px; padding-top: 397px; margin-left: 370px;"&gt;&lt;div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: right;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Server Pattern&lt;div&gt;Example #3&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="738" y="409" fill="#333333" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="end"&gt;Server Pattern&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-64"&gt;&lt;g&gt;&lt;rect x="600" y="430" width="130" height="60" rx="9" ry="9" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 460px; margin-left: 601px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Identity Provider with&lt;div&gt;OIDC support&amp;nbsp;&lt;/div&gt;&lt;div&gt;(e.g. Active Directory)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="665" y="464" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;Identity Provider wit&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-65"&gt;&lt;g&gt;&lt;rect x="380" y="430" width="120" height="60" rx="9" ry="9" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/rect&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 460px; margin-left: 381px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;&lt;div&gt;KeyCloak as&lt;/div&gt;&lt;div&gt;Identity Broker&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="440" y="464" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="12px" text-anchor="middle"&gt;KeyCloak as&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-66"&gt;&lt;g&gt;&lt;path d="M 519.45 464.19 L 519.34 474.69 L 500.5 459 L 519.65 443.7 L 519.55 454.2 L 580.55 454.81 L 580.66 444.31 L 599.5 460 L 580.35 475.3 L 580.45 464.8 Z" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g data-cell-id="MkZjjGYqzHr3rgLKbynL-67"&gt;&lt;g&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 426px; margin-left: 551px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"&gt;Other Protocol&lt;div&gt;(e.g. LDAP, SAML)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="551" y="429" fill="rgb(0, 0, 0)" font-family="&amp;quot;Helvetica&amp;quot;" font-size="11px" text-anchor="middle"&gt;Other Protocol&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/g&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.drawio.com/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Text is not SVG &amp;#8211; cannot display&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In essence, we need an OIDC client app and an authorization server. And I find the most simplistic test architecture to be pattern #2 (without reverse proxy) on the client side, and pattern #1 on the server side. In the next section let&amp;#8217;s discuss why I prefer this test architecture.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Choice of Tools&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The principle of this lab is that we can focus on the OIDC flows itself and simplify every other aspects as much as we can.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The authorization server needs to access my client app. As a result, if I use a public authorization server, such as Azure or Google, I&amp;#8217;d have to host my client app on a public IP with domain name as well. This creates churns. To see how much hassle this can involve, review my article &lt;a href="https://www.digihunch.com/2022/02/istio-external-authorization/"&gt;Istio External Authentication lab&lt;/a&gt;. Instead, I need a tool to host the authorization server on my laptop. And yes, it&amp;#8217;s &lt;a href="https://www.keycloak.org/"&gt;KeyCloak&lt;/a&gt;. It is a well-renewed open-source project for identity and access management. It is also the upstream project of &lt;a href="https://access.redhat.com/products/red-hat-single-sign-on"&gt;RedHat SSO&lt;/a&gt;. Another reason is it operates on PostgreSQL database which is a common relational database technology with release in Docker images.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On the client side, we can have our web service with built-in OIDC capability, which usually requires development work in a language with OIDC library. Alternatively, if the client app lacks such capability, we would address this capability in a different component on the client side. It can either be a standalone, purpose-built proxy, or a generic reverse proxy with OIDC capability. For this, I examined a few options. &lt;a href="https://github.com/nginxinc/nginx-openid-connect"&gt;Nginx&lt;/a&gt; and &lt;a href="https://doc.traefik.io/traefik-enterprise/middlewares/oidc/"&gt;Traefik&lt;/a&gt; have OIDC support for a fee in their Enterprise product. Apache has a module &lt;a href="https://github.com/OpenIDC/mod_auth_openidc"&gt;mod_auth_oidc&lt;/a&gt; for free but it requires building the plug-in on my own for Mac platform. Eventually I landed on the purpose-built option, using the &lt;a href="https://github.com/oauth2-proxy/oauth2-proxy"&gt;oauth2-proxy&lt;/a&gt; open source project. This project can act as both actor #3 (with a minimal HTTP server) and actor #4 (OIDC client). So it also saves me from hosting a separate web server. Also, I have used it in the past in the &lt;a href="https://www.digihunch.com/2022/02/istio-external-authorization/"&gt;Istio External Authentication lab&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In reality, each message in the OIDC flows must be TLS encrypted. In local testing though we don&amp;#8217;t really care. Similarly we don&amp;#8217;t necessary need a reverse proxy if it plays no role in the OIDC flow. Both Keycloak (with PostgreSQL) and oauth2-proxy are released in Docker images. As a result, we are ready to roll with only three tools: the browser on local host, Keycloak and oauth2-proxy in the Docker daemon, which provides great portability.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To get started, let&amp;#8217;s have two fictitious domains, &lt;code&gt;web.digihunch.com&lt;/code&gt;, and &lt;code&gt;keycloak.digihunch.com&lt;/code&gt;. In the Docker compose manifest, I name the services based on their hostname so that they can reference each other from within the container network namespace. To access the service from the host, I force the DNS resolution to localhost in &lt;code&gt;/etc/hosts&lt;/code&gt; on my Mac, and make sure to declare the same host port in the port mapping (4180 for dummy web service; and 8080 for Keycloak).&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Configuration&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I created a Docker compose file as below, to set up my test:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;web.digihunch.com&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;oauth2-proxy&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;quay.io/oauth2-proxy/oauth2-proxy:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - --&lt;span style="color:#ae81ff"&gt;http-address&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;0.0.0.0&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;4180&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_COOKIE_SECRET&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;NYZaClZinINKwxNGzEDeFGh64W6tmq1eB6uHQPa4S5o &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_CLIENT_ID&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;dh-user-client&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_CLIENT_SECRET&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pmkwBjkVesrj7fw1MY7h5s9e3cmAKXgc &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_PROVIDER&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;oidc &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_OIDC_ISSUER_URL&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http://keycloak.digihunch.com:8080/realms/digihunch-users&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_PASS_ACCESS_TOKEN&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_EMAIL_DOMAINS&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;*&amp;#39;&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_REDIRECT_URL&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http://web.digihunch.com:4180/oauth2/callback&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_PROVIDER_DISPLAY_NAME&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;DHCKC&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_COOKIE_CSRF_EXPIRE&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;5m&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_COOKIE_CSRF_PER_REQUEST&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_COOKIE_SECURE&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Needed for HTTP connection&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#OAUTH2_PROXY_UPSTREAMS: file:///var/www/static/#/home/ # serve page at /home path&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;OAUTH2_PROXY_UPSTREAMS&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;static://202 &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./config/oauth2-proxy.cfg:/etc/oauth2-proxy.cfg&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# - ./config/www:/var/www/static/&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;4180&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;4180&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;oidc_network&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;keycloak.digihunch.com&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;postgres-db&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;postgres&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;postgresdb&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;shm_size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;128mb&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;5432&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;5432&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;oidc_network&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./data/pgdata:/var/lib/postgresql/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;POSTGRES_USER=master&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;POSTGRES_PASSWORD=masterpass&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;POSTGRES_DB=keycloak&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;keycloak.digihunch.com&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;quay.io/keycloak/keycloak&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;start&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;: &lt;span style="color:#75715e"&gt;# Based on Hostname:v2 https://www.keycloak.org/docs/25.0.0/upgrading/#migrating-to-25-0-0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KC_HOSTNAME&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http://keycloak.digihunch.com:8080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#KC_HOSTNAME_ADMIN: For simplicity, no separate management URL or port&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KC_HOSTNAME_BACKCHANNEL_DYNAMIC&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KC_HTTP_ENABLED&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt; &lt;span style="color:#75715e"&gt;## Otherwise HTTPS is the enforced by default.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KC_HEALTH_ENABLED&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KEYCLOAK_ADMIN&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;admin &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KEYCLOAK_ADMIN_PASSWORD&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;kcadminpass&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KC_DB&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;postgres&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KC_DB_URL&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jdbc:postgresql://postgres-db/keycloak&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KC_DB_USERNAME&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;master&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KC_DB_PASSWORD&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;masterpass &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;oidc_network&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;postgres-db&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;oidc_network&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enable_ipv6&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;driver&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;bridge&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;For OAuth2-Proxy, the &lt;a href="https://quay.io/repository/oauth2-proxy/oauth2-proxy"&gt;official image&lt;/a&gt; for oauth2-proxy is distroless. So if you have to troubleshoot its container file system, you need to access it via an &lt;a href="https://medium.com/@ananthsekar007/troubleshooting-strategies-for-distroless-containers-unveiling-solutions-in-minimalist-ca2e284b6e1a"&gt;assistant container&lt;/a&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run --rm -it --name debugger --privileged --pid container:oauth2-proxy --network container:oauth2-proxy busybox sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# to see target container&amp;#39;s file system, go to: ls -l /proc/1/root/&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Alternatively, we can use the one that Bitnami &lt;a href="https://hub.docker.com/r/bitnami/oauth2-proxy/tags"&gt;releases&lt;/a&gt; but be vary of some &lt;a href="https://github.com/oauth2-proxy/oauth2-proxy/issues/1802#issuecomment-1447999568"&gt;nuances&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the Keycloak part, I use environment variables and had to watch out for the recent changes on &lt;a href="https://www.keycloak.org/docs/25.0.0/upgrading/#migrating-to-25-0-0"&gt;hostname v2&lt;/a&gt;. We should first start up the Keycloak service. From http://keycloak.digihunch.com:8080, we can login using the credential specified in the environment variables, then we can create a client app: &lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Create a new realm (dropdown-&amp;gt; Create realm) with name digihunch-users&lt;/li&gt;&#10;&lt;li&gt;Switch to this realm from the dropdown, create a couple users (e.g. dhadmin@www.digihunch.com), and set password.&lt;/li&gt;&#10;&lt;li&gt;Create a group (e.g. myadmin) and join the user to the group&lt;/li&gt;&#10;&lt;li&gt;Under the same realm, create a client, with OpenID Connect as type, client ID being dh-user-client. turn on client authentication (without Direct access grants)&lt;/li&gt;&#10;&lt;li&gt;Save the client for now and grab the client secret. Note that the OIDC discovery document (http://keycloak.digihunch.com:8080/realms/digihunch-users/.well-known/openid-configuration) should come online.&lt;/li&gt;&#10;&lt;li&gt;Update the docker compose file:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;The value for OAUTH2_PROXY_CLIENT_SECRET is from the client secret; &lt;/li&gt;&#10;&lt;li&gt;The value for OAUTH2_PROXY_OIDC_ISSUER_URL should be http://keycloak.digihunch.com:8080/realms/digihunch-users; &lt;/li&gt;&#10;&lt;li&gt;The value for OAUTH2_PROXY_CLIENT_ID is dh-user-client;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Restart all services including the web. Log on to keycloak and go back to the client configuration in the realm. Under settings. Put in valid redirect URIs as http://web.digihunch.com:4180/oauth2/callback and save the client. &lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now, let&amp;#8217;s start an private browser session, and browse to http://web.digihunch.com:4180/. The browser should redirect you to keycloak&amp;#8217;s login page. Once log in is successful, it should redirect you to the static response with 202 code as the manifest configured.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this post, I laid out the steps to test the login for OIDC authorization code flow locally as a starting point. For a bullet-proof solution, I recommend taking a look at KeyCloak &lt;a href="https://www.keycloak.org/docs/latest/server_admin/"&gt;administration guide&lt;/a&gt;. For example, we typically disable the master realm for security. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There is a similar test setup on &lt;a href="https://developer.okta.com/blog/2022/07/14/add-auth-to-any-app-with-oauth2-proxy"&gt;Otka blog&lt;/a&gt; but I simplified all the aspects that I regard as distractors. There are many other flows that can be tested. However, some of the testing still requires client pattern #1 if we need to initiates an activity from the client application.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2024/07/iam-roles-for-any-workload/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;IAM Roles for any workload&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2024/08/debating-between-count-and-for_each-in-terraform/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Debating between count and for_each in Terraform&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Kubernetes Admission Control</title><link>https://www.digihunch.com/2022/01/kubernetes-admission-control/</link><pubDate>Fri, 07 Jan 2022 22:21:00 -0400</pubDate><guid>https://www.digihunch.com/2022/01/kubernetes-admission-control/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-k8s-admin-control.webp" alt="Featured image of post Kubernetes Admission Control" /&gt;&lt;p class="wp-block-paragraph"&gt;This post discusses admission control, and its implementation &amp;#8211; the OPA Gatekeeper. I also discuss Azure Policy as a different Gatekeeper implementation.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="admission-webhooks"&gt;Admission Webhooks&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Admission controller intercepts requests to the Kubernetes API server after the request has been authenticated and authorized, and prior to persistence of the object into etcd store. There are many compiled-in &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-does-each-admission-controller-do"&gt;controllers&lt;/a&gt;, which can be turned on and off on the node with the arguments of &lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/"&gt;kube-apiserver&lt;/a&gt; process. For example, the &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#imagepolicywebhook"&gt;ImagePolicyWebhook&lt;/a&gt; can be enabled with value ImagePolicyWebhook added to the &amp;#8211;enable-admission-plugins switch. Its configuration can be provided via the &amp;#8211;admission-control-config-file &lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/#options"&gt;switch&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In addition to the compiled-in admission plugins (which must be configured for kube-apiserver process on the node), admission plugins can be developed as extensions and run as webhooks configured at runtime. This allows users to configure webhooks via API access, dynamically without having to restart kube-apiserver process on the Node, which is usually hard to do with managed Kubernetes platforms. They are therefore called Dynamic Admission Control.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can define two types of admission webhooks in dynamic admission control: validating admission webhook, and mutating admission webhook. Their interaction with API server can be illustrated in the diagram below:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="307" src="https://www.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-1024x307.jpeg" alt="" class="wp-image-7733" srcset="https://www.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-1024x307.jpeg 1024w, https://www.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-300x90.jpeg 300w, https://www.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-768x231.jpeg 768w, https://www.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-1536x461.jpeg 1536w, https://www.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-2048x615.jpeg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The mutating admission hook takes action to change the API request, whereas the validating admission hook accepts or denies the request. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A good example of mutating webhook is Istio&amp;#8217;s sidecar injector. We can view the configuration with this command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl get MutatingWebhookConfiguration istio-sidecar-injector -o yaml | less&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From the manifest returned, we can see that in this configuration the request is forwarded to istiod service on port 443, at path /inject for processing. We can also see some matching rules to find the target Pod creation API request.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Validating webhook can be display with the following call:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl get ValidatingWebhookConfiguration&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The output of validating webhook is a yes or no decision. We usually use validating webhook in conjunction with a policy engine to decide whether the request should be accepted or denied.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="open-policy-agent"&gt;Open Policy Agent&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Open Policy Agent (OPA) is an open-source general-purpose policy engine that applies policies written in &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/"&gt;Rego language&lt;/a&gt; to ingested JSON document and returns a result. It is usually integrated with system which requires a policy engine. For example, &lt;a href="https://kyverno.io/"&gt;Kyverno&lt;/a&gt; is a policy engine designed for Kubernetes. &lt;a href="https://blog.styra.com/blog/authorize-better-istio-traffic-policies-with-opa-styra-das"&gt;Styra&lt;/a&gt; (one of the OPA contributors) develops policy engines to integrate with Istio&amp;#8217;s authorization policy. They have &lt;a href="https://academy.styra.com/"&gt;online courses&lt;/a&gt; on &lt;a href="https://academy.styra.com/courses/opa-rego"&gt;OPA policy authoring&lt;/a&gt; and &lt;a href="https://academy.styra.com/courses/microservice"&gt;microservice authorization&lt;/a&gt; with their product.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;OPA is build to be a general-purpose, unified way of solving policy and authorization problem. With microservice authorization, the activities includes decision making (determine action based on input, aka Policy Decision Point, PDP), and decision enforcement (issue 400 code or 200 code depending on decision, aka Policy Enforcement Point, PEP). OPA is introduced to decouple these two activities. OPA&amp;#8217;s input is a JSON payload and it uses Policy in Rego language to come to decision.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The team that developers Open Policy Agent also created their controller (with OPA as the core component) to run validating web hook and mutating web hook. The original version is OPA-Kubernetes that uses kube-mgmt. This original version is also dubbed Gatekeeper v1.0. When OPA starts, the kube-mgmt sidecar container will load Kubernetes Namespace and Ingress objects into OPA. You can configure the sidecar to load any kind of Kubernetes object into OPA. The sidecar establishes watches on the Kubernetes API server so that OPA has access to an eventually consistent cache of Kubernetes objects. It has gone through a couple of major version changes as summarized in &lt;a href="https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/#evolution"&gt;this&lt;/a&gt; section. As of today, when we deploy Gatekeeper we should use version 3.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="gatekeeper-v3"&gt;Gatekeeper v3&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Currently, Gatekeeper v3 is the most popular choice for Kubernetes Policy Controller. The diagram bellow illustrate how Gatekeeper integrates with Kubernetes API server.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="943" height="478" src="https://www.digihunch.com/wp-content/uploads/2022/02/apiserver.png" alt="" class="wp-image-3481"/&gt;&lt;figcaption class="wp-element-caption"&gt;Gatekeeper and Kubernetes&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can follow &lt;a href="https://open-policy-agent.github.io/gatekeeper/website/docs/install/"&gt;this&lt;/a&gt; guide to install Gatekeeper but the key step is as simple as to apply the correct version of manifest. Alternatively it can be installed &lt;a href="https://open-policy-agent.github.io/gatekeeper/website/docs/install#deploying-via-helm"&gt;using Helm&lt;/a&gt;. After the installation, we should see a Service named &lt;em&gt;gatekeeper-webhook-service&lt;/em&gt; in the &lt;em&gt;gatekeeper-system&lt;/em&gt; namespace. We can also inspect the newly created validationg web hook configuration&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;k get validatingwebhookconfiguration gatekeeper-validating-webhook-configuration -o yaml | less&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The result indicates that the configuration forwards incoming manifests to the &lt;em&gt;gatekeeper-webhook-service&lt;/em&gt; web service at the path /v1/admin for validation, and then at /v1/admitlabel for labelling. The configuration also stores rules as matching criteria.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can smoke test Gatekeeper v3, with the basic example in its &lt;a href="https://github.com/open-policy-agent/gatekeeper/tree/master/example"&gt;directory&lt;/a&gt;. Apply the template, constraint and then the manifests in resources. The pod creation will fail with an error like:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Error from server &lt;span style="color:#f92672"&gt;([&lt;/span&gt;pod-must-have-gk&lt;span style="color:#f92672"&gt;]&lt;/span&gt; you must provide labels: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;gatekeeper&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;})&lt;/span&gt;: error when creating &lt;span style="color:#e6db74"&gt;&amp;#34;resources/bad_pod_namespaceselector.yaml&amp;#34;&lt;/span&gt;: admission webhook &lt;span style="color:#e6db74"&gt;&amp;#34;validation.gatekeeper.sh&amp;#34;&lt;/span&gt; denied the request: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;pod-must-have-gk&lt;span style="color:#f92672"&gt;]&lt;/span&gt; you must provide labels: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;gatekeeper&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The gatekeeper &lt;a href="https://open-policy-agent.github.io/gatekeeper/website/docs/howto"&gt;document&lt;/a&gt; also covers the details of using ConstraintTemplate and Constraints. However, Writing your own a policy in Rego still takes time and we want to piggyback on the community for commonly used policies. &lt;a href="https://github.com/open-policy-agent"&gt;OPA&lt;/a&gt;&amp;#8216;s &lt;a href="https://github.com/open-policy-agent/gatekeeper-library"&gt;gatekeeper-library&lt;/a&gt; projects keeps a handful of those in its &lt;a href="https://github.com/open-policy-agent/gatekeeper-library/tree/master/library"&gt;library&lt;/a&gt; directory. We can test the &lt;a href="https://github.com/open-policy-agent/gatekeeper-library/tree/master/library/pod-security-policy/privileged-containers"&gt;privileged container&lt;/a&gt; example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cd gatekeeper-library/library/pod-security-policy/privileged-containers&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kustomize build . | kubectl apply -f -&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;constrainttemplate.templates.gatekeeper.sh/k8spspprivilegedcontainer created&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f samples/psp-privileged-container/example_disallowed.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pod/nginx-privileged-disallowed created&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl delete -f samples/psp-privileged-container/example_disallowed.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pod &lt;span style="color:#e6db74"&gt;&amp;#34;nginx-privileged-disallowed&amp;#34;&lt;/span&gt; deleted&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f samples/psp-privileged-container/constraint.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;k8spspprivilegedcontainer.constraints.gatekeeper.sh/psp-privileged-container created&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f samples/psp-privileged-container/example_disallowed.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Error from server &lt;span style="color:#f92672"&gt;([&lt;/span&gt;psp-privileged-container&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Privileged container is not allowed: nginx, securityContext: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;privileged&amp;#34;&lt;/span&gt;: true&lt;span style="color:#f92672"&gt;})&lt;/span&gt;: error when creating &lt;span style="color:#e6db74"&gt;&amp;#34;samples/psp-privileged-container/example_disallowed.yaml&amp;#34;&lt;/span&gt;: admission webhook &lt;span style="color:#e6db74"&gt;&amp;#34;validation.gatekeeper.sh&amp;#34;&lt;/span&gt; denied the request: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;psp-privileged-container&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Privileged container is not allowed: nginx, securityContext: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;privileged&amp;#34;&lt;/span&gt;: true&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Currently the library &lt;a href="https://github.com/open-policy-agent/gatekeeper-library/tree/master/library"&gt;directory&lt;/a&gt; contains two sub-directories, general and pod-scurity-policy. The latter is to regulate Pod creation, while the former includes more common usecases such as disable node port, enforce https, and enforce probes. This is the place I start with when building a policy.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The policy constraints take effect cluster wide. When we have multiple clusters, we would like a unified place to manage policies. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="azure-policy-with-aks"&gt;Azure Policy with AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We take Azure Policy with AKS as an example to illustrate how public cloud platform can simplify policy management. When building AKS cluster, an &lt;a href="https://docs.microsoft.com/en-us/azure/governance/policy/concepts/policy-for-kubernetes#install-azure-policy-add-on-for-aks"&gt;addon&lt;/a&gt; profile for Azure Policy can be installed. This allows Azure Policy to connect to the AKS cluster. Azure Policy contains many built-in policies definitions (as well as initiative definitions which are groups of related policies). We can simply search by Kubernetes keyword and look for the built-in policies. For example, there is a built-in policy definition &amp;#8220;Kubernetes clusters should not allow container privilege escalation. The definitions (policy or initiative) can be assigned to a resource group with enforcement action set to denied, and with excluded namespaces, as shown in the screenshot below&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1566" height="1056" src="https://www.digihunch.com/wp-content/uploads/2022/02/image-2.png" alt="" class="wp-image-3497"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The assignment can take as long as 10 minutes to push down to the cluster. Then we should be able to confirm by checking the constraint CRDs. We can see this This setup brings a centralized policy management system that can be easily hooked up to multiple clusters.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://ahmedkhamessi.com/img/azurepolicy/azurepolicy.png" alt="Azure Policy and OPA Gatekeeper underlay for AKS"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Other benefits of this architecture includes the ability to report compliance. As per CIS report for Azure AKS recommendation 4.3:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Azure Policy extends Gatekeeper v3, an admission controller webhook for Open Policy Agent (OPA), to apply at-scale enforcements and safeguards on your clusters in a centralized, consistent manner. It covers many &lt;a href="https://www.digihunch.com/2021/01/basic-kubernetes-resource-object-1-of-2/"&gt;basic resource&lt;/a&gt; &lt;a href="https://www.digihunch.com/2021/02/basic-resource-object-in-kubernetes-2-of-2/"&gt;types&lt;/a&gt; but does not cover any well-known CRDs. Azure Policy makes it possible to manage and report on the compliance state of your Kubernetes clusters from one place.&amp;nbsp;&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Checks with Azure Policy service for policy assignments to the cluster.&lt;/li&gt;&#10;&lt;li&gt;Deploys policy definitions into the cluster as constraint template and constraint custom resources.&lt;/li&gt;&#10;&lt;li&gt;Reports auditing and compliance details back to Azure Policy service.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As of February 2022, AWS EKS doesn&amp;#8217;t seem to have the equivalent of this capability to integrate with a policy management. The only option would be to install Gatekeeper v3 &lt;a href="https://aws.amazon.com/blogs/opensource/using-open-policy-agent-on-amazon-eks/"&gt;yourself&lt;/a&gt; on the cluster, or host it separately. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="bottom-line"&gt;Bottom line&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Admission control should be a standard setup in Kubernetes deployment. When building gatekeeper system on your own, it can be set up separately on a different cluster. When Kubernetes is provided as a platform, it is very helpful for platform operator to manage their tenants. If the tenant is application development team, it also makes sense for them to develop their own policies for the developers in their team.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2021/12/from-ingress-to-gateway-why-you-need-istio-gateways-on-kubernetes-platforms/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;From Ingress to CRD: why my solution needs Istio Gateways on Kubernetes platforms&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2022/01/fluxcd-continuous-deployment-with-gitops/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;FluxCD: Continuous Deployment with GitOps&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>