【DevOps工具篇】使用Ansible部署Keycloak oauth2proxy 和 单点登录(SSO)设置

2019-09-10T13:11:33+08:00 | 5分钟阅读 | 更新于 2019-09-10T13:11:33+08:00

Macro Zhao

【DevOps工具篇】使用Ansible部署Keycloak oauth2proxy 和 单点登录(SSO)设置

@TOC

推荐超级课程:

Ansible 是一种基础设施即代码定义语言,Keycloak 是一种 OpenID-Connect 提供者、认证代理和可以处理用户联合的工具。

在本文中,我将描述如何通过 Ansible 完全基于模型创建可扩展的 Keycloak 单点登录(SSO)设置。要跟进,您需要对 Docker(compose)、Ansible、代理、Linux 和 OpenID-Connect 有基本了解。

Ansible 基础知识

我假设您已经对 Ansible 有一些基本了解,但通常您需要以下目录:

# 文件模板 Keycloak
mkdir ./roles/keycloak/templates

# Ansible 任务 Keycloak
mkdir ./roles/keycloak/tasks

# 文件模板部署
mkdir ./roles/deployments/templates

# Ansible 任务部署
mkdir ./roles/deployments/tasks

# 变量
mkdir ./group_vars/

如果您想要一个用于存储机密的 Ansible 保险库,而不是使用 group_vars/all.yaml,请参考 Ansible 保险库文档

部署 Keycloak

OIDC 需要 https,这意味着您需要在 Keycloak 前面设置一个 TLS 设置。最简单的方法是使用 nginx 和 Let’s Encrypt 。Keycloak 的外部 https 地址将从现在起被引用为 {{ keycloak_address }}

让我们从 Keycloak compose 文件开始。如果您选择使用 Ansible,这应该放在 roles/keycloak/templates 中,并命名为 keycloak.yaml

---
version: '3.3'
services:
  keycloak:
    container_name: keycloak-container
    command: start --hostname-strict=false --log-level=WARNING
    image: quay.io/keycloak/keycloak:23.0.3 # <- 截至 2023 年 12 月版本
    environment:
      - KEYCLOAK_ADMIN=admin
      - KEYCLOAK_ADMIN_PASSWORD={{ keycloak_admin_password }}
      - PROXY_ADDRESS_FORWARDING=true
      - KC_PROXY=edge
      - KC_LOG_LEVEL=ALL
      - KC_DB=postgres # <- 不再是默认值
      - KC_DB_URL_HOST=postgres
      - KC_DB_USERNAME=keycloak
      - KC_DB_PASSWORD={{ keycloak_postgres_password }}
      - KC_HEALTH_ENABLED=true
      - KC_METRICS_ENABLED=true
      - KEYCLOAK_LOGLEVEL=WARN
    restart: unless-stopped
    ports:
    - 5050:8080
    depends_on:
    - postgres
  postgres:
    container_name: postgres-container
    image: postgres:15.1
    environment:
      - POSTGRES_DB=keycloak
      - POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password
      - POSTGRES_USER=keycloak
    restart: unless-stopped
    secrets:
    - postgres_password
    volumes:
    - /data/keycloak-postgres/:/var/lib/postgresql/data

secrets:
  postgres_password:
    file: postgres_password

...

这里有很多内容,首先我们必须在 compose 文件中定义引用的变量/机密,这些变量是 keycloak_admin_passwordkeycloak_postgres_password,我们还需要用于机密文件的 secrets 文件。其次,我们有一个卷,它将主机的文件系统路径挂载到 postgres 容器中。实际上,如果您所有的配置都是在 Ansible 中进行建模,这并不是绝对必要的,但这也意味着您无需在每次重新创建容器时都运行 Playbook。

出于演示目的,我们将这些机密定义在 group_vars/all.yaml 中,但通常机密应该在保险库中定义,并且只适用于个别主机:

# group_vars/all.yaml
keycloak_admin_password=adminpassword
keycloak_postgres_password=pgpassword

最后,我们需要定义 Ansible 任务来:

  • 安装系统上必要的软件包
  • 创建 /data/ 目录的卷数据
  • 创建用于 docker-compose 部署的目标目录
  • 将 compose 文件模板化并复制到远程
  • 部署 compose 文件
  • 在开始配置之前等待 Keycloak 启动

