Merge "Add support to OpenID Connect Authentication flow"

This commit is contained in:
Zuul 2021-02-19 23:15:07 +00:00 committed by Gerrit Code Review
commit 87d8bd414d
17 changed files with 951 additions and 13 deletions

View File

@ -557,6 +557,7 @@ enable_glance: "{{ enable_openstack_core | bool }}"
enable_haproxy: "yes"
enable_keepalived: "{{ enable_haproxy | bool }}"
enable_keystone: "{{ enable_openstack_core | bool }}"
enable_keystone_federation: "{{ (keystone_identity_providers | length > 0) and (keystone_identity_mappings | length > 0) }}"
enable_mariadb: "yes"
enable_memcached: "yes"
enable_neutron: "{{ enable_openstack_core | bool }}"
@ -1011,6 +1012,7 @@ enable_neutron_horizon_policy_file: "{{ enable_neutron }}"
enable_nova_horizon_policy_file: "{{ enable_nova }}"
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 }}"
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 }}"
###################
# External Ceph options
@ -1158,3 +1160,45 @@ swift_public_endpoint: "{{ public_protocol }}://{{ swift_external_fqdn | put_add
octavia_admin_endpoint: "{{ admin_protocol }}://{{ octavia_internal_fqdn | put_address_in_context('url') }}:{{ octavia_api_port }}"
octavia_internal_endpoint: "{{ internal_protocol }}://{{ octavia_internal_fqdn | put_address_in_context('url') }}:{{ octavia_api_port }}"
octavia_public_endpoint: "{{ public_protocol }}://{{ octavia_external_fqdn | put_address_in_context('url') }}:{{ octavia_api_port }}"
###################################
# Identity federation configuration
###################################
# Here we configure all of the IdPs meta informations that will be required to implement identity federation with OpenStack Keystone.
# We require the administrator to enter the following metadata:
# * name (internal name of the IdP in Keystone);
# * openstack_domain (the domain in Keystone that the IdP belongs to)
# * protocol (the federated protocol used by the IdP; e.g. openid or saml);
# * identifier (the IdP identifier; e.g. https://accounts.google.com);
# * public_name (the public name that will be shown for users in Horizon);
# * attribute_mapping (the attribute mapping to be used for this IdP. This mapping is configured in the "keystone_identity_mappings" configuration);
# * metadata_folder (folder containing all the identity provider metadata as jsons named as the identifier without the protocol
# and with '/' escaped as %2F followed with '.provider' or '.client' or '.conf'; e.g. accounts.google.com.provider; PS, all .conf,
# .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 '{}');
# * certificate_file (the path to the Identity Provider certificate file, the file must be named as 'certificate-key-id.pem';
# e.g. LRVweuT51StjMdsna59jKfB3xw0r8Iz1d1J1HeAbmlw.pem; You can find the key-id in the Identity provider '.well-known/openid-configuration' jwks_uri as kid);
#
# The IdPs meta information are to be presented to Kolla-Ansible as the following example:
# keystone_identity_providers:
# - name: "myidp1"
# openstack_domain: "my-domain"
# protocol: "openid"
# identifier: "https://accounts.google.com"
# public_name: "Authenticate via myidp1"
# attribute_mapping: "mappingId1"
# metadata_folder: "path/to/metadata/folder"
# certificate_file: "path/to/certificate/file.pem"
#
# We also need to configure the attribute mapping that is used by IdPs.
# The configuration of attribute mappings is a list of objects, where each
# object must have a 'name' (that mapps to the 'attribute_mapping' to the IdP
# object in the IdPs set), and the 'file' with a full qualified path to a mapping file.
# keystone_identity_mappings:
# - name: "mappingId1"
# file: "/full/qualified/path/to/mapping/json/file/to/mappingId1"
# - name: "mappingId2"
# file: "/full/qualified/path/to/mapping/json/file/to/mappingId2"
# - name: "mappingId3"
# file: "/full/qualified/path/to/mapping/json/file/to/mappingId3"
keystone_identity_providers: []
keystone_identity_mappings: []

View File

@ -15,3 +15,5 @@ haproxy_backend_tcp_extra: []
haproxy_health_check: "check inter 2000 rise 2 fall 5"
haproxy_health_check_ssl: "check check-ssl inter 2000 rise 2 fall 5"
haproxy_enable_federation_openid: "{{ keystone_identity_providers | selectattr('protocol','equalto','openid') | list | count > 0 }}"

View File

@ -123,7 +123,7 @@ horizon_extra_volumes: "{{ default_extra_volumes }}"
# OpenStack
####################
horizon_logging_debug: "{{ openstack_logging_debug }}"
horizon_keystone_url: "{{ keystone_internal_url }}/v3"
horizon_keystone_url: "{{ keystone_public_url if horizon_use_keystone_public_url | bool else keystone_internal_url }}/v3"
####################
@ -149,3 +149,9 @@ horizon_murano_source_version: "{{ kolla_source_version }}"
# TLS
####################
horizon_enable_tls_backend: "{{ kolla_enable_tls_backend }}"
# This variable was created for administrators to define which one of the Keystone's URLs should be configured in Horizon.
# In some cases, such as when using OIDC, horizon will need to be configured with Keystone's public URL.
# Therefore, instead of overriding the whole "horizon_keystone_url", this change allows an easier integration because
# the Keystone public URL is already defined with variable "keystone_public_url".
horizon_use_keystone_public_url: False

