1 package resolver
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "regexp"
9 "strings"
10
11 "github.com/rs/zerolog/log"
12 corev1 "k8s.io/api/core/v1"
13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14
15 "edge-infra.dev/pkg/edge/api/graph/mapper"
16 "edge-infra.dev/pkg/edge/api/graph/model"
17 "edge-infra.dev/pkg/edge/api/graphqlhelpers"
18 "edge-infra.dev/pkg/edge/api/middleware"
19 "edge-infra.dev/pkg/edge/api/services"
20 "edge-infra.dev/pkg/edge/api/utils"
21 "edge-infra.dev/pkg/edge/constants"
22 ctypes "edge-infra.dev/pkg/edge/constants/api/cluster"
23
24 "edge-infra.dev/pkg/edge/constants/api/fleet"
25 palletconst "edge-infra.dev/pkg/edge/constants/api/pallet"
26 "edge-infra.dev/pkg/edge/externalsecrets"
27 "edge-infra.dev/pkg/edge/flux/bootstrap"
28 "edge-infra.dev/pkg/edge/k8objectsutils"
29 "edge-infra.dev/pkg/f8n/warehouse"
30 ff "edge-infra.dev/pkg/lib/featureflag"
31 edgeiam "edge-infra.dev/pkg/lib/gcp/iam"
32 "edge-infra.dev/pkg/lib/gcp/iam/roles"
33 "edge-infra.dev/pkg/lib/runtime/version"
34 "edge-infra.dev/pkg/lib/uuid"
35 "edge-infra.dev/pkg/sds/k8s/bootstrap/tokens"
36 )
37
38 func (r *mutationResolver) beginBootstrapCluster(ctx context.Context, payload model.BootstrapPayload) (*model.BootstrapResponse, error) {
39 if err := r.checkEdgeBootstrapClusterEdgeID(ctx, payload.ClusterEdgeID); err != nil {
40 return nil, err
41 }
42
43 if err := r.TerminalService.RemoveClusterEdgeBootstrapToken(ctx, payload.ClusterEdgeID); err != nil {
44 return nil, err
45 }
46
47
48 var (
49 err error
50 projectID string
51 cluster *model.Cluster
52 )
53
54 if payload.ClusterEdgeID != "" {
55 cluster, err = r.StoreClusterService.GetCluster(ctx, payload.ClusterEdgeID)
56 } else {
57 return nil, err
58 }
59 if err != nil {
60 log.Ctx(ctx).Err(err).Msg("error getting cluster from edge db. was the cluster registered?")
61 return nil, err
62 }
63 clusterActive := cluster.Active != nil && *cluster.Active
64 forceBootstrap := payload.Force != nil && *payload.Force
65 if clusterActive && !forceBootstrap {
66 return nil, fmt.Errorf("cluster is already bootstrapped, to bootstrap again use the `force` flag")
67 }
68 labelKeys, err := r.LabelService.GetEdgeClusterLabelKeys(ctx, cluster.ClusterEdgeID)
69 if err != nil {
70 log.Ctx(ctx).Err(err).Msg("error getting cluster label keys: fleet/cluster type")
71 return nil, err
72 }
73 fleetType, err := mapper.ToFleetType(labelKeys)
74 if err != nil {
75 log.Ctx(ctx).Err(err).Msg("error getting cluster label keys: fleet/cluster type")
76 return nil, err
77 }
78 clusterType, err := mapper.ToClusterType(labelKeys)
79 if err != nil {
80 log.Ctx(ctx).Err(err).Msg("error getting cluster label keys: fleet/cluster type")
81 return nil, err
82 }
83
84 if fleetType == fleet.Banner {
85 projectID = r.Config.Bff.TopLevelProjectID
86 } else {
87 projectID = cluster.ProjectID
88 }
89
90 var secrets []string
91 clusterPath := cluster.ClusterEdgeID
92
93 log.Ctx(ctx).Debug().Msgf("bootstrap api called %v", payload)
94
95
96 extSecretString, err := r.createExternalSecretSA(ctx, cluster, projectID)
97 if err != nil {
98 return nil, err
99 }
100 secrets = append(secrets, extSecretString)
101
102
103 if clusterType != ctypes.GKE {
104 fluxSAString, err := r.createFluxSA(ctx, cluster, projectID)
105 if err != nil {
106 return nil, err
107 }
108 secrets = append(secrets, fluxSAString)
109 }
110
111
112 if clusterType != ctypes.GKE {
113 lumperSAString, err := r.createLumperSA(ctx, cluster, projectID, r.Config.Bff.TopLevelProjectID)
114 if err != nil {
115 return nil, err
116 } else if lumperSAString != "" {
117 secrets = append(secrets, lumperSAString)
118 }
119 }
120
121 dps, err := r.getDockerPullSecret(ctx, warehouse.WarehouseNamespace)
122 if err != nil {
123 return nil, err
124 }
125 secrets = append(secrets, dps)
126
127
128 bucketBuilder := bootstrap.BucketFluxConfig().
129 Name(constants.EdgeBucketName).
130 Namespace(constants.FluxEdgeNamespace).
131 BucketName(projectID).
132 ForCluster(clusterPath)
133 if clusterType != ctypes.GKE {
134 bucketBuilder = bucketBuilder.SecretName(constants.TenantBucketSecret)
135 }
136 bucket := bucketBuilder.Build()
137 fluxBucket, err := json.Marshal(bucket)
138 if err != nil {
139 err := fmt.Errorf("error marshaling flux bucket: %w", err)
140 log.Ctx(ctx).Err(err).Msg("flux config marshaling failed")
141 return nil, err
142 }
143 fluxBucketString := string(fluxBucket)
144
145
146 kustomizations, err := services.CreateKustomizations(ctx, constants.EdgeBucketName, clusterPath, cluster.FleetVersion)
147 if err != nil {
148 return nil, err
149 }
150
151 err = r.RegistrationService.UpdateClusterSQLEntry(ctx, true, cluster.ClusterEdgeID)
152 if err != nil {
153 err := fmt.Errorf("error updating cluster entry with active = true in sql: %w", err)
154 log.Ctx(ctx).Err(err).Msg("cluster entry failed")
155 return nil, err
156 }
157
158 if payload.ClusterCaHash != nil {
159
160 if validHash, _ := regexp.MatchString("^[A-Fa-f0-9]{64}$", *payload.ClusterCaHash); !validHash {
161 return nil, fmt.Errorf("cluster CA Hash is not valid")
162 }
163 err := r.RegistrationService.UploadClusterCaHash(ctx, cluster.ClusterEdgeID, *payload.ClusterCaHash)
164 if err != nil {
165 err = fmt.Errorf("error upload cluster ca hash: %w", err)
166 return nil, err
167 }
168 }
169
170
171
172 bsStrings, err := r.BootstrapService.GetManifests(ctx, clusterType, palletconst.BootstrapPallets, cluster)
173 if err != nil {
174 return nil, err
175 }
176
177
178 namespaces, err := r.getBootstrapNamespaces(constants.FluxEdgeNamespace)
179 if err != nil {
180 return nil, err
181 }
182
183 bsStrings = append(bsStrings, namespaces...)
184 bsStrings = append(bsStrings, secrets...)
185 bsStrings = append(bsStrings, fluxBucketString)
186 bsStrings = append(bsStrings, kustomizations...)
187
188 bootstrapResponse := &model.BootstrapResponse{
189 Secrets: secrets,
190 ProjectID: &projectID,
191 FluxConfig: &model.FluxBootstrapResponse{
192 FluxBucket: []string{fluxBucketString},
193 FluxKustomize: kustomizations,
194 },
195 InstallManifests: bsStrings,
196 }
197
198
199
200 if clusterType == ctypes.DSDS {
201 preBootstrapManifests, err := r.getDSDSPreBootstrapManifests(ctx, cluster)
202 if err != nil {
203 return nil, err
204 }
205 bootstrapResponse.PreBootstrapManifests = preBootstrapManifests
206
207 preBootstrapStaticManifests, err := r.BootstrapService.GetManifests(
208 ctx,
209 ctypes.DSDS,
210 palletconst.PreBootstrapStaticPallets,
211 cluster,
212 )
213 if err != nil {
214 return nil, err
215 }
216
217 bootstrapResponse.PreBootstrapStaticManifests = preBootstrapStaticManifests
218 }
219
220 return bootstrapResponse, nil
221 }
222
223 func (r *mutationResolver) getBootstrapNamespaces(namespaces ...string) ([]string, error) {
224 var nsStrings []string
225 if namespaces == nil {
226 return nsStrings, nil
227 }
228 for _, namespace := range namespaces {
229 ns, err := json.Marshal(&corev1.Namespace{
230 TypeMeta: metav1.TypeMeta{
231 Kind: "Namespace",
232 APIVersion: "v1",
233 },
234 ObjectMeta: metav1.ObjectMeta{
235 Name: namespace,
236 },
237 })
238 if err != nil {
239 return nil, err
240 }
241 nsStrings = append(nsStrings, string(ns))
242 }
243 return nsStrings, nil
244 }
245
246 func (r *mutationResolver) getDSDSPreBootstrapManifests(ctx context.Context, cluster *model.Cluster) ([]*string, error) {
247
248 var (
249 preBootstrapManifests []*string
250 ienCRs []*string
251 )
252
253 namespaces, err := r.getBootstrapNamespaces(constants.SdsNamespace, constants.SpegelNamespace)
254 if err != nil {
255 return nil, err
256 }
257
258 nadps, err := r.getDockerPullSecret(ctx, constants.SdsNamespace)
259 if err != nil {
260 return nil, err
261 }
262
263
264
265
266 pbPallets, err := r.BootstrapService.GetManifests(
267 ctx,
268 ctypes.DSDS,
269 palletconst.PreBootstrapPallets,
270 cluster,
271 )
272 if err != nil {
273 return nil, err
274 }
275
276
277 clusterNetworkServices, err := r.StoreClusterService.GetClusterNetworkServices(ctx, cluster.ClusterEdgeID)
278 if err != nil {
279 return nil, err
280 }
281 terminals, err := r.TerminalService.GetTerminals(ctx, &cluster.ClusterEdgeID, nil)
282 if err != nil {
283 return nil, err
284 }
285
286 for _, terminal := range terminals {
287 customLabels, err := r.getTerminalCustomLabels(ctx, terminal.TerminalID)
288 if err != nil {
289 return nil, err
290 }
291 dsdsIENode := mapper.TerminalToIENode(terminal, clusterNetworkServices, customLabels, cluster.FleetVersion)
292 bits, err := json.Marshal(dsdsIENode)
293 if err != nil {
294 return nil, err
295 }
296 ienCrString := string(bits)
297 ienCRs = append(ienCRs, &ienCrString)
298 }
299
300
301 clusterConfig, err := r.ClusterConfigService.GetClusterConfig(ctx, cluster.ClusterEdgeID)
302 if err != nil {
303 return nil, err
304 }
305
306 topologyConfigMap := mapper.CreateTopologyInfoConfigMap(*clusterConfig)
307 cfgBits, err := json.Marshal(topologyConfigMap)
308 if err != nil {
309 return nil, err
310 }
311 topologyConfigMapString := string(cfgBits)
312
313
314 artifactRegistries, err := r.ArtifactRegistryService.GetArtifactRegistriesForCluster(ctx, cluster.ClusterEdgeID)
315 if err != nil {
316 return nil, err
317 }
318 spegelConfig := mapper.CreateSpegelConfigMap(artifactRegistries)
319 cfgBits, err = json.Marshal(spegelConfig)
320 if err != nil {
321 return nil, err
322 }
323 spegelConfigString := string(cfgBits)
324
325 for i := range namespaces {
326 preBootstrapManifests = append(preBootstrapManifests, &namespaces[i])
327 }
328 preBootstrapManifests = append(preBootstrapManifests, &nadps)
329 preBootstrapManifests = append(preBootstrapManifests, &topologyConfigMapString)
330 preBootstrapManifests = append(preBootstrapManifests, &spegelConfigString)
331 for i := range pbPallets {
332 preBootstrapManifests = append(preBootstrapManifests, &pbPallets[i])
333 }
334
335 preBootstrapManifests = append(preBootstrapManifests, ienCRs...)
336 return preBootstrapManifests, nil
337 }
338
339 func (r *mutationResolver) createExternalSecretSA(ctx context.Context, cluster *model.Cluster, projectID string) (string, error) {
340 var (
341
342 externalSecretSADisplayName = fmt.Sprintf("%s Kubernetes External Secrets", cluster.Name)
343
344 externalSecretSADescription = "Used by external-secrets for a specific cluster."
345
346 saName = fmt.Sprintf("ext-sec-%s", uuid.FromUUID(cluster.ClusterEdgeID).Hash())
347 )
348
349 extSecretIAM, err := r.IAMService.CreateSAandSAKey(ctx, projectID, saName, externalSecretSADisplayName, externalSecretSADescription)
350 if err != nil {
351 err := fmt.Errorf("error creating iam SA for external secrets: %w", err)
352 log.Ctx(ctx).Err(err).Msg("external secret SA creation failed")
353 return "", err
354 }
355 extSecretString, err := r.BootstrapService.GetSAKeySecret(externalsecrets.SecretName, externalsecrets.SecretNamespace, externalsecrets.SecretKey, extSecretIAM.APIKey.PrivateKeyData)
356 if err != nil {
357 err := fmt.Errorf("error creating iam SA key for external secrets: %w", err)
358 log.Ctx(ctx).Err(err).Msg("building SA Key secret for external secret failed")
359 return "", err
360 }
361 return extSecretString, nil
362 }
363
364
365 func (r *mutationResolver) createFluxSA(ctx context.Context, cluster *model.Cluster, projectID string) (string, error) {
366 var (
367
368
369 fluxSADescription = "Service account for Flux Edge GKE bootstrapper"
370
371 fluxSADisplayName = fmt.Sprintf("%s Flux edge storage bucket accessor", cluster.ClusterEdgeID)
372
373 getFluxRole = fmt.Sprintf("projects/%s/roles/%s", projectID, bootstrap.FluxRoleGet)
374
375 listFluxRole = fmt.Sprintf("projects/%s/roles/%s", projectID, bootstrap.FluxRoleList)
376
377 saName = fmt.Sprintf("flux-%s", uuid.FromUUID(cluster.ClusterEdgeID).Hash())
378 )
379 fluxIAM, err := r.IAMService.CreateSAandSAKey(ctx, projectID, saName, fluxSADisplayName, fluxSADescription)
380 if err != nil {
381 err := fmt.Errorf("error creating iam resources for flux: %w", err)
382 log.Ctx(ctx).Err(err).Msg("flux SA creation failed")
383 return "", err
384 }
385 fluxIAM, err = r.createEdgeIAMInSingleProject(ctx, projectID, saName, fluxIAM, []services.IAMRole{services.NewIamRole(getFluxRole), services.NewIamRole(listFluxRole)})
386 if err != nil {
387 err := fmt.Errorf("error update iam resources for flux: %w", err)
388 log.Ctx(ctx).Err(err).Msg("flux SA policy update failed")
389 return "", err
390 }
391 fluxSAString, err := r.BootstrapService.GetSAKeySecret(constants.TenantBucketSecret, constants.FluxEdgeNamespace, constants.FluxSAKey, fluxIAM.APIKey.PrivateKeyData)
392 if err != nil {
393 err := fmt.Errorf("error creating iam SA key for flux: %w", err)
394 log.Ctx(ctx).Err(err).Msg("building SA Key secret for flux failed")
395 return "", err
396 }
397 return fluxSAString, err
398 }
399
400 func (r *mutationResolver) createLumperSA(ctx context.Context, cluster *model.Cluster, projectID string, foremanProjectID string) (string, error) {
401 lumperSADescription := "Service account for lumper ctl"
402 hash := uuid.FromUUID(cluster.ClusterEdgeID).Hash()
403 saName := fmt.Sprintf("lumperctl-%s", hash)
404 lumperDisplayName := fmt.Sprintf("%s OCI controller", hash)
405 isGKECluster := ctypes.GetClusterTypeFromLabels(cluster.Labels).IsGKE()
406
407
408 rolesForLumper := getRoleForLumper(isGKECluster)
409
410
411 lumperIAM, err := r.IAMService.CreateSAandSAKey(ctx, projectID, saName, lumperDisplayName, lumperSADescription)
412 if err != nil {
413 err := fmt.Errorf("error creating iam resources for lumper: %w", err)
414 log.Ctx(ctx).Err(err).Msg("lumper SA creation failed")
415 return "", err
416 }
417
418 foremanArtifactory, err := ff.FeatureEnabled(ff.ForemanArtifactory, false)
419 if err != nil {
420 log.Ctx(ctx).Err(err).Msg(fmt.Sprintf("unable to get ld flag for foreman-artifactory. using default value %v.", false))
421 }
422
423
424 if foremanArtifactory {
425 lumperIAM, err = r.IAMService.CreateOrUpdateEdgeIAM(ctx, projectID, foremanProjectID, saName, lumperIAM, rolesForLumper)
426 if err != nil {
427 err := fmt.Errorf("error creating policy binding for lumper: %w", err)
428 log.Ctx(ctx).Err(err).Msg("binding foreman project artifact registry read role for lumper SA failed")
429 return "", err
430 }
431 } else {
432 lumperIAM, err = r.createEdgeIAMInSingleProject(ctx, projectID, saName, lumperIAM, rolesForLumper)
433 if err != nil {
434 err := fmt.Errorf("error creating policy binding for lumper: %w", err)
435 log.Ctx(ctx).Err(err).Msg("binding banner project artifact registry read role for lumper SA failed")
436 return "", err
437 }
438 }
439
440
441
442 if !isGKECluster {
443 lumperSAString, err := r.BootstrapService.GetSAKeySecret(warehouse.ServiceAccountKey, warehouse.WarehouseNamespace, warehouse.SecretKey, lumperIAM.APIKey.PrivateKeyData)
444 if err != nil {
445 err := fmt.Errorf("error creating iam SA key for lumper: %w", err)
446 log.Ctx(ctx).Err(err).Msg("building SA Key secret for lumper failed")
447 return "", err
448 }
449 return lumperSAString, err
450 }
451 return "", err
452 }
453
454 func (r *Resolver) getDockerPullSecret(ctx context.Context, ns string) (string, error) {
455 dp := constants.DockerPullCfg
456 dps, err := r.GCPService.GetSecrets(ctx, &dp, nil, nil, true, r.Config.Bff.TopLevelProjectID)
457 if err != nil {
458 return "", err
459 }
460 secret := corev1.Secret{
461 TypeMeta: metav1.TypeMeta{
462 Kind: "Secret",
463 APIVersion: "v1",
464 },
465 ObjectMeta: metav1.ObjectMeta{
466 Name: constants.EdgeDockerSecret,
467 Namespace: ns,
468 },
469 Type: corev1.SecretTypeDockerConfigJson,
470 Data: map[string][]byte{corev1.DockerConfigJsonKey: []byte(dps[0].Values[0].Value)},
471 }
472 secretBits, err := json.Marshal(secret)
473 if err != nil {
474 return "", err
475 }
476 secretString := string(secretBits)
477 return secretString, nil
478 }
479
480 func getRoleForLumper(isGKECluster bool) []services.IAMRole {
481 rolesForLumper := []services.IAMRole{services.NewIamRole(roles.ArtifactoryReader)}
482 if isGKECluster {
483 rolesForLumper = append(rolesForLumper, services.NewWIIamRole(roles.WorkloadIdentityUser, warehouse.WarehouseNamespace, "lumperctl"))
484 }
485 return rolesForLumper
486 }
487
488
489
490
491
492 func (r *queryResolver) beginTerminalBootstrap(ctx context.Context, macAddresses []string, edgeOSVersion *string) (*model.TerminalBootstrap, error) {
493 macAddresses, err := utils.FormatMacAddresses(macAddresses)
494 if err != nil {
495 return nil, err
496 }
497
498 terminalID, ok := ctx.Value(middleware.TerminalIDCtxKey).(string)
499 if !ok {
500 return nil, fmt.Errorf("could not retrieve terminalID from context")
501 }
502
503 getLabel := false
504
505 terminal, err := r.TerminalService.GetTerminal(ctx, terminalID, &getLabel)
506 if err != nil {
507 return nil, err
508 }
509
510 macAddressMatch, err := utils.MatchTerminalMacAddresses(macAddresses, terminal)
511 if err != nil {
512 return nil, err
513 }
514
515
516 cluster, err := r.StoreClusterService.GetCluster(ctx, terminal.ClusterEdgeID)
517 if err != nil {
518 return nil, err
519 }
520
521
522 if edgeOSVersion != nil && !(strings.HasSuffix(*edgeOSVersion, "-dev") || strings.Contains(*edgeOSVersion, "-rc")) {
523 if cluster.FleetVersion == "latest" || cluster.FleetVersion == "" {
524 cluster.FleetVersion = version.New().SemVer
525 }
526 infraBaseVersion, edgeOSMinorVersion := utils.SetVersionToBaseAndMinorVersion(&cluster.FleetVersion, edgeOSVersion)
527 edgeOSArtifact := constants.EdgeOSArtifact
528 compatibleOSVersions, err := r.CompatibilityService.GetArtifactVersionCompatibility(ctx, model.ArtifactVersion{Name: fleet.Store, Version: infraBaseVersion}, &edgeOSArtifact)
529 if err != nil {
530 return nil, err
531 }
532 if len(compatibleOSVersions.CompatibleArtifacts) == 0 {
533 return nil, fmt.Errorf("the edge-os compatibility for %s does not exist in the current environment", cluster.FleetVersion)
534 }
535 if edgeOSMinorVersion != compatibleOSVersions.CompatibleArtifacts[0].Version {
536 return nil, fmt.Errorf("the ien version %s cannot be installed against store infra version %s, any patch version of the release %s of the ien must be used", *edgeOSVersion, cluster.FleetVersion, compatibleOSVersions.CompatibleArtifacts[0].Version)
537 }
538 }
539
540 if macAddresses == nil || macAddressMatch {
541 return r.execTerminalBootstrap(ctx, terminal)
542 }
543
544 return nil, fmt.Errorf(`status code %d: the MAC address of this terminal does not match the MAC registered for this activation
545 code - please check the activation code and try again`, http.StatusBadRequest)
546 }
547
548 func (r *queryResolver) execTerminalBootstrap(ctx context.Context, terminal *model.Terminal) (*model.TerminalBootstrap, error) {
549 cluster, err := r.StoreClusterService.GetCluster(ctx, terminal.ClusterEdgeID)
550 if err != nil {
551 return nil, err
552 }
553
554 clusterInfra, err := r.BannerService.GetClusterInfraInfo(ctx, cluster.BannerEdgeID)
555 if err != nil {
556 return nil, err
557 }
558
559 tenant, err := r.TenantService.GetTenantByBannerID(ctx, cluster.BannerEdgeID)
560 if err != nil {
561 return nil, err
562 }
563
564 breakglassSecret, err := r.fetchClusterSecret(ctx, cluster.ClusterEdgeID, model.ClusterSecretTypeBreakglass, "latest")
565 if err != nil {
566 return nil, err
567 }
568
569 grubSecret, err := r.fetchClusterSecret(ctx, cluster.ClusterEdgeID, model.ClusterSecretTypeGrub, "latest")
570 if err != nil {
571 return nil, err
572 }
573
574 clusterNetworkServices, err := r.StoreClusterService.GetClusterNetworkServices(ctx, cluster.ClusterEdgeID)
575 if err != nil {
576 return nil, err
577 }
578
579
580 if !*cluster.Active && terminal.Role == model.TerminalRoleTypeWorker {
581 return nil, fmt.Errorf("cluster is not active and cannot join worker node %s first", terminal.Hostname)
582 }
583
584
585 isFirstNode := terminalIsFirstNode(cluster, terminal)
586
587 bootstrapAck := true
588 clusterConfig, _ := r.ClusterConfig(ctx, cluster.ClusterEdgeID)
589 if clusterConfig != nil {
590 bootstrapAck = clusterConfig.BootstrapAck
591 }
592
593 clusterCaHash, bootstrapTokenValues, err := r.getWorkerNodeConfig(ctx, cluster, isFirstNode)
594 if err != nil {
595 return nil, err
596 }
597
598 endpoint := r.Config.EdgeAPIEndpoint
599
600 config, err := r.TerminalService.GetTerminalBootstrapConfig(
601 ctx,
602 terminal,
603 clusterNetworkServices,
604 breakglassSecret,
605 grubSecret,
606 bootstrapAck,
607 isFirstNode,
608 tenant.OrgName,
609 bootstrapTokenValues,
610 clusterCaHash,
611 endpoint,
612 )
613 if err != nil {
614 return nil, err
615 }
616
617
618
619 if err := r.ActivationCodeService.MarkUsed(ctx, terminal.TerminalID, cluster, clusterInfra); err != nil {
620 return nil, err
621 }
622
623
624 log.Ctx(ctx).Info().Msg(sanitizeTerminalBootstrapConfig(config))
625
626 return &model.TerminalBootstrap{
627 Configuration: config,
628 }, nil
629 }
630
631 func (r *queryResolver) generateBootstrapToken(ctx context.Context, cluster *model.Cluster) ([]*model.KeyValues, error) {
632 owner := string(constants.EdgeNamespaceSelector)
633 bootstrapTokenValues, expireAt, err := tokens.GenerateBootstrapJoinToken()
634 if err != nil {
635 return nil, err
636 }
637
638 secretPrefix := "bootstrap-token"
639 secretName := k8objectsutils.NameWithPrefix(secretPrefix, bootstrapTokenValues[0].Value)
640 workload := string(constants.TenantNamespaceSelector)
641
642
643 err = r.GCPService.AddSecret(ctx, secretName, owner, secretPrefix, bootstrapTokenValues, cluster.ProjectID, &workload, expireAt)
644 if err != nil {
645 return nil, err
646 }
647
648 if err := r.BootstrapService.CreateClusterBootstrapTokenEntry(ctx, cluster.ClusterEdgeID, secretName, *expireAt); err != nil {
649 return nil, err
650 }
651
652 return bootstrapTokenValues, nil
653 }
654
655
656 func terminalIsFirstNode(cluster *model.Cluster, terminal *model.Terminal) bool {
657 isControlPlane := terminal.Role == model.TerminalRoleTypeControlplane
658 isInactive := !*cluster.Active
659 return isControlPlane && isInactive
660 }
661
662 func (r *queryResolver) getWorkerNodeConfig(ctx context.Context, cluster *model.Cluster, isFirstNode bool) (string, []*model.KeyValues, error) {
663 var clusterCaHash string
664 var bootstrapTokenValues []*model.KeyValues
665 var err error
666
667
668 if !isFirstNode {
669 bootstrapTokenValues, err = r.generateBootstrapToken(ctx, cluster)
670 if err != nil {
671 return "", nil, err
672 }
673
674 clusterCaHash, err = r.RegistrationService.ClusterCaHash(ctx, cluster.ClusterEdgeID)
675 if err != nil {
676 return "", nil, err
677 }
678
679 if clusterCaHash == "" {
680 return "", nil, fmt.Errorf("cannot bootstrap new nodes until first node has joined (cluster ca hash missing)")
681 }
682 }
683 return clusterCaHash, bootstrapTokenValues, nil
684 }
685
686 func (r *mutationResolver) checkEdgeBootstrapClusterEdgeID(ctx context.Context, clusterEdgeID string) error {
687 edgeBootstrapTokenClusterEdgeID, ok := ctx.Value(middleware.ClusterEdgeIDCtxKey).(string)
688 if !ok {
689 return nil
690 }
691 if clusterEdgeID != edgeBootstrapTokenClusterEdgeID {
692 return fmt.Errorf("edge bootstrap token was not matched to cluster")
693 }
694 return nil
695 }
696
697 func (r *mutationResolver) createEdgeIAMInSingleProject(ctx context.Context, projectID, serviceAccountName string, componentIAM *edgeiam.Component, roles []services.IAMRole) (*edgeiam.Component, error) {
698 EdgeIAM, err := r.IAMService.CreateOrUpdateEdgeIAM(ctx, projectID, projectID, serviceAccountName, componentIAM, roles)
699 if err != nil {
700 return nil, err
701 }
702 return EdgeIAM, nil
703 }
704
705
706 func sanitizeTerminalBootstrapConfig(config string) string {
707
708 sensitiveFields := []string{
709 "zylevel0_hash",
710 "grub_hash",
711 "grub_user",
712 "token",
713 "clusterCaHash",
714 }
715
716 pattern := regexp.MustCompile(`(?i)(` + strings.Join(sensitiveFields, "|") + `)(:\s*).*`)
717
718 return pattern.ReplaceAllString(config, fmt.Sprintf("$1$2%s", graphqlhelpers.SensitiveMask))
719 }
720
View as plain text