VMware vSphere Integrated Containers provider (#206)

* 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
This commit is contained in:
Loc Nguyen
2018-06-04 15:41:32 -07:00
committed by Ria Bhatia
parent 98a111e8b7
commit 513cebe7b7
6296 changed files with 1123685 additions and 8 deletions

723
vendor/github.com/vmware/vic/cmd/tether/attach.go generated vendored Normal file
View File

@@ -0,0 +1,723 @@
// Copyright 2016 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.
package main
import (
"context"
"errors"
"fmt"
"net"
"sync"
"sync/atomic"
"time"
log "github.com/Sirupsen/logrus"
"golang.org/x/crypto/ssh"
"github.com/vmware/vic/lib/migration/feature"
"github.com/vmware/vic/lib/tether"
"github.com/vmware/vic/lib/tether/msgs"
"github.com/vmware/vic/pkg/serial"
"github.com/vmware/vic/pkg/trace"
)
const (
attachChannelType = "attach"
)
// server is the singleton attachServer for the tether - there can be only one
// as the backchannel line protocol may not provide multiplexing of connections
var server AttachServer
var once sync.Once
type AttachServer interface {
tether.Extension
start() error
stop() error
}
// config is a struct that holds Sessions and Execs
type config struct {
Key []byte
Sessions map[string]*tether.SessionConfig
Execs map[string]*tether.SessionConfig
}
type attachServerSSH struct {
// serializes data access for exported functions
m sync.Mutex
// conn is the underlying net.Conn which carries SSH
// held directly as it is how we stop the attach server
conn struct {
sync.Mutex
conn net.Conn
}
// we pass serverConn to the channelMux goroutine so we need to lock it
serverConn struct {
sync.Mutex
*ssh.ServerConn
}
// extension local copy of the bits of config important to attach
config config
sshConfig *ssh.ServerConfig
enabled int32
// Cancelable context and its cancel func. Used for resolving the deadlock
// between run() and stop()
ctx context.Context
cancel context.CancelFunc
// INTERNAL: must set by testAttachServer only
testing bool
}
// NewAttachServerSSH either creates a new instance or returns the initialized one
func NewAttachServerSSH() AttachServer {
once.Do(func() {
// create a cancelable context and assign it to the CancelFunc
// it isused for resolving the deadlock between run() and stop()
// it has a Background parent as we don't want timeouts here,
// otherwise we may start leaking goroutines in the handshake code
ctx, cancel := context.WithCancel(context.Background())
server = &attachServerSSH{
ctx: ctx,
cancel: cancel,
}
})
return server
}
// Reload - tether.Extension implementation
func (t *attachServerSSH) Reload(tconfig *tether.ExecutorConfig) error {
defer trace.End(trace.Begin("attach reload"))
t.m.Lock()
defer t.m.Unlock()
// We copy this stuff so that we're not referencing the direct config
// structure if/while it's being updated.
// The subelements generally have locks or updated in single assignment
t.config.Key = tconfig.Key
t.config.Sessions = make(map[string]*tether.SessionConfig)
for k, v := range tconfig.Sessions {
t.config.Sessions[k] = v
}
t.config.Execs = make(map[string]*tether.SessionConfig)
for k, v := range tconfig.Execs {
t.config.Execs[k] = v
}
err := server.start()
if err != nil {
detail := fmt.Sprintf("unable to start attach server: %s", err)
log.Error(detail)
return errors.New(detail)
}
return nil
}
// Enable sets the enabled to true
func (t *attachServerSSH) Enable() {
atomic.StoreInt32(&t.enabled, 1)
}
// Disable sets the enabled to false
func (t *attachServerSSH) Disable() {
atomic.StoreInt32(&t.enabled, 0)
}
// Enabled returns whether the enabled is true
func (t *attachServerSSH) Enabled() bool {
return atomic.LoadInt32(&t.enabled) == 1
}
func (t *attachServerSSH) Start() error {
defer trace.End(trace.Begin(""))
return nil
}
// Stop needed for tether.Extensions interface
func (t *attachServerSSH) Stop() error {
defer trace.End(trace.Begin("stop attach server"))
t.m.Lock()
defer t.m.Unlock()
// calling server.start not t.start so that test impl gets invoked
return server.stop()
}
func (t *attachServerSSH) reload() error {
t.serverConn.Lock()
defer t.serverConn.Unlock()
// push the exec'ed session ids to the portlayer
if t.serverConn.ServerConn != nil {
msg := msgs.ContainersMsg{
IDs: t.sessions(false),
}
payload := msg.Marshal()
ok, _, err := t.serverConn.SendRequest(msgs.ContainersReq, true, payload)
if !ok || err != nil {
return fmt.Errorf("failed to send container ids: %s, %t", err, ok)
}
}
return nil
}
func (t *attachServerSSH) start() error {
defer trace.End(trace.Begin("start attach server"))
// if we come here while enabled, reload
if t.Enabled() {
log.Debugf("Start called while enabled, reloading")
if err := t.reload(); err != nil {
log.Warn(err)
}
return nil
}
// don't assume that the key hasn't changed
pkey, err := ssh.ParsePrivateKey([]byte(t.config.Key))
if err != nil {
detail := fmt.Sprintf("failed to load key for attach: %s", err)
log.Error(detail)
return errors.New(detail)
}
// An SSH server is represented by a ServerConfig, which holds
// certificate details and handles authentication of ServerConns.
// TODO: update this with generated credentials for the appliance
t.sshConfig = &ssh.ServerConfig{
PublicKeyCallback: func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
if c.User() == "daemon" {
return &ssh.Permissions{}, nil
}
return nil, fmt.Errorf("expected daemon user")
},
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
if c.User() == "daemon" {
return &ssh.Permissions{}, nil
}
return nil, fmt.Errorf("expected daemon user")
},
NoClientAuth: true,
}
t.sshConfig.AddHostKey(pkey)
// enable the server and start it
t.Enable()
go t.run()
return nil
}
// stop is not thread safe with start
func (t *attachServerSSH) stop() error {
defer trace.End(trace.Begin("stop attach server"))
if t == nil {
err := fmt.Errorf("attach server is not configured")
log.Error(err)
return err
}
if !t.Enabled() {
err := fmt.Errorf("attach server is not enabled")
log.Error(err)
return err
}
// disable the server
t.Disable()
// This context is used by backchannel only. We need to cancel it before
// trying to obtain the following lock so that backchannel interrupts the
// underlying Read call by calling Close on it.
// The lock is held by backchannel's caller and not released until it returns
log.Debugf("Canceling AttachServer's context")
t.cancel()
t.conn.Lock()
if t.conn.conn != nil {
log.Debugf("Close called again on rawconn - squashing")
// #nosec: Errors unhandled.
t.conn.conn.Close()
t.conn.conn = nil
}
t.conn.Unlock()
return nil
}
func backchannel(ctx context.Context, conn net.Conn) error {
defer trace.End(trace.Begin("establish tether backchannel"))
// used for shutting down the goroutine cleanly otherwise we leak a goroutine for every successful return from this function
done := make(chan struct{})
// HACK: currently RawConn dosn't implement timeout so throttle the spinning
// it does implement the Timeout methods so the intermediary code can be written
// to support it, but they are stub implementation in rawconn impl.
// This needs to tick *faster* than the ticker in connection.go on the
// portlayer side. The PL sends the first syn and if this isn't waiting,
// alignment will take a few rounds (or it may never happen).
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
// We run this in a separate goroutine because HandshakeServer
// calls a Read on rawconn which is a blocking call which causes
// the caller to block as well so this is the only way to cancel.
// Calling Close() will unblock us and on the next tick we will
// return ctx.Err()
go func() {
select {
case <-ctx.Done():
conn.Close()
case <-done:
return
}
}()
for {
select {
case <-ticker.C:
if ctx.Err() != nil {
return ctx.Err()
}
deadline, ok := ctx.Deadline()
if ok {
conn.SetReadDeadline(deadline)
}
err := serial.HandshakeServer(conn)
if err == nil {
conn.SetReadDeadline(time.Time{})
close(done)
return nil
}
switch et := err.(type) {
case *serial.HandshakeError:
log.Debugf("HandshakeServer: %v", et)
default:
log.Errorf("HandshakeServer: %v", err)
}
}
}
}
func (t *attachServerSSH) establish() error {
var err error
// we hold the t.conn.Lock during the scope of this function
t.conn.Lock()
defer t.conn.Unlock()
// tests are passing their own connections so do not create connections when testing is set
if !t.testing {
// close the connection if required
if t.conn.conn != nil {
// #nosec: Errors unhandled.
t.conn.conn.Close()
t.conn.conn = nil
}
t.conn.conn, err = rawConnectionFromSerial()
if err != nil {
detail := fmt.Errorf("failed to create raw connection: %s", err)
log.Error(detail)
return detail
}
} else {
// A series of unfortunate events can lead calling backchannel with nil when we run unit tests.
// https://github.com/vmware/vic/pull/5327#issuecomment-305619860
// This check is here to handle that
if t.conn.conn == nil {
return fmt.Errorf("nil connection")
}
}
// wait for backchannel to establish
err = backchannel(t.ctx, t.conn.conn)
if err != nil {
detail := fmt.Errorf("failed to establish backchannel: %s", err)
log.Error(detail)
return detail
}
return nil
}
func (t *attachServerSSH) cleanup() {
t.serverConn.Lock()
defer t.serverConn.Unlock()
log.Debugf("cleanup on connection")
if t.serverConn.ServerConn != nil {
log.Debugf("closing underlying connection")
t.serverConn.Close()
t.serverConn.ServerConn = nil
}
}
// run should not be called directly, but via start
// run will establish an ssh server listening on the backchannel
func (t *attachServerSSH) run() error {
defer trace.End(trace.Begin("main attach server loop"))
var established bool
var chans <-chan ssh.NewChannel
var reqs <-chan *ssh.Request
var err error
// main run loop
for t.Enabled() {
t.serverConn.Lock()
established = t.serverConn.ServerConn != nil
t.serverConn.Unlock()
// keep waiting for the connection to establish
for !established && t.Enabled() {
log.Infof("Trying to establish a connection")
if err := t.establish(); err != nil {
log.Error(err)
continue
}
// create the SSH server using underlying t.conn
t.serverConn.Lock()
t.serverConn.ServerConn, chans, reqs, err = ssh.NewServerConn(t.conn.conn, t.sshConfig)
if err != nil {
detail := fmt.Errorf("failed to establish ssh handshake: %s", err)
log.Error(detail)
}
established = t.serverConn.ServerConn != nil
t.serverConn.Unlock()
}
// Global requests
go t.globalMux(reqs, t.cleanup)
log.Infof("Ready to service attach requests")
// Service the incoming channels
for attachchan := range chans {
// The only channel type we'll support is attach
if attachchan.ChannelType() != attachChannelType {
detail := fmt.Sprintf("unknown channel type %s", attachchan.ChannelType())
attachchan.Reject(ssh.UnknownChannelType, detail)
log.Error(detail)
continue
}
// check we have a Session matching the requested ID
bytes := attachchan.ExtraData()
if bytes == nil {
detail := "attach channel requires ID in ExtraData"
attachchan.Reject(ssh.Prohibited, detail)
log.Error(detail)
continue
}
sessionid := string(bytes)
s, oks := t.config.Sessions[sessionid]
e, oke := t.config.Execs[sessionid]
if !oks && !oke {
detail := fmt.Sprintf("session %s is invalid", sessionid)
attachchan.Reject(ssh.Prohibited, detail)
log.Error(detail)
continue
}
// we have sessionid
session := s
if oke {
session = e
}
// session is potentially blocked in launch until we've got the unblock message, so we cannot lock it.
// check that session is valid
// The detail remains concise as it'll eventually make its way to the user
if session.Started != "" && session.Started != "true" {
detail := fmt.Sprintf("launch failed with: %s", session.Started)
attachchan.Reject(ssh.Prohibited, detail)
log.Error(detail)
continue
}
if session.StopTime != 0 {
detail := fmt.Sprintf("process finished with exit code: %d", session.ExitStatus)
attachchan.Reject(ssh.Prohibited, detail)
log.Error(detail)
continue
}
channel, requests, err := attachchan.Accept()
if err != nil {
detail := fmt.Sprintf("could not accept channel: %s", err)
log.Errorf(detail)
continue
}
// bind the channel to the Session
log.Debugf("binding reader/writers for channel for %s", sessionid)
log.Debugf("Adding [%p] to Outwriter", channel)
session.Outwriter.Add(channel)
log.Debugf("Adding [%p] to Reader", channel)
session.Reader.Add(channel)
// cleanup on detach from the session
cleanup := func() {
log.Debugf("Cleanup on detach from the session")
log.Debugf("Removing [%p] from Outwriter", channel)
session.Outwriter.Remove(channel)
log.Debugf("Removing [%p] from Reader", channel)
session.Reader.Remove(channel)
channel.Close()
}
detach := cleanup
// tty's merge stdout and stderr so we don't bind an additional reader in that case but we need to do so for non-tty
if !session.Tty {
// persist the value as we end up with different values each time we access it
stderr := channel.Stderr()
log.Debugf("Adding [%p] to Errwriter", stderr)
session.Errwriter.Add(stderr)
detach = func() {
log.Debugf("Cleanup on detach from the session (non-tty)")
log.Debugf("Removing [%p] from Errwriter", stderr)
session.Errwriter.Remove(stderr)
cleanup()
}
}
log.Debugf("reader/writers bound for channel for %s", sessionid)
go t.channelMux(requests, session, detach)
}
log.Info("Incoming attach channel closed")
}
return nil
}
func (t *attachServerSSH) sessions(all bool) []string {
defer trace.End(trace.Begin(""))
var keys []string
// this iterates the local copies of the sessions maps
// so we don't need to care whether they're initialized or not
// as extension reload comes after that point
// whether include sessions or not
if all {
for k, v := range t.config.Sessions {
if v.Active && v.StopTime == 0 {
keys = append(keys, k)
}
}
}
for k, v := range t.config.Execs {
// skip those that have had launch errors
if v.Active && v.StopTime == 0 && (v.Started == "" || v.Started == "true") {
keys = append(keys, k)
}
}
log.Debugf("Returning %d keys", len(keys))
return keys
}
func (t *attachServerSSH) globalMux(in <-chan *ssh.Request, cleanup func()) {
defer trace.End(trace.Begin("attach server global request handler"))
// cleanup function passed by the caller
defer cleanup()
// for the actions after we process the request
var pendingFn func()
for req := range in {
var payload []byte
ok := true
log.Infof("received global request type %v", req.Type)
switch req.Type {
case msgs.ContainersReq:
msg := msgs.ContainersMsg{
IDs: t.sessions(true),
}
payload = msg.Marshal()
case msgs.VersionReq:
msg := msgs.VersionMsg{
Version: feature.MaxPluginVersion - 1,
}
payload = msg.Marshal()
default:
ok = false
payload = []byte("unknown global request type: " + req.Type)
}
log.Debugf("Returning payload: %s", string(payload))
// make sure that errors get send back if we failed
if req.WantReply {
log.Debugf("Sending global request reply %t back with %#v", ok, payload)
if err := req.Reply(ok, payload); err != nil {
log.Warnf("Failed to reply a global request back")
}
}
// run any pending work now that a reply has been sent
if pendingFn != nil {
log.Debug("Invoking pending work for global mux")
go pendingFn()
pendingFn = nil
}
}
}
func (t *attachServerSSH) channelMux(in <-chan *ssh.Request, session *tether.SessionConfig, cleanup func()) {
defer trace.End(trace.Begin("attach server channel request handler"))
// cleanup function passed by the caller
defer cleanup()
// to make sure we close the channel once
var once sync.Once
// for the actions after we process the request
var pendingFn func()
for req := range in {
ok := true
abort := false
log.Infof("received channel mux type %v", req.Type)
switch req.Type {
case msgs.PingReq:
log.Infof("Received PingReq for %s", session.ID)
if string(req.Payload) != msgs.PingMsg {
log.Infof("Received corrupted PingReq for %s", session.ID)
ok = false
}
case msgs.UnblockReq:
log.Infof("Received UnblockReq for %s", session.ID)
if string(req.Payload) != msgs.UnblockMsg {
log.Infof("Received corrupted UnblockReq for %s", session.ID)
ok = false
break
}
// if the process has exited, or couldn't launch
if session.Started != "" && session.Started != "true" {
// we need to force the session closed so that error handling occurs on the callers
// side
ok = false
abort = true
} else {
// unblock ^ (above)
pendingFn = func() {
once.Do(func() {
launchChan := session.ClearToLaunch
if session.RunBlock && launchChan != nil && session.Started == "" {
log.Infof("Unblocking the launch of %s", session.Common.ID)
// make sure that portlayer received the container id back
launchChan <- struct{}{}
log.Infof("Unblocked the launch of %s", session.Common.ID)
}
})
}
}
case msgs.WindowChangeReq:
session.Lock()
pty := session.Pty
session.Unlock()
msg := msgs.WindowChangeMsg{}
if pty == nil {
ok = false
log.Errorf("illegal window-change request for non-tty")
} else if err := msg.Unmarshal(req.Payload); err != nil {
ok = false
log.Errorf(err.Error())
} else if err := resizePty(pty.Fd(), &msg); err != nil {
ok = false
log.Errorf(err.Error())
}
case msgs.CloseStdinReq:
log.Infof("Received CloseStdinReq for %s", session.ID)
log.Debugf("Configuring reader to propagate EOF for %s", session.ID)
session.Reader.PropagateEOF(true)
default:
ok = false
log.Error(fmt.Sprintf("ssh request type %s is not supported", req.Type))
}
// payload is ignored on channel specific replies. The ok is passed, however.
if req.WantReply {
log.Debugf("Sending channel request reply %t back", ok)
if err := req.Reply(ok, nil); err != nil {
log.Warnf("Failed replying to a channel request: %s", err)
}
}
// run any pending work now that a reply has been sent
if pendingFn != nil {
log.Debug("Invoking pending work for channel mux")
go pendingFn()
pendingFn = nil
}
if abort {
break
}
}
}
// The syscall struct
type winsize struct {
wsRow uint16
wsCol uint16
wsXpixel uint16
wsYpixel uint16
}