View File

@ -209,8 +209,9 @@ OPENSTACK_HOST = "{{ kolla_internal_fqdn }}"
OPENSTACK_KEYSTONE_URL = "{{ horizon_keystone_url }}"
OPENSTACK_KEYSTONE_DEFAULT_ROLE = "{{ keystone_default_user_role }}"
{% if enable_keystone_federation | bool %}
# Enables keystone web single-sign-on if set to True.
#WEBSSO_ENABLED = False
WEBSSO_ENABLED = True
# Determines which authentication choice to show as default.
#WEBSSO_INITIAL_CHOICE = "credentials"
@ -223,13 +224,13 @@ OPENSTACK_KEYSTONE_DEFAULT_ROLE = "{{ keystone_default_user_role }}"
# Do not remove the mandatory credentials mechanism.
# Note: The last two tuples are sample mapping keys to a identity provider
# and federation protocol combination (WEBSSO_IDP_MAPPING).
#WEBSSO_CHOICES = (
# ("credentials", _("Keystone Credentials")),
# ("oidc", _("OpenID Connect")),
# ("saml2", _("Security Assertion Markup Language")),
# ("acme_oidc", "ACME - OpenID Connect"),
# ("acme_saml2", "ACME - SAML2"),
#)
WEBSSO_KEYSTONE_URL = "{{ keystone_public_url }}/v3"
WEBSSO_CHOICES = (
("credentials", _("Keystone Credentials")),
{% for idp in keystone_identity_providers %}
("{{ idp.name }}_{{ idp.protocol }}", "{{ idp.public_name }}"),
{% endfor %}
)
# A dictionary of specific identity provider and federation protocol
# combinations. From the selected authentication mechanism, the value
@ -238,10 +239,12 @@ OPENSTACK_KEYSTONE_DEFAULT_ROLE = "{{ keystone_default_user_role }}"
# specific WebSSO endpoint in keystone, otherwise it will use the value
# as the protocol_id when redirecting to the WebSSO by protocol endpoint.
# NOTE: The value is expected to be a tuple formatted as: (<idp_id>, <protocol_id>).
#WEBSSO_IDP_MAPPING = {
# "acme_oidc": ("acme", "oidc"),
# "acme_saml2": ("acme", "saml2"),
#}
WEBSSO_IDP_MAPPING = {
{% for idp in keystone_identity_providers %}
"{{ idp.name }}_{{ idp.protocol }}": ("{{ idp.name }}", "{{ idp.protocol }}"),
{% endfor %}
}
{% endif %}
# Disable SSL certificate checks (useful for self-signed certificates):
#OPENSTACK_SSL_NO_VERIFY = True

View File

@ -18,6 +18,7 @@ keystone_services:
tls_backend: "{{ keystone_enable_tls_backend }}"
port: "{{ keystone_public_port }}"
listen_port: "{{ keystone_public_listen_port }}"
backend_http_extra: "{{ ['balance source'] if enable_keystone_federation | bool else [] }}"
keystone_external:
enabled: "{{ enable_keystone }}"
mode: "http"
@ -25,6 +26,7 @@ keystone_services:
tls_backend: "{{ keystone_enable_tls_backend }}"
port: "{{ keystone_public_port }}"
listen_port: "{{ keystone_public_listen_port }}"
backend_http_extra: "{{ ['balance source'] if enable_keystone_federation | bool else [] }}"
keystone_admin:
enabled: "{{ enable_keystone }}"
mode: "http"
@ -179,3 +181,23 @@ keystone_ks_services:
# TLS
####################
keystone_enable_tls_backend: "{{ kolla_enable_tls_backend }}"
###############################
# OpenStack identity federation
###############################
# Default OpenID Connect remote attribute key
keystone_remote_id_attribute_oidc: "HTTP_OIDC_ISS"
keystone_container_federation_oidc_metadata_folder: "{{ '/etc/apache2/metadata' if kolla_base_distro in ['debian', 'ubuntu'] else '/etc/httpd/metadata' }}"
keystone_container_federation_oidc_idp_certificate_folder: "{{ '/etc/apache2/cert' if kolla_base_distro in ['debian', 'ubuntu'] else '/etc/httpd/cert' }}"
keystone_container_federation_oidc_attribute_mappings_folder: "{{ container_config_directory }}/federation/oidc/attribute_maps"
keystone_host_federation_oidc_metadata_folder: "{{ node_config_directory }}/keystone/federation/oidc/metadata"
keystone_host_federation_oidc_idp_certificate_folder: "{{ node_config_directory }}/keystone/federation/oidc/cert"
keystone_host_federation_oidc_attribute_mappings_folder: "{{ node_config_directory }}/keystone/federation/oidc/attribute_maps"
# These variables are used to define multiple trusted Horizon dashboards.
# keystone_trusted_dashboards: ['<https://dashboardServerOne/auth/websso/>', '<https://dashboardServerTwo/auth/websso/>', '<https://dashboardServerN/auth/websso/>']
keystone_trusted_dashboards: "{{ ['%s://%s/auth/websso/' % (public_protocol, kolla_external_fqdn), '%s/auth/websso/' % (horizon_public_endpoint)] if enable_horizon | bool else [] }}"
keystone_enable_federation_openid: "{{ enable_keystone_federation | bool and keystone_identity_providers | selectattr('protocol','equalto','openid') | list | count > 0 }}"
keystone_should_remove_attribute_mappings: False
keystone_should_remove_identity_providers: False
keystone_federation_oidc_scopes: "openid email profile"

