2018-02-08 02:32:50 +00:00
|
|
|
|
/*
|
2018-12-12 00:22:44 +00:00
|
|
|
|
Copyright 2017-2019 Gravitational, Inc.
|
2018-02-08 02:32:50 +00:00
|
|
|
|
|
|
|
|
|
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 service
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"net"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"os/signal"
|
|
|
|
|
"strings"
|
|
|
|
|
"syscall"
|
|
|
|
|
"time"
|
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
|
"github.com/gravitational/teleport/lib/defaults"
|
2018-02-08 02:32:50 +00:00
|
|
|
|
"github.com/gravitational/teleport/lib/utils"
|
2018-12-12 00:22:44 +00:00
|
|
|
|
|
2018-02-08 02:32:50 +00:00
|
|
|
|
"github.com/gravitational/trace"
|
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// printShutdownStatus prints running services until shut down
|
|
|
|
|
func (process *TeleportProcess) printShutdownStatus(ctx context.Context) {
|
2018-12-12 00:22:44 +00:00
|
|
|
|
t := time.NewTicker(defaults.HighResReportingPeriod)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
defer t.Stop()
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
case <-t.C:
|
|
|
|
|
log.Infof("Waiting for services: %v to finish.", process.Supervisor.Services())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WaitForSignals waits for system signals and processes them.
|
|
|
|
|
// Should not be called twice by the process.
|
|
|
|
|
func (process *TeleportProcess) WaitForSignals(ctx context.Context) error {
|
2018-03-18 02:47:06 +00:00
|
|
|
|
|
2018-02-08 02:32:50 +00:00
|
|
|
|
sigC := make(chan os.Signal, 1024)
|
Fix remaining staticcheck findings in lib/...
Fixed findings:
```
lib/sshutils/server_test.go:163:2: SA4006: this value of `clt` is never used (staticcheck)
clt, err := ssh.Dial("tcp", srv.Addr(), &cc)
^
lib/sshutils/server_test.go:91:3: SA5001: should check returned error before deferring ch.Close() (staticcheck)
defer ch.Close()
^
lib/shell/shell_test.go:33:2: SA4006: this value of `shell` is never used (staticcheck)
shell, err = GetLoginShell("non-existent-user")
^
lib/cgroup/cgroup_test.go:111:2: SA9003: empty branch (staticcheck)
if err != nil {
^
lib/cgroup/cgroup_test.go:119:2: SA5001: should check returned error before deferring service.Close() (staticcheck)
defer service.Close()
^
lib/client/keystore_test.go:138:2: SA4006: this value of `keyCopy` is never used (staticcheck)
keyCopy, err = s.store.GetKey("host.a", "bob")
^
lib/client/api.go:1604:3: SA4004: the surrounding loop is unconditionally terminated (staticcheck)
return makeProxyClient(sshClient, m), nil
^
lib/backend/test/suite.go:156:2: SA4006: this value of `err` is never used (staticcheck)
result, err = s.B.GetRange(ctx, prefix("/prefix/c/c1"), backend.RangeEnd(prefix("/prefix/c/cz")), backend.NoLimit)
^
lib/utils/timeout_test.go:84:2: SA1019: t.Dial is deprecated: Use DialContext instead, which allows the transport to cancel dials as soon as they are no longer needed. If both are set, DialContext takes priority. (staticcheck)
t.Dial = func(network string, addr string) (net.Conn, error) {
^
lib/utils/websocketwriter.go:83:3: SA4006: this value of `err` is never used (staticcheck)
utf8, err = w.encoder.String(string(data))
^
lib/utils/loadbalancer_test.go:134:2: SA4006: this value of `out` is never used (staticcheck)
out, err = Roundtrip(frontend.String())
^
lib/utils/loadbalancer_test.go:209:2: SA4006: this value of `out` is never used (staticcheck)
out, err = RoundtripWithConn(conn)
^
lib/srv/forward/sshserver.go:582:3: SA4004: the surrounding loop is unconditionally terminated (staticcheck)
return
^
lib/service/service.go:347:4: SA4006: this value of `err` is never used (staticcheck)
i, err = auth.GenerateIdentity(process.localAuth, id, principals, dnsNames)
^
lib/service/signals.go:60:3: SA1016: syscall.SIGKILL cannot be trapped (did you mean syscall.SIGTERM?) (staticcheck)
syscall.SIGKILL, // fast shutdown
^
lib/config/configuration_test.go:184:2: SA4006: this value of `conf` is never used (staticcheck)
conf, err = ReadFromFile(s.configFileBadContent)
^
lib/config/configuration.go:129:2: SA5001: should check returned error before deferring reader.Close() (staticcheck)
defer reader.Close()
^
lib/kube/kubeconfig/kubeconfig_test.go:227:2: SA4006: this value of `err` is never used (staticcheck)
tlsCert, err := ca.GenerateCertificate(tlsca.CertificateRequest{
^
lib/srv/sess.go:720:3: SA4006: this value of `err` is never used (staticcheck)
result, err := s.term.Wait()
^
lib/multiplexer/multiplexer_test.go:169:11: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
_, err = fmt.Fprintf(conn, proxyLine.String())
^
lib/multiplexer/multiplexer_test.go:221:11: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
_, err = fmt.Fprintf(conn, proxyLine.String())
^
```
2020-04-27 21:32:59 +00:00
|
|
|
|
// Note: SIGKILL can't be trapped.
|
2018-02-17 23:51:57 +00:00
|
|
|
|
signal.Notify(sigC,
|
|
|
|
|
syscall.SIGQUIT, // graceful shutdown
|
|
|
|
|
syscall.SIGTERM, // fast shutdown
|
|
|
|
|
syscall.SIGINT, // fast shutdown
|
|
|
|
|
syscall.SIGUSR1, // log process diagnostic info
|
|
|
|
|
syscall.SIGUSR2, // initiate process restart procedure
|
|
|
|
|
syscall.SIGHUP, // graceful restart procedure
|
|
|
|
|
syscall.SIGCHLD, // collect child status
|
|
|
|
|
)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
|
|
|
|
|
doneContext, cancel := context.WithCancel(ctx)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
2018-09-07 21:35:23 +00:00
|
|
|
|
serviceErrorsC := make(chan Event, 10)
|
|
|
|
|
process.WaitForEvent(ctx, ServiceExitedWithErrorEvent, serviceErrorsC)
|
|
|
|
|
|
2018-02-08 02:32:50 +00:00
|
|
|
|
// Block until a signal is received or handler got an error.
|
|
|
|
|
// Notice how this handler is serialized - it will only receive
|
|
|
|
|
// signals in sequence and will not run in parallel.
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case signal := <-sigC:
|
|
|
|
|
switch signal {
|
2018-02-17 23:51:57 +00:00
|
|
|
|
case syscall.SIGQUIT:
|
2018-02-08 02:32:50 +00:00
|
|
|
|
go process.printShutdownStatus(doneContext)
|
|
|
|
|
process.Shutdown(ctx)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Infof("All services stopped, exiting.")
|
2018-02-08 02:32:50 +00:00
|
|
|
|
return nil
|
2018-02-17 23:51:57 +00:00
|
|
|
|
case syscall.SIGTERM, syscall.SIGKILL, syscall.SIGINT:
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Infof("Got signal %q, exiting immediately.", signal)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
process.Close()
|
|
|
|
|
return nil
|
2018-02-17 23:51:57 +00:00
|
|
|
|
case syscall.SIGUSR1:
|
|
|
|
|
// All programs placed diagnostics on the standard output.
|
|
|
|
|
// This had always caused trouble when the output was redirected into a file, but became intolerable
|
|
|
|
|
// when the output was sent to an unsuspecting process.
|
|
|
|
|
// Nevertheless, unwilling to violate the simplicity of the standard-input-standard-output model,
|
|
|
|
|
// people tolerated this state of affairs through v6. Shortly thereafter Dennis Ritchie cut the Gordian
|
|
|
|
|
// knot by introducing the standard error file.
|
|
|
|
|
// That was not quite enough. With pipelines diagnostics could come from any of several programs running simultaneously.
|
|
|
|
|
// Diagnostics needed to identify themselves.
|
|
|
|
|
// - Doug McIllroy, "A Research UNIX Reader: Annotated Excerpts from the Programmer’s Manual, 1971-1986"
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Infof("Got signal %q, logging diagostic info to stderr.", signal)
|
2018-02-17 23:51:57 +00:00
|
|
|
|
writeDebugInfo(os.Stderr)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
case syscall.SIGUSR2:
|
|
|
|
|
log.Infof("Got signal %q, forking a new process.", signal)
|
|
|
|
|
if err := process.forkChild(); err != nil {
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Warningf("Failed to fork: %v", err)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
} else {
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Infof("Successfully started new process.")
|
2018-02-08 02:32:50 +00:00
|
|
|
|
}
|
|
|
|
|
case syscall.SIGHUP:
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Infof("Got signal %q, performing graceful restart.", signal)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
if err := process.forkChild(); err != nil {
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Warningf("Failed to fork: %v", err)
|
2018-04-03 22:41:12 +00:00
|
|
|
|
continue
|
2018-02-08 02:32:50 +00:00
|
|
|
|
}
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Infof("Successfully started new process, shutting down gracefully.")
|
2018-02-08 02:32:50 +00:00
|
|
|
|
go process.printShutdownStatus(doneContext)
|
|
|
|
|
process.Shutdown(ctx)
|
|
|
|
|
log.Infof("All services stopped, exiting.")
|
|
|
|
|
return nil
|
|
|
|
|
case syscall.SIGCHLD:
|
2018-03-18 02:47:06 +00:00
|
|
|
|
process.collectStatuses()
|
2018-02-08 02:32:50 +00:00
|
|
|
|
default:
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Infof("Ignoring %q.", signal)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
}
|
2018-04-08 21:37:33 +00:00
|
|
|
|
case <-process.ReloadContext().Done():
|
|
|
|
|
process.Infof("Exiting signal handler: process has started internal reload.")
|
|
|
|
|
return ErrTeleportReloading
|
|
|
|
|
case <-process.ExitContext().Done():
|
|
|
|
|
process.Infof("Someone else has closed context, exiting.")
|
|
|
|
|
return nil
|
2018-02-08 02:32:50 +00:00
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
process.Close()
|
|
|
|
|
process.Wait()
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Info("Got request to shutdown, context is closing")
|
2018-02-08 02:32:50 +00:00
|
|
|
|
return nil
|
2018-09-07 21:35:23 +00:00
|
|
|
|
case event := <-serviceErrorsC:
|
|
|
|
|
se, ok := event.Payload.(ServiceExit)
|
|
|
|
|
if !ok {
|
|
|
|
|
process.Warningf("Failed to decode service exit event, %T", event.Payload)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if se.Service.IsCritical() {
|
|
|
|
|
process.Errorf("Critical service %v has exited with error %v, aborting.", se.Service, se.Error)
|
|
|
|
|
if err := process.Close(); err != nil {
|
|
|
|
|
process.Errorf("Error when shutting down teleport %v.", err)
|
|
|
|
|
}
|
|
|
|
|
return trace.Wrap(se.Error)
|
|
|
|
|
} else {
|
|
|
|
|
process.Warningf("Non-critical service %v has exited with error %v, continuing to operate.", se.Service, se.Error)
|
|
|
|
|
}
|
2018-02-08 02:32:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
|
// ErrTeleportReloading is returned when signal waiter exits
|
|
|
|
|
// because the teleport process has initiaded shutdown
|
|
|
|
|
var ErrTeleportReloading = &trace.CompareFailedError{Message: "teleport process is reloading"}
|
|
|
|
|
|
|
|
|
|
// ErrTeleportExited means that teleport has exited
|
|
|
|
|
var ErrTeleportExited = &trace.CompareFailedError{Message: "teleport process has shutdown"}
|
|
|
|
|
|
2018-07-22 23:01:38 +00:00
|
|
|
|
func (process *TeleportProcess) writeToSignalPipe(signalPipe *os.File, message string) error {
|
2018-04-03 22:41:12 +00:00
|
|
|
|
messageSignalled, cancel := context.WithCancel(context.Background())
|
|
|
|
|
// Below the cancel is called second time, but it's ok.
|
|
|
|
|
// After the first call, subsequent calls to a CancelFunc do nothing.
|
|
|
|
|
defer cancel()
|
|
|
|
|
go func() {
|
|
|
|
|
_, err := signalPipe.Write([]byte(message))
|
|
|
|
|
if err != nil {
|
2018-07-22 23:01:38 +00:00
|
|
|
|
process.Debugf("Failed to write to pipe: %v.", trace.DebugReport(err))
|
2018-04-03 22:41:12 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
cancel()
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case <-time.After(signalPipeTimeout):
|
2018-07-22 23:01:38 +00:00
|
|
|
|
return trace.BadParameter("Failed to write to parent process pipe.")
|
2018-04-03 22:41:12 +00:00
|
|
|
|
case <-messageSignalled.Done():
|
2018-07-22 23:01:38 +00:00
|
|
|
|
process.Infof("Signalled success to parent process.")
|
2018-04-03 22:41:12 +00:00
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-08 02:32:50 +00:00
|
|
|
|
// closeImportedDescriptors closes imported but unused file descriptors,
|
|
|
|
|
// what could happen if service has updated configuration
|
|
|
|
|
func (process *TeleportProcess) closeImportedDescriptors(prefix string) error {
|
|
|
|
|
process.Lock()
|
|
|
|
|
defer process.Unlock()
|
|
|
|
|
|
|
|
|
|
var errors []error
|
|
|
|
|
for i := range process.importedDescriptors {
|
|
|
|
|
d := process.importedDescriptors[i]
|
|
|
|
|
if strings.HasPrefix(d.Type, prefix) {
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Infof("Closing imported but unused descriptor %v %v.", d.Type, d.Address)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
errors = append(errors, d.File.Close())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return trace.NewAggregate(errors...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// importOrCreateListener imports listener passed by the parent process (happens during live reload)
|
|
|
|
|
// or creates a new listener if there was no listener registered
|
2020-05-13 22:36:42 +00:00
|
|
|
|
func (process *TeleportProcess) importOrCreateListener(typ listenerType, address string) (net.Listener, error) {
|
|
|
|
|
l, err := process.importListener(typ, address)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
if err == nil {
|
2020-05-13 22:36:42 +00:00
|
|
|
|
process.Infof("Using file descriptor %v %v passed by the parent process.", typ, address)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
return l, nil
|
|
|
|
|
}
|
|
|
|
|
if !trace.IsNotFound(err) {
|
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
|
}
|
2020-05-13 22:36:42 +00:00
|
|
|
|
process.Infof("Service %v is creating new listener on %v.", typ, address)
|
|
|
|
|
return process.createListener(typ, address)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-03 22:41:12 +00:00
|
|
|
|
func (process *TeleportProcess) importSignalPipe() (*os.File, error) {
|
|
|
|
|
process.Lock()
|
|
|
|
|
defer process.Unlock()
|
|
|
|
|
|
|
|
|
|
for i := range process.importedDescriptors {
|
|
|
|
|
d := process.importedDescriptors[i]
|
|
|
|
|
if d.Type == signalPipeName {
|
|
|
|
|
process.importedDescriptors = append(process.importedDescriptors[:i], process.importedDescriptors[i+1:]...)
|
|
|
|
|
return d.File, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil, trace.NotFound("no file descriptor %v was found", signalPipeName)
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-08 02:32:50 +00:00
|
|
|
|
// importListener imports listener passed by the parent process, if no listener is found
|
|
|
|
|
// returns NotFound, otherwise removes the file from the list
|
2020-05-13 22:36:42 +00:00
|
|
|
|
func (process *TeleportProcess) importListener(typ listenerType, address string) (net.Listener, error) {
|
2018-02-08 02:32:50 +00:00
|
|
|
|
process.Lock()
|
|
|
|
|
defer process.Unlock()
|
|
|
|
|
|
|
|
|
|
for i := range process.importedDescriptors {
|
|
|
|
|
d := process.importedDescriptors[i]
|
2020-05-13 22:36:42 +00:00
|
|
|
|
if d.Type == string(typ) && d.Address == address {
|
2018-02-08 02:32:50 +00:00
|
|
|
|
l, err := d.ToListener()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
|
}
|
|
|
|
|
process.importedDescriptors = append(process.importedDescriptors[:i], process.importedDescriptors[i+1:]...)
|
2020-05-13 22:36:42 +00:00
|
|
|
|
process.registeredListeners = append(process.registeredListeners, registeredListener{typ: typ, address: address, listener: l})
|
2018-02-08 02:32:50 +00:00
|
|
|
|
return l, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-13 22:36:42 +00:00
|
|
|
|
return nil, trace.NotFound("no file descriptor for type %v and address %v has been imported", typ, address)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// createListener creates listener and adds to a list of tracked listeners
|
2020-05-13 22:36:42 +00:00
|
|
|
|
func (process *TeleportProcess) createListener(typ listenerType, address string) (net.Listener, error) {
|
2018-02-08 02:32:50 +00:00
|
|
|
|
listener, err := net.Listen("tcp", address)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
|
}
|
|
|
|
|
process.Lock()
|
|
|
|
|
defer process.Unlock()
|
2020-05-13 22:36:42 +00:00
|
|
|
|
r := registeredListener{typ: typ, address: address, listener: listener}
|
2018-02-08 02:32:50 +00:00
|
|
|
|
process.registeredListeners = append(process.registeredListeners, r)
|
|
|
|
|
return listener, nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
|
// ExportFileDescriptors exports file descriptors to be passed to child process
|
|
|
|
|
func (process *TeleportProcess) ExportFileDescriptors() ([]FileDescriptor, error) {
|
2018-02-08 02:32:50 +00:00
|
|
|
|
var out []FileDescriptor
|
|
|
|
|
process.Lock()
|
|
|
|
|
defer process.Unlock()
|
|
|
|
|
for _, r := range process.registeredListeners {
|
2020-05-13 22:36:42 +00:00
|
|
|
|
file, err := utils.GetListenerFile(r.listener)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
|
}
|
2020-05-13 22:36:42 +00:00
|
|
|
|
out = append(out, FileDescriptor{File: file, Type: string(r.typ), Address: r.address})
|
2018-02-08 02:32:50 +00:00
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// importFileDescriptors imports file descriptors from environment if there are any
|
|
|
|
|
func importFileDescriptors() ([]FileDescriptor, error) {
|
|
|
|
|
// These files may be passed in by the parent process
|
|
|
|
|
filesString := os.Getenv(teleportFilesEnvVar)
|
|
|
|
|
if filesString == "" {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
files, err := filesFromString(filesString)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, trace.BadParameter("child process has failed to read files, error %q", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(files) != 0 {
|
|
|
|
|
log.Infof("Child has been passed files: %v", files)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return files, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-13 22:36:42 +00:00
|
|
|
|
// registeredListener is a listener registered
|
2018-02-08 02:32:50 +00:00
|
|
|
|
// within teleport process, can be passed to child process
|
2020-05-13 22:36:42 +00:00
|
|
|
|
type registeredListener struct {
|
2018-02-08 02:32:50 +00:00
|
|
|
|
// Type is a listener type, e.g. auth:ssh
|
2020-05-13 22:36:42 +00:00
|
|
|
|
typ listenerType
|
2018-02-08 02:32:50 +00:00
|
|
|
|
// Address is an address listener is serving on, e.g. 127.0.0.1:3025
|
2020-05-13 22:36:42 +00:00
|
|
|
|
address string
|
2018-02-08 02:32:50 +00:00
|
|
|
|
// Listener is a file listener object
|
2020-05-13 22:36:42 +00:00
|
|
|
|
listener net.Listener
|
2018-02-08 02:32:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FileDescriptor is a file descriptor associated
|
|
|
|
|
// with a listener
|
|
|
|
|
type FileDescriptor struct {
|
|
|
|
|
// Type is a listener type, e.g. auth:ssh
|
|
|
|
|
Type string
|
2019-10-22 18:10:28 +00:00
|
|
|
|
// Address is an address of the listener, e.g. 127.0.0.1:3025
|
2018-02-08 02:32:50 +00:00
|
|
|
|
Address string
|
|
|
|
|
// File is a file descriptor associated with the listener
|
|
|
|
|
File *os.File
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (fd *FileDescriptor) ToListener() (net.Listener, error) {
|
|
|
|
|
listener, err := net.FileListener(fd.File)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
fd.File.Close()
|
|
|
|
|
return listener, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type fileDescriptor struct {
|
|
|
|
|
Address string `json:"addr"`
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
FileFD int `json:"fd"`
|
|
|
|
|
FileName string `json:"fileName"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// filesToString serializes file descriptors as well as accompanying information (like socket host and port)
|
|
|
|
|
func filesToString(files []FileDescriptor) (string, error) {
|
|
|
|
|
out := make([]fileDescriptor, len(files))
|
|
|
|
|
for i, f := range files {
|
|
|
|
|
out[i] = fileDescriptor{
|
|
|
|
|
// Once files will be passed to the child process and their FDs will change.
|
|
|
|
|
// The first three passed files are stdin, stdout and stderr, every next file will have the index + 3
|
|
|
|
|
// That's why we rearrange the FDs for child processes to get the correct file descriptors.
|
|
|
|
|
FileFD: i + 3,
|
|
|
|
|
FileName: f.File.Name(),
|
|
|
|
|
Address: f.Address,
|
|
|
|
|
Type: f.Type,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bytes, err := json.Marshal(out)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return string(bytes), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const teleportFilesEnvVar = "TELEPORT_OS_FILES"
|
|
|
|
|
|
|
|
|
|
func execPath() (string, error) {
|
|
|
|
|
name, err := exec.LookPath(os.Args[0])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
if _, err = os.Stat(name); nil != err {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return name, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// filesFromString de-serializes the file descriptors and turns them in the os.Files
|
|
|
|
|
func filesFromString(in string) ([]FileDescriptor, error) {
|
|
|
|
|
var out []fileDescriptor
|
|
|
|
|
if err := json.Unmarshal([]byte(in), &out); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
files := make([]FileDescriptor, len(out))
|
|
|
|
|
for i, o := range out {
|
|
|
|
|
files[i] = FileDescriptor{
|
|
|
|
|
File: os.NewFile(uintptr(o.FileFD), o.FileName),
|
|
|
|
|
Address: o.Address,
|
|
|
|
|
Type: o.Type,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return files, nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-03 22:41:12 +00:00
|
|
|
|
const (
|
|
|
|
|
signalPipeName = "teleport-signal-pipe"
|
|
|
|
|
// signalPipeTimeout is a time parent process is expecting
|
|
|
|
|
// the child process to initialize and write back,
|
|
|
|
|
// or child process is blocked on write to the pipe
|
2018-04-08 21:37:33 +00:00
|
|
|
|
signalPipeTimeout = 2 * time.Minute
|
2018-04-03 22:41:12 +00:00
|
|
|
|
)
|
|
|
|
|
|
2018-02-08 02:32:50 +00:00
|
|
|
|
func (process *TeleportProcess) forkChild() error {
|
2018-04-03 22:41:12 +00:00
|
|
|
|
readPipe, writePipe, err := os.Pipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return trace.ConvertSystemError(err)
|
|
|
|
|
}
|
|
|
|
|
defer readPipe.Close()
|
|
|
|
|
defer writePipe.Close()
|
|
|
|
|
|
2018-02-08 02:32:50 +00:00
|
|
|
|
path, err := execPath()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return trace.Wrap(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
workingDir, err := os.Getwd()
|
|
|
|
|
if nil != err {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log := log.WithFields(logrus.Fields{"path": path, "workingDir": workingDir})
|
|
|
|
|
|
|
|
|
|
log.Info("Forking child.")
|
|
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
|
listenerFiles, err := process.ExportFileDescriptors()
|
2018-02-08 02:32:50 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return trace.Wrap(err)
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-03 22:41:12 +00:00
|
|
|
|
listenerFiles = append(listenerFiles, FileDescriptor{
|
|
|
|
|
File: writePipe,
|
|
|
|
|
Type: signalPipeName,
|
|
|
|
|
Address: "127.0.0.1:0",
|
|
|
|
|
})
|
|
|
|
|
|
2018-02-08 02:32:50 +00:00
|
|
|
|
// These files will be passed to the child process
|
|
|
|
|
files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
|
|
|
|
|
for _, f := range listenerFiles {
|
|
|
|
|
files = append(files, f.File)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Serialize files to JSON string representation
|
|
|
|
|
vals, err := filesToString(listenerFiles)
|
|
|
|
|
if err != nil {
|
2018-04-03 22:41:12 +00:00
|
|
|
|
return trace.Wrap(err)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Infof("Passing %s to child", vals)
|
|
|
|
|
os.Setenv(teleportFilesEnvVar, vals)
|
|
|
|
|
|
|
|
|
|
p, err := os.StartProcess(path, os.Args, &os.ProcAttr{
|
|
|
|
|
Dir: workingDir,
|
|
|
|
|
Env: os.Environ(),
|
|
|
|
|
Files: files,
|
|
|
|
|
Sys: &syscall.SysProcAttr{},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return trace.ConvertSystemError(err)
|
|
|
|
|
}
|
2018-03-18 02:47:06 +00:00
|
|
|
|
process.pushForkedPID(p.Pid)
|
2018-04-03 22:41:12 +00:00
|
|
|
|
log.WithFields(logrus.Fields{"pid": p.Pid}).Infof("Forked new child process.")
|
|
|
|
|
|
|
|
|
|
messageReceived, cancel := context.WithCancel(context.TODO())
|
|
|
|
|
defer cancel()
|
|
|
|
|
go func() {
|
|
|
|
|
data := make([]byte, 1024)
|
|
|
|
|
len, err := readPipe.Read(data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Debugf("Failed to read from pipe")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
log.Infof("Received message from pid %v: %v", p.Pid, string(data[:len]))
|
|
|
|
|
cancel()
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case <-time.After(signalPipeTimeout):
|
|
|
|
|
return trace.BadParameter("Failed waiting from process")
|
|
|
|
|
case <-messageReceived.Done():
|
|
|
|
|
log.WithFields(logrus.Fields{"pid": p.Pid}).Infof("Child process signals success.")
|
|
|
|
|
}
|
2018-02-08 02:32:50 +00:00
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2018-03-18 02:47:06 +00:00
|
|
|
|
|
|
|
|
|
// collectStatuses attempts to collect exit statuses from
|
|
|
|
|
// forked teleport child processes.
|
|
|
|
|
// If forked teleport process exited with an error during graceful
|
|
|
|
|
// restart, parent process has to collect the child process status
|
|
|
|
|
// otherwise the child process will become a zombie process.
|
|
|
|
|
// Call Wait4(-1) is trying to collect status of any child
|
|
|
|
|
// leads to warnings in logs, because other parts of the program could
|
|
|
|
|
// have tried to collect the status of this process.
|
|
|
|
|
// Instead this logic tries to collect statuses of the processes
|
|
|
|
|
// forked during restart procedure.
|
|
|
|
|
func (process *TeleportProcess) collectStatuses() {
|
|
|
|
|
pids := process.getForkedPIDs()
|
|
|
|
|
if len(pids) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
for _, pid := range pids {
|
|
|
|
|
var wait syscall.WaitStatus
|
|
|
|
|
rpid, err := syscall.Wait4(pid, &wait, syscall.WNOHANG, nil)
|
|
|
|
|
if err != nil {
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Errorf("Wait call failed: %v.", err)
|
2018-03-18 02:47:06 +00:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if rpid == pid {
|
|
|
|
|
process.popForkedPID(pid)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
process.Warningf("Forked teleport process %v has exited with status: %v.", pid, wait.ExitStatus())
|
2018-03-18 02:47:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (process *TeleportProcess) pushForkedPID(pid int) {
|
|
|
|
|
process.Lock()
|
|
|
|
|
defer process.Unlock()
|
|
|
|
|
process.forkedPIDs = append(process.forkedPIDs, pid)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (process *TeleportProcess) popForkedPID(pid int) {
|
|
|
|
|
process.Lock()
|
|
|
|
|
defer process.Unlock()
|
|
|
|
|
for i, p := range process.forkedPIDs {
|
|
|
|
|
if p == pid {
|
|
|
|
|
process.forkedPIDs = append(process.forkedPIDs[:i], process.forkedPIDs[i+1:]...)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (process *TeleportProcess) getForkedPIDs() []int {
|
|
|
|
|
process.Lock()
|
|
|
|
|
defer process.Unlock()
|
|
|
|
|
if len(process.forkedPIDs) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
out := make([]int, len(process.forkedPIDs))
|
|
|
|
|
copy(out, process.forkedPIDs)
|
|
|
|
|
return out
|
|
|
|
|
}
|