121
vendor/github.com/vmware/vic/cmd/tether/attach_linux.go generated vendored Normal file
View File

@@ -0,0 +1,121 @@
// 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.
package main
import (
"fmt"
"net"
"os"
"path"
"syscall"
"unsafe"
"golang.org/x/crypto/ssh/terminal"
log "github.com/Sirupsen/logrus"
"github.com/vmware/vic/lib/tether/msgs"
"github.com/vmware/vic/pkg/serial"
"github.com/vmware/vic/pkg/trace"
)
var backchannelMode = os.ModePerm
func setTerminalSpeed(fd uintptr) error {
var current struct {
termios syscall.Termios
}
// get the current state
// #nosec: Use of unsafe calls should be audited
if _, _, errno := syscall.Syscall6(syscall.SYS_IOCTL,
uintptr(fd),
syscall.TCGETS,
uintptr(unsafe.Pointer(&current.termios)),
0,
0,
0,
); errno != 0 {
return errno
}
// copy it as the future
future := current.termios
// unset 9600 bps
future.Cflag &^= syscall.B9600
// set them to 115200 bps
future.Cflag |= syscall.B115200
future.Ispeed = syscall.B115200
future.Ospeed = syscall.B115200
// set the future values
// #nosec: Use of unsafe calls should be audited
if _, _, errno := syscall.Syscall6(
syscall.SYS_IOCTL,
uintptr(fd),
syscall.TCSETS,
uintptr(unsafe.Pointer(&future)),
0,
0,
0,
); errno != 0 {
return errno
}
return nil
}
func rawConnectionFromSerial() (net.Conn, error) {
log.Info("opening ttyS0 for backchannel")
f, err := os.OpenFile(path.Join(pathPrefix, "ttyS0"), os.O_RDWR|os.O_SYNC|syscall.O_NOCTTY, backchannelMode)
if err != nil {
detail := fmt.Errorf("failed to open serial port for backchannel: %s", err)
log.Error(detail)
return nil, detail
}
// set the provided FDs to raw if it's a termial
// 0 is the uninitialized value for Fd
if f.Fd() != 0 && terminal.IsTerminal(int(f.Fd())) {
log.Info("setting terminal to raw mode")
_, err := terminal.MakeRaw(int(f.Fd()))
if err != nil {
return nil, err
}
}
if err := setTerminalSpeed(f.Fd()); err != nil {
log.Errorf("Setting terminal speed failed with %s", err)
}
log.Infof("creating raw connection from ttyS0 (fd=%d)", f.Fd())
return serial.NewFileConn(f)
}
func resizePty(pty uintptr, winSize *msgs.WindowChangeMsg) error {
defer trace.End(trace.Begin(""))
ws := &winsize{uint16(winSize.Rows), uint16(winSize.Columns), uint16(winSize.WidthPx), uint16(winSize.HeightPx)}
// #nosec: Use of unsafe calls should be audited
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
pty,
syscall.TIOCSWINSZ,
uintptr(unsafe.Pointer(ws)),
)
if errno != 0 {
return syscall.Errno(errno)
}
return nil
}

