2022-12-07 05:48:54 -05:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
|
|
|
package cloudcmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-02-24 05:36:41 -05:00
|
|
|
"encoding/base64"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2022-12-07 05:48:54 -05:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"path"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
|
|
|
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
2023-02-24 05:36:41 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
2022-12-07 05:48:54 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
|
|
|
)
|
|
|
|
|
2023-02-24 05:36:41 -05:00
|
|
|
// IAMDestroyer destroys an IAM configuration.
|
|
|
|
type IAMDestroyer struct {
|
|
|
|
client terraformClient
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewIAMDestroyer creates a new IAM Destroyer.
|
|
|
|
func NewIAMDestroyer(ctx context.Context) (*IAMDestroyer, error) {
|
|
|
|
cl, err := terraform.New(ctx, constants.TerraformIAMWorkingDir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &IAMDestroyer{client: cl}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTfstateServiceAccountKey returns the sa_key output from the terraform state.
|
|
|
|
func (d *IAMDestroyer) GetTfstateServiceAccountKey(ctx context.Context) (gcpshared.ServiceAccountKey, error) {
|
|
|
|
tfState, err := d.client.Show(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return gcpshared.ServiceAccountKey{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if tfState.Values == nil {
|
|
|
|
return gcpshared.ServiceAccountKey{}, errors.New("no Values field in terraform state")
|
|
|
|
}
|
|
|
|
|
|
|
|
saKeyJSON := tfState.Values.Outputs["sa_key"]
|
|
|
|
if saKeyJSON == nil {
|
|
|
|
return gcpshared.ServiceAccountKey{}, errors.New("no sa_key in outputs of the terraform state")
|
|
|
|
}
|
|
|
|
|
|
|
|
saKeyString, ok := saKeyJSON.Value.(string)
|
|
|
|
if !ok {
|
|
|
|
return gcpshared.ServiceAccountKey{}, errors.New("sa_key field in terraform state is not a string")
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// DestroyIAMConfiguration destroys the previously created IAM configuration and deletes the local IAM terraform files.
|
2023-04-14 08:15:07 -04:00
|
|
|
func (d *IAMDestroyer) DestroyIAMConfiguration(ctx context.Context, logLevel terraform.LogLevel) error {
|
|
|
|
if err := d.client.Destroy(ctx, logLevel); err != nil {
|
2023-02-24 05:36:41 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return d.client.CleanUpWorkspace()
|
|
|
|
}
|
|
|
|
|
2022-12-07 05:48:54 -05:00
|
|
|
// IAMCreator creates the IAM configuration on the cloud provider.
|
|
|
|
type IAMCreator struct {
|
|
|
|
out io.Writer
|
|
|
|
newTerraformClient func(ctx context.Context) (terraformClient, error)
|
|
|
|
}
|
|
|
|
|
2023-04-14 08:15:07 -04:00
|
|
|
// IAMConfigOptions holds the necessary values for IAM configuration.
|
|
|
|
type IAMConfigOptions struct {
|
|
|
|
GCP GCPIAMConfig
|
|
|
|
Azure AzureIAMConfig
|
|
|
|
AWS AWSIAMConfig
|
|
|
|
TFLogLevel terraform.LogLevel
|
2022-12-07 05:48:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
Region 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: func(ctx context.Context) (terraformClient, error) {
|
|
|
|
return terraform.New(ctx, constants.TerraformIAMWorkingDir)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create prepares and hands over the corresponding providers IAM creator.
|
2023-04-14 08:15:07 -04:00
|
|
|
func (c *IAMCreator) Create(ctx context.Context, provider cloudprovider.Provider, opts *IAMConfigOptions) (iamid.File, error) {
|
2022-12-07 05:48:54 -05:00
|
|
|
switch provider {
|
|
|
|
case cloudprovider.GCP:
|
|
|
|
cl, err := c.newTerraformClient(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return iamid.File{}, err
|
|
|
|
}
|
|
|
|
defer cl.RemoveInstaller()
|
2023-04-14 08:15:07 -04:00
|
|
|
return c.createGCP(ctx, cl, opts)
|
2022-12-07 05:48:54 -05:00
|
|
|
case cloudprovider.Azure:
|
|
|
|
cl, err := c.newTerraformClient(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return iamid.File{}, err
|
|
|
|
}
|
|
|
|
defer cl.RemoveInstaller()
|
2023-04-14 08:15:07 -04:00
|
|
|
return c.createAzure(ctx, cl, opts)
|
2022-12-07 05:48:54 -05:00
|
|
|
case cloudprovider.AWS:
|
|
|
|
cl, err := c.newTerraformClient(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return iamid.File{}, err
|
|
|
|
}
|
|
|
|
defer cl.RemoveInstaller()
|
2023-04-14 08:15:07 -04:00
|
|
|
return c.createAWS(ctx, cl, opts)
|
2022-12-07 05:48:54 -05:00
|
|
|
default:
|
|
|
|
return iamid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// createGCP creates the IAM configuration on GCP.
|
2023-04-14 08:15:07 -04:00
|
|
|
func (c *IAMCreator) createGCP(ctx context.Context, cl terraformClient, opts *IAMConfigOptions) (retFile iamid.File, retErr error) {
|
|
|
|
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
|
2022-12-07 05:48:54 -05:00
|
|
|
|
|
|
|
vars := terraform.GCPIAMVariables{
|
2023-04-14 08:15:07 -04:00
|
|
|
ServiceAccountID: opts.GCP.ServiceAccountID,
|
|
|
|
Project: opts.GCP.ProjectID,
|
|
|
|
Region: opts.GCP.Region,
|
|
|
|
Zone: opts.GCP.Zone,
|
2022-12-07 05:48:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil {
|
|
|
|
return iamid.File{}, err
|
|
|
|
}
|
|
|
|
|
2023-07-24 04:30:53 -04:00
|
|
|
iamOutput, err := cl.ApplyIAMConfig(ctx, cloudprovider.GCP, opts.TFLogLevel)
|
2022-12-07 05:48:54 -05:00
|
|
|
if err != nil {
|
|
|
|
return iamid.File{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return iamid.File{
|
|
|
|
CloudProvider: cloudprovider.GCP,
|
|
|
|
GCPOutput: iamid.GCPFile{
|
|
|
|
ServiceAccountKey: iamOutput.GCP.SaKey,
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// createAzure creates the IAM configuration on Azure.
|
2023-04-14 08:15:07 -04:00
|
|
|
func (c *IAMCreator) createAzure(ctx context.Context, cl terraformClient, opts *IAMConfigOptions) (retFile iamid.File, retErr error) {
|
|
|
|
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
|
2022-12-07 05:48:54 -05:00
|
|
|
|
|
|
|
vars := terraform.AzureIAMVariables{
|
2023-04-14 08:15:07 -04:00
|
|
|
Region: opts.Azure.Region,
|
|
|
|
ResourceGroup: opts.Azure.ResourceGroup,
|
|
|
|
ServicePrincipal: opts.Azure.ServicePrincipal,
|
2022-12-07 05:48:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil {
|
|
|
|
return iamid.File{}, err
|
|
|
|
}
|
|
|
|
|
2023-07-24 04:30:53 -04:00
|
|
|
iamOutput, err := cl.ApplyIAMConfig(ctx, cloudprovider.Azure, opts.TFLogLevel)
|
2022-12-07 05:48:54 -05:00
|
|
|
if err != nil {
|
|
|
|
return iamid.File{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return iamid.File{
|
|
|
|
CloudProvider: cloudprovider.Azure,
|
|
|
|
AzureOutput: iamid.AzureFile{
|
2023-05-26 05:45:03 -04:00
|
|
|
SubscriptionID: iamOutput.Azure.SubscriptionID,
|
|
|
|
TenantID: iamOutput.Azure.TenantID,
|
|
|
|
UAMIID: iamOutput.Azure.UAMIID,
|
2022-12-07 05:48:54 -05:00
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// createAWS creates the IAM configuration on AWS.
|
2023-04-14 08:15:07 -04:00
|
|
|
func (c *IAMCreator) createAWS(ctx context.Context, cl terraformClient, opts *IAMConfigOptions) (retFile iamid.File, retErr error) {
|
|
|
|
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
|
2022-12-07 05:48:54 -05:00
|
|
|
|
|
|
|
vars := terraform.AWSIAMVariables{
|
2023-04-14 08:15:07 -04:00
|
|
|
Region: opts.AWS.Region,
|
|
|
|
Prefix: opts.AWS.Prefix,
|
2022-12-07 05:48:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil {
|
|
|
|
return iamid.File{}, err
|
|
|
|
}
|
|
|
|
|
2023-07-24 04:30:53 -04:00
|
|
|
iamOutput, err := cl.ApplyIAMConfig(ctx, cloudprovider.AWS, opts.TFLogLevel)
|
2022-12-07 05:48:54 -05:00
|
|
|
if err != nil {
|
|
|
|
return iamid.File{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return iamid.File{
|
|
|
|
CloudProvider: cloudprovider.AWS,
|
|
|
|
AWSOutput: iamid.AWSFile{
|
|
|
|
WorkerNodeInstanceProfile: iamOutput.AWS.WorkerNodeInstanceProfile,
|
|
|
|
ControlPlaneInstanceProfile: iamOutput.AWS.ControlPlaneInstanceProfile,
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|