Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion galaxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace: cifmw
name: general

# The version of the collection. Must be compatible with semantic versioning
version: 1.0.0
version: 1.0.0+e5fa7b63

# The path to the Markdown (.md) readme file. This path is relative to the root of the collection
readme: README.md
Expand Down
152 changes: 149 additions & 3 deletions roles/federation/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,150 @@
federation
=========
# federation

This role will setup Openstack for user federation. The keycloak system will be used for the IdP provider.
This role sets up OpenStack Keystone federation with Keycloak (Red Hat SSO) as the Identity Provider.

## Overview

The federation role configures:
- Keycloak realm(s) with test users and groups
- Keystone Identity Provider and protocol configuration
- OIDC authentication for OpenStack CLI
- Comprehensive authentication testing

## Supported OIDC Authentication Methods

This role supports testing all OIDC authentication methods available in keystoneauth1:

| Plugin Name | Description | Status |
|-------------|-------------|--------|
| `v3oidcpassword` | Resource Owner Password Credentials flow | ✅ Supported |
| `v3oidcclientcredentials` | Client Credentials flow | ✅ Supported |
| `v3oidcaccesstoken` | Reuse existing access token | ✅ Supported |
| `v3oidcauthcode` | Authorization Code flow | ✅ Supported |
| `v3oidcdeviceauthz` | Device Authorization flow (RFC 8628) | ⚠️ Requires Python 3.10+ |

## Variables

### Infrastructure Configuration

| Variable | Default | Description |
|----------|---------|-------------|
| `cifmw_federation_keycloak_namespace` | `openstack` | Kubernetes namespace for Keycloak |
| `cifmw_federation_run_osp_cmd_namespace` | `openstack` | Kubernetes namespace for openstackclient |
| `cifmw_federation_domain` | - | Base domain for service URLs |

### Keycloak Configuration

| Variable | Default | Description |
|----------|---------|-------------|
| `cifmw_federation_keycloak_realm` | `openstack` | Primary Keycloak realm name |
| `cifmw_federation_keycloak_realm2` | `openstack2` | Secondary realm (multirealm mode) |
| `cifmw_federation_keycloak_admin_username` | `admin` | Keycloak admin username |
| `cifmw_federation_keycloak_admin_password` | `nomoresecrets` | Keycloak admin password |
| `cifmw_federation_deploy_multirealm` | `false` | Deploy multiple realms |

### Test Users

| Variable | Default | Description |
|----------|---------|-------------|
| `cifmw_federation_keycloak_testuser1_username` | `kctestuser1` | Test user 1 username |
| `cifmw_federation_keycloak_testuser1_password` | `nomoresecrets1` | Test user 1 password |
| `cifmw_federation_keycloak_testuser2_username` | `kctestuser2` | Test user 2 username |
| `cifmw_federation_keycloak_testuser2_password` | `nomoresecrets2` | Test user 2 password |

### Keystone Integration

| Variable | Default | Description |
|----------|---------|-------------|
| `cifmw_federation_IdpName` | `kcIDP` | Identity Provider name in Keystone |
| `cifmw_federation_keystone_domain` | `SSO` | Keystone domain for federated users |
| `cifmw_federation_mapping_name` | `SSOmap` | Keystone mapping name |
| `cifmw_federation_project_name` | `SSOproject` | Project for federated users |
| `cifmw_federation_group_name` | `SSOgroup` | Group for federated users |

### OIDC Client Configuration

| Variable | Default | Description |
|----------|---------|-------------|
| `cifmw_federation_keystone_OIDC_ClientID` | `rhoso` | OIDC client ID |
| `cifmw_federation_keystone_OIDC_ClientSecret` | `COX8bmlKAWn56XCGMrKQJj7dgHNAOl6f` | OIDC client secret |
| `cifmw_federation_keystone_OIDC_Scope` | `openid email profile` | OIDC scopes |

### Testing Configuration

| Variable | Default | Description |
|----------|---------|-------------|
| `cifmw_federation_run_oidc_auth_tests` | `true` | Run comprehensive OIDC auth tests |

## Task Files

### Main Tasks

- `hook_pre_deploy.yml` - Deploys Keycloak before OpenStack
- `hook_post_deploy.yml` - Configures federation after OpenStack deployment
- `hook_controlplane_config.yml` - Adds federation config to control plane

### Setup Tasks

- `run_keycloak_setup.yml` - Deploy Keycloak operator and instance
- `run_keycloak_realm_setup.yml` - Configure Keycloak realm, users, and client
- `run_keycloak_client_setup.yml` - Enable advanced client features (Service Accounts, Device Auth)
- `run_openstack_setup.yml` - Configure Keystone IdP and mappings
- `run_openstack_auth_setup.yml` - Deploy authentication scripts to openstackclient pod

