diff --git a/data/data/manifests/openshift/ignition-provisioning-secret.yaml.template b/data/data/manifests/openshift/ignition-provisioning-secret.yaml.template new file mode 100644 index 00000000000..68743c28247 --- /dev/null +++ b/data/data/manifests/openshift/ignition-provisioning-secret.yaml.template @@ -0,0 +1,7 @@ +kind: Secret +apiVersion: v1 +metadata: + namespace: openshift-machine-config-operator + name: provisioning-token +data: + token: {{.IgnitionProvisioningToken}} diff --git a/pkg/asset/cluster/cluster.go b/pkg/asset/cluster/cluster.go index 365a7b56aad..d059f6e51d9 100644 --- a/pkg/asset/cluster/cluster.go +++ b/pkg/asset/cluster/cluster.go @@ -13,6 +13,7 @@ import ( "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/cluster/aws" "github.com/openshift/installer/pkg/asset/cluster/azure" + "github.com/openshift/installer/pkg/asset/ignition" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/password" "github.com/openshift/installer/pkg/asset/quota" diff --git a/pkg/asset/ignition/machine/master.go b/pkg/asset/ignition/machine/master.go index f6156e843ff..2a63992a8ed 100644 --- a/pkg/asset/ignition/machine/master.go +++ b/pkg/asset/ignition/machine/master.go @@ -29,6 +29,7 @@ var _ asset.WritableAsset = (*Master)(nil) func (a *Master) Dependencies() []asset.Asset { return []asset.Asset{ &installconfig.InstallConfig{}, + &ignition.ProvisioningToken{}, &tls.RootCA{}, } } @@ -37,9 +38,10 @@ func (a *Master) Dependencies() []asset.Asset { func (a *Master) Generate(dependencies asset.Parents) error { installConfig := &installconfig.InstallConfig{} rootCA := &tls.RootCA{} - dependencies.Get(installConfig, rootCA) + token := &ignition.ProvisioningToken{} + dependencies.Get(installConfig, rootCA, token) - a.Config = pointerIgnitionConfig(installConfig.Config, rootCA.Cert(), "master") + a.Config = pointerIgnitionConfig(installConfig.Config, rootCA.Cert(), token.Token, "master") data, err := ignition.Marshal(a.Config) if err != nil { diff --git a/pkg/asset/ignition/machine/master_test.go b/pkg/asset/ignition/machine/master_test.go index d547f42ec2d..9b08f009e1c 100644 --- a/pkg/asset/ignition/machine/master_test.go +++ b/pkg/asset/ignition/machine/master_test.go @@ -8,6 +8,7 @@ import ( "k8s.io/utils/pointer" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/ignition" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/tls" "github.com/openshift/installer/pkg/ipnet" @@ -41,9 +42,12 @@ func TestMasterGenerate(t *testing.T) { rootCA := &tls.RootCA{} err := rootCA.Generate(nil) assert.NoError(t, err, "unexpected error generating root CA") + token := &ignition.ProvisioningToken{} + err = token.Generate(nil) + assert.NoError(t, err, "unexpected error generating token") parents := asset.Parents{} - parents.Add(installConfig, rootCA) + parents.Add(installConfig, rootCA, token) master := &Master{} err = master.Generate(parents) diff --git a/pkg/asset/ignition/machine/node.go b/pkg/asset/ignition/machine/node.go index 1ebda003e28..fe7cc153c7b 100644 --- a/pkg/asset/ignition/machine/node.go +++ b/pkg/asset/ignition/machine/node.go @@ -18,7 +18,7 @@ import ( // pointerIgnitionConfig generates a config which references the remote config // served by the machine config server. -func pointerIgnitionConfig(installConfig *types.InstallConfig, rootCA []byte, role string) *igntypes.Config { +func pointerIgnitionConfig(installConfig *types.InstallConfig, rootCA []byte, token, role string) *igntypes.Config { var ignitionHost string // Default platform independent ignitionHost ignitionHost = fmt.Sprintf("api-int.%s:22623", installConfig.ClusterDomain()) @@ -37,6 +37,8 @@ func pointerIgnitionConfig(installConfig *types.InstallConfig, rootCA []byte, ro ignitionHost = net.JoinHostPort(installConfig.VSphere.APIVIP, "22623") } } + queryvals := url.Values{} + queryvals.Add("token", token) return &igntypes.Config{ Ignition: igntypes.Ignition{ Version: igntypes.MaxVersion.String(), @@ -44,9 +46,10 @@ func pointerIgnitionConfig(installConfig *types.InstallConfig, rootCA []byte, ro Merge: []igntypes.Resource{{ Source: ignutil.StrToPtr(func() *url.URL { return &url.URL{ - Scheme: "https", - Host: ignitionHost, - Path: fmt.Sprintf("/config/%s", role), + Scheme: "https", + Host: ignitionHost, + Path: fmt.Sprintf("/config/%s", role), + RawQuery: queryvals.Encode(), } }().String()), }}, diff --git a/pkg/asset/ignition/machine/worker.go b/pkg/asset/ignition/machine/worker.go index a1904aa038f..4426b563db8 100644 --- a/pkg/asset/ignition/machine/worker.go +++ b/pkg/asset/ignition/machine/worker.go @@ -29,6 +29,7 @@ var _ asset.WritableAsset = (*Worker)(nil) func (a *Worker) Dependencies() []asset.Asset { return []asset.Asset{ &installconfig.InstallConfig{}, + &ignition.ProvisioningToken{}, &tls.RootCA{}, } } @@ -37,9 +38,10 @@ func (a *Worker) Dependencies() []asset.Asset { func (a *Worker) Generate(dependencies asset.Parents) error { installConfig := &installconfig.InstallConfig{} rootCA := &tls.RootCA{} - dependencies.Get(installConfig, rootCA) + token := &ignition.ProvisioningToken{} + dependencies.Get(installConfig, rootCA, token) - a.Config = pointerIgnitionConfig(installConfig.Config, rootCA.Cert(), "worker") + a.Config = pointerIgnitionConfig(installConfig.Config, rootCA.Cert(), token.Token, "worker") data, err := ignition.Marshal(a.Config) if err != nil { diff --git a/pkg/asset/ignition/machine/worker_test.go b/pkg/asset/ignition/machine/worker_test.go index 91e0833511f..c08f2107625 100644 --- a/pkg/asset/ignition/machine/worker_test.go +++ b/pkg/asset/ignition/machine/worker_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/ignition" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/tls" "github.com/openshift/installer/pkg/ipnet" @@ -31,9 +32,12 @@ func TestWorkerGenerate(t *testing.T) { rootCA := &tls.RootCA{} err := rootCA.Generate(nil) assert.NoError(t, err, "unexpected error generating root CA") + token := &ignition.ProvisioningToken{} + err = token.Generate(nil) + assert.NoError(t, err, "unexpected error generating token") parents := asset.Parents{} - parents.Add(installConfig, rootCA) + parents.Add(installConfig, rootCA, token) worker := &Worker{} err = worker.Generate(parents) diff --git a/pkg/asset/ignition/provisioningtoken.go b/pkg/asset/ignition/provisioningtoken.go new file mode 100644 index 00000000000..a226c197e53 --- /dev/null +++ b/pkg/asset/ignition/provisioningtoken.go @@ -0,0 +1,42 @@ +package ignition + +import ( + "crypto/rand" + "encoding/base64" + + "github.com/openshift/installer/pkg/asset" +) + +// tokenLen is how many bytes of random input go into generating the token. +// It should be a multiple of 3 to render nicely in base64 +// encoding. The current default is long; it's a lot of entropy. The MCS +// will have mitigations against brute forcing. +const tokenLen = 30 + +// ProvisioningToken implements https://github.com/openshift/enhancements/pull/443/ +type ProvisioningToken struct { + Token string +} + +var _ asset.Asset = (*ProvisioningToken)(nil) + +// Dependencies returns no dependencies. +func (a *ProvisioningToken) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate the token +func (a *ProvisioningToken) Generate(asset.Parents) error { + b := make([]byte, tokenLen) + _, err := rand.Read(b) + if err != nil { + return err + } + a.Token = base64.StdEncoding.EncodeToString(b) + return nil +} + +// Name returns the human-friendly name of the asset. +func (a *ProvisioningToken) Name() string { + return "Ignition Provisioning Password" +} diff --git a/pkg/asset/manifests/openshift.go b/pkg/asset/manifests/openshift.go index bf18f9d22bb..04b58f4e52e 100644 --- a/pkg/asset/manifests/openshift.go +++ b/pkg/asset/manifests/openshift.go @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/ignition" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/installconfig/gcp" "github.com/openshift/installer/pkg/asset/installconfig/ovirt" @@ -57,10 +58,12 @@ func (o *Openshift) Dependencies() []asset.Asset { &installconfig.InstallConfig{}, &installconfig.ClusterID{}, &password.KubeadminPassword{}, + &ignition.ProvisioningToken{}, &openshiftinstall.Config{}, &openshift.CloudCredsSecret{}, &openshift.KubeadminPasswordSecret{}, + &openshift.IgnitionProvisioningSecret{}, &openshift.RoleCloudCredsSecretReader{}, &openshift.PrivateClusterOutbound{}, &openshift.BaremetalConfig{}, @@ -73,8 +76,9 @@ func (o *Openshift) Generate(dependencies asset.Parents) error { installConfig := &installconfig.InstallConfig{} clusterID := &installconfig.ClusterID{} kubeadminPassword := &password.KubeadminPassword{} + ignitionProvisioningToken := &ignition.ProvisioningToken{} openshiftInstall := &openshiftinstall.Config{} - dependencies.Get(installConfig, kubeadminPassword, clusterID, openshiftInstall) + dependencies.Get(installConfig, kubeadminPassword, clusterID, openshiftInstall, ignitionProvisioningToken) var cloudCreds cloudCredsSecretData platform := installConfig.Config.Platform.Name() switch platform { @@ -186,11 +190,13 @@ func (o *Openshift) Generate(dependencies asset.Parents) error { templateData := &openshiftTemplateData{ CloudCreds: cloudCreds, + IgnitionProvisioningToken: ignitionProvisioningToken.Token, Base64EncodedKubeadminPwHash: base64.StdEncoding.EncodeToString(kubeadminPassword.PasswordHash), } cloudCredsSecret := &openshift.CloudCredsSecret{} kubeadminPasswordSecret := &openshift.KubeadminPasswordSecret{} + ignitionProvisioningSecret := &openshift.IgnitionProvisioningSecret{} roleCloudCredsSecretReader := &openshift.RoleCloudCredsSecretReader{} baremetalConfig := &openshift.BaremetalConfig{} rhcosImage := new(rhcos.Image) @@ -198,12 +204,14 @@ func (o *Openshift) Generate(dependencies asset.Parents) error { dependencies.Get( cloudCredsSecret, kubeadminPasswordSecret, + ignitionProvisioningSecret, roleCloudCredsSecretReader, baremetalConfig, rhcosImage) assetData := map[string][]byte{ - "99_kubeadmin-password-secret.yaml": applyTemplateData(kubeadminPasswordSecret.Files()[0].Data, templateData), + "99_ignition-provisioning-secret.yaml": applyTemplateData(ignitionProvisioningSecret.Files()[0].Data, templateData), + "99_kubeadmin-password-secret.yaml": applyTemplateData(kubeadminPasswordSecret.Files()[0].Data, templateData), } switch platform { diff --git a/pkg/asset/manifests/template.go b/pkg/asset/manifests/template.go index 5480a30a8be..c4d11fb8fcf 100644 --- a/pkg/asset/manifests/template.go +++ b/pkg/asset/manifests/template.go @@ -82,5 +82,6 @@ type baremetalTemplateData struct { type openshiftTemplateData struct { CloudCreds cloudCredsSecretData + IgnitionProvisioningToken string Base64EncodedKubeadminPwHash string } diff --git a/pkg/asset/templates/content/openshift/ignition-provisioning-token.go b/pkg/asset/templates/content/openshift/ignition-provisioning-token.go new file mode 100644 index 00000000000..303dd1211b3 --- /dev/null +++ b/pkg/asset/templates/content/openshift/ignition-provisioning-token.go @@ -0,0 +1,63 @@ +package openshift + +import ( + "os" + "path/filepath" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/templates/content" +) + +const ( + fileName = "ignition-provisioning-secret.yaml.template" +) + +var _ asset.WritableAsset = (*IgnitionProvisioningSecret)(nil) + +// IgnitionProvisioningSecret implements https://github.com/openshift/enhancements/pull/443 +type IgnitionProvisioningSecret struct { + FileList []*asset.File +} + +// Dependencies returns all of the dependencies directly needed by the asset +func (t *IgnitionProvisioningSecret) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Name returns the human-friendly name of the asset. +func (t *IgnitionProvisioningSecret) Name() string { + return "IgnitionProvisioningSecret" +} + +// Generate generates the actual files by this asset +func (t *IgnitionProvisioningSecret) Generate(parents asset.Parents) error { + data, err := content.GetOpenshiftTemplate(fileName) + if err != nil { + return err + } + t.FileList = []*asset.File{ + { + Filename: filepath.Join(content.TemplateDir, fileName), + Data: []byte(data), + }, + } + return nil +} + +// Files returns the files generated by the asset. +func (t *IgnitionProvisioningSecret) Files() []*asset.File { + return t.FileList +} + +// Load returns the asset from disk. +func (t *IgnitionProvisioningSecret) Load(f asset.FileFetcher) (bool, error) { + file, err := f.FetchByName(filepath.Join(content.TemplateDir, fileName)) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + t.FileList = []*asset.File{file} + return true, nil +}