diff --git a/doc/blueprints/source/_static/images/SCR-20231208-ezg.png b/doc/blueprints/source/_static/images/SCR-20231208-ezg.png new file mode 100644 index 0000000..e271338 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231208-ezg.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231208-fh3.png b/doc/blueprints/source/_static/images/SCR-20231208-fh3.png new file mode 100644 index 0000000..d684888 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231208-fh3.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231208-k2x.png b/doc/blueprints/source/_static/images/SCR-20231208-k2x.png new file mode 100644 index 0000000..c2dcb75 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231208-k2x.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231208-k8t.png b/doc/blueprints/source/_static/images/SCR-20231208-k8t.png new file mode 100644 index 0000000..c309d70 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231208-k8t.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231208-ka7.png b/doc/blueprints/source/_static/images/SCR-20231208-ka7.png new file mode 100644 index 0000000..4a2df80 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231208-ka7.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231211-di1.png b/doc/blueprints/source/_static/images/SCR-20231211-di1.png new file mode 100644 index 0000000..87140d6 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231211-di1.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231211-f5u.png b/doc/blueprints/source/_static/images/SCR-20231211-f5u.png new file mode 100644 index 0000000..64a0792 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231211-f5u.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231211-ffb.png b/doc/blueprints/source/_static/images/SCR-20231211-ffb.png new file mode 100644 index 0000000..66cb296 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231211-ffb.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231211-fj8.png b/doc/blueprints/source/_static/images/SCR-20231211-fj8.png new file mode 100644 index 0000000..9f91553 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231211-fj8.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231211-fp6.png b/doc/blueprints/source/_static/images/SCR-20231211-fp6.png new file mode 100644 index 0000000..715d0e9 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231211-fp6.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231211-g7y.png b/doc/blueprints/source/_static/images/SCR-20231211-g7y.png new file mode 100644 index 0000000..8e6935d Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231211-g7y.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231211-i88.png b/doc/blueprints/source/_static/images/SCR-20231211-i88.png new file mode 100644 index 0000000..1b980b4 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231211-i88.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231211-ni4.png b/doc/blueprints/source/_static/images/SCR-20231211-ni4.png new file mode 100644 index 0000000..ffaf6ce Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231211-ni4.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-df8.png b/doc/blueprints/source/_static/images/SCR-20231212-df8.png new file mode 100644 index 0000000..ad58e7f Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-df8.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-dfp.png b/doc/blueprints/source/_static/images/SCR-20231212-dfp.png new file mode 100644 index 0000000..1d41c0e Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-dfp.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-dsj.png b/doc/blueprints/source/_static/images/SCR-20231212-dsj.png new file mode 100644 index 0000000..565b300 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-dsj.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-fhq.png b/doc/blueprints/source/_static/images/SCR-20231212-fhq.png new file mode 100644 index 0000000..cd95f91 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-fhq.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-mfl.png b/doc/blueprints/source/_static/images/SCR-20231212-mfl.png new file mode 100644 index 0000000..8750c9c Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-mfl.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-mmx.png b/doc/blueprints/source/_static/images/SCR-20231212-mmx.png new file mode 100644 index 0000000..7221942 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-mmx.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-mr5.png b/doc/blueprints/source/_static/images/SCR-20231212-mr5.png new file mode 100644 index 0000000..b98f3fe Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-mr5.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-n0d.png b/doc/blueprints/source/_static/images/SCR-20231212-n0d.png new file mode 100644 index 0000000..b0ee85a Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-n0d.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-n0n.png b/doc/blueprints/source/_static/images/SCR-20231212-n0n.png new file mode 100644 index 0000000..13043dd Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-n0n.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-n15.png b/doc/blueprints/source/_static/images/SCR-20231212-n15.png new file mode 100644 index 0000000..00d884c Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-n15.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-n1w.png b/doc/blueprints/source/_static/images/SCR-20231212-n1w.png new file mode 100644 index 0000000..1074b2b Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-n1w.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-n8b.png b/doc/blueprints/source/_static/images/SCR-20231212-n8b.png new file mode 100644 index 0000000..625ec41 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-n8b.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-ngd.png b/doc/blueprints/source/_static/images/SCR-20231212-ngd.png new file mode 100644 index 0000000..8e39230 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-ngd.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-nj4.png b/doc/blueprints/source/_static/images/SCR-20231212-nj4.png new file mode 100644 index 0000000..824e9b2 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-nj4.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-nq7.png b/doc/blueprints/source/_static/images/SCR-20231212-nq7.png new file mode 100644 index 0000000..d54fe61 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-nq7.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-nw9.png b/doc/blueprints/source/_static/images/SCR-20231212-nw9.png new file mode 100644 index 0000000..d001c5a Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-nw9.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-o7i.png b/doc/blueprints/source/_static/images/SCR-20231212-o7i.png new file mode 100644 index 0000000..464998f Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-o7i.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20231212-och.png b/doc/blueprints/source/_static/images/SCR-20231212-och.png new file mode 100644 index 0000000..41a447b Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20231212-och.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20240122-k0w.png b/doc/blueprints/source/_static/images/SCR-20240122-k0w.png new file mode 100644 index 0000000..b444b1a Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20240122-k0w.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20240122-k1y.png b/doc/blueprints/source/_static/images/SCR-20240122-k1y.png new file mode 100644 index 0000000..b8dbf95 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20240122-k1y.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20240122-k3x.png b/doc/blueprints/source/_static/images/SCR-20240122-k3x.png new file mode 100644 index 0000000..fa2020e Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20240122-k3x.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20240122-k51.png b/doc/blueprints/source/_static/images/SCR-20240122-k51.png new file mode 100644 index 0000000..ec7eb72 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20240122-k51.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20240122-k68.png b/doc/blueprints/source/_static/images/SCR-20240122-k68.png new file mode 100644 index 0000000..2417a22 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20240122-k68.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20240122-k76.png b/doc/blueprints/source/_static/images/SCR-20240122-k76.png new file mode 100644 index 0000000..737241c Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20240122-k76.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20240201-erg.png b/doc/blueprints/source/_static/images/SCR-20240201-erg.png new file mode 100644 index 0000000..4899415 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20240201-erg.png differ diff --git a/doc/blueprints/source/_static/images/SCR-20240201-k76.png b/doc/blueprints/source/_static/images/SCR-20240201-k76.png new file mode 100644 index 0000000..1a2fc03 Binary files /dev/null and b/doc/blueprints/source/_static/images/SCR-20240201-k76.png differ diff --git a/doc/blueprints/source/best-practices/security/deploy_keycloak.rst b/doc/blueprints/source/best-practices/security/deploy_keycloak.rst new file mode 100755 index 0000000..b76630b --- /dev/null +++ b/doc/blueprints/source/best-practices/security/deploy_keycloak.rst @@ -0,0 +1,592 @@ +.. _my-reference-label: deploy_keycloak + +.. meta:: + :description: Deploy Keycloak on an Open Telekom Cloud CCE Cluster + :keywords: keycloak, open telekom cloud, cce, identity federation, cce, kubernetes, rds, postgresql, externaldns + +====================== +Deploy Keycloak on CCE +====================== + +.. Overview + +Overview +======== + +Keycloak is an open-source identity and access management (IAM) solution developed by Red Hat. It provides features for +single sign-on (SSO), user authentication, authorization, and identity brokering. Keycloak aims to simplify the +implementation of authentication and authorization mechanisms in applications by offering a centralized and configurable +platform. + +Key features of Keycloak include: + +1. **Single Sign-On (SSO):** Keycloak enables users to log in once and gain access to multiple applications without the need to re-enter credentials for each application. +2. **Identity Federation:** It supports identity brokering, allowing users to log in with existing accounts from social networks (such as Google, Facebook, or GitHub) or other identity providers. +3. **User Authentication:** Keycloak provides a variety of authentication mechanisms, including username and password, multi-factor authentication, and support for external identity providers. +4. **Authorization Services:** It includes fine-grained access control and authorization policies to manage what users can and cannot do within applications. +5. **User Account Management:** Keycloak offers user self-registration, password reset, and other account management features. +6. **LDAP and Active Directory Integration:** It supports integration with LDAP (Lightweight Directory Access Protocol) and Microsoft Active Directory for seamless user management. +7. **Client Adapters:** Keycloak provides client adapters for various platforms and languages, making it easier to integrate with applications built using different technologies. +8. **Security and Compliance:** Keycloak follows best practices for security and compliance, including support for OAuth 2.0 and OpenID Connect standards. + +Developers can integrate Keycloak with their applications using various protocols such as OpenID Connect, OAuth 2.0, +SAML (Security Assertion Markup Language), and more. It is commonly used in microservices architectures and +distributed systems to manage authentication and authorization in a centralized manner. Keycloak is often employed in +scenarios where secure user authentication and access control are crucial, such as enterprise applications, +web applications, and APIs. + +In this blueprint, we are going to discuss the steps to install Keycloak, in Open Telekom Cloud, on a CCE Cluster. + + +.. Main Article + +.. Components + +.. Sections 1..n + +Create a VPC and a Subnet +========================= + +We are going to need a Virtual Private Cloud (VPC) and at least one Subnet where we are going +to provision both RDS instances and CCE nodes. For enhanced security granularity, we could split +those resources in two different Subnets. + +.. image:: /_static/images/SCR-20231208-ezg.png + +.. warning:: RDS and CCE nodes have to be on the same VPC. + +Deploy a PostgreSQL with RDS +============================ + +Keycloak, as a stateful workload, requires the presence of a persistent storage in order to +maintain its data and configuration during pod restarts. We could deploy a PostgreSQL database +as a CCE workload, but this would require additional administrative overhead from your side. +The Managed Relational Database Service of Open Telekom Cloud is a perfect fit for this scenario. +A scalable turn-key solution, that fully integrated with the rest of managed services of the platform +without demanding from the consumer additional administrative effort. + +Create Security Groups +++++++++++++++++++++++ + +We are going to need two different Security Groups. One for the RDS nodes, so it can accept client calls +on port ``5432`` (Inbound Rules), which they only reside in the same Subnet (in case we went for a single Subnet solution): + +.. image:: /_static/images/SCR-20231208-fh3.png + +| + +And one Security Group for the client nodes that need to access the database (Outbound Rules), in our case those would +be the CCE nodes where Keycloak is going to be installed on. + +.. image:: /_static/images/SCR-20231208-k2x.png + +Provision a Database +++++++++++++++++++++ + +Now as next, we need to provision a PostgreSQL 14 database. Pick the instance and storage class size that fit your needs: + +.. image:: /_static/images/SCR-20231208-k8t.png + +| + +and make sure that you: + +- you place the RDS nodes in the same VPC with the CCE nodes +- assign ``rds-instances`` as the Security Group for the RDS nodes + +.. image:: /_static/images/SCR-20231208-ka7.png + +Create a Private DNS Zone ++++++++++++++++++++++++++ + +We are provisioning PostgreSQL in order to support the functionality of Keycloak. For that matter, although Open Telekom +Cloud employs this RDS instance with a floating IP address, it would be better that we connect the RDS instance with +Keycloak via a fully qualified domain name and let the Open Telekom Cloud's DNS service to manage the resolution of that +endpoints. In the Domain Name Service management panel click Private Zone and create a new one that points to the VPC +that CCE and RDS nodes are placed: + +.. image:: /_static/images/SCR-20231211-f5u.png + +| + +and then click Manage Record Set to add a new **A Record** to this zone: + +.. image:: /_static/images/SCR-20231211-ffb.png + +| + +.. note:: The domain name, will be a fictitious domain representing your solution and not a public one. It can be + virtually any domain or subdomain that conforms to the a FQDN rules. + +| + +The floating IP of the RDS instance can be found in the Basic Information panel of the database: + +.. image:: /_static/images/SCR-20231211-fj8.png + + +Provision a CCE Cluster +======================= + +We are going to need a CCE Cluster. In order to provision one, you can follow the configuration steps of the wizard +paying attention to the following details: + +- We are not going to need an HA cluster - of course adjust to your needs because this is not something you can + change in the future. +- We need to provision the CCE Cluster in the same VPC as the RDS nodes. +- If you follow the single Subnet lab instructions make sure you place the CCE Nodes in the same Subnet that RDS nodes + reside. + +| + +.. image:: /_static/images/SCR-20231211-fp6.png + +| + +Add worker nodes to the CCE cluster using the wizard, and wait all nodes to become operational. Then add to **each** node +an additional Security Group, in particular the ``rds-client`` that we created earlier in this lab. + +.. image:: /_static/images/SCR-20231211-g7y.png + +.. note:: Make your own decision how you're going to access this CCE Cluster afterwards. You can assign an Elastic + IP Address and access it over the Internet or provision and additional public-facing bastion host and access + it through this machine. **We categorically recommend the latter**. + +Deploy Keycloak +=============== + +We are going to deploy Keycloak using simple Kubernetes manifests. Deploy those YAML manifests in the order described +below using the command on your bastion host (or in any other machine if you chose to go for an EIP): + +.. code-block:: yaml + + kubectl apply -f <> + +Deploy Keycloak Secrets ++++++++++++++++++++++++ + +First we are going to need a Namespace in our CCE Cluster, in order to deploy all the resources required by Keycloak: + +.. code :: shell + + kubectl create namespace keycloak + +We are going to need two Secrets. One, ``postgres-credentials``, that will contain the credentials to access the PostgreSQL +database instance and a second one, ``keycloak-secrets``, that will contain the necessary credential to access the web +console of Keycloak: + +.. code-block:: yaml + :linenos: + :emphasize-lines: 9,20 + + apiVersion: v1 + kind: Secret + metadata: + name: postgres-credentials + namespace: keycloak + type: Opaque + stringData: + POSTGRES_USER: root + POSTGRES_PASSWORD: <> + POSTGRES_DB: postgres + --- + apiVersion: v1 + kind: Secret + metadata: + name: keycloak-secrets + namespace: keycloak + type: Opaque + stringData: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: <> + +.. note:: ``POSTGRES_PASSWORD`` is the password for the ``root`` user your provided during the creation of the RDS instance. + +``KEYCLOAK_ADMIN_PASSWORD``, as we mentioned before, is the password for the ``admin`` user of the Keycloak web console. +You can easily create random strong passwords, in Linux terminal, with the following command: + +.. code-block:: shell + + openssl rand -base64 14 + +Deploy Keycloak Application ++++++++++++++++++++++++++++ + +Next step, is deploying Keycloak itself: + +.. code-block:: yaml + :linenos: + :emphasize-lines: 23,26,27,31,32,48,49,50,51,55,56,60,61 + + apiVersion: apps/v1 + kind: Deployment + metadata: + name: keycloak + namespace: keycloak + labels: + app: keycloak + spec: + replicas: 1 + selector: + matchLabels: + app: keycloak + template: + metadata: + labels: + app: keycloak + spec: + containers: + - name: keycloak + image: quay.io/keycloak/keycloak:21.0.2 + args: ["start-dev"] + env: + - name: KEYCLOAK_ADMIN + valueFrom: + secretKeyRef: + key: KEYCLOAK_ADMIN + name: keycloak-secrets + - name: KEYCLOAK_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + key: KEYCLOAK_ADMIN_PASSWORD + name: keycloak-secrets + - name: KC_PROXY + value: "edge" + - name: KC_HEALTH_ENABLED + value: "true" + - name: KC_METRICS_ENABLED + value: "true" + - name: KC_HOSTNAME_STRICT_HTTPS + value: "true" + - name: KC_LOG_LEVEL + value: INFO + - name: KC_DB + value: postgres + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: postgres-credentials + key: POSTGRES_DB + - name: KC_DB_URL + value: jdbc:postgresql://postgresql.blueprints.arc:5432/$(POSTGRES_DB) + - name: KC_DB_USERNAME + valueFrom: + secretKeyRef: + name: postgres-credentials + key: POSTGRES_USER + - name: KC_DB_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-credentials + key: POSTGRES_PASSWORD + ports: + - name: http + containerPort: 8080 + readinessProbe: + httpGet: + path: /health/ready + port: 8080 + initialDelaySeconds: 250 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health/live + port: 8080 + initialDelaySeconds: 500 + periodSeconds: 30 + resources: + limits: + memory: 512Mi + cpu: "1" + requests: + memory: 256Mi + cpu: "0.2" + +As you will notice in the highlighted lines, we parameterize the credentials portion of this manifest by referencing +the variables and their values we installed in the previous step with the Secrets. Important to mention the significance +of line 51, where we connect Keycloak with the RDS instance using the FQDN we created in our Private DNS Zone for this +instance. + +Deploy Keycloak Service ++++++++++++++++++++++++ + +We deployed the application, but at the time being is not accessible by an internal or external actor (direct access +from Pods does not count in this case). For that matter, we need to deploy a Service that will expose Keycloak's +workload: + +.. code-block:: yaml + :linenos: + :emphasize-lines: 15 + + apiVersion: v1 + kind: Service + metadata: + name: keycloak + namespace: keycloak + labels: + app: keycloak + spec: + ports: + - name: https + port: 443 + targetPort: 8080 + selector: + app: keycloak + type: NodePort + +.. note:: Pay attention to **line 15**, where we set the ``type`` as ``NodePort``. That's because we want to expose + this service externally, in a later step, via an Ingress. + +Expose Keycloak +=============== + +.. image:: /_static/images/SCR-20231211-di1.png + + +Create an Elastic Load Balancer ++++++++++++++++++++++++++++++++ + +First in our list for this part, is to create an Elastic Load Balancer that will be employed with the following: + +- An EIP address +- Support L4 and L7 load balancing +- Be in the same VPC/Subnet as the nodes of our CCE Cluster +- Associate backend servers by using their IP addresses (*IP as Backend*) + +.. image:: /_static/images/SCR-20231211-i88.png + +.. note:: Note down the **ELB ID**, we are going to need it to configure the Nginx Ingress that we will deploy next. + +Deploy Nginx Ingress on CCE ++++++++++++++++++++++++++++ + +We are going to deploy in this step the Ingress that will sit between our ELB and the Keycloak Service and expose it +in the address of our preference (keycloak.example.com for this lab) + +.. warning:: Do not forget that the FQDN we are going to use to expose the Keycloak Service has to point to a **real** domain or + subdomain that you actually **own**! + +We will use `Helm `_ to deploy Nginx Ingress to our CCE Cluster. Helm is the de-facto package manager +of Kubernetes and if you don't have it already installed on your remote machine or your bastion host, you can do it with +the following commands: + +.. code-block:: shell + + curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 + chmod 700 get_helm.sh + ./get_helm.sh + +We have to provide to the helm chart a couple configuration values (``overrides.yaml``), among them the internal ID +of the Elastic Load Balancer is the most important - as it will bind the future ingresses that will be created using +this ingress class with the specific load balancer. + +.. code-block:: yaml + :linenos: + :emphasize-lines: 6 + + controller: + replicaCount: 1 + service: + externalTrafficPolicy: Cluster + annotations: + kubernetes.io/elb.id: "0000000-0000-0000-0000-000000000000" + +.. note:: Special attention required at **line 6**, replace the placeholder value with the ID you copied from the + main panel of your newly created Elastic Load Balancer. + +We can now install the chart (it will automatically create and deploy everything in a namespace named ``nginx-system``): + +.. code-block:: shell + + helm upgrade --install -f overrides.yaml --install ingress-nginx ingress-nginx \ + --repo https://kubernetes.github.io/ingress-nginx \ + --namespace nginx-system --create-namespace + +Create a Public DNS Endpoint +++++++++++++++++++++++++++++ + +As we will see later, when we will reach to the point that we are ready to register this Keycloak installation as an +Identity Provider (IdP) in our Open Telekom Cloud tenant, it is really pertinent that the EIP of our ELB resolves to a +real, secure URL address: + +.. image:: /_static/images/SCR-20231211-ni4.png + +| + +In order to accomplish that, we have to transfer the management of the NS-Records of your domain to the Domain Name Service +of Open Telekom Cloud. Go on the site of your registar and make sure you configure the following: + +- Turn off any dynamic dns service for the domain or the subdomains you are going to bind with Keycloak. +- Change the NS-Records of your domain to point to: ``ns1.open-telekom-cloud.com`` **and** ``ns2.open-telekom-cloud.com`` + +If those two prerequisites are met, then you are ready to configure a new DNS Public Zone and Record Sets for your +domain in Open Telekom Cloud. We do have two mutually exclusive options to do that: + +- Create manually from Open Telekom Cloud Console, a new Public DNS Zone that binds to your domain and an A-Record + in that zone that points to the EIP of the external load balancer. +- Automate everything using `ExternalDNS `_. + +Create the Endpoint manually +---------------------------- + +Follow the same steps we did earlier for the Private Zone, but this time create a Public Zone targeting to your domain +and add an A-Record that binds your Keycloak's (sub)domain with the Elastic IP Address of the Elastic Load Balancer. + +Create the Endpoint with ExternalDNS +------------------------------------ + +What is ExternalDNS? Quoting directly from the official repo of the project: + +*Inspired by Kubernetes DNS, Kubernetes' cluster-internal DNS server, ExternalDNS makes Kubernetes resources discoverable +via public DNS servers. Like KubeDNS, it retrieves a list of resources (Services, Ingresses, etc.) from the Kubernetes +API to determine a desired list of DNS records. Unlike KubeDNS, however, it's not a DNS server itself, +but merely configures other DNS providers accordingly—e.g. AWS Route 53 or Google Cloud DNS.* + +*In a broader sense, ExternalDNS allows you to control DNS records dynamically via Kubernetes resources in a +DNS provider-agnostic way.* + +Deploy ExternalDNS on CCE +````````````````````````` +We are going to deploy ExternalDNS with Helm as well. First let's lay down the configuration of the chart in a file +name ``overrides.yaml``: + +.. code-block:: yaml + :linenos: + :emphasize-lines: 11,13-14 + + sources: + - crd + - service + - ingress + provider: designate + combineFQDNAnnotation: true + crd: + create: true + logFormat: json + designate: + username: "OTCAC_DNS_ServiceAccount" + password: <> + authUrl: "https://iam.eu-de.otc.t-systems.com:443/v3" + regionName: "eu-de" + userDomainName: "OTCXXXXXXXXXXXXXXXXXXXX" + projectName: "eu-de_XXXXXXXXXXX" + +.. warning:: Special attention required at **lines 13,14**. Although DNS is a global service, **all** changes have to + be applied in Region **eu-de**. + +Install the chart (it will deploy all the necessary resources in an automatically created namespace called +``external-dns``: + +.. code-block:: shell + + helm repo add bitnami https://charts.bitnami.com/bitnami + helm repo update + + helm upgrade --install -f overrides.yaml external-dns bitnami/external-dns -n external-dns --create-namespace + +| + +Create a dedicated DNS Service Account +`````````````````````````````````````` + +.. note:: This is required **only** when ExternalDNS is used. + +Go to IAM management console, and create a new User that permits programmatic access to Open Telekom Cloud resources: + +.. image:: /_static/images/SCR-20231212-dfp.png + +| + +Grant this User the following permissions or add him directly to User Group ``dns-admins`` (if it exists) + +.. image:: /_static/images/SCR-20231212-df8.png + +| + +Deploy a Keycloak Endpoint +`````````````````````````` + +We have now laid all the groundwork in order to automatically provision a Public DNS Zone and a dedicated A-Record that +will bind the EIP of our ELB with Keycloak's subdomain FQDN. For that matter we need to install a Custom Resource based +on a CRD installed by ExternalDNS that is called ``DNSEndpoint``: + +.. code-block:: yaml + :linenos: + :emphasize-lines: 8, 12 + + apiVersion: externaldns.k8s.io/v1alpha1 + kind: DNSEndpoint + metadata: + name: keycloak + namespace: keycloak + spec: + endpoints: + - dnsName: keycloak.example.de + recordTTL: 300 + recordType: A + targets: + - XXX.XXX.XXX.XXX + +.. note:: At line 12, replace the placeholder with the Elastic IP Address that is assigned to your Elastic Load Balancer. + At line 8, replace the (sub)domain with the one of yours + +Wait for a couple of seconds, till the reconciliation loop of the ExternalDNS controller is done, and if all went well +you should now see the Record Sets of your Public Zone populated with various entries: + +.. image:: /_static/images/SCR-20231212-dsj.png + +| + +Deploy Keycloak Ingress ++++++++++++++++++++++++ + +And finally, the last step of this lab is to deploy an ingress for the Keycloak Service: + +.. code-block:: yaml + :linenos: + + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: keycloak-ingress + namespace: keycloak + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / + kubernetes.io/ingress.class: nginx + spec: + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: keycloak + port: + number: 443 + +We can now open the url address we defined in our Public DNS Zone for this application and land on the welcome +page of Keycloak: + +.. image:: /_static/images/SCR-20231212-fhq.png + +.. Next steps & Related Resources + +Next Steps +========== + +.. seealso:: + + - `Identity Federation through Keycloak/GitHub `_ + - `Identity Federation through Keycloak/Microsoft Active Directory `_ + + +Resources +========= + +.. Resources + +.. seealso:: + + - `GitHub repo `_ + diff --git a/doc/blueprints/source/best-practices/security/index.rst b/doc/blueprints/source/best-practices/security/index.rst index 3a38ea9..6975fdb 100644 --- a/doc/blueprints/source/best-practices/security/index.rst +++ b/doc/blueprints/source/best-practices/security/index.rst @@ -12,6 +12,8 @@ critical infrastructure. .. toctree:: :maxdepth: 1 - cce_vault + cce_vault.rst + deploy_keycloak.rst + diff --git a/doc/blueprints/source/use-cases/security/index.rst b/doc/blueprints/source/use-cases/security/index.rst index 12880c2..26dde61 100644 --- a/doc/blueprints/source/use-cases/security/index.rst +++ b/doc/blueprints/source/use-cases/security/index.rst @@ -8,3 +8,5 @@ regulatory adherence, empowering users to design and deploy secure, compliant so .. toctree:: :maxdepth: 1 + + keycloak_github.rst \ No newline at end of file diff --git a/doc/blueprints/source/use-cases/security/keycloak_github.rst b/doc/blueprints/source/use-cases/security/keycloak_github.rst new file mode 100755 index 0000000..ea9379e --- /dev/null +++ b/doc/blueprints/source/use-cases/security/keycloak_github.rst @@ -0,0 +1,291 @@ +.. meta:: + :description: Deploy Keycloak on an Open Telekom Cloud CCE Cluster + :keywords: keycloak, open telekom cloud, cce, identity federation, cce, kubernetes, github + +=========================================== +Identity Federation through Keycloak/GitHub +=========================================== + +.. Overview + +Overview +======== + +Identity Federation in Keycloak refers to the ability to use external identity providers to authenticate users in your +application. In this context, GitHub can be used as an identity provider, allowing users to log in to your +Open Telekom Cloud tenant using their GitHub credentials. Users can choose to log in with their GitHub accounts and +Keycloak takes care of the authentication process, providing a seamless experience for users while ensuring security +and centralized identity management for external accounts that are not actively managed in your tenant's IAM. + +.. Main Article + +.. Components + +Prerequisites ++++++++++++++ + +For this lab, you are going to need a: + +#. **Keycloak** server: You should have a Keycloak server instance set up and running +#. **GitHub** account: You need a GitHub account to register your application and obtain client ID and secret + +.. Sections 1..n + + +Deploy Keycloak +=============== + +You can follow this blueprint to setup a working instance of Keycloak on CCE: +:ref: `deploy_keycloak`. + +Configure Keycloak & IAM +======================== + +Create a new Realm +++++++++++++++++++ + +A realm manages users, credentials, roles, and groups. A user belongs to and logs into the realm he is assigned to. +Realms are isolated from one another and can manage and authenticate only those users that they belong to them. + +Open and login to your Keycloak instance. Create a new realm (let's call it ``otcac_test_company_1`` for the course of +this blueprint) and mark it as enabled: + +.. image:: /_static/images/SCR-20231212-mfl.png + +| + +Create a new Client ++++++++++++++++++++ + +Clients are applications, or services, that can request the authentication of a user. Create a new client (let's call it +``otcac_test_company_1_client`` with type ``OpenID Connect`` and in the *Capability config* step of the wizard, activate the following Authentication +flows: + +- Standard flow +- Implicit flow +- Direct access grants + +.. image:: /_static/images/SCR-20231212-mmx.png + +| + +Configure Mappers ++++++++++++++++++ + +Open the management console of the Client you just created, and navigate to the *Client scopes* tab. Click on the list +item with the name: ``otcac_test_company_1_client-dedicated``: + +.. image:: /_static/images/SCR-20231212-mr5.png + +| + +Now we need to add some mappers. We will first add one of the predefined ones: + +.. image:: /_static/images/SCR-20231212-n1w.png + +| + +and from the list choose ``email``: + +.. image:: /_static/images/SCR-20231212-n0d.png + +| + +Next we need to add a group membership mapper. Click *Add mapper/By Configuration*: + +.. image:: /_static/images/SCR-20231212-n0n.png + +and from the list choose ``Group Membership``: + +.. image:: /_static/images/SCR-20231212-n15.png + +| + +Open the configuration of the mapper. Insert a mapper and token name as ``gruppen``. The token name will be used in the +OTC Conversion Rules. Disable the `Full group path` option: + +.. image:: /_static/images/SCR-20231212-n8b.png + +| + +Get OpenID Endpoint Configuration ++++++++++++++++++++++++++++++++++ + +Open `Realm Settings` and click on `OpenID Endpoint Configuration`: + +.. image:: /_static/images/SCR-20231212-nj4.png + +| + +You will be redirected to web page rendering, as JSON, all the endpoints and the current configuration of your realm: + +.. image:: /_static/images/SCR-20231212-ngd.png + +| + +.. note:: It is recommended to keep this web page open in a separate tab or screen, because we are going to need to + grab some values from it, for our the next steps. + + +Create a new IAM Identity Provider +++++++++++++++++++++++++++++++++++ + +For this step we will change to Open Telekom Cloud Console and particularly to IAM and Identity Providers. Create a new +one, and set `Protocol` to ``OpenID Connect``, `SSO Type` to ``Virtual User`` and `Status` to ``Enabled``: + +.. image:: /_static/images/SCR-20231212-nq7.png + +| + +Configure the IAM Identity Provider ++++++++++++++++++++++++++++++++++++ + +Find your newly created provider in Identity Providers list and click `Modify`: + +.. image:: /_static/images/SCR-20231212-nw9.png + +| + +Set the following values: + +- `Access Type`: ``Programmatic access and management console access`` +- `Client ID`: The id of your client as defined in Keycloak (in this example is ``otcac_test_company_1_client``) +- `Authorization Endpoint`: copy the value from key **authorization_endpoint** of the `OpenID Endpoint Configuration` JSON output +- `Response Mode`: ``form_post`` +- `Signing Key`: open in a new tab the URL address that is value of the key **jwks_uri** of the `OpenID Endpoint Configuration` JSON output. Copy the whole output of the new page and paste it as is in the respective textbox for `Signing Key`. + + +.. image:: /_static/images/SCR-20231212-o7i.png + +| + +Save the changes, **but before closing this panel copy the value** of the `Identity Provider URL` because we are going to +need this value in the next step of this blueprint. + +Configure Client's Access Settings +++++++++++++++++++++++++++++++++++ + +For this step we will switch back to Keycloak Administration Console, and navigate to `Access Settings` for our client: + +.. image:: /_static/images/SCR-20231212-och.png + +| + +Set the following values: + +- `Root URL`: The `Identity Provider URL` you copied in the previous step. +- `Home URL`: ``https://auth.otc.t-systems.com`` +- `Valid redirect URIs`: ``https://auth.otc.t-systems.com/authui/oidc/post`` + +GitHub Integration +================== + +Add GitHub as Identity Provider ++++++++++++++++++++++++++++++++ + +Then we have to add a new Identity Provider that will allow users to authenticate using their GitHub accounts: + +.. image:: /_static/images/SCR-20240122-k3x.png + +Enable the provider and copy the `Redirect URI` because we are going to need in the next step, that will interconnect +this Keycloak realm with a GitHub OAuth application. + +.. image:: /_static/images/SCR-20240201-k76.png + +Create new GitHub OAuth App ++++++++++++++++++++++++++++ + +Open your GitHub account and find *OAuth Apps* under *Settings/Developer Settings* and create a new app: + +.. image:: /_static/images/SCR-20240122-k0w.png + +and set the following values: + +- `Homepage URL`: ``https://auth.otc.t-systems.com`` +- `Authorization call back URL`: the **Redirect URI** we picked up from the previous step + +.. image:: /_static/images/SCR-20240122-k68.png + +| + +Last piece of creating an OAuth App is to generate a client secret: + +.. image:: /_static/images/SCR-20240122-k76.png + +.. note:: Make immediately a copy of the client secret value. We are going to need it (along with the *Client ID* of the app) + during our next step and additionally that is the last time that it will be visible on the GitHub console. + +Configure GitHub Identity Provider +++++++++++++++++++++++++++++++++++ + +Next, let's return back to the configuration panel of our newly created GitHub Identity Provider in Keycloak, and set +the following values: + +.. image:: /_static/images/SCR-20240122-k1y.png + +| + +- `Client ID`: the **Client ID** of the GitHub OAUth app we just created +- `Client Secret`: the **Client Secret** of the GitHub OAUth app + +Configure the IAM Identity Provider Conversion Rules +==================================================== + +By default federated users are named *FederationUser* in the Open Telekom Cloud platform. These users can only log in to +the cloud platform and they do not have **any** other permissions. You can configure identity conversion rules on the +IAM console to achieve the following: + +- Display enterprise users with different names in the cloud platform. +- Assign permissions to enterprise users to use the cloud platform resources by mapping these users to IAM user groups. + Ensure that you have created the required user groups. + +This can be achieved by editing the Identity Conversion Rules under IAM/Identity Providers: + +.. image:: /_static/images/SCR-20240201-erg.png + +| + +Paste the following conversion rule in the *Edit Rule* panel: + +.. code-block:: json + :linenos: + + [ + { + "remote": [ + { + "type": "email" + }, + { + "type": "gruppen" + }], + "local": [ + { + "user": { + "name": "{0}" + } + }, + { + "groups": "{1}" + }] + } + ] + +The *remote* part describes the *Predefined Mappers* (``email`` and ``gruppen``) we created in KeyCloak Client's configuration. +The *local* part defines the mapping between the remote properties and the OTC account. The user will get as ``name`` +the the value of ``remote.email`` and will automatically belong to the ``groups`` defined in ``remote.gruppen``. + +.. warning:: Bear in mind, that we have to create those OTC groups on before hands so they match 1-1 name-wise in order + the mapping to work and our federated user to get the desired permissions. + +Resources +========= + +.. Resources + +.. seealso:: + + - `Configure Identity Conversion Rules `_ + - `Syntax of Identity Conversion Rules `_ + +