Skip to content

Commit d6d9664

Browse files
pedro-martinsJason Anderson
authored andcommitted
Add support to OpenID Connect Authentication flow
This pull request adds support for the OpenID Connect authentication flow in Keystone and enables both ID and access token authentication flows. The ID token configuration is designed to allow users to authenticate via Horizon using an identity federation; whereas the Access token is used to allow users to authenticate in the OpenStack CLI using a federated user. Without this PR, if one wants to configure OpenStack to use identity federation, he/she needs to do a lot of configurations in the keystone, Horizon, and register quite a good number of different parameters using the CLI such as mappings, identity providers, federated protocols, and so on. Therefore, with this PR, we propose a method for operators to introduce/present the IdP's metadata to Kolla-ansible, and based on the presented metadata, Kolla-ansible takes care of all of the configurations to prepare OpenStack to work in a federated environment. Implements: blueprint add-openid-support Co-Authored-By: Jason Anderson <[email protected]> Change-Id: I0203a3470d7f8f2a54d5e126d947f540d93b8210 (cherry picked from commit f3fbe83)
1 parent 846496f commit d6d9664

File tree

17 files changed

+951
-13
lines changed

17 files changed

+951
-13
lines changed

ansible/group_vars/all.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,7 @@ enable_glance: "{{ enable_openstack_core | bool }}"
571571
enable_haproxy: "yes"
572572
enable_keepalived: "{{ enable_haproxy | bool }}"
573573
enable_keystone: "{{ enable_openstack_core | bool }}"
574+
enable_keystone_federation: "{{ (keystone_identity_providers | length > 0) and (keystone_identity_mappings | length > 0) }}"
574575
enable_mariadb: "yes"
575576
enable_memcached: "yes"
576577
enable_neutron: "{{ enable_openstack_core | bool }}"
@@ -1041,6 +1042,7 @@ enable_neutron_horizon_policy_file: "{{ enable_neutron }}"
10411042
enable_nova_horizon_policy_file: "{{ enable_nova }}"
10421043

10431044
horizon_internal_endpoint: "{{ internal_protocol }}://{{ kolla_internal_fqdn | put_address_in_context('url') }}:{{ horizon_tls_port if kolla_enable_tls_internal | bool else horizon_port }}"
1045+
horizon_public_endpoint: "{{ public_protocol }}://{{ kolla_external_fqdn | put_address_in_context('url') }}:{{ horizon_tls_port if kolla_enable_tls_external | bool else horizon_port }}"
10441046

10451047
#################
10461048
# Qinling options
@@ -1196,3 +1198,45 @@ swift_public_endpoint: "{{ public_protocol }}://{{ swift_external_fqdn | put_add
11961198
octavia_admin_endpoint: "{{ admin_protocol }}://{{ octavia_internal_fqdn | put_address_in_context('url') }}:{{ octavia_api_port }}"
11971199
octavia_internal_endpoint: "{{ internal_protocol }}://{{ octavia_internal_fqdn | put_address_in_context('url') }}:{{ octavia_api_port }}"
11981200
octavia_public_endpoint: "{{ public_protocol }}://{{ octavia_external_fqdn | put_address_in_context('url') }}:{{ octavia_api_port }}"
1201+
1202+
###################################
1203+
# Identity federation configuration
1204+
###################################
1205+
# Here we configure all of the IdPs meta informations that will be required to implement identity federation with OpenStack Keystone.
1206+
# We require the administrator to enter the following metadata:
1207+
# * name (internal name of the IdP in Keystone);
1208+
# * openstack_domain (the domain in Keystone that the IdP belongs to)
1209+
# * protocol (the federated protocol used by the IdP; e.g. openid or saml);
1210+
# * identifier (the IdP identifier; e.g. https://accounts.google.com);
1211+
# * public_name (the public name that will be shown for users in Horizon);
1212+
# * attribute_mapping (the attribute mapping to be used for this IdP. This mapping is configured in the "keystone_identity_mappings" configuration);
1213+
# * metadata_folder (folder containing all the identity provider metadata as jsons named as the identifier without the protocol
1214+
# and with '/' escaped as %2F followed with '.provider' or '.client' or '.conf'; e.g. accounts.google.com.provider; PS, all .conf,
1215+
# .provider and .client jsons must be in the folder, even if you dont override any conf in the .conf json, you must leave it as an empty json '{}');
1216+
# * certificate_file (the path to the Identity Provider certificate file, the file must be named as 'certificate-key-id.pem';
1217+
# e.g. LRVweuT51StjMdsna59jKfB3xw0r8Iz1d1J1HeAbmlw.pem; You can find the key-id in the Identity provider '.well-known/openid-configuration' jwks_uri as kid);
1218+
#
1219+
# The IdPs meta information are to be presented to Kolla-Ansible as the following example:
1220+
# keystone_identity_providers:
1221+
# - name: "myidp1"
1222+
# openstack_domain: "my-domain"
1223+
# protocol: "openid"
1224+
# identifier: "https://accounts.google.com"
1225+
# public_name: "Authenticate via myidp1"
1226+
# attribute_mapping: "mappingId1"
1227+
# metadata_folder: "path/to/metadata/folder"
1228+
# certificate_file: "path/to/certificate/file.pem"
1229+
#
1230+
# We also need to configure the attribute mapping that is used by IdPs.
1231+
# The configuration of attribute mappings is a list of objects, where each
1232+
# object must have a 'name' (that mapps to the 'attribute_mapping' to the IdP
1233+
# object in the IdPs set), and the 'file' with a full qualified path to a mapping file.
1234+
# keystone_identity_mappings:
1235+
# - name: "mappingId1"
1236+
# file: "/full/qualified/path/to/mapping/json/file/to/mappingId1"
1237+
# - name: "mappingId2"
1238+
# file: "/full/qualified/path/to/mapping/json/file/to/mappingId2"
1239+
# - name: "mappingId3"
1240+
# file: "/full/qualified/path/to/mapping/json/file/to/mappingId3"
1241+
keystone_identity_providers: []
1242+
keystone_identity_mappings: []

