============================================
Kubernetes VIM OpenID Token Auth Usage Guide
============================================

Overview
--------
Kubernetes has multiple authentication strategies. This document describes
how Tacker use `OpenID token`_ to authenticate with Kubernetes.

The OpenID token authentication of Kubernetes relies on an external OpenID
provider, and `Keycloak`_ acts as the OpenID provider in this document.

Preparation
-----------

Prerequisites
~~~~~~~~~~~~~

The following packages should be installed:

* Kubernetes
* Docker

Start Keycloak
~~~~~~~~~~~~~~

Create a CSR config file ``csr.cnf``:

.. code-block:: cfg

   [req]
   req_extensions = v3_req
   distinguished_name = req_distinguished_name
   [req_distinguished_name]

   [ v3_req ]
   basicConstraints = CA:FALSE
   keyUsage = nonRepudiation, digitalSignature, keyEncipherment
   subjectAltName = @alt_names

   [alt_names]
   IP.1 = 127.0.0.1
   IP.2 = 192.168.2.33 # Host IP

Generate SSL certificate for Keycloak:

.. code-block:: shell

   #!/bin/bash

   req_conf=csr.cnf
   ssl_dir=/etc/keycloak/ssl
   key_file=$ssl_dir/keycloak.key
   csr_file=$ssl_dir/keycloak.csr
   crt_file=$ssl_dir/keycloak.crt

   k8s_ssl_dir=/etc/kubernetes/pki
   k8s_ca_crt=$k8s_ssl_dir/ca.crt
   k8s_ca_key=$k8s_ssl_dir/ca.key

   # make a directory for storing certificate
   mkdir -p $ssl_dir

   # generate private key
   openssl genrsa -out $key_file 2048

   # generate certificate signing request
   openssl req -new -key $key_file -out $csr_file -subj "/CN=Keycloak" \
   -config $req_conf

   # use Kubernetes's CA for issuing certificate
   openssl x509 -req -in $csr_file -CA $k8s_ca_crt -CAkey $k8s_ca_key \
   -CAcreateserial -out $crt_file -days 365 -extensions v3_req \
   -extfile $req_conf

   # add executeable permission to key file
   chmod 755 $key_file

Starts a Keycloak container with docker:

.. code-block:: console

   $ docker run -d \
     --net=host \
     -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin \
     -e KC_HTTP_PORT=8080 -e KC_HTTPS_PORT=8443 \
     -e KC_HTTPS_CERTIFICATE_FILE=/opt/keycloak/conf/keycloak.crt \
     -e KC_HTTPS_CERTIFICATE_KEY_FILE=/opt/keycloak/conf/keycloak.key \
     -v /etc/keycloak/ssl:/opt/keycloak/conf \
     quay.io/keycloak/keycloak:18.0.2 \
     start-dev

Setup Keycloak
~~~~~~~~~~~~~~

Login to the admin console

* Open the URL https://192.168.2.33:8443/admin with a browser
* Fill in the form with the following values:

  + :guilabel:`Username`: **admin**
  + :guilabel:`Password`: **admin**

Create a realm

* Click :guilabel:`Add realm`
* Fill in the :guilabel:`Name` with **oidc**
* Click :guilabel:`Create`


Create a user

* Click :guilabel:`Users` in the menu
* Click :guilabel:`Add user`
* Fill in the :guilabel:`Username` with **end-user**
* Click :guilabel:`Save`

Set user credentials

* Click :guilabel:`Credentials`
* Fill in the form with the following values:

  + :guilabel:`Password` : **end-user**
  + :guilabel:`Password Confirmation` : **end-user**

* Turn off Temporary
* Click :guilabel:`Save`

Set user attributes

* Click :guilabel:`Attributes`
* Fill in the form with the following values:

  + :guilabel:`Key` : **name**
  + :guilabel:`Value` : **end-user**

* Click :guilabel:`Add`
* Click :guilabel:`Save`

Create a client

* Click :guilabel:`Clients` in the menu
* Click :guilabel:`Create`
* Fill in the :guilabel:`Client ID` with **tacker**
* Click :guilabel:`Save`

Set client type and valid redirect URIs

* Click :guilabel:`Settings`
* Select **confidential** as the :guilabel:`Access Type`
* Fill in the :guilabel:`Valid Redirect URIs` with  **http://***
* Click :guilabel:`Save`

Set client mappers

* Click :guilabel:`Mappers`
* Click :guilabel:`Create`
* Select **User Attribute** as the :guilabel:`Mapper Type`
* Fill in the form with the following values:

  + :guilabel:`Name` : **name**
  + :guilabel:`User Attribute` : **name**
  + :guilabel:`Token Claim Name` : **name**

* Select **String** as the :guilabel:`Claim JSON Type`
* Click :guilabel:`Save`

View client secret

* Click :guilabel:`Credentials` and view the secret.

Setup Kubernetes
~~~~~~~~~~~~~~~~

Add oidc related startup parameters to kube-apiserver manifest file.

