* Add Virtual Kubelet provider for VIC Initial virtual kubelet provider for VMware VIC. This provider currently handles creating and starting of a pod VM via the VIC portlayer and persona server. Image store handling via the VIC persona server. This provider currently requires the feature/wolfpack branch of VIC. * Added pod stop and delete. Also added node capacity. Added the ability to stop and delete pod VMs via VIC. Also retrieve node capacity information from the VCH. * Cleanup and readme file Some file clean up and added a Readme.md markdown file for the VIC provider. * Cleaned up errors, added function comments, moved operation code 1. Cleaned up error handling. Set standard for creating errors. 2. Added method prototype comments for all interface functions. 3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder. * Add mocking code and unit tests for podcache, podcreator, and podstarter Used the unit test framework used in VIC to handle assertions in the provider's unit test. Mocking code generated using OSS project mockery, which is compatible with the testify assertion framework. * Vendored packages for the VIC provider Requires feature/wolfpack branch of VIC and a few specific commit sha of projects used within VIC. * Implementation of POD Stopper and Deleter unit tests (#4) * Updated files for initial PR
261 lines
6.7 KiB
Go
261 lines
6.7 KiB
Go
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// +build !windows,!darwin
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/vmware/govmomi/toolbox"
|
|
"github.com/vmware/govmomi/toolbox/vix"
|
|
"github.com/vmware/vic/pkg/trace"
|
|
"github.com/vmware/vic/pkg/vsphere/diag"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
)
|
|
|
|
// startCommand is the switch for the synthetic commands that are permitted within the appliance.
|
|
// This is not intended to allow arbitrary commands to be executed.
|
|
// returns:
|
|
// pid: toolbox ProcessManager Process id
|
|
// error
|
|
func startCommand(m *toolbox.ProcessManager, r *vix.StartProgramRequest) (int64, error) {
|
|
defer trace.End(trace.Begin(r.ProgramPath))
|
|
var p *toolbox.Process
|
|
|
|
switch r.ProgramPath {
|
|
case "enable-ssh":
|
|
p = toolbox.NewProcessFunc(func(ctx context.Context, args string) error {
|
|
err := enableSSH(args)
|
|
// #nosec: Errors unhandled.
|
|
_ = enableShell()
|
|
return err
|
|
})
|
|
case "passwd":
|
|
p = toolbox.NewProcessFunc(func(ctx context.Context, args string) error {
|
|
err := passwd(args)
|
|
// #nosec: Errors unhandled.
|
|
_ = enableShell()
|
|
return err
|
|
})
|
|
case "test-vc-api":
|
|
p = toolbox.NewProcessFunc(func(ctx context.Context, args string) error {
|
|
rc := diag.CheckAPIAvailability(args)
|
|
if rc == diag.VCStatusOK {
|
|
return nil
|
|
}
|
|
|
|
return &toolbox.ProcessError{
|
|
Err: errors.New(diag.UserReadableVCAPITestDescription(rc)),
|
|
ExitCode: int32(rc),
|
|
}
|
|
})
|
|
default:
|
|
return -1, os.ErrNotExist
|
|
}
|
|
|
|
return m.Start(r, p)
|
|
|
|
}
|
|
|
|
// enableShell changes the root shell from /bin/false to /bin/bash
|
|
// We try to ensure the password is not expired via chage, as chsh
|
|
// requires an unexpired password to succeed.
|
|
func enableShell() error {
|
|
defer trace.End(trace.Begin(""))
|
|
|
|
// if reset fails, try the rest anyway
|
|
// #nosec: Errors unhandled.
|
|
resetPasswdExpiry()
|
|
|
|
// #nosec: Subprocess launching should be audited
|
|
chsh := exec.Command("/bin/chsh", "-s", "/bin/bash", "root")
|
|
err := chsh.Start()
|
|
if err != nil {
|
|
err := fmt.Errorf("Failed to launch chsh: %s", err)
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
|
|
// ignore the error - it's likely raced with child reaper, we just want to make sure
|
|
// that it's exited by the time we pass this point
|
|
// #nosec: Errors unhandled.
|
|
chsh.Wait()
|
|
|
|
// confirm the change
|
|
file, err := os.Open("/etc/passwd")
|
|
if err != nil {
|
|
err := fmt.Errorf("Failed to open file to confirm change: %s", err)
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
|
|
reader := bufio.NewReader(file)
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
err := fmt.Errorf("Failed to read line from file to confirm change: %s", err)
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
|
|
// assert that first line is root
|
|
if !strings.HasPrefix(line, "root") {
|
|
err := fmt.Errorf("Expected line to start with root: %s", line)
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
|
|
// assert that first line is root
|
|
if !strings.HasSuffix(line, "/bin/bash\n") {
|
|
err := fmt.Errorf("Expected line to end with /bin/bash: %s", line)
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
|
|
log.Info("Attempted to enable the shell for root")
|
|
|
|
return nil
|
|
}
|
|
|
|
// passwd sets the password for the root user to that provided as an argument
|
|
func passwd(pass string) error {
|
|
defer trace.End(trace.Begin(""))
|
|
|
|
// #nosec: Subprocess launching should be audited
|
|
setPasswd := exec.Command("/sbin/chpasswd")
|
|
stdin, err := setPasswd.StdinPipe()
|
|
if err != nil {
|
|
err := fmt.Errorf("Failed to create stdin pipe for chpasswd: %s", err)
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
|
|
err = setPasswd.Start()
|
|
if err != nil {
|
|
err := fmt.Errorf("Failed to launch chpasswd: %s", err)
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
|
|
_, err = stdin.Write([]byte("root:" + pass))
|
|
|
|
// so that we're actively waiting when the process exits, or we'll race (and lose) to child reaper
|
|
go func() {
|
|
// #nosec: Errors unhandled.
|
|
setPasswd.Wait()
|
|
}()
|
|
|
|
err = stdin.Close()
|
|
if err != nil {
|
|
err := fmt.Errorf("Failed to close input to chpasswd: %s", err)
|
|
log.Error(err)
|
|
|
|
// fire and forget as we're already on error path
|
|
// #nosec: Errors unhandled.
|
|
setPasswd.Process.Kill()
|
|
|
|
return err
|
|
}
|
|
|
|
log.Info("Attempted to set the password for root")
|
|
|
|
return nil
|
|
}
|
|
|
|
// enableSSH receives a key as an argument
|
|
func enableSSH(key string) error {
|
|
defer trace.End(trace.Begin(""))
|
|
|
|
// basic sanity check for args - we don't bother validating it's a key
|
|
if len(key) != 0 {
|
|
err := os.MkdirAll("/root/.ssh", 0700)
|
|
if err != nil {
|
|
err := fmt.Errorf("unable to create path for keys: %s", err)
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
|
|
err = ioutil.WriteFile("/root/.ssh/authorized_keys", []byte(key), 0600)
|
|
if err != nil {
|
|
err := fmt.Errorf("unable to create authorized_keys: %s", err)
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
return startSSH()
|
|
}
|
|
|
|
// startSSH launches the sshd server
|
|
func startSSH() error {
|
|
// #nosec: Subprocess launching should be audited
|
|
c := exec.Command("/usr/bin/systemctl", "start", "sshd")
|
|
|
|
var b bytes.Buffer
|
|
c.Stdout = &b
|
|
c.Stderr = &b
|
|
|
|
if err := c.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
// because init is explicitly reaping child processes we cannot use simple
|
|
// exec commands to gather status
|
|
// #nosec: Errors unhandled.
|
|
_ = c.Wait()
|
|
log.Info("Attempted to start ssh service:\n %s", b)
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func resetPasswdExpiry() error {
|
|
defer trace.End(trace.Begin(""))
|
|
|
|
// add just enough time for the password not to be expired
|
|
// if the user wants more time they can actually change the password
|
|
// This will expire in at most 1 day, perhaps sooner depending on local time
|
|
// NB: Format is example based - that's the reference time format
|
|
expireDate := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
|
|
|
|
// #nosec: Subprocess launching should be audited
|
|
chage := exec.Command("/bin/chage", "-M", "1", "-d", expireDate, "root")
|
|
err := chage.Start()
|
|
if err != nil {
|
|
err := fmt.Errorf("Failed to launch chage: %s", err)
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
|
|
// ignore the error - it's likely raced with child reaper, we just want to make sure
|
|
// that it's exited by the time we pass this point
|
|
// #nosec: Errors unhandled.
|
|
chage.Wait()
|
|
|
|
log.Infof("Attempted reset of password expiry: /bin/chage -M 1 -d %s root", expireDate)
|
|
return nil
|
|
}
|