### Test Tasks

- `run_openstack_auth_test.yml` - Basic v3oidcpassword authentication test
- `run_openstack_oidc_auth_tests.yml` - Comprehensive OIDC authentication test suite

## Authentication Scripts

The following scripts are deployed to `/home/cloud-admin/` in the openstackclient pod:

| Script | Description |
|--------|-------------|
| `get-token.sh <user>` | Get token using v3oidcpassword |
| `oidc-clientcredentials.sh` | Configure v3oidcclientcredentials auth |
| `oidc-accesstoken.sh <token>` | Configure v3oidcaccesstoken auth |
| `oidc-authcode.sh <code>` | Configure v3oidcauthcode auth |
| `get-keycloak-token.sh` | Helper to obtain tokens from Keycloak |

### Example Usage

```bash
# v3oidcpassword - Password flow
kubectl exec -n openstack openstackclient -- bash -c \
'source /home/cloud-admin/kctestuser1 && openstack token issue'

# v3oidcclientcredentials - Client Credentials flow
kubectl exec -n openstack openstackclient -- bash -c \
'source /home/cloud-admin/oidc-clientcredentials.sh && openstack token issue'

# v3oidcaccesstoken - Access Token flow
ACCESS_TOKEN=$(/home/cloud-admin/get-keycloak-token.sh access_token kctestuser1 nomoresecrets1)
kubectl exec -n openstack openstackclient -- bash -c \
"source /home/cloud-admin/oidc-accesstoken.sh '$ACCESS_TOKEN' && openstack token issue"

# v3oidcauthcode - Authorization Code flow
AUTH_CODE=$(/home/cloud-admin/get-keycloak-token.sh auth_code kctestuser1 nomoresecrets1)
kubectl exec -n openstack openstackclient -- bash -c \
"source /home/cloud-admin/oidc-authcode.sh '$AUTH_CODE' && openstack token issue"
```

## Test Execution

The comprehensive OIDC authentication tests are automatically run during the `hook_post_deploy.yml` phase when `cifmw_federation_run_oidc_auth_tests` is `true` (default).

To run the tests manually:

```yaml
- name: Run OIDC authentication tests
ansible.builtin.include_role:
name: federation
tasks_from: run_openstack_oidc_auth_tests.yml
```

## Notes

- **Device Authorization Flow**: The `v3oidcdeviceauthz` plugin requires keystoneauth1 with Python 3.10+ support. OSP18 ships with Python 3.9 and does not include this plugin.
- **Multirealm**: CLI-based OIDC authentication testing only works in single realm mode. Multirealm federation is supported for Horizon-based authentication.
- **Keycloak Client**: The role automatically enables Service Accounts and Device Authorization on the Keycloak client to support all authentication methods.
17 changes: 17 additions & 0 deletions roles/federation/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,20 @@ cifmw_federation_keystone_idp1_provider_filename: "keycloak-{{ cifmw_federation_
cifmw_federation_keystone_idp2_conf_filename: "keycloak-{{ cifmw_federation_keycloak_namespace }}.{{ cifmw_federation_domain }}%2Fauth%2Frealms%2F{{ cifmw_federation_keycloak_realm2 }}.conf"
cifmw_federation_keystone_idp2_client_filename: "keycloak-{{ cifmw_federation_keycloak_namespace }}.{{ cifmw_federation_domain }}%2Fauth%2Frealms%2F{{ cifmw_federation_keycloak_realm2 }}.client"
cifmw_federation_keystone_idp2_provider_filename: "keycloak-{{ cifmw_federation_keycloak_namespace }}.{{ cifmw_federation_domain }}%2Fauth%2Frealms%2F{{ cifmw_federation_keycloak_realm2 }}.provider"

# =============================================================================
# OIDC AUTHENTICATION TESTING
# =============================================================================
# Configuration for comprehensive OIDC authentication method testing
#
# When enabled, tests all supported OIDC authentication methods:
# - v3oidcpassword: Resource Owner Password Credentials flow
# - v3oidcclientcredentials: Client Credentials flow
# - v3oidcaccesstoken: Access Token Reuse flow
# - v3oidcauthcode: Authorization Code flow
#
# Note: v3oidcdeviceauthz (Device Authorization flow) requires Python 3.10+
# and is not available in OSP18.

