github.com/coreos/mantle@v0.13.0/platform/util.go (about) 1 // Copyright 2015 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package platform 16 17 import ( 18 "context" 19 "crypto/rand" 20 "crypto/rsa" 21 "fmt" 22 "os" 23 24 "golang.org/x/crypto/ssh" 25 "golang.org/x/crypto/ssh/terminal" 26 ) 27 28 // Manhole connects os.Stdin, os.Stdout, and os.Stderr to an interactive shell 29 // session on the Machine m. Manhole blocks until the shell session has ended. 30 // If os.Stdin does not refer to a TTY, Manhole returns immediately with a nil 31 // error. 32 func Manhole(m Machine) error { 33 fd := int(os.Stdin.Fd()) 34 if !terminal.IsTerminal(fd) { 35 return nil 36 } 37 38 tstate, _ := terminal.MakeRaw(fd) 39 defer terminal.Restore(fd, tstate) 40 41 client, err := m.SSHClient() 42 if err != nil { 43 return fmt.Errorf("SSH client failed: %v", err) 44 } 45 46 defer client.Close() 47 48 session, err := client.NewSession() 49 if err != nil { 50 return fmt.Errorf("SSH session failed: %v", err) 51 } 52 53 defer session.Close() 54 55 session.Stdin = os.Stdin 56 session.Stdout = os.Stdout 57 session.Stderr = os.Stderr 58 59 modes := ssh.TerminalModes{ 60 ssh.TTY_OP_ISPEED: 115200, 61 ssh.TTY_OP_OSPEED: 115200, 62 } 63 64 cols, lines, err := terminal.GetSize(int(os.Stdin.Fd())) 65 if err != nil { 66 return err 67 } 68 69 if err = session.RequestPty(os.Getenv("TERM"), lines, cols, modes); err != nil { 70 return fmt.Errorf("failed to request pseudo terminal: %s", err) 71 } 72 73 if err := session.Shell(); err != nil { 74 return fmt.Errorf("failed to start shell: %s", err) 75 } 76 77 if err := session.Wait(); err != nil { 78 return fmt.Errorf("failed to wait for session: %s", err) 79 } 80 81 return nil 82 } 83 84 // Enable SELinux on a machine (skip on machines without SELinux support) 85 func EnableSelinux(m Machine) error { 86 _, stderr, err := m.SSH("if type -P setenforce; then sudo setenforce 1; fi") 87 if err != nil { 88 return fmt.Errorf("Unable to enable SELinux: %s: %s", err, stderr) 89 } 90 return nil 91 } 92 93 // Reboots a machine, stopping ssh first. 94 // Afterwards run CheckMachine to verify the system is back and operational. 95 func StartReboot(m Machine) error { 96 // stop sshd so that commonMachineChecks will only work if the machine 97 // actually rebooted 98 out, stderr, err := m.SSH("sudo systemctl stop sshd.socket && sudo reboot") 99 if _, ok := err.(*ssh.ExitMissingError); ok { 100 // A terminated session is perfectly normal during reboot. 101 err = nil 102 } 103 if err != nil { 104 return fmt.Errorf("issuing reboot command failed: %s: %s: %s", out, err, stderr) 105 } 106 return nil 107 } 108 109 // RebootMachine will reboot a given machine, provided the machine's journal. 110 func RebootMachine(m Machine, j *Journal) error { 111 if err := StartReboot(m); err != nil { 112 return fmt.Errorf("machine %q failed to begin rebooting: %v", m.ID(), err) 113 } 114 return StartMachine(m, j) 115 } 116 117 // StartMachine will start a given machine, provided the machine's journal. 118 func StartMachine(m Machine, j *Journal) error { 119 if err := j.Start(context.TODO(), m); err != nil { 120 return fmt.Errorf("machine %q failed to start: %v", m.ID(), err) 121 } 122 if err := CheckMachine(context.TODO(), m); err != nil { 123 return fmt.Errorf("machine %q failed basic checks: %v", m.ID(), err) 124 } 125 if !m.RuntimeConf().NoEnableSelinux { 126 if err := EnableSelinux(m); err != nil { 127 return fmt.Errorf("machine %q failed to enable selinux: %v", m.ID(), err) 128 } 129 } 130 return nil 131 } 132 133 // GenerateFakeKey generates a SSH key pair, returns the public key, and 134 // discards the private key. This is useful for droplets that don't need a 135 // public key, since DO & Azure insists on requiring one. 136 func GenerateFakeKey() (string, error) { 137 rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) 138 if err != nil { 139 return "", err 140 } 141 sshKey, err := ssh.NewPublicKey(&rsaKey.PublicKey) 142 if err != nil { 143 return "", err 144 } 145 return string(ssh.MarshalAuthorizedKey(sshKey)), nil 146 }