From d5127a8a26b29eba02c8a09e7ae6d4157bf204d6 Mon Sep 17 00:00:00 2001 From: Bo-Chun Louis Chen Date: Mon, 17 Jun 2019 15:16:51 -0500 Subject: [PATCH 1/5] Update keystone to support application credential Add condition at adding password to post body Update get_body_3 to have application credential body if auth type is set to v3applicationcredentail --- .../client/keystone.rb | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/source/lib/vagrant-openstack-provider/client/keystone.rb b/source/lib/vagrant-openstack-provider/client/keystone.rb index 7c16f89..3e47c8c 100644 --- a/source/lib/vagrant-openstack-provider/client/keystone.rb +++ b/source/lib/vagrant-openstack-provider/client/keystone.rb @@ -37,7 +37,9 @@ def authenticate(env) if config.identity_api_version == '2' post_body[:auth][:passwordCredentials][:password] = config.password elsif config.identity_api_version == '3' - post_body[:auth][:identity][:password][:user][:password] = config.password + if config.openstack_auth_type != 'v3applicationcredential' + post_body[:auth][:identity][:password][:user][:password] = config.password + end end authentication = RestUtils.post(env, auth_url, post_body.to_json, headers) do |response| @@ -87,29 +89,46 @@ def get_body_2(config) end def get_body_3(config) - { - auth: - { - identity: { - methods: ['password'], - password: { - user: { - name: config.username, - domain: { - name: config.user_domain_name - }, - password: '****' + body = {} + if config.openstack_auth_type != 'v3applicationcredential' + body = { + auth: + { + identity: { + methods: ['password'], + password: { + user: { + name: config.username, + domain: { + name: config.user_domain_name + }, + password: '****' + } + } + }, + scope: { + project: { + name: config.project_name, + domain: { name: config.project_domain_name } } } - }, - scope: { - project: { - name: config.project_name, - domain: { name: config.project_domain_name } + } + } + else + body = { + auth: + { + identity: { + methods: ['application_credential'], + application_credential: { + id: config.app_cred_id, + secret: config.app_cred_secret, + } } } } - } + end + body end def get_auth_url_3(env) From fea9e88f567cd53e285bbd2a442e763d909b3938 Mon Sep 17 00:00:00 2001 From: Bo-Chun Louis Chen Date: Mon, 17 Jun 2019 15:20:21 -0500 Subject: [PATCH 2/5] Update config.rb Add three variables: app_cred_id, app_cred_secret, and openstack_auth_type Define validate_auth_type method Move password, username required check into validate_auth_type method since application credential doesnt require username and password Update validate_api_version method since application credential doesnt require other variables like: user_domain or project_name --- .../lib/vagrant-openstack-provider/config.rb | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/source/lib/vagrant-openstack-provider/config.rb b/source/lib/vagrant-openstack-provider/config.rb index 2d267ec..bbf29f2 100644 --- a/source/lib/vagrant-openstack-provider/config.rb +++ b/source/lib/vagrant-openstack-provider/config.rb @@ -9,6 +9,10 @@ class Config < Vagrant.plugin('2', :config) # attr_accessor :password + attr_accessor :app_cred_id + attr_accessor :app_cred_secret + attr_accessor :openstack_auth_type + # The compute service url to access Openstack. If nil, it will read from hypermedia catalog form REST API # attr_accessor :openstack_compute_url @@ -264,6 +268,9 @@ class Config < Vagrant.plugin('2', :config) attr_accessor :ssl_verify_peer def initialize + @app_cred_id = UNSET_VALUE + @app_cred_secret = UNSET_VALUE + @openstack_auth_type = UNSET_VALUE @password = UNSET_VALUE @openstack_compute_url = UNSET_VALUE @openstack_network_url = UNSET_VALUE @@ -355,6 +362,9 @@ def merge(other) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity def finalize! + @app_cred_id = nil if @app_cred_id == UNSET_VALUE + @app_cred_secret = nil if @app_cred_secret == UNSET_VALUE + @openstack_auth_type = nil if @openstack_auth_type == UNSET_VALUE @password = nil if @password == UNSET_VALUE @openstack_compute_url = nil if @openstack_compute_url == UNSET_VALUE @openstack_network_url = nil if @openstack_network_url == UNSET_VALUE @@ -440,10 +450,9 @@ def rsync_include(inc) def validate(machine) errors = _detected_errors - errors << I18n.t('vagrant_openstack.config.password_required') if @password.nil? || @password.empty? - errors << I18n.t('vagrant_openstack.config.username_required') if @username.nil? || @username.empty? errors << I18n.t('vagrant_openstack.config.invalid_api_version') unless %w(2 3).include?(@identity_api_version) + validate_auth_type(errors) validate_api_version(errors) validate_ssh_username(machine, errors) validate_stack_config(errors) @@ -473,22 +482,33 @@ def validate(machine) private + def validate_auth_type(errors) + if @openstack_auth_type == 'v3applicationcredential' + errors << I18n.t('vagrant_openstack.config.app_cred_id_required') if @app_cred_id.nil? || @app_cred_id.empty? + errors << I18n.t('vagrant_openstack.config.app_cred_secret_required') if @app_cred_secret.nil? || @app_cred_secret.empty? + elsif @openstack_auth_type.nil? || @openstack_auth_type.empty? + errors << I18n.t('vagrant_openstack.config.password_required') if @password.nil? || @password.empty? + errors << I18n.t('vagrant_openstack.config.username_required') if @username.nil? || @username.empty? + end + end def validate_api_version(errors) if @identity_api_version == '2' errors << I18n.t('vagrant_openstack.config.tenant_name_required') if @tenant_name.nil? || @tenant_name.empty? errors << I18n.t('vagrant_openstack.config.invalid_endpoint_type') unless %w(publicURL adminURL internalURL).include?(@endpoint_type) elsif @identity_api_version == '3' - if @domain_name == UNSET_VALUE || @domain_name.nil? || @domain_name.empty? - if (@user_domain_name.nil? || @user_domain_name.empty?) && (@project_domain_name_name.nil? || @project_domain_name.empty?) - errors << I18n.t('vagrant_openstack.config.domain_required') - elsif @user_domain_name.nil? || @user_domain_name.empty? - errors << I18n.t('vagrant_openstack.config.user_domain_required') - elsif @project_domain_name.nil? || @project_domain_name.empty? - errors << I18n.t('vagrant_openstack.config.project_domain_required') + if @openstack_auth_type != 'v3applicationcredential' + if @domain_name == UNSET_VALUE || @domain_name.nil? || @domain_name.empty? + if (@user_domain_name.nil? || @user_domain_name.empty?) && (@project_domain_name_name.nil? || @project_domain_name.empty?) + errors << I18n.t('vagrant_openstack.config.domain_required') + elsif @user_domain_name.nil? || @user_domain_name.empty? + errors << I18n.t('vagrant_openstack.config.user_domain_required') + elsif @project_domain_name.nil? || @project_domain_name.empty? + errors << I18n.t('vagrant_openstack.config.project_domain_required') + end end + errors << I18n.t('vagrant_openstack.config.project_name_required') if @project_name.nil? || @project_name.empty? + errors << I18n.t('vagrant_openstack.config.invalid_interface_type') unless %w(public admin internal).include?(@interface_type) end - errors << I18n.t('vagrant_openstack.config.project_name_required') if @project_name.nil? || @project_name.empty? - errors << I18n.t('vagrant_openstack.config.invalid_interface_type') unless %w(public admin internal).include?(@interface_type) end end From 1842f74f336c76b61dc639230ced11977f44e9d5 Mon Sep 17 00:00:00 2001 From: Bo-Chun Louis Chen Date: Mon, 17 Jun 2019 15:28:56 -0500 Subject: [PATCH 3/5] Add two error message in locales app_cred_id_required and app_cred_secret_required --- source/locales/en.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/locales/en.yml b/source/locales/en.yml index 933ed5c..1bf81f9 100644 --- a/source/locales/en.yml +++ b/source/locales/en.yml @@ -101,6 +101,10 @@ en: Sync folders are disabled in the provider configuration config: + app_cred_id_required: |- + A application credential id is required. + app_cred_secret_required: |- + A application credential secret is required. password_required: |- A password is required. username_required: |- From d65b25442073d634800a995ed0b6af3bb41caedf Mon Sep 17 00:00:00 2001 From: Bo-Chun Louis Chen Date: Mon, 17 Jun 2019 16:36:47 -0500 Subject: [PATCH 4/5] Update code per RuboCop --- .../client/keystone.rb | 2 +- source/lib/vagrant-openstack-provider/config.rb | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/source/lib/vagrant-openstack-provider/client/keystone.rb b/source/lib/vagrant-openstack-provider/client/keystone.rb index 3e47c8c..b0651bc 100644 --- a/source/lib/vagrant-openstack-provider/client/keystone.rb +++ b/source/lib/vagrant-openstack-provider/client/keystone.rb @@ -122,7 +122,7 @@ def get_body_3(config) methods: ['application_credential'], application_credential: { id: config.app_cred_id, - secret: config.app_cred_secret, + secret: config.app_cred_secret } } } diff --git a/source/lib/vagrant-openstack-provider/config.rb b/source/lib/vagrant-openstack-provider/config.rb index bbf29f2..65319ed 100644 --- a/source/lib/vagrant-openstack-provider/config.rb +++ b/source/lib/vagrant-openstack-provider/config.rb @@ -491,20 +491,19 @@ def validate_auth_type(errors) errors << I18n.t('vagrant_openstack.config.username_required') if @username.nil? || @username.empty? end end + def validate_api_version(errors) if @identity_api_version == '2' errors << I18n.t('vagrant_openstack.config.tenant_name_required') if @tenant_name.nil? || @tenant_name.empty? errors << I18n.t('vagrant_openstack.config.invalid_endpoint_type') unless %w(publicURL adminURL internalURL).include?(@endpoint_type) elsif @identity_api_version == '3' - if @openstack_auth_type != 'v3applicationcredential' - if @domain_name == UNSET_VALUE || @domain_name.nil? || @domain_name.empty? - if (@user_domain_name.nil? || @user_domain_name.empty?) && (@project_domain_name_name.nil? || @project_domain_name.empty?) - errors << I18n.t('vagrant_openstack.config.domain_required') - elsif @user_domain_name.nil? || @user_domain_name.empty? - errors << I18n.t('vagrant_openstack.config.user_domain_required') - elsif @project_domain_name.nil? || @project_domain_name.empty? - errors << I18n.t('vagrant_openstack.config.project_domain_required') - end + if @openstack_auth_type != 'v3applicationcredential' && (@domain_name == UNSET_VALUE || @domain_name.nil? || @domain_name.empty?) + if (@user_domain_name.nil? || @user_domain_name.empty?) && (@project_domain_name_name.nil? || @project_domain_name.empty?) + errors << I18n.t('vagrant_openstack.config.domain_required') + elsif @user_domain_name.nil? || @user_domain_name.empty? + errors << I18n.t('vagrant_openstack.config.user_domain_required') + elsif @project_domain_name.nil? || @project_domain_name.empty? + errors << I18n.t('vagrant_openstack.config.project_domain_required') end errors << I18n.t('vagrant_openstack.config.project_name_required') if @project_name.nil? || @project_name.empty? errors << I18n.t('vagrant_openstack.config.invalid_interface_type') unless %w(public admin internal).include?(@interface_type) From 339bd1e98fded22ff09150a4acd81564be2f1f2f Mon Sep 17 00:00:00 2001 From: Bo-Chun Louis Chen Date: Tue, 18 Jun 2019 16:11:24 -0500 Subject: [PATCH 5/5] Update keystone_spec Add app_cred_id and app_cred_secret Add keystone_request_body_app_cred Add context with good application credentials Add context with wrong application credentials --- .../client/keystone_spec.rb | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/source/spec/vagrant-openstack-provider/client/keystone_spec.rb b/source/spec/vagrant-openstack-provider/client/keystone_spec.rb index 0df475c..4b7d7af 100644 --- a/source/spec/vagrant-openstack-provider/client/keystone_spec.rb +++ b/source/spec/vagrant-openstack-provider/client/keystone_spec.rb @@ -23,6 +23,8 @@ config.stub(:project_name) { 'testTenant' } config.stub(:ssl_ca_file) { nil } config.stub(:ssl_verify_peer) { true } + config.stub(:app_cred_id) { 'dummy' } + config.stub(:app_cred_secret) { 'dummy' } end end @@ -81,6 +83,10 @@ ]}}' end + let(:keystone_request_body_app_cred) do + '{"auth":{"identity":{"methods":["application_credential"],"application_credential":{"id":"dummy","secret":"dummy"}}}}' + end + before :each do @keystone_client = VagrantPlugins::Openstack.keystone end @@ -183,6 +189,7 @@ config.stub(:project_domain_name) { 'dummy' } config.stub(:identity_api_version) { '3' } config.stub(:openstack_auth_url) { 'http://keystoneAuthV3' } + config.stub(:openstack_auth_type) { nil } stub_request(:post, 'http://keystoneAuthV3/auth/tokens') .with( @@ -206,6 +213,7 @@ config.stub(:project_domain_name) { 'dummy' } config.stub(:identity_api_version) { '3' } config.stub(:openstack_auth_url) { 'http://keystoneAuthV3' } + config.stub(:openstack_auth_type) { nil } stub_request(:post, 'http://keystoneAuthV3/auth/tokens') .with( @@ -225,5 +233,53 @@ expect { @keystone_client.authenticate(env) }.to raise_error(Errors::AuthenticationFailed) end end + + context 'with good application credentials' do + it 'store token and tenant id' do + config.stub(:identity_api_version) { '3' } + config.stub(:openstack_auth_url) { 'http://keystoneAuthV3' } + config.stub(:openstack_auth_type) { 'v3applicationcredential' } + + stub_request(:post, 'http://keystoneAuthV3/auth/tokens') + .with( + body: keystone_request_body_app_cred, + headers: keystone_request_headers) + .to_return( + status: 200, + body: keystone_response_body_v3, + headers: keystone_response_headers_v3) + + @keystone_client.authenticate(env) + + session.token.should eq('0123456789') + session.project_id.should eq('012345678910') + end + end + + context 'with wrong application credentials' do + it 'raise an unauthorized error ' do + config.stub(:identity_api_version) { '3' } + config.stub(:openstack_auth_url) { 'http://keystoneAuthV3' } + config.stub(:openstack_auth_type) { nil } + config.stub(:openstack_auth_type) { 'v3applicationcredential' } + + stub_request(:post, 'http://keystoneAuthV3/auth/tokens') + .with( + body: keystone_request_body_app_cred, + headers: keystone_request_headers) + .to_return( + status: 404, + body: '{ + "error": { + "message": "Could not find Application Credential: dummy", + "code": 404, + "title": "Not Found" + } + }', + headers: keystone_response_headers_v3) + + expect { @keystone_client.authenticate(env) }.to raise_error(Errors::BadAuthenticationEndpoint) + end + end end end