Skip to content

Commit

Permalink
Add support for OpenStack application credentials and update validati…
Browse files Browse the repository at this point in the history
…on (kubermatic#1666)

* Add variables for application credentials and validate them

Signed-off-by: Marvin Beckers <[email protected]>

* Remove MachineControllerName field

Co-authored-by: Marko Mudrinić <[email protected]>

* Handle both 'partial credentials' situations and add test cases

Signed-off-by: Marvin Beckers <[email protected]>

* Make the linter happy

Signed-off-by: Marvin Beckers <[email protected]>

* Further disagreements with the linter

Signed-off-by: Marvin Beckers <[email protected]>

Co-authored-by: Marko Mudrinić <[email protected]>
  • Loading branch information
embik and xmudrii authored Dec 7, 2021
1 parent f74a388 commit 0340e75
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 27 deletions.
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,6 @@ issues:

- path: pkg/scripts
text: "`registry` always receives `\"127.0.0.1:5000\"`"

- path: pkg/credentials
text: "cyclomatic complexity 32 of func `openstackValidationFunc` is high"
100 changes: 73 additions & 27 deletions pkg/credentials/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,29 @@ import (
// The environment variable names with credential in them
const (
// Variables that KubeOne (and Terraform) expect to see
AWSAccessKeyID = "AWS_ACCESS_KEY_ID"
AWSSecretAccessKey = "AWS_SECRET_ACCESS_KEY" //nolint:gosec
AzureClientID = "ARM_CLIENT_ID"
AzureClientSecret = "ARM_CLIENT_SECRET" //nolint:gosec
AzureTenantID = "ARM_TENANT_ID"
AzureSubscribtionID = "ARM_SUBSCRIPTION_ID"
DigitalOceanTokenKey = "DIGITALOCEAN_TOKEN"
GoogleServiceAccountKey = "GOOGLE_CREDENTIALS"
HetznerTokenKey = "HCLOUD_TOKEN"
OpenStackAuthURL = "OS_AUTH_URL"
OpenStackDomainName = "OS_DOMAIN_NAME"
OpenStackPassword = "OS_PASSWORD"
OpenStackRegionName = "OS_REGION_NAME"
OpenStackTenantID = "OS_TENANT_ID"
OpenStackTenantName = "OS_TENANT_NAME"
OpenStackUserName = "OS_USERNAME"
PacketAPIKey = "PACKET_AUTH_TOKEN" //nolint:gosec
PacketProjectID = "PACKET_PROJECT_ID"
VSphereAddress = "VSPHERE_SERVER"
VSpherePassword = "VSPHERE_PASSWORD"
VSphereUsername = "VSPHERE_USER"
AWSAccessKeyID = "AWS_ACCESS_KEY_ID"
AWSSecretAccessKey = "AWS_SECRET_ACCESS_KEY" //nolint:gosec
AzureClientID = "ARM_CLIENT_ID"
AzureClientSecret = "ARM_CLIENT_SECRET" //nolint:gosec
AzureTenantID = "ARM_TENANT_ID"
AzureSubscribtionID = "ARM_SUBSCRIPTION_ID"
DigitalOceanTokenKey = "DIGITALOCEAN_TOKEN"
GoogleServiceAccountKey = "GOOGLE_CREDENTIALS"
HetznerTokenKey = "HCLOUD_TOKEN"
OpenStackAuthURL = "OS_AUTH_URL"
OpenStackDomainName = "OS_DOMAIN_NAME"
OpenStackPassword = "OS_PASSWORD"
OpenStackRegionName = "OS_REGION_NAME"
OpenStackTenantID = "OS_TENANT_ID"
OpenStackTenantName = "OS_TENANT_NAME"
OpenStackUserName = "OS_USERNAME"
OpenStackApplicationCredentialID = "OS_APPLICATION_CREDENTIAL_ID"
OpenStackApplicationCredentialSecret = "OS_APPLICATION_CREDENTIAL_SECRET"
PacketAPIKey = "PACKET_AUTH_TOKEN" //nolint:gosec
PacketProjectID = "PACKET_PROJECT_ID"
VSphereAddress = "VSPHERE_SERVER"
VSpherePassword = "VSPHERE_PASSWORD"
VSphereUsername = "VSPHERE_USER"

// Variables that machine-controller expects
AzureClientIDMC = "AZURE_CLIENT_ID"
Expand Down Expand Up @@ -158,6 +160,8 @@ func ProviderCredentials(cloudProvider kubeone.CloudProviderSpec, credentialsFil
{Name: OpenStackAuthURL},
{Name: OpenStackUserName, MachineControllerName: OpenStackUserNameMC},
{Name: OpenStackPassword},
{Name: OpenStackApplicationCredentialID},
{Name: OpenStackApplicationCredentialSecret},
{Name: OpenStackDomainName},
{Name: OpenStackRegionName},
{Name: OpenStackTenantID},
Expand Down Expand Up @@ -283,15 +287,57 @@ func defaultValidationFunc(creds map[string]string) error {
}

func openstackValidationFunc(creds map[string]string) error {
for k, v := range creds {
if k == OpenStackTenantID || k == OpenStackTenantName {
continue
}
if len(v) == 0 {
return errors.Errorf("key %v is required but isn't present", k)
alwaysRequired := []string{OpenStackAuthURL, OpenStackDomainName, OpenStackRegionName}

for _, key := range alwaysRequired {
if v, ok := creds[key]; !ok || len(v) == 0 {
return errors.Errorf("key %v is required but is not present", key)
}
}

var (
appCredsIDOkay bool
appCredsSecretOkay bool
userCredsUsernameOkay bool
userCredsPasswordOkay bool
)

if v, ok := creds[OpenStackApplicationCredentialID]; ok && len(v) != 0 {
appCredsIDOkay = true
}

if v, ok := creds[OpenStackApplicationCredentialSecret]; ok && len(v) != 0 {
appCredsSecretOkay = true
}

if v, ok := creds[OpenStackUserName]; ok && len(v) != 0 {
userCredsUsernameOkay = true
}

if v, ok := creds[OpenStackPassword]; ok && len(v) != 0 {
userCredsPasswordOkay = true
}

if (appCredsIDOkay || appCredsSecretOkay) && (userCredsUsernameOkay || userCredsPasswordOkay) {
return errors.Errorf("both app credentials (%s %s) and user credentials (%s %s) found",
OpenStackApplicationCredentialID, OpenStackApplicationCredentialSecret,
OpenStackUserName, OpenStackPassword)
}

if (appCredsIDOkay && !appCredsSecretOkay) || (!appCredsIDOkay && appCredsSecretOkay) {
return errors.Errorf("only one of %s, %s is set for application credentials",
OpenStackApplicationCredentialID, OpenStackApplicationCredentialSecret)
}

if (userCredsUsernameOkay && !userCredsPasswordOkay) || (!userCredsUsernameOkay && userCredsPasswordOkay) {
return errors.Errorf("only one of %s, %s is set for user credentials",
OpenStackUserName, OpenStackPassword)
}

if (!appCredsIDOkay && !appCredsSecretOkay) && (!userCredsUsernameOkay && !userCredsPasswordOkay) {
return errors.New("no valid credentials (either application or user) found")
}

if v, ok := creds[OpenStackTenantID]; !ok || len(v) == 0 {
if v, ok := creds[OpenStackTenantName]; !ok || len(v) == 0 {
return errors.Errorf("key %v or %v is required but isn't present", OpenStackTenantID, OpenStackTenantName)
Expand Down
157 changes: 157 additions & 0 deletions pkg/credentials/credentials_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
Copyright 2021 The KubeOne Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package credentials

import (
"errors"
"testing"
)

func TestOpenstackValidationFunc(t *testing.T) {
t.Parallel()

tests := []struct {
name string
creds map[string]string
err error
}{
{
name: "no-credentials",
creds: map[string]string{},
err: errors.New("key OS_AUTH_URL is required but is not present"),
},
{
name: "application-credentials",
creds: map[string]string{
"OS_TENANT_NAME": "test",
"OS_AUTH_URL": "https://localhost:5000",
"OS_DOMAIN_NAME": "test",
"OS_REGION_NAME": "de",
"OS_APPLICATION_CREDENTIAL_ID": "1234",
"OS_APPLICATION_CREDENTIAL_SECRET": "5678",
},
err: nil,
},
{
name: "no-credential-secret",
creds: map[string]string{
"OS_TENANT_NAME": "test",
"OS_AUTH_URL": "https://localhost:5000",
"OS_DOMAIN_NAME": "test",
"OS_REGION_NAME": "de",
"OS_APPLICATION_CREDENTIAL_ID": "1234",
},
err: errors.New("only one of OS_APPLICATION_CREDENTIAL_ID, OS_APPLICATION_CREDENTIAL_SECRET is set for application credentials"),
},
{
name: "no-credential-id",
creds: map[string]string{
"OS_TENANT_NAME": "test",
"OS_AUTH_URL": "https://localhost:5000",
"OS_DOMAIN_NAME": "test",
"OS_REGION_NAME": "de",
"OS_APPLICATION_CREDENTIAL_SECRET": "5678",
},
err: errors.New("only one of OS_APPLICATION_CREDENTIAL_ID, OS_APPLICATION_CREDENTIAL_SECRET is set for application credentials"),
},
{
name: "user-credentials",
creds: map[string]string{
"OS_TENANT_NAME": "test",
"OS_AUTH_URL": "https://localhost:5000",
"OS_DOMAIN_NAME": "test",
"OS_REGION_NAME": "de",
"OS_USERNAME": "1234",
"OS_PASSWORD": "5678",
},
err: nil,
},
{
name: "no-password",
creds: map[string]string{
"OS_TENANT_NAME": "test",
"OS_AUTH_URL": "https://localhost:5000",
"OS_DOMAIN_NAME": "test",
"OS_REGION_NAME": "de",
"OS_USERNAME": "1234",
},
err: errors.New("only one of OS_USERNAME, OS_PASSWORD is set for user credentials"),
},
{
name: "no-username",
creds: map[string]string{
"OS_TENANT_NAME": "test",
"OS_AUTH_URL": "https://localhost:5000",
"OS_DOMAIN_NAME": "test",
"OS_REGION_NAME": "de",
"OS_PASSWORD": "5678",
},
err: errors.New("only one of OS_USERNAME, OS_PASSWORD is set for user credentials"),
},
{
name: "mixed-credentials-1",
creds: map[string]string{
"OS_TENANT_NAME": "test",
"OS_AUTH_URL": "https://localhost:5000",
"OS_DOMAIN_NAME": "test",
"OS_REGION_NAME": "de",
"OS_APPLICATION_CREDENTIAL_ID": "1234",
"OS_PASSWORD": "5678",
},
err: errors.New("both app credentials (OS_APPLICATION_CREDENTIAL_ID OS_APPLICATION_CREDENTIAL_SECRET) and user credentials (OS_USERNAME OS_PASSWORD) found"),
},
{
name: "mixed-credentials-2",
creds: map[string]string{
"OS_TENANT_NAME": "test",
"OS_AUTH_URL": "https://localhost:5000",
"OS_DOMAIN_NAME": "test",
"OS_REGION_NAME": "de",
"OS_APPLICATION_CREDENTIAL_SECRET": "5678",
"OS_USERNAME": "1234",
},
err: errors.New("both app credentials (OS_APPLICATION_CREDENTIAL_ID OS_APPLICATION_CREDENTIAL_SECRET) and user credentials (OS_USERNAME OS_PASSWORD) found"),
},
{
name: "mixed-credentials-3",
creds: map[string]string{
"OS_TENANT_NAME": "test",
"OS_AUTH_URL": "https://localhost:5000",
"OS_DOMAIN_NAME": "test",
"OS_REGION_NAME": "de",
"OS_APPLICATION_CREDENTIAL_ID": "1234",
"OS_APPLICATION_CREDENTIAL_SECRET": "5678",
"OS_USERNAME": "1234",
"OS_PASSWORD": "5678",
},
err: errors.New("both app credentials (OS_APPLICATION_CREDENTIAL_ID OS_APPLICATION_CREDENTIAL_SECRET) and user credentials (OS_USERNAME OS_PASSWORD) found"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := openstackValidationFunc(tt.creds)
if tt.err != nil && err != nil {
if err.Error() != tt.err.Error() {
t.Errorf("expected error = '%v', got error = '%v'", tt.err.Error(), err.Error())
}
} else if err != tt.err {
t.Errorf("%s: expected error = %v, got error = %v", tt.name, tt.err, err)
}
})
}
}

0 comments on commit 0340e75

Please sign in to comment.