Support clouds.yaml to manage keystone user credentials

Recent openstack cli supports loading user credentials from clouds.yaml
instead of passing each parameters by environment variables or command
options.

This allows us to manage user credentials more flexibly. The biggest
benefit of the clouds.yaml file is that it supports managing multiple
credentials in a single file. When SRBAC is enforced, each API request
should be made with the proper scope credential, and we need to switch
credentials for different scopes(project, domain and system) according.
Usage of clouds.yaml helps this use case hugely because it allows us to
store credentials for each scope in a single file and switch them by
the single OS_CLOUD environment variable(or the --os-cloud option).

Change-Id: Ie8246aa18d90ba506fe708be13c9a5afa3e5d2fd
This commit is contained in:
Takashi Kajinami 2021-10-25 22:37:05 +09:00
parent 0027bf6893
commit 2099a4ca49
6 changed files with 190 additions and 2 deletions

View File

@ -88,7 +88,9 @@ class Puppet::Provider::Openstack::CredentialsV3 < Puppet::Provider::Openstack::
:trust_id,
:user_domain_id,
:user_domain_name,
:user_id
:user_id,
:cloud,
:client_config_file,
]
KEYS.each { |var| attr_accessor var }
@ -113,12 +115,14 @@ class Puppet::Provider::Openstack::CredentialsV3 < Puppet::Provider::Openstack::
elsif @system_scope
return 'system'
else
# When clouds.yaml is used, parameters are not directly passed to puppet
# so the scope can't be detected.
return nil
end
end
def user_password_set?
return true if user_set? && @password && scope_set? && @auth_url
return true if (user_set? && @password && scope_set? && @auth_url) || @cloud
end
def initialize

73
manifests/clouds.pp Normal file
View File

@ -0,0 +1,73 @@
# == Class: openstacklib::clouds
#
# Generates clouds.yaml for openstack CLI
#
# == Parameters
#
# [*username*]
# (Required) The name of the keystone user.
#
# [*password*]
# (Required) Password of the keystone user.
#
# [*auth_url*]
# (Required) The URL to use for authentication.
#
# [*path*]
# (Optional) Path to the clouds.yaml file.
# Defaults to $name
#
# [*user_domain_name*]
# (Optional) Name of domain for $username.
# Defaults to 'Default'
#
# [*project_name*]
# (Optional) The name of the keystone project.
# Defaults to undef
#
# [*project_domain_name*]
# (Optional) Name of domain for $project_name.
# Defaults to 'Default'
#
# [*system_scope*]
# (Optional) Scope for system operations.
# Defaults to undef
#
# [*identity_api_version*]
# (Optional) Version of identity API.
# Defaults to '3'
#
# [*interface*]
# (Optional) Determine the endpoint to be used.
# Defaults to undef
#
# [*region_name*]
# (Optional) The region in which the service can be found.
# Defaults to undef
#
define openstacklib::clouds(
$username,
$password,
$auth_url,
$path = $name,
$user_domain_name = 'Default',
$project_name = undef,
$project_domain_name = 'Default',
$system_scope = undef,
$identity_api_version = '3',
$interface = undef,
$region_name = undef,
) {
if !$project_name and !$system_scope {
fail('One of project_name and system_scope should be set')
}
file { $path:
ensure => 'present',
mode => '0600',
owner => 'root',
group => 'root',
content => template('openstacklib/clouds.yaml.erb'),
}
}

View File

@ -0,0 +1,10 @@
---
features:
- |
The new ``openstacklib::clouds`` resource type has been added, which
manages the ``clouds.yaml`` file to store credentials for the ``openstack``
cli.
- |
Now ``Puppet::Provider::Openstack::CredentialsV3`` supports loading
credentials from the ``clouds.yaml`` file.

View File

@ -0,0 +1,41 @@
require 'spec_helper'
describe 'openstacklib::clouds' do
shared_examples 'openstacklib::clouds' do
let :title do
'/etc/openstack/clouds.yaml'
end
context 'with the required parameters' do
let :params do
{
:username => 'admin',
:password => 'secrete',
:auth_url => 'http://127.0.0.1:5000/',
:project_name => 'demo',
}
end
it 'creates a clouds.yaml file' do
should contain_file('/etc/openstack/clouds.yaml').with(
:mode => '0600',
:owner => 'root',
:group => 'root',
)
end
end
end
on_supported_os({
:supported_os => OSDefaults.get_supported_os
}).each do |os,facts|
context "on #{os}" do
let (:facts) do
facts.merge!(OSDefaults.get_facts())
end
it_behaves_like 'openstacklib::clouds'
end
end
end

View File