作为一个 Ansible 任务文件,它应该如下所示:

# roles/keycloak/tasks/main.yaml
- name: Install docker-compose
  package:
      name:
          - docker-compose # 应包括系统的容器管理器
      state: present
          
- name: Create data-dir
  file:
    name: /data/
    state: directory

- name: Create keycloak psql volume-mount
  file:
    name: /data/keycloak-postgres/
    state: directory

- name: Create compose directory keycloak
  file:
    name: "/opt/keycloak/"
    state: directory

- name: Copy compose templates keycloak
  template:
    src: "keycloak.yaml"
    dest: "/opt/keycloak/"

- name: Copy compose postgres secret file
  copy:
    content: "{{ keycloak_postgres_password }}"
    dest: "/opt/keycloak/postgres_password"

- name: Deploy compose templates
  community.docker.docker_compose:
    project_src: "/opt/keycloak/"
    pull: true
    files:
      - "keycloak.yaml"

- name: Check/Wait for Keycloak to be up
  uri:
    url: https://keycloak.atlantishq.de/health
    method: GET
    return_content: yes
    status_code: 200
    body_format: json
  register: result
  until: result.status == 200 and result.json.status == "UP"
  retries: 10
  delay: 20
  check_mode: false

创建 OIDC-客户端

现在让我们使用 oauth2proxy 来保护一个本身不支持 OIDC 的应用程序。如果您已经在使用,Traeffic 也支持此功能。

要创建一个可工作的设置,我们需要:

  • 在 Keycloak 中创建一个 OIDC 客户端
  • 配置并部署一个 oauth2proxy 容器在我们的应用程序之前

我们将使用 Ansible 模块 keycloak-client 作为 local_action

首先,让我们为我们的客户端定义必要的变量,并以一种可以轻松部署多个客户端的方式构建 Ansible 任务。

请注意,这些机密需要精确给定的长度,您应该使用所提供的命令(由同名包提供)来创建它们,这是您可以在 Ansible 中执行的操作,但我会将它们静态配置为变量。

# 这将放入 group_vars/all.yaml
keycloak_clients:
    client_name:
        party_secret: "$(pwgen -s 16 -n 1)"
        client_id: name_of_your_client
        client_secret: "$(pwgen -s 32 -n 1)"
        redirect_uris:
            - "https://target_subdomain.example.com/*"
        description: "在 Keycloak 中显示的描述"
        keycloak_id: "00000000-0000-0000-0000-000000000001"
        groups:
        # groups: "group1,group2"
        master_address: "https://target_subdomain.example.com"
        skips:
            - "/logo/light.svg"
  • groups 可选地定义了用户必须是其一部分才能继续的一组组
  • redirect_uris 定义了一组允许的重定向 URL,这意味着在登录后将重定向到的 URL。这很重要,因为页面在重定向到登录页面时可以请求任意重定向 URL。
  • master_address 定义了默认重定向,如果在登录请求中没有给出
  • skips 可选地定义了一个路径列表,这些路径将在未经身份验证的情况下转发,您可以将其用于健康端点、图标或其他未经特权的页面

现在我们在任务中使用 Ansible 列表 keycloak_clients 来为我们的部署创建和更新这些客户端,就像这样:

