Fix the dependency issue (#231)

This commit is contained in:
Robbie Zhang
2018-06-21 12:09:42 -07:00
committed by GitHub
parent 027b76651d
commit 6ec1098bb8
16629 changed files with 74837 additions and 4975021 deletions

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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)
}