Add Automation Orchestrator Install and setup
This commit is contained in:
286
roles/automation_orchestrator_oidc/tasks/main.yml
Normal file
286
roles/automation_orchestrator_oidc/tasks/main.yml
Normal file
@@ -0,0 +1,286 @@
|
||||
---
|
||||
# Configure Keycloak OIDC login for Automation Orchestrator.
|
||||
#
|
||||
# 1. Keycloak: mapped groups + confidential client with a "groups" claim mapper;
|
||||
# the client secret is read back rather than stored elsewhere.
|
||||
# 2. Backend Deployment: APP_OIDC_ALLOW_PRIVATE_NETWORKS (no CR field exists for it).
|
||||
# 3. AO REST API: create/update the identity provider with group mappings.
|
||||
#
|
||||
# AO identity providers are runtime configuration in the AO database — the
|
||||
# AutomationOrchestrator CR has no auth fields.
|
||||
|
||||
- name: Validate group mappings
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- automation_orchestrator_oidc_group_mappings | length > 0
|
||||
- >-
|
||||
automation_orchestrator_oidc_group_mappings | map(attribute='group')
|
||||
| difference(__automation_orchestrator_oidc_valid_groups) | length == 0
|
||||
fail_msg: >-
|
||||
automation_orchestrator_oidc_group_mappings must map at least one Keycloak group to an
|
||||
AO built-in group ({{ __automation_orchestrator_oidc_valid_groups | join(', ') }});
|
||||
otherwise no Keycloak user can log in.
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 1: Resolve the Automation Orchestrator public URL
|
||||
# ------------------------------------------------------------------
|
||||
- name: Read Automation Orchestrator Route
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: route.openshift.io/v1
|
||||
kind: Route
|
||||
namespace: "{{ automation_orchestrator_oidc_namespace }}"
|
||||
name: "{{ automation_orchestrator_oidc_instance_name }}"
|
||||
register: __automation_orchestrator_oidc_route
|
||||
when: automation_orchestrator_oidc_url | length == 0
|
||||
failed_when: __automation_orchestrator_oidc_route.resources | length == 0
|
||||
|
||||
- name: Set Automation Orchestrator URL
|
||||
ansible.builtin.set_fact:
|
||||
__automation_orchestrator_oidc_url: >-
|
||||
{{ (automation_orchestrator_oidc_url
|
||||
if automation_orchestrator_oidc_url | length > 0
|
||||
else 'https://' + __automation_orchestrator_oidc_route.resources[0].spec.host)
|
||||
| regex_replace('/$', '') }}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 2: Keycloak client and groups
|
||||
# ------------------------------------------------------------------
|
||||
- name: Configure Keycloak
|
||||
module_defaults:
|
||||
community.general.keycloak_group: &keycloak_auth
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: "{{ automation_orchestrator_oidc_keycloak_url }}"
|
||||
auth_realm: master
|
||||
auth_username: "{{ automation_orchestrator_oidc_keycloak_admin_user }}"
|
||||
auth_password: "{{ automation_orchestrator_oidc_keycloak_admin_password }}"
|
||||
validate_certs: "{{ automation_orchestrator_oidc_keycloak_validate_certs }}"
|
||||
community.general.keycloak_clientsecret_info: *keycloak_auth
|
||||
middleware_automation.keycloak.keycloak_client: *keycloak_auth
|
||||
block:
|
||||
- name: Create mapped Keycloak groups
|
||||
community.general.keycloak_group:
|
||||
realm: "{{ automation_orchestrator_oidc_keycloak_realm }}"
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ automation_orchestrator_oidc_group_mappings | map(attribute='idp_group') | unique }}"
|
||||
when: automation_orchestrator_oidc_create_keycloak_groups | bool
|
||||
|
||||
# No secret is passed: Keycloak generates one on create and keeps it on update.
|
||||
- name: Create Automation Orchestrator OIDC client in Keycloak
|
||||
middleware_automation.keycloak.keycloak_client:
|
||||
realm: "{{ automation_orchestrator_oidc_keycloak_realm }}"
|
||||
client_id: "{{ automation_orchestrator_oidc_client_id }}"
|
||||
name: Automation Orchestrator
|
||||
description: OIDC client for Automation Orchestrator at {{ __automation_orchestrator_oidc_url }}
|
||||
enabled: true
|
||||
protocol: openid-connect
|
||||
public_client: false
|
||||
standard_flow_enabled: true
|
||||
implicit_flow_enabled: false
|
||||
direct_access_grants_enabled: false
|
||||
service_accounts_enabled: false
|
||||
root_url: "{{ __automation_orchestrator_oidc_url }}"
|
||||
redirect_uris:
|
||||
- "{{ __automation_orchestrator_oidc_url }}/api/v1/auth/oidc/callback"
|
||||
web_origins:
|
||||
- "+"
|
||||
attributes:
|
||||
post.logout.redirect.uris: "{{ __automation_orchestrator_oidc_url }}##{{ __automation_orchestrator_oidc_url }}/*"
|
||||
protocol_mappers:
|
||||
- name: groups
|
||||
protocol: openid-connect
|
||||
protocolMapper: oidc-group-membership-mapper
|
||||
config:
|
||||
full.path: "false"
|
||||
id.token.claim: "true"
|
||||
access.token.claim: "true"
|
||||
userinfo.token.claim: "true"
|
||||
claim.name: groups
|
||||
state: present
|
||||
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||
|
||||
- name: Read Automation Orchestrator client secret from Keycloak
|
||||
community.general.keycloak_clientsecret_info:
|
||||
realm: "{{ automation_orchestrator_oidc_keycloak_realm }}"
|
||||
client_id: "{{ automation_orchestrator_oidc_client_id }}"
|
||||
register: __automation_orchestrator_oidc_clientsecret
|
||||
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 3: Allow private-network issuers on the backend
|
||||
# ------------------------------------------------------------------
|
||||
- name: Set APP_OIDC_ALLOW_PRIVATE_NETWORKS on backend Deployment
|
||||
kubernetes.core.k8s:
|
||||
state: patched
|
||||
merge_type: strategic-merge
|
||||
api_version: apps/v1
|
||||
kind: Deployment
|
||||
namespace: "{{ automation_orchestrator_oidc_namespace }}"
|
||||
name: "{{ automation_orchestrator_oidc_instance_name }}-backend"
|
||||
definition:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: backend
|
||||
env:
|
||||
- name: APP_OIDC_ALLOW_PRIVATE_NETWORKS
|
||||
value: "{{ automation_orchestrator_oidc_allow_private_networks | string | lower }}"
|
||||
register: __automation_orchestrator_oidc_backend_patch
|
||||
|
||||
- name: Wait for backend rollout
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: apps/v1
|
||||
kind: Deployment
|
||||
namespace: "{{ automation_orchestrator_oidc_namespace }}"
|
||||
name: "{{ automation_orchestrator_oidc_instance_name }}-backend"
|
||||
register: __automation_orchestrator_oidc_backend
|
||||
until: >-
|
||||
__automation_orchestrator_oidc_backend.resources | length > 0 and
|
||||
(__automation_orchestrator_oidc_backend.resources[0].status.observedGeneration | default(0)) >=
|
||||
__automation_orchestrator_oidc_backend.resources[0].metadata.generation and
|
||||
(__automation_orchestrator_oidc_backend.resources[0].status.updatedReplicas | default(0)) ==
|
||||
__automation_orchestrator_oidc_backend.resources[0].spec.replicas and
|
||||
(__automation_orchestrator_oidc_backend.resources[0].status.availableReplicas | default(0)) ==
|
||||
__automation_orchestrator_oidc_backend.resources[0].spec.replicas
|
||||
retries: 30
|
||||
delay: 10
|
||||
when: __automation_orchestrator_oidc_backend_patch is changed
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 4: Register Keycloak as an AO identity provider
|
||||
# ------------------------------------------------------------------
|
||||
- name: Read AO initial admin password
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: v1
|
||||
kind: Secret
|
||||
namespace: "{{ automation_orchestrator_oidc_namespace }}"
|
||||
name: "{{ automation_orchestrator_oidc_instance_name }}-initial-admin-password"
|
||||
register: __automation_orchestrator_oidc_admin_secret
|
||||
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||
|
||||
- name: Log in to the AO API
|
||||
ansible.builtin.uri:
|
||||
url: "{{ __automation_orchestrator_oidc_url }}/api/v1/auth/login"
|
||||
method: POST
|
||||
body_format: json
|
||||
body:
|
||||
username: "{{ automation_orchestrator_oidc_admin_user }}"
|
||||
password: "{{ __automation_orchestrator_oidc_admin_secret.resources[0].data.password | b64decode }}"
|
||||
validate_certs: "{{ automation_orchestrator_oidc_validate_certs }}"
|
||||
register: __automation_orchestrator_oidc_login
|
||||
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||
|
||||
- name: Call the AO API
|
||||
module_defaults:
|
||||
ansible.builtin.uri:
|
||||
headers:
|
||||
Authorization: "Bearer {{ __automation_orchestrator_oidc_login.json.access_token }}"
|
||||
validate_certs: "{{ automation_orchestrator_oidc_validate_certs }}"
|
||||
block:
|
||||
- name: List AO groups
|
||||
ansible.builtin.uri:
|
||||
url: "{{ __automation_orchestrator_oidc_url }}/api/v1/groups?limit=100"
|
||||
register: __automation_orchestrator_oidc_groups
|
||||
|
||||
- name: List AO identity providers
|
||||
ansible.builtin.uri:
|
||||
url: "{{ __automation_orchestrator_oidc_url }}/api/v1/identity_providers?limit=100"
|
||||
register: __automation_orchestrator_oidc_idps
|
||||
|
||||
- name: Build desired identity provider configuration
|
||||
ansible.builtin.set_fact:
|
||||
__automation_orchestrator_oidc_existing: >-
|
||||
{{ __automation_orchestrator_oidc_idps.json.resources
|
||||
| selectattr('name', '==', automation_orchestrator_oidc_provider_name) | first | default({}) }}
|
||||
__automation_orchestrator_oidc_config:
|
||||
provider_type: oidc
|
||||
idp_type: custom
|
||||
auto_discovery: true
|
||||
issuer_url: "{{ __automation_orchestrator_oidc_issuer }}"
|
||||
client_id: "{{ automation_orchestrator_oidc_client_id }}"
|
||||
redirect_uri: "{{ __automation_orchestrator_oidc_url }}/api/v1/auth/oidc/callback"
|
||||
scopes: openid profile email
|
||||
group_jmespath_expression: groups
|
||||
allow_all_authenticated: false
|
||||
enable_rp_initiated_logout: "{{ automation_orchestrator_oidc_rp_initiated_logout | bool }}"
|
||||
__automation_orchestrator_oidc_group_ids: >-
|
||||
{{ __automation_orchestrator_oidc_groups.json.resources | items2dict(key_name='name', value_name='id') }}
|
||||
__automation_orchestrator_oidc_entries: []
|
||||
|
||||
- name: Build group mapping entries
|
||||
ansible.builtin.set_fact:
|
||||
__automation_orchestrator_oidc_entries: >-
|
||||
{{ __automation_orchestrator_oidc_entries + [{
|
||||
'idp_group_value': item.idp_group,
|
||||
'mapped_group_id': __automation_orchestrator_oidc_group_ids[item.group]}] }}
|
||||
loop: "{{ automation_orchestrator_oidc_group_mappings }}"
|
||||
loop_control:
|
||||
label: "{{ item.idp_group }} -> {{ item.group }}"
|
||||
|
||||
# Compare everything except client_secret (never returned by the API).
|
||||
- name: Determine whether the identity provider needs updating
|
||||
ansible.builtin.set_fact:
|
||||
__automation_orchestrator_oidc_needs_update: >-
|
||||
{{ automation_orchestrator_oidc_force_update | bool
|
||||
or not (__automation_orchestrator_oidc_existing.enabled | default(false))
|
||||
or (__automation_orchestrator_oidc_existing.configuration | default({}))
|
||||
!= ((__automation_orchestrator_oidc_existing.configuration | default({}))
|
||||
| combine(__automation_orchestrator_oidc_config))
|
||||
or ((__automation_orchestrator_oidc_existing.configuration.group_mapping_entries | default([])
|
||||
| map(attribute='idp_group_value') | zip(__automation_orchestrator_oidc_existing.configuration.group_mapping_entries
|
||||
| default([]) | map(attribute='mapped_group_id')) | map('join', '=') | sort)
|
||||
!= (__automation_orchestrator_oidc_entries | map(attribute='idp_group_value')
|
||||
| zip(__automation_orchestrator_oidc_entries | map(attribute='mapped_group_id'))
|
||||
| map('join', '=') | sort)) }}
|
||||
when: __automation_orchestrator_oidc_existing | length > 0
|
||||
|
||||
- name: Create Keycloak identity provider in AO
|
||||
ansible.builtin.uri:
|
||||
url: "{{ __automation_orchestrator_oidc_url }}/api/v1/identity_providers"
|
||||
method: POST
|
||||
body_format: json
|
||||
body:
|
||||
name: "{{ automation_orchestrator_oidc_provider_name }}"
|
||||
description: Keycloak realm {{ automation_orchestrator_oidc_keycloak_realm }}
|
||||
configuration: >-
|
||||
{{ __automation_orchestrator_oidc_config | combine({
|
||||
'client_secret': __automation_orchestrator_oidc_clientsecret.clientsecret_info.value,
|
||||
'group_mapping_entries': __automation_orchestrator_oidc_entries}) }}
|
||||
status_code: 201
|
||||
changed_when: true
|
||||
when: __automation_orchestrator_oidc_existing | length == 0
|
||||
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||
|
||||
- name: Update Keycloak identity provider in AO
|
||||
ansible.builtin.uri:
|
||||
url: "{{ __automation_orchestrator_oidc_url }}/api/v1/identity_providers/{{ __automation_orchestrator_oidc_existing.id }}"
|
||||
method: PATCH
|
||||
body_format: json
|
||||
body:
|
||||
enabled: true
|
||||
configuration: >-
|
||||
{{ __automation_orchestrator_oidc_config | combine({
|
||||
'client_secret': __automation_orchestrator_oidc_clientsecret.clientsecret_info.value,
|
||||
'group_mapping_entries': __automation_orchestrator_oidc_entries}) }}
|
||||
changed_when: true
|
||||
when:
|
||||
- __automation_orchestrator_oidc_existing | length > 0
|
||||
- __automation_orchestrator_oidc_needs_update | bool
|
||||
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 5: Summary
|
||||
# ------------------------------------------------------------------
|
||||
- name: Display Automation Orchestrator OIDC summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Keycloak OIDC configured for Automation Orchestrator:"
|
||||
- " Login URL : {{ __automation_orchestrator_oidc_url }}"
|
||||
- " Issuer : {{ __automation_orchestrator_oidc_issuer }}"
|
||||
- " Client : {{ automation_orchestrator_oidc_client_id }}"
|
||||
- " Mappings : {{ automation_orchestrator_oidc_group_mappings | map(attribute='idp_group')
|
||||
| zip(automation_orchestrator_oidc_group_mappings | map(attribute='group'))
|
||||
| map('join', ' -> ') | join(', ') }}"
|
||||
- ""
|
||||
- "Add users to the mapped Keycloak groups to grant access."
|
||||
Reference in New Issue
Block a user