Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
devops:deploy_keycloak_using_helm [2020/06/10 18:56] – [keycloak_values.yaml] admindevops:deploy_keycloak_using_helm [2020/06/10 18:59] (current) – [Ошибки при обновлении] admin
Line 1: Line 1:
 +====== Установка keycloak с помощью helm-чарта ======
 +Клонируем репозиторий с чартом:
 +  mkdir ~/keycloak && cd ~/keycloak
 +  git clone https://github.com/codecentric/helm-charts.git
 +  echo '' > helm-charts/charts/keycloak/requirements.yaml
 +===== Cоздание базы на сервере postgres =====
 +<code>user@postgres:~$ sudo su - postgres
 +postgres@postgres:~$ psql
  
 +postgres=# create database keycloak;
 +CREATE DATABASE
 +postgres=# create user keycloak with encrypted password 'keycloak_db_password';
 +CREATE ROLE
 +postgres=# grant all privileges on database keycloak to keycloak;
 +GRANT</code>
 +
 +===== Создание неймспейса и секретов (паролей и сертов) =====
 +Создаем неймспейс
 +  kubectl create ns keycloak
 +Создаем секреты с дефолтным паролем админской учетки **keycloak** и паролем пользователя базы данных:
 +  kubectl create secret generic keycloak-default-admin-password -n keycloak --from-literal=password=keycloak_console_password 
 +  kubectl create secret generic keycloak-db-password -n keycloak --from-literal=password=keycloak_db_password --from-literal=username=keycloak 
 +
 +Создаем секрет с **ssl**-сертификатом **web**-интерфейса:
 +<code>openssl pkcs12 -in sso.domain.local.pfx -nocerts -out sso.domain.local.key
 +openssl rsa -in sso.domain.local.key -out sso.domain.local.key.pem
 +openssl pkcs12 -in sso.domain.local.pfx -clcerts -nokeys -out sso.domain.local.cert.crt
 +kubectl create secret generic -n keycloak sso-domain-local --from-file=tls.crt=./sso.domain.local.cert.crt --from-file=tls.key=./sso.domain.local.key.pem</code>
 +
 +Создаем **ConfigMap** с корпоративными корневыми сертификатами. Они должны быть в формате **pem** и оформлены стандартными разделителями - **-----BEGIN CERTIFICATE-----** и **-----END CERTIFICATE-----**.
 +  kubectl -n keycloak create configmap ca-bundle --from-file=./ca.cer
 +
 +===== keycloak_values.yaml =====
 +<code>
 +keycloak:
 +  replicas: 1
 +  image:
 +    tag: 8.0.2
 +  existingSecret: "keycloak-default-admin-password"
 +
 +  extraVolumes: |
 +    - name: ca-bundle
 +      configMap:
 +        name: ca-bundle
 +  extraVolumeMounts: |
 +    - name: ca-bundle
 +      mountPath: /custom_certs/
 +
 +  extraEnv: |
 +    - name: X509_CA_BUNDLE
 +      value: "/custom_certs/ca.cer"
 +    - name:  PROXY_ADDRESS_FORWARDING
 +      value: "true"
 +    - name: JAVA_OPTS
 +      value: |
 +        -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true -Dkeycloak.profile.feature.upload_scripts=enabled
 +
 +  ingress:
 +    enabled: true
 +    path: /
 +    annotations: 
 +      kubernetes.io/ingress.class: nginx
 +    hosts:
 +      - sso.domain.local
 +    tls:
 +      - hosts:
 +        - sso.domain.local
 +        secretName: sso-domain-local
 +
 +  persistence:
 +    deployPostgres: false
 +    dbVendor: "postgres"
 +    existingSecret: "keycloak-db-password"
 +    dbName: keycloak
 +    dbHost: 10.10.10.10
 +    dbPort: 5432
 +
 +test:
 +  enabled: false
 +</code>
 +
 +  * Созданный **ConfigMap** с корпоративными корневыми сертификатами монтируется в директорию  **/custom_certs/**.
 +  * Переменная **X509_CA_BUNDLE** указывает на путь к файлу с сертификатами и при старте контейнера используется скриптом **/opt/jboss/tools/x509.sh**, который импортирует сертификаты из файла в **keystore**.
 +  * Переменная **PROXY_ADDRESS_FORWARDING** нужна для нормального проксирования заголовков **X-Forwarded-For**. Если ёе не задать, то при попытке открыть **Keycloak Administration Console** можно будет увидеть лишь пустую страницу. \\
 +  * Переменная **JAVA_OPTS** переопределяется для того, чтобы добавить **-Dkeycloak.profile.feature.upload_scripts=enabled**. Это нужно для того, чтобы нормально работал импорт ранее забекапленных (экспортированных) реалмов. Без этого параметра, при попытке импорта ранее экспортированного реалма в логах будет ошибка: <code>keycloak Uncaught server error: java.lang.RuntimeException: Script upload is disabled</code>
 + 
 +===== Ошибки при обновлении =====
 +При обновлении с версии **8.0.2** до версии **9.0.3** в логах возникла ошибка:
 +<code>16:11:15,512 INFO  [org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider] (ServerService Thread Pool -- 68) Updating database. Using changelog META-INF/jpa-changelog-master.xml
 +16:11:15,595 ERROR [org.keycloak.connections.jpa.updater.liquibase.conn.DefaultLiquibaseConnectionProvider] (ServerService Thread Pool -- 68) Change Set META-INF/jpa-changelog-9.0.1.xml::9.0.1-KEYCLOAK-12579-add-not-null-constraint::keycloak failed.  Error: ERROR: duplicate key value violates unique constraint "sibling_names"
 +  Detail: Key (realm_id, parent_group, name)=(rdleas,  , 1_SrvDocs_DocumentationAIB_Owner) already exists. [Failed SQL: UPDATE public.KEYCLOAK_GROUP SET PARENT_GROUP = ' ' WHERE PARENT_GROUP IS NULL]
 +16:11:15,602 FATAL [org.keycloak.services] (ServerService Thread Pool -- 68) java.lang.RuntimeException: Failed to update database</code>
 +
 +Судя по всему - в базе две записи о группе **1_SrvDocs_DocumentationAIB_Owner**. \\
 +Сначала - останавливаем **keycloak**. для этого редактируем **statefullSet** и ставим **replicas: 0** \\
 +Идем на сервер **postgres**, перевоплощаемся в пользователя **postgres**:
 +  sudo su - postgres
 +и лечим. Для начала - восстановим базу из бекапа в исходное состояние:
 +  dropdb keycloak
 +  createdb keycloak
 +  psql -d keycloak -f ./keycloak_10.06.20_15-03.bak
 +Дальше запускаем **psql** и смотрим список баз:
 +  \l
 +Переключаемся на базу **keycloak**:
 +  \c keycloak
 +Убеждаемся, что записей о группе две:
 +  SELECT * FROM public.KEYCLOAK_GROUP WHERE name = '1_SrvDocs_DocumentationAIB_Owner';
 +Непонятно, какая из них правильная - удаляем обе:
 +  DELETE FROM public.KEYCLOAK_GROUP WHERE name = '1_SrvDocs_DocumentationAIB_Owner';
 +
 +====== Приложение для тестирования KeyCloak (python Flask) ======
 +Протестировать **keycloak** и убедиться, что он настроен правильно и работает можно с помощью простых приложений.
 +===== flask_oidc =====
 +https://gist.github.com/thomasdarimont/145dc9aa857b831ff2eff221b79d179a (https://gist.githubusercontent.com/thomasdarimont/145dc9aa857b831ff2eff221b79d179a/raw/a72a9462b2bd693913efba86cbc74f87c043121d/app.py) \\
 +https://gist.githubusercontent.com/thomasdarimont/145dc9aa857b831ff2eff221b79d179a/raw/a72a9462b2bd693913efba86cbc74f87c043121d/client_secrets.json
 +\\
 +Ставим то что нужно:
 +  sudo pip3 --trusted-host pypi.org --trusted-host files.pythonhosted.org --proxy=http://127.0.0.1:3130 install flask flask_oidc cherrypy cryptojwt cryptography>=2.8
 +Тестовое приложение на фреймворке **flask** - **app.py**. \\
 +<code>#!/usr/bin/env python3
 +import json
 +import logging
 +
 +from flask import Flask, g
 +from flask_oidc import OpenIDConnect
 +import requests
 +
 +logging.basicConfig(level=logging.DEBUG)
 +
 +app = Flask(__name__)
 +app.config.update({
 +    'SECRET_KEY': 'SomethingNotEntirelySecret',
 +    'TESTING': True,
 +    'DEBUG': True,
 +    'OIDC_CLIENT_SECRETS': 'client_secrets.json',
 +    'OIDC_ID_TOKEN_COOKIE_SECURE': False,
 +    'OIDC_REQUIRE_VERIFIED_EMAIL': False,
 +    'OIDC_USER_INFO_ENABLED': True,
 +    'OIDC_OPENID_REALM': 'flask-demo',
 +    'OIDC_SCOPES': ['openid', 'email', 'profile'],
 +    'OIDC_INTROSPECTION_AUTH_METHOD': 'client_secret_post'
 +})
 +
 +oidc = OpenIDConnect(app)
 +
 +@app.route('/')
 +def hello_world():
 +    if oidc.user_loggedin:
 +        return ('Hello, %s, <a href="/private">See private</a> '
 +                '<a href="/logout">Log out</a>') % \
 +            oidc.user_getfield('preferred_username')
 +    else:
 +        return 'Welcome anonymous, <a href="/private">Log in</a>'
 +
 +@app.route('/private')
 +@oidc.require_login
 +def hello_me():
 +    """Example for protected endpoint that extracts private information from the OpenID Connect id_token.
 +       Uses the accompanied access_token to access a backend service.
 +    """
 +
 +    info = oidc.user_getinfo(['preferred_username', 'email', 'sub', 'groups'])
 +
 +    username = info.get('preferred_username')
 +    email = info.get('email')
 +    user_id = info.get('sub')
 +    groups = info.get('groups')
 +    greeting = 'Greeting!!!'
 +    """if user_id in oidc.credentials_store:
 +        try:
 +            from oauth2client.client import OAuth2Credentials
 +            access_token = OAuth2Credentials.from_json(oidc.credentials_store[user_id]).access_token
 +            print ('access_token=<%s>' % access_token)
 +            headers = {'Authorization': 'Bearer %s' % (access_token)}
 +            # YOLO
 +            # greeting = requests.get('http://localhost:8080/greeting', headers=headers).text
 +        except:
 +            print ("Could not access greeting-service")
 +            greeting = "Hello %s" % username
 +"""
 +    return (""" %s </br>
 +               Your email is %s </br>
 +               Your user_id is %s! </br>
 +               Your Groups - %s </br>
 +               <ul>
 +                 <li><a href="/">Home</a></li>
 +                 <li><a href="https://sso.rdleas.ru/auth/realms/rdleas/account?referrer=flask-app&referrer_uri=http://192.168.104.94:5000/private&">Account</a></li>
 +                </ul>""" %
 +            (greeting, email, user_id, groups))
 +
 +@app.route('/api', methods=['POST'])
 +@oidc.accept_token(require_token=True, scopes_required=['openid'])
 +def hello_api():
 +    """OAuth 2.0 protected API endpoint accessible via AccessToken"""
 +
 +    return json.dumps({'hello': 'Welcome %s' % g.oidc_token_info['sub']})
 +
 +@app.route('/logout')
 +def logout():
 +    """Performs local logout by removing the session cookie."""
 +
 +    oidc.logout()
 +    return 'Hi, you have been logged out! <a href="/">Return</a>'
 +
 +if __name__ == '__main__':
 +     app.run(host= '0.0.0.0')
 +#    app.run()
 +</code>
 +И конфигурационный файл для него - **client_secrets.json** 
 +<code>{
 +    "web": {
 +        "issuer": "https://sso.rdleas.ru/auth/realms/rdleas",
 +        "auth_uri": "https://sso.rdleas.ru/auth/realms/rdleas/protocol/openid-connect/auth",
 +        "client_id": "test",
 +        "client_secret": "32e8263a-9edc-4159-902a-e23c22606b1d",
 +        "redirect_uris": [
 +            "http://192.168.104.94:5000/*"
 +        ],
 +        "userinfo_uri": "https://sso.rdleas.ru/auth/realms/rdleas/protocol/openid-connect/userinfo",
 +        "token_uri": "https://sso.rdleas.ru/auth/realms/rdleas/protocol/openid-connect/token",
 +        "token_introspection_uri": "https://sso.rdleas.ru/auth/realms/rdleas/protocol/openid-connect/token/introspect"
 +    }
 +}</code>
 +Запускаем так:
 +  ./app.py
 +===== JWTConnect-Python-OidcRP =====
 +Вот еще один ваиант тестового приложения. Но его запуск я не осилил. 
 +  git clone https://github.com/openid/JWTConnect-Python-OidcRP.git
 +  cd ./JWTConnect-Python-OidcRP/chrp
 +  cp ./example_conf.py ./conf.py
 +  ./make_opbyuid_html.py conf > html/opbyuid.html
 +  sudo pip3 --trusted-host pypi.org --trusted-host files.pythonhosted.org --proxy=http://127.0.0.1:3130 install cherrypy cryptojwt cryptography>=2.8  
  • devops/deploy_keycloak_using_helm.txt
  • Last modified: 2020/06/10 18:59
  • by admin