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 }