1096
vendor/github.com/vmware/vic/cmd/tether/attach_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
// Copyright 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.
package main
import (
"fmt"
"os"
"os/exec"
log "github.com/Sirupsen/logrus"
"github.com/vmware/vic/lib/tether"
)
// Haveged is a tether extension that wraps command
type Haveged struct {
p *os.Process
exec func() (*os.Process, error)
}
// NewHaveged returns a tether.Extension that wraps haveged
func NewHaveged() *Haveged {
return &Haveged{
exec: func() (*os.Process, error) {
args := []string{"/.tether/lib/ld-linux-x86-64.so.2", "--library-path", "/.tether/lib", "/.tether/haveged", "-w", "1024", "-v", "1", "-F"}
// #nosec: Subprocess launching with variable
cmd := exec.Command(args[0], args[1:]...)
log.Infof("Starting haveged with args: %q", args)
if err := cmd.Start(); err != nil {
log.Errorf("Starting haveged failed with %q", err.Error())
return nil, err
}
return cmd.Process, nil
},
}
}
// Start implementation of the tether.Extension interface
func (h *Haveged) Start() error {
log.Infof("Starting haveged")
var err error
h.p, err = h.exec()
return err
}
// Stop implementation of the tether.Extension interface
func (h *Haveged) Stop() error {
log.Infof("Stopping haveged")
if h.p != nil {
return h.p.Kill()
}
return fmt.Errorf("haveged process is missing")
}
// Reload implementation of the tether.Extension interface
func (h *Haveged) Reload(config *tether.ExecutorConfig) error {
// haveged doesn't support reloading
return nil
}

