14807: Allow driver to specify SSH username.
authorTom Clegg <tclegg@veritasgenetics.com>
Wed, 13 Feb 2019 06:42:40 +0000 (01:42 -0500)
committerTom Clegg <tclegg@veritasgenetics.com>
Fri, 15 Feb 2019 21:13:19 +0000 (16:13 -0500)
Use a non-root account with passwordless sudo on a provider (Azure)
that can easily set that up, but can't easily set up direct root
login.

Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg@veritasgenetics.com>

lib/cloud/azure/azure.go
lib/cloud/azure/azure_test.go
lib/cloud/interfaces.go
lib/dispatchcloud/ssh_executor/executor.go
lib/dispatchcloud/ssh_executor/executor_test.go
lib/dispatchcloud/test/ssh_service.go
lib/dispatchcloud/test/stub_driver.go
lib/dispatchcloud/worker/pool.go
lib/dispatchcloud/worker/worker.go

index d745e7e54d27473147e3f214a213a4514a596131..d8d23d1bc3f9de178d644838cf8c3a2c08f9ee62 100644 (file)
@@ -47,6 +47,7 @@ type azureInstanceSetConfig struct {
        StorageAccount               string
        BlobContainer                string
        DeleteDanglingResourcesAfter arvados.Duration
+       AdminUsername                string
 }
 
 type virtualMachinesClientWrapper interface {
@@ -372,7 +373,7 @@ func (az *azureInstanceSet) Create(
                name)
 
        customData := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`#!/bin/sh
-echo '%s-%s' > /home/crunch/node-token`, name, newTags["node-token"])))
+echo '%s-%s' > '/home/%s/node-token'`, name, newTags["node-token"], az.azconfig.AdminUsername)))
 
        vmParameters := compute.VirtualMachine{
                Location: &az.azconfig.Location,
@@ -406,13 +407,13 @@ echo '%s-%s' > /home/crunch/node-token`, name, newTags["node-token"])))
                        },
                        OsProfile: &compute.OSProfile{
                                ComputerName:  &name,
-                               AdminUsername: to.StringPtr("crunch"),
+                               AdminUsername: to.StringPtr(az.azconfig.AdminUsername),
                                LinuxConfiguration: &compute.LinuxConfiguration{
                                        DisablePasswordAuthentication: to.BoolPtr(true),
                                        SSH: &compute.SSHConfiguration{
                                                PublicKeys: &[]compute.SSHPublicKey{
-                                                       compute.SSHPublicKey{
-                                                               Path:    to.StringPtr("/home/crunch/.ssh/authorized_keys"),
+                                                       {
+                                                               Path:    to.StringPtr("/home/" + az.azconfig.AdminUsername + "/.ssh/authorized_keys"),
                                                                KeyData: to.StringPtr(string(ssh.MarshalAuthorizedKey(publicKey))),
                                                        },
                                                },
@@ -634,6 +635,10 @@ func (ai *azureInstance) Address() string {
        return *(*ai.nic.IPConfigurations)[0].PrivateIPAddress
 }
 
+func (ai *azureInstance) RemoteUser() string {
+       return ai.provider.azconfig.AdminUsername
+}
+
 func (ai *azureInstance) VerifyHostKey(receivedKey ssh.PublicKey, client *ssh.Client) error {
        ai.provider.stopWg.Add(1)
        defer ai.provider.stopWg.Done()
@@ -660,7 +665,7 @@ func (ai *azureInstance) VerifyHostKey(receivedKey ssh.PublicKey, client *ssh.Cl
                return err
        }
 
-       nodetokenbytes, err := sess.Output("cat /home/crunch/node-token")
+       nodetokenbytes, err := sess.Output("cat /home/" + ai.provider.azconfig.AdminUsername + "/node-token")
        if err != nil {
                return err
        }
index 850a3fb4270fd9c5da7b7829e9f00fa27c630a49..859765ccf02d294c76bb1ab7d195b53bad246d0a 100644 (file)
@@ -25,6 +25,7 @@
 //      StorageAccount: example
 //      BlobContainer: vhds
 //      DeleteDanglingResourcesAfter: 20s
+//      AdminUsername: crunch
 
 package azure
 
index 46a2c1682404ec067bc7c332a77c469f0b353d57..3f03b650b4100f7dfe89126cada79b921681ea20 100644 (file)
@@ -63,6 +63,9 @@ type ExecutorTarget interface {
        // unknown while instance is booting.
        Address() string
 
+       // Remote username to send during SSH authentication.
+       RemoteUser() string
+
        // Return nil if the given public key matches the instance's
        // SSH server key. If the provided Dialer is not nil,
        // VerifyHostKey can use it to make outgoing network
index d0fb54c54cd932df806e0129a0f92d78cd3a9999..646e90f31ea8698f6b8d0df3727120cac31f4b64 100644 (file)
@@ -38,6 +38,7 @@ func New(t cloud.ExecutorTarget) *Executor {
 type Executor struct {
        target     cloud.ExecutorTarget
        targetPort string
+       targetUser string
        signers    []ssh.Signer
        mtx        sync.RWMutex // controls access to instance after creation
 
@@ -189,7 +190,7 @@ func (exr *Executor) setupSSHClient() (*ssh.Client, error) {
        }
        var receivedKey ssh.PublicKey
        client, err := ssh.Dial("tcp", addr, &ssh.ClientConfig{
-               User: "root",
+               User: target.RemoteUser(),
                Auth: []ssh.AuthMethod{
                        ssh.PublicKeys(exr.signers...),
                },
index f8565b4a710705c1367a54f9a954a753ff1d2715..e7c023586b4bb3c09ac8968c35c2cc3f1ed01ee2 100644 (file)
@@ -73,6 +73,7 @@ func (s *ExecutorSuite) TestBadHostKey(c *check.C) {
                                return 0
                        },
                        HostKey:        hostpriv,
+                       AuthorizedUser: "username",
                        AuthorizedKeys: []ssh.PublicKey{clientpub},
                },
        }
@@ -121,6 +122,7 @@ func (s *ExecutorSuite) TestExecute(c *check.C) {
                                        return uint32(exitcode)
                                },
                                HostKey:        hostpriv,
+                               AuthorizedUser: "username",
                                AuthorizedKeys: []ssh.PublicKey{clientpub},
                        },
                }
index ed5995f4c5f0faa84356c01f2777d1e0a366cbc1..5fab9572a953858fcc0f9aff7abc4886f5a54c8d 100644 (file)
@@ -39,6 +39,7 @@ type SSHExecFunc func(env map[string]string, command string, stdin io.Reader, st
 type SSHService struct {
        Exec           SSHExecFunc
        HostKey        ssh.Signer
+       AuthorizedUser string
        AuthorizedKeys []ssh.PublicKey
 
        listener net.Listener
@@ -64,6 +65,11 @@ func (ss *SSHService) Address() string {
        return ln.Addr().String()
 }
 
+// RemoteUser returns the username that will be accepted.
+func (ss *SSHService) RemoteUser() string {
+       return ss.AuthorizedUser
+}
+
 // Close shuts down the server and releases resources. Established
 // connections are unaffected.
 func (ss *SSHService) Close() {
index 4a88bfab141d511860dad811befcd376245fb615..917853cf83f8b996a557a9ff1e7afc83f88c5234 100644 (file)
@@ -126,6 +126,7 @@ func (sis *StubInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID,
        }
        svm.SSHService = SSHService{
                HostKey:        sis.driver.HostKey,
+               AuthorizedUser: "root",
                AuthorizedKeys: ak,
                Exec:           svm.Exec,
        }
@@ -332,6 +333,10 @@ func (si stubInstance) Address() string {
        return si.addr
 }
 
+func (si stubInstance) RemoteUser() string {
+       return si.svm.SSHService.AuthorizedUser
+}
+
 func (si stubInstance) Destroy() error {
        sis := si.svm.sis
        if sis.driver.HoldCloudOps {
index e6b50629892e62c250ebcf05ba5bb226daec1ce1..df37326a080de92e4d03eab3bd3dad17cf8bf200 100644 (file)
@@ -485,7 +485,11 @@ func (wp *Pool) kill(wkr *worker, uuid string) {
                "Instance":      wkr.instance,
        })
        logger.Debug("killing process")
-       stdout, stderr, err := wkr.executor.Execute(nil, "crunch-run --kill 15 "+uuid, nil)
+       cmd := "crunch-run --kill 15 " + uuid
+       if u := wkr.instance.RemoteUser(); u != "root" {
+               cmd = "sudo " + cmd
+       }
+       stdout, stderr, err := wkr.executor.Execute(nil, cmd, nil)
        if err != nil {
                logger.WithFields(logrus.Fields{
                        "stderr": string(stderr),
index d0810f7a8ad8597a9e6c4465ff3492edc796bfd9..fbb6981ca930fb1bcba9ad9c97d5d29daf37ade0 100644 (file)
@@ -101,10 +101,14 @@ func (wkr *worker) startContainer(ctr arvados.Container) {
        wkr.starting[ctr.UUID] = struct{}{}
        wkr.state = StateRunning
        go func() {
+               cmd := "crunch-run --detach '" + ctr.UUID + "'"
                stdin := bytes.NewBufferString(fmt.Sprintf("export %s=%q\nexport %s=%q\n",
                        "ARVADOS_API_HOST", wkr.wp.arvClient.APIHost,
                        "ARVADOS_API_TOKEN", wkr.wp.arvClient.AuthToken))
-               cmd := "source /dev/stdin; crunch-run --detach '" + ctr.UUID + "'"
+               if u := wkr.instance.RemoteUser(); u != "root" {
+                       cmd = "sudo -E " + cmd
+               }
+               cmd = "source /dev/stdin; " + cmd
                stdout, stderr, err := wkr.executor.Execute(nil, cmd, stdin)
                wkr.mtx.Lock()
                defer wkr.mtx.Unlock()
@@ -325,6 +329,9 @@ func (wkr *worker) probeAndUpdate() {
 
 func (wkr *worker) probeRunning() (running []string, ok bool) {
        cmd := "crunch-run --list"
+       if u := wkr.instance.RemoteUser(); u != "root" {
+               cmd = "sudo " + cmd
+       }
        stdout, stderr, err := wkr.executor.Execute(nil, cmd, nil)
        if err != nil {
                wkr.logger.WithFields(logrus.Fields{