constellation/cli/internal/cloudcmd/iam.go
Adrian Stobbe 9667dfff58
terraform: align infrastructure module attributes (#2703)
* all vars have snail_case

* make iam schema consistent

* infrastructure schema

* terraform: update AWS infrastructure module

* fix ci

* terraform: update AWS infrastructure module

* terraform: update AWS IAM module

* terraform: update Azure Infrastructure module inputs

Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>

* terraform: update Azure IAM module

Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>

* terraform: update GCP infrastructure module

Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>

* terraform: update GCP IAM module

Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>

* terraform: update OpenStack Infrastructure module

Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>

* terraform: update QEMU Infrastructure module

Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>

* terraform-module: fix input name

Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>

* terraform: tidy

* cli: ignore whitespace in Terraform variable tests

* terraform-module: fix AWS output names

* terraform-module: fix output references

Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>

* terraform: rename `api_server_cert_sans`

* Update terraform/infrastructure/aws/modules/public_private_subnet/variables.tf

Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>

* fix self-managed

* terraform: revert AWS modules output file renaming

* terraform: remove duplicate varable declaration

* terraform: rename Azure location field

* ci: adjust output name in self-managed e2e test

* e2e: continuously print output in upgrade test

* e2e: write to output variables

* cli: migrate IAM variable names

* cli: make `location` field optional

---------

Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>
Co-authored-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>
Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>
2023-12-15 10:36:58 +01:00

254 lines
7.8 KiB
Go

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cloudcmd
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"path"
"strings"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/constants"
)
// IAMDestroyer destroys an IAM configuration.
type IAMDestroyer struct {
newTerraformClient newTFIAMClientFunc
}
// NewIAMDestroyer creates a new IAM Destroyer.
func NewIAMDestroyer() *IAMDestroyer {
return &IAMDestroyer{newTerraformClient: newTerraformIAMClient}
}
// GetTfStateServiceAccountKey returns the service_account_key output from the terraform state.
func (d *IAMDestroyer) GetTfStateServiceAccountKey(ctx context.Context, tfWorkspace string) (gcpshared.ServiceAccountKey, error) {
client, err := d.newTerraformClient(ctx, tfWorkspace)
if err != nil {
return gcpshared.ServiceAccountKey{}, err
}
tfState, err := client.ShowIAM(ctx, cloudprovider.GCP)
if err != nil {
return gcpshared.ServiceAccountKey{}, fmt.Errorf("getting terraform state: %w", err)
}
if saKeyString := tfState.GCP.SaKey; saKeyString != "" {
saKey, err := base64.StdEncoding.DecodeString(saKeyString)
if err != nil {
return gcpshared.ServiceAccountKey{}, err
}
var tfSaKey gcpshared.ServiceAccountKey
if err := json.Unmarshal(saKey, &tfSaKey); err != nil {
return gcpshared.ServiceAccountKey{}, err
}
return tfSaKey, nil
}
return gcpshared.ServiceAccountKey{}, errors.New("no saKey in terraform state")
}
// DestroyIAMConfiguration destroys the previously created IAM configuration and deletes the local IAM terraform files.
func (d *IAMDestroyer) DestroyIAMConfiguration(ctx context.Context, tfWorkspace string, logLevel terraform.LogLevel) error {
client, err := d.newTerraformClient(ctx, tfWorkspace)
if err != nil {
return err
}
if err := client.Destroy(ctx, logLevel); err != nil {
return err
}
return client.CleanUpWorkspace()
}
// IAMCreator creates the IAM configuration on the cloud provider.
type IAMCreator struct {
out io.Writer
newTerraformClient newTFIAMClientFunc
}
// IAMConfigOptions holds the necessary values for IAM configuration.
type IAMConfigOptions struct {
GCP GCPIAMConfig
Azure AzureIAMConfig
AWS AWSIAMConfig
TFLogLevel terraform.LogLevel
TFWorkspace string
}
// GCPIAMConfig holds the necessary values for GCP IAM configuration.
type GCPIAMConfig struct {
Region string
Zone string
ProjectID string
ServiceAccountID string
}
// AzureIAMConfig holds the necessary values for Azure IAM configuration.
type AzureIAMConfig struct {
Location string
ServicePrincipal string
ResourceGroup string
}
// AWSIAMConfig holds the necessary values for AWS IAM configuration.
type AWSIAMConfig struct {
Region string
Prefix string
}
// NewIAMCreator creates a new IAM creator.
func NewIAMCreator(out io.Writer) *IAMCreator {
return &IAMCreator{
out: out,
newTerraformClient: newTerraformIAMClient,
}
}
// Create prepares and hands over the corresponding providers IAM creator.
func (c *IAMCreator) Create(ctx context.Context, provider cloudprovider.Provider, opts *IAMConfigOptions) (IAMOutput, error) {
cl, err := c.newTerraformClient(ctx, opts.TFWorkspace)
if err != nil {
return IAMOutput{}, err
}
defer cl.RemoveInstaller()
switch provider {
case cloudprovider.GCP:
return c.createGCP(ctx, cl, opts)
case cloudprovider.Azure:
return c.createAzure(ctx, cl, opts)
case cloudprovider.AWS:
return c.createAWS(ctx, cl, opts)
default:
return IAMOutput{}, fmt.Errorf("unsupported cloud provider: %s", provider)
}
}
// createGCP creates the IAM configuration on GCP.
func (c *IAMCreator) createGCP(ctx context.Context, cl tfIAMClient, opts *IAMConfigOptions) (iam IAMOutput, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
vars := terraform.GCPIAMVariables{
ServiceAccountID: opts.GCP.ServiceAccountID,
Project: opts.GCP.ProjectID,
Region: opts.GCP.Region,
Zone: opts.GCP.Zone,
}
if err := cl.PrepareWorkspace(path.Join(constants.TerraformEmbeddedDir, "iam", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil {
return IAMOutput{}, err
}
iamOutput, err := cl.ApplyIAM(ctx, cloudprovider.GCP, opts.TFLogLevel)
if err != nil {
return IAMOutput{}, err
}
return IAMOutput{
CloudProvider: cloudprovider.GCP,
GCPOutput: GCPIAMOutput{
ServiceAccountKey: iamOutput.GCP.SaKey,
},
}, nil
}
// createAzure creates the IAM configuration on Azure.
func (c *IAMCreator) createAzure(ctx context.Context, cl tfIAMClient, opts *IAMConfigOptions) (iam IAMOutput, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
vars := terraform.AzureIAMVariables{
Location: opts.Azure.Location,
ResourceGroup: opts.Azure.ResourceGroup,
ServicePrincipal: opts.Azure.ServicePrincipal,
}
if err := cl.PrepareWorkspace(path.Join(constants.TerraformEmbeddedDir, "iam", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil {
return IAMOutput{}, err
}
iamOutput, err := cl.ApplyIAM(ctx, cloudprovider.Azure, opts.TFLogLevel)
if err != nil {
return IAMOutput{}, err
}
return IAMOutput{
CloudProvider: cloudprovider.Azure,
AzureOutput: AzureIAMOutput{
SubscriptionID: iamOutput.Azure.SubscriptionID,
TenantID: iamOutput.Azure.TenantID,
UAMIID: iamOutput.Azure.UAMIID,
},
}, nil
}
// createAWS creates the IAM configuration on AWS.
func (c *IAMCreator) createAWS(ctx context.Context, cl tfIAMClient, opts *IAMConfigOptions) (iam IAMOutput, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
vars := terraform.AWSIAMVariables{
Region: opts.AWS.Region,
Prefix: opts.AWS.Prefix,
}
if err := cl.PrepareWorkspace(path.Join(constants.TerraformEmbeddedDir, "iam", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil {
return IAMOutput{}, err
}
iamOutput, err := cl.ApplyIAM(ctx, cloudprovider.AWS, opts.TFLogLevel)
if err != nil {
return IAMOutput{}, err
}
return IAMOutput{
CloudProvider: cloudprovider.AWS,
AWSOutput: AWSIAMOutput{
WorkerNodeInstanceProfile: iamOutput.AWS.WorkerNodeInstanceProfile,
ControlPlaneInstanceProfile: iamOutput.AWS.ControlPlaneInstanceProfile,
},
}, nil
}
// IAMOutput is the output of creating a new IAM profile.
type IAMOutput struct {
// CloudProvider is the cloud provider of the cluster.
CloudProvider cloudprovider.Provider `json:"cloudprovider,omitempty"`
GCPOutput GCPIAMOutput `json:"gcpOutput,omitempty"`
AzureOutput AzureIAMOutput `json:"azureOutput,omitempty"`
AWSOutput AWSIAMOutput `json:"awsOutput,omitempty"`
}
// GCPIAMOutput contains the output information of a GCP IAM configuration.
type GCPIAMOutput struct {
ServiceAccountKey string `json:"serviceAccountID,omitempty"`
}
// AzureIAMOutput contains the output information of a Microsoft Azure IAM configuration.
type AzureIAMOutput struct {
SubscriptionID string `json:"subscriptionID,omitempty"`
TenantID string `json:"tenantID,omitempty"`
UAMIID string `json:"uamiID,omitempty"`
}
// AWSIAMOutput contains the output information of an AWS IAM configuration.
type AWSIAMOutput struct {
ControlPlaneInstanceProfile string `json:"controlPlaneInstanceProfile,omitempty"`
WorkerNodeInstanceProfile string `json:"workerNodeInstanceProfile,omitempty"`
}
type newTFIAMClientFunc func(ctx context.Context, workspace string) (tfIAMClient, error)
func newTerraformIAMClient(ctx context.Context, workspace string) (tfIAMClient, error) {
return terraform.New(ctx, workspace)
}