<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>rosa on Digi Hunch</title><link>https://static.digihunch.com/tag/rosa/</link><description>Recent content in rosa on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 02 Apr 2025 10:04:04 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/rosa/index.xml" rel="self" type="application/rss+xml"/><item><title>Authentication to kube-apiserver via OIDC</title><link>https://static.digihunch.com/2023/07/authenticate-kube-apiserver-via-oidc/</link><pubDate>Fri, 28 Jul 2023 09:20:00 -0400</pubDate><guid>https://static.digihunch.com/2023/07/authenticate-kube-apiserver-via-oidc/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-kubectl-oidc.webp" alt="Featured image of post Authentication to kube-apiserver via OIDC" /&gt;&lt;h2 class="wp-block-heading"&gt;Background&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are many benefits of using OIDC to authenticate to kube-api server, especially with multiple clusters that need consistent log-in experience. With the &lt;a href="https://static.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/"&gt;last post&lt;/a&gt; on how OIDC Authorization Code Flow works, now I will discuss options with authentication for kubectl to connect to kube API server.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To start, let&amp;#8217;s look at the anatomy of kubeconfig file. The full schema is in the &lt;a href="https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/"&gt;documentation&lt;/a&gt;. Looking at my kubeconfig file, there are three sections:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;clusters: each entry specifies a cluster&amp;#8217;s name, server address and certificate authority data (in base64 encoding or a file location). &lt;/li&gt;&#10;&lt;li&gt;users: each entry specifies a username. Some users are identified with client key and certificate. Some specify a command to provide client authentication. Refer to the &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/"&gt;authentication strategies&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;contexts: each entry links a user to a cluster&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Therefore, the key to use OIDC integration, is to use command to provide client authentication. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-vanilla-kubernetes"&gt;Vanilla Kubernetes&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The documentation on authenticating has a diagram on how to use &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens"&gt;OpenID Connect tokens&lt;/a&gt;. The diagram does not give details on how access_token and id_token were obtained. So it could be any OIDC flow (Authorization Code Flow, Implicit Flow, etc) as we have &lt;a href="https://static.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/"&gt;discussed&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full is-resized"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="828" src="https://static.digihunch.com/wp-content/uploads/2023/07/oidc-flow.webp" alt="" class="wp-image-12940" style="width:651px;height:auto" srcset="https://static.digihunch.com/wp-content/uploads/2023/07/oidc-flow.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/07/oidc-flow-300x243.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/07/oidc-flow-768x621.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Although the instruction does not mandate which OIDC flow to use, we should use Authorization Code Flow in this architecture. The API server needs to trust the OIDC issuer, and the &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuring-the-api-server"&gt;document&lt;/a&gt; covers how to configure API server.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the diagram, step 2 and step 3 are required by kubectl itself does not perform these activities. All kubectl does is carry the JWT token in the Authorization Bearer. Vanilla Kubernetes does not provide a solution for OIDC integration. It only provides some instructions and we still need some helper scripts to glue all these instruction steps together.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are many open-source project for this purpose. For example, Jetstack has &lt;a href="https://github.com/jetstack/kube-oidc-proxy"&gt;kube-oidc-proxy&lt;/a&gt; and Int128 developed &lt;a href="https://github.com/int128/kubelogin"&gt;kubelogin&lt;/a&gt;. Other projects such as &lt;a href="https://github.com/vimond/k8s-auth-client"&gt;k8s-auth-client&lt;/a&gt;, &lt;a href="https://github.com/micahhausler/k8s-oidc-helper"&gt;k8s-oidc-helper&lt;/a&gt;, and &lt;a href="https://github.com/vmware-archive/gangway"&gt;gangway&lt;/a&gt; are no longer being updated. The kubelogin project remains influential. It has a clear diagram too:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="503" src="https://static.digihunch.com/wp-content/uploads/2023/07/kube-oidc-proxy.webp" alt="" class="wp-image-12941" srcset="https://static.digihunch.com/wp-content/uploads/2023/07/kube-oidc-proxy.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/07/kube-oidc-proxy-300x147.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/07/kube-oidc-proxy-768x377.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;From the diagram we can see kubelogin proposes authorization code flow. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Also, one design concern with Kubernetes control plane is the placement of endpoint. From this diagram we can see that even if the cluster endpoint is on private subnet, OIDC integration should still work. The control plane (specifically kube-apiserver) initiates outbound connection to OIDC Provider. There is no inbound connection to it from the OIDC provider.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the kubelogin setup, the redirect URI is set to localhost:8080 because it stands up a server on the same host where browser is running. The browser can always resolve localhost. For a full configuration steps, Okta has &lt;a href="https://developer.okta.com/blog/2021/11/08/k8s-api-server-oidc"&gt;this blog post&lt;/a&gt; on how to use kubelogin as helper, and Okta as Authorization Server to authenticate kubectl via OIDC. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Step-by-step with kubelogin&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We&amp;#8217;ll go through an example with int128/kubelogin because it works with any Kubernetes flavour, including managed Kubernetes services. It is also fairly simple. The instruction covers a few types of Authorization Servers (Google Identity Platform, KeyCloak, Dex with GitHub, Okta and Ping Identity). I&amp;#8217;ll take KinD cluster as an example and use Azure AD as Authorization Server.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;First, we&amp;#8217;ll register an App in Azure Portal. Go to &amp;#8220;App Registrations&amp;#8221; and &amp;#8220;New registration&amp;#8221;. Give it a name &amp;#8220;kubeoidc&amp;#8221; and set Redirect URI to &amp;#8220;Web&amp;#8221; with URL &amp;#8220;localhost:8000&amp;#8221;. Click on Register.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="399" src="https://static.digihunch.com/wp-content/uploads/2023/07/kubeoidc-entra-1.webp" alt="" class="wp-image-12942" srcset="https://static.digihunch.com/wp-content/uploads/2023/07/kubeoidc-entra-1.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/07/kubeoidc-entra-1-300x117.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/07/kubeoidc-entra-1-768x299.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The next page shows the details for this app. The Application (client) ID is important for next steps. Click on &amp;#8220;Add a certificate or secret&amp;#8221;, then &amp;#8220;New client secret&amp;#8221;, put in expiry and description. The secret value is generated and displayed on the next page, which is important for our next step. &lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="204" src="https://static.digihunch.com/wp-content/uploads/2023/07/kubeoidc-entra-2.webp" alt="" class="wp-image-12943" srcset="https://static.digihunch.com/wp-content/uploads/2023/07/kubeoidc-entra-2.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/07/kubeoidc-entra-2-300x60.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/07/kubeoidc-entra-2-768x153.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We also need to find out the issuer URL. From the App page above, click on &amp;#8220;Endpoints&amp;#8221; and find out the URL from field &amp;#8220;OpenID Connect metadata document&amp;#8221;. My metadata document URL looks 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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;https://login.microsoftonline.com/xx8x8xx8-7777-66yy-55b5-444aaaaa3322/v2.0/.well-known/openid-configuration&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The OIDC Issuer URL is the part before .well-known. In this case, it is:&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;https://login.microsoftonline.com/xx8x8xx8-7777-66yy-55b5-444aaaaa3322/v2.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now we have collected what we need for the next few steps: ClientID, ClientSecret and OIDC Issuer URL. We can then create the KinD cluster, and reference ClientID and IssuerURL in the cluster 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;cat &lt;span style="color:#e6db74"&gt;&amp;lt;&amp;lt; EOF &amp;gt; kind-config.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;kind: Cluster&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;apiVersion: kind.x-k8s.io/v1alpha4&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;nodes:&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - role: control-plane&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; kubeadmConfigPatches:&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - |&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; kind: ClusterConfiguration&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; apiServer:&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; extraArgs:&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; oidc-issuer-url: https://login.microsoftonline.com/xx8x8xx8-7777-66yy-55b5-444aaaaa3322/v2.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; oidc-client-id: 99999e88-e777-6666-c5c5-c444444d3d22&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - role: worker&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - role: worker&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - role: worker&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;EOF&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;kind create cluster --config&lt;span style="color:#f92672"&gt;=&lt;/span&gt;kind-config.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command will create cluster and configure kubeconfig file with a user named kind-kind as admin, which isn&amp;#8217;t what we need. Now we use kubelogin helper 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 oidc-login setup &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --oidc-issuer-url&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ISSUER_URL &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --oidc-client-id&lt;span style="color:#f92672"&gt;=&lt;/span&gt;YOUR_CLIENT_ID &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --oidc-client-secret&lt;span style="color:#f92672"&gt;=&lt;/span&gt;YOUR_CLIENT_SECRET&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This command will act as the client in the OIDC flow and prompts you to log in to Azure in a Browser. Once logged on, it gives you the next few commands to run. Since we&amp;#8217;ve already created a cluster with the Issuer URL and Client ID, we can skip creating cluster, and run the steps to:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;bind the Azure user to a ClusterRole&lt;/li&gt;&#10;&lt;li&gt;set up the kubeconfig locally with a user oidc, which needs to execute the oidc-login command&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Lastly, we can test the oidc user with &lt;code&gt;kubectl --user=oidc get nodes&lt;/code&gt;. We can also set the context to use oidc user by default. Voila.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;From this example, we learned how to configure OIDC integration for any Kubernetes distros. The steps that we need to take are:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;On the cluster side, we customize kube-api-server flag with OIDC provider info. Specifically, we provided Issuer URL and client ID in this example. In the OIDC Flow diagram, this step establish a trust from the Resource Server (K8s cluster) to the OIDC provider. &lt;/li&gt;&#10;&lt;li&gt;On the kubectl side, kubectl itself cannot fulfill all the duties of a client app in the OIDC Flow. It needs a helper script and we&amp;#8217;ve made friend with int128/kubelogin.&lt;/li&gt;&#10;&lt;li&gt;On the Identity Store side, we expect it to be an OIDC-compliant Authorization Server. Otherwise, we consider using Dex as a broker in between. &lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When it comes to managed service, many allows us to customize the OIDC related flags for kube-api-server. Let&amp;#8217;s look at how some managed services get this to work.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-azure-kubernetes-service"&gt;Azure Kubernetes Service&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I find the kubectl authentication with AKS highly opinionated in its documentation. The recommendation is using Azure Active Directory as identity store but I don&amp;#8217;t find it work with other OIDC providers.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To work with Azure Active Directory, you would configure the cluster and specify Azure role-based access control with the Group UUID in Azure AD. On the client side, you will need to install &lt;a href="https://github.com/Azure/kubelogin"&gt;Azure&amp;#8217;s kubelogin&lt;/a&gt; utility. Do not confuse it with int128&amp;#8217;s kubelogin, which is for any cluster. This &lt;a href="https://blog.baeke.info/2021/06/03/a-quick-look-at-azure-kubelogin/"&gt;kubelogin&lt;/a&gt; is just for Azure. Once installed, you can use az-cli command to update your kubeconfig file, which call this utility from kubeconfig. To examine details about &lt;a href="https://github.com/digihunch/cloudkube/blob/4735426f5c3e8f3b448bdb4f4b8ef33d340f71eb/azure/modules/bastion/files/bastion_init_sh.tpl#L27"&gt;client configuration&lt;/a&gt; and &lt;a href="https://github.com/digihunch/cloudkube/blob/4735426f5c3e8f3b448bdb4f4b8ef33d340f71eb/azure/modules/aks/main.tf#L43"&gt;AAD integration&lt;/a&gt;, check out the Terraform template in the &lt;a href="https://github.com/digihunch/cloudkube/tree/main/azure"&gt;azure&lt;/a&gt; directory of my &lt;a href="https://github.com/digihunch/cloudkube"&gt;cloudkube&lt;/a&gt; project.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full is-resized"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="620" src="https://static.digihunch.com/wp-content/uploads/2023/07/aks-kubeconfig.webp" alt="" class="wp-image-12944" style="width:782px;height:auto" srcset="https://static.digihunch.com/wp-content/uploads/2023/07/aks-kubeconfig.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/07/aks-kubeconfig-300x182.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/07/aks-kubeconfig-768x465.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The document only covers &lt;a href="https://learn.microsoft.com/en-us/azure/aks/concepts-identity#azure-ad-integration"&gt;Azure AD integration&lt;/a&gt; and I tried to find if there&amp;#8217;s a way to integrate with third-party OIDC providers. Unfortunately I have no luck. ChatGPT points me to &lt;a href="https://learn.microsoft.com/en-gb/azure/aks/use-oidc-issuer"&gt;a page about enabling OIDC provider&lt;/a&gt; but it is in the context of workload identity and it does not allow you to customize the issuer. So it&amp;#8217;s completely irrelevant. Because you cannot customize OIDC issuer, etc, it simply won&amp;#8217;t work with any third-party OIDC provider. Sure enough, most of Azure&amp;#8217;s client use Azure AD anyways.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Azure makes it streamlined to configure OIDC integration of AKS with Azure AD, its own identity store. To my disappointment, it is currently not possible to integrate with third-party OIDC provider for authentication at cluster endpoint. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-rosa"&gt;ROSA&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;RedHat OpenShift on AWS (ROSA) is a. However it reflects how OpenShift configures third-party identity provider. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Let&amp;#8217;s first create a cluster off custom VPC and private endpoint. This is covered in my previous &lt;a href="https://static.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/"&gt;post&lt;/a&gt; about ROSA. After the cluster creation, we&amp;#8217;re at the point where we can run &lt;code&gt;oc&lt;/code&gt; command against cluster endpoint from Bastion host because it is a private cluster. However, being a private cluster is irrelevant to how we configure OIDC integration. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We&amp;#8217;ll use Azure AD again as OIDC provider. So we need to register an app the same way as I did above in the kubelogin example. We need to have Client ID, Secret and OIDC issuer URL. For RedirectURI, go to your &lt;a href="https://console.redhat.com/openshift/"&gt;OpenShift console&lt;/a&gt;, and under &lt;code&gt;Cluster&lt;/code&gt;, click on the cluster name → access control → identity providers → select OpenID. Note the page clearly states that this is Authorization Code Flow, and the OAuth Callback URL is provided. Use it to regiter App in Azure, and fill in the page with Client ID, Secret and OIDC issuer URL. Also fill in other fields accordingly and click on Add. Now you should have it configured!&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="288" src="https://static.digihunch.com/wp-content/uploads/2023/07/rosa-idp.webp" alt="" class="wp-image-12946" srcset="https://static.digihunch.com/wp-content/uploads/2023/07/rosa-idp.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/07/rosa-idp-300x84.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/07/rosa-idp-768x216.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Identity Providers for OpenShift cluster&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The next step is trying to login. From Bastion host, run &lt;code&gt;oc login&lt;/code&gt; and it will give me an URL to use. The URL contains the cluster endpoint, which resolvable from the Bastion Host itself. However I need a Browser session here, so I have to run Bastion host as SOCKS5 proxy and tell Chrome on my MacBook to use it:&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;~ open /Applications/Google&lt;span style="color:#ae81ff"&gt;\ &lt;/span&gt;Chrome.app --args --proxy-server&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;socks5://localhost:1080&amp;#34;&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 browser session redirects to Azure AD for log in. Once completed the webpage will display a token that I can use with &lt;code&gt;oc login&lt;/code&gt; command. Run this command with token from Bastion, I&amp;#8217;m logged in:&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;oc login --token&lt;span style="color:#f92672"&gt;=&lt;/span&gt;sha256~3ZT5JGWELOcBzfohftEm9D2UwoOVFvATASuZk3_uxps --server&lt;span style="color:#f92672"&gt;=&lt;/span&gt;https://api.dhc.62q3.p1.openshiftapps.com:6443&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oc whoami&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oc get no&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;At this point, if I run &lt;code&gt;oc whoami&lt;/code&gt;, I get the user name. However, this user cannot do anything. This is because it is not associated with a role yet. You grant more permission to this user: go back to OpenShift console, Clusters → ClusterName → Access Control → Cluster Roles and Access → Add user. Here you can map the user name to a role (let&amp;#8217;s say ClusterAdmin). Then this user will have its priviledge:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="346" src="https://static.digihunch.com/wp-content/uploads/2023/07/oc-command.webp" alt="" class="wp-image-12947" srcset="https://static.digihunch.com/wp-content/uploads/2023/07/oc-command.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/07/oc-command-300x101.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/07/oc-command-768x260.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The whole Flow works with private cluster, because the redirect URI is resolvable from the Bastion host. If you chose to expose cluster endpoint publicly (not recommended), you can perform the above steps directly from your MacBook or Laptop. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;So the ROSA experience has been smooth. Unlike kubectl, the &lt;code&gt;oc&lt;/code&gt; utility can act as the Client App in Authorization Code Flow. The other part of the configuration such as client secret and issuer URL are made in OpenShift console. Good job!&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-elastic-kuberentes-service"&gt;Elastic Kuberentes Service&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;EKS allows you to specify &lt;a href="https://aws.amazon.com/blogs/containers/introducing-oidc-identity-provider-authentication-amazon-eks/"&gt;OIDC issuers&lt;/a&gt; from console or CLI to set up third-party OIDC configuration. There is a blog &lt;a href="https://developer.okta.com/blog/2021/10/08/secure-access-to-aws-eks#add-okta-as-an-oidc-provider-on-your-eks-cluster"&gt;post&lt;/a&gt; from Okta on this, which works for private clusters. In the instruction, the author first manually created kubeconfig file with int128 kubelogin, and then bind ClusterRole with the user.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full is-resized"&gt;&lt;img loading="lazy" decoding="async" width="1021" height="1024" src="https://static.digihunch.com/wp-content/uploads/2023/07/eks-idp.webp" alt="" class="wp-image-12948" style="width:556px;height:auto" srcset="https://static.digihunch.com/wp-content/uploads/2023/07/eks-idp.webp 1021w, https://static.digihunch.com/wp-content/uploads/2023/07/eks-idp-300x300.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/07/eks-idp-150x150.webp 150w, https://static.digihunch.com/wp-content/uploads/2023/07/eks-idp-768x770.webp 768w, https://static.digihunch.com/wp-content/uploads/2023/07/eks-idp-410x410.webp 410w" sizes="auto, (max-width: 1021px) 100vw, 1021px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The blog post is very detailed. Instead of repeating it, I would like to discuss two SSO models available in AWS. I summarize them as 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="721px" viewBox="-0.5 -0.5 721 671" style="max-width:100%;max-height:671px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="0" y="0" width="720" height="670" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&gt;&lt;rect x="30" y="375" width="220" height="235" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/rect&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: 218px; height: 1px; padding-top: 382px; margin-left: 31px;"&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;EKS Cluster&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="140" y="394" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;EKS Cluster&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="60" y="480" width="110" height="40" rx="6" ry="6" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&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: 108px; height: 1px; padding-top: 500px; margin-left: 61px;"&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;RoleBinding&lt;br&gt;ClusterRoleBinding&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="115" y="504" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;RoleBinding&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="440" cy="430" rx="60" ry="25" fill="#ffe6cc" stroke="#d79b00" pointer-events="all"&gt;&lt;/ellipse&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: 430px; 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;OIDC compatible&lt;br&gt;Identity Provider&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="440" y="434" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;OIDC compatible&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="40" width="230" height="220" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/rect&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: 228px; height: 1px; padding-top: 47px; margin-left: 31px;"&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;EKS Cluster&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="145" y="59" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;EKS Cluster&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="620" cy="95" rx="50" ry="25" fill="#ffe6cc" stroke="#d79b00" pointer-events="all"&gt;&lt;/ellipse&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: 95px; margin-left: 571px;"&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;SAML compliant&lt;br&gt;Identity Provider&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="620" y="99" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;SAML compliant&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="280" y="65" width="100" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&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: 95px; margin-left: 281px;"&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;IAM Role via&lt;br&gt;PermissionSet&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="330" y="99" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;IAM Role via&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="410" y="65" width="100" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&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: 95px; margin-left: 411px;"&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;AWS IAM&lt;br&gt;Identity Center&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="460" y="99" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;AWS IAM&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 380 95 L 410 95" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 250 95 L 280 95" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 570 95 L 510 95" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&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: 96px; margin-left: 541px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " 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; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;SAML&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="541" y="99" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;SAML&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="180" y="75" width="70" height="40" rx="6" ry="6" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&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: 95px; margin-left: 181px;"&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;ConfigMap&lt;br&gt;aws-auth&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="215" y="99" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;ConfigMap&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="80" y="75" width="70" height="40" rx="6" ry="6" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&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: 95px; margin-left: 81px;"&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;group&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="115" y="99" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;group&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="80" y="410" width="70" height="40" rx="6" ry="6" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&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: 430px; margin-left: 81px;"&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;group&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="115" y="434" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;group&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="260" width="270" height="30" fill="none" stroke="none" pointer-events="all"&gt;&lt;/rect&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: 268px; height: 1px; padding-top: 275px; margin-left: 31px;"&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;EKS SSO Model 1 &amp;#8211; IAM Identity Center&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="165" y="279" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;EKS SSO Model 1 &amp;#8211; IAM Identity Center&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="50" y="610" width="180" height="30" fill="none" stroke="none" pointer-events="all"&gt;&lt;/rect&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: 178px; height: 1px; padding-top: 625px; 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;EKS SSO Model 2 &amp;#8211; direct OIDC&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="140" y="629" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;EKS SSO Model 2 &amp;#8211; direct OIDC&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 150 430 L 380 430" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&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: 431px; margin-left: 311px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " 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; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;OIDC&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="311" y="434" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;OIDC&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="60" y="140" width="110" height="40" rx="6" ry="6" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&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: 108px; height: 1px; padding-top: 160px; margin-left: 61px;"&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;RoleBinding&lt;br&gt;ClusterRoleBinding&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="115" y="164" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;RoleBinding&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 150 95 L 180 95" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;rect x="80" y="210" width="70" height="40" rx="6" ry="6" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&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: 230px; margin-left: 81px;"&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;Role&lt;br&gt;ClusterRole&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="115" y="234" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Role&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 115 180 L 115 210" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 115 115 L 115 140" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;rect x="60" y="555" width="110" height="40" rx="6" ry="6" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&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: 108px; height: 1px; padding-top: 575px; margin-left: 61px;"&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;Role&lt;br&gt;ClusterRole&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="115" y="579" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Role&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 115 450 L 115 480" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 115 520 L 115 555" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 270 330 L 370 330 L 370 380 L 340 380 L 320 410 L 320 380 L 270 380 Z" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&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: 355px; margin-left: 271px;"&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 claim:&lt;br&gt;user=john&lt;br&gt;group=admin&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="320" y="359" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;OIDC claim:&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="280" y="160" width="100" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"&gt;&lt;/rect&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: 190px; margin-left: 281px;"&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;AssumeRole&lt;br&gt;WithWebIdentity&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="330" y="194" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;AssumeRole&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="480" cy="190" rx="60" ry="20" fill="#ffe6cc" stroke="#d79b00" pointer-events="all"&gt;&lt;/ellipse&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: 190px; margin-left: 421px;"&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;Pipeline&lt;br&gt;User&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="480" y="194" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Pipeline&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 380 190 L 420 190" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 250 115 L 280 190" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&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;Both are SSO models for EKS. Model 1 (IAM Identity Center) is home grown as AWS using IAM Identity Center (formerly AWS SSO). Users start with an IAM principal (AWS construct) and use the &lt;code&gt;aws-auth&lt;/code&gt; config map to tie them to Kubernetes groups. This AWS &lt;a href="https://aws.amazon.com/blogs/containers/a-quick-path-to-amazon-eks-single-sign-on-using-aws-sso/"&gt;blog post &lt;/a&gt;and this &lt;a href="https://repost.aws/knowledge-center/eks-configure-sso-user"&gt;support article&lt;/a&gt; are based on the IAM Identity Center model. On the other hand, Model 2 (Direct OIDC) is the vanilla Kubernetes approach. It takes group claim from OIDC identity token. The Okta blog &lt;a href="https://developer.okta.com/blog/2021/10/08/secure-access-to-aws-eks#add-okta-as-an-oidc-provider-on-your-eks-cluster"&gt;post&lt;/a&gt; is based on this model.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The IAM identity center model works with SAML compliant identity providers, oftentimes Active Directory, although there seems to be a plan to &lt;a href="https://docs.aws.amazon.com/singlesignon/latest/OIDCAPIReference/Welcome.html"&gt;support OIDC-compliant identity provider&lt;/a&gt; as well in the future. Even if it was supported today, I&amp;#8217;d still prefer model 2 for working with any OIDC compliant identity provider because it&amp;#8217;s simple. Why not leverage K8s&amp;#8217; native capability? For identity providers that do not support OIDC natively, or does not issue group claim (e.g. &lt;a href="https://aws.amazon.com/blogs/containers/authenticate-to-amazon-eks-using-google-workspace/"&gt;Google workspace&lt;/a&gt;), as we &lt;a href="https://static.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/"&gt;discussed&lt;/a&gt;, we can also consider alternatives such as Dex as identity broker. However, this model comes handy when a pipeline user with IAM role needs to authenticate into EKS.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;One should always go for the direct OIDC model, if the upstream identity provider supports OIDC. The provider itself can even be AWS Cognito User pool. Cognito User Pool itself supports federated identity but again, I would directly connect EKS cluster to the OIDC compatible identity provider, instead of going through Cognito User Pool. As a result, the only use case where Cognito user pool is absolutely necessary, is when we need the Cognito user pool itself as the identity provider, as we have in &lt;a href="https://github.com/digihunch/cloudkube"&gt;CloudKube&lt;/a&gt;&amp;#8216;s &lt;a href="https://github.com/digihunch/cloudkube"&gt;eks&lt;/a&gt; directory. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-summary"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Configuring OIDC provider for cluster endpoint can be confusing and we need to understand how OIDC flows work. I dived into OIDC in the previous post and in this post, I explained how to get it to work with vanilla Kubernetes. I summarized the three requirements and looked at the OIDC provider option with some managed services. Then I went through OIDC options for AKS, ROSA and EKS.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;OAuth 2.0 and OIDC 2 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2023/08/control-tower-aws-landing-zone/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Orchestrate Landing Zone with AWS Control Tower&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Kubernetes Platform as a Service and Red Hat OpenShift</title><link>https://static.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/</link><pubDate>Sun, 25 Jun 2023 11:10:15 -0400</pubDate><guid>https://static.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-rosa.webp" alt="Featured image of post Kubernetes Platform as a Service and Red Hat OpenShift" /&gt;&lt;h2 class="wp-block-heading"&gt;The Three-layer model&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Kubernetes is so complex that it becomes a buzz word itself. I categorize the related work into three layers: a cluster layer, a platform layer and an application layer, by their purposes. The three layers are illustrated as 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="471px" viewBox="-0.5 -0.5 471 161" style="max-width:100%;max-height:161px;"&gt;&lt;defs&gt;&lt;style type="text/css"&gt;@import url(https://fonts.googleapis.com/css?family=Architects+Daughter);&amp;#xa;&lt;/style&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="0" y="0" width="470" height="160" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/&gt;&lt;rect x="210" y="60" width="130" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"/&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: 80px; margin-left: 211px;"&gt;&lt;div data-drawio-colors="color: #333333; " 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(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Kubernetes Platform&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="84" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Kubernetes Platform&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="210" y="100" width="130" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"/&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: 120px; margin-left: 211px;"&gt;&lt;div data-drawio-colors="color: #333333; " 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(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Kubernetes Cluster&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="124" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Kubernetes Cluster&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="210" y="20" width="130" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"/&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: 40px; margin-left: 211px;"&gt;&lt;div data-drawio-colors="color: #333333; " 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(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Application&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="44" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Application&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 380 60 L 375 60 Q 370 60 370 70 L 370 90 Q 370 100 365 100 L 362.5 100 Q 360 100 365 100 L 367.5 100 Q 370 100 370 110 L 370 130 Q 370 140 375 140 L 380 140" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" transform="translate(370,0)scale(-1,1)translate(-370,0)" pointer-events="all"/&gt;&lt;path d="M 190 100 L 185 100 Q 180 100 180 110 L 180 115 Q 180 120 175 120 L 172.5 120 Q 170 120 175 120 L 177.5 120 Q 180 120 180 130 L 180 135 Q 180 140 185 140 L 190 140" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;rect x="0" y="105" width="170" height="30" fill="none" stroke="none" pointer-events="all"/&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: 168px; height: 1px; padding-top: 120px; margin-left: 1px;"&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;AKS, EKS, self-built cluster&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="124" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;AKS, EKS, self-built cluster&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="375" y="85" width="85" height="30" fill="none" stroke="none" pointer-events="all"/&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: 83px; height: 1px; padding-top: 100px; margin-left: 376px;"&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;ROSA, ARO&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="418" y="104" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;ROSA, ARO&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 190 60 L 185 60 Q 180 60 180 70 L 180 75 Q 180 80 175 80 L 172.5 80 Q 170 80 175 80 L 177.5 80 Q 180 80 180 90 L 180 95 Q 180 100 185 100 L 190 100" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;rect x="10" y="65" width="160" height="30" fill="none" stroke="none" pointer-events="all"/&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: 80px; margin-left: 11px;"&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;OpenShift Container Platform&lt;br /&gt;Self-managed platform&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="90" y="84" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;OpenShift Container Platfo&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/&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;Let&amp;#8217;s examine each layer in this model and where the Kubernetes Platform as a Service fits in.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-the-kubernetes-cluster-layer"&gt;The Kubernetes Cluster Layer&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At the bottom, the Kubernetes Cluster layer is the foundational layer. It focus on using self-hosted VMs or cloud resources to build a functional Kubernetes cluster and worker node groups. A functional cluster includes a highly available control plane, as well as scalable node groups that all communicate with the control plane. Cloud Service Providers like AWS and Azure provides managed Kubernetes service, which takes away the complexity (and flexibility as well) of managing control plane components such as etcd store and API server. The managed services also automatically provisions computing nodes and join them into the cluster. The cluster layer may also involve integration with of CNI and CSI, to ensure Pod-to-Pod communication and available storage classes. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Professionals working at this layer are infrastructure experts who understand networking, storage, as well as how to manage cloud resources or VMs, infrastructure as code. On a daily basis, they deal with VPCs/V-Nets, subnets, EBS/Azure Disk, File storage, EC2/Azure VMs, etc. When the team is doing a bad job at this layer, you might see symptoms like unresponsive cluster API, orphaned worker nodes, or kubectl failing to connect to cluster endpoint.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The tenants (applications) of the Kubernetes platform does not directly interact with this layer. If you decide to switch CSP vendor, this layer requires 100% re-engineering because the managed Kubernetes service by each CSP is different.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;The Kubernetes Platform Layer&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Platform layer sits in the middle. When organization decides to adopt Kubernetes, they often underestimate the efforts required in this layer. This layer works on a functional cluster, without directly interacting with the underlying cloud resources. This layer involves any Kubernetes abstractions that do not creates tangible business value. Rather, this layer is an enabler. It allows the applications to deploy smoothly, evolve quickly, and more importantly, focus on the business.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Teams working on this layer needs to be Kubernetes experts. On a daily basis, they play with common CNCF toolings, such as Prometheus, ArgoCD, Istio, Cilium, Tekton, Open Policy Agent, etc. They are comfortable with Operators, Helm Charts, Ingress, etc. Inside of the Kubernetes cluster, they also manage the foundational services such as Event streaming (e.g. Kafka), PostgreSQL database (e.g. PostgreSQL), software-defined storage (e.g. Ceph), service mesh (e.g. Istio), Authentication (e.g. Keykloak) , etc. These services act as the infrastructure layer to the business workload. If the team is doing a bad job, you would see data loss with database, observability service not populating data, ingress does not process request, etc. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The tenants (application) share services in this layer. If you decide to switch CSP vendor. I estimate 80% of the work at this layer is portable, and 20% requires re-engineering. That is because each CSP offers different external resources, therefor the low level Kubernetes objects in this layer, such as storage classes, load balancers, supported CNIs are different. High level objects such as Kafka remains portable across platforms.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;The Application Layer&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The next layer at the top is application layer. Workloads in this layer are directly linked to the business value. The applications are very diverse. Most of the time, the release team is the main player at this layer. If the organization develops its own application, the software development team also work at this layer. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In terms of knowledge, the members of development team are experts in software engineering, and Software Development Life Cycle (SDLC), etc. On a daily basis, they deal with programming languages, product development, build and release. If they screw up their work, expect business errors, such as orders sent to wrong client, incorrect balance sheet, etc. This team has high visibility in the organization due to its direct link to business value.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This layer of work involves multiple tenants. Each tenant is isolated within their own namespace. When you switch CSP vendor, this layer should be readily portable with minimal effort.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is also worth noting that, with solid platform and cluster layers, the team working at this layer do not write bespoke code for networking, observability, authentication and authorization, encryption and many other aspects not relevant to the core business. Once deployed, the application services are resilient, scale to demands, and cost efficient. This layer reaps the benefits of Kubernetes. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Kubernetes Platform as a Service&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As the Kubernetes dust is still settling, a builder&amp;#8217;s title may not always reflect which layer she or he focuses on. Today it is pretty common for infrastructure engineers to expand their role into the platform layer, or likewise, a software engineer to drill down to the platform layer. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The boundary between platform layer and cluster layer is clear. The cluster layer deals with underlying infrastructure, either in the cloud or on premise. They abstract away the complex infrastructure world from those working with the platform layer. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The boundary between platform layer and application layer is a little tricky to articulate. The application layer focuses on implementing the business logics. The platform layer takes care of the functions that are not part of business logic but essential to the business application. Take an HTTP request for example, application developer should not have to write code to terminate TLS (not part of business logic). They should only write the code to process the HTTP request (business logic). TLS termination is delegated to an Ingress, to be configured by platform builders. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The folks working at the Platform layer needs to interface with both sides. They provide Platform as a Service to the Application teams. However, their work appears mostly invisible in an organization. Their effort is oftentimes underestimated. There are several reasons for that. First, the platform layer does not directly create tangible business value. They are just someone else&amp;#8217;s enabler. Second, their building blocks involve a lot of abstractions by Kubernetes API. Third, the idea of platform engineering is newly emerged. There hasn&amp;#8217;t been a populous recognition of its value.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Red Hat OpenShift Container Platform&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The platform team builds the platform with their choice of open-source tools. For &lt;a href="https://static.digihunch.com/2022/09/build-a-kubernetes-cluster/"&gt;clusters&lt;/a&gt; using OpenShift Kubernetes &lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift/kubernetes-engine"&gt;Engine&lt;/a&gt;, Red Hat introduces Open Shift container platform consisting of Red Hat&amp;#8217;s opinionated (but validated) choice of toolings, for example:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;OpenShift Service Mesh: Istio&lt;/li&gt;&#10;&lt;li&gt;OpenShift Streams: Apache Kafka&lt;/li&gt;&#10;&lt;li&gt;OpenShift GitOps: ArgoCD&lt;/li&gt;&#10;&lt;li&gt;OpenShift Container Platform Pipelines: Tekton&lt;/li&gt;&#10;&lt;li&gt;OpenShift Serverless: Knative&lt;/li&gt;&#10;&lt;li&gt;OpenShift Data Foundation: Ceph&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Clients building their clusters with OpenShift Kubernetes Engine may build their own platform with the toolings in the &lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift"&gt;OpenShift enterprise Kubernetes container platform&lt;/a&gt;. For more services, check out the &lt;a href="https://docs.openshift.com/container-platform/4.13/welcome/index.html"&gt;documentation&lt;/a&gt; for OpenShift Container Platform. For customers with OpenShift Kubernetes &lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift/kubernetes-engine"&gt;Engine&lt;/a&gt;, their options to DIY platform are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Entry-Level: Red Hat OpenShift Kubernetes Engine: Enterprise Kubernetes distribution on RHEL CoreOS&lt;/li&gt;&#10;&lt;li&gt;Mid-Level: Red Hat OpenShift Container Platform (RHOCP):&lt;/li&gt;&#10;&lt;li&gt;Plus-Level: Red Hat OpenShift Platform Plus: RHOCP + advanced cluster management, security, data management essentials, enterprise container registry&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;OpenShift runs the business model of Kubernetes PaaS.This is a unique business model that I do not find a matching competitor. Even if you choose to DIY your own platform, the Red Hat&amp;#8217;s choices are still a great reference. The OpenShift enterprise Kubernetes container platform maps perfectly to the platform layer of the three-layer model, aiming to simplify the work in the platform layer.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Managed RedHat OpenShift&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At first, the OpenShift container platform started as a value add-on to the Kubernetes Engine. Now it&amp;#8217;s a separate product line in their business model. In the mean time, OpenShift partners with major CSPs, to develop the cloud service editions, including:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Red Hat OpenShift on AWS (ROSA)&lt;/li&gt;&#10;&lt;li&gt;Microsoft Azure Red Hat OpenShift (ARO)&lt;/li&gt;&#10;&lt;li&gt;Red Hat OpenShift Dedicated &amp;#8211; on AWS and GCP&lt;/li&gt;&#10;&lt;li&gt;Red Hat OpenShift on IBM Cloud&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These offerings are managed Kubernetes Platform as a Service in the cloud. Since RedHat is the only player in this model, we can refer to them as managed OpenShift services. In addition to an already-confusing world of Kubernetes platform portfolios, these offerings gives consumers even &lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift/openshift-cloud-services"&gt;more options&lt;/a&gt;. On AWS for example, users have the following options:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Managed Platform: OpenShift Dedicated, managed by Red Hat&lt;/li&gt;&#10;&lt;li&gt;Managed Platform: Red Hat OpenShift Service on AWS (ROSA), managed by Red Hat and AWS&lt;/li&gt;&#10;&lt;li&gt;Self-built cluster: OpenShift Container Platform&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://pages.awscloud.com/apn-tv-491.html"&gt;This&lt;/a&gt; video discussed more details about these options, such as support model. It is also worth noting that these options tend to be much pricier than managed clusters such as EKS and AKS.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Since a Managed RedHat Platform makes it easy to deploy, let&amp;#8217;s take ROSA as an example and create a cluster. To enable ROSA in AWS &lt;a href="https://console.aws.amazon.com/rosa/home"&gt;console&lt;/a&gt;, click on &amp;#8220;Getting Started&amp;#8221;. The next page ensures ROSA is enabled and checks other prerequisite such as meeting service quotas and creating ELB service-linked role, as show below:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="912" height="1024" src="https://static.digihunch.com/wp-content/uploads/2023/06/rosa-ui.webp" alt="" class="wp-image-12926" srcset="https://static.digihunch.com/wp-content/uploads/2023/06/rosa-ui.webp 912w, https://static.digihunch.com/wp-content/uploads/2023/06/rosa-ui-267x300.webp 267w, https://static.digihunch.com/wp-content/uploads/2023/06/rosa-ui-768x862.webp 768w" sizes="auto, (max-width: 912px) 100vw, 912px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now, with an AWS account (and ROSA enabled), a RedHat account, and the &lt;a href="https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws/4/html/rosa_cli/rosa-get-started-cli"&gt;rosa-cli&lt;/a&gt; utility, we can create a cluster with just a few commands. As a note, be wary of the cost and do not forget to delete the cluster afterwards.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Create a ROSA cluster&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With the following set of commands, we can kick off cluster creation, using STS. We can bring our own VPC, so long as it meets certain &lt;a href="https://docs.openshift.com/rosa/rosa_planning/rosa-sts-aws-prereqs.html#rosa-vpc_rosa-sts-aws-prereqs"&gt;prerequisites&lt;/a&gt;. I use the Terraform template in the &lt;a href="https://github.com/digihunch/vpc-base"&gt;vpc-base&lt;/a&gt; project, to create the underlying VPC. We&amp;#8217;ll need the followings from this template:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;The CIDR range of the VPC: as &lt;a href="https://github.com/digihunch/cloudkube/blob/9d8667c81fc0989e6e48fba9ed5a87ab761d4044/aws_vpc/variables.tf#L3"&gt;input&lt;/a&gt; with a default&lt;/li&gt;&#10;&lt;li&gt;The subnet Ids of the private subnet to place, printed in the &lt;a href="https://github.com/digihunch/cloudkube/blob/9d8667c81fc0989e6e48fba9ed5a87ab761d4044/aws_vpc/output.tf#L18"&gt;output&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The subnets are private subnets, because we want to provision the cluster with private node and private endpoint. When we use rosa CLI, we provide the CIDR and subnet IDs.&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;&lt;span style="color:#75715e"&gt;# start with AWS cli configured to the correct profile&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa login &lt;span style="color:#75715e"&gt;# with redhat account and past token&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa create account-roles --mode auto -y &lt;span style="color:#75715e"&gt;# this command creates the IAM roles ManagedOpenShift-*-Role, with RedHat account as trust entity&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa verify permissions &lt;span style="color:#75715e"&gt;# optional&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa verify quota &lt;span style="color:#75715e"&gt;# optional&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;export ROSA_CLUSTER_NAME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;dhc&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;OPENSHIFT_VERSION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;4.13.4 &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AWS_ACCOUNT_ID&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;aws sts get-caller-identity --query Account --output text&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AWS_DEFAULT_REGION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;us-east-1&#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;rosa create cluster --sts --private &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --cluster-name $ROSA_CLUSTER_NAME &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --multi-az &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --private-link &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --region $AWS_DEFAULT_REGION &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --version $OPENSHIFT_VERSION &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --enable-autoscaling &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --min-replicas &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --max-replicas &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --compute-machine-type m5.xlarge &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --machine-cidr 147.206.0.0/16 &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --subnet-ids subnet-052852a1fb4d7d2ad,subnet-06d8d40ae39d55c47,subnet-0f67ce08bc588012c&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The CLI will pick up the correct VPC by CIDR, and prompt you to confirm creation of private cluster. After the command kicks off, it will wait for OIDC provider creation, and role creation. Then it uses a Terraform template to create the related resources including VPC. Use this command to check status:&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;rosa list clusters&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa describe cluster -c dhc&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;When the second command displays the state of waiting (Waiting for OIDC configuration), we can create OIDC provider:&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;rosa create operator-roles -c $ROSA_CLUSTER_NAME --mode auto --yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa create oidc-provider -c $ROSA_CLUSTER_NAME --mode auto --yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Throughout the process, we can monitor the install log (terraform output) with:&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;rosa logs install -c dhc --watch&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In the log, you might see errors with terminals connecting to the terraform backend, which doesn’t necessarily indicate a cluster creation error. Always check the cluster state until it reports success. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Kick the tires &lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Eventually the describe cluster command will show ready state. We can now create an admin user:&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;rosa create admin -c $ROSA_CLUSTER_NAME&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command above prints an &lt;code&gt;oc&lt;/code&gt; command (&lt;a href="https://access.redhat.com/documentation/en-us/openshift_container_platform/4.2/html/cli_tools/openshift-cli-oc"&gt;OpenShift CLI&lt;/a&gt;, equivalent to kubectl) with password to log in. Let&amp;#8217;s examine the cluster with oc. Because it is a private cluster, the endpoint is not available publicly. However, it is accessible from the Bastion host. Use the SSM Session Manager technique from my &lt;a href="https://static.digihunch.com/2023/06/connect-kubectl-to-private-kubernetes-cluster-in-eks-and-aks/"&gt;previous post&lt;/a&gt; to SSH to the Bastion Host, which should have &lt;code&gt;oc&lt;/code&gt; installed. To install &lt;code&gt;oc&lt;/code&gt; yourself, use HomeBrew on Mac. On Linux or Windows, log on to &lt;a href="https://console.redhat.com/openshift/downloads"&gt;OpenShift console&lt;/a&gt;, go to Downloads on the left pannel and find it out under CLI tools.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;code&gt;oc&lt;/code&gt; command may report insecure TLS on the login URL. Wait for a few minutes for the certificate to come off as safe. Once you run the &lt;code&gt;oc&lt;/code&gt; command with password, it should return &amp;#8220;Login successful&amp;#8221; and then we can connect to the cluster:&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;$ oc get node &lt;span style="color:#75715e"&gt;# or kubectl get node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME STATUS ROLES AGE VERSION&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-135-41.ec2.internal Ready,SchedulingDisabled infra,worker 3m5s v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-155-141.ec2.internal Ready control-plane,master 25m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-156-81.ec2.internal Ready worker 19m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-164-21.ec2.internal Ready infra,worker 3m3s v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-179-90.ec2.internal Ready worker 19m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-191-118.ec2.internal Ready,SchedulingDisabled control-plane,master 26m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-192-232.ec2.internal Ready worker 19m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-193-198.ec2.internal Ready infra,worker 3m20s v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-218-114.ec2.internal Ready control-plane,master 26m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;You can use oc the same way you&amp;#8217;d use kubectl. Both works through &lt;a href="https://kubernetes.io/docs/tasks/extend-kubernetes/socks5-proxy-access-api/"&gt;SOCK5 proxy.&lt;/a&gt; In the meantime, log in to the &lt;a href="https://console.redhat.com/openshift"&gt;RedHat console&lt;/a&gt; with your Red Hat credential, you can see the cluster in Ready state as well:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="330" src="https://static.digihunch.com/wp-content/uploads/2023/06/openshift-ui.webp" alt="" class="wp-image-12927" srcset="https://static.digihunch.com/wp-content/uploads/2023/06/openshift-ui.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/06/openshift-ui-300x97.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/06/openshift-ui-768x248.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;code&gt;rosa create admin&lt;/code&gt; command creates a &lt;code&gt;htpasswd&lt;/code&gt; type (username-password) of identity provider (IdP) with a user named cluster-admin and a preset password. In real life however, we often configure third party IdP with OIDC integration. I&amp;#8217;ll have to leave this to the &lt;a href="https://static.digihunch.com/2023/07/authenticate-kube-apiserver-via-oidc/"&gt;next blog post&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We shall see the nodes as EC2 instances from AWS console as well. Note that there are three roles: control-plane, worker and infra. The &lt;a href="https://docs.openshift.com/container-platform/4.13/nodes/nodes/nodes-nodes-creating-infrastructure-nodes.html"&gt;infra nodes&lt;/a&gt; are for infrastructure services. These services (Ingress Controller, GitOps, Pipeliens) are the ones in the platform player as we discussed above. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are many &lt;a href="https://access.redhat.com/solutions/6347302"&gt;customizations&lt;/a&gt; you can make in this installation process and I&amp;#8217;d have to defer to the &lt;a href="https://docs.openshift.com/rosa/rosa_planning/rosa-sts-aws-prereqs.html"&gt;ROSA documentation&lt;/a&gt;. To clean up, use the following ROSA 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;rosa remove cluster -c dhc&#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 also gives you the command to delete operator roles and OIDC provider, for 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;rosa delete operator-roles -c 23o4u3j98tqmlbtjo612opb7a4bbim5f --mode auto --yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa delete oidc-provider -c 23o4u3j98tqmlbtjo612opb7a4bbim5f --mode auto --yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we can destroy the VPCs using terraform.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;ROSA with HCP&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Update Oct 2023:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The deployment above provisioned a few nodes for control plane, which add to the overall time to provision a cluster. In Aug 2023, there is a new option Hosted Control Plane (HCP) that came to allow users to provision a hosted control plane. This results in cost savings and shorter time to provision a cluster. &lt;a href="https://docs.aws.amazon.com/ROSA/latest/userguide/rosa-deployment-options.html"&gt;Here&lt;/a&gt; is a table of comparison between the ROSA with HCP and ROSA classic.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Final words&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this post, I discussed the three-layer model and pointed out that platform layer isn&amp;#8217;t as visible as the other two. I also experimented ROSA as a turn-key Kubernetes platform with its opinionated stack of services.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Some misinformed organizations even skip the entire platform layer in their estimate of effort. They build a cluster, ran a hello-world service and assumes they can start putting applications on the Kubernetes cluster. There are also customers who purchased the entire Managed OpenShift platform but only use it as a cluster. Yikes!&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The concept of Kubernetes platform, or generally platform engineering is still spreading. The consulting team that I worked in full-time last year re-branded itself as platform engineering. Marketings are pushing it. Builders are doing it. We&amp;#8217;ll keep an eye, on whether customers are buying it.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2023/06/connect-kubectl-to-private-kubernetes-cluster-in-eks-and-aks/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Connect kubectl to private Kubernetes cluster in EKS and AKS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;OAuth 2.0 and OIDC 2 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>