openstack: read credentials from clouds.yaml

This commit is contained in:
Malte Poll 2024-03-11 14:20:19 +01:00
parent d69673fab7
commit c23f17de41
17 changed files with 169 additions and 160 deletions

View file

@ -0,0 +1,15 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "clouds",
srcs = [
"clouds.go",
"read.go",
],
importpath = "github.com/edgelesssys/constellation/v2/internal/cloud/openstack/clouds",
visibility = ["//:__subpackages__"],
deps = [
"//internal/file",
"@com_github_mitchellh_go_homedir//:go-homedir",
],
)

View file

@ -188,21 +188,21 @@ func (r *Region) UnmarshalYAML(unmarshal func(interface{}) error) error {
type AuthType string
const (
// AuthPassword defines an unknown version of the password
// AuthPassword defines an unknown version of the password.
AuthPassword AuthType = "password"
// AuthToken defined an unknown version of the token
// AuthToken defined an unknown version of the token.
AuthToken AuthType = "token"
// AuthV2Password defines version 2 of the password
// AuthV2Password defines version 2 of the password.
AuthV2Password AuthType = "v2password"
// AuthV2Token defines version 2 of the token
// AuthV2Token defines version 2 of the token.
AuthV2Token AuthType = "v2token"
// AuthV3Password defines version 3 of the password
// AuthV3Password defines version 3 of the password.
AuthV3Password AuthType = "v3password"
// AuthV3Token defines version 3 of the token
// AuthV3Token defines version 3 of the token.
AuthV3Token AuthType = "v3token"
// AuthV3ApplicationCredential defines version 3 of the application credential
// AuthV3ApplicationCredential defines version 3 of the application credential.
AuthV3ApplicationCredential AuthType = "v3applicationcredential"
)

View file

@ -0,0 +1,59 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package clouds
import (
"fmt"
"os"
"path/filepath"
"github.com/mitchellh/go-homedir"
"github.com/edgelesssys/constellation/v2/internal/file"
)
// ReadCloudsYAML reads a clouds.yaml file and returns its contents.
func ReadCloudsYAML(fileHandler file.Handler, path string) (Clouds, error) {
// Order of operations as performed by the OpenStack CLI:
// Define a search path for clouds.yaml:
// 1. If OS_CLIENT_CONFIG_FILE is set, use it as search path
// 2. Otherwise, use the following paths:
// - current directory
// - `openstack` directory under standard user config directory (e.g. ~/.config/openstack)
// - /etc/openstack (Unix only)
var searchPaths []string
if path != "" {
expanded, err := homedir.Expand(path)
if err == nil {
searchPaths = append(searchPaths, expanded)
} else {
searchPaths = append(searchPaths, path)
}
} else if osClientConfigFile := os.Getenv("OS_CLIENT_CONFIG_FILE"); osClientConfigFile != "" {
searchPaths = append(searchPaths, filepath.Join(osClientConfigFile, "clouds.yaml"))
} else {
searchPaths = append(searchPaths, "clouds.yaml")
confDir, err := os.UserConfigDir()
if err != nil {
return Clouds{}, fmt.Errorf("getting user config directory: %w", err)
}
searchPaths = append(searchPaths, filepath.Join(confDir, "openstack", "clouds.yaml"))
if os.PathSeparator == '/' {
searchPaths = append(searchPaths, "/etc/openstack/clouds.yaml")
}
}
var cloudsYAML Clouds
for _, path := range searchPaths {
if err := fileHandler.ReadYAML(path, &cloudsYAML); err == nil {
return cloudsYAML, nil
}
}
return Clouds{}, fmt.Errorf("clouds.yaml not found in search paths: %v", searchPaths)
}

View file

@ -198,40 +198,22 @@ type OpenStackConfig struct {
// OpenStack cloud name to select from "clouds.yaml". Only required if config file for OpenStack is used. Fallback authentication uses environment variables. For details see: https://docs.openstack.org/openstacksdk/latest/user/config/configuration.html.
Cloud string `yaml:"cloud"`
// description: |
// Path to OpenStack "clouds.yaml" file. Only required if automatic detection fails.
CloudsYAMLPath string `yaml:"cloudsYAMLPath"`
// description: |
// Availability zone to place the VMs in. For details see: https://docs.openstack.org/nova/latest/admin/availability-zones.html
AvailabilityZone string `yaml:"availabilityZone" validate:"required"`
// description: |
// Floating IP pool to use for the VMs. For details see: https://docs.openstack.org/ocata/user-guide/cli-manage-ip-addresses.html
FloatingIPPoolID string `yaml:"floatingIPPoolID" validate:"required"`
// description: |
// AuthURL is the OpenStack Identity endpoint to use inside the cluster.
AuthURL string `yaml:"authURL" validate:"required"`
// description: |
// ProjectID is the ID of the OpenStack project where a user resides.
ProjectID string `yaml:"projectID" validate:"required"`
// description: |
// STACKITProjectID is the ID of the STACKIT project where a user resides.
// Only used if cloud is "stackit".
STACKITProjectID string `yaml:"stackitProjectID"`
// description: |
// ProjectName is the name of the project where a user resides.
ProjectName string `yaml:"projectName" validate:"required"`
// description: |
// UserDomainName is the name of the domain where a user resides.
UserDomainName string `yaml:"userDomainName" validate:"required"`
// description: |
// ProjectDomainName is the name of the domain where a project resides.
ProjectDomainName string `yaml:"projectDomainName" validate:"required"`
// description: |
// RegionName is the name of the region to use inside the cluster.
RegionName string `yaml:"regionName" validate:"required"`
// description: |
// Username to use inside the cluster.
Username string `yaml:"username" validate:"required"`
// description: |
// Password to use inside the cluster. You can instead use the environment variable "CONSTELL_OS_PASSWORD".
Password string `yaml:"password"`
// description: |
// Deploy Yawol loadbalancer. For details see: https://github.com/stackitcloud/yawol
DeployYawolLoadBalancer *bool `yaml:"deployYawolLoadBalancer" validate:"required"`
// description: |
@ -496,11 +478,6 @@ func New(fileHandler file.Handler, name string, fetcher attestationconfigapi.Fet
fmt.Fprintf(os.Stderr, "WARNING: the environment variable %s is no longer used %s", constants.EnvVarAzureClientSecretValue, appRegistrationErrStr)
}
openstackPassword := os.Getenv(constants.EnvVarOpenStackPassword)
if openstackPassword != "" && c.Provider.OpenStack != nil {
c.Provider.OpenStack.Password = openstackPassword
}
return c, c.Validate(force)
}
@ -909,9 +886,6 @@ func (c *Config) WithOpenStackProviderDefaults(csp cloudprovider.Provider, openS
case "stackit":
c.Provider.OpenStack.Cloud = "stackit"
c.Provider.OpenStack.FloatingIPPoolID = "970ace5c-458f-484a-a660-0903bcfd91ad"
c.Provider.OpenStack.AuthURL = "https://keystone.api.iaas.eu01.stackit.cloud/v3"
c.Provider.OpenStack.UserDomainName = "portal_mvp"
c.Provider.OpenStack.ProjectDomainName = "portal_mvp"
c.Provider.OpenStack.RegionName = "RegionOne"
c.Provider.OpenStack.DeployYawolLoadBalancer = toPtr(true)
c.Provider.OpenStack.YawolImageID = "bcd6c13e-75d1-4c3f-bf0f-8f83580cc1be"

View file

@ -276,87 +276,57 @@ func init() {
FieldName: "openstack",
},
}
OpenStackConfigDoc.Fields = make([]encoder.Doc, 16)
OpenStackConfigDoc.Fields = make([]encoder.Doc, 10)
OpenStackConfigDoc.Fields[0].Name = "cloud"
OpenStackConfigDoc.Fields[0].Type = "string"
OpenStackConfigDoc.Fields[0].Note = ""
OpenStackConfigDoc.Fields[0].Description = "OpenStack cloud name to select from \"clouds.yaml\". Only required if config file for OpenStack is used. Fallback authentication uses environment variables. For details see: https://docs.openstack.org/openstacksdk/latest/user/config/configuration.html."
OpenStackConfigDoc.Fields[0].Comments[encoder.LineComment] = "OpenStack cloud name to select from \"clouds.yaml\". Only required if config file for OpenStack is used. Fallback authentication uses environment variables. For details see: https://docs.openstack.org/openstacksdk/latest/user/config/configuration.html."
OpenStackConfigDoc.Fields[1].Name = "availabilityZone"
OpenStackConfigDoc.Fields[1].Name = "cloudsYAMLPath"
OpenStackConfigDoc.Fields[1].Type = "string"
OpenStackConfigDoc.Fields[1].Note = ""
OpenStackConfigDoc.Fields[1].Description = "Availability zone to place the VMs in. For details see: https://docs.openstack.org/nova/latest/admin/availability-zones.html"
OpenStackConfigDoc.Fields[1].Comments[encoder.LineComment] = "Availability zone to place the VMs in. For details see: https://docs.openstack.org/nova/latest/admin/availability-zones.html"
OpenStackConfigDoc.Fields[2].Name = "floatingIPPoolID"
OpenStackConfigDoc.Fields[1].Description = "Path to OpenStack \"clouds.yaml\" file. Only required if automatic detection fails."
OpenStackConfigDoc.Fields[1].Comments[encoder.LineComment] = "Path to OpenStack \"clouds.yaml\" file. Only required if automatic detection fails."
OpenStackConfigDoc.Fields[2].Name = "availabilityZone"
OpenStackConfigDoc.Fields[2].Type = "string"
OpenStackConfigDoc.Fields[2].Note = ""
OpenStackConfigDoc.Fields[2].Description = "Floating IP pool to use for the VMs. For details see: https://docs.openstack.org/ocata/user-guide/cli-manage-ip-addresses.html"
OpenStackConfigDoc.Fields[2].Comments[encoder.LineComment] = "Floating IP pool to use for the VMs. For details see: https://docs.openstack.org/ocata/user-guide/cli-manage-ip-addresses.html"
OpenStackConfigDoc.Fields[3].Name = "authURL"
OpenStackConfigDoc.Fields[2].Description = "Availability zone to place the VMs in. For details see: https://docs.openstack.org/nova/latest/admin/availability-zones.html"
OpenStackConfigDoc.Fields[2].Comments[encoder.LineComment] = "Availability zone to place the VMs in. For details see: https://docs.openstack.org/nova/latest/admin/availability-zones.html"
OpenStackConfigDoc.Fields[3].Name = "floatingIPPoolID"
OpenStackConfigDoc.Fields[3].Type = "string"
OpenStackConfigDoc.Fields[3].Note = ""
OpenStackConfigDoc.Fields[3].Description = "description: |\nAuthURL is the OpenStack Identity endpoint to use inside the cluster.\n"
OpenStackConfigDoc.Fields[3].Comments[encoder.LineComment] = "description: |"
OpenStackConfigDoc.Fields[4].Name = "projectID"
OpenStackConfigDoc.Fields[3].Description = "Floating IP pool to use for the VMs. For details see: https://docs.openstack.org/ocata/user-guide/cli-manage-ip-addresses.html"
OpenStackConfigDoc.Fields[3].Comments[encoder.LineComment] = "Floating IP pool to use for the VMs. For details see: https://docs.openstack.org/ocata/user-guide/cli-manage-ip-addresses.html"
OpenStackConfigDoc.Fields[4].Name = "stackitProjectID"
OpenStackConfigDoc.Fields[4].Type = "string"
OpenStackConfigDoc.Fields[4].Note = ""
OpenStackConfigDoc.Fields[4].Description = "ProjectID is the ID of the OpenStack project where a user resides."
OpenStackConfigDoc.Fields[4].Comments[encoder.LineComment] = "ProjectID is the ID of the OpenStack project where a user resides."
OpenStackConfigDoc.Fields[5].Name = "stackitProjectID"
OpenStackConfigDoc.Fields[4].Description = "STACKITProjectID is the ID of the STACKIT project where a user resides.\nOnly used if cloud is \"stackit\"."
OpenStackConfigDoc.Fields[4].Comments[encoder.LineComment] = "STACKITProjectID is the ID of the STACKIT project where a user resides."
OpenStackConfigDoc.Fields[5].Name = "regionName"
OpenStackConfigDoc.Fields[5].Type = "string"
OpenStackConfigDoc.Fields[5].Note = ""
OpenStackConfigDoc.Fields[5].Description = "STACKITProjectID is the ID of the STACKIT project where a user resides.\nOnly used if cloud is \"stackit\"."
OpenStackConfigDoc.Fields[5].Comments[encoder.LineComment] = "STACKITProjectID is the ID of the STACKIT project where a user resides."
OpenStackConfigDoc.Fields[6].Name = "projectName"
OpenStackConfigDoc.Fields[6].Type = "string"
OpenStackConfigDoc.Fields[5].Description = "description: |\nRegionName is the name of the region to use inside the cluster.\n"
OpenStackConfigDoc.Fields[5].Comments[encoder.LineComment] = "description: |"
OpenStackConfigDoc.Fields[6].Name = "deployYawolLoadBalancer"
OpenStackConfigDoc.Fields[6].Type = "bool"
OpenStackConfigDoc.Fields[6].Note = ""
OpenStackConfigDoc.Fields[6].Description = "ProjectName is the name of the project where a user resides."
OpenStackConfigDoc.Fields[6].Comments[encoder.LineComment] = "ProjectName is the name of the project where a user resides."
OpenStackConfigDoc.Fields[7].Name = "userDomainName"
OpenStackConfigDoc.Fields[6].Description = "Deploy Yawol loadbalancer. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[6].Comments[encoder.LineComment] = "Deploy Yawol loadbalancer. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[7].Name = "yawolImageID"
OpenStackConfigDoc.Fields[7].Type = "string"
OpenStackConfigDoc.Fields[7].Note = ""
OpenStackConfigDoc.Fields[7].Description = "UserDomainName is the name of the domain where a user resides."
OpenStackConfigDoc.Fields[7].Comments[encoder.LineComment] = "UserDomainName is the name of the domain where a user resides."
OpenStackConfigDoc.Fields[8].Name = "projectDomainName"
OpenStackConfigDoc.Fields[7].Description = "OpenStack OS image used by the yawollet. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[7].Comments[encoder.LineComment] = "OpenStack OS image used by the yawollet. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[8].Name = "yawolFlavorID"
OpenStackConfigDoc.Fields[8].Type = "string"
OpenStackConfigDoc.Fields[8].Note = ""
OpenStackConfigDoc.Fields[8].Description = "ProjectDomainName is the name of the domain where a project resides."
OpenStackConfigDoc.Fields[8].Comments[encoder.LineComment] = "ProjectDomainName is the name of the domain where a project resides."
OpenStackConfigDoc.Fields[9].Name = "regionName"
OpenStackConfigDoc.Fields[9].Type = "string"
OpenStackConfigDoc.Fields[8].Description = "OpenStack flavor id used for yawollets. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[8].Comments[encoder.LineComment] = "OpenStack flavor id used for yawollets. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[9].Name = "deployCSIDriver"
OpenStackConfigDoc.Fields[9].Type = "bool"
OpenStackConfigDoc.Fields[9].Note = ""
OpenStackConfigDoc.Fields[9].Description = "description: |\nRegionName is the name of the region to use inside the cluster.\n"
OpenStackConfigDoc.Fields[9].Comments[encoder.LineComment] = "description: |"
OpenStackConfigDoc.Fields[10].Name = "username"
OpenStackConfigDoc.Fields[10].Type = "string"
OpenStackConfigDoc.Fields[10].Note = ""
OpenStackConfigDoc.Fields[10].Description = "Username to use inside the cluster."
OpenStackConfigDoc.Fields[10].Comments[encoder.LineComment] = "Username to use inside the cluster."
OpenStackConfigDoc.Fields[11].Name = "password"
OpenStackConfigDoc.Fields[11].Type = "string"
OpenStackConfigDoc.Fields[11].Note = ""
OpenStackConfigDoc.Fields[11].Description = "Password to use inside the cluster. You can instead use the environment variable \"CONSTELL_OS_PASSWORD\"."
OpenStackConfigDoc.Fields[11].Comments[encoder.LineComment] = "Password to use inside the cluster. You can instead use the environment variable \"CONSTELL_OS_PASSWORD\"."
OpenStackConfigDoc.Fields[12].Name = "deployYawolLoadBalancer"
OpenStackConfigDoc.Fields[12].Type = "bool"
OpenStackConfigDoc.Fields[12].Note = ""
OpenStackConfigDoc.Fields[12].Description = "Deploy Yawol loadbalancer. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[12].Comments[encoder.LineComment] = "Deploy Yawol loadbalancer. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[13].Name = "yawolImageID"
OpenStackConfigDoc.Fields[13].Type = "string"
OpenStackConfigDoc.Fields[13].Note = ""
OpenStackConfigDoc.Fields[13].Description = "OpenStack OS image used by the yawollet. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[13].Comments[encoder.LineComment] = "OpenStack OS image used by the yawollet. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[14].Name = "yawolFlavorID"
OpenStackConfigDoc.Fields[14].Type = "string"
OpenStackConfigDoc.Fields[14].Note = ""
OpenStackConfigDoc.Fields[14].Description = "OpenStack flavor id used for yawollets. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[14].Comments[encoder.LineComment] = "OpenStack flavor id used for yawollets. For details see: https://github.com/stackitcloud/yawol"
OpenStackConfigDoc.Fields[15].Name = "deployCSIDriver"
OpenStackConfigDoc.Fields[15].Type = "bool"
OpenStackConfigDoc.Fields[15].Note = ""
OpenStackConfigDoc.Fields[15].Description = "Deploy Cinder CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
OpenStackConfigDoc.Fields[15].Comments[encoder.LineComment] = "Deploy Cinder CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
OpenStackConfigDoc.Fields[9].Description = "Deploy Cinder CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
OpenStackConfigDoc.Fields[9].Comments[encoder.LineComment] = "Deploy Cinder CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
QEMUConfigDoc.Type = "QEMUConfig"
QEMUConfigDoc.Comments[encoder.LineComment] = "QEMUConfig holds config information for QEMU based Constellation deployments."

View file

@ -328,7 +328,7 @@ func TestFromFile(t *testing.T) {
}
func TestValidate(t *testing.T) {
const defaultErrCount = 38 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
const defaultErrCount = 32 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
const azErrCount = 7
const awsErrCount = 8
const gcpErrCount = 8

View file

@ -381,14 +381,7 @@ func V3ToV4(path string, fileHandler file.Handler) error {
Cloud: cfgV3.Provider.OpenStack.Cloud,
AvailabilityZone: cfgV3.Provider.OpenStack.AvailabilityZone,
FloatingIPPoolID: cfgV3.Provider.OpenStack.FloatingIPPoolID,
AuthURL: cfgV3.Provider.OpenStack.AuthURL,
ProjectID: cfgV3.Provider.OpenStack.ProjectID,
ProjectName: cfgV3.Provider.OpenStack.ProjectName,
UserDomainName: cfgV3.Provider.OpenStack.UserDomainName,
ProjectDomainName: cfgV3.Provider.OpenStack.ProjectDomainName,
RegionName: cfgV3.Provider.OpenStack.RegionName,
Username: cfgV3.Provider.OpenStack.Username,
Password: cfgV3.Provider.OpenStack.Password,
DeployYawolLoadBalancer: cfgV3.Provider.OpenStack.DeployYawolLoadBalancer,
YawolImageID: cfgV3.Provider.OpenStack.YawolImageID,
YawolFlavorID: cfgV3.Provider.OpenStack.YawolFlavorID,

View file

@ -97,7 +97,7 @@ type Options struct {
func (h Client) PrepareApply(
flags Options, stateFile *state.State, serviceAccURI string, masterSecret uri.MasterSecret,
) (Applier, bool, error) {
releases, err := h.loadReleases(flags.CSP, flags.AttestationVariant, flags.K8sVersion, masterSecret, stateFile, flags, serviceAccURI)
releases, err := h.loadReleases(masterSecret, stateFile, flags, serviceAccURI)
if err != nil {
return nil, false, fmt.Errorf("loading Helm releases: %w", err)
}
@ -110,10 +110,9 @@ func (h Client) PrepareApply(
}
func (h Client) loadReleases(
csp cloudprovider.Provider, attestationVariant variant.Variant, k8sVersion versions.ValidK8sVersion, secret uri.MasterSecret,
stateFile *state.State, flags Options, serviceAccURI string,
secret uri.MasterSecret, stateFile *state.State, flags Options, serviceAccURI string,
) ([]release, error) {
helmLoader := newLoader(csp, attestationVariant, k8sVersion, stateFile, h.cliVersion)
helmLoader := newLoader(flags.CSP, flags.AttestationVariant, flags.K8sVersion, stateFile, h.cliVersion)
h.log.Debug("Created new Helm loader")
return helmLoader.loadReleases(flags.Conformance, flags.DeployCSIDriver, flags.HelmWaitMode, secret, serviceAccURI, flags.OpenStackValues)
}

View file

@ -176,18 +176,23 @@ func (i *chartLoader) loadReleases(conformanceMode, deployCSIDriver bool, helmWa
}
releases = append(releases, awsRelease)
}
if i.csp == cloudprovider.OpenStack && openStackValues != nil && openStackValues.DeployYawolLoadBalancer {
yawolRelease, err := i.loadRelease(yawolLBControllerInfo, WaitModeNone)
if err != nil {
return nil, fmt.Errorf("loading yawol chart: %w", err)
if i.csp == cloudprovider.OpenStack {
if openStackValues == nil {
return nil, errors.New("provider is OpenStack but OpenStack config is missing")
}
if openStackValues.DeployYawolLoadBalancer {
yawolRelease, err := i.loadRelease(yawolLBControllerInfo, WaitModeNone)
if err != nil {
return nil, fmt.Errorf("loading yawol chart: %w", err)
}
yawolVals, err := extraYawolValues(serviceAccURI, i.stateFile.Infrastructure, openStackValues)
if err != nil {
return nil, fmt.Errorf("extending yawol chart values: %w", err)
yawolVals, err := extraYawolValues(serviceAccURI, i.stateFile.Infrastructure, openStackValues)
if err != nil {
return nil, fmt.Errorf("extending yawol chart values: %w", err)
}
yawolRelease.values = mergeMaps(yawolRelease.values, yawolVals)
releases = append(releases, yawolRelease)
}
yawolRelease.values = mergeMaps(yawolRelease.values, yawolVals)
releases = append(releases, yawolRelease)
}
return releases, nil