TUN-5679: Add support for service install using Tunnel Token
This commit is contained in:
parent
c2a32de35f
commit
706523389c
|
@ -0,0 +1,30 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||||
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildArgsForToken(c *cli.Context, log *zerolog.Logger) ([]string, error) {
|
||||||
|
token := c.Args().First()
|
||||||
|
if _, err := tunnel.ParseToken(token); err != nil {
|
||||||
|
return nil, cliutil.UsageError("Provided tunnel token is not valid (%s).", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{
|
||||||
|
"tunnel", "run", "--token", token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServiceExtraArgsFromCliArgs(c *cli.Context, log *zerolog.Logger) ([]string, error) {
|
||||||
|
if c.NArg() > 0 {
|
||||||
|
// currently, we only support extra args for token
|
||||||
|
return buildArgsForToken(c, log)
|
||||||
|
} else {
|
||||||
|
// empty extra args
|
||||||
|
return make([]string, 0), nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
@ -26,12 +25,6 @@ func runApp(app *cli.App, graceShutdownC chan struct{}) {
|
||||||
Name: "install",
|
Name: "install",
|
||||||
Usage: "Install Cloudflare Tunnel as a system service",
|
Usage: "Install Cloudflare Tunnel as a system service",
|
||||||
Action: cliutil.ConfiguredAction(installLinuxService),
|
Action: cliutil.ConfiguredAction(installLinuxService),
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "legacy",
|
|
||||||
Usage: "Generate service file for non-named tunnels",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "uninstall",
|
Name: "uninstall",
|
||||||
|
@ -62,7 +55,7 @@ After=network.target
|
||||||
[Service]
|
[Service]
|
||||||
TimeoutStartSec=0
|
TimeoutStartSec=0
|
||||||
Type=notify
|
Type=notify
|
||||||
ExecStart={{ .Path }} --config /etc/cloudflared/config.yml --no-autoupdate{{ range .ExtraArgs }} {{ . }}{{ end }}
|
ExecStart={{ .Path }} --no-autoupdate{{ range .ExtraArgs }} {{ . }}{{ end }}
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5s
|
RestartSec=5s
|
||||||
|
|
||||||
|
@ -112,7 +105,7 @@ var sysvTemplate = ServiceTemplate{
|
||||||
# Description: Cloudflare Tunnel agent
|
# Description: Cloudflare Tunnel agent
|
||||||
### END INIT INFO
|
### END INIT INFO
|
||||||
name=$(basename $(readlink -f $0))
|
name=$(basename $(readlink -f $0))
|
||||||
cmd="{{.Path}} --config /etc/cloudflared/config.yml --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s{{ range .ExtraArgs }} {{ . }}{{ end }}"
|
cmd="{{.Path}} --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s{{ range .ExtraArgs }} {{ . }}{{ end }}"
|
||||||
pid_file="/var/run/$name.pid"
|
pid_file="/var/run/$name.pid"
|
||||||
stdout_log="/var/log/$name.log"
|
stdout_log="/var/log/$name.log"
|
||||||
stderr_log="/var/log/$name.err"
|
stderr_log="/var/log/$name.err"
|
||||||
|
@ -191,27 +184,6 @@ func isSystemd() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile string, log *zerolog.Logger) error {
|
|
||||||
srcCredentialPath := filepath.Join(userConfigDir, userCredentialFile)
|
|
||||||
destCredentialPath := filepath.Join(serviceConfigDir, serviceCredentialFile)
|
|
||||||
if srcCredentialPath != destCredentialPath {
|
|
||||||
if err := copyCredential(srcCredentialPath, destCredentialPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
srcConfigPath := filepath.Join(userConfigDir, userConfigFile)
|
|
||||||
destConfigPath := filepath.Join(serviceConfigDir, serviceConfigFile)
|
|
||||||
if srcConfigPath != destConfigPath {
|
|
||||||
if err := copyConfig(srcConfigPath, destConfigPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Info().Msgf("Copied %s to %s", srcConfigPath, destConfigPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func installLinuxService(c *cli.Context) error {
|
func installLinuxService(c *cli.Context) error {
|
||||||
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
|
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
|
||||||
|
|
||||||
|
@ -223,52 +195,19 @@ func installLinuxService(c *cli.Context) error {
|
||||||
Path: etPath,
|
Path: etPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ensureConfigDirExists(serviceConfigDir); err != nil {
|
var extraArgsFunc func(c *cli.Context, log *zerolog.Logger) ([]string, error)
|
||||||
|
if c.NArg() == 0 {
|
||||||
|
extraArgsFunc = buildArgsForConfig
|
||||||
|
} else {
|
||||||
|
extraArgsFunc = buildArgsForToken
|
||||||
|
}
|
||||||
|
|
||||||
|
extraArgs, err := extraArgsFunc(c, log)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c.Bool("legacy") {
|
|
||||||
userConfigDir := filepath.Dir(c.String("config"))
|
|
||||||
userConfigFile := filepath.Base(c.String("config"))
|
|
||||||
userCredentialFile := config.DefaultCredentialFile
|
|
||||||
if err = copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile, log); err != nil {
|
|
||||||
log.Err(err).Msgf("Failed to copy user configuration. Before running the service, ensure that %s contains two files, %s and %s",
|
|
||||||
serviceConfigDir, serviceCredentialFile, serviceConfigFile)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
templateArgs.ExtraArgs = []string{
|
|
||||||
"--origincert", serviceConfigDir + "/" + serviceCredentialFile,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
src, _, err := config.ReadConfigFile(c, log)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// can't use context because this command doesn't define "credentials-file" flag
|
templateArgs.ExtraArgs = extraArgs
|
||||||
configPresent := func(s string) bool {
|
|
||||||
val, err := src.String(s)
|
|
||||||
return err == nil && val != ""
|
|
||||||
}
|
|
||||||
if src.TunnelID == "" || !configPresent(tunnel.CredFileFlag) {
|
|
||||||
return fmt.Errorf(`Configuration file %s must contain entries for the tunnel to run and its associated credentials:
|
|
||||||
tunnel: TUNNEL-UUID
|
|
||||||
credentials-file: CREDENTIALS-FILE
|
|
||||||
`, src.Source())
|
|
||||||
}
|
|
||||||
if src.Source() != serviceConfigPath {
|
|
||||||
if exists, err := config.FileExists(serviceConfigPath); err != nil || exists {
|
|
||||||
return fmt.Errorf("Possible conflicting configuration in %[1]s and %[2]s. Either remove %[2]s or run `cloudflared --config %[2]s service install`", src.Source(), serviceConfigPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := copyFile(src.Source(), serviceConfigPath); err != nil {
|
|
||||||
return fmt.Errorf("failed to copy %s to %s: %w", src.Source(), serviceConfigPath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
templateArgs.ExtraArgs = []string{
|
|
||||||
"tunnel", "run",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case isSystemd():
|
case isSystemd():
|
||||||
|
@ -280,6 +219,42 @@ credentials-file: CREDENTIALS-FILE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildArgsForConfig(c *cli.Context, log *zerolog.Logger) ([]string, error) {
|
||||||
|
if err := ensureConfigDirExists(serviceConfigDir); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
src, _, err := config.ReadConfigFile(c, log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// can't use context because this command doesn't define "credentials-file" flag
|
||||||
|
configPresent := func(s string) bool {
|
||||||
|
val, err := src.String(s)
|
||||||
|
return err == nil && val != ""
|
||||||
|
}
|
||||||
|
if src.TunnelID == "" || !configPresent(tunnel.CredFileFlag) {
|
||||||
|
return nil, fmt.Errorf(`Configuration file %s must contain entries for the tunnel to run and its associated credentials:
|
||||||
|
tunnel: TUNNEL-UUID
|
||||||
|
credentials-file: CREDENTIALS-FILE
|
||||||
|
`, src.Source())
|
||||||
|
}
|
||||||
|
if src.Source() != serviceConfigPath {
|
||||||
|
if exists, err := config.FileExists(serviceConfigPath); err != nil || exists {
|
||||||
|
return nil, fmt.Errorf("Possible conflicting configuration in %[1]s and %[2]s. Either remove %[2]s or run `cloudflared --config %[2]s service install`", src.Source(), serviceConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyFile(src.Source(), serviceConfigPath); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to copy %s to %s: %w", src.Source(), serviceConfigPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{
|
||||||
|
"--config", "/etc/cloudflared/config.yml", "tunnel", "run",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func installSystemd(templateArgs *ServiceTemplateArgs, log *zerolog.Logger) error {
|
func installSystemd(templateArgs *ServiceTemplateArgs, log *zerolog.Logger) error {
|
||||||
for _, serviceTemplate := range systemdTemplates {
|
for _, serviceTemplate := range systemdTemplates {
|
||||||
err := serviceTemplate.Generate(templateArgs)
|
err := serviceTemplate.Generate(templateArgs)
|
||||||
|
|
|
@ -50,6 +50,9 @@ func newLaunchdTemplate(installPath, stdoutPath, stderrPath string) *ServiceTemp
|
||||||
<key>ProgramArguments</key>
|
<key>ProgramArguments</key>
|
||||||
<array>
|
<array>
|
||||||
<string>{{ .Path }}</string>
|
<string>{{ .Path }}</string>
|
||||||
|
{{- range $i, $item := .ExtraArgs}}
|
||||||
|
<string>{{ $item }}</string>
|
||||||
|
{{- end}}
|
||||||
</array>
|
</array>
|
||||||
<key>RunAtLoad</key>
|
<key>RunAtLoad</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
@ -129,6 +132,13 @@ func installLaunchd(c *cli.Context) error {
|
||||||
log.Err(err).Msg("Error determining install path")
|
log.Err(err).Msg("Error determining install path")
|
||||||
return errors.Wrap(err, "Error determining install path")
|
return errors.Wrap(err, "Error determining install path")
|
||||||
}
|
}
|
||||||
|
extraArgs, err := getServiceExtraArgsFromCliArgs(c, log)
|
||||||
|
if err != nil {
|
||||||
|
errMsg := "Unable to determine extra arguments for launch daemon"
|
||||||
|
log.Err(err).Msg(errMsg)
|
||||||
|
return errors.Wrap(err, errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
stdoutPath, err := stdoutPath()
|
stdoutPath, err := stdoutPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("error determining stdout path")
|
log.Err(err).Msg("error determining stdout path")
|
||||||
|
@ -140,7 +150,7 @@ func installLaunchd(c *cli.Context) error {
|
||||||
return errors.Wrap(err, "error determining stderr path")
|
return errors.Wrap(err, "error determining stderr path")
|
||||||
}
|
}
|
||||||
launchdTemplate := newLaunchdTemplate(installPath, stdoutPath, stderrPath)
|
launchdTemplate := newLaunchdTemplate(installPath, stdoutPath, stderrPath)
|
||||||
templateArgs := ServiceTemplateArgs{Path: etPath}
|
templateArgs := ServiceTemplateArgs{Path: etPath, ExtraArgs: extraArgs}
|
||||||
err = launchdTemplate.Generate(&templateArgs)
|
err = launchdTemplate.Generate(&templateArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("error generating launchd template")
|
log.Err(err).Msg("error generating launchd template")
|
||||||
|
|
|
@ -644,7 +644,7 @@ func runCommand(c *cli.Context) error {
|
||||||
|
|
||||||
// Check if token is provided and if not use default tunnelID flag method
|
// Check if token is provided and if not use default tunnelID flag method
|
||||||
if tokenStr := c.String(TunnelTokenFlag); tokenStr != "" {
|
if tokenStr := c.String(TunnelTokenFlag); tokenStr != "" {
|
||||||
if token, err := parseToken(tokenStr); err == nil {
|
if token, err := ParseToken(tokenStr); err == nil {
|
||||||
return sc.runWithCredentials(token.Credentials())
|
return sc.runWithCredentials(token.Credentials())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -663,7 +663,7 @@ func runCommand(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseToken(tokenStr string) (*connection.TunnelToken, error) {
|
func ParseToken(tokenStr string) (*connection.TunnelToken, error) {
|
||||||
content, err := base64.StdEncoding.DecodeString(tokenStr)
|
content, err := base64.StdEncoding.DecodeString(tokenStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -183,7 +183,7 @@ func Test_validateHostname(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_TunnelToken(t *testing.T) {
|
func Test_TunnelToken(t *testing.T) {
|
||||||
token, err := parseToken("aabc")
|
token, err := ParseToken("aabc")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Nil(t, token)
|
require.Nil(t, token)
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ func Test_TunnelToken(t *testing.T) {
|
||||||
|
|
||||||
token64 := base64.StdEncoding.EncodeToString(tokenJsonStr)
|
token64 := base64.StdEncoding.EncodeToString(tokenJsonStr)
|
||||||
|
|
||||||
token, err = parseToken(token64)
|
token, err = ParseToken(token64)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, token, expectedToken)
|
require.Equal(t, token, expectedToken)
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,8 +193,15 @@ func installWindowsService(c *cli.Context) error {
|
||||||
s.Close()
|
s.Close()
|
||||||
return fmt.Errorf("Service %s already exists", windowsServiceName)
|
return fmt.Errorf("Service %s already exists", windowsServiceName)
|
||||||
}
|
}
|
||||||
|
extraArgs, err := getServiceExtraArgsFromCliArgs(c, &log)
|
||||||
|
if err != nil {
|
||||||
|
errMsg := "Unable to determine extra arguments for windows service"
|
||||||
|
log.Err(err).Msg(errMsg)
|
||||||
|
return errors.Wrap(err, errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
config := mgr.Config{StartType: mgr.StartAutomatic, DisplayName: windowsServiceDescription}
|
config := mgr.Config{StartType: mgr.StartAutomatic, DisplayName: windowsServiceDescription}
|
||||||
s, err = m.CreateService(windowsServiceName, exepath, config)
|
s, err = m.CreateService(windowsServiceName, exepath, config, extraArgs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Cannot install service")
|
return errors.Wrap(err, "Cannot install service")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue