mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-05-13 23:56:35 +00:00

This is a cherry-pick of 157f5d1412
followed by build/CI changes so that amd64/linux FIPS compliance is
provided by new/separate binaries/artifacts/packages.
The reasoning being that FIPS compliance places excessive requirements
in the encryption algorithms used for regular users that do not care
about that. This can cause cloudflared to reject HTTPS origins that
would otherwise be accepted without FIPS checks.
This way, by having separate binaries, existing ones remain as they
were, and only FIPS-needy users will opt-in to the new FIPS binaries.
376 lines
10 KiB
Go
376 lines
10 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/rs/zerolog"
|
|
"github.com/urfave/cli/v2"
|
|
|
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
|
|
"github.com/cloudflare/cloudflared/config"
|
|
"github.com/cloudflare/cloudflared/logger"
|
|
)
|
|
|
|
func runApp(app *cli.App, graceShutdownC chan struct{}) {
|
|
app.Commands = append(app.Commands, &cli.Command{
|
|
Name: "service",
|
|
Usage: "Manages the Cloudflare Tunnel system service",
|
|
Subcommands: []*cli.Command{
|
|
{
|
|
Name: "install",
|
|
Usage: "Install Cloudflare Tunnel as a system service",
|
|
Action: cliutil.ConfiguredAction(installLinuxService),
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "legacy",
|
|
Usage: "Generate service file for non-named tunnels",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "uninstall",
|
|
Usage: "Uninstall the Cloudflare Tunnel service",
|
|
Action: cliutil.ConfiguredAction(uninstallLinuxService),
|
|
},
|
|
},
|
|
})
|
|
app.Run(os.Args)
|
|
}
|
|
|
|
// The directory and files that are used by the service.
|
|
// These are hard-coded in the templates below.
|
|
const (
|
|
serviceConfigDir = "/etc/cloudflared"
|
|
serviceConfigFile = "config.yml"
|
|
serviceCredentialFile = "cert.pem"
|
|
serviceConfigPath = serviceConfigDir + "/" + serviceConfigFile
|
|
)
|
|
|
|
var systemdTemplates = []ServiceTemplate{
|
|
{
|
|
Path: "/etc/systemd/system/cloudflared.service",
|
|
Content: `[Unit]
|
|
Description=Cloudflare Tunnel
|
|
After=network.target
|
|
|
|
[Service]
|
|
TimeoutStartSec=0
|
|
Type=notify
|
|
ExecStart={{ .Path }} --config /etc/cloudflared/config.yml --no-autoupdate{{ range .ExtraArgs }} {{ . }}{{ end }}
|
|
Restart=on-failure
|
|
RestartSec=5s
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
`,
|
|
},
|
|
{
|
|
Path: "/etc/systemd/system/cloudflared-update.service",
|
|
Content: `[Unit]
|
|
Description=Update Cloudflare Tunnel
|
|
After=network.target
|
|
|
|
[Service]
|
|
ExecStart=/bin/bash -c '{{ .Path }} update; code=$?; if [ $code -eq 11 ]; then systemctl restart cloudflared; exit 0; fi; exit $code'
|
|
`,
|
|
},
|
|
{
|
|
Path: "/etc/systemd/system/cloudflared-update.timer",
|
|
Content: `[Unit]
|
|
Description=Update Cloudflare Tunnel
|
|
|
|
[Timer]
|
|
OnCalendar=daily
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
`,
|
|
},
|
|
}
|
|
|
|
var sysvTemplate = ServiceTemplate{
|
|
Path: "/etc/init.d/cloudflared",
|
|
FileMode: 0755,
|
|
Content: `#!/bin/sh
|
|
# For RedHat and cousins:
|
|
# chkconfig: 2345 99 01
|
|
# description: Cloudflare Tunnel agent
|
|
# processname: {{.Path}}
|
|
### BEGIN INIT INFO
|
|
# Provides: {{.Path}}
|
|
# Required-Start:
|
|
# Required-Stop:
|
|
# Default-Start: 2 3 4 5
|
|
# Default-Stop: 0 1 6
|
|
# Short-Description: Cloudflare Tunnel
|
|
# Description: Cloudflare Tunnel agent
|
|
### END INIT INFO
|
|
name=$(basename $(readlink -f $0))
|
|
cmd="{{.Path}} --config /etc/cloudflared/config.yml --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s{{ range .ExtraArgs }} {{ . }}{{ end }}"
|
|
pid_file="/var/run/$name.pid"
|
|
stdout_log="/var/log/$name.log"
|
|
stderr_log="/var/log/$name.err"
|
|
[ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name
|
|
get_pid() {
|
|
cat "$pid_file"
|
|
}
|
|
is_running() {
|
|
[ -f "$pid_file" ] && ps $(get_pid) > /dev/null 2>&1
|
|
}
|
|
case "$1" in
|
|
start)
|
|
if is_running; then
|
|
echo "Already started"
|
|
else
|
|
echo "Starting $name"
|
|
$cmd >> "$stdout_log" 2>> "$stderr_log" &
|
|
echo $! > "$pid_file"
|
|
fi
|
|
;;
|
|
stop)
|
|
if is_running; then
|
|
echo -n "Stopping $name.."
|
|
kill $(get_pid)
|
|
for i in {1..10}
|
|
do
|
|
if ! is_running; then
|
|
break
|
|
fi
|
|
echo -n "."
|
|
sleep 1
|
|
done
|
|
echo
|
|
if is_running; then
|
|
echo "Not stopped; may still be shutting down or shutdown may have failed"
|
|
exit 1
|
|
else
|
|
echo "Stopped"
|
|
if [ -f "$pid_file" ]; then
|
|
rm "$pid_file"
|
|
fi
|
|
fi
|
|
else
|
|
echo "Not running"
|
|
fi
|
|
;;
|
|
restart)
|
|
$0 stop
|
|
if is_running; then
|
|
echo "Unable to stop, will not attempt to start"
|
|
exit 1
|
|
fi
|
|
$0 start
|
|
;;
|
|
status)
|
|
if is_running; then
|
|
echo "Running"
|
|
else
|
|
echo "Stopped"
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Usage: $0 {start|stop|restart|status}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
exit 0
|
|
`,
|
|
}
|
|
|
|
func isSystemd() bool {
|
|
if _, err := os.Stat("/run/systemd/system"); err == nil {
|
|
return true
|
|
}
|
|
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 {
|
|
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
|
|
|
|
etPath, err := os.Executable()
|
|
if err != nil {
|
|
return fmt.Errorf("error determining executable path: %v", err)
|
|
}
|
|
templateArgs := ServiceTemplateArgs{
|
|
Path: etPath,
|
|
}
|
|
|
|
if err := ensureConfigDirExists(serviceConfigDir); err != nil {
|
|
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
|
|
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 {
|
|
case isSystemd():
|
|
log.Info().Msgf("Using Systemd")
|
|
return installSystemd(&templateArgs, log)
|
|
default:
|
|
log.Info().Msgf("Using SysV")
|
|
return installSysv(&templateArgs, log)
|
|
}
|
|
}
|
|
|
|
func installSystemd(templateArgs *ServiceTemplateArgs, log *zerolog.Logger) error {
|
|
for _, serviceTemplate := range systemdTemplates {
|
|
err := serviceTemplate.Generate(templateArgs)
|
|
if err != nil {
|
|
log.Err(err).Msg("error generating service template")
|
|
return err
|
|
}
|
|
}
|
|
if err := runCommand("systemctl", "enable", "cloudflared.service"); err != nil {
|
|
log.Err(err).Msg("systemctl enable cloudflared.service error")
|
|
return err
|
|
}
|
|
if err := runCommand("systemctl", "start", "cloudflared-update.timer"); err != nil {
|
|
log.Err(err).Msg("systemctl start cloudflared-update.timer error")
|
|
return err
|
|
}
|
|
log.Info().Msg("systemctl daemon-reload")
|
|
return runCommand("systemctl", "daemon-reload")
|
|
}
|
|
|
|
func installSysv(templateArgs *ServiceTemplateArgs, log *zerolog.Logger) error {
|
|
confPath, err := sysvTemplate.ResolvePath()
|
|
if err != nil {
|
|
log.Err(err).Msg("error resolving system path")
|
|
return err
|
|
}
|
|
if err := sysvTemplate.Generate(templateArgs); err != nil {
|
|
log.Err(err).Msg("error generating system template")
|
|
return err
|
|
}
|
|
for _, i := range [...]string{"2", "3", "4", "5"} {
|
|
if err := os.Symlink(confPath, "/etc/rc"+i+".d/S50et"); err != nil {
|
|
continue
|
|
}
|
|
}
|
|
for _, i := range [...]string{"0", "1", "6"} {
|
|
if err := os.Symlink(confPath, "/etc/rc"+i+".d/K02et"); err != nil {
|
|
continue
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func uninstallLinuxService(c *cli.Context) error {
|
|
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
|
|
|
|
switch {
|
|
case isSystemd():
|
|
log.Info().Msg("Using Systemd")
|
|
return uninstallSystemd(log)
|
|
default:
|
|
log.Info().Msg("Using SysV")
|
|
return uninstallSysv(log)
|
|
}
|
|
}
|
|
|
|
func uninstallSystemd(log *zerolog.Logger) error {
|
|
if err := runCommand("systemctl", "disable", "cloudflared.service"); err != nil {
|
|
log.Err(err).Msg("systemctl disable cloudflared.service error")
|
|
return err
|
|
}
|
|
if err := runCommand("systemctl", "stop", "cloudflared-update.timer"); err != nil {
|
|
log.Err(err).Msg("systemctl stop cloudflared-update.timer error")
|
|
return err
|
|
}
|
|
for _, serviceTemplate := range systemdTemplates {
|
|
if err := serviceTemplate.Remove(); err != nil {
|
|
log.Err(err).Msg("error removing service template")
|
|
return err
|
|
}
|
|
}
|
|
log.Info().Msgf("Successfully uninstalled cloudflared service from systemd")
|
|
return nil
|
|
}
|
|
|
|
func uninstallSysv(log *zerolog.Logger) error {
|
|
if err := sysvTemplate.Remove(); err != nil {
|
|
log.Err(err).Msg("error removing service template")
|
|
return err
|
|
}
|
|
for _, i := range [...]string{"2", "3", "4", "5"} {
|
|
if err := os.Remove("/etc/rc" + i + ".d/S50et"); err != nil {
|
|
continue
|
|
}
|
|
}
|
|
for _, i := range [...]string{"0", "1", "6"} {
|
|
if err := os.Remove("/etc/rc" + i + ".d/K02et"); err != nil {
|
|
continue
|
|
}
|
|
}
|
|
log.Info().Msgf("Successfully uninstalled cloudflared service from sysv")
|
|
return nil
|
|
}
|