View File

@ -0,0 +1,86 @@
---
- name: Remove OpenID certificate and metadata files
become: true
vars:
keystone: "{{ keystone_services['keystone'] }}"
file:
state: absent
path: "{{ item }}"
when:
- inventory_hostname in groups[keystone.group]
with_items:
- "{{ keystone_host_federation_oidc_metadata_folder }}"
- "{{ keystone_host_federation_oidc_idp_certificate_folder }}"
- "{{ keystone_host_federation_oidc_attribute_mappings_folder }}"
- name: Create OpenID configuration directories
vars:
keystone: "{{ keystone_services['keystone'] }}"
file:
dest: "{{ item }}"
state: "directory"
mode: "0770"
become: true
with_items:
- "{{ keystone_host_federation_oidc_metadata_folder }}"
- "{{ keystone_host_federation_oidc_idp_certificate_folder }}"
- "{{ keystone_host_federation_oidc_attribute_mappings_folder }}"
when:
- inventory_hostname in groups[keystone.group]
- name: Copying OpenID Identity Providers metadata
vars:
keystone: "{{ keystone_services['keystone'] }}"
become: true
copy:
src: "{{ item.metadata_folder }}/"
dest: "{{ keystone_host_federation_oidc_metadata_folder }}"
mode: "0660"
with_items: "{{ keystone_identity_providers }}"
when:
- item.protocol == 'openid'
- inventory_hostname in groups[keystone.group]
- name: Copying OpenID Identity Providers certificate
vars:
keystone: "{{ keystone_services['keystone'] }}"
become: true
copy:
src: "{{ item.certificate_file }}"
dest: "{{ keystone_host_federation_oidc_idp_certificate_folder }}"
mode: "0660"
with_items: "{{ keystone_identity_providers }}"
when:
- item.protocol == 'openid'
- inventory_hostname in groups[keystone.group]
- name: Copying OpenStack Identity Providers attribute mappings
vars:
keystone: "{{ keystone_services['keystone'] }}"
become: true
copy:
src: "{{ item.file }}"
dest: "{{ keystone_host_federation_oidc_attribute_mappings_folder }}/{{ item.file | basename }}"
mode: "0660"
with_items: "{{ keystone_identity_mappings }}"
when:
- inventory_hostname in groups[keystone.group]
- name: Setting the certificates files variable
become: true
vars:
keystone: "{{ keystone_services['keystone'] }}"
find:
path: "{{ keystone_host_federation_oidc_idp_certificate_folder }}"
pattern: "*.pem"
register: certificates_path
when:
- inventory_hostname in groups[keystone.group]
- name: Setting the certificates variable
vars:
keystone: "{{ keystone_services['keystone'] }}"
set_fact:
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
when:
- inventory_hostname in groups[keystone.group]

View File

@ -144,6 +144,10 @@
notify:
- Restart {{ item.key }} container
- include_tasks: config-federation-oidc.yml
when:
- keystone_enable_federation_openid | bool
- name: Copying over wsgi-keystone.conf
vars:
keystone: "{{ keystone_services.keystone }}"

View File

@ -19,3 +19,7 @@
- import_tasks: register.yml
- import_tasks: check.yml
- include_tasks: register_identity_providers.yml
when:
- enable_keystone_federation | bool

View File