ansible/roles/haproxy-config/defaults/main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ haproxy_backend_tcp_extra: []
1515

1616
haproxy_health_check: "check inter 2000 rise 2 fall 5"
1717
haproxy_health_check_ssl: "check check-ssl inter 2000 rise 2 fall 5"
18+
19+
haproxy_enable_federation_openid: "{{ keystone_identity_providers | selectattr('protocol','equalto','openid') | list | count > 0 }}"

ansible/roles/horizon/defaults/main.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ horizon_extra_volumes: "{{ default_extra_volumes }}"
126126
# OpenStack
127127
####################
128128
horizon_logging_debug: "{{ openstack_logging_debug }}"
129-
horizon_keystone_url: "{{ keystone_internal_url }}/v3"
129+
horizon_keystone_url: "{{ keystone_public_url if horizon_use_keystone_public_url | bool else keystone_internal_url }}/v3"
130130

131131

132132
####################
@@ -152,3 +152,9 @@ horizon_murano_source_version: "{{ kolla_source_version }}"
152152
# TLS
153153
####################
154154
horizon_enable_tls_backend: "{{ kolla_enable_tls_backend }}"
155+
156+
# This variable was created for administrators to define which one of the Keystone's URLs should be configured in Horizon.
157+
# In some cases, such as when using OIDC, horizon will need to be configured with Keystone's public URL.
158+
# Therefore, instead of overriding the whole "horizon_keystone_url", this change allows an easier integration because
159+
# the Keystone public URL is already defined with variable "keystone_public_url".
160+
horizon_use_keystone_public_url: False

ansible/roles/horizon/templates/local_settings.j2

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,9 @@ OPENSTACK_HOST = "{{ kolla_internal_fqdn }}"
209209
OPENSTACK_KEYSTONE_URL = "{{ horizon_keystone_url }}"
210210
OPENSTACK_KEYSTONE_DEFAULT_ROLE = "{{ keystone_default_user_role }}"
211211

212+
{% if enable_keystone_federation | bool %}
212213
# Enables keystone web single-sign-on if set to True.
213-
#WEBSSO_ENABLED = False
214+
WEBSSO_ENABLED = True
214215

215216
# Determines which authentication choice to show as default.
216217
#WEBSSO_INITIAL_CHOICE = "credentials"
@@ -223,13 +224,13 @@ OPENSTACK_KEYSTONE_DEFAULT_ROLE = "{{ keystone_default_user_role }}"
223224
# Do not remove the mandatory credentials mechanism.
224225
# Note: The last two tuples are sample mapping keys to a identity provider
225226
# and federation protocol combination (WEBSSO_IDP_MAPPING).
226-
#WEBSSO_CHOICES = (
227-
# ("credentials", _("Keystone Credentials")),
228-
# ("oidc", _("OpenID Connect")),
229-
# ("saml2", _("Security Assertion Markup Language")),
230-
# ("acme_oidc", "ACME - OpenID Connect"),
231-
# ("acme_saml2", "ACME - SAML2"),
232-
#)
227+
WEBSSO_KEYSTONE_URL = "{{ keystone_public_url }}/v3"
228+
WEBSSO_CHOICES = (
229+
("credentials", _("Keystone Credentials")),
230+
{% for idp in keystone_identity_providers %}
231+
("{{ idp.name }}_{{ idp.protocol }}", "{{ idp.public_name }}"),
232+
{% endfor %}
233+
)
233234

