Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions cmd/cloudflared/access/carrier.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -125,6 +128,9 @@ func ssh(c *cli.Context) error {
return errors.Wrap(err, "error validating origin URL")
}
log.Info().Str(LogFieldHost, forwarder.Host).Msg("Start Websocket listener")
if path := c.String(sshPidfileFlag); path != "" {
writePidFile(path, log)
}
err = carrier.StartForwarder(wsConn, forwarder.Host, shutdownC, options)
if err != nil {
log.Err(err).Msg("Error on Websocket listener")
Expand All @@ -145,3 +151,21 @@ func ssh(c *cli.Context) error {
}
return carrier.StartClient(wsConn, s, options)
}

// writePidFile writes the current PID to pidPathname so a supervising script can
// stop this forwarder with something like `pkill -F`. Failures are logged rather
// than returned: the forwarder itself is still usable without the file.
func writePidFile(pidPathname string, log *zerolog.Logger) {
expandedPath, err := homedir.Expand(pidPathname)
if err != nil {
log.Err(err).Str("pidPathname", pidPathname).Msg("Unable to expand the path, try to use absolute path in --pidfile")
return
}
file, err := os.Create(filepath.Clean(expandedPath))
if err != nil {
log.Err(err).Str("expandedPath", expandedPath).Msg("Unable to write pid")
return
}
defer func() { _ = file.Close() }()
_, _ = fmt.Fprintf(file, "%d", os.Getpid())
}
50 changes: 50 additions & 0 deletions cmd/cloudflared/access/carrier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package access

import (
"io"
"os"
"path/filepath"
"strconv"
"testing"

"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestWritePidFile(t *testing.T) {
log := zerolog.New(io.Discard)
path := filepath.Join(t.TempDir(), "cloudflared.pid")

writePidFile(path, &log)

contents, err := os.ReadFile(path)
require.NoError(t, err)

pid, err := strconv.Atoi(string(contents))
require.NoError(t, err)
assert.Equal(t, os.Getpid(), pid)
}

func TestWritePidFileTruncatesExistingFile(t *testing.T) {
log := zerolog.New(io.Discard)
path := filepath.Join(t.TempDir(), "cloudflared.pid")

// a stale file from a previous run must not leave trailing digits behind
require.NoError(t, os.WriteFile(path, []byte("999999999999"), 0o600))

writePidFile(path, &log)

contents, err := os.ReadFile(path)
require.NoError(t, err)
assert.Equal(t, strconv.Itoa(os.Getpid()), string(contents))
}

func TestWritePidFileUnwritablePathDoesNotPanic(t *testing.T) {
log := zerolog.New(io.Discard)
// a directory that does not exist, so os.Create fails
path := filepath.Join(t.TempDir(), "missing", "cloudflared.pid")

assert.NotPanics(t, func() { writePidFile(path, &log) })
assert.NoFileExists(t, path)
}
6 changes: 6 additions & 0 deletions cmd/cloudflared/access/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (
sshGenCertFlag = "short-lived-cert"
sshConnectTo = "connect-to"
sshDebugStream = "debug-stream"
sshPidfileFlag = "pidfile"
sshConfigTemplate = `
Add to your {{.Home}}/.ssh/config:

Expand Down Expand Up @@ -181,6 +182,11 @@ func Commands() []*cli.Command {
Usage: "specify an Access service token secret you wish to use.",
EnvVars: []string{"TUNNEL_SERVICE_TOKEN_SECRET"},
},
&cli.StringFlag{
Name: sshPidfileFlag,
Usage: "Write the application's PID to this file once the listener is ready.",
EnvVars: []string{"TUNNEL_PIDFILE"},
},
&cli.StringFlag{
Name: cfdflags.LogFile,
Usage: "Save application log to this file for reporting issues.",
Expand Down