139
vendor/github.com/vmware/vic/cmd/tether/main_linux.go generated vendored Normal file
View File

@@ -0,0 +1,139 @@
// 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.
package main
import (
"os"
"os/signal"
"path"
"runtime/debug"
"strings"
"syscall"
log "github.com/Sirupsen/logrus"
"github.com/vmware/vic/lib/tether"
viclog "github.com/vmware/vic/pkg/log"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/extraconfig"
)
var tthr tether.Tether
func init() {
// Initiliaze logger with default TextFormatter
log.SetFormatter(viclog.NewTextFormatter())
// use the same logger for trace and other logging
trace.Logger.Level = log.DebugLevel
log.SetLevel(log.DebugLevel)
// init and start the HUP handler
startSignalHandler()
pathPrefix = "/dev"
}
func main() {
if strings.HasSuffix(os.Args[0], "-debug") {
// very, very verbose
extraconfig.SetLogLevel(log.DebugLevel)
}
defer func() {
if r := recover(); r != nil {
log.Errorf("run time panic: %s : %s", r, debug.Stack())
}
halt()
}()
logFile, err := os.OpenFile(path.Join(pathPrefix, "ttyS1"), os.O_WRONLY|os.O_SYNC, 0)
if err != nil {
log.Errorf("Could not open serial port for debugging info. Some debug info may be lost! Error reported was %s", err)
}
if err = syscall.Dup3(int(logFile.Fd()), int(os.Stderr.Fd()), 0); err != nil {
log.Errorf("Could not pipe logfile to standard error due to error %s", err)
}
if _, err = os.Stderr.WriteString("all stderr redirected to debug log"); err != nil {
log.Errorf("Could not write to Stderr due to error %s", err)
}
// where to look for the various devices and files related to tether
// TODO: hard code executor initialization status reporting via guestinfo here
sshserver := NewAttachServerSSH()
src, err := extraconfig.GuestInfoSource()
if err != nil {
log.Error(err)
return
}
sink, err := extraconfig.GuestInfoSink()
if err != nil {
log.Error(err)
return
}
// create the tether
tthr = tether.New(src, sink, &operations{})
// register the attach extension
tthr.Register("Attach", sshserver)
// register the toolbox extension
tthr.Register("Toolbox", tether.NewToolbox().InContainer())
// register the executor extension
tthr.Register("Haveged", NewHaveged())
err = tthr.Start()
if err != nil {
log.Error(err)
return
}
log.Info("Clean exit from tether")
}
// exit cleanly shuts down the system
func halt() {
log.Infof("Powering off the system")
if strings.HasSuffix(os.Args[0], "-debug") {
log.Info("Squashing power off for debug tether")
return
}
syscall.Sync()
syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
}
func startSignalHandler() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGHUP)
go func() {
for s := range sigs {
switch s {
case syscall.SIGHUP:
log.Infof("Reloading tether configuration")
tthr.Reload()
default:
log.Infof("%s signal not defined", s.String())
}
}
}()
}

160
vendor/github.com/vmware/vic/cmd/tether/main_test.go generated vendored Normal file
View File

@@ -0,0 +1,160 @@
// 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.
package main
import (
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"syscall"
"testing"
log "github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/vmware/vic/lib/tether"
"github.com/vmware/vic/pkg/trace"
)
func init() {
trace.Logger.Level = log.DebugLevel
}
// createFakeDevices creates regular files or pipes in place of the char devices used
// in a full VM
func createFakeDevices() error {
var err error
// create control channel
path := fmt.Sprintf("%s/ttyS0", pathPrefix)
err = syscall.Mkfifo(path+"s", uint32(backchannelMode))
if err != nil {
detail := fmt.Sprintf("failed to create fifo pipe %ss for com0: %s", path, err)
return errors.New(detail)
}
err = syscall.Mkfifo(path+"c", uint32(backchannelMode))
if err != nil {
detail := fmt.Sprintf("failed to create fifo pipe %sc for com0: %s", path, err)
return errors.New(detail)
}
log.Debugf("created %s/ttyS0{c,s} as raw conn pipes", pathPrefix)
// others are non-interactive
for i := 1; i < 3; i++ {
path = fmt.Sprintf("%s/ttyS%d", pathPrefix, i)
_, err = os.Create(path)
if err != nil {
detail := fmt.Sprintf("failed to create %s for com%d: %s", path, i+1, err)
return errors.New(detail)
}
log.Debugf("created %s as persistent log destinations", path)
}
// make an access to urandom
path = fmt.Sprintf("%s/urandom", pathPrefix)
err = os.Symlink("/dev/urandom", path)
if err != nil {
detail := fmt.Sprintf("failed to create urandom access: %s", err)
return errors.New(detail)
}
return nil
}
func testSetup(t *testing.T) *Mocker {
var err error
pathPrefix, err = ioutil.TempDir("", path.Base(t.Name()))
if err != nil {
fmt.Println(err)
t.Error(err)
}
mocker := tetherTestSetup(t)
err = os.MkdirAll(pathPrefix, 0777)
if err != nil {
fmt.Println(err)
t.Error(err)
}
log.Infof("Using %s as test prefix", pathPrefix)
backchannelMode = os.ModeNamedPipe | os.ModePerm
err = createFakeDevices()
if err != nil {
fmt.Println(err)
t.Error(err)
}
ctx, cancel := context.WithCancel(context.Background())
// supply custom attach server so we can inspect its state
testServer := &testAttachServer{
updated: make(chan bool, 10),
attachServerSSH: attachServerSSH{
ctx: ctx,
cancel: cancel,
},
}
server = testServer
return mocker
}
func testTeardown(t *testing.T, mocker *Mocker) {
tetherTestTeardown(t, mocker)
}
type mockery struct {
reloaded chan bool
}
func (m *mockery) Reload() {
m.reloaded <- true
}
func (m *mockery) Start() error {
return nil
}
func (m *mockery) Stop() error {
return nil
}
func (m *mockery) Register(name string, config tether.Extension) {
}
func (m *mockery) LaunchUtility(fn tether.UtilityFn) (<-chan int, error) {
return nil, nil
}
// Test reloading via signal helper
func TestReload(t *testing.T) {
m := &mockery{make(chan bool)}
tthr = m
startSignalHandler()
if !assert.NoError(t, tether.ReloadConfig()) {
return
}
// check the started channel is closed (which gets closed on reconfig)
if !assert.True(t, <-m.reloaded) {
return
}
}

View File

