sigs.k8s.io/cluster-api-provider-aws@v1.5.5/test/e2e/shared/template.go (about)

     1  //go:build e2e
     2  // +build e2e
     3  
     4  /*
     5  Copyright 2020 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11  	http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package shared
    21  
    22  import (
    23  	"context"
    24  	"os"
    25  	"path"
    26  
    27  	"github.com/awslabs/goformation/v4/cloudformation"
    28  	cfn_iam "github.com/awslabs/goformation/v4/cloudformation/iam"
    29  	. "github.com/onsi/ginkgo"
    30  	. "github.com/onsi/gomega"
    31  	"gopkg.in/yaml.v2"
    32  
    33  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    34  	bootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/cmd/clusterawsadm/api/bootstrap/v1beta1"
    35  	cfn_bootstrap "sigs.k8s.io/cluster-api-provider-aws/cmd/clusterawsadm/cloudformation/bootstrap"
    36  	"sigs.k8s.io/cluster-api-provider-aws/cmd/clusterawsadm/credentials"
    37  	iamv1 "sigs.k8s.io/cluster-api-provider-aws/iam/api/v1beta1"
    38  	"sigs.k8s.io/cluster-api/test/framework"
    39  	"sigs.k8s.io/cluster-api/test/framework/clusterctl"
    40  )
    41  
    42  const (
    43  	// MultiTenancyJumpPolicy is the policy name for jump host to be used in multi-tenancy test.
    44  	MultiTenancyJumpPolicy = "CAPAMultiTenancyJumpPolicy"
    45  )
    46  
    47  var (
    48  	accountRef = cloudformation.Sub("arn:${AWS::Partition}:iam::${AWS::AccountId}:root")
    49  )
    50  
    51  // newBootstrapTemplate generates a clusterawsadm configuration, and prints it
    52  // and the resultant cloudformation template to the artifacts directory.
    53  func newBootstrapTemplate(e2eCtx *E2EContext) *cfn_bootstrap.Template {
    54  	By("Creating a bootstrap AWSIAMConfiguration")
    55  	t := cfn_bootstrap.NewTemplate()
    56  	t.Spec.BootstrapUser.Enable = true
    57  	t.Spec.BootstrapUser.ExtraStatements = []iamv1.StatementEntry{
    58  		{
    59  			Effect: iamv1.EffectAllow,
    60  			Resource: iamv1.Resources{
    61  				iamv1.Any,
    62  			},
    63  			Action: iamv1.Actions{
    64  				"ecr-public:*",
    65  				"sts:*",
    66  				"servicequotas:GetServiceQuota",
    67  				"servicequotas:RequestServiceQuotaIncrease",
    68  				"servicequotas:ListRequestedServiceQuotaChangeHistory",
    69  				"elasticloadbalancing:DescribeAccountLimits",
    70  				"ec2:DescribeAccountLimits",
    71  				"cloudtrail:LookupEvents",
    72  				"ssm:StartSession",
    73  				"ssm:DescribeSessions",
    74  				"ssm:GetConnectionStatus",
    75  				"ssm:DescribeInstanceProperties",
    76  				"ssm:GetDocument",
    77  				"ssm:TerminateSession",
    78  				"ssm:ResumeSession",
    79  				"ec2:DescribeSubnets",
    80  				"ec2:DescribeNetworkInterfaces",
    81  				"ec2:CreateNetworkInterface",
    82  				"ec2:DescribeAvailabilityZones",
    83  				"ec2:DeleteNetworkInterface",
    84  				"elasticfilesystem:DescribeMountTargets",
    85  				"elasticfilesystem:CreateMountTarget",
    86  				"elasticfilesystem:DeleteMountTarget",
    87  				"elasticfilesystem:DescribeFileSystems",
    88  				"elasticfilesystem:CreateFileSystem",
    89  				"elasticfilesystem:DeleteFileSystem",
    90  				"elasticfilesystem:DescribeAccessPoints",
    91  				"elasticfilesystem:CreateAccessPoint",
    92  				"elasticfilesystem:DeleteAccessPoint",
    93  			},
    94  		},
    95  		{
    96  			Effect: iamv1.EffectAllow,
    97  			Resource: iamv1.Resources{
    98  				"arn:*:iam::*:role/aws-service-role/servicequotas.amazonaws.com/AWSServiceRoleForServiceQuotas",
    99  				"arn:*:iam::*:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
   100  				"arn:*:iam::*:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor",
   101  			},
   102  			Action: iamv1.Actions{
   103  				"iam:CreateServiceLinkedRole",
   104  			},
   105  		},
   106  	}
   107  	t.Spec.SecureSecretsBackends = []infrav1.SecretBackend{
   108  		infrav1.SecretBackendSecretsManager,
   109  		infrav1.SecretBackendSSMParameterStore,
   110  	}
   111  	t.Spec.EventBridge = &bootstrapv1.EventBridgeConfig{
   112  		Enable: true,
   113  	}
   114  
   115  	region, err := credentials.ResolveRegion("")
   116  	Expect(err).NotTo(HaveOccurred())
   117  	t.Spec.Region = region
   118  	t.Spec.EKS.Disable = false
   119  	t.Spec.EKS.AllowIAMRoleCreation = false
   120  	t.Spec.EKS.DefaultControlPlaneRole.Disable = false
   121  	t.Spec.EKS.ManagedMachinePool.Disable = false
   122  	t.Spec.S3Buckets.Enable = true
   123  	t.Spec.Nodes.ExtraStatements = []iamv1.StatementEntry{
   124  		{
   125  			Effect: iamv1.EffectAllow,
   126  			Resource: iamv1.Resources{
   127  				iamv1.Any,
   128  			},
   129  			Action: iamv1.Actions{
   130  				"elasticfilesystem:DescribeMountTargets",
   131  				"elasticfilesystem:DeleteAccessPoint",
   132  				"elasticfilesystem:DescribeAccessPoints",
   133  				"elasticfilesystem:DescribeFileSystems",
   134  				"elasticfilesystem:CreateAccessPoint",
   135  				"ec2:DescribeAvailabilityZones",
   136  			},
   137  		},
   138  	}
   139  	str, err := yaml.Marshal(t.Spec)
   140  	Expect(err).NotTo(HaveOccurred())
   141  	Expect(os.WriteFile(path.Join(e2eCtx.Settings.ArtifactFolder, "awsiamconfiguration.yaml"), str, 0644)).To(Succeed()) //nolint:gosec
   142  	cloudformationTemplate := renderCustomCloudFormation(&t)
   143  	cfnData, err := cloudformationTemplate.YAML()
   144  	Expect(err).NotTo(HaveOccurred())
   145  	Expect(os.WriteFile(path.Join(e2eCtx.Settings.ArtifactFolder, "cloudformation.yaml"), cfnData, 0644)).To(Succeed()) //nolint:gosec
   146  	return &t
   147  }
   148  
   149  func renderCustomCloudFormation(t *cfn_bootstrap.Template) *cloudformation.Template {
   150  	cloudformationTemplate := t.RenderCloudFormation()
   151  	appendMultiTenancyRoles(t, cloudformationTemplate)
   152  	return cloudformationTemplate
   153  }
   154  
   155  func appendMultiTenancyRoles(t *cfn_bootstrap.Template, cfnt *cloudformation.Template) {
   156  	controllersPolicy := cfnt.Resources[string(cfn_bootstrap.ControllersPolicy)].(*cfn_iam.ManagedPolicy)
   157  	controllersPolicy.Roles = append(
   158  		controllersPolicy.Roles,
   159  		cloudformation.Ref(MultiTenancySimpleRole.RoleName()),
   160  		cloudformation.Ref(MultiTenancyNestedRole.RoleName()),
   161  	)
   162  	cfnt.Resources[MultiTenancyJumpPolicy] = &cfn_iam.ManagedPolicy{
   163  		ManagedPolicyName: MultiTenancyJumpPolicy,
   164  		PolicyDocument: &iamv1.PolicyDocument{
   165  			Version: iamv1.CurrentVersion,
   166  			Statement: []iamv1.StatementEntry{
   167  				{
   168  					Effect:   iamv1.EffectAllow,
   169  					Resource: iamv1.Resources{cloudformation.GetAtt(MultiTenancyNestedRole.RoleName(), "Arn")},
   170  					Action:   iamv1.Actions{"sts:AssumeRole"},
   171  				},
   172  			},
   173  		},
   174  		Roles: []string{cloudformation.Ref(MultiTenancyJumpRole.RoleName())},
   175  	}
   176  
   177  	cfnt.Resources[MultiTenancySimpleRole.RoleName()] = &cfn_iam.Role{
   178  		RoleName:                 MultiTenancySimpleRole.RoleName(),
   179  		AssumeRolePolicyDocument: cfn_bootstrap.AssumeRolePolicy(iamv1.PrincipalAWS, []string{accountRef}),
   180  	}
   181  	cfnt.Resources[MultiTenancyJumpRole.RoleName()] = &cfn_iam.Role{
   182  		RoleName:                 MultiTenancyJumpRole.RoleName(),
   183  		AssumeRolePolicyDocument: cfn_bootstrap.AssumeRolePolicy(iamv1.PrincipalAWS, []string{accountRef}),
   184  	}
   185  	cfnt.Resources[MultiTenancyNestedRole.RoleName()] = &cfn_iam.Role{
   186  		RoleName:                 MultiTenancyNestedRole.RoleName(),
   187  		AssumeRolePolicyDocument: cfn_bootstrap.AssumeRolePolicy(iamv1.PrincipalAWS, []string{accountRef}),
   188  	}
   189  }
   190  
   191  // getBootstrapTemplate gets or generates a new bootstrap template.
   192  func getBootstrapTemplate(e2eCtx *E2EContext) *cfn_bootstrap.Template {
   193  	if e2eCtx.Environment.BootstrapTemplate == nil {
   194  		e2eCtx.Environment.BootstrapTemplate = newBootstrapTemplate(e2eCtx)
   195  	}
   196  	return e2eCtx.Environment.BootstrapTemplate
   197  }
   198  
   199  // ApplyTemplate will render a cluster template and apply it to the management cluster.
   200  func ApplyTemplate(ctx context.Context, configCluster clusterctl.ConfigClusterInput, clusterProxy framework.ClusterProxy) error {
   201  	Expect(ctx).NotTo(BeNil(), "ctx is required for ApplyClusterTemplateAndWait")
   202  
   203  	Byf("Getting the cluster template yaml")
   204  	workloadClusterTemplate := clusterctl.ConfigCluster(ctx, clusterctl.ConfigClusterInput{
   205  		KubeconfigPath:           configCluster.KubeconfigPath,
   206  		ClusterctlConfigPath:     configCluster.ClusterctlConfigPath,
   207  		Flavor:                   configCluster.Flavor,
   208  		Namespace:                configCluster.Namespace,
   209  		ClusterName:              configCluster.ClusterName,
   210  		KubernetesVersion:        configCluster.KubernetesVersion,
   211  		ControlPlaneMachineCount: configCluster.ControlPlaneMachineCount,
   212  		WorkerMachineCount:       configCluster.WorkerMachineCount,
   213  		InfrastructureProvider:   configCluster.InfrastructureProvider,
   214  		LogFolder:                configCluster.LogFolder,
   215  	})
   216  	Expect(workloadClusterTemplate).ToNot(BeNil(), "Failed to get the cluster template")
   217  
   218  	Byf("Applying the %s cluster template yaml to the cluster", configCluster.Flavor)
   219  	return clusterProxy.Apply(ctx, workloadClusterTemplate)
   220  }