Setup the Keystone Federation Plugins
Create the Rackspace Domain
Global Auth Identity Provider
First step is to create the identity provider and connect it to a domain.
openstack --os-cloud default identity provider create --remote-id rackspace --domain rackspace_cloud_domain rackspace
Create the Global Auth mapping for our identity provider
You're also welcome to generate your own mapping to suit your needs; however, if you want to use the example mapping (which is suitable for production) you can.
Example keystone mapping.json
file
[
{
"local": [
{
"user": {
"name": "{0}",
"email": "{1}",
"domain": {
"name": "rackspace_cloud_domain"
}
}
},
{
"projects": [
{
"name": "{2}",
"domain": {
"name": "rackspace_cloud_domain"
},
"description": "Project for DDI {3}",
"metadata": [
{
"key": "ddi",
"value": "{3}"
}
],
"tags": [
{
"project_tag": "{3}"
}
],
"roles": []
}
]
}
],
"remote": [
{
"type": "RXT_UserName"
},
{
"type": "RXT_Email"
},
{
"type": "RXT_TenantName"
},
{
"type": "RXT_DomainID"
},
{
"type": "RXT_orgPersonType",
"any_one_of": [
"creator",
"member",
"reader"
]
}
]
}
]
The example mapping JSON file can be found within the genestack repository at /opt/genestack/etc/keystone/mapping.json
.
Creating the creator
role
The creator role does not exist by default, but is included in the example mapping. One must create the creator role in order to prevent authentication errors if using the mapping "as is".
Register the Global Auth mapping within Keystone
openstack --os-cloud default mapping create --rules /tmp/mapping.json --schema-version 2.0 rackspace_mapping
Create the Global Auth federation protocol
openstack --os-cloud default federation protocol create rackspace --mapping rackspace_mapping --identity-provider rackspace
Rackspace Global Auth Configuration Options
The [rackspace]
section can also be used in your keystone.conf
to allow you to configure how to anchor on
roles.
key | value | default |
---|---|---|
role_attribute |
A string option used as an anchor to discover roles attributed to a given user | os_flex |
role_attribute_enforcement |
When set true will limit a users project to only the discovered GUID for the defined role_attribute |
false |
SAML2 Service Provider
Before getting started with the SAML2 service provider setup, you will need to locate your SAML2 identity provider entity ID. This is the value that will be used to configure the SAML2 identity provider within Keystone.
High level diagram of the SAML2 Service Provider environment
flowchart TB
direction LR
Auth["User -> keystone.api.example.com"]
UI["User -> skyline.api.example.com"]
envoyAuth[Envoy Gateway cookie: KeystoneSession]
envoyUI[Envoy Gateway cookie: SkylineSession]
svc[ClusterIP Service keystone-api]
Auth --> envoyAuth --> svc
UI --> envoyUI --> envoyAuth
svc --> Apache1
subgraph "Infrastructure Hosts"
subgraph P1["Keystone pod x 3"]
Shib1[Shibd]
Apache1[Apache]
Shib1 -. shared Socket .- Apache1
end
subgraph P2["Memcached pod x 3"]
memc["Memcached"] -.- Shib1
end
end
Gather Shibboleth files
The following steps make the assumption that the system is running Docker and that the docker
command is available. While these steps are useful,
they are not required. You can also use the shibd
command directly on the host system or use file copies from the shibboleth
package.
Retrieve the SAML2 files
Using the docker
command and the shibd image, retrieve the SAML2 files from the container and place them in a local directory.
docker run -v /etc/genestack/keystone-sp:/mnt \
ghcr.io/rackerlabs/keystone-rxt/shibd:latest \
cp -R /etc/shibboleth /mnt/
Update the shibboleth2.xml
file
The files will be created and stored in a kubernetes secret named keystone-shib-etc
and mounted to /etc/shibboleth
in the keystone
pod. The files are:
Store the SAML metadata and configure the SAML identity provider at /etc/genestack/keystone-sp/shibboleth/idp-metadata.xml
.
Before you can upload the secrets to the kubernetes cluster, you need to edit the shibboleth2.xml
file.
shibboleth2.xml file
<SPConfig xmlns="urn:mace:shibboleth:3.0:native:sp:config"
xmlns:conf="urn:mace:shibboleth:3.0:native:sp:config"
clockSkew="180">
<OutOfProcess logger="shibd.logger"
tranLogFormat="%u|%s|%IDP|%i|%ac|%t|%attr|%n|%b|%E|%S|%SS|%L|%UA|%a">
<Extensions>
<Library path="memcache-store.so" fatal="true" />
</Extensions>
</OutOfProcess>
<StorageService type="MEMCACHE"
id="mc"
prefix="shib:">
<Hosts>
memcached.openstack.svc.cluster.local:11211
</Hosts>
</StorageService>
<StorageService type="MEMCACHE"
id="mc-ctx"
prefix="shib:"
buildMap="1">
<Hosts>
memcached.openstack.svc.cluster.local:11211
</Hosts>
</StorageService>
<SessionCache type="StorageService"
StorageService="mc-ctx"
StorageServiceLite="mc"
cacheAllowance="3600"
inprocTimeout="900"
cleanupInterval="900" />
<ReplayCache StorageService="mc" />
<ArtifactMap StorageService="mc" artifactTTL="180" />
<!-- The following is a sample configuration for the SP.
You will need to replace the entityID and the metadata URL with your own values.
For the REMOTE_USER, common values are "eppn" "persistent-id" "targeted-id"
The example "uid" value is a custom mapping comming from the IDP -->
<ApplicationDefaults
entityID="https://skyline.api.example.com/api/openstack/skyline/api/v1/websso"
REMOTE_USER="eppn persistent-id targeted-id uid"
cipherSuites="DEFAULT:!EXP:!LOW:!aNULL:!eNULL:!DES:!IDEA:!SEED:!RC4:!3DES:!kRSA:!SSLv2:!SSLv3:!TLSv1:!TLSv1.1">
<Sessions lifetime="28800"
timeout="3600"
relayState="ss:mc"
checkAddress="false"
handlerSSL="true"
cookieProps="https"
redirectLimit="exact">
<SSO entityID="EXAMPLE_ENTITY_ID_REPLACED_WITH_THE_ENTITY_ID_OF_THE_IDP">
SAML2
</SSO>
<Logout>
SAML2 Local
</Logout>
<LogoutInitiator type="Admin" Location="/Logout/Admin" acl="127.0.0.1 ::1" />
<Handler type="MetadataGenerator" Location="/Metadata" signing="false" />
<Handler type="Status" Location="/Status" acl="127.0.0.1 ::1" />
<Handler type="Session" Location="/Session" showAttributeValues="false" />
<Handler type="DiscoveryFeed" Location="/DiscoFeed" />
</Sessions>
<Errors supportContact="admin@example.com"
helpLocation="https://example.com/about.html"
styleSheet="/shibboleth-sp/main.css" />
<MetadataProvider type="XML" path="idp-metadata.xml" />
<AttributeExtractor type="XML"
validate="true"
reloadChanges="false"
path="attribute-map.xml" />
<AttributeFilter type="XML" validate="true" path="attribute-policy.xml" />
<CredentialResolver type="File"
use="signing"
key="sp-key.pem"
certificate="sp-cert.pem" />
<CredentialResolver type="File"
use="encryption"
key="sp-key.pem"
certificate="sp-cert.pem" />
</ApplicationDefaults>
<SecurityPolicyProvider type="XML" validate="true" path="security-policy.xml" />
<ProtocolProvider type="XML"
validate="true"
reloadChanges="false"
path="protocols.xml" />
</SPConfig>
- The entity ID in the
SSO
field is the same value as theEXAMPLE_ENTITY_ID_REPLACED_WITH_THE_ENTITY_ID_OF_THE_IDP
used when defining the OpenStack Identity provider. - The references to
example.com
should be replaced with the actual domain name used within the cloud - Take note of the memcached configuration in the
shibboleth2.xml
file. Thememcached
service is used to cache the SAML2 responses and requests. The default configuration uses thememcached
service running on the assumed cluster memcached environment using three different nodes:memcached-{0,1,2}.memcached.openstack.svc.cluster.local:11211
. If the memcached service is on a different host or port, update theshibboleth2.xml
file accordingly.
Update the logger to use the console
logger"
The shibd.logger
file is used to configure the logging for the SAML2 identity provider. The default logger is set to log to a
file which isn't ideal. It is recommended to update the logger to use the console
.
Update the Attribute Map
The attribute-map.xml
file is used to configure the attribute mapping for the SAML2 identity provider. Every provider has a different
attribute mapping. The default attribute mapping is set to use the urn:oid:
values. The urn:oid:
values are the standard values
used by the SAML2 identity provider.
Example attribute-map.xml
configurations
The followig attributes are used by the Auth-0 identity provider. Add the following optins to the
attribute-map.xml
to have it compatible with the provided saml-mapping.json
which will be used
within keystone.
<!-- Auth-0 attributes -->
<Attribute name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" id="REMOTE_EMAIL"/>
<Attribute name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" id="uid"/>
<Attribute name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" id="REMOTE_UID"/>
<Attribute name="http://schemas.auth0.com/email_verified" id="REMOTE_VERIFIED"/>
<Attribute name="http://schemas.auth0.com/project_ids" id="REMOTE_PROJECT_NAME"/>
<Attribute name="http://schemas.auth0.com/roles" id="REMOTE_ORG_PERSON_TYPE"/>
Within the Auth-0 portal you can find the project_ids
and roles
attributes. The project_ids
attribute
is used to map the project name to the project ID. The roles
attribute is used to map the roles to the user.
The following attributes are used by the OKTA identity provider. Add the following options to the
attribute-map.xml
to have it compatible with the provided saml-mapping.json
which will be used
within keystone.
<!-- OKTA attributes -->
<Attribute name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" id="REMOTE_EMAIL"/>
<Attribute name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" id="uid"/>
<Attribute name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" id="REMOTE_UID"/>
<Attribute name="http://schemas.okta.com/claims/email_verified" id="REMOTE_VERIFIED"/>
<Attribute name="http://schemas.okta.com/claims/project_ids" id="REMOTE_PROJECT_NAME"/>
<Attribute name="http://schemas.okta.com/claims/roles" id="REMOTE_ORG_PERSON_TYPE"/>
Within the OKTA portal you can find the project_ids
and person_type
attributes. The project_ids
attribute is used to map the project name to the project ID. The person_type
attribute is used to map
the roles to the user.
When using the Rackspace the shibboleth2.xml
file must be updated to include signing for all requests and responses.
The shibboleth2.xml
file must be updated to include the following options:
<ApplicationDefaults entityID="EXAMPLE_ENTITY_ID_REPLACED_WITH_THE_ENTITY_ID_OF_THE_IDP"
REMOTE_USER="eppn persistent-id targeted-id uid"
cipherSuites="DEFAULT:!EXP:!LOW:!aNULL:!eNULL:!DES:!IDEA:!SEED:!RC4:!3DES:!kRSA:!SSLv2:!SSLv3:!TLSv1:!TLSv1.1"
signing="true"
encryption="false"
signingAlg="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
digestAlg="http://www.w3.org/2001/04/xmlenc#sha256"
NameIDFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
The MetadataProvider
should be made dynamic.
<MetadataProvider type="XML"
validate="true"
url="https://example.com/idp.xml"
maxRefreshDelay="7200"
id="login.rackspace.com"
ignoreTransport="true"
cacheDirectory="rackspace">
<MetadataFilter type="RequireValidUntil" maxValidityInterval="2419200"/>
</MetadataProvider>
Additionally the Handler
for the MetadataGenerator
must be updated to include signing:
<Handler type="MetadataGenerator"
Location="/Metadata"
signing="true"
signingAlg="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
digestAlg="http://www.w3.org/2001/04/xmlenc#sha256" />
The following attributes are used by the Rackspace identity provider. Add the following options to the
attribute-map.xml
to have it compatible with the provided mapping.json
which will be used
within keystone.
<!-- Rackspace attributes -->
<Attribute name="account_name" id="REMOTE_ACCOUNT_NAME"/>
<Attribute name="auth_token" id="REMOTE_AUTH_TOKEN"/>
<Attribute name="auth_url" id="REMOTE_AUTH_URL"/>
<Attribute name="ddi" id="REMOTE_DDI"/>
<Attribute name="domain" id="REMOTE_DOMAIN"/>
<Attribute name="scoped_token" id="REMOTE_SCOPED_TOKEN"/>
<Attribute name="session_creation_time" id="REMOTE_SESSION_CREATION"/>
<Attribute name="urn:oid:1.2.840.113549.1.9.1.1" id="REMOTE_EMAIL"/>
<Attribute name="user_id" id="uid"/>
<Attribute name="username" id="REMOTE_USERNAME"/>
Generate the SAML Keys
For N numbers of years. The keys are used to sign the SAML requests and responses.
Define all values for the following options
Name | Description |
---|---|
Country Name | (2 letter code) |
State or Province Name | (full name) |
Locality Name | (eg, city) |
Organization Name | (eg, company) |
Organizational Unit Name | (eg, section) |
Common Name | (e.g. server FQDN or YOUR name) |
Email Address | (contact address) |
The default filenames are:
- sp-key.pem
- sp-cert.pem
Upload the SAML2 files to the kubernetes cluster
Ingest all files into the kubernetes secret as a secrete, which will be mounted to the keystone pod.
kubectl -n openstack create secret generic keystone-shibd-etc \
--from-file=/etc/genestack/keystone-sp/shibboleth/attrChecker.html \
--from-file=/etc/genestack/keystone-sp/shibboleth/attribute-map.xml \
--from-file=/etc/genestack/keystone-sp/shibboleth/attribute-policy.xml \
--from-file=/etc/genestack/keystone-sp/shibboleth/bindingTemplate.html \
--from-file=/etc/genestack/keystone-sp/shibboleth/discoveryTemplate.html \
--from-file=/etc/genestack/keystone-sp/shibboleth/globalLogout.html \
--from-file=/etc/genestack/keystone-sp/shibboleth/idp-metadata.xml \
--from-file=/etc/genestack/keystone-sp/shibboleth/localLogout.html \
--from-file=/etc/genestack/keystone-sp/shibboleth/metadataError.html \
--from-file=/etc/genestack/keystone-sp/shibboleth/partialLogout.html \
--from-file=/etc/genestack/keystone-sp/shibboleth/postTemplate.html \
--from-file=/etc/genestack/keystone-sp/shibboleth/protocols.xml \
--from-file=/etc/genestack/keystone-sp/shibboleth/security-policy.xml \
--from-file=/etc/genestack/keystone-sp/shibboleth/sessionError.html \
--from-file=/etc/genestack/keystone-sp/shibboleth/shibboleth2.xml \
--from-file=/etc/genestack/keystone-sp/shibboleth/shibd.logger \
--from-file=/etc/genestack/keystone-sp/shibboleth/sp-cert.pem \
--from-file=/etc/genestack/keystone-sp/shibboleth/sp-key.pem \
--from-file=/etc/genestack/keystone-sp/shibboleth/sslError.html
Create the SAML identity provider
Create the identity provider within Keystone. The --remote-id
is the entity ID of the SAML identity provider. This is the value that will be used to configure the SAML2 shibboleth2.xml
file.
openstack --os-cloud default identity provider create \
--remote-id EXAMPLE_ENTITY_ID_REPLACED_WITH_THE_ENTITY_ID_OF_THE_IDP \
--domain rackspace_cloud_domain \
IDENTITY_PROVIDER_NAME
Create the SAML auth mapping for our identity provider
You're also welcome to generate your own mapping to suit your needs; however, if you want to use the example mapping (which is suitable for production) you can.
Example keystone saml-mapping.json
file
[
{
"local": [
{
"user": {
"id": "{0}",
"name": "{1}",
"email": "{2}",
"domain": {
"name": "rackspace_cloud_domain"
}
}
},
{
"projects": [
{
"name": "{3}",
"domain": {
"name": "rackspace_cloud_domain"
},
"roles": [
{
"name": "reader"
},
{
"name": "load-balancer_observer"
},
{
"name": "network_observer"
},
{
"name": "heat_stack_user"
}
]
}
]
}
],
"remote": [
{
"type": "REMOTE_UID"
},
{
"type": "REMOTE_USER"
},
{
"type": "REMOTE_EMAIL"
},
{
"type": "REMOTE_PROJECT_NAME"
},
{
"type": "REMOTE_ORG_PERSON_TYPE",
"any_one_of": [
"observer"
]
},
{
"type": "REMOTE_VERIFIED",
"any_one_of": [
"true"
]
}
]
},
{
"local": [
{
"user": {
"id": "{0}",
"name": "{1}",
"email": "{2}",
"domain": {
"name": "rackspace_cloud_domain"
}
}
},
{
"projects": [
{
"name": "{3}",
"domain": {
"name": "rackspace_cloud_domain"
},
"roles": [
{
"name": "member"
},
{
"name": "load-balancer_member"
},
{
"name": "network_member"
},
{
"name": "heat_stack_user"
}
]
}
]
}
],
"remote": [
{
"type": "REMOTE_UID"
},
{
"type": "REMOTE_USER"
},
{
"type": "REMOTE_EMAIL"
},
{
"type": "REMOTE_PROJECT_NAME"
},
{
"type": "REMOTE_ORG_PERSON_TYPE",
"any_one_of": [
"member"
]
},
{
"type": "REMOTE_VERIFIED",
"any_one_of": [
"true"
]
}
]
},
{
"local": [
{
"user": {
"id": "{0}",
"name": "{1}",
"email": "{2}",
"domain": {
"name": "rackspace_cloud_domain"
}
}
},
{
"projects": [
{
"name": "{3}",
"domain": {
"name": "rackspace_cloud_domain"
},
"roles": [
{
"name": "creator"
},
{
"name": "load-balancer_member"
},
{
"name": "network_creator"
},
{
"name": "heat_stack_user"
}
]
}
]
}
],
"remote": [
{
"type": "REMOTE_UID"
},
{
"type": "REMOTE_USER"
},
{
"type": "REMOTE_EMAIL"
},
{
"type": "REMOTE_PROJECT_NAME"
},
{
"type": "REMOTE_ORG_PERSON_TYPE",
"any_one_of": [
"creator"
]
},
{
"type": "REMOTE_VERIFIED",
"any_one_of": [
"true"
]
}
]
}
]
The example mapping JSON file can be found within the genestack repository at etc/keystone/saml-mapping.json
.
Register the SAML mapping within Keystone
openstack --os-cloud default mapping create \
--rules /opt/genestack/etc/keystone/saml-mapping.json \
--schema-version 2.0 \
saml_mapping
Create the SAML federation protocol
openstack --os-cloud default federation protocol create saml2 \
--mapping saml_mapping \
--identity-provider IDENTITY_PROVIDER_NAME
Redeploy the Keystone Helm Chart
Before running the deployment, you need to edit the keystone-helm-overrides.yaml
file to include the following configuration options.
keystone-helm-overrides.yaml
---
conf:
software:
apache2:
a2enmod:
- shib
keystone:
auth:
methods: "saml2,password,token,application_credential,totp"
saml2: "rxt"
saml2:
remote_id_attribute: Shib-Identity-Provider
federation:
trusted_dashboard:
type: multistring
values:
- https://skyline.api.example.com/api/openstack/skyline/api/v1/websso
- https://horizon.api.example.com/auth/websso
wsgi_keystone: |
{{- $portInt := tuple "identity" "service" "api" $ | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
Listen 0.0.0.0:{{ $portInt }}
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy
SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded
CustomLog /dev/stdout combined env=!forwarded
CustomLog /dev/stdout proxy env=forwarded
<VirtualHost *:{{ $portInt }}>
ServerName https://keystone.api.example.com:443
UseCanonicalName On
WSGIDaemonProcess keystone-public processes={{ .Values.conf.keystone_api_wsgi.wsgi.processes }} threads={{ .Values.conf.keystone_api_wsgi.wsgi.threads }} user=keystone group=keystone display-name=%{GROUP}
WSGIProcessGroup keystone-public
WSGIScriptAlias / /var/www/cgi-bin/keystone/keystone-wsgi-public
WSGIScriptAliasMatch ^(/v3/OS-FEDERATION/identity_providers/IDENTITY_PROVIDER_NAME/protocols/saml2/auth)$ /var/www/cgi-bin/keystone/keystone-wsgi-public/$1
WSGIApplicationGroup %{GLOBAL}
WSGIPassAuthorization On
LimitRequestBody 114688
<IfVersion >= 2.4>
ErrorLogFormat "%{cu}t %M"
</IfVersion>
ErrorLog /dev/stdout
SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded
CustomLog /dev/stdout combined env=!forwarded
CustomLog /dev/stdout proxy env=forwarded
<Location /Shibboleth.sso>
SetHandler shib
</Location>
<Location /v3/OS-FEDERATION/identity_providers/IDENTITY_PROVIDER_NAME/protocols/saml2/auth>
Require valid-user
AuthType shibboleth
ShibRequestSetting requireSession 1
ShibExportAssertion off
<IfVersion < 2.4>
ShibRequireSession On
ShibRequireAll On
</IfVersion>
</Location>
RedirectMatch ^/v3/auth/OS-FEDERATION/websso/IDENTITY_PROVIDER_NAME$ /v3/auth/OS-FEDERATION/websso/saml2
<Location /v3/auth/OS-FEDERATION/websso/saml2>
Require valid-user
AuthType shibboleth
ShibRequestSetting requireSession 1
ShibExportAssertion off
<IfVersion < 2.4>
ShibRequireSession On
ShibRequireAll On
</IfVersion>
</Location>
<Location /v3/auth/OS-FEDERATION/identity_providers/IDENTITY_PROVIDER_NAME/protocols/saml2/websso>
Require valid-user
AuthType shibboleth
ShibRequestSetting requireSession 1
ShibExportAssertion off
<IfVersion < 2.4>
ShibRequireSession On
ShibRequireAll On
</IfVersion>
</Location>
</VirtualHost>
Keystone Configuration Options
The above example is a sample configuration file that can be used to deploy Keystone with SAML2 support. The following options are important:
- The
trusted_dashboard
configuration option in the above example must be updated to the public URL of the UI experience for the cloud - The references to
example.com
should be replaced with the actual domain name used within the cloud - The references to
IDENTITY_PROVIDER_NAME
should be replaced with the name of your identity provider (e.g.,Auth0
,OKTA
)
Edit the Keystone Onverlay file at /etc/genestack/kustomize/keystone/overlay/kustomization.yaml
to ensure that it is including the federation
backend as the base.
Now run the helm deployment to update keystone to use the new federation setup.
Redeploy the Skyline UI with SSO Enabled
Finally update the Skyline configuration to use the SAML2 identity provider.
echo sso-protocols: $(echo -n '["IDENTITY_PROVIDER_NAME"]' | base64)
echo sso-enabled: $(echo -n 'true' | base64)
echo sso-region: $(echo -n 'RegionOne' | base64)
echo keystone-endpoint: $(echo -n 'https://keystone.api.example.com/v3' | base64)
Output of the above command
sso-protocols: WyJJREVOVElUWV9QUk9WSURFUl9OQU1FIl0=
sso-enabled: dHJ1ZQ==
sso_region: UmVnaW9uT25l
keystone-endpoint: aHR0cHM6Ly9rZXlzdG9uZS5hcGkuZXhhbXBsZS5jb20vdjM=
The Keystone Endpoint must be the public endpoint
The Keystone URL must be the URL of your Public Cloud Keystone service.
Edit the skyline-apiserver-secrets
secret to include the new SSO configuration options.
Run the following command to update the skyline-apiserver deployment.
Using the API with WebSSO and Federation
When the SAML driver is enabled, the OpenStack API will not permit authentication via username and password.
To best leverage the OpenStack API, it is recommended to use an Application Credential to authenticate. This
can be done by creating an Application Credential in the Skyline UI and then using the openstack
CLI to
authenticate. Once an Application Credential is created, the OpenStack Clouds YAML file can be updated to
include the Application Credential ID and Secret, as show here.