@@ -0,0 +1,127 @@
// Copyright 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 linux
package main
import (
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vishvananda/netlink"
"github.com/vmware/vic/lib/config/executor"
"github.com/vmware/vic/lib/tether"
"github.com/vmware/vic/pkg/vsphere/extraconfig"
)
// addInterface utility method to add an interface to Mocked
// This assigns the interface name and returns the "slot" as a string
func addInterface(name string, mocker *Mocker) string {
mocker.maxSlot++
mocker.Interfaces[name] = &Interface{
LinkAttrs: netlink.LinkAttrs{
Name: name,
Index: mocker.maxSlot,
},
Up: true,
}
return strconv.Itoa(mocker.maxSlot)
}
func TestOutboundRuleAndCmd(t *testing.T) {
t.Skip("https://github.com/vmware/vic/issues/5965")
mocker := testSetup(t)
defer testTeardown(t, mocker)
bridge := addInterface("eth1", mocker)
ip, _ := netlink.ParseIPNet("172.16.0.2/24")
gwIP, _ := netlink.ParseIPNet("172.16.0.1/24")
cfg := executor.ExecutorConfig{
ExecutorConfigCommon: executor.ExecutorConfigCommon{
ID: "outboundrule",
Name: "tether_test_executor",
},
Diagnostics: executor.Diagnostics{
DebugLevel: 3,
},
Networks: map[string]*executor.NetworkEndpoint{
"bridge": {
Common: executor.Common{
ID: bridge,
// interface rename
Name: "bridge",
},
Network: executor.ContainerNetwork{
Common: executor.Common{
Name: "bridge",
},
Default: true,
Gateway: *gwIP,
},
Static: true,
IP: ip,
},
},
Sessions: map[string]*executor.SessionConfig{
"outboundrule": {
Common: executor.Common{
ID: "outboundrule",
Name: "tether_test_session",
},
Tty: false,
Active: true,
Cmd: executor.Cmd{
// test relative path
Path: "./date",
Args: []string{"./date", "--reference=/"},
Env: []string{"PATH="},
Dir: "/bin",
},
},
},
}
_, src, _ := StartTether(t, &cfg, mocker)
fmt.Println("Waiting for tether start")
<-mocker.Started
// wait for tether to exit
fmt.Println("Waiting for tether exit")
<-mocker.Cleaned
result := tether.ExecutorConfig{}
extraconfig.Decode(src, &result)
// confirm outbound rules configured
// this should modify state depending on prior rule state
// confirm command ran - necessary to detect early exit due to net config error
// TODO: this should be modifed to fail if the last rule to be configured hasn't completed with expected output
// when this is run. Pending mocked iptables interface
assert.Equal(t, "true", result.Sessions["outboundrule"].Started, "Expected command to have been started successfully")
assert.Equal(t, 0, result.Sessions["outboundrule"].ExitStatus, "Expected command to have exited cleanly")
}

52
vendor/github.com/vmware/vic/cmd/tether/ops.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
// Copyright 2016 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.
package main
import "github.com/vmware/vic/lib/tether"
// pathPrefix is present to allow the various files referenced by tether to be placed
// in specific directories, primarily for testing.
var pathPrefix string
func (t *operations) Cleanup() error {
return t.BaseOperations.Cleanup()
}
func (t *operations) Apply(endpoint *tether.NetworkEndpoint) error {
return t.BaseOperations.Apply(endpoint)
}
// HandleSessionExit controls the behaviour on session exit - for the tether if the session exiting
// is the primary session (i.e. SessionID matches ExecutorID) then we exit everything.
func (t *operations) HandleSessionExit(config *tether.ExecutorConfig, session *tether.SessionConfig) func() {
// if the session that's exiting is the primary session, stop the tether
return func() {
pod := false
for _, s := range config.Sessions {
if s.ExecutionEnvironment != "" {
pod = true
}
if s.StopTime <= s.StartTime {
return
}
}
for _, s := range config.Execs {
if pod && s.StopTime <= s.StartTime {
return
}
}
tthr.Stop()
}
}

490
vendor/github.com/vmware/vic/cmd/tether/ops_linux.go generated vendored Normal file
View File

