mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
openstack: implement account key for cluster-internal authentication
This commit is contained in:
parent
1b2a927b84
commit
f785ae560b
@ -4,6 +4,7 @@ load("//bazel/go:go_test.bzl", "go_test")
|
||||
go_library(
|
||||
name = "openstack",
|
||||
srcs = [
|
||||
"accountkey.go",
|
||||
"api.go",
|
||||
"imds.go",
|
||||
"openstack.go",
|
||||
@ -27,6 +28,7 @@ go_library(
|
||||
go_test(
|
||||
name = "openstack_test",
|
||||
srcs = [
|
||||
"accountkey_test.go",
|
||||
"api_test.go",
|
||||
"imds_test.go",
|
||||
"openstack_test.go",
|
||||
|
145
internal/cloud/openstack/accountkey.go
Normal file
145
internal/cloud/openstack/accountkey.go
Normal file
@ -0,0 +1,145 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// AccountKey is a OpenStack account key.
|
||||
type AccountKey struct {
|
||||
AuthURL string
|
||||
Username string
|
||||
Password string
|
||||
ProjectID string
|
||||
ProjectName string
|
||||
UserDomainName string
|
||||
ProjectDomainName string
|
||||
RegionName string
|
||||
}
|
||||
|
||||
// AccountKeyFromURI parses ServiceAccountKey from URI.
|
||||
func AccountKeyFromURI(serviceAccountURI string) (AccountKey, error) {
|
||||
uri, err := url.Parse(serviceAccountURI)
|
||||
if err != nil {
|
||||
return AccountKey{}, err
|
||||
}
|
||||
if uri.Scheme != "serviceaccount" {
|
||||
return AccountKey{}, fmt.Errorf("invalid service account URI: invalid scheme: %s", uri.Scheme)
|
||||
}
|
||||
if uri.Host != "openstack" {
|
||||
return AccountKey{}, fmt.Errorf("invalid service account URI: invalid host: %s", uri.Host)
|
||||
}
|
||||
query := uri.Query()
|
||||
if query.Get("auth_url") == "" {
|
||||
return AccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"auth_url\": %s", uri)
|
||||
}
|
||||
if query.Get("username") == "" {
|
||||
return AccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"username\": %s", uri)
|
||||
}
|
||||
if query.Get("password") == "" {
|
||||
return AccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"password\": %s", uri)
|
||||
}
|
||||
if query.Get("project_id") == "" {
|
||||
return AccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"project_id\": %s", uri)
|
||||
}
|
||||
if query.Get("project_name") == "" {
|
||||
return AccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"project_name\": %s", uri)
|
||||
}
|
||||
if query.Get("user_domain_name") == "" {
|
||||
return AccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"user_domain_name\": %s", uri)
|
||||
}
|
||||
if query.Get("project_domain_name") == "" {
|
||||
return AccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"project_domain_name\": %s", uri)
|
||||
}
|
||||
if query.Get("region_name") == "" {
|
||||
return AccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"region_name\": %s", uri)
|
||||
}
|
||||
return AccountKey{
|
||||
AuthURL: query.Get("auth_url"),
|
||||
Username: query.Get("username"),
|
||||
Password: query.Get("password"),
|
||||
ProjectID: query.Get("project_id"),
|
||||
ProjectName: query.Get("project_name"),
|
||||
UserDomainName: query.Get("user_domain_name"),
|
||||
ProjectDomainName: query.Get("project_domain_name"),
|
||||
RegionName: query.Get("region_name"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ToCloudServiceAccountURI converts the AccountKey into a cloud service account URI.
|
||||
func (k AccountKey) ToCloudServiceAccountURI() string {
|
||||
query := url.Values{}
|
||||
query.Add("auth_url", k.AuthURL)
|
||||
query.Add("username", k.Username)
|
||||
query.Add("password", k.Password)
|
||||
query.Add("project_id", k.ProjectID)
|
||||
query.Add("project_name", k.ProjectName)
|
||||
query.Add("user_domain_name", k.UserDomainName)
|
||||
query.Add("project_domain_name", k.ProjectDomainName)
|
||||
query.Add("region_name", k.RegionName)
|
||||
uri := url.URL{
|
||||
Scheme: "serviceaccount",
|
||||
Host: "openstack",
|
||||
RawQuery: query.Encode(),
|
||||
}
|
||||
return uri.String()
|
||||
}
|
||||
|
||||
// CloudINI converts the AccountKey into a CloudINI.
|
||||
func (k AccountKey) CloudINI() CloudINI {
|
||||
return CloudINI{
|
||||
AuthURL: k.AuthURL,
|
||||
Username: k.Username,
|
||||
Password: k.Password,
|
||||
TenantID: k.ProjectID,
|
||||
TenantName: k.ProjectName,
|
||||
UserDomainName: k.UserDomainName,
|
||||
TenantDomainName: k.ProjectDomainName,
|
||||
Region: k.RegionName,
|
||||
}
|
||||
}
|
||||
|
||||
// CloudINI is a struct that represents the cloud.ini file used by OpenStack k8s deployments.
|
||||
type CloudINI struct {
|
||||
AuthURL string `gcfg:"auth-url" mapstructure:"auth-url" name:"os-authURL" dependsOn:"os-password|os-trustID|os-applicationCredentialSecret|os-clientCertPath"`
|
||||
Username string `name:"os-userName" value:"optional" dependsOn:"os-password"`
|
||||
Password string `name:"os-password" value:"optional" dependsOn:"os-domainID|os-domainName,os-projectID|os-projectName,os-userID|os-userName"`
|
||||
TenantID string `gcfg:"tenant-id" mapstructure:"project-id" name:"os-projectID" value:"optional" dependsOn:"os-password|os-clientCertPath"`
|
||||
TenantName string `gcfg:"tenant-name" mapstructure:"project-name" name:"os-projectName" value:"optional" dependsOn:"os-password|os-clientCertPath"`
|
||||
UserDomainName string `gcfg:"user-domain-name" mapstructure:"user-domain-name" name:"os-userDomainName" value:"optional"`
|
||||
TenantDomainName string `gcfg:"tenant-domain-name" mapstructure:"project-domain-name" name:"os-projectDomainName" value:"optional"`
|
||||
Region string `name:"os-region"`
|
||||
}
|
||||
|
||||
// String returns the string representation of the CloudINI.
|
||||
func (i CloudINI) String() string {
|
||||
// sanitize parameters to not include newlines
|
||||
authURL := newlineRegexp.ReplaceAllString(i.AuthURL, "")
|
||||
username := newlineRegexp.ReplaceAllString(i.Username, "")
|
||||
password := newlineRegexp.ReplaceAllString(i.Password, "")
|
||||
tenantID := newlineRegexp.ReplaceAllString(i.TenantID, "")
|
||||
tenantName := newlineRegexp.ReplaceAllString(i.TenantName, "")
|
||||
userDomainName := newlineRegexp.ReplaceAllString(i.UserDomainName, "")
|
||||
tenantDomainName := newlineRegexp.ReplaceAllString(i.TenantDomainName, "")
|
||||
region := newlineRegexp.ReplaceAllString(i.Region, "")
|
||||
|
||||
return fmt.Sprintf(`[Global]
|
||||
auth-url = %s
|
||||
username = %s
|
||||
password = %s
|
||||
tenant-id = %s
|
||||
tenant-name = %s
|
||||
user-domain-name = %s
|
||||
tenant-domain-name = %s
|
||||
region = %s
|
||||
`, authURL, username, password, tenantID, tenantName, userDomainName, tenantDomainName, region)
|
||||
}
|
||||
|
||||
var newlineRegexp = regexp.MustCompile(`[\r\n]+`)
|
175
internal/cloud/openstack/accountkey_test.go
Normal file
175
internal/cloud/openstack/accountkey_test.go
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAccountKeyFromURI(t *testing.T) {
|
||||
accountKey := AccountKey{
|
||||
AuthURL: "auth-url",
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
UserDomainName: "user-domain-name",
|
||||
ProjectDomainName: "project-domain-name",
|
||||
RegionName: "region-name",
|
||||
}
|
||||
testCases := map[string]struct {
|
||||
cloudServiceAccountURI string
|
||||
wantKey AccountKey
|
||||
wantErr bool
|
||||
}{
|
||||
"successful": {
|
||||
cloudServiceAccountURI: "serviceaccount://openstack?auth_url=auth-url&username=username&password=password&project_id=project-id&project_name=project-name&user_domain_name=user-domain-name&project_domain_name=project-domain-name®ion_name=region-name",
|
||||
wantKey: accountKey,
|
||||
},
|
||||
"missing auth_url": {
|
||||
cloudServiceAccountURI: "serviceaccount://openstack?username=username&password=password&project_id=project-id&project_name=project-name&user_domain_name=user-domain-name&project_domain_name=project-domain-name®ion_name=region-name",
|
||||
wantErr: true,
|
||||
},
|
||||
"missing username": {
|
||||
cloudServiceAccountURI: "serviceaccount://openstack?auth_url=auth-url&password=password&project_id=project-id&project_name=project-name&user_domain_name=user-domain-name&project_domain_name=project-domain-name®ion_name=region-name",
|
||||
wantErr: true,
|
||||
},
|
||||
"missing password": {
|
||||
cloudServiceAccountURI: "serviceaccount://openstack?auth_url=auth-url&username=username&project_id=project-id&project_name=project-name&user_domain_name=user-domain-name&project_domain_name=project-domain-name®ion_name=region-name",
|
||||
wantErr: true,
|
||||
},
|
||||
"missing project_id": {
|
||||
cloudServiceAccountURI: "serviceaccount://openstack?auth_url=auth-url&username=username&password=password&project_name=project-name&user_domain_name=user-domain-name&project_domain_name=project-domain-name®ion_name=region-name",
|
||||
wantErr: true,
|
||||
},
|
||||
"missing project_name": {
|
||||
cloudServiceAccountURI: "serviceaccount://openstack?auth_url=auth-url&username=username&password=password&project_id=project-id&user_domain_name=user-domain-name&project_domain_name=project-domain-name®ion_name=region-name",
|
||||
wantErr: true,
|
||||
},
|
||||
"missing user_domain_name": {
|
||||
cloudServiceAccountURI: "serviceaccount://openstack?auth_url=auth-url&username=username&password=password&project_id=project-id&project_name=project-name&project_domain_name=project-domain-name®ion_name=region-name",
|
||||
wantErr: true,
|
||||
},
|
||||
"missing project_domain_name": {
|
||||
cloudServiceAccountURI: "serviceaccount://openstack?auth_url=auth-url&username=username&password=password&project_id=project-id&project_name=project-name&user_domain_name=user-domain-name®ion_name=region-name",
|
||||
wantErr: true,
|
||||
},
|
||||
"missing region_name": {
|
||||
cloudServiceAccountURI: "serviceaccount://openstack?auth_url=auth-url&username=username&password=password&project_id=project-id&project_name=project-name&user_domain_name=user-domain-name&project_domain_name=project-domain-name",
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid URI fails": {
|
||||
cloudServiceAccountURI: "\x00",
|
||||
wantErr: true,
|
||||
},
|
||||
"incorrect URI scheme fails": {
|
||||
cloudServiceAccountURI: "invalid",
|
||||
wantErr: true,
|
||||
},
|
||||
"incorrect URI host fails": {
|
||||
cloudServiceAccountURI: "serviceaccount://incorrect",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
key, err := AccountKeyFromURI(tc.cloudServiceAccountURI)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantKey, key)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToCloudServiceAccountURI(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
key := AccountKey{
|
||||
AuthURL: "auth-url",
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
UserDomainName: "user-domain-name",
|
||||
ProjectDomainName: "project-domain-name",
|
||||
RegionName: "region-name",
|
||||
}
|
||||
accountURI := key.ToCloudServiceAccountURI()
|
||||
uri, err := url.Parse(accountURI)
|
||||
require.NoError(err)
|
||||
query := uri.Query()
|
||||
assert.Equal("serviceaccount", uri.Scheme)
|
||||
assert.Equal("openstack", uri.Host)
|
||||
assert.Equal(url.Values{
|
||||
"auth_url": []string{"auth-url"},
|
||||
"username": []string{"username"},
|
||||
"password": []string{"password"},
|
||||
"project_id": []string{"project-id"},
|
||||
"project_name": []string{"project-name"},
|
||||
"user_domain_name": []string{"user-domain-name"},
|
||||
"project_domain_name": []string{"project-domain-name"},
|
||||
"region_name": []string{"region-name"},
|
||||
}, query)
|
||||
}
|
||||
|
||||
func TestAccountKeyToCloudINI(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
key := AccountKey{
|
||||
AuthURL: "auth-url",
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
UserDomainName: "user-domain-name",
|
||||
ProjectDomainName: "project-domain-name",
|
||||
RegionName: "region-name",
|
||||
}
|
||||
ini := key.CloudINI()
|
||||
assert.Equal(CloudINI{
|
||||
AuthURL: "auth-url",
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
TenantID: "project-id",
|
||||
TenantName: "project-name",
|
||||
UserDomainName: "user-domain-name",
|
||||
TenantDomainName: "project-domain-name",
|
||||
Region: "region-name",
|
||||
}, ini)
|
||||
}
|
||||
|
||||
func TestCloudINIToString(t *testing.T) {
|
||||
ini := CloudINI{
|
||||
AuthURL: "auth-url",
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
TenantID: "project-id",
|
||||
TenantName: "project-name",
|
||||
UserDomainName: "user-domain-name",
|
||||
TenantDomainName: "project-domain-name",
|
||||
Region: "region-name",
|
||||
}
|
||||
assert.Equal(t, `[Global]
|
||||
auth-url = auth-url
|
||||
username = username
|
||||
password = password
|
||||
tenant-id = project-id
|
||||
tenant-name = project-name
|
||||
user-domain-name = user-domain-name
|
||||
tenant-domain-name = project-domain-name
|
||||
region = region-name
|
||||
`, ini.String())
|
||||
}
|
Loading…
Reference in New Issue
Block a user