Fix the dependency issue (#231)
This commit is contained in:
530
vendor/github.com/vmware/vic/cmd/vicadmin/logs.go
generated
vendored
530
vendor/github.com/vmware/vic/cmd/vicadmin/logs.go
generated
vendored
@@ -1,530 +0,0 @@
|
||||
// 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 (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/hpcloud/tail"
|
||||
|
||||
"path/filepath"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/lib/pprof"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
const (
|
||||
nBytes = 1024
|
||||
tailLines = 8
|
||||
uint32max = (1 << 32) - 1
|
||||
|
||||
// how many lines of log data to collect
|
||||
logLines = 5000
|
||||
// how many lines to request per call
|
||||
lines = 500
|
||||
)
|
||||
|
||||
type dlogReader struct {
|
||||
c *session.Session
|
||||
name string
|
||||
host *object.HostSystem
|
||||
}
|
||||
|
||||
func (r dlogReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(r.name))
|
||||
|
||||
name := r.name
|
||||
if r.host != nil {
|
||||
name = fmt.Sprintf("%s-%s", path.Base(r.host.InventoryPath), r.name)
|
||||
}
|
||||
|
||||
m := object.NewDiagnosticManager(r.c.Vim25())
|
||||
ctx := context.Background()
|
||||
|
||||
// Currently we collect the tail of diagnostic log files to avoid
|
||||
// reading the entire file into memory or writing local disk.
|
||||
|
||||
// get LineEnd without any LineText
|
||||
h, err := m.BrowseLog(ctx, r.host, r.name, math.MaxInt32, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
end := h.LineEnd
|
||||
start := end - logLines
|
||||
|
||||
var buf bytes.Buffer
|
||||
for start < end {
|
||||
h, err = m.BrowseLog(ctx, r.host, r.name, start, lines)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, line := range h.LineText {
|
||||
buf.WriteString(line)
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
start += lines
|
||||
}
|
||||
|
||||
return newBytesEntry(name+".log", buf.Bytes()), nil
|
||||
}
|
||||
|
||||
// logFiles has a potential race condition since logrotation can rotate files this moment of time.
|
||||
// however, the likely hood of this race is so low that it doesn't worth investing the time to do
|
||||
// cross process synchronization.
|
||||
func logFiles() []string {
|
||||
defer trace.End(trace.Begin(""))
|
||||
files, err := ioutil.ReadDir(logFileDir)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get a list of log files: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
names := []string{}
|
||||
for _, fileInfo := range files {
|
||||
if fileInfo.IsDir() {
|
||||
continue
|
||||
}
|
||||
fname := fileInfo.Name()
|
||||
log.Debugf("Found potential file for export: %s", fname)
|
||||
|
||||
for _, f := range logFileListPrefixes {
|
||||
if strings.HasPrefix(fname, f) {
|
||||
fp := filepath.Join(logFileDir, fname)
|
||||
log.Debugf("Adding file for export: %s", fp)
|
||||
names = append(names, fp)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// logFile writes the contents of file f and any of it's rotated files to the http writer.
|
||||
// includeF is used for tailing, in which case we want to write rotated files but not f itself
|
||||
func writeLogFiles(w http.ResponseWriter, r *http.Request, f string, includeF bool) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
files, err := ioutil.ReadDir(logFileDir)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get a list of log files: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// find all rotated files, but not f itself
|
||||
names := []string{}
|
||||
for _, fileInfo := range files {
|
||||
fname := fileInfo.Name()
|
||||
if fileInfo.IsDir() || (fname == f) {
|
||||
continue
|
||||
}
|
||||
log.Debugf("Found potential file for export: %s", fname)
|
||||
|
||||
if strings.HasPrefix(fname, f) {
|
||||
fp := filepath.Join(logFileDir, fname)
|
||||
names = append(names, fp)
|
||||
}
|
||||
}
|
||||
// sort file names to preserve time rotation order
|
||||
sort.Strings(names)
|
||||
|
||||
// find f last and append it to names
|
||||
for _, fileInfo := range files {
|
||||
fname := fileInfo.Name()
|
||||
if fileInfo.IsDir() {
|
||||
continue
|
||||
}
|
||||
if fname == f {
|
||||
log.Debugf("Found potential file for export: %s", fname)
|
||||
fp := filepath.Join(logFileDir, fname)
|
||||
names = append(names, fp)
|
||||
}
|
||||
}
|
||||
|
||||
if len(names) == 0 {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
// write file contents to w
|
||||
for _, fileName := range names {
|
||||
file, err := os.Open(fileName)
|
||||
log.Debugf("Writing contents of: %s", fileName)
|
||||
if err != nil {
|
||||
log.Errorf("error opening file %s: %s", fileName, err.Error())
|
||||
continue
|
||||
}
|
||||
// using interface type here so we can reassign r as a gzip reader for rotated logs
|
||||
var r io.ReadCloser = file
|
||||
if strings.HasSuffix(fileName, "gz") {
|
||||
r, err = gzip.NewReader(file)
|
||||
if err != nil {
|
||||
log.Errorf("error opening gzipped file %s: %s", fileName, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
_, err = io.Copy(w, r)
|
||||
if err != nil {
|
||||
log.Errorf("error writing contents of %s: %s", fileName, err.Error())
|
||||
continue
|
||||
}
|
||||
r.Close()
|
||||
file.Close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func configureReaders() map[string]entryReader {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
pprofPaths := map[string]string{
|
||||
// verbose
|
||||
"verbose": "/debug/pprof/goroutine?debug=2",
|
||||
// concise
|
||||
"concise": "/debug/pprof/goroutine?debug=1",
|
||||
"block": "/debug/pprof/block?debug=1",
|
||||
"heap": "/debug/pprof/heap?debug=1",
|
||||
"profile": "/debug/pprof/profile",
|
||||
}
|
||||
|
||||
pprofSources := map[string]string{
|
||||
"docker": pprof.GetPprofEndpoint(pprof.DockerPort).String(),
|
||||
"portlayer": pprof.GetPprofEndpoint(pprof.PortlayerPort).String(),
|
||||
"vicadm": pprof.GetPprofEndpoint(pprof.VicadminPort).String(),
|
||||
"vic-init": pprof.GetPprofEndpoint(pprof.VCHInitPort).String(),
|
||||
}
|
||||
|
||||
readers := map[string]entryReader{
|
||||
"proc-mounts": fileReader("/proc/mounts"),
|
||||
"uptime": commandReader("uptime"),
|
||||
"df": commandReader("df"),
|
||||
"free": commandReader("free"),
|
||||
"netstat": commandReader("netstat -ant"),
|
||||
"iptables": commandReader("sudo iptables --list"),
|
||||
"ip-link": commandReader("ip link"),
|
||||
"ip-addr": commandReader("ip addr"),
|
||||
"ip-route": commandReader("ip route"),
|
||||
"lsmod": commandReader("lsmod"),
|
||||
// TODO: ls without shelling out
|
||||
"disk-by-path": commandReader("ls -l /dev/disk/by-path"),
|
||||
"disk-by-label": commandReader("ls -l /dev/disk/by-label"),
|
||||
"disk-by-uuid": commandReader("ls -l /dev/disk/by-uuid"),
|
||||
"lsblk": commandReader("lsblk -S"),
|
||||
// To check we are not leaking any fds
|
||||
"proc-self-fd": commandReader("ls -l /proc/self/fd"),
|
||||
"ps": commandReader("ps -ef"),
|
||||
"meminfo": fileReader("/proc/meminfo"),
|
||||
"journalctl": commandReader("/bin/journalctl --no-pager"),
|
||||
"dmesg": commandReader("/bin/journalctl --dmesg --no-pager"),
|
||||
"sys-block": commandReader("ls -l /sys/block/"),
|
||||
// To check version
|
||||
"VERSION": versionReader("version"),
|
||||
}
|
||||
|
||||
// add the pprof collection
|
||||
for sname, source := range pprofSources {
|
||||
for pname, paths := range pprofPaths {
|
||||
rname := fmt.Sprintf("%s/%s", sname, pname)
|
||||
readers[rname] = urlReader(source + paths)
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range logFiles() {
|
||||
// Strip off leading '/'
|
||||
readers[path[1:]] = fileReader(path)
|
||||
}
|
||||
|
||||
return readers
|
||||
}
|
||||
|
||||
func findDiagnosticLogs(c *session.Session) (map[string]entryReader, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// When connected to VC, we collect vpxd.log and hostd.log for all cluster hosts attached to the datastore.
|
||||
// When connected to ESX, we just collect hostd.log.
|
||||
const (
|
||||
vpxdKey = "vpxd:vpxd.log"
|
||||
hostdKey = "hostd"
|
||||
)
|
||||
|
||||
logs := map[string]entryReader{}
|
||||
var err error
|
||||
|
||||
if c.IsVC() {
|
||||
logs[vpxdKey] = dlogReader{c, vpxdKey, nil}
|
||||
|
||||
var hosts []*object.HostSystem
|
||||
if c.Cluster == nil && c.Host != nil {
|
||||
hosts = []*object.HostSystem{c.Host}
|
||||
} else {
|
||||
hosts, err = c.Datastore.AttachedClusterHosts(context.TODO(), c.Cluster)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
lname := fmt.Sprintf("%s/%s", hostdKey, host)
|
||||
logs[lname] = dlogReader{c, hostdKey, host}
|
||||
}
|
||||
} else {
|
||||
logs[hostdKey] = dlogReader{c, hostdKey, nil}
|
||||
}
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func tarEntries(readers map[string]entryReader, out io.Writer) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
r, w := io.Pipe()
|
||||
t := tar.NewWriter(w)
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(1)
|
||||
|
||||
// stream tar to out
|
||||
go func() {
|
||||
_, err := io.Copy(out, r)
|
||||
if err != nil {
|
||||
log.Errorf("error copying tar: %s", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
for name, r := range readers {
|
||||
log.Infof("Collecting log with reader %s(%#v)", name, r)
|
||||
|
||||
e, err := r.open()
|
||||
if err != nil {
|
||||
log.Warningf("error reading %s(%s): %s\n", name, r, err)
|
||||
continue
|
||||
}
|
||||
var sz int64
|
||||
if e != nil {
|
||||
sz = e.Size()
|
||||
}
|
||||
|
||||
header := tar.Header{
|
||||
Name: name,
|
||||
Size: sz,
|
||||
Mode: 0640,
|
||||
ModTime: time.Now(),
|
||||
}
|
||||
|
||||
err = t.WriteHeader(&header)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to write header for %s: %s", header.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("%s has size %d", header.Name, header.Size)
|
||||
|
||||
// be explicit about the number of bytes to copy as the log files will likely
|
||||
// be written to during this exercise
|
||||
if e != nil {
|
||||
_, err = io.CopyN(t, e, sz)
|
||||
// #nosec: Errors unhandled.
|
||||
_ = e.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Failed to write content for %s: %s", header.Name, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
_ = t.Flush()
|
||||
// #nosec: Errors unhandled.
|
||||
_ = w.Close()
|
||||
wg.Wait()
|
||||
// #nosec: Errors unhandled.
|
||||
_ = r.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func zipEntries(readers map[string]entryReader, out *zip.Writer) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
defer out.Close()
|
||||
defer out.Flush()
|
||||
|
||||
for name, r := range readers {
|
||||
log.Infof("Collecting log with reader %s(%#v)", name, r)
|
||||
|
||||
e, err := r.open()
|
||||
if err != nil {
|
||||
log.Warningf("error reading %s(%s): %s\n", name, r, err)
|
||||
}
|
||||
var sz int64
|
||||
if e != nil {
|
||||
sz = e.Size()
|
||||
}
|
||||
header := &zip.FileHeader{
|
||||
Name: name,
|
||||
Method: zip.Deflate,
|
||||
}
|
||||
|
||||
header.SetModTime(time.Now())
|
||||
header.SetMode(0644)
|
||||
if sz > uint32max {
|
||||
header.UncompressedSize = uint32max
|
||||
} else {
|
||||
header.UncompressedSize = uint32(sz)
|
||||
}
|
||||
|
||||
w, err := out.CreateHeader(header)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create Zip writer for %s: %s", header.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("%s has size %d", header.Name, sz)
|
||||
|
||||
// be explicit about the number of bytes to copy as the log files will likely
|
||||
// be written to during this exercise
|
||||
if e != nil {
|
||||
_, err = io.CopyN(w, e, sz)
|
||||
// #nosec: Errors unhandled.
|
||||
_ = e.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Failed to write content for %s: %s", header.Name, err)
|
||||
continue
|
||||
}
|
||||
log.Infof("Wrote %d bytes to %s", sz, header.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func tailFile(wr io.Writer, file string, done *chan bool) error {
|
||||
defer trace.End(trace.Begin(file))
|
||||
|
||||
// By default, seek to EOF (if file doesn't exist)
|
||||
spos := tail.SeekInfo{
|
||||
Offset: 0,
|
||||
Whence: 2,
|
||||
}
|
||||
|
||||
// If the file exists, we want to go back tailLines lines
|
||||
// and pass that new offset into the TailFile() constructor
|
||||
// Per @fdawg4l, use bytes.LastIndex() and a 1k buffer to reduce
|
||||
// seeks/reads
|
||||
f, err := os.Open(file)
|
||||
if err == nil {
|
||||
spos = tail.SeekInfo{
|
||||
Offset: findSeekPos(f),
|
||||
Whence: 0,
|
||||
}
|
||||
}
|
||||
|
||||
tcfg := tail.Config{
|
||||
Location: &spos,
|
||||
ReOpen: true,
|
||||
MustExist: false,
|
||||
Follow: true,
|
||||
}
|
||||
|
||||
t, err := tail.TailFile(file, tcfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We KNOW there's a data race here.
|
||||
// But it doesn't break anything, so we just trap it.
|
||||
defer func() {
|
||||
t.Stop()
|
||||
_ = recover()
|
||||
}()
|
||||
for true {
|
||||
select {
|
||||
case l := <-t.Lines:
|
||||
if l.Err != nil {
|
||||
return l.Err
|
||||
}
|
||||
fmt.Fprint(wr, l.Text, "\n")
|
||||
case _ = <-*done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find the offset we want to start tailing from.
|
||||
// This should either be beginning-of-file or tailLines
|
||||
// newlines from the EOF.
|
||||
func findSeekPos(f *os.File) int64 {
|
||||
defer trace.End(trace.Begin(""))
|
||||
nlines := tailLines
|
||||
readPos, err := f.Seek(0, 2)
|
||||
// If for some reason we can't seek, we will just start tailing from beginning-of-file
|
||||
if err != nil {
|
||||
return int64(0)
|
||||
}
|
||||
|
||||
// Buffer so we can seek nBytes (default: 1k) at a time
|
||||
buf := make([]byte, nBytes)
|
||||
|
||||
for readPos > 0 {
|
||||
// Go back nBytes from the last readPos we've seen (stopping at beginning-of-file)
|
||||
// and read the next nBytes
|
||||
readPos -= int64(len(buf))
|
||||
if readPos < 0 {
|
||||
// We don't want to overlap our read with previous reads...
|
||||
buf = buf[:(int(readPos) + nBytes)]
|
||||
readPos = 0
|
||||
}
|
||||
bufend, err := f.ReadAt(buf, readPos)
|
||||
|
||||
// It's OK to get io.EOF here. Anything else is bad.
|
||||
if err != nil && err != io.EOF {
|
||||
log.Errorf("Error reading from file %s: %s", f.Name(), err)
|
||||
return 0
|
||||
}
|
||||
|
||||
// Start from the end of the buffer and start looking for newlines
|
||||
for bufend > 0 {
|
||||
bufend = bytes.LastIndexByte(buf[:bufend], '\n')
|
||||
if bufend < 0 {
|
||||
break
|
||||
}
|
||||
nlines--
|
||||
if nlines < 0 {
|
||||
return readPos + int64(bufend) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
575
vendor/github.com/vmware/vic/cmd/vicadmin/server.go
generated
vendored
575
vendor/github.com/vmware/vic/cmd/vicadmin/server.go
generated
vendored
@@ -1,575 +0,0 @@
|
||||
// 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 (
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/google/uuid"
|
||||
gorillacontext "github.com/gorilla/context"
|
||||
|
||||
"github.com/vmware/vic/lib/vicadmin"
|
||||
"github.com/vmware/vic/pkg/filelock"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
l net.Listener
|
||||
addr string
|
||||
mux *http.ServeMux
|
||||
uss *UserSessionStore
|
||||
}
|
||||
|
||||
// LoginPageData contains items needed to render the login page template
|
||||
type LoginPageData struct {
|
||||
Hostname string
|
||||
SystemTime string
|
||||
InvalidLogin string
|
||||
}
|
||||
|
||||
type format int
|
||||
|
||||
const (
|
||||
formatTGZ format = iota
|
||||
formatZip
|
||||
)
|
||||
|
||||
var beginningOfTime = time.Unix(0, 0).Format(time.RFC3339)
|
||||
|
||||
const (
|
||||
sessionExpiration = time.Hour * 24
|
||||
sessionCookieKey = "session-data"
|
||||
sessionCreationTimeKey = "created"
|
||||
sessionKey = "session-id"
|
||||
ipAddressKey = "ip"
|
||||
loginPagePath = "/authentication"
|
||||
authFailure = loginPagePath + "?unauthorized"
|
||||
genericErrorMessage = "Internal Server Error; see /var/log/vic/vicadmin.log for details" // for http errors that shouldn't be displayed in the browser to the user
|
||||
)
|
||||
|
||||
func (s *server) listen() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var err error
|
||||
var certificate *tls.Certificate
|
||||
s.uss = NewUserSessionStore()
|
||||
if vchConfig.HostCertificate != nil {
|
||||
certificate, err = vchConfig.HostCertificate.Certificate()
|
||||
} else {
|
||||
var c tls.Certificate
|
||||
if c, err = tls.X509KeyPair(
|
||||
rootConfig.serverCert.Cert.Bytes(),
|
||||
rootConfig.serverCert.Key.Bytes()); err != nil {
|
||||
log.Errorf("Could not generate self-signed certificate for vicadmin running with due to error %s", err.Error())
|
||||
return err
|
||||
}
|
||||
certificate = &c
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Could not load certificate from config - running without TLS: %s", err)
|
||||
|
||||
s.l, err = net.Listen("tcp", s.addr)
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME: assignment copies lock value to tlsConfig: crypto/tls.Config contains sync.Once contains sync.Mutex
|
||||
tlsconfig := func(c *tls.Config) *tls.Config {
|
||||
// if there are CAs, then TLS is enabled
|
||||
if len(vchConfig.CertificateAuthorities) != 0 {
|
||||
if c.ClientCAs == nil {
|
||||
c.ClientCAs = x509.NewCertPool()
|
||||
}
|
||||
if !c.ClientCAs.AppendCertsFromPEM(vchConfig.CertificateAuthorities) {
|
||||
log.Errorf("Unable to load CAs from config; client auth via certificate will not function")
|
||||
}
|
||||
c.ClientAuth = tls.VerifyClientCertIfGiven
|
||||
} else {
|
||||
log.Warnf("No certificate authorities found for certificate-based authentication. This may be intentional, however, certificate-based authentication is disabled")
|
||||
}
|
||||
|
||||
// #nosec: TLS InsecureSkipVerify may be true
|
||||
return &tls.Config{
|
||||
Certificates: c.Certificates,
|
||||
NameToCertificate: c.NameToCertificate,
|
||||
GetCertificate: c.GetCertificate,
|
||||
RootCAs: c.RootCAs,
|
||||
NextProtos: c.NextProtos,
|
||||
ServerName: c.ServerName,
|
||||
ClientAuth: c.ClientAuth,
|
||||
ClientCAs: c.ClientCAs,
|
||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||
CipherSuites: c.CipherSuites,
|
||||
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
||||
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
||||
SessionTicketKey: c.SessionTicketKey,
|
||||
ClientSessionCache: c.ClientSessionCache,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: c.MaxVersion,
|
||||
CurvePreferences: c.CurvePreferences,
|
||||
}
|
||||
}(tlsconfig.ServerDefault())
|
||||
|
||||
tlsconfig.Certificates = []tls.Certificate{*certificate}
|
||||
|
||||
innerListener, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s.l = tls.NewListener(innerListener, tlsconfig)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) listenPort() int {
|
||||
return s.l.Addr().(*net.TCPAddr).Port
|
||||
}
|
||||
|
||||
// Enforces authentication on route `link` and runs `handler` on successful auth
|
||||
func (s *server) AuthenticatedHandle(link string, h http.Handler) {
|
||||
s.Authenticated(link, h.ServeHTTP)
|
||||
}
|
||||
|
||||
func (s *server) Handle(link string, h http.Handler) {
|
||||
log.Debugf("%s --- %s", time.Now().String(), link)
|
||||
s.mux.Handle(link, gorillacontext.ClearHandler(h))
|
||||
}
|
||||
|
||||
// Enforces authentication on route `link` and runs `handler` on successful auth
|
||||
func (s *server) Authenticated(link string, handler func(http.ResponseWriter, *http.Request)) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
authHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
// #nosec: Errors unhandled because it is okay if the cookie doesn't exist.
|
||||
websession, _ := s.uss.cookies.Get(r, sessionCookieKey)
|
||||
|
||||
if len(r.TLS.PeerCertificates) > 0 {
|
||||
// the user is authenticated by certificate at connection time
|
||||
log.Infof("Authenticated connection via client certificate with serial %s from %s", r.TLS.PeerCertificates[0].SerialNumber, r.RemoteAddr)
|
||||
key := uuid.New().String()
|
||||
|
||||
vs, err := vSphereSessionGet(&rootConfig.Config)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get vSphere session with default config for cert-auth'd user")
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
usersess := s.uss.Add(key, &rootConfig.Config, vs)
|
||||
|
||||
timeNow, err := usersess.created.MarshalText()
|
||||
if err != nil {
|
||||
// it's probably safe to ignore this error since we just created usersess.created when we called Add() above
|
||||
// but just in case..
|
||||
log.Errorf("Failed to unmarshal time object %+v into text due to error: %s", usersess.created, err)
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
websession.Values[sessionCreationTimeKey] = string(timeNow)
|
||||
websession.Values[sessionKey] = key
|
||||
|
||||
remoteAddr := strings.SplitN(r.RemoteAddr, ":", 2)
|
||||
if len(remoteAddr) != 2 { // TODO: ctrl+f RemoteAddr and move this routine to helper
|
||||
log.Errorf("Format of IP address %s (should be IP:PORT) not recognized", r.RemoteAddr)
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
websession.Values[ipAddressKey] = remoteAddr[0]
|
||||
err = websession.Save(r, w)
|
||||
if err != nil {
|
||||
log.Errorf("Could not create session for user authenticated via client certificate due to error \"%s\"", err.Error())
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// user was authenticated via cert
|
||||
handler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
c := websession.Values[sessionCreationTimeKey]
|
||||
if c == nil { // no cookie, so redirect to login
|
||||
log.Infof("No authentication token: %+v", websession.Values)
|
||||
http.Redirect(w, r, loginPagePath, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// here we have a cookie, but we need to make sure it's not expired:
|
||||
// parse the cookie creation time
|
||||
created, err := time.Parse(time.RFC3339, c.(string))
|
||||
if err != nil {
|
||||
// we pulled this value out of a cookie, so if it doesn't parse, it might've been tampered with
|
||||
// though the cookie's encrypted so that would destroy the whole cookie..
|
||||
// Handling the error in any case:
|
||||
log.Errorf("Couldn't parse time out of retrieved cookie due to error %s", err.Error())
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// cookie exists but is expired
|
||||
if time.Since(created) > sessionExpiration {
|
||||
http.Redirect(w, r, loginPagePath, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
// verify that the auth token is being used by the same IP it was created for
|
||||
c = websession.Values[ipAddressKey]
|
||||
if c == nil {
|
||||
log.Errorf("Couldn't get IP address out of cookie for user connecting from %s at %s", r.RemoteAddr, time.Now())
|
||||
http.Redirect(w, r, loginPagePath, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
connectingAddr := strings.SplitN(r.RemoteAddr, ":", 2)
|
||||
if len(connectingAddr) != 2 { // TODO: ctrl+f r.RemoteAddr and move this routine to helper
|
||||
log.Errorf("Format of IP address %s (should be IP:PORT) not recognized", r.RemoteAddr)
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if c.(string) != connectingAddr[0] {
|
||||
log.Warnf("User with a valid auth cookie from %s has reappeared at %s. Their token will be expired.", c.(string), connectingAddr[0])
|
||||
s.logoutHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// if the date & remote IP on the cookie were valid, then the user is authenticated
|
||||
log.Infof("User with a valid auth cookie at %s is authenticated.", connectingAddr[0])
|
||||
handler(w, r)
|
||||
}
|
||||
s.Handle(link, http.HandlerFunc(authHandler))
|
||||
}
|
||||
|
||||
// renders the page for login and handles authorization requests
|
||||
func (s *server) loginPage(res http.ResponseWriter, req *http.Request) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
var invalidLoginMessage = ""
|
||||
|
||||
if req.Method == "POST" {
|
||||
// take the form data and use it to try to authenticate with vsphere
|
||||
|
||||
// create a userconfig
|
||||
userconfig := session.Config{
|
||||
Insecure: false,
|
||||
Thumbprint: rootConfig.Thumbprint,
|
||||
Keepalive: rootConfig.Keepalive,
|
||||
ClusterPath: rootConfig.ClusterPath,
|
||||
DatacenterPath: rootConfig.DatacenterPath,
|
||||
DatastorePath: rootConfig.DatastorePath,
|
||||
HostPath: rootConfig.Config.HostPath,
|
||||
PoolPath: rootConfig.PoolPath,
|
||||
User: url.UserPassword(req.FormValue("username"), req.FormValue("password")),
|
||||
Service: rootConfig.Service,
|
||||
}
|
||||
|
||||
// check login
|
||||
vs, err := vSphereSessionGet(&userconfig)
|
||||
if err != nil || vs == nil {
|
||||
// something went wrong or we could not authenticate
|
||||
log.Warnf("%s failed to authenticate ", req.RemoteAddr)
|
||||
invalidLoginMessage = "Authentication failed due to incorrect credential(s)"
|
||||
hostName, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostName = "VCH"
|
||||
}
|
||||
loginPageData := &LoginPageData{
|
||||
Hostname: hostName,
|
||||
SystemTime: time.Now().Format(time.UnixDate),
|
||||
InvalidLogin: invalidLoginMessage,
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles("auth.html")
|
||||
err = tmpl.ExecuteTemplate(res, "auth.html", loginPageData)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing template: %s", err)
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// successful login above; user is authenticated, reported for audit purposes
|
||||
log.Debugf("User %s from %s was successfully authenticated", userconfig.User.Username(), req.RemoteAddr)
|
||||
|
||||
// create a token to save as an encrypted & signed cookie
|
||||
websession, err := s.uss.cookies.Get(req, sessionCookieKey)
|
||||
if websession == nil {
|
||||
log.Errorf("Web session object could not be created due to error %s", err)
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
key := uuid.New().String()
|
||||
userconfig.User = nil
|
||||
userconfig.Service = ""
|
||||
us := s.uss.Add(key, &userconfig, vs)
|
||||
|
||||
timeNow, err := us.created.MarshalText()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to unmarshal time object %+v into text due to error: %s", us.created, err)
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
websession.Values[sessionCreationTimeKey] = string(timeNow)
|
||||
websession.Values[sessionKey] = key
|
||||
|
||||
remoteAddr := strings.SplitN(req.RemoteAddr, ":", 2)
|
||||
if len(remoteAddr) != 2 { // TODO: ctrl+f RemoteAddr and move this routine to helper
|
||||
log.Errorf("Format of IP address %s (should be IP:PORT) not recognized", req.RemoteAddr)
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
websession.Values[ipAddressKey] = remoteAddr[0]
|
||||
|
||||
if err := websession.Save(req, res); err != nil {
|
||||
log.Errorf("\"%s\" occurred while trying to save session to browser", err.Error())
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// redirect to dashboard
|
||||
http.Redirect(res, req, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// Render login page (shows up on non-POST requests)
|
||||
hostName, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostName = "VCH"
|
||||
}
|
||||
loginPageData := &LoginPageData{
|
||||
Hostname: hostName,
|
||||
SystemTime: time.Now().Format(time.UnixDate),
|
||||
InvalidLogin: invalidLoginMessage,
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles("auth.html")
|
||||
err = tmpl.ExecuteTemplate(res, "auth.html", loginPageData)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing template: %s", err)
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) serve() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
s.mux = http.NewServeMux()
|
||||
|
||||
// unauthenticated routes
|
||||
// these assets bypass authentication & are world-readable
|
||||
s.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("css/"))))
|
||||
s.Handle("/images/", http.StripPrefix("/images/", http.FileServer(http.Dir("images/"))))
|
||||
s.Handle("/fonts/", http.StripPrefix("/fonts/", http.FileServer(http.Dir("fonts/"))))
|
||||
|
||||
s.Handle(loginPagePath, http.HandlerFunc(s.loginPage))
|
||||
|
||||
// authenticated routes
|
||||
// tar of appliance system logs
|
||||
s.Authenticated("/logs.tar.gz", s.tarDefaultLogs)
|
||||
s.Authenticated("/logs.zip", s.zipDefaultLogs)
|
||||
|
||||
// tar of appliance system logs + container logs
|
||||
s.Authenticated("/container-logs.tar.gz", s.tarContainerLogs)
|
||||
s.Authenticated("/container-logs.zip", s.zipContainerLogs)
|
||||
|
||||
// get single log file (no tail)
|
||||
s.Authenticated("/logs/", func(w http.ResponseWriter, r *http.Request) {
|
||||
file := strings.TrimPrefix(r.URL.Path, "/logs/")
|
||||
log.Debugf("writing contents for %s", file)
|
||||
writeLogFiles(w, r, file, true)
|
||||
})
|
||||
|
||||
// get single log file (with tail)
|
||||
s.Authenticated("/logs/tail/", func(w http.ResponseWriter, r *http.Request) {
|
||||
file := strings.TrimPrefix(r.URL.Path, "/logs/tail/")
|
||||
log.Debugf("writing contents for %s", file)
|
||||
writeLogFiles(w, r, file, false)
|
||||
s.tailFiles(w, r, []string{filepath.Join(logFileDir, file)})
|
||||
})
|
||||
|
||||
s.Authenticated("/logout", s.logoutHandler)
|
||||
s.Authenticated("/", s.index)
|
||||
server := &http.Server{
|
||||
Handler: s.mux,
|
||||
}
|
||||
|
||||
return server.Serve(s.l)
|
||||
}
|
||||
|
||||
func (s *server) stop() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if s.l != nil {
|
||||
err := s.l.Close()
|
||||
s.l = nil
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// logout handler expires the user's session cookie by setting its creation time to the beginning of time
|
||||
func (s *server) logoutHandler(res http.ResponseWriter, req *http.Request) {
|
||||
// #nosec: Errors unhandled.
|
||||
websession, _ := s.uss.cookies.Get(req, sessionCookieKey)
|
||||
// ignore parsing/marshalling errors because we're parsing a hardcoded beginning-of-time string
|
||||
websession.Values[sessionCreationTimeKey] = beginningOfTime
|
||||
if err := websession.Save(req, res); err != nil {
|
||||
http.Error(res, "Failed to expire user session", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
s.uss.Delete(websession.Values[sessionKey].(string))
|
||||
http.Redirect(res, req, "/authentication", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *server) bundleContainerLogs(res http.ResponseWriter, req *http.Request, f format) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
logrotateLock := filelock.NewFileLock(filelock.LogRotateLockName)
|
||||
if err := logrotateLock.Acquire(); err != nil {
|
||||
log.Errorf("Failed to acquire logrotate lock: %s", err)
|
||||
} else {
|
||||
defer func() { logrotateLock.Release() }()
|
||||
}
|
||||
|
||||
readers := configureReaders()
|
||||
c, err := s.getSessionFromRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get vSphere session while bundling container logs due to error: %s", err.Error())
|
||||
http.Redirect(res, req, "/logout", http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
logs, err := findDatastoreLogs(c)
|
||||
if err != nil {
|
||||
log.Warningf("error searching datastore: %s", err)
|
||||
} else {
|
||||
for key, rdr := range logs {
|
||||
readers[key] = rdr
|
||||
}
|
||||
}
|
||||
|
||||
logs, err = findDiagnosticLogs(c)
|
||||
if err != nil {
|
||||
log.Warningf("error collecting diagnostic logs: %s", err)
|
||||
} else {
|
||||
for key, rdr := range logs {
|
||||
readers[key] = rdr
|
||||
}
|
||||
}
|
||||
|
||||
s.bundleLogs(res, req, readers, f)
|
||||
}
|
||||
|
||||
func (s *server) tarDefaultLogs(res http.ResponseWriter, req *http.Request) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
s.bundleLogs(res, req, configureReaders(), formatTGZ)
|
||||
}
|
||||
|
||||
func (s *server) zipDefaultLogs(res http.ResponseWriter, req *http.Request) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
s.bundleLogs(res, req, configureReaders(), formatZip)
|
||||
}
|
||||
|
||||
func (s *server) bundleLogs(res http.ResponseWriter, req *http.Request, readers map[string]entryReader, f format) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var err error
|
||||
if f == formatTGZ {
|
||||
res.Header().Set("Content-Type", "application/x-gzip")
|
||||
z := gzip.NewWriter(res)
|
||||
defer z.Close()
|
||||
err = tarEntries(readers, z)
|
||||
} else if f == formatZip {
|
||||
res.Header().Set("Content-Type", "application/zip")
|
||||
z := zip.NewWriter(res)
|
||||
defer z.Close()
|
||||
err = zipEntries(readers, z)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error bundling logs: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) tarContainerLogs(res http.ResponseWriter, req *http.Request) {
|
||||
s.bundleContainerLogs(res, req, formatTGZ)
|
||||
}
|
||||
|
||||
func (s *server) zipContainerLogs(res http.ResponseWriter, req *http.Request) {
|
||||
s.bundleContainerLogs(res, req, formatZip)
|
||||
}
|
||||
|
||||
func (s *server) tailFiles(res http.ResponseWriter, req *http.Request, names []string) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
cc := res.(http.CloseNotifier).CloseNotify()
|
||||
|
||||
fw := &flushWriter{
|
||||
f: res.(http.Flusher),
|
||||
w: res,
|
||||
}
|
||||
|
||||
done := make(chan bool)
|
||||
for _, file := range names {
|
||||
go tailFile(fw, file, &done)
|
||||
}
|
||||
|
||||
<-cc
|
||||
for range names {
|
||||
done <- true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) index(res http.ResponseWriter, req *http.Request) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
ctx := context.Background()
|
||||
sess, err := s.getSessionFromRequest(ctx, req)
|
||||
if err != nil {
|
||||
log.Errorf("While loading index page got %s looking up a vSphere session", err.Error())
|
||||
http.Redirect(res, req, "/logout", http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
v := vicadmin.NewValidator(ctx, &vchConfig, sess)
|
||||
if sess == nil {
|
||||
// We're unable to connect to vSphere, so display an error message
|
||||
v.VCHIssues = template.HTML("<span class=\"error-message\">We're having some trouble communicating with vSphere. <a href=\"/logout\">Logging in again</a> may resolve the issue.</span>\n")
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles("dashboard.html")
|
||||
err = tmpl.ExecuteTemplate(res, "dashboard.html", v)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing template: %s", err)
|
||||
}
|
||||
}
|
||||
133
vendor/github.com/vmware/vic/cmd/vicadmin/usersession.go
generated
vendored
133
vendor/github.com/vmware/vic/cmd/vicadmin/usersession.go
generated
vendored
@@ -1,133 +0,0 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/sessions"
|
||||
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
// UserSession holds a user's session metadata
|
||||
type UserSession struct {
|
||||
id string
|
||||
created time.Time
|
||||
config *session.Config
|
||||
vsphere *session.Session
|
||||
}
|
||||
|
||||
// UserSessionStore holds and manages user sessions
|
||||
type UserSessionStore struct {
|
||||
mutex sync.RWMutex
|
||||
sessions map[string]*UserSession
|
||||
ticker *time.Ticker
|
||||
cookies *sessions.CookieStore
|
||||
}
|
||||
|
||||
type UserSessionStorer interface {
|
||||
Add(id string, config *session.Config, vs *session.Session) *UserSession
|
||||
Delete(id string)
|
||||
VSphere(id string) (vSphereSession *session.Session, err error)
|
||||
UserSession(id string) *UserSession
|
||||
}
|
||||
|
||||
// Add a session. VS may be nil if host is plain ESX
|
||||
func (u *UserSessionStore) Add(id string, config *session.Config, vs *session.Session) *UserSession {
|
||||
u.mutex.Lock()
|
||||
defer u.mutex.Unlock()
|
||||
sess := &UserSession{
|
||||
id: id,
|
||||
created: time.Now(),
|
||||
// TODO strip out config cause it's not needed anymore, but shows up in a number of places
|
||||
config: config,
|
||||
vsphere: vs,
|
||||
}
|
||||
u.sessions[id] = sess
|
||||
return sess
|
||||
}
|
||||
|
||||
func (u *UserSessionStore) Delete(id string) {
|
||||
u.mutex.Lock()
|
||||
defer u.mutex.Unlock()
|
||||
delete(u.sessions, id)
|
||||
}
|
||||
|
||||
// Grabs the UserSession metadata object and doesn't establish a connection to vSphere
|
||||
func (u *UserSessionStore) UserSession(id string) *UserSession {
|
||||
u.mutex.RLock()
|
||||
defer u.mutex.RUnlock()
|
||||
return u.sessions[id]
|
||||
}
|
||||
|
||||
// Returns a vSphere session object. Caller responsible for error handling/logout
|
||||
func (u *UserSessionStore) VSphere(ctx context.Context, id string) (*session.Session, error) {
|
||||
us := u.UserSession(id)
|
||||
if us == nil {
|
||||
return nil, fmt.Errorf("User session with unique ID %s does not exist", id)
|
||||
}
|
||||
if us.vsphere == nil {
|
||||
return nil, fmt.Errorf("No vSphere session found for user: %s", id)
|
||||
}
|
||||
|
||||
vsphus, err := us.vsphere.SessionManager.UserSession(ctx)
|
||||
if err != nil || vsphus == nil {
|
||||
if err != nil {
|
||||
log.Warnf("Failed to validate user %s session: %v", id, err)
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("User %s session has expired", id)
|
||||
}
|
||||
log.Infof("Found vSphere session for vicadmin usersession %s", id)
|
||||
return us.vsphere, nil
|
||||
}
|
||||
|
||||
// reaper takes abandoned sessions to a farm upstate so they don't build up forever
|
||||
func (u *UserSessionStore) reaper() {
|
||||
for range u.ticker.C {
|
||||
for id, session := range u.sessions {
|
||||
if time.Since(session.created) > sessionExpiration {
|
||||
u.Delete(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewUserSessionStore creates & initializes a UserSessionStore and starts a session reaper in the background
|
||||
func NewUserSessionStore() *UserSessionStore {
|
||||
u := &UserSessionStore{
|
||||
sessions: make(map[string]*UserSession),
|
||||
ticker: time.NewTicker(time.Minute * 10),
|
||||
mutex: sync.RWMutex{},
|
||||
cookies: sessions.NewCookieStore(
|
||||
[]byte(securecookie.GenerateRandomKey(64)),
|
||||
[]byte(securecookie.GenerateRandomKey(32))),
|
||||
}
|
||||
u.cookies.Options = &sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: int(sessionExpiration.Seconds()),
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
}
|
||||
go u.reaper()
|
||||
return u
|
||||
}
|
||||
634
vendor/github.com/vmware/vic/cmd/vicadmin/vicadm.go
generated
vendored
634
vendor/github.com/vmware/vic/cmd/vicadmin/vicadm.go
generated
vendored
@@ -1,634 +0,0 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/govmomi"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
vchconfig "github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/lib/pprof"
|
||||
"github.com/vmware/vic/pkg/certificate"
|
||||
viclog "github.com/vmware/vic/pkg/log"
|
||||
"github.com/vmware/vic/pkg/log/syslog"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/compute"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
timeout = time.Duration(2 * time.Second)
|
||||
)
|
||||
|
||||
type serverCertificate struct {
|
||||
Key bytes.Buffer
|
||||
Cert bytes.Buffer
|
||||
}
|
||||
|
||||
type vicAdminConfig struct {
|
||||
session.Config
|
||||
addr string
|
||||
tls bool
|
||||
serverCert *serverCertificate
|
||||
}
|
||||
|
||||
var (
|
||||
logFileDir = "/var/log/vic"
|
||||
logFileListPrefixes = []string{
|
||||
"docker-personality.log",
|
||||
"port-layer.log",
|
||||
"vicadmin.log",
|
||||
"init.log",
|
||||
"kubelet-starter.log",
|
||||
"virtual-kubelet.log",
|
||||
}
|
||||
|
||||
// VMFiles is the set of files to collect per VM associated with the VCH
|
||||
vmFiles = []string{
|
||||
"output.log",
|
||||
"vmware.log",
|
||||
"tether.debug",
|
||||
}
|
||||
|
||||
// this struct holds root credentials or vSphere extension private key instead if available
|
||||
// if you are exposing log information to a user, create a new session for that user, do not use this one
|
||||
// also, 'root' is a pun -- this is both the "root" config, e.g., the base config, and the one w/ root creds
|
||||
rootConfig vicAdminConfig
|
||||
|
||||
resources vchconfig.Resources
|
||||
|
||||
vchConfig vchconfig.VirtualContainerHostConfigSpec
|
||||
|
||||
datastore types.ManagedObjectReference
|
||||
)
|
||||
|
||||
type logfile struct {
|
||||
URL url.URL
|
||||
VMName string
|
||||
Host *object.HostSystem
|
||||
}
|
||||
|
||||
func Init() {
|
||||
// #nosec: Errors unhandled.
|
||||
_ = pprof.StartPprof("vicadmin", pprof.VicadminPort)
|
||||
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// load the vch config
|
||||
src, err := extraconfig.GuestInfoSource()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to load configuration from guestinfo")
|
||||
return
|
||||
}
|
||||
|
||||
extraconfig.Decode(src, &vchConfig)
|
||||
|
||||
logcfg := viclog.NewLoggingConfig()
|
||||
if vchConfig.Diagnostics.DebugLevel > 0 {
|
||||
logcfg.Level = log.DebugLevel
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
syslog.Logger.Level = log.DebugLevel
|
||||
}
|
||||
|
||||
if vchConfig.Diagnostics.SysLogConfig != nil {
|
||||
logcfg.Syslog = &viclog.SyslogConfig{
|
||||
Network: vchConfig.Diagnostics.SysLogConfig.Network,
|
||||
RAddr: vchConfig.Diagnostics.SysLogConfig.RAddr,
|
||||
Priority: syslog.Info | syslog.Daemon,
|
||||
}
|
||||
}
|
||||
|
||||
viclog.Init(logcfg)
|
||||
trace.InitLogger(logcfg)
|
||||
|
||||
// We don't want to run this as root.
|
||||
ud := syscall.Getuid()
|
||||
gd := syscall.Getgid()
|
||||
log.Info(fmt.Sprintf("Current UID/GID = %d/%d", ud, gd))
|
||||
// TODO: Enable this after we figure out to NOT break the test suite with it.
|
||||
// if ud == 0 {
|
||||
// log.Errorf("Error: vicadmin must not run as root.")
|
||||
// time.Sleep(60 * time.Second)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
|
||||
flag.StringVar(&rootConfig.addr, "l", "client.localhost:2378", "Listen address")
|
||||
|
||||
// TODO: This should all be pulled from the config
|
||||
flag.StringVar(&rootConfig.DatacenterPath, "dc", "", "Path of the datacenter")
|
||||
flag.StringVar(&rootConfig.ClusterPath, "cluster", "", "Path of the cluster")
|
||||
flag.StringVar(&rootConfig.PoolPath, "pool", "", "Path of the resource pool")
|
||||
|
||||
if vchConfig.HostCertificate == nil {
|
||||
log.Infoln("--no-tls is enabled on the personality")
|
||||
rootConfig.serverCert = &serverCertificate{}
|
||||
rootConfig.serverCert.Cert, rootConfig.serverCert.Key, err = certificate.CreateSelfSigned(rootConfig.addr, []string{"VMware, Inc."}, 2048)
|
||||
if err != nil {
|
||||
log.Errorf("--no-tls was specified but we couldn't generate a self-signed cert for vic admin due to error %s so vicadmin will not run", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: pull the rest from flags
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
type entryReader interface {
|
||||
open() (entry, error)
|
||||
}
|
||||
|
||||
type entry interface {
|
||||
io.ReadCloser
|
||||
Name() string
|
||||
Size() int64
|
||||
}
|
||||
|
||||
type bytesEntry struct {
|
||||
io.ReadCloser
|
||||
name string
|
||||
size int64
|
||||
}
|
||||
|
||||
func (e *bytesEntry) Name() string {
|
||||
return e.name
|
||||
}
|
||||
|
||||
func (e *bytesEntry) Size() int64 {
|
||||
return e.size
|
||||
}
|
||||
|
||||
func newBytesEntry(name string, b []byte) entry {
|
||||
r := bytes.NewReader(b)
|
||||
|
||||
return &bytesEntry{
|
||||
ReadCloser: ioutil.NopCloser(r),
|
||||
size: int64(r.Len()),
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
type versionReader string
|
||||
|
||||
func (path versionReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(string(path)))
|
||||
return newBytesEntry(string(path), []byte(version.GetBuild().ShortVersion())), nil
|
||||
}
|
||||
|
||||
type commandReader string
|
||||
|
||||
func (path commandReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(string(path)))
|
||||
|
||||
args := strings.Split(string(path), " ")
|
||||
// #nosec: Subprocess launching with variable
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %s", err, string(output))
|
||||
}
|
||||
|
||||
return newBytesEntry(string(path), output), nil
|
||||
}
|
||||
|
||||
type fileReader string
|
||||
|
||||
type fileEntry struct {
|
||||
io.ReadCloser
|
||||
os.FileInfo
|
||||
}
|
||||
|
||||
func (path fileReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(string(path)))
|
||||
|
||||
f, err := os.Open(string(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := os.Stat(string(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Files in /proc always have struct stat.st_size==0, so just read it into memory.
|
||||
if s.Size() == 0 && strings.HasPrefix(f.Name(), "/proc/") {
|
||||
b, err := ioutil.ReadAll(f)
|
||||
// #nosec: Errors unhandled.
|
||||
_ = f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newBytesEntry(f.Name(), b), nil
|
||||
}
|
||||
|
||||
return &fileEntry{
|
||||
ReadCloser: f,
|
||||
FileInfo: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type urlReader string
|
||||
|
||||
func httpEntry(name string, res *http.Response) (entry, error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(res.Status)
|
||||
}
|
||||
|
||||
if res.ContentLength > 0 {
|
||||
return &bytesEntry{
|
||||
ReadCloser: res.Body,
|
||||
size: res.ContentLength,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// If we don't have Content-Length, read into memory for the tar.Header.Size
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
// #nosec: Errors unhandled.
|
||||
_ = res.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newBytesEntry(name, body), nil
|
||||
}
|
||||
|
||||
func (path urlReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(string(path)))
|
||||
client := http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
res, err := client.Get(string(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return httpEntry(string(path), res)
|
||||
}
|
||||
|
||||
type datastoreReader struct {
|
||||
ds *object.Datastore
|
||||
path string
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// listVMPaths returns an array of datastore paths for VMs associated with the
|
||||
// VCH - this includes containerVMs and the appliance
|
||||
func listVMPaths(ctx context.Context, s *session.Session) ([]logfile, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var err error
|
||||
var children []*vm.VirtualMachine
|
||||
|
||||
if len(vchConfig.ComputeResources) == 0 {
|
||||
return nil, errors.New("compute resources is empty")
|
||||
}
|
||||
|
||||
ref := vchConfig.ComputeResources[0]
|
||||
rp := compute.NewResourcePool(ctx, s, ref)
|
||||
if children, err = rp.GetChildrenVMs(ctx, s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
self, err := guest.GetSelf(ctx, s)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get handle to self for log filtering")
|
||||
}
|
||||
|
||||
log.Infof("Found %d candidate VMs in resource pool %s for log collection", len(children), ref.String())
|
||||
|
||||
logfiles := []logfile{}
|
||||
for _, child := range children {
|
||||
path, err := child.VMPathNameAsURL(ctx)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get datastore path for child VM %s: %s", child.Reference(), err)
|
||||
// we need to get as many logs as possible
|
||||
continue
|
||||
}
|
||||
|
||||
logname, err := child.Name(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get the vm name for %s: %s", child.Reference(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if self != nil && child.Reference().String() == self.Reference().String() {
|
||||
// FIXME: until #2630 is addressed, and we confirm this filters secrets from appliance vmware.log as well,
|
||||
// we're skipping direct collection of those logs.
|
||||
log.Info("Skipping collection for appliance VM (moref match)")
|
||||
continue
|
||||
}
|
||||
|
||||
// backup check if we were unable to initialize self for some reason
|
||||
if self == nil && logname == vchConfig.Name {
|
||||
log.Info("Skipping collection for appliance VM (string match)")
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("Adding VM for log collection: %s", path.String())
|
||||
h, err := child.HostSystem(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to get host system for VM %s - will use default host for log collection: %s", logname, err)
|
||||
}
|
||||
|
||||
log := logfile{
|
||||
URL: path,
|
||||
VMName: logname,
|
||||
Host: h,
|
||||
}
|
||||
|
||||
logfiles = append(logfiles, log)
|
||||
}
|
||||
|
||||
log.Infof("Collecting logs from %d VMs", len(logfiles))
|
||||
log.Infof("Found VM paths are : %#v", logfiles)
|
||||
return logfiles, nil
|
||||
}
|
||||
|
||||
// addApplianceLogs whitelists the logs to include for the appliance.
|
||||
// TODO: once we've started encrypting all potentially sensitive data and filtering out guestinfo.ovfEnv
|
||||
// we can resume collection of vmware.log and drop the appliance specific handling
|
||||
func addApplianceLogs(ctx context.Context, s *session.Session, readers map[string]entryReader) error {
|
||||
self, err := guest.GetSelf(ctx, s)
|
||||
if err != nil || self == nil {
|
||||
return fmt.Errorf("Unable to collect appliance logs due to unknown self-reference: %s", err)
|
||||
}
|
||||
|
||||
self2 := vm.NewVirtualMachineFromVM(ctx, s, self)
|
||||
path, err := self2.VMPathNameAsURL(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ds, err := s.Finder.Datastore(ctx, path.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h, err := self2.HostSystem(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to get host system for appliance - will use default host for log collection: %s", err)
|
||||
} else {
|
||||
ctx = ds.HostContext(ctx, h)
|
||||
}
|
||||
|
||||
wpath := fmt.Sprintf("appliance/tether.debug")
|
||||
rpath := fmt.Sprintf("%s/%s", path.Path, "tether.debug")
|
||||
log.Infof("Processed File read Path : %s", rpath)
|
||||
log.Infof("Processed File write Path : %s", wpath)
|
||||
readers[wpath] = datastoreReader{
|
||||
ds: ds,
|
||||
path: rpath,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// find datastore logs for the appliance itself and all containers
|
||||
func findDatastoreLogs(c *session.Session) (map[string]entryReader, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// Create an empty reader as opposed to a nil reader...
|
||||
readers := map[string]entryReader{}
|
||||
ctx := context.Background()
|
||||
|
||||
logfiles, err := listVMPaths(ctx, c)
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("unable to perform datastore log collection due to failure looking up paths: %s", err)
|
||||
log.Error(detail)
|
||||
return nil, errors.New(detail)
|
||||
}
|
||||
|
||||
err = addApplianceLogs(ctx, c, readers)
|
||||
if err != nil {
|
||||
log.Errorf("Issue collecting appliance logs: %s", err)
|
||||
}
|
||||
|
||||
for _, logfile := range logfiles {
|
||||
log.Debugf("Assembling datastore readers for %s", logfile.URL.String())
|
||||
// obtain datastore object
|
||||
ds, err := c.Finder.Datastore(ctx, logfile.URL.Host)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to acquire reference to datastore %s: %s", logfile.URL.Host, err)
|
||||
continue
|
||||
}
|
||||
|
||||
hCtx := ctx
|
||||
if logfile.Host != nil {
|
||||
hCtx = ds.HostContext(ctx, logfile.Host)
|
||||
}
|
||||
|
||||
// generate the full paths to collect
|
||||
for _, file := range vmFiles {
|
||||
wpath := fmt.Sprintf("%s/%s", logfile.VMName, file)
|
||||
rpath := fmt.Sprintf("%s/%s", logfile.URL.Path, file)
|
||||
log.Infof("Processed File read Path : %s", rpath)
|
||||
log.Infof("Processed File write Path : %s", wpath)
|
||||
readers[wpath] = datastoreReader{
|
||||
ds: ds,
|
||||
path: rpath,
|
||||
ctx: hCtx,
|
||||
}
|
||||
|
||||
log.Debugf("Added log file for collection: %s", logfile.URL.String())
|
||||
}
|
||||
}
|
||||
|
||||
return readers, nil
|
||||
}
|
||||
|
||||
func (r datastoreReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(r.path))
|
||||
|
||||
u, ticket, err := r.ds.ServiceTicket(r.ctx, r.path, "GET")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if ticket != nil {
|
||||
req.AddCookie(ticket)
|
||||
}
|
||||
|
||||
res, err := r.ds.Client().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return httpEntry(r.path, res)
|
||||
}
|
||||
|
||||
// stripCredentials removes user credentials from "in"
|
||||
func stripCredentials(in *session.Session) error {
|
||||
serviceURL, err := soap.ParseURL(rootConfig.Service)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing service URL from config: %s", err)
|
||||
return err
|
||||
}
|
||||
serviceURL.User = nil
|
||||
newclient, err := govmomi.NewClient(context.Background(), serviceURL, true)
|
||||
if err != nil {
|
||||
log.Errorf("Error creating new govmomi client without credentials but with auth cookie: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
newclient.Jar = in.Client.Jar
|
||||
in.Client = newclient
|
||||
return nil
|
||||
}
|
||||
|
||||
func vSphereSessionGet(sessconfig *session.Config) (*session.Session, error) {
|
||||
s := session.NewSession(sessconfig)
|
||||
s.UserAgent = version.UserAgent("vic-admin")
|
||||
|
||||
ctx := context.Background()
|
||||
_, err := s.Connect(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to connect: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = s.Populate(ctx)
|
||||
if err != nil {
|
||||
// not a critical error for vicadmin
|
||||
log.Warnf("Unable to populate session: %s", err)
|
||||
}
|
||||
usersession, err := s.SessionManager.UserSession(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Got %s while creating user session", err)
|
||||
return nil, err
|
||||
}
|
||||
if usersession == nil {
|
||||
return nil, fmt.Errorf("vSphere session is no longer valid")
|
||||
}
|
||||
|
||||
log.Infof("Got session from vSphere with key: %s username: %s", usersession.Key, usersession.UserName)
|
||||
|
||||
err = stripCredentials(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *server) getSessionFromRequest(ctx context.Context, r *http.Request) (*session.Session, error) {
|
||||
sessionData, err := s.uss.cookies.Get(r, sessionCookieKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var d interface{}
|
||||
var ok bool
|
||||
if d, ok = sessionData.Values[sessionKey]; !ok {
|
||||
return nil, fmt.Errorf("User-provided cookie did not contain a session ID -- it is corrupt or tampered")
|
||||
}
|
||||
c, err := s.uss.VSphere(ctx, d.(string))
|
||||
return c, err
|
||||
}
|
||||
|
||||
type flushWriter struct {
|
||||
f http.Flusher
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (fw *flushWriter) Write(p []byte) (int, error) {
|
||||
n, err := fw.w.Write(p)
|
||||
|
||||
fw.f.Flush()
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func main() {
|
||||
Init()
|
||||
|
||||
if version.Show() {
|
||||
fmt.Fprintf(os.Stdout, "%s\n", version.String())
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: these should just be consumed directly inside Session
|
||||
rootConfig.Service = vchConfig.Target
|
||||
rootConfig.User = url.UserPassword(vchConfig.Username, vchConfig.Token)
|
||||
rootConfig.Thumbprint = vchConfig.TargetThumbprint
|
||||
rootConfig.DatastorePath = vchConfig.Storage.ImageStores[0].Host
|
||||
|
||||
if vchConfig.Diagnostics.DebugLevel > 0 {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.Info("Setting debug logging")
|
||||
}
|
||||
|
||||
if vchConfig.Diagnostics.DebugLevel > 2 {
|
||||
rootConfig.addr = "0.0.0.0:2378"
|
||||
log.Warn("Listening on all networks because of debug level")
|
||||
}
|
||||
s := &server{
|
||||
addr: rootConfig.addr,
|
||||
}
|
||||
|
||||
err := s.listen()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Infof("listening on %s", s.addr)
|
||||
signals := []syscall.Signal{
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGINT,
|
||||
}
|
||||
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
for _, signum := range signals {
|
||||
signal.Notify(sigchan, signum)
|
||||
}
|
||||
|
||||
go func() {
|
||||
signal := <-sigchan
|
||||
log.Infof("received %s", signal)
|
||||
s.stop()
|
||||
}()
|
||||
|
||||
s.serve()
|
||||
}
|
||||
327
vendor/github.com/vmware/vic/cmd/vicadmin/vicadm_test.go
generated
vendored
327
vendor/github.com/vmware/vic/cmd/vicadmin/vicadm_test.go
generated
vendored
@@ -1,327 +0,0 @@
|
||||
// Copyright 2016-2018 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 (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
vchconfig "github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/test/env"
|
||||
)
|
||||
|
||||
// use an http client which we modify in init()
|
||||
// to be permissive with certificates so we can
|
||||
// use a self-signed cert hardcoded into these tests
|
||||
var insecureClient *http.Client
|
||||
|
||||
func init() {
|
||||
// init needs to be updated to include client certificates
|
||||
// so that the disabled tests can be re-enabled
|
||||
sdk := env.URL(nil)
|
||||
if sdk != "" {
|
||||
flag.Set("sdk", sdk)
|
||||
flag.Set("vm-path", "docker-appliance")
|
||||
flag.Set("cluster", os.Getenv("GOVC_CLUSTER"))
|
||||
}
|
||||
|
||||
// fake up a docker-host for pprof collection
|
||||
u := url.URL{Scheme: "http", Host: "127.0.0.1:6060"}
|
||||
|
||||
go func() {
|
||||
log.Println(http.ListenAndServe(u.Host, nil))
|
||||
}()
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
insecureClient = &http.Client{Transport: transport}
|
||||
flag.Set("docker-host", u.Host)
|
||||
|
||||
hostCertFile := "fixtures/vicadmin_test_cert.pem"
|
||||
hostKeyFile := "fixtures/vicadmin_test_pkey.pem"
|
||||
|
||||
cert, cerr := ioutil.ReadFile(hostCertFile)
|
||||
key, kerr := ioutil.ReadFile(hostKeyFile)
|
||||
if kerr != nil || cerr != nil {
|
||||
panic("unable to load test certificate")
|
||||
}
|
||||
vchConfig.HostCertificate = &vchconfig.RawCertificate{
|
||||
Cert: cert,
|
||||
Key: key,
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogFiles(t *testing.T) {
|
||||
logFileNames := []string{}
|
||||
for _, name := range logFiles() {
|
||||
logFileNames = append(logFileNames, name)
|
||||
}
|
||||
fileCount := 0
|
||||
//files should be in same order, otherwise we have evidence of a suspected race
|
||||
for _, name := range logFiles() {
|
||||
assert.Equal(t, name, logFileNames[fileCount])
|
||||
fileCount++
|
||||
}
|
||||
}
|
||||
|
||||
func testLogTar(t *testing.T, plainHTTP bool) {
|
||||
t.SkipNow() // TODO FIXME auth is in place now
|
||||
|
||||
if runtime.GOOS != "linux" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
logFileDir = "."
|
||||
|
||||
s := &server{
|
||||
addr: "127.0.0.1:0",
|
||||
}
|
||||
|
||||
err := s.listen()
|
||||
assert.NoError(t, err)
|
||||
|
||||
port := s.listenPort()
|
||||
|
||||
go s.serve()
|
||||
defer s.stop()
|
||||
|
||||
var res *http.Response
|
||||
res, err = insecureClient.Get(fmt.Sprintf("https://root:thisisinsecure@localhost:%d/container-logs.tar.gz", port))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
z, err := gzip.NewReader(res.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tz := tar.NewReader(z)
|
||||
|
||||
for {
|
||||
h, err := tz.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
name, err := url.QueryUnescape(h.Name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if testing.Verbose() {
|
||||
fmt.Printf("\n%s...\n", name)
|
||||
io.CopyN(os.Stdout, tz, 150)
|
||||
fmt.Printf("...\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogTar(t *testing.T) {
|
||||
t.SkipNow() // TODO FIXME auth is in place now
|
||||
testLogTar(t, false)
|
||||
testLogTar(t, true)
|
||||
}
|
||||
|
||||
func TestLogTail(t *testing.T) {
|
||||
t.SkipNow() // TODO FIXME auth is in place now
|
||||
if runtime.GOOS != "linux" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
f, err := os.OpenFile("./vicadmin.log", os.O_RDWR|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
f.WriteString("# not much here yet\n")
|
||||
|
||||
logFileDir = "."
|
||||
name := filepath.Base(f.Name())
|
||||
|
||||
s := &server{
|
||||
addr: "127.0.0.1:0",
|
||||
// auth: &credentials{"root", "thisisinsecure"},
|
||||
}
|
||||
|
||||
err = s.listen()
|
||||
assert.NoError(t, err)
|
||||
|
||||
port := s.listenPort()
|
||||
|
||||
go s.serve()
|
||||
defer s.stop()
|
||||
|
||||
out := ioutil.Discard
|
||||
if testing.Verbose() {
|
||||
out = os.Stdout
|
||||
}
|
||||
|
||||
paths := []string{
|
||||
"/logs/tail/" + name,
|
||||
}
|
||||
|
||||
u := url.URL{
|
||||
// User: url.UserPassword("root", "thisisinsecure"),
|
||||
Scheme: "https",
|
||||
Host: fmt.Sprintf("localhost:%d", port),
|
||||
}
|
||||
|
||||
str := "The quick brown fox jumps over the lazy dog.\n"
|
||||
|
||||
// Pre-populate the log file
|
||||
for i := 0; i < tailLines; i++ {
|
||||
f.WriteString(str)
|
||||
}
|
||||
|
||||
log.Printf("Testing TestLogTail\n")
|
||||
for _, path := range paths {
|
||||
u.Path = path
|
||||
log.Printf("GET %s:\n", u.String())
|
||||
res, err := insecureClient.Get(u.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
// Each line written to the log file has enough bytes to ensure
|
||||
// that 8 lines make up at least 256 bytes. This is in case this
|
||||
// goroutine finishes writing all lines before tail kicks in.
|
||||
go func() {
|
||||
for j := 1; j < 512; j++ {
|
||||
f.WriteString(str)
|
||||
}
|
||||
f.Sync()
|
||||
}()
|
||||
|
||||
size := int64(256)
|
||||
n, err := io.CopyN(out, res.Body, size)
|
||||
assert.NoError(t, err)
|
||||
out.Write([]byte("...\n"))
|
||||
|
||||
assert.Equal(t, size, n)
|
||||
}
|
||||
}
|
||||
|
||||
type seekTest struct {
|
||||
input string
|
||||
output int
|
||||
}
|
||||
|
||||
func testSeek(t *testing.T, st seekTest, td string) {
|
||||
f, err := ioutil.TempFile(td, "FindSeekPos")
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
log.Printf("Unable to create temporary file: %s", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err := f.WriteString(st.input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(st.input) {
|
||||
t.Fatal(fmt.Errorf("Incorrect byte count on write: %d/%d", n, len(st.input)))
|
||||
}
|
||||
|
||||
if ret := findSeekPos(f); ret != int64(st.output) {
|
||||
t.Fatal(fmt.Errorf("Incorrect seek position: %d/%d", ret, st.output))
|
||||
}
|
||||
log.Printf("Successfully seeked to position %d", st.output)
|
||||
os.Remove(f.Name())
|
||||
}
|
||||
func TestFindSeekPos(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
seekTests := []seekTest{
|
||||
{"abcd\nabcd\n", 0},
|
||||
{"abcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\n", 10},
|
||||
}
|
||||
|
||||
str := "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
||||
str = fmt.Sprintf("%s%s", str, str)
|
||||
str2 := fmt.Sprintf("%s\n%s\n%s\n", str, str, str)
|
||||
str2 = fmt.Sprintf("%s%s", str2, str2)
|
||||
// Verify we don't have overlapping reads at beginning of file
|
||||
// 6 lines, 1206 characters. Should come back with seek position 0
|
||||
seekTests = append(seekTests, seekTest{str2, 0})
|
||||
|
||||
for seg := 0; seg < 3; seg++ {
|
||||
str = str + str
|
||||
}
|
||||
str2 = str + "\n"
|
||||
fmt.Printf("str length is %d\n", len(str))
|
||||
// str is 1,601 chars long now
|
||||
for line := 0; line < 2; line++ {
|
||||
str2 = str2 + str2
|
||||
}
|
||||
|
||||
// str2 is 4 lines long. Should seek to beginning of file
|
||||
seekTests = append(seekTests, seekTest{str2, 0})
|
||||
|
||||
// str2 is now 12 lines long. Should seek to position 6,404
|
||||
str2 = fmt.Sprintf("%s%s%s", str2, str2, str2)
|
||||
seekTests = append(seekTests, seekTest{str2, 6404})
|
||||
|
||||
td := os.TempDir()
|
||||
for i, st := range seekTests {
|
||||
log.Printf("Test case #%d: ", i)
|
||||
testSeek(t, st, td)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionReaderOpen(t *testing.T) {
|
||||
version.Version = "1.2.1"
|
||||
version.BuildNumber = "12345"
|
||||
version.GitCommit = "abcdefg"
|
||||
|
||||
var vReader versionReader
|
||||
en, err := vReader.open()
|
||||
assert.NoError(t, err)
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(en)
|
||||
fullVersion := buf.String()
|
||||
versionFields := strings.SplitN(fullVersion, "-", 3)
|
||||
assert.Equal(t, len(versionFields), 3)
|
||||
assert.Equal(t, versionFields[0], version.Version)
|
||||
assert.Equal(t, versionFields[1], version.BuildNumber)
|
||||
assert.Equal(t, versionFields[2], version.GitCommit)
|
||||
}
|
||||
Reference in New Issue
Block a user