# Enable/disable comprehensive OIDC authentication method tests
cifmw_federation_run_oidc_auth_tests: true
23 changes: 23 additions & 0 deletions roles/federation/tasks/hook_post_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,26 @@
- "{{ cifmw_federation_keycloak_testuser1_username }}"
- "{{ cifmw_federation_keycloak_testuser2_username }}"
when: not cifmw_federation_deploy_multirealm|bool

# =============================================================================
# Comprehensive OIDC Authentication Methods Testing
# =============================================================================
# Tests all supported OIDC authentication methods when enabled.
# This requires Keycloak client to be configured for Service Accounts
# and Device Authorization.

- name: Configure Keycloak client for all OIDC auth methods
ansible.builtin.include_role:
name: federation
tasks_from: run_keycloak_client_setup.yml
when:
- not cifmw_federation_deploy_multirealm | bool
- cifmw_federation_run_oidc_auth_tests | default(true) | bool

- name: Run comprehensive OIDC authentication method tests
ansible.builtin.include_role:
name: federation
tasks_from: run_openstack_oidc_auth_tests.yml
when:
- not cifmw_federation_deploy_multirealm | bool
- cifmw_federation_run_oidc_auth_tests | default(true) | bool
116 changes: 116 additions & 0 deletions roles/federation/tasks/run_keycloak_client_setup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
# Copyright Red Hat, Inc.
# 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.

# =============================================================================
# Keycloak Client Configuration for OIDC Authentication Methods
# =============================================================================
# This task configures the Keycloak client to support all OIDC authentication
# methods including:
# - Service Accounts (for Client Credentials flow)
# - Device Authorization (for Device Authorization flow)
#
# Prerequisites:
# - Keycloak must be deployed and accessible
# - The OIDC client (rhoso) must already exist
# =============================================================================

- name: Get Keycloak admin token
ansible.builtin.uri:
url: "{{ cifmw_federation_keycloak_url }}/auth/realms/master/protocol/openid-connect/token"
method: POST
body_format: form-urlencoded
body:
grant_type: password
client_id: admin-cli
username: "{{ cifmw_federation_keycloak_admin_username }}"
password: "{{ cifmw_federation_keycloak_admin_password }}"
validate_certs: "{{ cifmw_federation_keycloak_url_validate_certs }}"
status_code: 200
register: federation_keycloak_admin_token_response

- name: Set admin token fact
ansible.builtin.set_fact:
federation_keycloak_admin_token: "{{ federation_keycloak_admin_token_response.json.access_token }}"

- name: Get current client configuration
ansible.builtin.uri:
url: "{{ cifmw_federation_keycloak_url }}/auth/admin/realms/{{ cifmw_federation_keycloak_realm }}/clients?clientId={{ cifmw_federation_keystone_OIDC_ClientID }}"
method: GET
headers:
Authorization: "Bearer {{ federation_keycloak_admin_token }}"
validate_certs: "{{ cifmw_federation_keycloak_url_validate_certs }}"
status_code: 200
register: federation_keycloak_client_response

- name: Extract client ID
ansible.builtin.set_fact:
federation_keycloak_client_uuid: "{{ federation_keycloak_client_response.json[0].id }}"
when: federation_keycloak_client_response.json | length > 0

- name: Display current client configuration
ansible.builtin.debug:
msg:
- "Client ID: {{ cifmw_federation_keystone_OIDC_ClientID }}"
- "Client UUID: {{ federation_keycloak_client_uuid }}"
- "Service Accounts Enabled: {{ federation_keycloak_client_response.json[0].serviceAccountsEnabled | default(false) }}"
- "Device Authorization: {{ federation_keycloak_client_response.json[0].attributes.get('oauth2.device.authorization.grant.enabled', 'false') | default('false') }}"
when: federation_keycloak_client_response.json | length > 0

- name: Update client to enable Service Accounts and Device Authorization
ansible.builtin.uri:
url: "{{ cifmw_federation_keycloak_url }}/auth/admin/realms/{{ cifmw_federation_keycloak_realm }}/clients/{{ federation_keycloak_client_uuid }}"
method: PUT
headers:
Authorization: "Bearer {{ federation_keycloak_admin_token }}"
Content-Type: application/json
body_format: json
body:
clientId: "{{ cifmw_federation_keystone_OIDC_ClientID }}"
serviceAccountsEnabled: true
directAccessGrantsEnabled: true
standardFlowEnabled: true
implicitFlowEnabled: true
publicClient: false
attributes:
oauth2.device.authorization.grant.enabled: "true"
validate_certs: "{{ cifmw_federation_keycloak_url_validate_certs }}"
status_code: 204
when: federation_keycloak_client_response.json | length > 0
register: federation_keycloak_client_update

