github.com/openshift/installer@v1.4.17/pkg/asset/ignition/bootstrap/common.go (about) 1 package bootstrap 2 3 import ( 4 "bytes" 5 "crypto/x509" 6 "encoding/json" 7 "encoding/pem" 8 "fmt" 9 "io" 10 "net" 11 "os" 12 "path" 13 "path/filepath" 14 "strings" 15 "text/template" 16 "time" 17 18 "github.com/containers/image/v5/pkg/sysregistriesv2" 19 ignutil "github.com/coreos/ignition/v2/config/util" 20 igntypes "github.com/coreos/ignition/v2/config/v3_2/types" 21 "github.com/pkg/errors" 22 "github.com/sirupsen/logrus" 23 "github.com/vincent-petithory/dataurl" 24 utilsnet "k8s.io/utils/net" 25 26 configv1 "github.com/openshift/api/config/v1" 27 "github.com/openshift/installer/data" 28 "github.com/openshift/installer/pkg/asset" 29 "github.com/openshift/installer/pkg/asset/ignition" 30 "github.com/openshift/installer/pkg/asset/ignition/bootstrap/baremetal" 31 "github.com/openshift/installer/pkg/asset/ignition/bootstrap/vsphere" 32 mcign "github.com/openshift/installer/pkg/asset/ignition/machine" 33 "github.com/openshift/installer/pkg/asset/installconfig" 34 "github.com/openshift/installer/pkg/asset/kubeconfig" 35 "github.com/openshift/installer/pkg/asset/machines" 36 "github.com/openshift/installer/pkg/asset/manifests" 37 "github.com/openshift/installer/pkg/asset/releaseimage" 38 "github.com/openshift/installer/pkg/asset/rhcos" 39 "github.com/openshift/installer/pkg/asset/tls" 40 "github.com/openshift/installer/pkg/types" 41 baremetaltypes "github.com/openshift/installer/pkg/types/baremetal" 42 vspheretypes "github.com/openshift/installer/pkg/types/vsphere" 43 ) 44 45 const ( 46 rootDir = "/opt/openshift" 47 ) 48 49 var ( 50 commonEnabledServices = []string{ 51 "progress.service", 52 "kubelet.service", 53 "approve-csr.service", 54 // baremetal & openstack platform services 55 "keepalived.service", 56 "coredns.service", 57 "ironic.service", 58 "master-bmh-update.service", 59 } 60 61 rhcosEnabledServices = []string{ 62 "chown-gatewayd-key.service", 63 "systemd-journal-gatewayd.socket", 64 } 65 ) 66 67 // bootstrapTemplateData is the data to use to replace values in bootstrap 68 // template files. 69 type bootstrapTemplateData struct { 70 AdditionalTrustBundle string 71 FIPS bool 72 EtcdCluster string 73 PullSecret string 74 SSHKey string 75 ReleaseImage string 76 ClusterProfile string 77 Proxy *configv1.ProxyStatus 78 Registries []sysregistriesv2.Registry 79 BootImage string 80 PlatformData platformTemplateData 81 BootstrapInPlace *types.BootstrapInPlace 82 UseIPv6ForNodeIP bool 83 UseDualForNodeIP bool 84 IsFCOS bool 85 IsSCOS bool 86 IsOKD bool 87 BootstrapNodeIP string 88 APIServerURL string 89 APIIntServerURL string 90 FeatureSet configv1.FeatureSet 91 Invoker string 92 ClusterDomain string 93 } 94 95 // platformTemplateData is the data to use to replace values in bootstrap 96 // template files that are specific to one platform. 97 type platformTemplateData struct { 98 BareMetal *baremetal.TemplateData 99 VSphere *vsphere.TemplateData 100 } 101 102 // Common is an asset that generates the ignition config for bootstrap nodes. 103 type Common struct { 104 Config *igntypes.Config 105 File *asset.File 106 } 107 108 // Dependencies returns the assets on which the Bootstrap asset depends. 109 func (a *Common) Dependencies() []asset.Asset { 110 return []asset.Asset{ 111 &baremetal.IronicCreds{}, 112 &CVOIgnore{}, 113 &installconfig.InstallConfig{}, 114 &kubeconfig.AdminInternalClient{}, 115 &kubeconfig.Kubelet{}, 116 &kubeconfig.LoopbackClient{}, 117 &mcign.MasterIgnitionCustomizations{}, 118 &mcign.WorkerIgnitionCustomizations{}, 119 &machines.Master{}, 120 &machines.Worker{}, 121 &manifests.Manifests{}, 122 &manifests.Openshift{}, 123 &manifests.Proxy{}, 124 &tls.AdminKubeConfigCABundle{}, 125 &tls.AggregatorCA{}, 126 &tls.AggregatorCABundle{}, 127 &tls.AggregatorClientCertKey{}, 128 &tls.AggregatorSignerCertKey{}, 129 &tls.APIServerProxyCertKey{}, 130 &tls.BootstrapSSHKeyPair{}, 131 &tls.BoundSASigningKey{}, 132 &tls.CloudProviderCABundle{}, 133 &tls.JournalCertKey{}, 134 &tls.KubeAPIServerLBCABundle{}, 135 &tls.KubeAPIServerExternalLBServerCertKey{}, 136 &tls.KubeAPIServerInternalLBServerCertKey{}, 137 &tls.KubeAPIServerLBSignerCertKey{}, 138 &tls.KubeAPIServerLocalhostCABundle{}, 139 &tls.KubeAPIServerLocalhostServerCertKey{}, 140 &tls.KubeAPIServerLocalhostSignerCertKey{}, 141 &tls.KubeAPIServerServiceNetworkCABundle{}, 142 &tls.KubeAPIServerServiceNetworkServerCertKey{}, 143 &tls.KubeAPIServerServiceNetworkSignerCertKey{}, 144 &tls.KubeAPIServerCompleteCABundle{}, 145 &tls.KubeAPIServerCompleteClientCABundle{}, 146 &tls.KubeAPIServerToKubeletCABundle{}, 147 &tls.KubeAPIServerToKubeletClientCertKey{}, 148 &tls.KubeAPIServerToKubeletSignerCertKey{}, 149 &tls.KubeControlPlaneCABundle{}, 150 &tls.KubeControlPlaneKubeControllerManagerClientCertKey{}, 151 &tls.KubeControlPlaneKubeSchedulerClientCertKey{}, 152 &tls.KubeControlPlaneSignerCertKey{}, 153 &tls.KubeletBootstrapCABundle{}, 154 &tls.KubeletClientCABundle{}, 155 &tls.KubeletClientCertKey{}, 156 &tls.KubeletCSRSignerCertKey{}, 157 &tls.KubeletServingCABundle{}, 158 &tls.MCSCertKey{}, 159 &tls.RootCA{}, 160 &tls.ServiceAccountKeyPair{}, 161 &releaseimage.Image{}, 162 new(rhcos.Image), 163 } 164 } 165 166 // Generate generates the ignition config for the Bootstrap asset. 167 func (a *Common) generateConfig(dependencies asset.Parents, templateData *bootstrapTemplateData) error { 168 installConfig := &installconfig.InstallConfig{} 169 bootstrapSSHKeyPair := &tls.BootstrapSSHKeyPair{} 170 dependencies.Get(installConfig, bootstrapSSHKeyPair) 171 172 a.Config = &igntypes.Config{ 173 Ignition: igntypes.Ignition{ 174 Version: igntypes.MaxVersion.String(), 175 }, 176 } 177 178 if err := AddStorageFiles(a.Config, "/", "bootstrap/files", templateData); err != nil { 179 return err 180 } 181 if err := AddSystemdUnits(a.Config, "bootstrap/systemd/common/units", templateData, commonEnabledServices); err != nil { 182 return err 183 } 184 if !templateData.IsOKD { 185 if err := AddSystemdUnits(a.Config, "bootstrap/systemd/rhcos/units", templateData, rhcosEnabledServices); err != nil { 186 return err 187 } 188 } 189 190 // Check for optional platform specific files/units 191 platform := installConfig.Config.Platform.Name() 192 platformFilePath := fmt.Sprintf("bootstrap/%s/files", platform) 193 directory, err := data.Assets.Open(platformFilePath) 194 if err == nil { 195 directory.Close() 196 err = AddStorageFiles(a.Config, "/", platformFilePath, templateData) 197 if err != nil { 198 return err 199 } 200 } 201 202 platformUnitPath := fmt.Sprintf("bootstrap/%s/systemd/units", platform) 203 directory, err = data.Assets.Open(platformUnitPath) 204 if err == nil { 205 directory.Close() 206 if err = AddSystemdUnits(a.Config, platformUnitPath, templateData, commonEnabledServices); err != nil { 207 return err 208 } 209 } 210 211 a.addParentFiles(dependencies) 212 213 a.Config.Passwd.Users = append( 214 a.Config.Passwd.Users, 215 igntypes.PasswdUser{Name: "core", SSHAuthorizedKeys: []igntypes.SSHAuthorizedKey{ 216 igntypes.SSHAuthorizedKey(installConfig.Config.SSHKey), 217 igntypes.SSHAuthorizedKey(string(bootstrapSSHKeyPair.Public())), 218 }}, 219 ) 220 221 return nil 222 } 223 224 func (a *Common) generateFile(filename string) error { 225 data, err := ignition.Marshal(a.Config) 226 if err != nil { 227 return errors.Wrap(err, "failed to Marshal Ignition config") 228 } 229 a.File = &asset.File{ 230 Filename: filename, 231 Data: data, 232 } 233 return nil 234 } 235 236 // Files returns the files generated by the asset. 237 func (a *Common) Files() []*asset.File { 238 if a.File != nil { 239 return []*asset.File{a.File} 240 } 241 return nil 242 } 243 244 // getTemplateData returns the data to use to execute bootstrap templates. 245 func (a *Common) getTemplateData(dependencies asset.Parents, bootstrapInPlace bool) *bootstrapTemplateData { 246 installConfig := &installconfig.InstallConfig{} 247 proxy := &manifests.Proxy{} 248 releaseImage := &releaseimage.Image{} 249 rhcosImage := new(rhcos.Image) 250 bootstrapSSHKeyPair := &tls.BootstrapSSHKeyPair{} 251 ironicCreds := &baremetal.IronicCreds{} 252 dependencies.Get(installConfig, proxy, releaseImage, rhcosImage, bootstrapSSHKeyPair, ironicCreds) 253 254 etcdEndpoints := make([]string, *installConfig.Config.ControlPlane.Replicas) 255 256 for i := range etcdEndpoints { 257 etcdEndpoints[i] = fmt.Sprintf("https://etcd-%d.%s:2379", i, installConfig.Config.ClusterDomain()) 258 } 259 260 registries := []sysregistriesv2.Registry{} 261 digestMirrorSources := []types.ImageDigestSource{} 262 if len(installConfig.Config.DeprecatedImageContentSources) > 0 { 263 digestMirrorSources = ContentSourceToDigestMirror(installConfig.Config.DeprecatedImageContentSources) 264 } else if len(installConfig.Config.ImageDigestSources) > 0 { 265 digestMirrorSources = append(digestMirrorSources, installConfig.Config.ImageDigestSources...) 266 } 267 for _, group := range MergedMirrorSets(digestMirrorSources) { 268 if len(group.Mirrors) == 0 { 269 continue 270 } 271 272 registry := sysregistriesv2.Registry{} 273 registry.Endpoint.Location = group.Source 274 registry.MirrorByDigestOnly = true 275 for _, mirror := range group.Mirrors { 276 registry.Mirrors = append(registry.Mirrors, sysregistriesv2.Endpoint{Location: mirror}) 277 } 278 registries = append(registries, registry) 279 } 280 281 // Generate platform-specific bootstrap data 282 var platformData platformTemplateData 283 284 switch installConfig.Config.Platform.Name() { 285 case baremetaltypes.Name: 286 platformData.BareMetal = baremetal.GetTemplateData( 287 installConfig.Config.Platform.BareMetal, 288 installConfig.Config.MachineNetwork, 289 *installConfig.Config.ControlPlane.Replicas, 290 ironicCreds.Username, 291 ironicCreds.Password, 292 ) 293 case vspheretypes.Name: 294 platformData.VSphere = vsphere.GetTemplateData(installConfig.Config.Platform.VSphere) 295 } 296 297 bootstrapNodeIP := os.Getenv("OPENSHIFT_INSTALL_BOOTSTRAP_NODE_IP") 298 if bootstrapNodeIP != "" && net.ParseIP(bootstrapNodeIP) == nil { 299 logrus.Warnf("OPENSHIFT_INSTALL_BOOTSTRAP_NODE_IP must have valid ip address, given %s. Skipping it", bootstrapNodeIP) 300 bootstrapNodeIP = "" 301 } 302 303 var hasIPv4, hasIPv6, ipv6Primary bool 304 for i, snet := range installConfig.Config.ServiceNetwork { 305 if utilsnet.IsIPv4(snet.IP) { 306 hasIPv4 = true 307 } else { 308 hasIPv6 = true 309 if i == 0 { 310 ipv6Primary = true 311 } 312 } 313 } 314 315 // Set cluster profile 316 clusterProfile := "" 317 if cp := os.Getenv("OPENSHIFT_INSTALL_EXPERIMENTAL_CLUSTER_PROFILE"); cp != "" { 318 logrus.Warnf("Found override for Cluster Profile: %q", cp) 319 clusterProfile = cp 320 } 321 var bootstrapInPlaceConfig *types.BootstrapInPlace 322 if bootstrapInPlace { 323 bootstrapInPlaceConfig = installConfig.Config.BootstrapInPlace 324 } 325 326 apiURL := fmt.Sprintf("api.%s", installConfig.Config.ClusterDomain()) 327 apiIntURL := fmt.Sprintf("api-int.%s", installConfig.Config.ClusterDomain()) 328 329 openshiftInstallInvoker := os.Getenv("OPENSHIFT_INSTALL_INVOKER") 330 331 return &bootstrapTemplateData{ 332 AdditionalTrustBundle: installConfig.Config.AdditionalTrustBundle, 333 FIPS: installConfig.Config.FIPS, 334 PullSecret: installConfig.Config.PullSecret, 335 SSHKey: installConfig.Config.SSHKey, 336 ReleaseImage: releaseImage.PullSpec, 337 EtcdCluster: strings.Join(etcdEndpoints, ","), 338 Proxy: &proxy.Config.Status, 339 Registries: registries, 340 BootImage: rhcosImage.ControlPlane, 341 PlatformData: platformData, 342 ClusterProfile: clusterProfile, 343 BootstrapInPlace: bootstrapInPlaceConfig, 344 UseIPv6ForNodeIP: ipv6Primary, 345 UseDualForNodeIP: hasIPv4 && hasIPv6, 346 IsFCOS: installConfig.Config.IsFCOS(), 347 IsSCOS: installConfig.Config.IsSCOS(), 348 IsOKD: installConfig.Config.IsOKD(), 349 BootstrapNodeIP: bootstrapNodeIP, 350 APIServerURL: apiURL, 351 APIIntServerURL: apiIntURL, 352 FeatureSet: installConfig.Config.FeatureSet, 353 Invoker: openshiftInstallInvoker, 354 ClusterDomain: installConfig.Config.ClusterDomain(), 355 } 356 } 357 358 // AddStorageFiles adds files to a Ignition config. 359 // Parameters: 360 // config - the ignition config to be modified 361 // base - path were the files are written to in to config 362 // uri - path under data/data specifying the files to be included 363 // templateData - struct to used to render templates 364 func AddStorageFiles(config *igntypes.Config, base string, uri string, templateData interface{}) (err error) { 365 file, err := data.Assets.Open(uri) 366 if err != nil { 367 return err 368 } 369 defer file.Close() 370 371 info, err := file.Stat() 372 if err != nil { 373 return err 374 } 375 376 if info.IsDir() { 377 children, err := file.Readdir(0) 378 if err != nil { 379 return err 380 } 381 if err = file.Close(); err != nil { 382 return err 383 } 384 385 for _, childInfo := range children { 386 name := childInfo.Name() 387 err = AddStorageFiles(config, path.Join(base, name), path.Join(uri, name), templateData) 388 if err != nil { 389 return err 390 } 391 } 392 return nil 393 } 394 395 name := info.Name() 396 _, data, err := readFile(name, file, templateData) 397 if err != nil { 398 return err 399 } 400 401 filename := path.Base(uri) 402 parentDir := path.Base(path.Dir(uri)) 403 404 var mode int 405 appendToFile := false 406 if parentDir == "bin" || parentDir == "dispatcher.d" { 407 mode = 0555 408 } else if filename == "motd" || filename == "containers.conf" { 409 mode = 0644 410 appendToFile = true 411 } else if filename == "registries.conf" { 412 // Having the mode be private breaks rpm-ostree, xref 413 // https://github.com/openshift/installer/pull/6789 414 mode = 0644 415 } else { 416 mode = 0600 417 } 418 ign := ignition.FileFromBytes(strings.TrimSuffix(base, ".template"), "root", mode, data) 419 if appendToFile { 420 ignition.ConvertToAppendix(&ign) 421 } 422 423 // Replace files that already exist in the slice with ones added later, otherwise append them 424 config.Storage.Files = replaceOrAppend(config.Storage.Files, ign) 425 426 return nil 427 } 428 429 // AddSystemdUnits adds systemd units to a Ignition config. 430 // Parameters: 431 // config - the ignition config to be modified 432 // uri - path under data/data specifying the systemd units files to be included 433 // templateData - struct to used to render templates 434 // enabledServices - a list of systemd units to be enabled by default 435 func AddSystemdUnits(config *igntypes.Config, uri string, templateData interface{}, enabledServices []string) (err error) { 436 enabled := make(map[string]struct{}, len(enabledServices)) 437 for _, s := range enabledServices { 438 enabled[s] = struct{}{} 439 } 440 441 directory, err := data.Assets.Open(uri) 442 if err != nil { 443 return err 444 } 445 defer directory.Close() 446 447 children, err := directory.Readdir(0) 448 if err != nil { 449 return err 450 } 451 452 for _, childInfo := range children { 453 dir := path.Join(uri, childInfo.Name()) 454 file, err := data.Assets.Open(dir) 455 if err != nil { 456 return err 457 } 458 defer file.Close() 459 460 info, err := file.Stat() 461 if err != nil { 462 return err 463 } 464 465 if info.IsDir() { 466 if dir := info.Name(); !strings.HasSuffix(dir, ".d") { 467 logrus.Tracef("Ignoring internal asset directory %q while looking for systemd drop-ins", dir) 468 continue 469 } 470 471 children, err := file.Readdir(0) 472 if err != nil { 473 return err 474 } 475 if err = file.Close(); err != nil { 476 return err 477 } 478 479 dropins := []igntypes.Dropin{} 480 for _, childInfo := range children { 481 file, err := data.Assets.Open(path.Join(dir, childInfo.Name())) 482 if err != nil { 483 return err 484 } 485 defer file.Close() 486 487 childName, contents, err := readFile(childInfo.Name(), file, templateData) 488 if err != nil { 489 return err 490 } 491 492 dropins = append(dropins, igntypes.Dropin{ 493 Name: childName, 494 Contents: ignutil.StrToPtr(string(contents)), 495 }) 496 } 497 498 name := strings.TrimSuffix(childInfo.Name(), ".d") 499 unit := igntypes.Unit{ 500 Name: name, 501 Dropins: dropins, 502 } 503 if _, ok := enabled[name]; ok { 504 unit.Enabled = ignutil.BoolToPtr(true) 505 } 506 config.Systemd.Units = append(config.Systemd.Units, unit) 507 } else { 508 name, contents, err := readFile(childInfo.Name(), file, templateData) 509 if err != nil { 510 return err 511 } 512 513 unit := igntypes.Unit{ 514 Name: name, 515 Contents: ignutil.StrToPtr(string(contents)), 516 } 517 if _, ok := enabled[name]; ok { 518 unit.Enabled = ignutil.BoolToPtr(true) 519 } 520 config.Systemd.Units = append(config.Systemd.Units, unit) 521 } 522 } 523 524 return nil 525 } 526 527 // replace is an utilitary function to do string replacement in templates. 528 func replace(input, from, to string) string { 529 return strings.ReplaceAll(input, from, to) 530 } 531 532 // Read data from the string reader, and, if the name ends with 533 // '.template', strip that extension from the name and render the 534 // template. 535 func readFile(name string, reader io.Reader, templateData interface{}) (finalName string, data []byte, err error) { 536 data, err = io.ReadAll(reader) 537 if err != nil { 538 return name, []byte{}, err 539 } 540 541 if filepath.Ext(name) == ".template" { 542 name = strings.TrimSuffix(name, ".template") 543 tmpl := template.New(name).Funcs(template.FuncMap{"replace": replace}) 544 tmpl, err := tmpl.Parse(string(data)) 545 if err != nil { 546 return name, data, err 547 } 548 stringData := applyTemplateData(tmpl, templateData) 549 data = []byte(stringData) 550 } 551 552 return name, data, nil 553 } 554 555 func (a *Common) addParentFiles(dependencies asset.Parents) { 556 // These files are all added with mode 0644, i.e. readable 557 // by all processes on the system. 558 for _, asset := range []asset.WritableAsset{ 559 &manifests.Manifests{}, 560 &manifests.Openshift{}, 561 &machines.Master{}, 562 &machines.Worker{}, 563 &mcign.MasterIgnitionCustomizations{}, 564 &mcign.WorkerIgnitionCustomizations{}, 565 &CVOIgnore{}, // this must come after manifests.Manifests so that it replaces cvo-overrides.yaml 566 } { 567 dependencies.Get(asset) 568 569 // Replace files that already exist in the slice with ones added later, otherwise append them 570 for _, file := range ignition.FilesFromAsset(rootDir, "root", 0644, asset) { 571 a.Config.Storage.Files = replaceOrAppend(a.Config.Storage.Files, file) 572 } 573 } 574 575 // These files are all added with mode 0600; use for secret keys and the like. 576 for _, asset := range []asset.WritableAsset{ 577 &kubeconfig.AdminInternalClient{}, 578 &kubeconfig.Kubelet{}, 579 &kubeconfig.LoopbackClient{}, 580 &tls.AdminKubeConfigCABundle{}, 581 &tls.AggregatorCA{}, 582 &tls.AggregatorCABundle{}, 583 &tls.AggregatorClientCertKey{}, 584 &tls.AggregatorSignerCertKey{}, 585 &tls.APIServerProxyCertKey{}, 586 &tls.BoundSASigningKey{}, 587 &tls.CloudProviderCABundle{}, 588 &tls.KubeAPIServerLBCABundle{}, 589 &tls.KubeAPIServerExternalLBServerCertKey{}, 590 &tls.KubeAPIServerInternalLBServerCertKey{}, 591 &tls.KubeAPIServerLBSignerCertKey{}, 592 &tls.KubeAPIServerLocalhostCABundle{}, 593 &tls.KubeAPIServerLocalhostServerCertKey{}, 594 &tls.KubeAPIServerLocalhostSignerCertKey{}, 595 &tls.KubeAPIServerServiceNetworkCABundle{}, 596 &tls.KubeAPIServerServiceNetworkServerCertKey{}, 597 &tls.KubeAPIServerServiceNetworkSignerCertKey{}, 598 &tls.KubeAPIServerCompleteCABundle{}, 599 &tls.KubeAPIServerCompleteClientCABundle{}, 600 &tls.KubeAPIServerToKubeletCABundle{}, 601 &tls.KubeAPIServerToKubeletClientCertKey{}, 602 &tls.KubeAPIServerToKubeletSignerCertKey{}, 603 &tls.KubeControlPlaneCABundle{}, 604 &tls.KubeControlPlaneKubeControllerManagerClientCertKey{}, 605 &tls.KubeControlPlaneKubeSchedulerClientCertKey{}, 606 &tls.KubeControlPlaneSignerCertKey{}, 607 &tls.KubeletBootstrapCABundle{}, 608 &tls.KubeletClientCABundle{}, 609 &tls.KubeletClientCertKey{}, 610 &tls.KubeletCSRSignerCertKey{}, 611 &tls.KubeletServingCABundle{}, 612 &tls.MCSCertKey{}, 613 &tls.ServiceAccountKeyPair{}, 614 &tls.JournalCertKey{}, 615 } { 616 dependencies.Get(asset) 617 618 // Replace files that already exist in the slice with ones added later, otherwise append them 619 for _, file := range ignition.FilesFromAsset(rootDir, "root", 0600, asset) { 620 a.Config.Storage.Files = replaceOrAppend(a.Config.Storage.Files, file) 621 } 622 } 623 624 rootCA := &tls.RootCA{} 625 dependencies.Get(rootCA) 626 a.Config.Storage.Files = replaceOrAppend(a.Config.Storage.Files, ignition.FileFromBytes(filepath.Join(rootDir, rootCA.CertFile().Filename), "root", 0644, rootCA.Cert())) 627 } 628 629 func replaceOrAppend(files []igntypes.File, file igntypes.File) []igntypes.File { 630 for i, f := range files { 631 if f.Node.Path == file.Node.Path { 632 files[i] = file 633 return files 634 } 635 } 636 files = append(files, file) 637 return files 638 } 639 640 func applyTemplateData(template *template.Template, templateData interface{}) string { 641 buf := &bytes.Buffer{} 642 if err := template.Execute(buf, templateData); err != nil { 643 panic(err) 644 } 645 return buf.String() 646 } 647 648 // Load returns the bootstrap ignition from disk. 649 func (a *Common) load(f asset.FileFetcher, filename string) (found bool, err error) { 650 file, err := f.FetchByName(filename) 651 if err != nil { 652 if os.IsNotExist(err) { 653 return false, nil 654 } 655 return false, err 656 } 657 658 config := &igntypes.Config{} 659 if err := json.Unmarshal(file.Data, config); err != nil { 660 return false, errors.Wrapf(err, "failed to unmarshal %s", filename) 661 } 662 663 a.File, a.Config = file, config 664 warnIfCertificatesExpired(a.Config) 665 return true, nil 666 } 667 668 // warnIfCertificatesExpired checks for expired certificates and warns if so 669 func warnIfCertificatesExpired(config *igntypes.Config) { 670 expiredCerts := 0 671 for _, file := range config.Storage.Files { 672 if filepath.Ext(file.Path) == ".crt" && file.Contents.Source != nil { 673 fileName := path.Base(file.Path) 674 decoded, err := dataurl.DecodeString(*file.Contents.Source) 675 if err != nil { 676 logrus.Debugf("Unable to decode certificate %s: %s", fileName, err.Error()) 677 continue 678 } 679 data := decoded.Data 680 for { 681 block, rest := pem.Decode(data) 682 if block == nil { 683 break 684 } 685 686 cert, err := x509.ParseCertificate(block.Bytes) 687 if err == nil { 688 if time.Now().UTC().After(cert.NotAfter) { 689 logrus.Warnf("Bootstrap Ignition-Config Certificate %s expired at %s.", path.Base(file.Path), cert.NotAfter.Format(time.RFC3339)) 690 expiredCerts++ 691 } 692 } else { 693 logrus.Debugf("Unable to parse certificate %s: %s", fileName, err.Error()) 694 break 695 } 696 697 data = rest 698 } 699 } 700 } 701 702 if expiredCerts > 0 { 703 logrus.Warnf("Bootstrap Ignition-Config: %d certificates expired. Installation attempts with the created Ignition-Configs will possibly fail.", expiredCerts) 704 } 705 }