@@ -0,0 +1,490 @@
// 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.
package main
import (
"context"
"errors"
"fmt"
"io"
"math"
"os"
"path"
"strconv"
"strings"
"syscall"
log "github.com/Sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/sys/unix"
"github.com/vmware/vic/lib/config/executor"
"github.com/vmware/vic/lib/constants"
"github.com/vmware/vic/lib/iolog"
"github.com/vmware/vic/lib/tether"
"github.com/vmware/vic/lib/tether/netfilter"
"github.com/vmware/vic/pkg/dio"
"github.com/vmware/vic/pkg/trace"
)
const (
runMountPoint = "/run"
// default values to set for ulimit fields
defaultNOFILE = 1024 * 1024
defaultULimit = math.MaxUint64
)
type operations struct {
tether.BaseOperations
logging bool
}
func (t *operations) Log() (io.Writer, error) {
defer trace.End(trace.Begin("operations.Log"))
// redirect logging to the serial log
log.Infof("opening %s/ttyS1 for debug log", pathPrefix)
f, err := os.OpenFile(path.Join(pathPrefix, "ttyS1"), os.O_RDWR|os.O_SYNC|syscall.O_NOCTTY, 0)
if err != nil {
detail := fmt.Sprintf("failed to open serial port for debug log: %s", err)
log.Error(detail)
return nil, errors.New(detail)
}
if err := setTerminalSpeed(f.Fd()); err != nil {
log.Errorf("Setting terminal speed failed with %s", err)
}
// enable raw mode
_, err = terminal.MakeRaw(int(f.Fd()))
if err != nil {
detail := fmt.Sprintf("Making ttyS1 raw failed with %s", err)
log.Error(detail)
return nil, errors.New(detail)
}
return io.MultiWriter(f, os.Stdout), nil
}
// sessionLogWriter returns a writer that will persist the session output
func (t *operations) SessionLog(session *tether.SessionConfig) (dio.DynamicMultiWriter, dio.DynamicMultiWriter, error) {
defer trace.End(trace.Begin("configure session log writer"))
if t.logging {
detail := "unable to log more than one session concurrently to persistent logging"
log.Warn(detail)
// use multi-writer so it's still viable for attach
return dio.MultiWriter(), dio.MultiWriter(), nil
}
t.logging = true
// open SttyS2 for session logging
log.Info("opening ttyS2 for session logging")
f, err := os.OpenFile(path.Join(pathPrefix, "ttyS2"), os.O_RDWR|os.O_SYNC|syscall.O_NOCTTY, 0)
if err != nil {
detail := fmt.Sprintf("failed to open serial port for session log: %s", err)
log.Error(detail)
return nil, nil, errors.New(detail)
}
if err := setTerminalSpeed(f.Fd()); err != nil {
log.Errorf("Setting terminal speed failed with %s", err)
}
// enable raw mode
_, err = terminal.MakeRaw(int(f.Fd()))
if err != nil {
detail := fmt.Sprintf("Making ttyS2 raw failed with %s", err)
log.Error(detail)
return nil, nil, errors.New(detail)
}
// wrap output in a LogWriter to serialize it into our persisted
// containerVM output format, using iolog.LogClock for timestamps
lw := iolog.NewLogWriter(f, iolog.LogClock{})
// use multi-writer so it goes to both screen and session log
return dio.MultiWriter(lw, os.Stdout), dio.MultiWriter(lw, os.Stderr), nil
}
func (t *operations) Setup(sink tether.Config) error {
if err := t.BaseOperations.Setup(sink); err != nil {
return err
}
// unmount /run - https://github.com/vmware/vic/issues/1643
if err := tether.Sys.Syscall.Unmount(runMountPoint, syscall.MNT_DETACH); err != nil {
if errno, ok := err.(syscall.Errno); !ok || errno != syscall.EINVAL {
return err
}
}
// NOTE: ulimit default values should change when we support ulimit configuration
ApplyDefaultULimit()
return nil
}
// ApplyDefaultULimit sets ulimit fields to their defined default value
func ApplyDefaultULimit() {
var rLimit syscall.Rlimit
// NOFILE does not support defaultULimit as a value due to kernel restriction on number of open files
rLimit.Max = defaultNOFILE
rLimit.Cur = rLimit.Max
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
log.Errorf("Cannot set ulimit for nofile: %s", err.Error())
}
rLimit.Max = defaultULimit
rLimit.Cur = rLimit.Max
if err := syscall.Setrlimit(syscall.RLIMIT_STACK, &rLimit); err != nil {
log.Errorf("Cannot set ulimit for stack: %s ", err.Error())
}
if err := syscall.Setrlimit(syscall.RLIMIT_CORE, &rLimit); err != nil {
log.Errorf("Cannot set ulimit for core blocks: %s", err.Error())
}
if err := syscall.Setrlimit(unix.RLIMIT_MEMLOCK, &rLimit); err != nil {
log.Errorf("Cannot set ulimit for memlock: %s", err.Error())
}
if err := syscall.Setrlimit(unix.RLIMIT_NPROC, &rLimit); err != nil {
log.Errorf("Cannot set ulimit for nproc: %s", err.Error())
}
}
// invoke will invoke the closure returned from the tether netfilter prep and
// block until complete. It handles both potential preparation errors and invocation
// errors.
// the 'task' specified is used to construct error messages with the specific operation
// embedded
func invoke(t *tether.BaseOperations, fn tether.UtilityFn, task string) error {
exitChan, err := t.LaunchUtility(fn)
if err != nil {
return fmt.Errorf("%s failed: %s", task, err)
}
exitCode := <-exitChan
if exitCode != 0 {
return fmt.Errorf("%s returned non-zero: %d", task, exitCode)
}
return nil
}
// SetupFirewall sets up firewall rules on the external scope only. Any
// portmaps are honored as are port exposes.
func (t *operations) SetupFirewall(ctx context.Context, config *tether.ExecutorConfig) error {
return setupFirewall(ctx, &t.BaseOperations, config)
}
// setupFirewall is broken out from SetupFirewall so that it can be referenced from the test code
func setupFirewall(ctx context.Context, t *tether.BaseOperations, config *tether.ExecutorConfig) error {
fn := netfilter.Flush(ctx, "VIC")
if err := invoke(t, fn, "flush"); err != nil {
return err
}
for _, endpoint := range config.Networks {
switch endpoint.Network.Type {
case constants.ExternalScopeType:
id, err := strconv.Atoi(endpoint.ID)
if err != nil {
log.Errorf("can't apply port rules: %s", err.Error())
continue
}
iface, err := t.LinkBySlot(int32(id))
if err != nil {
log.Errorf("can't apply rules: %s", err.Error())
continue
}
ifaceName := iface.Attrs().Name
log.Debugf("slot %d -> %s", endpoint.ID, ifaceName)
// ensure that we can pass DHCP traffic if it's necessary
if endpoint.Network.TrustLevel != executor.Open && endpoint.IsDynamic() {
allowDHCPTraffic(ctx, t, ifaceName)
}
switch endpoint.Network.TrustLevel {
case executor.Open:
// Configure port redirect in Open deployment
if err := setupPorts(ctx, t, endpoint, ifaceName, true); err != nil {
return err
}
// Accept all incoming and outgoing traffic
for _, chain := range []netfilter.Chain{netfilter.Input, netfilter.Output, netfilter.Forward} {
fn := (&netfilter.Rule{
Chain: chain,
Target: netfilter.Accept,
Interface: ifaceName,
}).Commit(ctx)
if err := invoke(t, fn, "accept all"); err != nil {
return err
}
}
case executor.Closed:
// Reject all incoming and outgoing traffic
// Since our default policy is to drop traffic, nothing is needed here.
case executor.Outbound:
// Reject all incoming traffic, but allow outgoing
if err := setupOutboundFirewall(ctx, t, ifaceName); err != nil {
return err
}
case executor.Peers:
// Outbound + all ports open to source addresses in --container-network-ip-range
if err := setupOutboundFirewall(ctx, t, ifaceName); err != nil {
return err
}
// Configure port redirect in Peer deployment
if err := setupPorts(ctx, t, endpoint, ifaceName, true); err != nil {
return err
}
sourceAddresses := make([]string, len(endpoint.Network.Pools))
for i, v := range endpoint.Network.Pools {
sourceAddresses[i] = v.String()
}
fn := (&netfilter.Rule{
Chain: netfilter.Input,
Target: netfilter.Accept,
SourceAddresses: sourceAddresses,
Interface: ifaceName,
}).Commit(ctx)
if err := invoke(t, fn, "allow outbound and peers"); err != nil {
return err
}
if err := allowPingTraffic(ctx, t, ifaceName, sourceAddresses); err != nil {
return err
}
default:
// covers executor.Published and executor.Unspecified as well as invalid values
log.Infof("Applying published rules for configuration %v", endpoint.Network.TrustLevel)
if err := setupOutboundFirewall(ctx, t, ifaceName); err != nil {
return err
}
if err := allowPingTraffic(ctx, t, ifaceName, nil); err != nil {
return err
}
if err := setupPorts(ctx, t, endpoint, ifaceName, false); err != nil {
return err
}
}
case constants.BridgeScopeType:
id, err := strconv.Atoi(endpoint.ID)
if err != nil {
log.Errorf("can't apply port rules: %s", err.Error())
continue
}
iface, err := t.LinkBySlot(int32(id))
if err != nil {
log.Errorf("can't apply rules: %s", err.Error())
continue
}
ifaceName := iface.Attrs().Name
log.Debugf("slot %d -> %s", endpoint.ID, ifaceName)
// Traffic over container bridge network should be peers+outbound.
if err := setupOutboundFirewall(ctx, t, ifaceName); err != nil {
return err
}
sourceAddresses := make([]string, len(endpoint.Network.Pools))
for i, v := range endpoint.Network.Pools {
sourceAddresses[i] = v.String()
}
fn := (&netfilter.Rule{
Chain: netfilter.Input,
Target: netfilter.Accept,
SourceAddresses: sourceAddresses,
Interface: ifaceName,
}).Commit(ctx)
if err := invoke(t, fn, "configure for bridge scope"); err != nil {
return err
}
}
}
return invoke(t, netfilter.Return(ctx, "VIC"), "return from VIC chain")
}
func setupOutboundFirewall(ctx context.Context, t *tether.BaseOperations, ifaceName string) error {
// All already established inputs are accepted
fn := (&netfilter.Rule{
Chain: netfilter.Input,
States: []netfilter.State{netfilter.Established, netfilter.Related},
Target: netfilter.Accept,
Interface: ifaceName,
}).Commit(ctx)
if err := invoke(t, fn, "permit established inbound"); err != nil {
return err
}
// All output is accepted
fn = (&netfilter.Rule{
Chain: netfilter.Output,
Target: netfilter.Accept,
Interface: ifaceName,
}).Commit(ctx)
return invoke(t, fn, "permit all outbound")
}
func setupPorts(ctx context.Context, t *tether.BaseOperations, endpoint *tether.NetworkEndpoint, ifaceName string, redirectOnly bool) error {
// handle the ports
for _, p := range endpoint.Ports {
// parse the port maps
rules, err := portToRule(p, redirectOnly)
if err != nil {
log.Errorf("can't apply port rule (%s): %s", p, err.Error())
continue
}
log.Infof("Applying %d rules for port %s", len(rules), p)
for _, r := range rules {
r.Interface = ifaceName
fn := r.Commit(ctx)
if err := invoke(t, fn, "allow incoming on published port"); err != nil {
return err
}
}
}
return nil
}
func allowPingTraffic(ctx context.Context, t *tether.BaseOperations, ifaceName string, sourceAddresses []string) error {
fn := (&netfilter.Rule{
Chain: netfilter.Input,
Target: netfilter.Accept,
Interface: ifaceName,
Protocol: netfilter.ICMP,
ICMPType: netfilter.EchoRequest,
SourceAddresses: sourceAddresses,
}).Commit(ctx)
if err := invoke(t, fn, "allow ping inbound"); err != nil {
return err
}
fn = (&netfilter.Rule{
Chain: netfilter.Output,
Target: netfilter.Accept,
Interface: ifaceName,
Protocol: netfilter.ICMP,
ICMPType: netfilter.EchoReply,
SourceAddresses: sourceAddresses,
}).Commit(ctx)
return invoke(t, fn, "allow ping outbound")
}
func allowDHCPTraffic(ctx context.Context, t *tether.BaseOperations, ifaceName string) error {
fn := (&netfilter.Rule{
Chain: netfilter.Input,
Target: netfilter.Accept,
Interface: ifaceName,
Protocol: netfilter.UDP,
FromPort: "67:68",
SrcPort: "67:68",
}).Commit(ctx)
if err := invoke(t, fn, "allow dhcp inbound"); err != nil {
return err
}
fn = (&netfilter.Rule{
Chain: netfilter.Output,
Target: netfilter.Accept,
Interface: ifaceName,
Protocol: netfilter.UDP,
FromPort: "67:68",
SrcPort: "67:68",
}).Commit(ctx)
return invoke(t, fn, "allow dhcp outbound")
}
func portToRule(p string, redirectOnly bool) ([]*netfilter.Rule, error) {
// 9999/tcp
s := strings.Split(p, "/")
if len(s) != 2 {
return nil, errors.New("can't parse port spec: " + p)
}
proto := netfilter.Protocol(s[1])
switch proto {
case netfilter.UDP:
case netfilter.TCP:
default:
return nil, errors.New("unknown protocol")
}
mapping := strings.Split(s[0], ":")
directPort := mapping[len(mapping)-1]
// publish the actual port directly
var rules []*netfilter.Rule
expose := &netfilter.Rule{
Chain: netfilter.Input,
Interface: "external",
Target: netfilter.Accept,
Protocol: proto,
// ranges are specified using a hyphen in docker
FromPort: strings.Replace(directPort, "-", ":", -1),
}
if !redirectOnly {
rules = append(rules, expose)
}
// if there's no redirection we're done
if len(mapping) == 1 || mapping[0] == mapping[1] {
return rules, nil
}
// redirect port
// https://wiki.debian.org/Firewalls-local-port-redirection contains a useful reference
if strings.Contains(s[0], "-") {
return nil, errors.New("cannot port forward a range")
}
rules = append(rules, &netfilter.Rule{
Table: netfilter.Nat,
Chain: netfilter.Prerouting,
Interface: "external",
Target: netfilter.Redirect,
Protocol: proto,
FromPort: mapping[0],
ToPort: mapping[1],
})
return rules, nil
}