- name: Verify updated client configuration
ansible.builtin.uri:
url: "{{ cifmw_federation_keycloak_url }}/auth/admin/realms/{{ cifmw_federation_keycloak_realm }}/clients/{{ federation_keycloak_client_uuid }}"
method: GET
headers:
Authorization: "Bearer {{ federation_keycloak_admin_token }}"
validate_certs: "{{ cifmw_federation_keycloak_url_validate_certs }}"
status_code: 200
register: federation_keycloak_client_updated
when: federation_keycloak_client_response.json | length > 0

- name: Display updated client configuration
ansible.builtin.debug:
msg:
- "Client updated successfully"
- "Service Accounts Enabled: {{ federation_keycloak_client_updated.json.serviceAccountsEnabled }}"
- "Direct Access Grants: {{ federation_keycloak_client_updated.json.directAccessGrantsEnabled }}"
- "Standard Flow: {{ federation_keycloak_client_updated.json.standardFlowEnabled }}"
- "Device Authorization: {{ federation_keycloak_client_updated.json.attributes.get('oauth2.device.authorization.grant.enabled', 'false') }}"
when:
- federation_keycloak_client_response.json | length > 0
- federation_keycloak_client_updated is defined

60 changes: 60 additions & 0 deletions roles/federation/tasks/run_openstack_auth_setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,63 @@
pod: openstackclient
remote_path: "/home/cloud-admin/full-ca-list.crt"
local_path: "{{ [ ansible_user_dir, 'ci-framework-data', 'tmp', 'full-ca-list.crt' ] | path_join }}"

# =============================================================================
# OIDC Authentication Method Scripts
# =============================================================================
# These scripts support testing different OIDC authentication methods:
# - v3oidcclientcredentials: Client Credentials flow
# - v3oidcaccesstoken: Access Token Reuse flow
# - v3oidcauthcode: Authorization Code flow

- name: Render OIDC client credentials script
ansible.builtin.template:
src: oidc-clientcredentials.sh.j2
dest: "{{ [ansible_user_dir, 'ci-framework-data', 'tmp', 'oidc-clientcredentials.sh'] | path_join }}"
mode: '0755'

- name: Copy OIDC client credentials script to pod
kubernetes.core.k8s_cp:
namespace: "{{ cifmw_federation_run_osp_cmd_namespace }}"
pod: openstackclient
remote_path: "/home/cloud-admin/oidc-clientcredentials.sh"
local_path: "{{ [ansible_user_dir, 'ci-framework-data', 'tmp', 'oidc-clientcredentials.sh'] | path_join }}"

- name: Render OIDC access token script
ansible.builtin.template:
src: oidc-accesstoken.sh.j2
dest: "{{ [ansible_user_dir, 'ci-framework-data', 'tmp', 'oidc-accesstoken.sh'] | path_join }}"
mode: '0755'

- name: Copy OIDC access token script to pod
kubernetes.core.k8s_cp:
namespace: "{{ cifmw_federation_run_osp_cmd_namespace }}"
pod: openstackclient
remote_path: "/home/cloud-admin/oidc-accesstoken.sh"
local_path: "{{ [ansible_user_dir, 'ci-framework-data', 'tmp', 'oidc-accesstoken.sh'] | path_join }}"

- name: Render OIDC authorization code script
ansible.builtin.template:
src: oidc-authcode.sh.j2
dest: "{{ [ansible_user_dir, 'ci-framework-data', 'tmp', 'oidc-authcode.sh'] | path_join }}"
mode: '0755'

- name: Copy OIDC authorization code script to pod
kubernetes.core.k8s_cp:
namespace: "{{ cifmw_federation_run_osp_cmd_namespace }}"
pod: openstackclient
remote_path: "/home/cloud-admin/oidc-authcode.sh"
local_path: "{{ [ansible_user_dir, 'ci-framework-data', 'tmp', 'oidc-authcode.sh'] | path_join }}"

- name: Render Keycloak token helper script
ansible.builtin.template:
src: get-keycloak-token.sh.j2
dest: "{{ [ansible_user_dir, 'ci-framework-data', 'tmp', 'get-keycloak-token.sh'] | path_join }}"
mode: '0755'

- name: Copy Keycloak token helper script to pod
kubernetes.core.k8s_cp:
namespace: "{{ cifmw_federation_run_osp_cmd_namespace }}"
pod: openstackclient
remote_path: "/home/cloud-admin/get-keycloak-token.sh"
local_path: "{{ [ansible_user_dir, 'ci-framework-data', 'tmp', 'get-keycloak-token.sh'] | path_join }}"
Loading
Loading