.. code-block:: console

   $ cat /etc/kubernetes/manifests/kube-apiserver.yaml

   spec:
     containers:
     - command:
       - kube-apiserver
       - --oidc-issuer-url=https://192.168.2.33:8443/realms/oidc
       - --oidc-client-id=tacker
       - --oidc-username-claim=name
       - --oidc-username-prefix=-
       - --oidc-ca-file=/etc/kubernetes/ssl/ca.crt

.. note::
   After modifying kube-apiserver manifest file, the kube-apiserver will be restarted.

Create a cluster role binding to grant end users permissions to manipulate
Kubernetes resources.

* Create a cluster role binding file ``cluster_role_binding.yaml``:

  .. code-block:: yaml

     kind: ClusterRoleBinding
     apiVersion: rbac.authorization.k8s.io/v1
     metadata:
       name: oidc-cluster-admin-binding
     roleRef:
       kind: ClusterRole
       name: cluster-admin
       apiGroup: rbac.authorization.k8s.io
     subjects:
     - kind: User
       name: end-user

* Create cluster role binding:

  .. code-block:: console

     $ kubectl create -f cluster_role_binding.yaml


Verify
~~~~~~

Get token endpoint from Keycloak:

.. code-block:: console

   $ curl -ks -X GET https://192.168.2.33:8443/realms/oidc/.well-known/openid-configuration | jq -r .token_endpoint

Result:

.. code-block:: console

   https://192.168.2.33:8443/realms/oidc/protocol/openid-connect/token

Get a OpenID token from Keycloak:

.. code-block:: console

   $ ID_TOKEN=$(curl -ks -X POST https://192.168.2.33:8443/realms/oidc/protocol/openid-connect/token \
     -d grant_type=password -d scope=openid -d username=end-user -d password=end-user \
     -d client_id=tacker -d client_secret=A93HfOUpySm6BjPug9PJdJumjEGUJMhc | jq -r .id_token)
   $ echo $ID_TOKEN

Result:

.. code-block:: console

   eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxbC1RMy1KanQ1eHVrNzhYbUVkZDU2Mko4YXRRVF95MU1zS0JDUTBBcklnIn0.eyJleHAiOjE2NjAxMjExNTUsImlhdCI6MTY2MDEyMDg1NSwiYXV0aF90aW1lIjowLCJqdGkiOiIwZjdkNDE2My05Njk1LTQ3MGMtYmE1OC02MWI4NDM4YTU4MzQiLCJpc3MiOiJodHRwczovLzE5Mi4xNjguMi4zMzo4NDQzL3JlYWxtcy9vaWRjIiwiYXVkIjoidGFja2VyIiwic3ViIjoiOGZlZDVhYzctZDY4OS00NWM1LWE5NmQtYTlmN2M3Y2QxZTJjIiwidHlwIjoiSUQiLCJhenAiOiJ0YWNrZXIiLCJzZXNzaW9uX3N0YXRlIjoiNzhiYzhmNDEtMjc4NC00YTU5LWJjOTUtNjNkZDM5YTQ5NjNiIiwiYXRfaGFzaCI6Ik9WczJ3Q29VclU0QWxZaml0dGNQLXciLCJhY3IiOiIxIiwic2lkIjoiNzhiYzhmNDEtMjc4NC00YTU5LWJjOTUtNjNkZDM5YTQ5NjNiIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJlbmQtdXNlciJ9.SAncMlpRCLY8JlRUZ7YwirmYs5Qc-5qrvJYmyCZSiRgHyn-7cuqNan3vyVGO46Iv9Da51_Im3L5HaVJTcReeCZ2fhuzgei3yOquPugcfaqKKZEujA042Cc0pFTLS_dPl1xX3XINEcN4nGYGhGtLi8CBH0iANi-IY_VEdxogTyc9MlKgjP9Ca8eYNUPhop49GwLC-ph5vMShS9O834ywtQargb51zokQsoXAYrGBJMTWr37uMxP7UWXpYAQa82OyX3fElpueurd5WGEzGT1AhN1Ad4uIAxgD6dxFsiQYOHRSH-sByV0IwMdZoqIm4GFS6NHLj5usr6PSA5U9QpgCI7Q

Get all namespaces with OpenID token from Kubernetes:

.. code-block:: console

   curl -ks -H "Authorization: Bearer $ID_TOKEN" https://192.168.2.33:6443/api/v1/namespaces

Result:

