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  }