# 这将放入 roles/keycloak/task/main.yaml,在等待任务完成之后
- name: Create Keycloak Clients
  local_action:
    module: keycloak_client
    auth_client_id: admin-cli
    auth_keycloak_url: https://keycloak.atlantishq.de/
    auth_realm: master
    auth_username: admin
    auth_password: "{{ keycloak_admin_password }}"
    state: present
    realm: master
    client_id: '{{ keycloak_clients[item]["client_id"] }}'
    id: '{{ keycloak_clients[item]["keycloak_id"] }}'
    name: '{{ keycloak_clients[item]["client_id"] }}'
    description: '{{ keycloak_clients[item]["description"] }}'
    enabled: True
    client_authenticator_type: client-secret
    public_client: false
    secret: '{{ keycloak_clients[item]["client_secret"] }}'
    authorization_services_enabled: true
    service_accounts_enabled: true
    redirect_uris: '{{ keycloak_clients[item]["redirect_uris"] }}'
    web_origins: '{{ keycloak_clients[item]["redirect_uris"] }}'
    frontchannel_logout: False
    protocol: openid-connect
    
    # >> 下面解释 <<
    protocol_mappers:
      - config:
            accesss.token.claim: true
            claim.name: "groups"
            id.token.claim: true
            userinfo.token.claim: true
            full.path: false
        id: "{{ keycloak_clients[item]['keycloak_id'] | regex_replace('^(?P<X>.{2})(.)', '\\g<X>' ~ '1') }}"
        consentRequired: false
        protocol: "openid-connect"
        protocolMapper: "oidc-group-membership-mapper"
        name: "client-group-mapper"
      - config:
            included.client.audience: '{{ keycloak_clients[item]["client_id"] }}'
            id.token.claim: false
            access.token.claim: true
        id: "{{ keycloak_clients[item]['keycloak_id'] | regex_replace('^(?P<X>.{2})(.)', '\\g<X>' ~ '2') }}"
        consentRequired: false
        protocol: "openid-connect"
        protocolMapper: "oidc-audience-mapper"
        name: "aud-mapper-client"
  with_items: "{{ keycloak_clients.keys() | list }}"

该任务对我们在之前步骤中在 group_vars/all.yaml 中定义的 keycloak_clients 列表进行迭代。第一部分应该相当明了。但第二部分呢?

这些是所谓的 OIDC 范围声明 ,简而言之,它们定义了应该传递给客户端的 OIDC 服务器上的信息。在我们的情况下,我们传递了两个特殊信息:

  • 用户所属的组
  • 预期的“受众”即我们正在进行身份验证的客户端的名称(oauth2proxy 在内部需要)

regex_replace 可能看起来很奇怪,但它只用一个 12 分别替换 ID 中的单个数字,以便为每个客户端创建唯一的关联 ID。意思是:

# keycloak 客户端基础 ID
00000000-0000-0000-0000-000000000001
# 变为
00100000-0000-0000-0000-000000000001
# 和
00200000-0000-0000-0000-000000000001

..您可以稍后使用类似的策略来管理更复杂映射器或声明的 ID。

创建 oauth2proxy 部署

准备工作完成后,我们现在终于可以部署一个带有应用程序的 oauth2proxy 容器。为此,首先再次创建一个 compose 模板(请注意 UPSTREAM 地址和端口,必须是目标应用程序正在运行的端口和地址):

version: "3.7"
services:
  oauth2-proxy-{{ item }}:
    image: bitnami/oauth2-proxy:7.3.0
    depends_on:
      - redis
    restart: always
    command:
{% if keycloak_clients[item].get("skips") %}
{% for route in keycloak_clients[item].skips %}
      - --skip-auth-route
      - {{ route }}
{% endfor %}
{% endif %}
      - --http-address
      - 0.0.0.0:{{ services[item].port }}
    ports:
      - {{ services[item].port }}:{{ services[item].port }}
    environment:
      OAUTH2_PROXY_SCOPE: openid email profile
      OAUTH2_PROXY_UPSTREAMS: http://{{ ansible_default_ipv4.address }}:5000
      OAUTH2_PROXY_EMAIL_DOMAINS: '*'
      OAUTH2_PROXY_PROVIDER: keycloak-oidc
      OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: "Display Name"
      OAUTH2_PROXY_REDIRECT_URL: "{{ keycloak_clients[item].master_address }}/oauth2/callback"
      OAUTH2_PROXY_OIDC_ISSUER_URL: "https://{{ keycloak_address }}/realms/master"
      OAUTH2_PROXY_CLIENT_ID: "{{ keycloak_clients[item].client_id }}"
      OAUTH2_PROXY_CLIENT_SECRET: "{{ keycloak_clients[item].client_secret }}"

      {% if keycloak_clients[item].groups %}