234235
# A dictionary of specific identity provider and federation protocol
235236
# combinations. From the selected authentication mechanism, the value
@@ -238,10 +239,12 @@ OPENSTACK_KEYSTONE_DEFAULT_ROLE = "{{ keystone_default_user_role }}"
238239
# specific WebSSO endpoint in keystone, otherwise it will use the value
239240
# as the protocol_id when redirecting to the WebSSO by protocol endpoint.
240241
# NOTE: The value is expected to be a tuple formatted as: (<idp_id>, <protocol_id>).
241-
#WEBSSO_IDP_MAPPING = {
242-
# "acme_oidc": ("acme", "oidc"),
243-
# "acme_saml2": ("acme", "saml2"),
244-
#}
242+
WEBSSO_IDP_MAPPING = {
243+
{% for idp in keystone_identity_providers %}
244+
"{{ idp.name }}_{{ idp.protocol }}": ("{{ idp.name }}", "{{ idp.protocol }}"),
245+
{% endfor %}
246+
}
247+
{% endif %}
245248

246249
# Disable SSL certificate checks (useful for self-signed certificates):
247250
#OPENSTACK_SSL_NO_VERIFY = True

ansible/roles/keystone/defaults/main.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ keystone_services:
1818
tls_backend: "{{ keystone_enable_tls_backend }}"
1919
port: "{{ keystone_public_port }}"
2020
listen_port: "{{ keystone_public_listen_port }}"
21+
backend_http_extra: "{{ ['balance source'] if enable_keystone_federation | bool else [] }}"
2122
keystone_external:
2223
enabled: "{{ enable_keystone }}"
2324
mode: "http"
2425
external: true
2526
tls_backend: "{{ keystone_enable_tls_backend }}"
2627
port: "{{ keystone_public_port }}"
2728
listen_port: "{{ keystone_public_listen_port }}"
29+
backend_http_extra: "{{ ['balance source'] if enable_keystone_federation | bool else [] }}"
2830
keystone_admin:
2931
enabled: "{{ enable_keystone }}"
3032
mode: "http"
@@ -179,3 +181,23 @@ keystone_ks_services:
179181
# TLS
180182
####################
181183
keystone_enable_tls_backend: "{{ kolla_enable_tls_backend }}"
184+
185+
###############################
186+
# OpenStack identity federation
187+
###############################
188+
# Default OpenID Connect remote attribute key
189+
keystone_remote_id_attribute_oidc: "HTTP_OIDC_ISS"
190+
keystone_container_federation_oidc_metadata_folder: "{{ '/etc/apache2/metadata' if kolla_base_distro in ['debian', 'ubuntu'] else '/etc/httpd/metadata' }}"
191+
keystone_container_federation_oidc_idp_certificate_folder: "{{ '/etc/apache2/cert' if kolla_base_distro in ['debian', 'ubuntu'] else '/etc/httpd/cert' }}"
192+
keystone_container_federation_oidc_attribute_mappings_folder: "{{ container_config_directory }}/federation/oidc/attribute_maps"
193+
keystone_host_federation_oidc_metadata_folder: "{{ node_config_directory }}/keystone/federation/oidc/metadata"
194+
keystone_host_federation_oidc_idp_certificate_folder: "{{ node_config_directory }}/keystone/federation/oidc/cert"
195+
keystone_host_federation_oidc_attribute_mappings_folder: "{{ node_config_directory }}/keystone/federation/oidc/attribute_maps"
196+
197+
# These variables are used to define multiple trusted Horizon dashboards.
198+
# keystone_trusted_dashboards: ['<https://dashboardServerOne/auth/websso/>', '<https://dashboardServerTwo/auth/websso/>', '<https://dashboardServerN/auth/websso/>']
199+
keystone_trusted_dashboards: "{{ ['%s://%s/auth/websso/' % (public_protocol, kolla_external_fqdn), '%s/auth/websso/' % (horizon_public_endpoint)] if enable_horizon | bool else [] }}"
200+
keystone_enable_federation_openid: "{{ enable_keystone_federation | bool and keystone_identity_providers | selectattr('protocol','equalto','openid') | list | count > 0 }}"
201+
keystone_should_remove_attribute_mappings: False
202+
keystone_should_remove_identity_providers: False
203+
keystone_federation_oidc_scopes: "openid email profile"
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
- name: Remove OpenID certificate and metadata files
3+
become: true
4+
vars:
5+
keystone: "{{ keystone_services['keystone'] }}"
6+
file:
7+
state: absent
8+
path: "{{ item }}"
9+
when:
10+
- inventory_hostname in groups[keystone.group]
11+
with_items:
12+
- "{{ keystone_host_federation_oidc_metadata_folder }}"
13+
- "{{ keystone_host_federation_oidc_idp_certificate_folder }}"
14+
- "{{ keystone_host_federation_oidc_attribute_mappings_folder }}"
15+
16+
- name: Create OpenID configuration directories
17+
vars:
18+
keystone: "{{ keystone_services['keystone'] }}"
19+
file:
20+
dest: "{{ item }}"
21+
state: "directory"
22+
mode: "0770"
23+
become: true
24+
with_items:
25+
- "{{ keystone_host_federation_oidc_metadata_folder }}"
26+
- "{{ keystone_host_federation_oidc_idp_certificate_folder }}"
27+
- "{{ keystone_host_federation_oidc_attribute_mappings_folder }}"
28+
when:
29+
- inventory_hostname in groups[keystone.group]
30+
31+
- name: Copying OpenID Identity Providers metadata
32+
vars:
33+
keystone: "{{ keystone_services['keystone'] }}"
34+
become: true
35+
copy:
36+
src: "{{ item.metadata_folder }}/"
37+
dest: "{{ keystone_host_federation_oidc_metadata_folder }}"
38+
mode: "0660"
39+
with_items: "{{ keystone_identity_providers }}"
40+
when:
41+
- item.protocol == 'openid'
42+
- inventory_hostname in groups[keystone.group]
43+
44+
- name: Copying OpenID Identity Providers certificate
45+
vars:
46+
keystone: "{{ keystone_services['keystone'] }}"
47+
become: true
48+
copy:
49+
src: "{{ item.certificate_file }}"
50+
dest: "{{ keystone_host_federation_oidc_idp_certificate_folder }}"
51+
mode: "0660"
52+
with_items: "{{ keystone_identity_providers }}"
53+
when:
54+
- item.protocol == 'openid'
55+
- inventory_hostname in groups[keystone.group]
56+
57+
- name: Copying OpenStack Identity Providers attribute mappings
58+
vars:
59+
keystone: "{{ keystone_services['keystone'] }}"
60+
become: true
61+
copy:
62+
src: "{{ item.file }}"
63+
dest: "{{ keystone_host_federation_oidc_attribute_mappings_folder }}/{{ item.file | basename }}"
64+
mode: "0660"
65+
with_items: "{{ keystone_identity_mappings }}"
66+
when:
67+
- inventory_hostname in groups[keystone.group]
68+
69+
- name: Setting the certificates files variable
70+
become: true
71+
vars:
72+
keystone: "{{ keystone_services['keystone'] }}"
73+
find:
74+
path: "{{ keystone_host_federation_oidc_idp_certificate_folder }}"
75+
pattern: "*.pem"
76+
register: certificates_path
77+
when:
78+
- inventory_hostname in groups[keystone.group]
79+
80+
- name: Setting the certificates variable
81+
vars:
82+
keystone: "{{ keystone_services['keystone'] }}"
83+
set_fact:
84+
keystone_federation_openid_certificate_key_ids: "{{ certificates_path.files | map(attribute='path') | map('regex_replace', '^.*/(.*)\\.pem$', '\\1#' + keystone_container_federation_oidc_idp_certificate_folder + '/\\1.pem') | list }}" # noqa 204
85+
when:
86+
- inventory_hostname in groups[keystone.group]

ansible/roles/keystone/tasks/config.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@
144144
notify:
145145
- Restart {{ item.key }} container
146146

147+
- include_tasks: config-federation-oidc.yml
148+
when:
149+
- keystone_enable_federation_openid | bool
150+
147151
- name: Copying over wsgi-keystone.conf
148152
vars:
149153
keystone: "{{ keystone_services.keystone }}"

ansible/roles/keystone/tasks/deploy.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@
1919
- import_tasks: register.yml
2020

2121
- import_tasks: check.yml
22+
23+
- include_tasks: register_identity_providers.yml
24+
when:
25+
- enable_keystone_federation | bool

0 commit comments

Comments
 (0)