@ -0,0 +1,238 @@
---
- name: List configured attribute mappings (that can be used by IdPs)
command: >
docker exec -t keystone openstack
--os-auth-url={{ openstack_auth.auth_url }}
--os-password={{ openstack_auth.password }}
--os-username={{ openstack_auth.username }}
--os-project-name={{ openstack_auth.project_name }}
--os-identity-api-version=3
--os-interface {{ openstack_interface }}
--os-project-domain-name {{ openstack_auth.domain_name }}
--os-user-domain-name {{ openstack_auth.domain_name }}
--os-region-name {{ openstack_region_name }}
{% if openstack_cacert != '' %}--os-cacert {{ openstack_cacert }} {% endif %}
mapping list -c ID --format value
run_once: True
become: True
register: existing_mappings_register
- name: Register existing mappings
set_fact:
existing_mappings: "{{ existing_mappings_register.stdout_lines | map('trim') | list }}"
- name: Remove unmanaged attribute mappings
command: >
docker exec -t keystone openstack
--os-auth-url={{ openstack_auth.auth_url }}
--os-password={{ openstack_auth.password }}
--os-username={{ openstack_auth.username }}
--os-project-name={{ openstack_auth.project_name }}
--os-identity-api-version=3
--os-interface {{ openstack_interface }}
--os-project-domain-name {{ openstack_auth.domain_name }}
--os-user-domain-name {{ openstack_auth.domain_name }}
--os-region-name {{ openstack_region_name }}
{% if openstack_cacert != '' %}--os-cacert {{ openstack_cacert }} {% endif %}
mapping delete {{ item }}
run_once: True
become: true
with_items: "{{ existing_mappings }}"
when:
- item not in (keystone_identity_mappings | map(attribute='name') | list)
- keystone_should_remove_attribute_mappings
- name: Create unexisting domains
become: true
kolla_toolbox:
module_name: "os_keystone_domain"
module_args:
name: "{{ item.openstack_domain }}"
auth: "{{ openstack_auth }}"
endpoint_type: "{{ openstack_interface }}"
cacert: "{{ openstack_cacert }}"
region_name: "{{ openstack_region_name }}"
run_once: True
with_items: "{{ keystone_identity_providers }}"
- name: Register attribute mappings in OpenStack
become: true
command: >
docker exec -t keystone openstack
--os-auth-url={{ openstack_auth.auth_url }}
--os-password={{ openstack_auth.password }}
--os-username={{ openstack_auth.username }}
--os-project-name={{ openstack_auth.project_name }}
--os-identity-api-version=3
--os-interface {{ openstack_interface }}
--os-project-domain-name {{ openstack_auth.domain_name }}
--os-user-domain-name {{ openstack_auth.domain_name }}
--os-region-name {{ openstack_region_name }}
{% if openstack_cacert != '' %}--os-cacert {{ openstack_cacert }} {% endif %}
mapping create
--rules "{{ keystone_container_federation_oidc_attribute_mappings_folder }}/{{ item.file | basename }}"
{{ item.name }}
run_once: True
when:
- item.name not in existing_mappings
with_items: "{{ keystone_identity_mappings }}"
- name: Update existing attribute mappings in OpenStack
become: true
command: >
docker exec -t keystone openstack
--os-auth-url={{ openstack_auth.auth_url }}
--os-password={{ openstack_auth.password }}
--os-username={{ openstack_auth.username }}
--os-project-name={{ openstack_auth.project_name }}
--os-identity-api-version=3
--os-interface {{ openstack_interface }}
--os-project-domain-name {{ openstack_auth.domain_name }}
--os-user-domain-name {{ openstack_auth.domain_name }}
--os-region-name {{ openstack_region_name }}
{% if openstack_cacert != '' %}--os-cacert {{ openstack_cacert }} {% endif %}
mapping set
--rules "{{ keystone_container_federation_oidc_attribute_mappings_folder }}/{{ item.file | basename }}"
{{ item.name }}
run_once: True
when:
- item.name in existing_mappings
with_items: "{{ keystone_identity_mappings }}"
- name: List configured IdPs
become: true
command: >
docker exec -t keystone openstack
--os-auth-url={{ openstack_auth.auth_url }}
--os-password={{ openstack_auth.password }}
--os-username={{ openstack_auth.username }}
--os-project-name={{ openstack_auth.project_name }}
--os-identity-api-version=3
--os-interface {{ openstack_interface }}
--os-project-domain-name {{ openstack_auth.domain_name }}
--os-user-domain-name {{ openstack_auth.domain_name }}
--os-region-name {{ openstack_region_name }}
{% if openstack_cacert != '' %}--os-cacert {{ openstack_cacert }} {% endif %}
identity provider list -c ID --format value
run_once: True
register: existing_idps_register
- name: Register existing idps
set_fact:
existing_idps: "{{ existing_idps_register.stdout.split('\n') | map('trim') | list }}"
- name: Remove unmanaged identity providers
become: true
command: >
docker exec -t keystone openstack
--os-auth-url={{ openstack_auth.auth_url }}
--os-password={{ openstack_auth.password }}
--os-username={{ openstack_auth.username }}
--os-project-name={{ openstack_auth.project_name }}
--os-identity-api-version=3
--os-interface {{ openstack_interface }}
--os-project-domain-name {{ openstack_auth.domain_name }}
--os-user-domain-name {{ openstack_auth.domain_name }}
--os-region-name {{ openstack_region_name }}
{% if openstack_cacert != '' %}--os-cacert {{ openstack_cacert }} {% endif %}
identity provider delete {{ item }}
run_once: True
with_items: "{{ existing_idps }}"
when:
- item not in (keystone_identity_providers | map(attribute='name') | list)
- keystone_should_remove_identity_providers
- name: Register Identity Providers in OpenStack
become: true
command: >
docker exec -t keystone openstack
--os-auth-url={{ openstack_auth.auth_url }}
--os-password={{ openstack_auth.password }}
--os-username={{ openstack_auth.username }}
--os-project-name={{ openstack_auth.project_name }}
--os-identity-api-version=3
--os-interface {{ openstack_interface }}
--os-project-domain-name {{ openstack_auth.domain_name }}
--os-user-domain-name {{ openstack_auth.domain_name }}
--os-region-name {{ openstack_region_name }}
{% if openstack_cacert != '' %}--os-cacert {{ openstack_cacert }} {% endif %}
identity provider create
--description "{{ item.public_name }}"
--remote-id "{{ item.identifier }}"
--domain "{{ item.openstack_domain }}"
{{ item.name }}
run_once: True
when:
- item.name not in existing_idps
with_items: "{{ keystone_identity_providers }}"
- name: Update Identity Providers in OpenStack according to Kolla-Ansible configuraitons
become: true
command: >
docker exec -t keystone openstack
--os-auth-url={{ openstack_auth.auth_url }}
--os-password={{ openstack_auth.password }}
--os-username={{ openstack_auth.username }}
--os-project-name={{ openstack_auth.project_name }}
--os-identity-api-version=3
--os-interface {{ openstack_interface }}
--os-project-domain-name {{ openstack_auth.domain_name }}
--os-user-domain-name {{ openstack_auth.domain_name }}
--os-region-name {{ openstack_region_name }}
{% if openstack_cacert != '' %}--os-cacert {{ openstack_cacert }} {% endif %}
identity provider set
--description "{{ item.public_name }}"
--remote-id "{{ item.identifier }}"
"{{ item.name }}"
run_once: True
when:
- item.name in existing_idps
with_items: "{{ keystone_identity_providers }}"
- name: Configure attribute mappings for each Identity Provider. (We expect the mappings to be configured by the operator)
become: true
command: >
docker exec -t keystone openstack
--os-auth-url={{ openstack_auth.auth_url }}
--os-password={{ openstack_auth.password }}
--os-username={{ openstack_auth.username }}
--os-project-name={{ openstack_auth.project_name }}
--os-identity-api-version=3
--os-interface {{ openstack_interface }}
--os-project-domain-name {{ openstack_auth.domain_name }}
--os-user-domain-name {{ openstack_auth.domain_name }}
--os-region-name {{ openstack_region_name }}
{% if openstack_cacert != '' %}--os-cacert {{ openstack_cacert }} {% endif %}
federation protocol create
--mapping {{ item.attribute_mapping }}
--identity-provider {{ item.name }}
{{ item.protocol }}
run_once: True
when:
- item.name not in existing_idps
with_items: "{{ keystone_identity_providers }}"
- name: Update attribute mappings for each Identity Provider. (We expect the mappings to be configured by the operator).
become: true
command: >
docker exec -t keystone openstack
--os-auth-url={{ openstack_auth.auth_url }}
--os-password={{ openstack_auth.password }}
--os-username={{ openstack_auth.username }}
--os-project-name={{ openstack_auth.project_name }}
--os-identity-api-version=3
--os-interface {{ openstack_interface }}
--os-project-domain-name {{ openstack_auth.domain_name }}
--os-user-domain-name {{ openstack_auth.domain_name }}
--os-region-name {{ openstack_region_name }}
{% if openstack_cacert != '' %}--os-cacert {{ openstack_cacert }} {% endif %}
federation protocol set
--identity-provider {{ item.name }}
--mapping {{ item.attribute_mapping }}
{{ item.protocol }}
run_once: True
register: result
failed_when: result.rc not in [0, 1] # This command returns RC 1 on success, so we need to add this to avoid fails.
when:
- item.name in existing_idps
with_items: "{{ keystone_identity_providers }}"

