Skip to content

Commit

Permalink
Merge pull request #164 from kikisdeliveryservice/pass-mco-keys
Browse files Browse the repository at this point in the history
Pass ssh keys from clusterconfig to machineconfig
  • Loading branch information
openshift-merge-robot authored Jan 7, 2019
2 parents 04b1d6a + a46fcad commit 7773bac
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 @@ -130,6 +132,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 @@ -91,6 +91,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 @@ -132,7 +136,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 @@ -170,8 +176,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 @@ -503,6 +526,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 7773bac

Please sign in to comment.