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  }