View File

@ -77,3 +77,18 @@ connection_string = {{ osprofiler_backend_connection_string }}
[cors]
allowed_origin = {{ grafana_public_endpoint }}
{% endif %}
{% if enable_keystone_federation %}
[federation]
{% for dashboard in keystone_trusted_dashboards %}
trusted_dashboard = {{ dashboard }}
{% endfor %}
sso_callback_template = /etc/keystone/sso_callback_template.html
[openid]
remote_id_attribute = {{ keystone_remote_id_attribute_oidc }}
[auth]
methods = password,token,openid,application_credential
{% endif %}

View File

@ -1,4 +1,5 @@
{% set keystone_dir = 'apache2/conf-enabled' if kolla_base_distro in ['ubuntu', 'debian'] else 'httpd/conf.d' %}
{% set apache_user = 'www-data' if kolla_base_distro in ['ubuntu', 'debian'] else 'apache' %}
{
"command": "/usr/bin/keystone-startup.sh",
"config_files": [
@ -52,6 +53,22 @@
"owner": "keystone",
"perm": "0600"
}{% endif %}
{% if keystone_enable_federation_openid %},
{
"source": "{{ container_config_directory }}/federation/oidc/metadata",
"dest": "{{ keystone_container_federation_oidc_metadata_folder }}",
"owner": "{{ apache_user }}:{{ apache_user }}",
"perm": "0600",
"merge": true
},
{
"source": "{{ container_config_directory }}/federation/oidc/cert",
"dest": "{{ keystone_container_federation_oidc_idp_certificate_folder }}",
"owner": "{{ apache_user }}:{{ apache_user }}",
"perm": "0600",
"merge": true
}
{% endif %}
],
"permissions": [
{
@ -61,7 +78,17 @@
{
"path": "/var/log/kolla/keystone/keystone.log",
"owner": "keystone:keystone"
},{% if keystone_enable_federation_openid %}
{
"path": "{{ keystone_container_federation_oidc_metadata_folder }}",
"owner": "{{ apache_user }}:{{ apache_user }}",
"perm": "0700"
},
{
"path": "{{ keystone_container_federation_oidc_idp_certificate_folder }}",
"owner": "{{ apache_user }}:{{ apache_user }}",
"perm": "0700"
},{% endif %}
{
"path": "/etc/keystone/fernet-keys",
"owner": "keystone:keystone",

View File

@ -51,6 +51,51 @@ LogLevel info
SSLCertificateFile /etc/keystone/certs/keystone-cert.pem
SSLCertificateKeyFile /etc/keystone/certs/keystone-key.pem
{% endif %}
{% if keystone_enable_federation_openid %}
OIDCClaimPrefix "OIDC-"
OIDCClaimDelimiter ";"
OIDCResponseType "id_token"
OIDCScope "{{ keystone_federation_oidc_scopes }}"
OIDCMetadataDir {{ keystone_container_federation_oidc_metadata_folder }}
{% if keystone_federation_openid_certificate_key_ids | length > 0 %}
OIDCOAuthVerifyCertFiles {{ keystone_federation_openid_certificate_key_ids | join(" ") }}
{% endif %}
OIDCCryptoPassphrase {{ keystone_federation_openid_crypto_password }}
OIDCRedirectURI {{ keystone_public_url }}/redirect_uri
<Location ~ "/redirect_uri">
Require valid-user
AuthType openid-connect
</Location>
{# WebSSO authentication endpoint -#}
<Location /v3/auth/OS-FEDERATION/websso/openid>
Require valid-user
AuthType openid-connect
</Location>
{% for idp in keystone_identity_providers %}
{% if idp.protocol == 'openid' %}
<LocationMatch /v3/auth/OS-FEDERATION/identity_providers/{{ idp.name }}/protocols/{{ idp.protocol }}/websso>
Require valid-user
AuthType openid-connect
</LocationMatch>
{% endif %}
{% endfor %}
{# CLI / API authentication endpoint -#}
{% for idp in keystone_identity_providers %}
{% if idp.protocol == 'openid' %}
<LocationMatch /v3/OS-FEDERATION/identity_providers/{{ idp.name }}/protocols/{{ idp.protocol }}/auth>
Require valid-user
{# Note(jasonanderson): `auth-openidc` is a special auth type that can -#}
{# additionally handle verifying bearer tokens -#}
AuthType auth-openidc
</LocationMatch>
{% endif %}
{% endfor %}
{% endif %}
</VirtualHost>
<VirtualHost *:{{ keystone_admin_listen_port }}>

View File

@ -21,3 +21,4 @@ We welcome everyone to join our project!
bug-triage
ptl-guide
release-management
setup-identity-provider

View File

@ -0,0 +1,193 @@
.. _setup-identity-provider:
============================
Test Identity Provider setup
============================
This guide shows how to create an Identity Provider that handles the OpenID
Connect protocol to authenticate users when
:keystone-doc:`using Federation with OpenStack
</admin/federation/configure_federation.html>` (these configurations must not
be used in a production environment).
Keycloak
========
Keycloak is a Java application that implements an Identity Provider handling
both OpenID Connect and SAML protocols.
To setup a Keycloak instance for testing is pretty simple with Docker.
Creating the Docker Keycloak instance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Run the docker command:
.. code-block:: console
docker run -p 8080:8080 -p 8443:8443 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:latest
This will create a Keycloak instance that has the admin credentials as
admin/admin and is listening on port 8080.
After creating the instance, you will need to log in to the Keycloak as
administrator and setup the first Identity Provider.
Creating an Identity Provider with Keycloak
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following guide assumes that the steps are executed from the same machine
(localhost), but you can change the hostname if you want to run it from
elsewhere.
In this guide, we will use the 'new_realm' as the realm name in Keycloak, so,
if you want to use any other realm name, you must to change 'new_realm' in the
URIs used in the guide and replace the 'new_realm' with the realm name that you
are using.
- Access the admin console on http://localhost:8080/auth/ in the Administration Console option.
- Authenticate using the credentials defined in the creation step.
- Create a new realm in the http://localhost:8080/auth/admin/master/console/#/create/realm page.
- After creating a realm, you will need to create a client to be used by Keystone; to do it, just access http://localhost:8080/auth/admin/master/console/#/create/client/new_realm.
- To create a client, you will need to set the client_id (just choose anyone),
the protocol (must be openid-connect) and the Root Url (you can leave it
blank)
- After creating the client, you will need to update some client's attributes
like:
- Enable the Implicit flow (this one allows you to use the OpenStack CLI with
oidcv3 plugin)
- Set Access Type to confidential
- Add the Horizon and Keystone URIs to the Valid Redirect URIs. Keystone should be within the '/redirect_uri' path, for example: https://horizon.com/ and https://keystone.com/redirect_uri
- Save the changes
- Access the client's Mappers tab to add the user's attributes that will be
shared with the client (Keystone):
- In this guide, we will need the following attribute mappers in Keycloak:
==================================== ==============
name/user attribute/token claim name mapper type
==================================== ==============
openstack-user-domain user attribute
openstack-default-project user attribute
==================================== ==============
- After creating the client, you will need to create a user in that realm to
log in OpenStack via identity federation
- To create a user, access http://localhost:8080/auth/admin/master/console/#/create/user/new_realm and fill the form with the user's data
- After creating the user, you can access the tab "Credentials" to set the
user's password
- Then, in the tab "Attributes", you must set the authorization attributes to
be used by Keystone, these attributes are defined in the :ref:`attribute
mapping <attribute_mapping>` in Keystone
After you create the Identity provider, you will need to get some data from the
Identity Provider to configure in Kolla-Ansible
Configuring Kolla Ansible to use the Identity Provider
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section is about how one can get the data needed in
:ref:`Setup OIDC via Kolla Ansible <setup-oidc-kolla-ansible>`.
- name: The realm name, in this case it will be "new_realm"
- identifier: http://localhost:8080/auth/realms/new_realm/ (again, the "new_realm" is the name of the realm)
- certificate_file: This one can be downloaded from http://localhost:8080/auth/admin/master/console/#/realms/new_realm/keys
- metadata_folder:
- localhost%3A8080%2Fauth%2Frealms%2Fnew_realm.client:
- client_id: Access http://localhost:8080/auth/admin/master/console/#/realms/new_realm/clients , and access the client you created for Keystone, copy the Client ID displayed in the page
- client_secret: In the same page you got the client_id, access the tab
"Credentials" and copy the secret value
- localhost%3A8080%2Fauth%2Frealms%2Fnew_realm.provider: Copy the json from http://localhost:8080/auth/realms/new_realm/.well-known/openid-configuration (the "new_realm" is the realm name)
- localhost%3A8080%2Fauth%2Frealms%2Fnew_realm.conf: You can leave this file
as an empty json "{}"
After you finished the configuration of the Identity Provider, your main
configuration should look something like the following:
.. code-block::
keystone_identity_providers:
- name: "new_realm"
openstack_domain: "new_domain"
protocol: "openid"
identifier: "http://localhost:8080/auth/realms/new_realm"
public_name: "Authenticate via new_realm"
attribute_mapping: "attribute_mapping_keycloak_new_realm"
metadata_folder: "/root/inDev/meta-idp"
certificate_file: "/root/inDev/certs/LRVweuT51StjMdsna59jKfB3xw0r8Iz1d1J1HeAbmlw.pem"
keystone_identity_mappings:
- name: "attribute_mapping_keycloak_new_realm"
file: "/root/inDev/attr_map/attribute_mapping.json"
Then, after deploying OpenStack, you should be able to log in Horizon
using the "Authenticate using" -> "Authenticate via new_realm", and writing
"new_realm.com" in the "E-mail or domain name" field. After that, you will be
redirected to a new page to choose the Identity Provider in Keystone. Just click in the link
"localhost:8080/auth/realms/new_realm"; this will redirect you to Keycloak (idP) where
you will need to log in with the user that you created. If the user's
attributes in Keycloak are ok, the user will be created in OpenStack and you will
be able to log in Horizon.
.. _attribute_mapping:
Attribute mapping
~~~~~~~~~~~~~~~~~
This section shows how to create the attribute mapping to map an Identity
Provider user to a Keystone user (ephemeral).
The 'OIDC-' prefix in the remote types is defined in the 'OIDCClaimPrefix'
configuration in the wsgi-keystone.conf file; this prefix must be in the
attribute mapping as the mod-oidc-wsgi is adding the prefix in the user's
attributes before sending it to Keystone. The attribute 'openstack-user-domain'
will define the user's domain in OpenStack and the attribute
'openstack-default-project' will define the user's project in the OpenStack
(the user will be assigned with the role 'member' in the project)
.. code-block:: json
[
{
"local": [
{
"user": {
"name": "{0}",
"email": "{1}",
"domain": {
"name": "{2}"
}
},
"domain": {
"name": "{2}"
},
"projects": [
{
"name": "{3}",
"roles": [
{
"name": "member"
}
]
}
]
}
],
"remote": [
{
"type": "OIDC-preferred_username"
},
{
"type": "OIDC-email"
},
{
"type": "OIDC-openstack-user-domain"
},
{
"type": "OIDC-openstack-default-project"
}
]
}
]

View File

@ -40,3 +40,241 @@ be configured in Keystone as necessary.
Further infomation on Fernet tokens is available in the :keystone-doc:`Keystone
documentation <admin/fernet-token-faq.html>`.
Federated identity
------------------
Keystone allows users to be authenticated via identity federation. This means
integrating OpenStack Keystone with an identity provider. The use of identity
federation allows users to access OpenStack services without the necessity of
an account in the OpenStack environment per se. The authentication is then
off-loaded to the identity provider of the federation.
To enable identity federation, you will need to execute a set of configurations
in multiple OpenStack systems. Therefore, it is easier to use Kolla Ansible
to execute this process for operators.
For upstream documentations, please see
:keystone-doc:`Configuring Keystone for Federation
<admin/federation/configure_federation.html>`
Supported protocols
~~~~~~~~~~~~~~~~~~~
OpenStack supports both OpenID Connect and SAML protocols for federated
identity, but for now, kolla Ansible supports only OpenID Connect.
Therefore, if you desire to use SAML in your environment, you will need
to set it up manually or extend Kolla Ansible to also support it.
.. _setup-oidc-kolla-ansible:
Setting up OpenID Connect via Kolla Ansible
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
First, you will need to register the OpenStack (Keystone) in your Identity
provider as a Service Provider.
After registering Keystone, you will need to add the Identity Provider
configurations in your kolla-ansible globals configuration as the example
below:
.. code-block:: yaml
keystone_identity_providers:
- name: "myidp1"
openstack_domain: "my-domain"
protocol: "openid"
identifier: "https://accounts.google.com"
public_name: "Authenticate via myidp1"
attribute_mapping: "mappingId1"
metadata_folder: "path/to/metadata/folder"
certificate_file: "path/to/certificate/file.pem"
keystone_identity_mappings:
- name: "mappingId1"
file: "/full/qualified/path/to/mapping/json/file/to/mappingId1"
Identity providers configurations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
name
****
The internal name of the Identity provider in OpenStack.
openstack_domain
****************
The OpenStack domain that the Identity Provider belongs.
protocol
********
The federated protocol used by the IdP; e.g. openid or saml. We support only
OpenID connect right now.
identifier
**********
The Identity provider URL; e.g. https://accounts.google.com .
public_name
***********
The Identity provider public name that will be shown for users in the Horizon
login page.
attribute_mapping
*****************
The attribute mapping to be used for the Identity Provider. This mapping is
expected to already exist in OpenStack or be configured in the
`keystone_identity_mappings` property.
metadata_folder
***************
Path to the folder containing all of the identity provider metadata as JSON
files.
The metadata folder must have all your Identity Providers configurations,
the name of the files will be the name (with path) of the Issuer configuration.
Such as:
.. code-block::
- <IDP metadata directory>
- keycloak.example.org%2Fauth%2Frealms%2Fidp.client
|
- keycloak.example.org%2Fauth%2Frealms%2Fidp.conf
|
- keycloak.example.org%2Fauth%2Frealms%2Fidp.provider
.. note::
The name of the file must be URL-encoded if needed. For example, if you have
an Issuer with ``/`` in the URL, then you need to escape it to ``%2F`` by
applying a URL escape in the file name.
The content of these files must be a JSON
``client``:
The ``.client`` file handles the Service Provider credentials in the Issuer.
During the first step, when you registered the OpenStack as a
Service Provider in the Identity Provider, you submitted a `cliend_id` and
generated a `client_secret`, so these are the values you must use in this
JSON file.
.. code-block:: json
{
"client_id":"<openid_client_id>",
"client_secret":"<openid_client_secret>"
}
``conf``:
This file will be a JSON that overrides some of the OpenID Connect options. The
options that can be overridden are listed in the
`OpenID Connect Apache2 plugin documentation`_.
.. _`OpenID Connect Apache2 plugin documentation`: https://github.com/zmartzone/mod_auth_openidc/wiki/Multiple-Providers#opclient-configuration
If you do not want to override the config values, you can leave this file as
an empty JSON file such as ``{}``.
``provider``:
This file will contain all specifications about the IdentityProvider. To
simplify, you can just use the JSON returned in the ``.well-known``
Identity provider's endpoint:
.. code-block:: json
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"revocation_endpoint": "https://oauth2.googleapis.com/revoke",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token",
"none"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid",
"email",
"profile"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_secret_basic"
],
"claims_supported": [
"aud",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"iss",
"locale",
"name",
"picture",
"sub"
],
"code_challenge_methods_supported": [
"plain",
"S256"
]
}
certificate_file
****************
Path to the Identity Provider certificate file, the file must be named as
'certificate-key-id.pem'. E.g.
.. code-block::
- fb8ca5b7d8d9a5c6c6788071e866c6c40f3fc1f9.pem
You can find the key-id in the Identity provider
`.well-known/openid-configuration` `jwks_uri` like in
`https://www.googleapis.com/oauth2/v3/certs` :
.. code-block:: json
{
"keys": [
{
"e": "AQAB",
"use": "sig",
"n": "zK8PHf_6V3G5rU-viUOL1HvAYn7q--dxMoU...",
"kty": "RSA",
"kid": "fb8ca5b7d8d9a5c6c6788071e866c6c40f3fc1f9",
"alg": "RS256"
}
]
}
.. note::
The public key is different from the certificate, the file in this
configuration must be the Identity provider's certificate and not the
Identity provider's public key.

View File

@ -252,3 +252,8 @@ redis_master_password:
####################
prometheus_mysql_exporter_database_password:
prometheus_alertmanager_password:
###############################
# OpenStack identity federation
###############################
keystone_federation_openid_crypto_password:

View File

@ -0,0 +1,5 @@
---
features:
- |
Add support for the OpenID Connect authentication protocol in Keystone and
enables both ID and access token authentication flows.