View File

@@ -0,0 +1,161 @@
// Copyright 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 linux
package main
import (
"errors"
"fmt"
"syscall"
"github.com/vishvananda/netlink"
"github.com/vmware/vic/pkg/trace"
)
// This has been copied from lib/tether/ but should be split into a common base package that is
// dedicated to mocking these operations. We cannot reference lib/tether/*_test elements from
// outside that package.
type Interface struct {
netlink.LinkAttrs
Up bool
Addrs []netlink.Addr
}
func (t *Interface) Attrs() *netlink.LinkAttrs {
return &t.LinkAttrs
}
func (t *Interface) Type() string {
return "mocked"
}
func (t *Mocker) LinkByName(name string) (netlink.Link, error) {
defer trace.End(trace.Begin(fmt.Sprintf("Getting link by name %s", name)))
return t.Interfaces[name], nil
}
func (t *Mocker) LinkSetName(link netlink.Link, name string) error {
defer trace.End(trace.Begin(fmt.Sprintf("Renaming %s to %s", link.Attrs().Name, name)))
iface := link.(*Interface)
_, ok := t.Interfaces[name]
if ok {
return fmt.Errorf("Interface with name %s already exists", name)
}
oldName := iface.Name
iface.Name = name
// make sure there's no period where the interface isn't "present"
t.Interfaces[name] = iface
delete(t.Interfaces, oldName)
return nil
}
func (t *Mocker) LinkSetDown(link netlink.Link) error {
defer trace.End(trace.Begin(fmt.Sprintf("Bringing %s down", link.Attrs().Name)))
iface := link.(*Interface)
iface.Up = false
// TODO: should this drop addresses?
return nil
}
func (t *Mocker) LinkSetUp(link netlink.Link) error {
defer trace.End(trace.Begin(fmt.Sprintf("Bringing %s up", link.Attrs().Name)))
iface := link.(*Interface)
iface.Up = true
return nil
}
func (t *Mocker) LinkSetAlias(link netlink.Link, alias string) error {
defer trace.End(trace.Begin(fmt.Sprintf("Adding alias %s to %s", alias, link.Attrs().Name)))
iface := link.(*Interface)
iface.Alias = alias
return nil
}
func (t *Mocker) AddrList(link netlink.Link, family int) ([]netlink.Addr, error) {
defer trace.End(trace.Begin(""))
iface := link.(*Interface)
return iface.Addrs, nil
}
func (t *Mocker) AddrAdd(link netlink.Link, addr *netlink.Addr) error {
defer trace.End(trace.Begin(fmt.Sprintf("Adding %s to %s", addr.String(), link.Attrs().Name)))
iface := link.(*Interface)
for _, adr := range iface.Addrs {
if addr.IP.String() == adr.IP.String() {
return syscall.EEXIST
}
}
iface.Addrs = append(iface.Addrs, *addr)
return nil
}
func (t *Mocker) AddrDel(link netlink.Link, addr *netlink.Addr) error {
iface := link.(*Interface)
for i, adr := range iface.Addrs {
if addr.IP.String() == adr.IP.String() {
iface.Addrs = append(iface.Addrs[:i], iface.Addrs[i+1:]...)
return nil
}
}
return syscall.EADDRNOTAVAIL
}
func (t *Mocker) RouteAdd(route *netlink.Route) error {
defer trace.End(trace.Begin("no implemented"))
// currently ignored
return nil
}
func (t *Mocker) RouteDel(route *netlink.Route) error {
defer trace.End(trace.Begin("no implemented"))
// currently ignored
return nil
}
func (t *Mocker) RuleList(int) ([]netlink.Rule, error) {
defer trace.End(trace.Begin("not implemented"))
return nil, nil
}
func (t *Mocker) LinkBySlot(slot int32) (netlink.Link, error) {
defer trace.End(trace.Begin(""))
id := int(slot)
for _, intf := range t.Interfaces {
if intf.Attrs().Index == id {
return intf, nil
}
}
return nil, errors.New("no such interface")
}

300
vendor/github.com/vmware/vic/cmd/tether/tether_test.go generated vendored Normal file
View File

