constellation/cli/internal/cloudcmd/iam.go
Adrian Stobbe a87b7894db
aws: use new LB controller to fix SecurityGroup cleanup on K8s service deletion (#2090)
* add current chart

add current helm chart

* disable service controller for aws ccm

* add new iam roles

* doc AWS internet LB + add to LB test

* pass clusterName to helm for AWS LB

* fix update-aws-lb chart to also include .helmignore

* move chart outside services

* working state

* add subnet tags for AWS subnet discovery

* fix .helmignore load rule with file in subdirectory

* upgrade iam profile

* revert new loader impl since cilium is not correctly loaded

* install chart if not already present during `upgrade apply`

* cleanup PR + fix build + add todos

cleanup PR + add todos

* shared helm pkg for cli install and bootstrapper

* add link to eks docs

* refactor iamMigrationCmd

* delete unused helm.symwallk

* move iammigrate to upgrade pkg

* fixup! delete unused helm.symwallk

* add to upgradecheck

* remove nodeSelector from go code (Otto)

* update iam docs and sort permission + remove duplicate roles

* fix bug in `upgrade check`

* better upgrade check output when svc version upgrade not possible

* pr feedback

* remove force flag in upgrade_test

* use upgrader.GetUpgradeID instead of extra type

* remove todos + fix check

* update doc lb (leo)

* remove bootstrapper helm package

* Update cli/internal/cmd/upgradecheck.go

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

* final nits

* add docs for e2e upgrade test setup

* Apply suggestions from code review

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

* Update cli/internal/helm/loader.go

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

* Update cli/internal/cmd/tfmigrationclient.go

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

* fix daniel review

* link to the iam permissions instead of manually updating them (agreed with leo)

* disable iam upgrade in upgrade apply

---------

Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>
Co-authored-by: Malte Poll
2023-07-24 10:30:53 +02:00

237 lines
7.0 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/iamid"
"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 {
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.
func (d *IAMDestroyer) DestroyIAMConfiguration(ctx context.Context, logLevel terraform.LogLevel) error {
if err := d.client.Destroy(ctx, logLevel); err != nil {
return err
}
return d.client.CleanUpWorkspace()
}
// IAMCreator creates the IAM configuration on the cloud provider.
type IAMCreator struct {
out io.Writer
newTerraformClient func(ctx context.Context) (terraformClient, error)
}
// IAMConfigOptions holds the necessary values for IAM configuration.
type IAMConfigOptions struct {
GCP GCPIAMConfig
Azure AzureIAMConfig
AWS AWSIAMConfig
TFLogLevel terraform.LogLevel
}
// 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.
func (c *IAMCreator) Create(ctx context.Context, provider cloudprovider.Provider, opts *IAMConfigOptions) (iamid.File, error) {
switch provider {
case cloudprovider.GCP:
cl, err := c.newTerraformClient(ctx)
if err != nil {
return iamid.File{}, err
}
defer cl.RemoveInstaller()
return c.createGCP(ctx, cl, opts)
case cloudprovider.Azure:
cl, err := c.newTerraformClient(ctx)
if err != nil {
return iamid.File{}, err
}
defer cl.RemoveInstaller()
return c.createAzure(ctx, cl, opts)
case cloudprovider.AWS:
cl, err := c.newTerraformClient(ctx)
if err != nil {
return iamid.File{}, err
}
defer cl.RemoveInstaller()
return c.createAWS(ctx, cl, opts)
default:
return iamid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider)
}
}
// createGCP creates the IAM configuration on GCP.
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)
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("terraform", "iam", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil {
return iamid.File{}, err
}
iamOutput, err := cl.ApplyIAMConfig(ctx, cloudprovider.GCP, opts.TFLogLevel)
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.
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)
vars := terraform.AzureIAMVariables{
Region: opts.Azure.Region,
ResourceGroup: opts.Azure.ResourceGroup,
ServicePrincipal: opts.Azure.ServicePrincipal,
}
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil {
return iamid.File{}, err
}
iamOutput, err := cl.ApplyIAMConfig(ctx, cloudprovider.Azure, opts.TFLogLevel)
if err != nil {
return iamid.File{}, err
}
return iamid.File{
CloudProvider: cloudprovider.Azure,
AzureOutput: iamid.AzureFile{
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 terraformClient, opts *IAMConfigOptions) (retFile iamid.File, 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("terraform", "iam", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil {
return iamid.File{}, err
}
iamOutput, err := cl.ApplyIAMConfig(ctx, cloudprovider.AWS, opts.TFLogLevel)
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
}