github.com/openshift/installer@v1.4.17/pkg/asset/machines/aws/awsmachines.go (about)

     1  // Package aws generates Machine objects for aws.
     2  package aws
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/pem"
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/vincent-petithory/dataurl"
    11  	v1 "k8s.io/api/core/v1"
    12  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  	"k8s.io/apimachinery/pkg/util/sets"
    14  	"k8s.io/utils/ptr"
    15  	capa "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
    16  	capi "sigs.k8s.io/cluster-api/api/v1beta1"
    17  
    18  	"github.com/openshift/installer/pkg/asset"
    19  	"github.com/openshift/installer/pkg/asset/manifests/capiutils"
    20  	"github.com/openshift/installer/pkg/types"
    21  	"github.com/openshift/installer/pkg/types/aws"
    22  )
    23  
    24  // MachineInput defines the inputs needed to generate a machine asset.
    25  type MachineInput struct {
    26  	Role           string
    27  	Pool           *types.MachinePool
    28  	Subnets        map[string]string
    29  	Tags           capa.Tags
    30  	PublicIP       bool
    31  	PublicIpv4Pool string
    32  	Ignition       *capa.Ignition
    33  }
    34  
    35  // GenerateMachines returns manifests and runtime objects to provision the control plane (including bootstrap, if applicable) nodes using CAPI.
    36  func GenerateMachines(clusterID string, in *MachineInput) ([]*asset.RuntimeFile, error) {
    37  	if poolPlatform := in.Pool.Platform.Name(); poolPlatform != aws.Name {
    38  		return nil, fmt.Errorf("non-AWS machine-pool: %q", poolPlatform)
    39  	}
    40  	mpool := in.Pool.Platform.AWS
    41  
    42  	total := int64(1)
    43  	if in.Pool.Replicas != nil {
    44  		total = *in.Pool.Replicas
    45  	}
    46  
    47  	imds := capa.HTTPTokensStateOptional
    48  	if mpool.EC2Metadata.Authentication == "Required" {
    49  		imds = capa.HTTPTokensStateRequired
    50  	}
    51  
    52  	instanceProfile := in.Pool.Platform.AWS.IAMProfile
    53  	if len(instanceProfile) == 0 {
    54  		instanceProfile = fmt.Sprintf("%s-master-profile", clusterID)
    55  	}
    56  
    57  	var result []*asset.RuntimeFile
    58  
    59  	for idx := int64(0); idx < total; idx++ {
    60  		subnet := &capa.AWSResourceReference{}
    61  		zone := mpool.Zones[int(idx)%len(mpool.Zones)]
    62  
    63  		// BYO VPC deployments when subnet IDs are set on install-config.yaml
    64  		if len(in.Subnets) > 0 {
    65  			subnetID, ok := in.Subnets[zone]
    66  			if len(in.Subnets) > 0 && !ok {
    67  				return nil, fmt.Errorf("no subnet for zone %s", zone)
    68  			}
    69  			if subnetID == "" {
    70  				return nil, fmt.Errorf("invalid subnet ID for zone %s", zone)
    71  			}
    72  			subnet.ID = ptr.To(subnetID)
    73  		} else {
    74  			subnetInternetScope := "private"
    75  			if in.PublicIP {
    76  				subnetInternetScope = "public"
    77  			}
    78  			subnet.Filters = []capa.Filter{
    79  				{
    80  					Name:   "tag:Name",
    81  					Values: []string{fmt.Sprintf("%s-subnet-%s-%s", clusterID, subnetInternetScope, zone)},
    82  				},
    83  			}
    84  		}
    85  
    86  		awsMachine := &capa.AWSMachine{
    87  			ObjectMeta: metav1.ObjectMeta{
    88  				Name: fmt.Sprintf("%s-%s-%d", clusterID, in.Pool.Name, idx),
    89  				Labels: map[string]string{
    90  					"cluster.x-k8s.io/control-plane": "",
    91  				},
    92  			},
    93  			Spec: capa.AWSMachineSpec{
    94  				Ignition:             in.Ignition,
    95  				UncompressedUserData: ptr.To(true),
    96  				InstanceType:         mpool.InstanceType,
    97  				AMI:                  capa.AMIReference{ID: ptr.To(mpool.AMIID)},
    98  				SSHKeyName:           ptr.To(""),
    99  				IAMInstanceProfile:   instanceProfile,
   100  				Subnet:               subnet,
   101  				PublicIP:             ptr.To(in.PublicIP),
   102  				AdditionalTags:       in.Tags,
   103  				RootVolume: &capa.Volume{
   104  					Size:          int64(mpool.EC2RootVolume.Size),
   105  					Type:          capa.VolumeType(mpool.EC2RootVolume.Type),
   106  					IOPS:          int64(mpool.EC2RootVolume.IOPS),
   107  					Encrypted:     ptr.To(true),
   108  					EncryptionKey: mpool.KMSKeyARN,
   109  				},
   110  				InstanceMetadataOptions: &capa.InstanceMetadataOptions{
   111  					HTTPTokens:   imds,
   112  					HTTPEndpoint: capa.InstanceMetadataEndpointStateEnabled,
   113  				},
   114  			},
   115  		}
   116  		awsMachine.SetGroupVersionKind(capa.GroupVersion.WithKind("AWSMachine"))
   117  
   118  		if in.Role == "bootstrap" {
   119  			awsMachine.Name = capiutils.GenerateBoostrapMachineName(clusterID)
   120  			awsMachine.Labels["install.openshift.io/bootstrap"] = ""
   121  
   122  			// Enable BYO Public IPv4 Pool when defined on install-config.yaml.
   123  			if len(in.PublicIpv4Pool) > 0 {
   124  				awsMachine.Spec.ElasticIPPool = &capa.ElasticIPPool{
   125  					PublicIpv4Pool:              ptr.To(in.PublicIpv4Pool),
   126  					PublicIpv4PoolFallBackOrder: ptr.To(capa.PublicIpv4PoolFallbackOrderAmazonPool),
   127  				}
   128  			}
   129  		}
   130  
   131  		// Handle additional security groups.
   132  		for _, sg := range mpool.AdditionalSecurityGroupIDs {
   133  			awsMachine.Spec.AdditionalSecurityGroups = append(
   134  				awsMachine.Spec.AdditionalSecurityGroups,
   135  				capa.AWSResourceReference{ID: ptr.To(sg)},
   136  			)
   137  		}
   138  
   139  		result = append(result, &asset.RuntimeFile{
   140  			File:   asset.File{Filename: fmt.Sprintf("10_inframachine_%s.yaml", awsMachine.Name)},
   141  			Object: awsMachine,
   142  		})
   143  
   144  		machine := &capi.Machine{
   145  			ObjectMeta: metav1.ObjectMeta{
   146  				Name: awsMachine.Name,
   147  				Labels: map[string]string{
   148  					"cluster.x-k8s.io/control-plane": "",
   149  				},
   150  			},
   151  			Spec: capi.MachineSpec{
   152  				ClusterName: clusterID,
   153  				Bootstrap: capi.Bootstrap{
   154  					DataSecretName: ptr.To(fmt.Sprintf("%s-%s", clusterID, in.Role)),
   155  				},
   156  				InfrastructureRef: v1.ObjectReference{
   157  					APIVersion: capa.GroupVersion.String(),
   158  					Kind:       "AWSMachine",
   159  					Name:       awsMachine.Name,
   160  				},
   161  			},
   162  		}
   163  		machine.SetGroupVersionKind(capi.GroupVersion.WithKind("Machine"))
   164  
   165  		result = append(result, &asset.RuntimeFile{
   166  			File:   asset.File{Filename: fmt.Sprintf("10_machine_%s.yaml", machine.Name)},
   167  			Object: machine,
   168  		})
   169  	}
   170  	return result, nil
   171  }
   172  
   173  // CapaTagsFromUserTags converts a map of user tags to a map of capa.Tags.
   174  func CapaTagsFromUserTags(clusterID string, usertags map[string]string) (capa.Tags, error) {
   175  	tags := capa.Tags{}
   176  	tags[fmt.Sprintf("kubernetes.io/cluster/%s", clusterID)] = "owned"
   177  
   178  	forbiddenTags := sets.New[string]()
   179  	for key := range tags {
   180  		forbiddenTags.Insert(key)
   181  	}
   182  
   183  	userTagKeys := sets.New[string]()
   184  	for key := range usertags {
   185  		userTagKeys.Insert(key)
   186  	}
   187  
   188  	if clobberedTags := userTagKeys.Intersection(forbiddenTags); clobberedTags.Len() > 0 {
   189  		return nil, fmt.Errorf("user tag keys %v are not allowed", sets.List(clobberedTags))
   190  	}
   191  
   192  	for _, k := range sets.List(userTagKeys) {
   193  		tags[k] = usertags[k]
   194  	}
   195  	return tags, nil
   196  }
   197  
   198  // CapaIgnitionWithCertBundleAndProxy generates CAPA ignition config with cert and proxy information.
   199  func CapaIgnitionWithCertBundleAndProxy(userCA string, proxy *types.Proxy) (*capa.Ignition, error) {
   200  	carefs, err := parseCertificateBundle([]byte(userCA))
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	return &capa.Ignition{
   205  		Version: "3.2",
   206  		TLS: &capa.IgnitionTLS{
   207  			CASources: carefs,
   208  		},
   209  		Proxy: capaIgnitionProxy(proxy),
   210  	}, nil
   211  }
   212  
   213  // TODO: try to share this code with ignition.bootstrap package?
   214  // parseCertificateBundle loads each certificate in the bundle to the CAPA
   215  // carrier type, ignoring any invisible character before, after and in between
   216  // certificates.
   217  func parseCertificateBundle(userCA []byte) ([]capa.IgnitionCASource, error) {
   218  	userCA = bytes.TrimSpace(userCA)
   219  
   220  	var carefs []capa.IgnitionCASource
   221  	for len(userCA) > 0 {
   222  		var block *pem.Block
   223  		block, userCA = pem.Decode(userCA)
   224  		if block == nil {
   225  			return nil, fmt.Errorf("unable to parse certificate, please check the certificates")
   226  		}
   227  
   228  		carefs = append(carefs, capa.IgnitionCASource(dataurl.EncodeBytes(pem.EncodeToMemory(block))))
   229  
   230  		userCA = bytes.TrimSpace(userCA)
   231  	}
   232  
   233  	return carefs, nil
   234  }
   235  
   236  func capaIgnitionProxy(proxy *types.Proxy) *capa.IgnitionProxy {
   237  	capaProxy := &capa.IgnitionProxy{}
   238  	if proxy == nil {
   239  		return capaProxy
   240  	}
   241  	if httpProxy := proxy.HTTPProxy; httpProxy != "" {
   242  		capaProxy.HTTPProxy = &httpProxy
   243  	}
   244  	if httpsProxy := proxy.HTTPSProxy; httpsProxy != "" {
   245  		capaProxy.HTTPSProxy = &httpsProxy
   246  	}
   247  	capaProxy.NoProxy = make([]capa.IgnitionNoProxy, 0, len(proxy.NoProxy))
   248  	if noProxy := proxy.NoProxy; noProxy != "" {
   249  		noProxySplit := strings.Split(noProxy, ",")
   250  		for _, p := range noProxySplit {
   251  			capaProxy.NoProxy = append(capaProxy.NoProxy, capa.IgnitionNoProxy(p))
   252  		}
   253  	}
   254  	return capaProxy
   255  }