This patch adds openid token auth support when calling k8s APIs. Openid token auth of k8s relies on an external openid provider, and Keycloak acts as the openid provider in this implementation. Implements: blueprint support-openid-k8s-vim Change-Id: Ie5e080a20cba3ba0ed514ede7955eb16729d797cchanges/01/851701/20
parent
dbca617b98
commit
57902730d6
@ -0,0 +1,322 @@
|
||||
============================================
|
||||
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
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds "openid token" authentication strategy for Kubernetes VIM. Users can
|
||||
specify openid authentication parameters in the `VIM Register Request`
|
||||
or the `VNF Instantiate Request` to indicate tacker to use openid token
|
||||
for authentication.
|
@ -0,0 +1 @@
|
||||
oidc_work_dir: "/tmp/oidc"
|
@ -0,0 +1,11 @@
|
||||
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
|
@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
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
|
@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
KEYCLOAK_BASE_URL=https://127.0.0.1:8443
|
||||
|
||||
ADMIN_TOKEN=$(curl -k -sS -X POST "${KEYCLOAK_BASE_URL}/realms/master/protocol/openid-connect/token" \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
-d 'username=admin' \
|
||||
-d 'password=admin' \
|
||||
-d 'grant_type=password' \
|
||||
-d 'client_id=admin-cli' | jq -r .access_token)
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
exit $?
|
||||
fi
|
||||
|
||||
curl -k -L -X POST "${KEYCLOAK_BASE_URL}/admin/realms" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-d @"oidc_realm.json"
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@
|
||||
[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
|
@ -0,0 +1,109 @@
|
||||
- block:
|
||||
- name: Create a work directory
|
||||
file:
|
||||
path: "{{ oidc_work_dir }}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
become: yes
|
||||
|
||||
- name: Copy setup files to k8s server
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: "{{ oidc_work_dir }}"
|
||||
owner: stack
|
||||
group: stack
|
||||
mode: 0644
|
||||
with_items:
|
||||
- "ssl_csr.conf"
|
||||
- "generate_ssl_cert.sh"
|
||||
- "create_keycloak.sh"
|
||||
- "oidc_realm.json"
|
||||
- "import_oidc_realm.sh"
|
||||
- "cluster_role_binding.yaml"
|
||||
become: yes
|
||||
|
||||
- name: Replace {{ oidc_work_dir }} if not default
|
||||
replace:
|
||||
path: "{{ item }}"
|
||||
regexp: "/tmp/oidc"
|
||||
replace: "{{ oidc_work_dir }}"
|
||||
with_items:
|
||||
- "{{ oidc_work_dir }}/generate_ssl_cert.sh"
|
||||
when:
|
||||
- oidc_work_dir != '/tmp/oidc'
|
||||
become: yes
|
||||
|
||||
- name: Add keycloak's ip to CSR conf
|
||||
lineinfile:
|
||||
path: "{{ oidc_work_dir }}/ssl_csr.conf"
|
||||
line: "IP.2 = {{ keycloak_host }}"
|
||||
become: yes
|
||||
|
||||
- name: Generate SSL certificate for keycloak
|
||||
command: /bin/bash {{ oidc_work_dir }}/generate_ssl_cert.sh
|
||||
become: yes
|
||||
|
||||
- name: Create and start keycloak server
|
||||
command: /bin/bash {{ oidc_work_dir }}/create_keycloak.sh
|
||||
become: yes
|
||||
|
||||
- name: Wait for keycloak be active
|
||||
wait_for:
|
||||
host: "{{ keycloak_host }}"
|
||||
port: "{{ keycloak_https_port }}"
|
||||
delay: 120
|
||||
timeout: 300
|
||||
|
||||
- name: Install jq command
|
||||
package:
|
||||
name: jq
|
||||
state: present
|
||||
become: yes
|
||||
|
||||
- name: Replace keycloak host:port to import_oidc_realm.sh
|
||||
replace:
|
||||
path: "{{ item }}"
|
||||
regexp: "https://127.0.0.1:8443"
|
||||
replace: "https://{{ keycloak_host}}:{{ keycloak_https_port }}"
|
||||
with_items:
|
||||
- "{{ oidc_work_dir }}/import_oidc_realm.sh"
|
||||
become: yes
|
||||
|
||||
- name: Import oidc realm
|
||||
command: /bin/bash import_oidc_realm.sh
|
||||
args:
|
||||
chdir: "{{ oidc_work_dir }}"
|
||||
become: yes
|
||||
|
||||
- name: Setup oidc on k8s server
|
||||
blockinfile:
|
||||
path: /etc/kubernetes/manifests/kube-apiserver.yaml
|
||||
insertafter: "- --tls-private-key-file=.*"
|
||||
block: |2
|
||||
- --oidc-issuer-url=https://{{ keycloak_host }}:{{ keycloak_https_port }}/realms/oidc
|
||||
- --oidc-client-id=tacker
|
||||
- --oidc-username-claim=name
|
||||
- --oidc-username-prefix=-
|
||||
- --oidc-ca-file=/etc/kubernetes/pki/ca.crt
|
||||
become: yes
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Wait for k8s apiserver to restart
|
||||
wait_for:
|
||||
host: "{{ hostvars['controller-k8s']['nodepool']['private_ipv4'] }}"
|
||||
port: 6443
|
||||
delay: 30
|
||||
timeout: 180
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Create clusterrolebinding on k8s server
|
||||
command: >
|
||||
kubectl create -f {{ oidc_work_dir }}/cluster_role_binding.yaml
|
||||
become: yes
|
||||
become_user: stack
|
||||
ignore_errors: yes
|
||||
|
||||
when:
|
||||
- inventory_hostname == 'controller-k8s'
|
||||
- keycloak_host is defined
|
||||
|
@ -0,0 +1,57 @@
|
||||
# Copyright (C) 2022 Fujitsu
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Utilities functions for get openid token."""
|
||||
|
||||
import requests
|
||||
|
||||
from tacker.extensions.vnfm import OIDCAuthFailed
|
||||
|
||||
|
||||
def get_id_token_with_password_grant(
|
||||
token_endpoint, username, password, client_id,
|
||||
client_secret=None, ssl_ca_cert=None, timeout=20):
|
||||
|
||||
if not token_endpoint or not username or not password or not client_id:
|
||||
raise OIDCAuthFailed(detail='token_endpoint, username, password,'
|
||||
' client_id can not be empty.')
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
body = {
|
||||
'grant_type': 'password',
|
||||
'scope': 'openid',
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'username': username,
|
||||
'password': password
|
||||
}
|
||||
|
||||
verify = ssl_ca_cert if ssl_ca_cert else False
|
||||
|
||||
try:
|
||||
resp = requests.post(token_endpoint, headers=headers, data=body,
|
||||
verify=verify, timeout=timeout)
|
||||
|
||||
if (resp.status_code == 200
|
||||
and resp.headers['Content-Type'] == 'application/json'):
|
||||
return resp.json()['id_token']
|
||||
|
||||
raise OIDCAuthFailed(
|
||||
detail=f'response code: {resp.status_code}, body: {resp.text}')
|
||||
except requests.exceptions.RequestException as exc:
|
||||
raise OIDCAuthFailed(detail=str(exc))
|
@ -0,0 +1,57 @@
|
||||
# Copyright (C) 2022 Fujitsu
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Utilities functions for get openid token."""
|
||||
|
||||
import requests
|
||||
|
||||
from tacker.sol_refactored.common.exceptions import OIDCAuthFailed
|
||||
|
||||
|
||||
def get_id_token_with_password_grant(
|
||||
token_endpoint, username, password, client_id,
|
||||
client_secret=None, ssl_ca_cert=None, timeout=20):
|
||||
|
||||
if not token_endpoint or not username or not password or not client_id:
|
||||
raise OIDCAuthFailed(detail='token_endpoint, username, password,'
|
||||
' client_id can not be empty.')
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
body = {
|
||||
'grant_type': 'password',
|
||||
'scope': 'openid',
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'username': username,
|
||||
'password': password
|
||||
}
|
||||
|
||||
verify = ssl_ca_cert if ssl_ca_cert else False
|
||||
|
||||
try:
|
||||
resp = requests.post(token_endpoint, headers=headers, data=body,
|
||||
verify=verify, timeout=timeout)
|
||||
|
||||
if (resp.status_code == 200
|
||||
and resp.headers['Content-Type'] == 'application/json'):
|
||||
return resp.json()['id_token']
|
||||
|
||||
raise OIDCAuthFailed(
|
||||
detail=f'response code: {resp.status_code}, body: {resp.text}')
|
||||
except requests.exceptions.RequestException as exc:
|
||||
raise OIDCAuthFailed(detail=str(exc))
|
@ -0,0 +1,9 @@
|
||||
auth_url: "https://127.0.0.1:6443"
|
||||
project_name: "default"
|
||||
oidc_token_url: "https://127.0.0.1:8443/realms/oidc/protocol/openid-connect/token"
|
||||
username: "end-user"
|
||||
password: "end-user"
|
||||
client_id: "tacker"
|
||||
client_secret: "K0Zp5dvdOFhZ7W9PVNZn14omW9NmCQvQ"
|
||||
ssl_ca_cert: None
|
||||
type: "kubernetes"
|
@ -0,0 +1,147 @@
|
||||
# Copyright (C) 2022 FUJITSU
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import yaml
|
||||
|
||||
from tackerclient.common import exceptions
|
||||
|
||||
from tacker.tests.functional import base
|
||||
from tacker.tests.utils import read_file
|
||||
|
||||
SECRET_PASSWORD = '***'
|
||||
|
||||
|
||||
class VimTest(base.BaseTackerTest):
|
||||
|
||||
@classmethod
|
||||
def generate_vim_info_oidc_for_creation(cls):
|
||||
|
||||
data = yaml.safe_load(read_file('local-k8s-vim-oidc.yaml'))
|
||||
auth_cred = {'oidc_token_url': data['oidc_token_url'],
|
||||
'username': data['username'],
|
||||
'password': data['password'],
|
||||
'client_id': data['client_id'],
|
||||
'client_secret': data['client_secret']}
|
||||
if 'ssl_ca_cert' in data:
|
||||
auth_cred['ssl_ca_cert'] = data['ssl_ca_cert']
|
||||
return {'vim': {'name': 'VIM-OIDC-AUTH',
|
||||
'description': 'Kubernetes VIM with oidc auth',
|
||||
'type': 'kubernetes',
|
||||
'auth_url': data['auth_url'],
|
||||
'auth_cred': auth_cred,
|
||||
'vim_project': {'name': 'default'},
|
||||
'is_default': False}}
|
||||
|
||||
@classmethod
|
||||
def generate_vim_info_oidc_for_update(cls):
|
||||
|
||||
data = yaml.safe_load(read_file('local-k8s-vim-oidc.yaml'))
|
||||
auth_cred = {'oidc_token_url': data['oidc_token_url'],
|
||||
'username': data['username'],
|
||||
'password': data['password'],
|
||||
'client_id': data['client_id'],
|
||||
'client_secret': data['client_secret']}
|
||||
if 'ssl_ca_cert' in data:
|
||||
auth_cred['ssl_ca_cert'] = data['ssl_ca_cert']
|
||||
return {'vim': {'name': 'VIM-OIDC-AUTH',
|
||||
'description': 'Kubernetes VIM with oidc auth',
|
||||
'auth_cred': auth_cred}}
|
||||
|
||||
@classmethod
|
||||
def generate_vim_info_token_for_update(cls):
|
||||
|
||||
data = yaml.safe_load(read_file('local-k8s-vim.yaml'))
|
||||
auth_cred = {'bearer_token': data['bearer_token']}
|
||||
if 'ssl_ca_cert' in data:
|
||||
auth_cred['ssl_ca_cert'] = data['ssl_ca_cert']
|
||||
return {'vim': {'name': 'VIM-BEARER-TOKEN',
|
||||
'description': 'Kubernetes VIM with bearer token',
|
||||
'auth_cred': auth_cred}}
|
||||
|
||||
def assert_vim_auth_oidc(self, vim_auth_req, vim_auth_res):
|
||||
unexpected_attrs = {'bearer_token'}
|
||||
# check only specified attributes exist
|
||||
self.assertNotIn(unexpected_attrs, vim_auth_res)
|
||||
self.assertEqual(vim_auth_req['oidc_token_url'],
|
||||
vim_auth_res['oidc_token_url'])
|
||||
self.assertEqual(vim_auth_req['username'],
|
||||
vim_auth_res['username'])
|
||||
self.assertEqual(SECRET_PASSWORD,
|
||||
vim_auth_res['password'])
|
||||
self.assertEqual(vim_auth_req['client_id'],
|
||||
vim_auth_res['client_id'])
|
||||
self.assertEqual(SECRET_PASSWORD,
|
||||
vim_auth_res['client_secret'])
|
||||
|
||||
def assert_vim_auth_token(self, vim_auth_res):
|
||||
unexpected_attrs = {'oidc_token_url', 'username', 'password',
|
||||
'client_id', 'client_secret'}
|
||||
# check only specified attributes exist
|
||||
self.assertNotIn(unexpected_attrs, vim_auth_res)
|
||||
self.assertEqual(SECRET_PASSWORD,
|
||||
vim_auth_res['bearer_token'])
|
||||
|
||||
def test_vim_creation_update_with_oidc_auth(self):
|
||||
|
||||
vim_oidc_create = self.generate_vim_info_oidc_for_creation()
|
||||
vim_oidc_update = self.generate_vim_info_oidc_for_update()
|
||||
vim_token_update = self.generate_vim_info_token_for_update()
|
||||
|
||||
# Register vim
|
||||
vim_res = self.client.create_vim(vim_oidc_create)
|
||||
vim_id = vim_res['vim']['id']
|
||||
self.assert_vim_auth_oidc(vim_oidc_create['vim']['auth_cred'],
|
||||
vim_res['vim']['auth_cred'])
|
||||
# Read vim
|
||||
vim_show_res = self.client.show_vim(vim_id)
|
||||
self.assert_vim_auth_oidc(vim_oidc_create['vim']['auth_cred'],
|
||||
vim_show_res['vim']['auth_cred'])
|