github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/stage1/enter_kvm/enter_kvm.go (about) 1 // Copyright 2015 The rkt Authors 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 main 16 17 import ( 18 "errors" 19 "flag" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "os/exec" 25 "os/user" 26 "path/filepath" 27 "syscall" 28 29 "github.com/coreos/rkt/common" 30 "github.com/coreos/rkt/networking/netinfo" 31 "github.com/coreos/rkt/pkg/lock" 32 rktlog "github.com/coreos/rkt/pkg/log" 33 "github.com/hashicorp/errwrap" 34 ) 35 36 const ( 37 kvmSettingsDir = "/var/lib/rkt-stage1-kvm" 38 kvmPrivateKeyFilename = "ssh_kvm_key" 39 // TODO: overwrite below default by environment value + generate .socket unit just before pod start 40 kvmSSHPort = "122" // hardcoded value in .socket file 41 ) 42 43 var ( 44 debug bool 45 podPid string 46 appName string 47 sshPath string 48 u, _ = user.Current() 49 log *rktlog.Logger 50 diag *rktlog.Logger 51 ) 52 53 // fileAccessible checks if the given path exists and is a regular file 54 func fileAccessible(path string) bool { 55 if info, err := os.Stat(path); err == nil { 56 return info.Mode().IsRegular() 57 } 58 return false 59 } 60 61 func sshPrivateKeyPath() string { 62 return filepath.Join(kvmSettingsDir, kvmPrivateKeyFilename) 63 } 64 65 func sshPublicKeyPath() string { 66 return sshPrivateKeyPath() + ".pub" 67 } 68 69 // generateKeyPair calls ssh-keygen with private key location for key generation purpose 70 func generateKeyPair(private string) error { 71 out, err := exec.Command( 72 "ssh-keygen", 73 "-q", // silence 74 "-t", "dsa", // type 75 "-b", "1024", // length in bits 76 "-f", private, // output file 77 "-N", "", // no passphrase 78 ).Output() 79 if err != nil { 80 // out is in form of bytes buffer and we have to turn it into slice ending on first \0 occurrence 81 return fmt.Errorf("error in keygen time. ret_val: %v, output: %v", err, string(out[:])) 82 } 83 return nil 84 } 85 86 func ensureKeysExistOnHost() error { 87 private, public := sshPrivateKeyPath(), sshPublicKeyPath() 88 if !fileAccessible(private) || !fileAccessible(public) { 89 if err := os.MkdirAll(kvmSettingsDir, 0700); err != nil { 90 return err 91 } 92 93 if err := generateKeyPair(private); err != nil { 94 return err 95 } 96 } 97 return nil 98 } 99 100 func ensureAuthorizedKeysExist(keyDirPath string) error { 101 fout, err := os.OpenFile( 102 filepath.Join(keyDirPath, "/authorized_keys"), 103 os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 104 0600, 105 ) 106 if err != nil { 107 return err 108 } 109 defer fout.Close() 110 111 fin, err := os.Open(sshPublicKeyPath()) 112 if err != nil { 113 return err 114 } 115 defer fin.Close() 116 117 if _, err := io.Copy(fout, fin); err != nil { 118 return err 119 } 120 return fout.Sync() 121 } 122 123 func ensureKeysExistInPod(workDir string) error { 124 destRootfs := common.Stage1RootfsPath(workDir) 125 keyDirPath := filepath.Join(destRootfs, u.HomeDir, ".ssh") 126 if err := os.MkdirAll(keyDirPath, 0700); err != nil { 127 return err 128 } 129 return ensureAuthorizedKeysExist(keyDirPath) 130 } 131 132 func kvmCheckSSHSetup(workDir string) error { 133 if err := ensureKeysExistOnHost(); err != nil { 134 return err 135 } 136 return ensureKeysExistInPod(workDir) 137 } 138 139 func init() { 140 flag.BoolVar(&debug, "debug", false, "Run in debug mode") 141 flag.StringVar(&podPid, "pid", "", "podPID") 142 flag.StringVar(&appName, "appname", "", "application to use") 143 144 log, diag, _ = rktlog.NewLogSet("kvm", false) 145 146 var err error 147 if sshPath, err = exec.LookPath("ssh"); err != nil { 148 log.FatalE("cannot find 'ssh' binary in PATH", err) 149 } 150 } 151 152 func getPodDefaultIP(workDir string) (string, error) { 153 // get pod lock 154 l, err := lock.NewLock(workDir, lock.Dir) 155 if err != nil { 156 return "", err 157 } 158 159 // get file descriptor for lock 160 fd, err := l.Fd() 161 if err != nil { 162 return "", err 163 } 164 165 // use this descriptor as method of reading pod network configuration 166 nets, err := netinfo.LoadAt(fd) 167 if err != nil { 168 return "", err 169 } 170 // kvm flavored container must have at first position default vm<->host network 171 if len(nets) == 0 { 172 return "", fmt.Errorf("pod has no configured networks") 173 } 174 175 for _, net := range nets { 176 if net.NetName == "default" || net.NetName == "default-restricted" { 177 return net.IP.String(), nil 178 } 179 } 180 181 return "", fmt.Errorf("pod has no default network!") 182 } 183 184 func getAppexecArgs() []string { 185 // Documentation/devel/stage1-implementors-guide.md#arguments-1 186 // also from ../enter/enter.c 187 args := []string{ 188 "/appexec", 189 fmt.Sprintf("/opt/stage2/%s/rootfs", appName), 190 "/", // as in ../enter/enter.c - this should be app.WorkingDirectory 191 fmt.Sprintf("/rkt/env/%s", appName), 192 u.Uid, 193 u.Gid, 194 } 195 return append(args, flag.Args()...) 196 } 197 198 func execSSH() error { 199 workDir, err := os.Getwd() 200 if err != nil { 201 return errwrap.Wrap(errors.New("cannot get working directory"), err) 202 } 203 204 podDefaultIP, err := getPodDefaultIP(workDir) 205 if err != nil { 206 return errwrap.Wrap(errors.New("cannot load networking configuration"), err) 207 } 208 209 // escape from running pod directory into base directory 210 if err = os.Chdir("../../.."); err != nil { 211 return errwrap.Wrap(errors.New("cannot change directory to rkt work directory"), err) 212 } 213 214 if err := kvmCheckSSHSetup(workDir); err != nil { 215 return errwrap.Wrap(errors.New("error setting up ssh keys"), err) 216 } 217 218 // prepare args for ssh invocation 219 keyFile := sshPrivateKeyPath() 220 args := []string{ 221 "ssh", 222 "-t", // use tty 223 "-i", keyFile, // use keyfile 224 "-l", u.Username, // login as user 225 "-p", kvmSSHPort, // port to connect 226 "-o", "StrictHostKeyChecking=no", // do not check changing host keys 227 "-o", "UserKnownHostsFile=/dev/null", // do not add host key to default knownhosts file 228 "-o", "LogLevel=quiet", // do not log minor informations 229 podDefaultIP, 230 } 231 args = append(args, getAppexecArgs()...) 232 233 // this should not return in case of success 234 err = syscall.Exec(sshPath, args, os.Environ()) 235 return errwrap.Wrap(errors.New("cannot exec to ssh"), err) 236 } 237 238 func main() { 239 flag.Parse() 240 241 log.SetDebug(debug) 242 diag.SetDebug(debug) 243 244 if !debug { 245 diag.SetOutput(ioutil.Discard) 246 } 247 248 if appName == "" { 249 log.Fatal("--appname not set to correct value") 250 } 251 252 // execSSH should return only with error 253 log.Error(execSSH()) 254 os.Exit(2) 255 }