@ -74,6 +74,12 @@ describe Puppet::Provider::Openstack::Credentials do
expect(creds.service_token_set?).to be_falsey
end
it 'is successful with cloud' do
creds.cloud = 'openstack'
expect(creds.user_password_set?).to be_truthy
expect(creds.service_token_set?).to be_falsey
end
it 'fails' do
creds.auth_url = 'auth_url'
creds.password = 'password'
@ -111,6 +117,8 @@ describe Puppet::Provider::Openstack::Credentials do
creds.endpoint = 'endpoint'
creds.region_name = 'region_name'
creds.identity_api_version = 'identity_api_version'
creds.cloud = 'openstack'
creds.client_config_file = '/etc/openstack/clouds.yaml'
creds.unset
expect(creds.auth_url).to eq('')
expect(creds.password).to eq('')
@ -122,6 +130,8 @@ describe Puppet::Provider::Openstack::Credentials do
expect(creds.endpoint).to eq('')
expect(creds.region_name).to eq('')
expect(creds.identity_api_version).to eq('identity_api_version')
expect(creds.cloud).to eq('')
expect(creds.client_config_file).to eq('')
newcreds = Puppet::Provider::Openstack::CredentialsV3.new
expect(newcreds.identity_api_version).to eq('3')
end
@ -141,6 +151,8 @@ describe Puppet::Provider::Openstack::Credentials do
creds.endpoint = 'endpoint'
creds.region_name = 'Region1'
creds.identity_api_version = 'identity_api_version'
creds.cloud = 'openstack'
creds.client_config_file = '/etc/openstack/clouds.yaml'
expect(creds.to_env).to eq({
'OS_USERNAME' => 'username',
'OS_PASSWORD' => 'password',
@ -152,6 +164,8 @@ describe Puppet::Provider::Openstack::Credentials do
'OS_ENDPOINT' => 'endpoint',
'OS_REGION_NAME' => 'Region1',
'OS_IDENTITY_API_VERSION' => 'identity_api_version',
'OS_CLOUD' => 'openstack',
'OS_CLIENT_CONFIG_FILE' => '/etc/openstack/clouds.yaml',
})
end
end
@ -173,6 +187,7 @@ describe Puppet::Provider::Openstack::Credentials do
creds.project_name = 'project_name'
creds.username = 'username'
expect(creds.user_password_set?).to be_truthy
expect(creds.scope).to eq('project')
end
end
describe '#password_set? with username and domain_name' do
@ -182,6 +197,7 @@ describe Puppet::Provider::Openstack::Credentials do
creds.domain_name = 'domain_name'
creds.username = 'username'
expect(creds.user_password_set?).to be_truthy
expect(creds.scope).to eq('domain')
end
end
describe '#password_set? with username and system_scope' do
@ -191,6 +207,14 @@ describe Puppet::Provider::Openstack::Credentials do
creds.system_scope = 'all'
creds.username = 'username'
expect(creds.user_password_set?).to be_truthy
expect(creds.scope).to eq('system')
end
end
describe '#password_set? with cloud' do
it 'is successful' do
creds.cloud = 'openstack'
expect(creds.user_password_set?).to be_truthy
expect(creds.scope).to eq(nil)
end
end
describe '#password_set? with user_id and project_id' do
@ -200,6 +224,7 @@ describe Puppet::Provider::Openstack::Credentials do
creds.project_id = 'projid'
creds.user_id = 'userid'
expect(creds.user_password_set?).to be_truthy
expect(creds.scope).to eq('project')
end
end
describe '#password_set? with user_id and domain_id' do
@ -209,6 +234,7 @@ describe Puppet::Provider::Openstack::Credentials do
creds.domain_id = 'domid'
creds.user_id = 'userid'
expect(creds.user_password_set?).to be_truthy
expect(creds.scope).to eq('domain')
end
end
end

34
templates/clouds.yaml.erb Normal file
View File

@ -0,0 +1,34 @@
clouds:
<% if @project_name -%>
project:
auth:
auth_url: <%= @auth_url %>
password: <%= @password %>
username: <%= @username %>
user_domain_name: <%= @user_domain_name %>
project_name: <%= @project_name %>
project_domain_name: <%= @project_domain_name %>
identity_api_version: <%= @identity_api_version %>
<% if @interface -%>
interface: <%= @interface %>
<% end -%>
<% if @region_name -%>
region_name: <%= @region_name %>
<% end -%>
<% end -%>
<% if @system_scope -%>
system:
auth:
auth_url: <%= @auth_url %>
password: <%= @password %>
username: <%= @username %>
user_domain_name: <%= @user_domain_name %>
system_scope: <%= @system_scope %>
identity_api_version: <%= @identity_api_version %>
<% if @interface -%>
interface: <%= @interface %>
<% end -%>
<% if @region_name -%>
region_name: <%= @region_name %>
<% end -%>
<% end -%>