Skip to content

Commit

Permalink
pass ssh keys from clusterconfig to machineconfig
Browse files Browse the repository at this point in the history
allow the installer to use the mco to distribute ssh keys. this
is the first step in adding the ability to update existing ssh keys.

closes installer issue #578
  • Loading branch information
kikisdeliveryservice committed Jan 4, 2019
1 parent 1268da7 commit a46fcad
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 9 deletions.
2 changes: 1 addition & 1 deletion lib/resourcemerge/machineconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func ensureControllerConfigSpec(modified *bool, existing *mcfgv1.ControllerConfi
setStringIfSet(modified, &existing.ClusterName, required.ClusterName)
setStringIfSet(modified, &existing.Platform, required.Platform)
setStringIfSet(modified, &existing.BaseDomain, required.BaseDomain)
setStringIfSet(modified, &existing.Platform, required.Platform)
setStringIfSet(modified, &existing.SSHKey, required.SSHKey)

setBytesIfSet(modified, &existing.EtcdCAData, required.EtcdCAData)
setBytesIfSet(modified, &existing.RootCAData, required.RootCAData)
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/machineconfiguration.openshift.io/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ type MCOConfigSpec struct {
Platform string `json:"platform"`

BaseDomain string `json:"baseDomain"`

SSHKey string `json:"sshKey"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
Expand Down Expand Up @@ -129,6 +131,9 @@ type ControllerConfigSpec struct {
// PullSecret is the default pull secret that needs to be installed
// on all machines.
PullSecret *corev1.ObjectReference `json:"pullSecret,omitempty"`

// Public SSH
SSHKey string `json:"sshKey"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
Expand Down
11 changes: 10 additions & 1 deletion pkg/controller/template/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func generateMachineConfigs(config *renderConfig, templateDir string) ([]*mcfgv1
}

cfgs := []*mcfgv1.MachineConfig{}

for _, info := range infos {
if !info.IsDir() {
glog.Infof("ignoring non-directory path %q", info.Name())
Expand All @@ -83,8 +84,17 @@ func generateMachineConfigsForRole(config *renderConfig, role string, path strin
if err != nil {
return nil, fmt.Errorf("failed to read dir %q: %v", path, err)
}
// for each role a machine config is created containing the sshauthorized keys to allow for ssh access
// ex: role = worker -> machine config "00-worker-ssh" created containing user core and ssh key
var tempIgnConfig ignv2_2types.Config
tempUser := ignv2_2types.PasswdUser{Name: "core", SSHAuthorizedKeys: []ignv2_2types.SSHAuthorizedKey{ignv2_2types.SSHAuthorizedKey(config.SSHKey)}}
tempIgnConfig.Passwd.Users = append(tempIgnConfig.Passwd.Users, tempUser)
sshConfigName := "00-" + role + "-ssh"
sshMachineConfigForRole := machineConfigFromIgnConfig(role, sshConfigName, &tempIgnConfig)

cfgs := []*mcfgv1.MachineConfig{}
cfgs = append(cfgs, sshMachineConfigForRole)

for _, info := range infos {
if !info.IsDir() {
glog.Infof("ignoring non-directory path %q", info.Name())
Expand Down Expand Up @@ -119,7 +129,6 @@ func generateMachineConfigForName(config *renderConfig, role, name, path string)

files := map[string]string{}
units := map[string]string{}

// walk all role dirs, with later ones taking precedence
for _, platformDir := range platformDirs {
// magic param
Expand Down
32 changes: 32 additions & 0 deletions pkg/controller/template/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,38 @@ func TestGenerateMachineConfigs(t *testing.T) {
}
}

func TestGenerateMachineConfigsSSH(t *testing.T) {
for _, config := range configs {
controllerConfig, err := controllerConfigFromFile(config)
if err != nil {
t.Fatalf("failed to get controllerconfig config: %v", err)
}

controllerConfig.Spec.SSHKey = "1234"
cfgs, err := generateMachineConfigs(&renderConfig{&controllerConfig.Spec, `{"dummy":"dummy"}`}, templateDir)

if err != nil {
t.Fatalf("failed to generate machine configs: %v", err)
}

if cfgs[0].Spec.Config.Passwd.Users[0].Name != "core" && cfgs[0].Spec.Config.Passwd.Users[0].SSHAuthorizedKeys[0] != "1234" {
t.Fatalf("Failed to create SSH machine config with user core and sshkey 1234, Got: %v", cfgs[0].Spec.Config.Passwd.Users[0])
}

if cfgs[0].ObjectMeta.Name != "00-master-ssh" {
t.Fatalf("SSH machine config named incorrectly. Expected: 00-master-ssh, Got: %v", cfgs[0].ObjectMeta.Name)
}

if cfgs[3].Spec.Config.Passwd.Users[0].Name != "core" && cfgs[3].Spec.Config.Passwd.Users[0].SSHAuthorizedKeys[0] != "1234" {
t.Fatalf("Failed to create SSH machine config with user core and sshkey 1234, Got: %v", cfgs[3].Spec.Config.Passwd.Users[0])
}

if cfgs[3].ObjectMeta.Name != "00-worker-ssh" {
t.Fatalf("SSH machine config named incorrectly. Expected: 00-worker-ssh, Got: %v", cfgs[3].ObjectMeta.Name)
}
}
}

func controllerConfigFromFile(path string) (*mcfgv1.ControllerConfig, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
Expand Down
58 changes: 56 additions & 2 deletions pkg/daemon/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ func (dn *Daemon) update(oldConfig, newConfig *mcfgv1.MachineConfig) error {
return err
}

if err = dn.updateSSHKeys(newConfig.Spec.Config.Passwd.Users); err != nil {
return err
}

// TODO: Change the logic to be clearer
// We need to skip draining of the node when we are running once
// and there is no cluster.
Expand Down Expand Up @@ -93,7 +97,9 @@ func (dn *Daemon) update(oldConfig, newConfig *mcfgv1.MachineConfig) error {
// we can only update machine configs that have changes to the files,
// directories, links, and systemd units sections of the included ignition
// config currently.

func (dn *Daemon) reconcilable(oldConfig, newConfig *mcfgv1.MachineConfig) *string {
glog.Info("Checking if configs are reconcilable")
// We skip out of reconcilable if there is no Kind and we are in runOnce mode. The
// reason is that there is a good chance a previous state is not available to match against.
if oldConfig.Kind == "" && dn.onceFrom != "" {
Expand Down Expand Up @@ -131,8 +137,25 @@ func (dn *Daemon) reconcilable(oldConfig, newConfig *mcfgv1.MachineConfig) *stri
// we don't currently configure groups or users in place. we can't fix it if
// something changed here.
if !reflect.DeepEqual(oldIgn.Passwd, newIgn.Passwd) {
msg := "Ignition passwd section contains changes"
return &msg
if !reflect.DeepEqual(oldIgn.Passwd.Groups, newIgn.Passwd.Groups) {
msg := "Ignition Passwd Groups section contains changes"
return &msg
}
// check if the prior config is empty and that this is the first time running.
// if so, the SSHKey from the cluster config and user "core" must be added to machine config,.
if !reflect.DeepEqual(oldIgn.Passwd.Users, newIgn.Passwd.Users) {
if len(oldIgn.Passwd.Users) == 0 && len(newIgn.Passwd.Users) == 1 {
if newIgn.Passwd.Users[0].Name == "core" && len(newIgn.Passwd.Users[0].SSHAuthorizedKeys) > 0 {
glog.Info("SSH Keys reconcilable")
} else {
msg := "Ignition passwd user section contains unsupported changes"
return &msg
}
}
} else {
msg := "Ignition passwd section contains unsupported changes"
return &msg
}
}

// Storage section
Expand Down Expand Up @@ -464,6 +487,37 @@ func getFileOwnership(file ignv2_2types.File) (int, int, error) {
return uid, gid, nil
}

// Update a given PasswdUser's SSHKey
func (dn *Daemon) updateSSHKeys(newUsers []ignv2_2types.PasswdUser) error {
// Keys should only be written to "/home/core/.ssh"
// Once Users are supported fully this should be writing to PasswdUser.HomeDir
if newUsers[0].Name != "core" {
// Double checking that we are only writing SSH Keys for user "core"
return fmt.Errorf("Expecting user core. Got %s instead", newUsers[0].Name)
}
sshDirPath := filepath.Join("/home", newUsers[0].Name, ".ssh")
// we are only dealing with the "core" User at this time, so only dealing with the first entry in Users[]
glog.Infof("Writing SSHKeys at %q:", sshDirPath)
if err := dn.fileSystemClient.MkdirAll(filepath.Dir(sshDirPath), os.FileMode(0600)); err != nil {
return fmt.Errorf("Failed to create directory %q: %v", filepath.Dir(sshDirPath), err)
}
glog.V(2).Infof("Created directory: %s", sshDirPath)

authkeypath := filepath.Join(sshDirPath, "authorized_keys")
var concatSSHKeys string
for _, k := range newUsers[0].SSHAuthorizedKeys {
concatSSHKeys = concatSSHKeys + string(k) + "\n"
}

if err := dn.fileSystemClient.WriteFile(authkeypath, []byte(concatSSHKeys), os.FileMode(0600)); err != nil {
return fmt.Errorf("Failed to write ssh key: %v", err)
}

glog.V(2).Infof("Wrote SSHKeys at %s", sshDirPath)

return nil
}

// updateOS updates the system OS to the one specified in newConfig
func (dn *Daemon) updateOS(oldConfig, newConfig *mcfgv1.MachineConfig) error {
if dn.OperatingSystem != MachineConfigDaemonOSRHCOS {
Expand Down
51 changes: 51 additions & 0 deletions pkg/daemon/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,54 @@ func TestReconcilable(t *testing.T) {
isReconcilable = d.reconcilable(oldConfig, newConfig)
checkReconcilableResults("raid", isReconcilable)
}

func TestUpdateSSHKeys(t *testing.T) {
// expectedError is the error we will use when expecting an error to return
expectedError := fmt.Errorf("broken")
// testClient is the NodeUpdaterClient mock instance that will front
// calls to update the host.
testClient := RpmOstreeClientMock{
GetBootedOSImageURLReturns: []GetBootedOSImageURLReturn{},
RunPivotReturns: []error{
// First run will return no error
nil,
// Second rrun will return our expected error
expectedError},
}
mockFS := &FsClientMock{MkdirAllReturns: []error{nil}, WriteFileReturns: []error{nil}}
// Create a Daemon instance with mocked clients
d := Daemon{
name: "nodeName",
OperatingSystem: MachineConfigDaemonOSRHCOS,
NodeUpdaterClient: testClient,
loginClient: nil, // set to nil as it will not be used within tests
client: fake.NewSimpleClientset(),
kubeClient: k8sfake.NewSimpleClientset(),
rootMount: "/",
bootedOSImageURL: "test",
fileSystemClient: mockFS,
}
// Set up machineconfigs that are identical except for SSH keys
tempUser := ignv2_2types.PasswdUser{Name: "core", SSHAuthorizedKeys: []ignv2_2types.SSHAuthorizedKey{"1234"}}

newMcfg := &mcfgv1.MachineConfig{
Spec: mcfgv1.MachineConfigSpec{
Config: ignv2_2types.Config{
Passwd: ignv2_2types.Passwd{
Users: []ignv2_2types.PasswdUser{tempUser},
},
},
},
}
err := d.updateSSHKeys(newMcfg.Spec.Config.Passwd.Users)
if err != nil {
t.Errorf("Expected no error. Got %s.", err)

}
// Until users are supported should not be writing keys for any user not named "core"
newMcfg.Spec.Config.Passwd.Users[0].Name = "not_core"
err = d.updateSSHKeys(newMcfg.Spec.Config.Passwd.Users)
if err == nil {
t.Errorf("Expected error, user is not core")
}
}
8 changes: 5 additions & 3 deletions pkg/operator/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import (

"github.com/ghodss/yaml"
"github.com/golang/glog"
configclientset "github.com/openshift/client-go/config/clientset/versioned"
installertypes "github.com/openshift/installer/pkg/types"

"k8s.io/api/core/v1"
apiextclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
apiextinformersv1beta1 "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1"
Expand All @@ -28,7 +27,9 @@ import (
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"


configclientset "github.com/openshift/client-go/config/clientset/versioned"
installertypes "github.com/openshift/installer/pkg/types"
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
mcfgclientset "github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned"
"github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned/scheme"
Expand Down Expand Up @@ -310,6 +311,7 @@ func getRenderConfig(mc *mcfgv1.MCOConfig, etcdCAData, rootCAData []byte, ps *v1
EtcdCAData: etcdCAData,
RootCAData: rootCAData,
PullSecret: ps,
SSHKey: mc.Spec.SSHKey,
}
return renderConfig{
TargetNamespace: mc.GetNamespace(),
Expand Down
3 changes: 1 addition & 2 deletions pkg/operator/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/apparentlymart/go-cidr/cidr"
"github.com/ghodss/yaml"
installertypes "github.com/openshift/installer/pkg/types"

mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
"github.com/openshift/machine-config-operator/pkg/operator/assets"
)
Expand Down Expand Up @@ -58,7 +57,6 @@ func discoverMCOConfig(f installConfigGetter) (*mcfgv1.MCOConfig, error) {
if err != nil {
return nil, err
}

dnsIP, err := clusterDNSIP(ic.Networking.ServiceCIDR.String())
if err != nil {
return nil, err
Expand All @@ -71,6 +69,7 @@ func discoverMCOConfig(f installConfigGetter) (*mcfgv1.MCOConfig, error) {
ClusterName: ic.ObjectMeta.Name,
Platform: platformFromInstallConfig(ic),
BaseDomain: ic.BaseDomain,
SSHKey: ic.SSHKey,
},
}, nil
}
Expand Down

0 comments on commit a46fcad

Please sign in to comment.