.. code-block:: console

   {
     "kind": "NamespaceList",
     "apiVersion": "v1",
     ...omit...

View certificates
~~~~~~~~~~~~~~~~~

View Kubernetes CA certificate:

.. code-block:: console

   $ cat /etc/kubernetes/pki/ca.crt
   -----BEGIN CERTIFICATE-----
   MIICwjCCAaqgAwIBAgIBADANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdrdWJl
   LWNhMB4XDTIwMDgyNjA5MzIzMVoXDTMwMDgyNDA5MzIzMVowEjEQMA4GA1UEAxMH
   a3ViZS1jYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALxkeE16lPAd
   pfJj5GJMvZJFcX/CD6EB/LUoKwGmqVoOUQPd3b/NGy+qm+3bO9EU73epUPsVaWk2
   Lr+Z1ua7u+iib/OMsfsSXMZ5OEPgd8ilrTGhXOH8jDkif9w1NtooJxYSRcHEwxVo
   +aXdIJhqKdw16NVP/elS9KODFdRZDfQ6vU5oHSg3gO49kgv7CaxFdkF7QEHbchsJ
   0S1nWMPAlUhA5b8IAx0+ecPlMYUGyGQIQgjgtHgeawJebH3PWy32UqfPhkLPzxsy
   TSxk6akiXJTg6mYelscuxPLSe9UqNvHRIUoad3VnkF3+0CJ1z0qvfWIrzX3w92/p
   YsDBZiP6vi8CAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB/wQFMAMB
   Af8wDQYJKoZIhvcNAQELBQADggEBAIbv2ulEcQi019jKz4REy7ZyH8+ExIUBBuIz
   InAkfxNNxV83GkdyA9amk+LDoF/IFLMltAMM4b033ZKO5RPrHoDKO+xCA0yegYqU
   BViaUiEXIvi/CcDpT9uh2aNO8wX5T/B0WCLfWFyiK+rr9qcosFYxWSdU0kFeg+Ln
   YAaeFY65ZWpCCyljGpr2Vv11MAq1Tws8rEs3rg601SdKhBmkgcTAcCzHWBXR1P8K
   rfzd6h01HhIomWzM9xrP2/2KlYRvExDLpp9qwOdMSanrszPDuMs52okXgfWnEqlB
   2ZrqgOcTmyFzFh9h2dj1DJWvCvExybRmzWK1e8JMzTb40MEApyY=
   -----END CERTIFICATE-----

View Keycloak certificate:

.. code-block:: console

   $ cat /etc/keycloak/ssl/keycloak.crt
   -----BEGIN CERTIFICATE-----
   MIIC7TCCAdWgAwIBAgIUQK2k5uNvlRLx43LI/t3a2/A/3iQwDQYJKoZIhvcNAQEL
   BQAwFTETMBEGA1UEAxMKa3ViZXJuZXRlczAeFw0yMjA4MDQwNjIwNTFaFw0yMzA4
   MDQwNjIwNTFaMBMxETAPBgNVBAMMCEtleWNsb2FrMIIBIjANBgkqhkiG9w0BAQEF
   AAOCAQ8AMIIBCgKCAQEAni7HWLn2IpUImGO1sbBf/XuqATkXSeIIRuQuFymwYPoX
   BP7RowzrbfF9KUwdIKlz9IXjqb1hplumiqNy1Sc7MmrTY9Fj87MNAMlnCIvyWkjE
   XVXWxGef49mqc85P2K1iuAsr2R7sDrv7SC0ch+lHclOjGDmCjKOk8qF3kD1LATWg
   zf42aXb4nNF9kyIOPEbI+jX4PWhAQpEz5nIG+xIRjTHGfacjpeg0+XOK21wLAuQB
   fqebJ6GxX4OzB37ZtLLgrKyBYWaWuYkWbexVRM3wEvQu8ENkvhV017iPuPHSxNWx
   Y8z072XMs9j8XRQD65EVqObXyizotPRJF4slEJ9qMQIDAQABozcwNTAJBgNVHRME
   AjAAMAsGA1UdDwQEAwIF4DAbBgNVHREEFDAShwR/AAABhwTAqAIhhwQKCgCMMA0G
   CSqGSIb3DQEBCwUAA4IBAQBebjmNHd8sJXjvPQc3uY/3KSDpk9AYfYzhUZvcvLNg
   z0llFqXHaFlMqHTsz1tOH4Ns4PDKKoRT0JIKC1FkvjzqgL+X2jWFS0NRoNyd3W3B
   yHLEL7MdQqDR+tZX02EGfaGXjuy8GHIU4J2hXhohmpn6ntfiRONfY8jaEjIecPFS
   IwZWXNhsDESa1zuDe0PatES/Ati8bAUpN2rb/7rsE/AeM5GXpQfOKV0XxdIeBZ82
   Vf5cUDWPipvq2Q9KS+yrTvEObGtA6gKhQ4bpz3MieU3N8AtQpEKtROH7mJWMHyl2
   roD1k8KeJlfvR/XcVTGFcgIdNLfKIdd99Xfi4gSaIKuw
   -----END CERTIFICATE-----

Deploy CNF with OpenID token
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Please refer to `CNF usage guide`_ to deploy CNF with OpenID token.

.. _OpenID token : https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens
.. _Keycloak: https://www.keycloak.org
.. _CNF usage guide: https://docs.openstack.org/tacker/latest/user/etsi_containerized_vnf_usage_guide.html