@@ -0,0 +1,300 @@
// Copyright 2016 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.
package main
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net"
"net/url"
"os"
"path"
"sync"
"testing"
log "github.com/Sirupsen/logrus"
"github.com/vishvananda/netlink"
"golang.org/x/crypto/ssh"
"github.com/vmware/vic/lib/config/executor"
"github.com/vmware/vic/lib/system"
"github.com/vmware/vic/lib/tether"
"github.com/vmware/vic/pkg/dio"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/extraconfig"
)
// Copied from lib/tether
// because there's no easy way to use test code from other packages and a separate
// package causes cyclic dependencies.
// Some modifications to deal with the change of package and attach usage
var Mocked Mocker
type Mocker struct {
Base tether.BaseOperations
// allow tests to tell when the tether has finished setup
Started chan bool
// allow tests to tell when the tether has finished
Cleaned chan bool
// debug output gets logged here
LogBuffer bytes.Buffer
// session output gets logged here
SessionLogBuffer bytes.Buffer
// track the number of interfaces for a run
maxSlot int
// the interfaces in the system indexed by name
Interfaces map[string]netlink.Link
// the hostname of the system
Hostname string
// the ip configuration for name index networks
IPs map[string]net.IP
// filesystem mounts, indexed by disk label
Mounts map[string]string
WindowCol uint32
WindowRow uint32
Signal ssh.Signal
once sync.Once
}
// Start implements the extension method
func (t *Mocker) Start() error {
return nil
}
// Stop implements the extension method
func (t *Mocker) Stop() error {
return nil
}
// Reload implements the extension method
func (t *Mocker) Reload(config *tether.ExecutorConfig) error {
t.once.Do(func() {
// the tether has definitely finished it's startup by the time we hit this
close(t.Started)
})
return nil
}
func (t *Mocker) Setup(config tether.Config) error {
return t.Base.Setup(config)
}
func (t *Mocker) Cleanup() error {
err := t.Base.Cleanup()
close(t.Cleaned)
return err
}
func (t *Mocker) Log() (io.Writer, error) {
return os.Stdout, nil
}
func (t *Mocker) SessionLog(session *tether.SessionConfig) (dio.DynamicMultiWriter, dio.DynamicMultiWriter, error) {
return dio.MultiWriter(&t.SessionLogBuffer), dio.MultiWriter(&t.SessionLogBuffer), nil
}
func (t *Mocker) HandleSessionExit(config *tether.ExecutorConfig, session *tether.SessionConfig) func() {
// check for executor behaviour
return func() {
if session.ID == config.ID {
tthr.Stop()
}
}
}
func (t *Mocker) ProcessEnv(env []string) []string {
return t.Base.ProcessEnv(env)
}
// SetHostname sets both the kernel hostname and /etc/hostname to the specified string
func (t *Mocker) SetHostname(hostname string, aliases ...string) error {
defer trace.End(trace.Begin("mocking hostname to " + hostname))
// TODO: we could mock at a much finer granularity, only extracting the syscall
// that would exercise the file modification paths, however it's much less generalizable
t.Hostname = hostname
return nil
}
func (t *Mocker) SetupFirewall(ctx context.Context, config *tether.ExecutorConfig) error {
err := setupFirewall(ctx, &t.Base, config)
// NOTE: we squash errors from here for now because it's almost certain to fail
// in absence of a mock for the iptables update
if err != nil {
log.Error(err)
}
return nil
}
// Apply takes the network endpoint configuration and applies it to the system
func (t *Mocker) Apply(endpoint *tether.NetworkEndpoint) error {
return tether.ApplyEndpoint(t, &t.Base, endpoint)
}
// MountLabel performs a mount with the source treated as a disk label
// This assumes that /dev/disk/by-label is being populated, probably by udev
func (t *Mocker) MountLabel(ctx context.Context, label, target string) error {
defer trace.End(trace.Begin(fmt.Sprintf("mocking mounting %s on %s", label, target)))
if t.Mounts == nil {
t.Mounts = make(map[string]string)
}
t.Mounts[label] = target
return nil
}
// MountTarget performs a mount with the source treated as an nfs target
func (t *Mocker) MountTarget(ctx context.Context, source url.URL, target string, mountOptions string) error {
defer trace.End(trace.Begin(fmt.Sprintf("mocking mounting %s on %s", source.String(), target)))
if t.Mounts == nil {
t.Mounts = make(map[string]string)
}
t.Mounts[source.String()] = target
return nil
}
// CopyExistingContent copies the underlying files shadowed by a mount on a directory
// to the volume mounted on the directory
func (t *Mocker) CopyExistingContent(source string) error {
defer trace.End(trace.Begin(fmt.Sprintf("mocking copyExistingContent from %s", source)))
return nil
}
// Fork triggers vmfork and handles the necessary pre/post OS level operations
func (t *Mocker) Fork() error {
return errors.New("Fork test not implemented")
}
// LaunchUtility uses the underlying implementation for launching and tracking utility processes
func (t *Mocker) LaunchUtility(fn tether.UtilityFn) (<-chan int, error) {
return t.Base.LaunchUtility(fn)
}
func (t *Mocker) HandleUtilityExit(pid, exitCode int) bool {
return t.Base.HandleUtilityExit(pid, exitCode)
}
// TestMain simply so we have control of debugging level and somewhere to call package wide test setup
func TestMain(m *testing.M) {
log.SetLevel(log.DebugLevel)
retCode := m.Run()
// call with result of m.Run()
os.Exit(retCode)
}
func StartTether(t *testing.T, cfg *executor.ExecutorConfig, mocker *Mocker) (tether.Tether, extraconfig.DataSource, extraconfig.DataSink) {
store := extraconfig.New()
sink := store.Put
src := store.Get
extraconfig.Encode(sink, cfg)
log.Debugf("Test configuration: %#v", sink)
tthr = tether.New(src, sink, mocker)
tthr.Register("mocker", mocker)
// run the tether to service the attach
go func() {
erR := tthr.Start()
if erR != nil {
t.Error(erR)
}
}()
return tthr, src, sink
}
func StartAttachTether(t *testing.T, cfg *executor.ExecutorConfig, mocker *Mocker) (tether.Tether, extraconfig.DataSource, net.Conn) {
store := extraconfig.New()
sink := store.Put
src := store.Get
extraconfig.Encode(sink, cfg)
log.Debugf("Test configuration: %#v", sink)
tthr = tether.New(src, sink, mocker)
tthr.Register("mocker", mocker)
tthr.Register("Attach", server)
// run the tether to service the attach
go func() {
erR := tthr.Start()
if erR != nil {
t.Error(erR)
}
}()
// create client on the mock pipe
conn, err := mockBackChannel(context.Background())
if err != nil && (err != io.EOF || server.(*testAttachServer).enabled) {
// we accept the case where the error is end-of-file and the attach server is disabled because that's
// expected when the tether is shut down.
t.Error(err)
}
return tthr, src, conn
}
func tetherTestSetup(t *testing.T) *Mocker {
log.Infof("Started test setup for %s", t.Name())
// use the mock ops - fresh one each time as tests might apply different mocked calls
mocker := Mocker{
Started: make(chan bool),
Cleaned: make(chan bool),
Interfaces: make(map[string]netlink.Link, 0),
}
// replace the Sys variable with a mock
tether.Sys = system.NewWithRoot(pathPrefix)
tether.Sys.Syscall = &tether.MockSyscall{}
tether.BindSys = system.NewWithRoot(path.Join(pathPrefix, "/.tether"))
tether.BindSys.Syscall = &tether.MockSyscall{}
// ensure that the sys and bindsys root exists
// #nosec
os.MkdirAll(tether.Sys.Root, 0700)
// #nosec
os.MkdirAll(tether.BindSys.Root, 0700)
return &mocker
}
func tetherTestTeardown(t *testing.T, mocker *Mocker) {
<-mocker.Cleaned
// cleanup
os.RemoveAll(pathPrefix)
log.SetOutput(os.Stdout)
log.Infof("Finished test teardown for %s", t.Name())
}