From 9b7bbdb720e2f792d5e7d96f28c79adfa1afcb31 Mon Sep 17 00:00:00 2001 From: kayrus Date: Wed, 23 Jan 2019 16:41:32 +0100 Subject: [PATCH 1/3] Auth: add application credential support --- openstack/clientconfig/requests.go | 84 +++++++++++++++++++++--------- openstack/clientconfig/results.go | 9 ++++ 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/openstack/clientconfig/requests.go b/openstack/clientconfig/requests.go index 4b38c7a8..79fc436d 100644 --- a/openstack/clientconfig/requests.go +++ b/openstack/clientconfig/requests.go @@ -517,41 +517,65 @@ func v3auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) { } } + if cloud.AuthInfo.ApplicationCredentialID == "" { + if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_ID"); v != "" { + cloud.AuthInfo.ApplicationCredentialID = v + } + } + + if cloud.AuthInfo.ApplicationCredentialName == "" { + if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_NAME"); v != "" { + cloud.AuthInfo.ApplicationCredentialName = v + } + } + + if cloud.AuthInfo.ApplicationCredentialSecret == "" { + if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_SECRET"); v != "" { + cloud.AuthInfo.ApplicationCredentialSecret = v + } + } + // Build a scope and try to do it correctly. // https://github.com/openstack/os-client-config/blob/master/os_client_config/config.py#L595 scope := new(gophercloud.AuthScope) - if !isProjectScoped(cloud.AuthInfo) { - if cloud.AuthInfo.DomainID != "" { - scope.DomainID = cloud.AuthInfo.DomainID - } else if cloud.AuthInfo.DomainName != "" { - scope.DomainName = cloud.AuthInfo.DomainName - } - } else { - // If Domain* is set, but UserDomain* or ProjectDomain* aren't, - // then use Domain* as the default setting. - cloud = setDomainIfNeeded(cloud) - - if cloud.AuthInfo.ProjectID != "" { - scope.ProjectID = cloud.AuthInfo.ProjectID + // Application credentials don't support scope + if !isApplicationCredential(cloud.AuthInfo) { + if !isProjectScoped(cloud.AuthInfo) { + if cloud.AuthInfo.DomainID != "" { + scope.DomainID = cloud.AuthInfo.DomainID + } else if cloud.AuthInfo.DomainName != "" { + scope.DomainName = cloud.AuthInfo.DomainName + } } else { - scope.ProjectName = cloud.AuthInfo.ProjectName - scope.DomainID = cloud.AuthInfo.ProjectDomainID - scope.DomainName = cloud.AuthInfo.ProjectDomainName + // If Domain* is set, but UserDomain* or ProjectDomain* aren't, + // then use Domain* as the default setting. + cloud = setDomainIfNeeded(cloud) + + if cloud.AuthInfo.ProjectID != "" { + scope.ProjectID = cloud.AuthInfo.ProjectID + } else { + scope.ProjectName = cloud.AuthInfo.ProjectName + scope.DomainID = cloud.AuthInfo.ProjectDomainID + scope.DomainName = cloud.AuthInfo.ProjectDomainName + } } } ao := &gophercloud.AuthOptions{ - Scope: scope, - IdentityEndpoint: cloud.AuthInfo.AuthURL, - TokenID: cloud.AuthInfo.Token, - Username: cloud.AuthInfo.Username, - UserID: cloud.AuthInfo.UserID, - Password: cloud.AuthInfo.Password, - TenantID: cloud.AuthInfo.ProjectID, - TenantName: cloud.AuthInfo.ProjectName, - DomainID: cloud.AuthInfo.UserDomainID, - DomainName: cloud.AuthInfo.UserDomainName, + Scope: scope, + IdentityEndpoint: cloud.AuthInfo.AuthURL, + TokenID: cloud.AuthInfo.Token, + Username: cloud.AuthInfo.Username, + UserID: cloud.AuthInfo.UserID, + Password: cloud.AuthInfo.Password, + TenantID: cloud.AuthInfo.ProjectID, + TenantName: cloud.AuthInfo.ProjectName, + DomainID: cloud.AuthInfo.UserDomainID, + DomainName: cloud.AuthInfo.UserDomainName, + ApplicationCredentialID: cloud.AuthInfo.ApplicationCredentialID, + ApplicationCredentialName: cloud.AuthInfo.ApplicationCredentialName, + ApplicationCredentialSecret: cloud.AuthInfo.ApplicationCredentialSecret, } // If an auth_type of "token" was specified, then make sure @@ -761,3 +785,11 @@ func setDomainIfNeeded(cloud *Cloud) *Cloud { return cloud } + +// isApplicationCredential determines if an application credential is used to auth. +func isApplicationCredential(authInfo *AuthInfo) bool { + if authInfo.ApplicationCredentialID == "" && authInfo.ApplicationCredentialName == "" && authInfo.ApplicationCredentialSecret == "" { + return false + } + return true +} diff --git a/openstack/clientconfig/results.go b/openstack/clientconfig/results.go index b49367c3..3dc849c6 100644 --- a/openstack/clientconfig/results.go +++ b/openstack/clientconfig/results.go @@ -61,6 +61,15 @@ type AuthInfo struct { // Password is the password of the user. Password string `yaml:"password"` + // Application Credential ID to login with. + ApplicationCredentialID string `yaml:"application_credential_id"` + + // Application Credential name to login with. + ApplicationCredentialName string `yaml:"application_credential_name"` + + // Application Credential secret to login with. + ApplicationCredentialSecret string `yaml:"application_credential_secret"` + // ProjectName is the common/human-readable name of a project. // Users can be scoped to a project. // ProjectName on its own is not enough to ensure a unique scope. It must From 9328775481bafa36b1486719cda9082b88aa053c Mon Sep 17 00:00:00 2001 From: kayrus Date: Wed, 23 Jan 2019 21:48:51 +0100 Subject: [PATCH 2/3] Fix formatting --- acceptance/gnocchi/metric/v1/resources.go | 2 +- openstack/clientconfig/requests.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance/gnocchi/metric/v1/resources.go b/acceptance/gnocchi/metric/v1/resources.go index 2c64d605..d1e174d8 100644 --- a/acceptance/gnocchi/metric/v1/resources.go +++ b/acceptance/gnocchi/metric/v1/resources.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/utils/gnocchi/metric/v1/resources" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" ) // CreateGenericResource will create a Gnocchi resource with a generic type. diff --git a/openstack/clientconfig/requests.go b/openstack/clientconfig/requests.go index 79fc436d..eb447170 100644 --- a/openstack/clientconfig/requests.go +++ b/openstack/clientconfig/requests.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" - "gopkg.in/yaml.v2" + yaml "gopkg.in/yaml.v2" ) // AuthType respresents a valid method of authentication. From 811c06ce0dc2a42331a9e6579be0ee34433b736f Mon Sep 17 00:00:00 2001 From: kayrus Date: Wed, 23 Jan 2019 23:46:04 +0100 Subject: [PATCH 3/3] Add unit tests --- openstack/clientconfig/requests.go | 5 ++++ openstack/clientconfig/testing/clouds.yaml | 8 ++++++- openstack/clientconfig/testing/fixtures.go | 24 +++++++++++++++++++ .../clientconfig/testing/requests_test.go | 4 ++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/openstack/clientconfig/requests.go b/openstack/clientconfig/requests.go index eb447170..00793901 100644 --- a/openstack/clientconfig/requests.go +++ b/openstack/clientconfig/requests.go @@ -30,6 +30,9 @@ const ( AuthV3Password AuthType = "v3password" // AuthV3Token defines version 3 of the token AuthV3Token AuthType = "v3token" + + // AuthV3ApplicationCredential defines version 3 of the application credential + AuthV3ApplicationCredential AuthType = "v3applicationcredential" ) // ClientOpts represents options to customize the way a client is @@ -333,6 +336,8 @@ func determineIdentityAPI(cloud *Cloud, opts *ClientOpts) string { identityAPI = "3" case AuthV3Token: identityAPI = "3" + case AuthV3ApplicationCredential: + identityAPI = "3" } } diff --git a/openstack/clientconfig/testing/clouds.yaml b/openstack/clientconfig/testing/clouds.yaml index 58f74207..e25bb720 100644 --- a/openstack/clientconfig/testing/clouds.yaml +++ b/openstack/clientconfig/testing/clouds.yaml @@ -127,4 +127,10 @@ clouds: password: "this should be overwritten by secure.yaml" project_name: "Some Project" region_name: "PHL" - + virginia: + auth_type: "v3applicationcredential" + auth: + auth_url: "https://va.example.com:5000/v3" + application_credential_id: "app-cred-id" + application_credential_secret: "secret" + region_name: "VA" diff --git a/openstack/clientconfig/testing/fixtures.go b/openstack/clientconfig/testing/fixtures.go index 911acdd6..704c99e7 100644 --- a/openstack/clientconfig/testing/fixtures.go +++ b/openstack/clientconfig/testing/fixtures.go @@ -8,6 +8,30 @@ import ( var iTrue = true var iFalse = false +var VirginiaEnvAuth = map[string]string{ + "OS_AUTH_URL": "https://va.example.com:5000/v3", + "OS_APPLICATION_CREDENTIAL_ID": "app-cred-id", + "OS_APPLICATION_CREDENTIAL_SECRET": "secret", +} + +var VirginiaAuthOpts = &gophercloud.AuthOptions{ + Scope: &gophercloud.AuthScope{}, + IdentityEndpoint: "https://va.example.com:5000/v3", + ApplicationCredentialID: "app-cred-id", + ApplicationCredentialSecret: "secret", +} + +var VirginiaCloudYAML = clientconfig.Cloud{ + RegionName: "VA", + AuthInfo: &clientconfig.AuthInfo{ + AuthURL: "https://va.example.com:5000/v3", + ApplicationCredentialID: "app-cred-id", + ApplicationCredentialSecret: "secret", + }, + Verify: &iTrue, + AuthType: "v3applicationcredential", +} + var PhiladelphiaCloudYAML = clientconfig.Cloud{ RegionName: "PHL", AuthInfo: &clientconfig.AuthInfo{ diff --git a/openstack/clientconfig/testing/requests_test.go b/openstack/clientconfig/testing/requests_test.go index f8b4d25a..2789669b 100644 --- a/openstack/clientconfig/testing/requests_test.go +++ b/openstack/clientconfig/testing/requests_test.go @@ -27,6 +27,7 @@ func TestGetCloudFromYAML(t *testing.T) { "chicago_legacy": &clientconfig.ClientOpts{Cloud: "chicago_legacy"}, "chicago_useprofile": &clientconfig.ClientOpts{Cloud: "chicago_useprofile"}, "philadelphia": &clientconfig.ClientOpts{Cloud: "philadelphia"}, + "virginia": &clientconfig.ClientOpts{Cloud: "virginia"}, } expectedClouds := map[string]*clientconfig.Cloud{ @@ -38,6 +39,7 @@ func TestGetCloudFromYAML(t *testing.T) { "chicago_legacy": &ChicagoCloudLegacyYAML, "chicago_useprofile": &ChicagoCloudUseProfileYAML, "philadelphia": &PhiladelphiaCloudYAML, + "virginia": &VirginiaCloudYAML, } for cloud, clientOpts := range allClientOpts { @@ -192,6 +194,7 @@ func TestAuthOptionsCreationFromEnv(t *testing.T) { "newmexico": NewMexicoEnvAuth, "nevada": NevadaEnvAuth, "texas": TexasEnvAuth, + "virginia": VirginiaEnvAuth, } expectedAuthOpts := map[string]*gophercloud.AuthOptions{ @@ -202,6 +205,7 @@ func TestAuthOptionsCreationFromEnv(t *testing.T) { "newmexico": NewMexicoAuthOpts, "nevada": NevadaAuthOpts, "texas": TexasAuthOpts, + "virginia": VirginiaAuthOpts, } for cloud, envVars := range allEnvVars {