Skip to content

Commit

Permalink
Add an automatically generated Ignition provisioning token
Browse files Browse the repository at this point in the history
Part of implementing: openshift/enhancements#443

The installer generates a random token (~password) and injects
it into the Ignition pointer configuration and as a secret into the
cluster.

The MCO will check it.
  • Loading branch information
cgwalters committed Nov 12, 2020
1 parent 9be316b commit f084994
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: Secret
apiVersion: v1
metadata:
namespace: openshift-machine-config-operator
name: provisioning-token
data:
token: {{.Base64EncodedKubeadminPwHash}}
2 changes: 2 additions & 0 deletions pkg/asset/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/openshift/installer/pkg/asset/cluster/azure"
"github.com/openshift/installer/pkg/asset/installconfig"
"github.com/openshift/installer/pkg/asset/password"
"github.com/openshift/installer/pkg/asset/ignition"
"github.com/openshift/installer/pkg/asset/quota"
"github.com/openshift/installer/pkg/metrics/timer"
"github.com/openshift/installer/pkg/terraform"
Expand Down Expand Up @@ -51,6 +52,7 @@ func (c *Cluster) Dependencies() []asset.Asset {
&quota.PlatformQuotaCheck{},
&TerraformVariables{},
&password.KubeadminPassword{},
&ignition.ProvisioningToken{},
}
}

Expand Down
6 changes: 4 additions & 2 deletions pkg/asset/ignition/machine/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var _ asset.WritableAsset = (*Master)(nil)
func (a *Master) Dependencies() []asset.Asset {
return []asset.Asset{
&installconfig.InstallConfig{},
&ignition.ProvisioningToken{},
&tls.RootCA{},
}
}
Expand All @@ -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 {
Expand Down
11 changes: 7 additions & 4 deletions pkg/asset/ignition/machine/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -37,16 +37,19 @@ 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(),
Config: igntypes.IgnitionConfig{
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()),
}},
Expand Down
6 changes: 4 additions & 2 deletions pkg/asset/ignition/machine/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var _ asset.WritableAsset = (*Worker)(nil)
func (a *Worker) Dependencies() []asset.Asset {
return []asset.Asset{
&installconfig.InstallConfig{},
&ignition.ProvisioningToken{},
&tls.RootCA{},
}
}
Expand All @@ -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 {
Expand Down
47 changes: 47 additions & 0 deletions pkg/asset/ignition/provisioningtoken.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package ignition

import (
"crypto/rand"
"encoding/base64"
"fmt"

"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)
n, err := rand.Read(b)
if err != nil {
return err
}
if n != tokenLen {
panic(fmt.Sprintf("Expected %d bytes", tokenLen))
}
a.Token = base64.StdEncoding.EncodeToString(b)
fmt.Printf("Generated token: %s\n", a.Token)
return nil
}

// Name returns the human-friendly name of the asset.
func (a *ProvisioningToken) Name() string {
return "Ignition Provisioning Password"
}
12 changes: 10 additions & 2 deletions pkg/asset/manifests/openshift.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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{},
Expand All @@ -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 {
Expand Down Expand Up @@ -186,24 +190,28 @@ 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)

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 {
Expand Down
1 change: 1 addition & 0 deletions pkg/asset/manifests/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,6 @@ type baremetalTemplateData struct {

type openshiftTemplateData struct {
CloudCreds cloudCredsSecretData
IgnitionProvisioningToken string
Base64EncodedKubeadminPwHash string
}
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit f084994

Please sign in to comment.