OAUTH2_PROXY_ALLOWED_GROUPS: {{ keycloak_clients[item].groups }}
      {% endif %}

      OAUTH2_PROXY_OIDC_EMAIL_CLAIM: sub
      OAUTH2_PROXY_SET_XAUTHREQUEST: "true"

      OAUTH2_PROXY_SESSION_STORE_TYPE: redis
      OAUTH2_PROXY_REDIS_CONNECTION_URL: redis://redis

      OAUTH2_PROXY_COOKIE_REFRESH: 15m
      OAUTH2_PROXY_COOKIE_NAME: SESSION
      OAUTH2_PROXY_COOKIE_SECRET: "{{ keycloak_clients[item].party_secret }}"

      OAUTH2_PROXY_REVERSE_PROXY: "true"
      OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "true"

      OAUTH2_PROXY_WHITELIST_DOMAIN: "{{ keycloak_address }}"

  #作为 compose 文件的一部分,我们还需要一个会话存储
  redis:
    image: redis:7.2.4-alpine
    restart: always
    volumes:
      - cache:/data

#没有挂载,因为会话存储是短暂的
volumes:
  cache:
    driver: local

..然后使用 Ansible 任务部署它:

# 这将放入 roles/deployments/tasks/main.yaml
- name: Create opt-dir
  file:
    name: /opt/
    state: directory

- name: OAuth2Proxy directories
  file:
    path: "/opt/oauth2proxy/{{ item }}/"
    state: directory
    recurse: yes
  with_items:
    - client_name

- name: Deploy OAuth2Proxy compose files
  template:
    src: oauth-standalone-docker-compose.yaml
    dest: "/opt/oauth2proxy/{{ item }}/docker-compose.yaml"
  with_items:
    - client_name

- name: Deploy OAuth2Proxy
  community.docker.docker_compose:
    project_src: /opt/oauth2proxy/{{ item }}/
    pull: true
  with_items:
    - client_name

现在,如果您想要进行测试,您可以尝试将其与您的应用程序一起使用,或者通过在正确的端口上运行一个简单的 Web 服务器来尝试如下:

docker run -p 5000:80 nginx

顶级 Ansible Playbook

如果您想要在您的服务器上部署整个系统,我们需要定义一些额外的 Ansible 代码。所有以下文件将会放在您的 Ansible 项目的根目录中(与 group_varsroles 目录所在的同一目录)。

Host.ini

描述一组 主机 的文件,例如:

# host.ini
[keycloak]
192.168.122.1
[deployments]
192.168.122.1

playbook.yaml

描述应在哪个 主机 上运行哪些 角色 的 Playbook 文件:

- hosts: keycloak
  roles:
      - keycloak

- hosts: deployments
  roles:
      - deployments

准备好这些文件后,我们最终可以运行整个系统:

ansible-playbook -i hosts.ini playbook.yaml --diff

© 2011 - 2025 Macro Zhao的分享站

关于我

如遇到加载502错误,请尝试刷新😄

Hi,欢迎访问 Macro Zhao 的博客。Macro Zhao(或 Macro)是我在互联网上经常使用的名字。

我是一个热衷于技术探索和分享的IT工程师,在这里我会记录分享一些关于技术、工作和生活上的事情。

我的CSDN博客:
https://macro-zhao.blog.csdn.net/

欢迎你通过评论或者邮件与我交流。
Mail Me

推荐好玩(You'll Like)
  • AI 动·画
    • 这是一款有趣·免费的能让您画的画中的角色动起来的AI工具。
    • 支持几十种动作生成。
我的项目(My Projects)
  • 爱学习网

  • 小乙日语App

    • 这是一个帮助日语学习者学习日语的App。
      (当然初衷也是为了自用😄)
    • 界面干净,简洁,漂亮!
    • 其中包含 N1 + N2 的全部单词和语法。
    • 不需注册,更不需要订阅!完全免费!
  • 小乙日文阅读器

    • 词汇不够?照样能读日语名著!
    • 越读积累越多,积跬步致千里!
    • 哪里不会点哪里!妈妈再也不担心我读不了原版读物了!
赞助我(Sponsor Me)

如果你喜欢我的作品或者发现它们对你有所帮助,可以考虑给我买一杯咖啡 ☕️。这将激励我在未来创作和分享更多的项目和技术。🦾

👉 请我喝一杯咖啡

If you like my works or find them helpful, please consider buying me a cup of coffee ☕️. It inspires me to create and share more projects in the future. 🦾

👉 Buy me a coffee