mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
04c4cff9f6
* Add .DS_Store to .gitignore * Add AWS to config / supported instance types * Move AWS terraform skeleton to cli/internal/terraform * Move currently unused IAM to hack/terraform/aws * Print supported AWS instance types when AWS dev flag is set * Block everything aTLS related (e.g. init, verify) until AWS attestation is available * Create/Terminate AWS dev cluster when dev flag is set * Restrict Nitro instances to NitroTPM supported specifically * Pin zone for subnets This is not great for HA, but for now we need to avoid the two subnets ending up in different zones, causing the load balancer to not be able to connect to the targets. Should be replaced later with a better implementation that just uses multiple subnets within the same region dynamically based on # of nodes or similar. * Add AWS/GCP to Terraform TestLoader unit test * Add uid tag and create log group Co-authored-by: Daniel Weiße <dw@edgeless.systems> Co-authored-by: Malte Poll <mp@edgeless.systems>
271 lines
9.0 KiB
Go
271 lines
9.0 KiB
Go
/*
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
package cloudcmd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
|
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
|
|
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
|
)
|
|
|
|
// Creator creates cloud resources.
|
|
type Creator struct {
|
|
out io.Writer
|
|
newTerraformClient func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error)
|
|
newLibvirtRunner func() libvirtRunner
|
|
}
|
|
|
|
// NewCreator creates a new creator.
|
|
func NewCreator(out io.Writer) *Creator {
|
|
return &Creator{
|
|
out: out,
|
|
newTerraformClient: func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) {
|
|
return terraform.New(ctx, provider)
|
|
},
|
|
newLibvirtRunner: func() libvirtRunner {
|
|
return libvirt.New()
|
|
},
|
|
}
|
|
}
|
|
|
|
// Create creates the handed amount of instances and all the needed resources.
|
|
func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, config *config.Config, name, insType string, controlPlaneCount, workerCount int,
|
|
) (clusterid.File, error) {
|
|
switch provider {
|
|
case cloudprovider.AWS:
|
|
// TODO: Remove this once AWS is supported.
|
|
if os.Getenv("CONSTELLATION_AWS_DEV") != "1" {
|
|
return clusterid.File{}, fmt.Errorf("AWS isn't supported yet")
|
|
}
|
|
cl, err := c.newTerraformClient(ctx, provider)
|
|
if err != nil {
|
|
return clusterid.File{}, err
|
|
}
|
|
defer cl.RemoveInstaller()
|
|
return c.createAWS(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
|
|
case cloudprovider.GCP:
|
|
cl, err := c.newTerraformClient(ctx, provider)
|
|
if err != nil {
|
|
return clusterid.File{}, err
|
|
}
|
|
defer cl.RemoveInstaller()
|
|
return c.createGCP(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
|
|
case cloudprovider.Azure:
|
|
cl, err := c.newTerraformClient(ctx, provider)
|
|
if err != nil {
|
|
return clusterid.File{}, err
|
|
}
|
|
defer cl.RemoveInstaller()
|
|
return c.createAzure(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
|
|
case cloudprovider.QEMU:
|
|
if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" {
|
|
return clusterid.File{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
}
|
|
cl, err := c.newTerraformClient(ctx, provider)
|
|
if err != nil {
|
|
return clusterid.File{}, err
|
|
}
|
|
defer cl.RemoveInstaller()
|
|
lv := c.newLibvirtRunner()
|
|
return c.createQEMU(ctx, cl, lv, name, config, controlPlaneCount, workerCount)
|
|
default:
|
|
return clusterid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider)
|
|
}
|
|
}
|
|
|
|
func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *config.Config,
|
|
name, insType string, controlPlaneCount, workerCount int,
|
|
) (idFile clusterid.File, retErr error) {
|
|
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
|
|
|
|
vars := &terraform.AWSVariables{
|
|
CommonVariables: terraform.CommonVariables{
|
|
Name: name,
|
|
CountControlPlanes: controlPlaneCount,
|
|
CountWorkers: workerCount,
|
|
StateDiskSizeGB: config.StateDiskSizeGB,
|
|
},
|
|
Region: config.Provider.AWS.Region,
|
|
Zone: config.Provider.AWS.Zone,
|
|
InstanceType: insType,
|
|
AMIImageID: config.Provider.AWS.Image,
|
|
IAMProfileControlPlane: config.Provider.AWS.IAMProfileControlPlane,
|
|
IAMProfileWorkerNodes: config.Provider.AWS.IAMProfileWorkerNodes,
|
|
Debug: config.IsDebugCluster(),
|
|
}
|
|
|
|
ip, err := cl.CreateCluster(ctx, name, vars)
|
|
if err != nil {
|
|
return clusterid.File{}, err
|
|
}
|
|
|
|
return clusterid.File{
|
|
CloudProvider: cloudprovider.AWS,
|
|
IP: ip,
|
|
}, nil
|
|
}
|
|
|
|
func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *config.Config,
|
|
name, insType string, controlPlaneCount, workerCount int,
|
|
) (idFile clusterid.File, retErr error) {
|
|
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
|
|
|
|
vars := terraform.GCPVariables{
|
|
CommonVariables: terraform.CommonVariables{
|
|
Name: name,
|
|
CountControlPlanes: controlPlaneCount,
|
|
CountWorkers: workerCount,
|
|
StateDiskSizeGB: config.StateDiskSizeGB,
|
|
},
|
|
Project: config.Provider.GCP.Project,
|
|
Region: config.Provider.GCP.Region,
|
|
Zone: config.Provider.GCP.Zone,
|
|
CredentialsFile: config.Provider.GCP.ServiceAccountKeyPath,
|
|
InstanceType: insType,
|
|
StateDiskType: config.Provider.GCP.StateDiskType,
|
|
ImageID: config.Provider.GCP.Image,
|
|
Debug: config.IsDebugCluster(),
|
|
}
|
|
|
|
ip, err := cl.CreateCluster(ctx, name, &vars)
|
|
if err != nil {
|
|
return clusterid.File{}, err
|
|
}
|
|
|
|
return clusterid.File{
|
|
CloudProvider: cloudprovider.GCP,
|
|
IP: ip,
|
|
}, nil
|
|
}
|
|
|
|
func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *config.Config,
|
|
name, insType string, controlPlaneCount, workerCount int,
|
|
) (idFile clusterid.File, retErr error) {
|
|
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
|
|
|
|
vars := terraform.AzureVariables{
|
|
CommonVariables: terraform.CommonVariables{
|
|
Name: name,
|
|
CountControlPlanes: controlPlaneCount,
|
|
CountWorkers: workerCount,
|
|
StateDiskSizeGB: config.StateDiskSizeGB,
|
|
},
|
|
Location: config.Provider.Azure.Location,
|
|
ResourceGroup: config.Provider.Azure.ResourceGroup,
|
|
UserAssignedIdentity: config.Provider.Azure.UserAssignedIdentity,
|
|
InstanceType: insType,
|
|
StateDiskType: config.Provider.Azure.StateDiskType,
|
|
ImageID: config.Provider.Azure.Image,
|
|
ConfidentialVM: *config.Provider.Azure.ConfidentialVM,
|
|
SecureBoot: *config.Provider.Azure.SecureBoot,
|
|
Debug: config.IsDebugCluster(),
|
|
}
|
|
|
|
vars = normalizeAzureURIs(vars)
|
|
|
|
ip, err := cl.CreateCluster(ctx, name, &vars)
|
|
if err != nil {
|
|
return clusterid.File{}, err
|
|
}
|
|
|
|
return clusterid.File{
|
|
CloudProvider: cloudprovider.Azure,
|
|
IP: ip,
|
|
}, nil
|
|
}
|
|
|
|
func normalizeAzureURIs(vars terraform.AzureVariables) terraform.AzureVariables {
|
|
vars.UserAssignedIdentity = strings.ReplaceAll(vars.UserAssignedIdentity, "resourcegroup", "resourceGroup")
|
|
|
|
vars.ImageID = strings.ReplaceAll(vars.ImageID, "CommunityGalleries", "communityGalleries")
|
|
vars.ImageID = strings.ReplaceAll(vars.ImageID, "Images", "images")
|
|
vars.ImageID = strings.ReplaceAll(vars.ImageID, "Versions", "versions")
|
|
|
|
return vars
|
|
}
|
|
|
|
func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirtRunner, name string, config *config.Config,
|
|
controlPlaneCount, workerCount int,
|
|
) (idFile clusterid.File, retErr error) {
|
|
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerQEMU{client: cl, libvirt: lv})
|
|
|
|
libvirtURI := config.Provider.QEMU.LibvirtURI
|
|
libvirtSocketPath := "."
|
|
|
|
switch {
|
|
// if no libvirt URI is specified, start a libvirt container
|
|
case libvirtURI == "":
|
|
if err := lv.Start(ctx, name, config.Provider.QEMU.LibvirtContainerImage); err != nil {
|
|
return clusterid.File{}, err
|
|
}
|
|
libvirtURI = libvirt.LibvirtTCPConnectURI
|
|
|
|
// socket for system URI should be in /var/run/libvirt/libvirt-sock
|
|
case libvirtURI == "qemu:///system":
|
|
libvirtSocketPath = "/var/run/libvirt/libvirt-sock"
|
|
|
|
// socket for session URI should be in /run/user/<uid>/libvirt/libvirt-sock
|
|
case libvirtURI == "qemu:///session":
|
|
libvirtSocketPath = fmt.Sprintf("/run/user/%d/libvirt/libvirt-sock", os.Getuid())
|
|
|
|
// if a unix socket is specified we need to parse the URI to get the socket path
|
|
case strings.HasPrefix(libvirtURI, "qemu+unix://"):
|
|
unixURI, err := url.Parse(strings.TrimPrefix(libvirtURI, "qemu+unix://"))
|
|
if err != nil {
|
|
return clusterid.File{}, err
|
|
}
|
|
libvirtSocketPath = unixURI.Query().Get("socket")
|
|
if libvirtSocketPath == "" {
|
|
return clusterid.File{}, fmt.Errorf("socket path not specified in qemu+unix URI: %s", libvirtURI)
|
|
}
|
|
}
|
|
|
|
metadataLibvirtURI := libvirtURI
|
|
if libvirtSocketPath != "." {
|
|
metadataLibvirtURI = "qemu:///system"
|
|
}
|
|
|
|
vars := terraform.QEMUVariables{
|
|
CommonVariables: terraform.CommonVariables{
|
|
Name: name,
|
|
CountControlPlanes: controlPlaneCount,
|
|
CountWorkers: workerCount,
|
|
StateDiskSizeGB: config.StateDiskSizeGB,
|
|
},
|
|
LibvirtURI: libvirtURI,
|
|
LibvirtSocketPath: libvirtSocketPath,
|
|
ImagePath: config.Provider.QEMU.Image,
|
|
ImageFormat: config.Provider.QEMU.ImageFormat,
|
|
CPUCount: config.Provider.QEMU.VCPUs,
|
|
MemorySizeMiB: config.Provider.QEMU.Memory,
|
|
MetadataAPIImage: config.Provider.QEMU.MetadataAPIImage,
|
|
MetadataLibvirtURI: metadataLibvirtURI,
|
|
NVRAM: config.Provider.QEMU.NVRAM,
|
|
Firmware: config.Provider.QEMU.Firmware,
|
|
}
|
|
|
|
ip, err := cl.CreateCluster(ctx, name, &vars)
|
|
if err != nil {
|
|
return clusterid.File{}, err
|
|
}
|
|
|
|
return clusterid.File{
|
|
CloudProvider: cloudprovider.QEMU,
|
|
IP: ip,
|
|
}, nil
|
|
}
|