Skip to content

Commit df59ebe

Browse files
sclevinestrideynet
andauthored
Add readyz socket endpoint to tbot for use by teleport-update (#60477)
* PID and readyz changes for tbot * remove teleport-update changes * tidy * tidy * godoc * linting, typos * lint * shared cli args + cli tests * lint * missing . * flag docs * pid example * Apply suggestion from @strideynet Co-authored-by: Noah Stride <[email protected]> * windows fix * windows fix for merge queue * missing errors on windows --------- Co-authored-by: Noah Stride <[email protected]>
1 parent 198c0cd commit df59ebe

File tree

22 files changed

+255
-63
lines changed

22 files changed

+255
-63
lines changed

docs/pages/reference/cli/tbot.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ specific section for details when using a YAML config file or legacy output.
291291
| `--registration-secret` | An optional joining secret to use on first join with the `bound_keypair` join method. This can also be provided via the `TBOT_REGISTRATION_SECRET` environment variable. |
292292
| `--registration-secret-path` | An optional path to a file containing a joining secret to use on first join with the `bound_keypair` join method. |
293293
| `--static-key-path` | An optional path to a file containing a static private key for use with the `bound_keypair` join method. A base64-encoded key can also be provided via the `TBOT_BOUND_KEYPAIR_STATIC_KEY` environment variable. |
294+
| `--pid-file` | Full path to the PID file. By default no PID file will be created. |
294295

295296
## tbot start legacy
296297

@@ -321,6 +322,7 @@ another dedicated mode instead.
321322
| `--join-method` | Method to use to join the cluster. Can be `token`, `azure`, `circleci`, `gcp`, `github`, `gitlab` or `iam`. |
322323
| `--oneshot` | If set, quit after the first renewal. |
323324
| `--log-format` | Controls the format of output logs. Can be `json` or `text`. Defaults to `text`. |
325+
| `--pid-file` | Full path to the PID file. By default no PID file will be created. |
324326

325327
### Examples
326328
<Tabs>

docs/pages/reference/machine-workload-identity/machine-id/diagnostics-service.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ Content-Type: application/json
7272
"status": "unhealthy",
7373
"reason": "access denied to perform action \"read\" on \"workload_identity\""
7474
}
75-
}
75+
},
76+
"pid": 42344
7677
}
7778
```
7879

lib/service/service.go

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ import (
5959
"golang.org/x/crypto/acme"
6060
"golang.org/x/crypto/acme/autocert"
6161
"golang.org/x/crypto/ssh"
62-
"golang.org/x/sys/unix"
6362
"google.golang.org/grpc"
6463
"google.golang.org/grpc/credentials"
6564
"google.golang.org/grpc/keepalive"
@@ -181,6 +180,7 @@ import (
181180
"github.com/gravitational/teleport/lib/utils"
182181
"github.com/gravitational/teleport/lib/utils/cert"
183182
logutils "github.com/gravitational/teleport/lib/utils/log"
183+
procutils "github.com/gravitational/teleport/lib/utils/process"
184184
"github.com/gravitational/teleport/lib/versioncontrol/endpoint"
185185
uw "github.com/gravitational/teleport/lib/versioncontrol/upgradewindow"
186186
"github.com/gravitational/teleport/lib/web"
@@ -1648,7 +1648,7 @@ func NewTeleport(cfg *servicecfg.Config) (_ *TeleportProcess, err error) {
16481648

16491649
// create the new pid file only after started successfully
16501650
if cfg.PIDFile != "" {
1651-
if err := createLockedPIDFile(cfg.PIDFile); err != nil {
1651+
if err := procutils.CreateLockedPIDFile(cfg.PIDFile); err != nil {
16521652
return nil, trace.Wrap(err, "creating pidfile")
16531653
}
16541654
}
@@ -7380,35 +7380,3 @@ func (process *TeleportProcess) newExternalAuditStorageConfigurator() (*external
73807380
statusService := local.NewStatusService(process.backend)
73817381
return externalauditstorage.NewConfigurator(process.ExitContext(), easSvc, integrationSvc, statusService)
73827382
}
7383-
7384-
// createLockedPIDFile creates a PID file in the path specified by pidFile
7385-
// containing the current PID, atomically swapping it in the final place and
7386-
// leaving it with an exclusive advisory lock that will get released when the
7387-
// process ends, for the benefit of "pkill -L".
7388-
func createLockedPIDFile(pidFile string) error {
7389-
pending, err := renameio.NewPendingFile(pidFile, renameio.WithPermissions(0o644))
7390-
if err != nil {
7391-
return trace.ConvertSystemError(err)
7392-
}
7393-
defer pending.Cleanup()
7394-
if _, err := fmt.Fprintf(pending, "%v\n", os.Getpid()); err != nil {
7395-
return trace.ConvertSystemError(err)
7396-
}
7397-
7398-
const minimumDupFD = 3 // skip stdio
7399-
locker, err := unix.FcntlInt(pending.Fd(), unix.F_DUPFD_CLOEXEC, minimumDupFD)
7400-
runtime.KeepAlive(pending)
7401-
if err != nil {
7402-
return trace.ConvertSystemError(err)
7403-
}
7404-
if err := unix.Flock(locker, unix.LOCK_EX|unix.LOCK_NB); err != nil {
7405-
_ = unix.Close(locker)
7406-
return trace.ConvertSystemError(err)
7407-
}
7408-
// deliberately leak the fd to hold the lock until the process dies
7409-
7410-
if err := pending.CloseAtomicallyReplace(); err != nil {
7411-
return trace.ConvertSystemError(err)
7412-
}
7413-
return nil
7414-
}

lib/service/signals.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ func (process *TeleportProcess) createListener(typ ListenerType, address string)
294294
return nil, trace.BadParameter("listening is blocked")
295295
}
296296

297-
// When the process exists, the socket files are left behind (to cover
297+
// When the process exits, the socket files are left behind (to cover
298298
// forking scenarios). To guarantee there won't be errors like "address
299299
// already in use", delete the file before starting the listener.
300300
if typ.Network() == "unix" {
@@ -318,7 +318,7 @@ func (process *TeleportProcess) createListener(typ ListenerType, address string)
318318

319319
// The default behavior for unix listeners is to delete the file when the
320320
// listener closes (unlinking). However, if the process forks, the file
321-
// descriptor will be gone when its parent process exists, causing the new
321+
// descriptor will be gone when its parent process exits, causing the new
322322
// listener to have no socket file.
323323
if unixListener, ok := listener.(*net.UnixListener); ok {
324324
unixListener.SetUnlinkOnClose(false)

lib/tbot/cli/start_legacy.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ type LegacyCommand struct {
132132
// If not set, no diagnostics listener is created.
133133
DiagAddr string
134134

135+
// DiagSocketForUpdater specifies the diagnostics http service address that
136+
// should be exposed to the updater via UNIX domain socket.
137+
DiagSocketForUpdater string
138+
139+
// PIDFile is the path to the PID file. If not set, no PID file will be created.
140+
PIDFile string
141+
135142
oneshotSetByUser bool
136143
}
137144

@@ -158,6 +165,8 @@ func NewLegacyCommand(parentCmd *kingpin.CmdClause, action MutatorAction, mode C
158165
c.cmd.Flag("join-method", "Method to use to join the cluster. "+joinMethodList).EnumVar(&c.JoinMethod, onboarding.SupportedJoinMethods...)
159166
c.cmd.Flag("oneshot", "If set, quit after the first renewal.").IsSetByUser(&c.oneshotSetByUser).BoolVar(&c.Oneshot)
160167
c.cmd.Flag("diag-addr", "If set and the bot is in debug mode, a diagnostics service will listen on specified address.").StringVar(&c.DiagAddr)
168+
c.cmd.Flag("diag-socket-for-updater", "If set, run the diagnostics service on the specified socket path for teleport-update to consume.").Hidden().StringVar(&c.DiagSocketForUpdater)
169+
c.cmd.Flag("pid-file", "Full path to the PID file. By default no PID file will be created.").StringVar(&c.PIDFile)
161170

162171
return c
163172
}
@@ -273,5 +282,8 @@ func (c *LegacyCommand) ApplyConfig(cfg *config.BotConfig, l *slog.Logger) error
273282
cfg.DiagAddr = c.DiagAddr
274283
}
275284

285+
cfg.DiagSocketForUpdater = c.DiagSocketForUpdater
286+
cfg.PIDFile = c.PIDFile
287+
276288
return nil
277289
}

lib/tbot/cli/start_legacy_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ func TestLegacyCommand(t *testing.T) {
4848
"--data-dir=/foo",
4949
"--destination-dir=/bar",
5050
"--auth-server=example.com:3024",
51+
"--pid-file=/run/tbot.pid",
52+
"--diag-socket-for-updater=/var/lib/teleport/bot/debug.sock",
5153
},
5254
assertConfig: func(t *testing.T, cfg *config.BotConfig) {
5355
token, err := cfg.Onboarding.Token()
@@ -61,6 +63,8 @@ func TestLegacyCommand(t *testing.T) {
6163
require.True(t, cfg.Oneshot)
6264
require.Equal(t, "0.0.0.0:8080", cfg.DiagAddr)
6365
require.Equal(t, "example.com:3024", cfg.AuthServer)
66+
require.Equal(t, "/run/tbot.pid", cfg.PIDFile)
67+
require.Equal(t, "/var/lib/teleport/bot/debug.sock", cfg.DiagSocketForUpdater)
6468

6569
dir, ok := cfg.Storage.Destination.(*destination.Directory)
6670
require.True(t, ok)

lib/tbot/cli/start_shared.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,10 @@ type sharedStartArgs struct {
119119
StaticKeyPath string
120120
Keypair string
121121

122-
Oneshot bool
123-
DiagAddr string
122+
Oneshot bool
123+
DiagAddr string
124+
DiagSocketForUpdater string
125+
PIDFile string
124126

125127
oneshotSetByUser bool
126128
}
@@ -146,6 +148,8 @@ func newSharedStartArgs(cmd *kingpin.CmdClause) *sharedStartArgs {
146148
cmd.Flag("registration-secret-path", "For bound keypair joining, specifies a file containing a registration secret for use at first join.").StringVar(&args.RegistrationSecretPath)
147149
cmd.Flag("static-key-path", "For bound keypair joining, specifies a path to a static key.").StringVar(&args.StaticKeyPath)
148150
cmd.Flag("join-uri", "An optional URI with joining and authentication parameters. Individual flags for proxy, join method, token, etc may be used instead.").StringVar(&args.JoiningURI)
151+
cmd.Flag("diag-socket-for-updater", "If set, run the diagnostics service on the specified socket path for teleport-update to consume.").Hidden().StringVar(&args.DiagSocketForUpdater)
152+
cmd.Flag("pid-file", "Full path to the PID file. By default no PID file will be created.").StringVar(&args.PIDFile)
149153

150154
return args
151155
}
@@ -264,6 +268,9 @@ func (s *sharedStartArgs) ApplyConfig(cfg *config.BotConfig, l *slog.Logger) err
264268
cfg.Onboarding.BoundKeypair.StaticPrivateKeyPath = s.StaticKeyPath
265269
}
266270

271+
cfg.DiagSocketForUpdater = s.DiagSocketForUpdater
272+
cfg.PIDFile = s.PIDFile
273+
267274
return nil
268275
}
269276

lib/tbot/cli/start_shared_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ func TestSharedStartArgs(t *testing.T) {
4444
"--diag-addr=0.0.0.0:8080",
4545
"--storage=file:///foo/bar",
4646
"--proxy-server=example.teleport.sh:443",
47+
"--pid-file=/run/tbot.pid",
48+
"--diag-socket-for-updater=/var/lib/teleport/bot/debug.sock",
4749
})
4850
require.NoError(t, err)
4951

@@ -56,6 +58,8 @@ func TestSharedStartArgs(t *testing.T) {
5658
require.Equal(t, "0.0.0.0:8080", args.DiagAddr)
5759
require.Equal(t, "file:///foo/bar", args.Storage)
5860
require.Equal(t, "example.teleport.sh:443", args.ProxyServer)
61+
require.Equal(t, "/run/tbot.pid", args.PIDFile)
62+
require.Equal(t, "/var/lib/teleport/bot/debug.sock", args.DiagSocketForUpdater)
5963

6064
// Convert these args to a BotConfig.
6165
cfg, err := LoadConfigWithMutators(&GlobalArgs{}, args)

lib/tbot/config/config.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ type BotConfig struct {
9999
// If not set, no diagnostics listener is created.
100100
DiagAddr string `yaml:"diag_addr,omitempty"`
101101

102+
// DiagSocketForUpdater specifies the path to the diagnostics http service socket that
103+
// should be exposed to the updater.
104+
DiagSocketForUpdater string `yaml:"-"`
105+
106+
// PIDFile is the path to the PID file that should be created by the bot.
107+
PIDFile string `yaml:"-"`
108+
102109
// ReloadCh allows a channel to be injected into the bot to trigger a
103110
// renewal.
104111
ReloadCh <-chan struct{} `yaml:"-"`

tool/tbot/systemd.tmpl renamed to lib/tbot/config/systemd/systemd.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Group={{ .Group }}
1010
Restart=always
1111
RestartSec=5
1212
Environment="TELEPORT_ANONYMOUS_TELEMETRY={{ if .AnonymousTelemetry }}1{{ else }}0{{ end }}"
13-
ExecStart={{ .TBotPath }} start -c {{ .ConfigPath }}
13+
ExecStart={{ .TBotPath }} start -c {{ .ConfigPath }}{{ with .DiagSocketForUpdater }} --diag-socket-for-updater={{ . }}{{ end }} --pid-file=/run/{{ .UnitName }}.pid
1414
ExecReload=/bin/kill -HUP $MAINPID
1515
PIDFile=/run/{{ .UnitName }}.pid
1616
LimitNOFILE=524288

0 commit comments

Comments
 (0)