From 369c516bd6e70d903786ba22edd65e322f257eb8 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Mon, 25 Oct 2021 22:37:05 +0900 Subject: [PATCH] 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 (cherry picked from commit 522d06ba8b330e59f3b9eb81dbf659a30e70691a) --- lib/puppet/provider/openstack/credentials.rb | 8 +- manifests/clouds.pp | 75 +++++++++++++++++++ .../notes/clouds-yaml-e8a87dfceba4619d.yaml | 10 +++ spec/defines/openstacklib_clouds_spec.rb | 41 ++++++++++ .../provider/openstack/credentials_spec.rb | 26 +++++++ templates/clouds.yaml.erb | 38 ++++++++++ 6 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 manifests/clouds.pp create mode 100644 releasenotes/notes/clouds-yaml-e8a87dfceba4619d.yaml create mode 100644 spec/defines/openstacklib_clouds_spec.rb create mode 100644 templates/clouds.yaml.erb diff --git a/lib/puppet/provider/openstack/credentials.rb b/lib/puppet/provider/openstack/credentials.rb index 5f65512b..c6d181c8 100644 --- a/lib/puppet/provider/openstack/credentials.rb +++ b/lib/puppet/provider/openstack/credentials.rb @@ -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 diff --git a/manifests/clouds.pp b/manifests/clouds.pp new file mode 100644 index 00000000..ec279b6d --- /dev/null +++ b/manifests/clouds.pp @@ -0,0 +1,75 @@ +# == 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 +# +# [*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 +# +# [*api_versions*] +# (Optional) Hash of service type and version to determine API version +# for that service to use. +# Example: { 'identity' => '3', 'compute' => '2.latest' } +# Defaults to {} +# +define openstacklib::clouds( + $username, + $password, + $auth_url, + $path = $name, + $user_domain_name = 'Default', + $project_name = undef, + $project_domain_name = 'Default', + $system_scope = undef, + $interface = undef, + $region_name = undef, + $api_versions = {}, +) { + + 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'), + } +} diff --git a/releasenotes/notes/clouds-yaml-e8a87dfceba4619d.yaml b/releasenotes/notes/clouds-yaml-e8a87dfceba4619d.yaml new file mode 100644 index 00000000..cda48ab4 --- /dev/null +++ b/releasenotes/notes/clouds-yaml-e8a87dfceba4619d.yaml @@ -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. diff --git a/spec/defines/openstacklib_clouds_spec.rb b/spec/defines/openstacklib_clouds_spec.rb new file mode 100644 index 00000000..2e4da62e --- /dev/null +++ b/spec/defines/openstacklib_clouds_spec.rb @@ -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 diff --git a/spec/unit/provider/openstack/credentials_spec.rb b/spec/unit/provider/openstack/credentials_spec.rb index 9f06d41c..e83c0c3e 100644 --- a/spec/unit/provider/openstack/credentials_spec.rb +++ b/spec/unit/provider/openstack/credentials_spec.rb @@ -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 diff --git a/templates/clouds.yaml.erb b/templates/clouds.yaml.erb new file mode 100644 index 00000000..f4382054 --- /dev/null +++ b/templates/clouds.yaml.erb @@ -0,0 +1,38 @@ +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 %> + <%- @api_versions.sort.each do |opt_name,opt_val| -%> + <%= opt_name %>_api_version: <%= opt_val %> + <%- end -%> + <%- if !(@interface.nil?) -%> + interface: <%= @interface %> + <%- end -%> + <%- if !(@region_name.nil?) -%> + 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 %> + <%- @api_versions.sort.each do |opt_name,opt_val| -%> + <%= opt_name %>_api_version: <%= opt_val %> + <%- end -%> + <%- if !(@interface.nil?) -%> + interface: <%= @interface %> + <%- end -%> + <%- if !(@region_name.nil?) -%> + region_name: <%= @region_name %> + <%- end -%> +<% end -%>