feat: support tailscale exit nodes
This commit is contained in:
parent
9680656ec9
commit
671d362e29
@ -199,13 +199,16 @@ tailscale:
|
||||
- tag:crabbox
|
||||
hostnameTemplate: crabbox-{slug}
|
||||
authKeyEnv: CRABBOX_TAILSCALE_AUTH_KEY
|
||||
exitNode: mac-studio.example.ts.net
|
||||
exitNodeAllowLanAccess: true
|
||||
```
|
||||
|
||||
Tailscale is a network plane, not a provider. `--tailscale` joins new managed
|
||||
Linux leases to the tailnet; `--network auto|tailscale|public` chooses how SSH
|
||||
and VNC tunnel commands resolve the host. Brokered mode uses Worker OAuth
|
||||
secrets to mint one-off keys; direct-provider mode reads the auth key from the
|
||||
configured env var. See [Tailscale](docs/features/tailscale.md).
|
||||
configured env var. `exitNode` is opt-in per lease for routing outbound internet
|
||||
through an approved tailnet exit node. See [Tailscale](docs/features/tailscale.md).
|
||||
|
||||
Forwarded environment is intentionally narrow: `NODE_OPTIONS` and `CI`. Do not pass secrets as command-line arguments. Full env-var reference and per-command flags are in [docs/cli.md](docs/cli.md) and [docs/commands/](docs/commands/README.md).
|
||||
|
||||
|
||||
@ -267,6 +267,8 @@ Flags:
|
||||
--tailscale-tags <csv> Tailscale tags for new managed leases
|
||||
--tailscale-hostname-template <template>
|
||||
--tailscale-auth-key-env <env-var>
|
||||
--tailscale-exit-node <name-or-100.x>
|
||||
--tailscale-exit-node-allow-lan-access
|
||||
--network auto|tailscale|public
|
||||
--no-sync run without syncing
|
||||
--sync-only sync and exit
|
||||
@ -559,6 +561,8 @@ CRABBOX_TAILSCALE_TAGS
|
||||
CRABBOX_TAILSCALE_HOSTNAME_TEMPLATE
|
||||
CRABBOX_TAILSCALE_AUTH_KEY_ENV
|
||||
CRABBOX_TAILSCALE_AUTH_KEY direct-provider only, via auth-key env
|
||||
CRABBOX_TAILSCALE_EXIT_NODE
|
||||
CRABBOX_TAILSCALE_EXIT_NODE_ALLOW_LAN_ACCESS
|
||||
```
|
||||
|
||||
Provider/deploy variables live outside normal CLI operation:
|
||||
|
||||
@ -36,6 +36,8 @@ tailscale:
|
||||
- tag:crabbox
|
||||
hostnameTemplate: crabbox-{slug}
|
||||
authKeyEnv: CRABBOX_TAILSCALE_AUTH_KEY
|
||||
exitNode: mac-studio.example.ts.net
|
||||
exitNodeAllowLanAccess: true
|
||||
capacity:
|
||||
market: spot
|
||||
strategy: most-available
|
||||
@ -72,3 +74,7 @@ env:
|
||||
Brokered `--tailscale` leases use Worker-minted one-off auth keys. Direct
|
||||
provider leases read a local one-off key from `tailscale.authKeyEnv`; do not
|
||||
store that key in repo config.
|
||||
|
||||
`tailscale.exitNode` routes lease egress through an approved tailnet exit node.
|
||||
`tailscale.exitNodeAllowLanAccess` keeps LAN access available while using that
|
||||
exit node.
|
||||
|
||||
@ -109,6 +109,8 @@ Flags:
|
||||
--tailscale-tags <comma-separated tags>
|
||||
--tailscale-hostname-template <template>
|
||||
--tailscale-auth-key-env <env-var>
|
||||
--tailscale-exit-node <name-or-100.x>
|
||||
--tailscale-exit-node-allow-lan-access
|
||||
--network auto|tailscale|public
|
||||
--keep
|
||||
--no-sync
|
||||
|
||||
@ -89,6 +89,8 @@ Flags:
|
||||
--tailscale-tags <comma-separated tags>
|
||||
--tailscale-hostname-template <template>
|
||||
--tailscale-auth-key-env <env-var>
|
||||
--tailscale-exit-node <name-or-100.x>
|
||||
--tailscale-exit-node-allow-lan-access
|
||||
--network auto|tailscale|public
|
||||
--keep
|
||||
--actions-runner
|
||||
|
||||
@ -58,6 +58,8 @@ tailscale:
|
||||
- tag:crabbox
|
||||
hostnameTemplate: crabbox-{slug}
|
||||
authKeyEnv: CRABBOX_TAILSCALE_AUTH_KEY
|
||||
exitNode: mac-studio.example.ts.net
|
||||
exitNodeAllowLanAccess: true
|
||||
```
|
||||
|
||||
Environment overrides:
|
||||
@ -68,6 +70,8 @@ CRABBOX_NETWORK=auto|tailscale|public
|
||||
CRABBOX_TAILSCALE_TAGS=tag:crabbox,tag:ci
|
||||
CRABBOX_TAILSCALE_HOSTNAME_TEMPLATE=crabbox-{slug}
|
||||
CRABBOX_TAILSCALE_AUTH_KEY=<direct-provider only>
|
||||
CRABBOX_TAILSCALE_EXIT_NODE=mac-studio.example.ts.net
|
||||
CRABBOX_TAILSCALE_EXIT_NODE_ALLOW_LAN_ACCESS=1
|
||||
```
|
||||
|
||||
`tailscale.enabled` and `--tailscale` request tailnet join for newly created
|
||||
@ -78,6 +82,11 @@ and `{provider}`.
|
||||
Direct-provider mode reads the one-off auth key from `tailscale.authKeyEnv`.
|
||||
Brokered mode does not require a local Tailscale key.
|
||||
|
||||
`tailscale.exitNode` asks the lease to route outbound internet through a
|
||||
tailnet exit node after it joins Tailscale. Use a MagicDNS name or 100.x address
|
||||
for an approved exit node. `tailscale.exitNodeAllowLanAccess` maps to
|
||||
Tailscale's LAN-access flag and requires `tailscale.exitNode`.
|
||||
|
||||
## Brokered Mode
|
||||
|
||||
The Worker mints a fresh auth key per requested lease using Tailscale OAuth.
|
||||
@ -93,8 +102,8 @@ CRABBOX_TAILSCALE_ENABLED set 0 to disable
|
||||
|
||||
Flow:
|
||||
|
||||
1. The CLI sends `tailscale`, `tailscaleTags`, and `tailscaleHostname` in
|
||||
`CreateLease`.
|
||||
1. The CLI sends `tailscale`, `tailscaleTags`, `tailscaleHostname`, and optional
|
||||
exit-node settings in `CreateLease`.
|
||||
2. The Worker validates requested tags against `CRABBOX_TAILSCALE_TAGS`.
|
||||
3. The Worker uses OAuth to mint a one-off, ephemeral, pre-approved, tagged auth
|
||||
key.
|
||||
@ -108,6 +117,19 @@ The auth key is never stored in lease records, provider labels, run logs, or
|
||||
local config. User-data can still contain the short-lived key at the provider,
|
||||
so use one-off ephemeral keys and avoid long-lived reusable keys.
|
||||
|
||||
## Exit Nodes
|
||||
|
||||
Exit-node egress is opt-in per lease:
|
||||
|
||||
```sh
|
||||
crabbox warmup --tailscale --tailscale-exit-node mac-studio.example.ts.net --tailscale-exit-node-allow-lan-access
|
||||
crabbox run --tailscale --tailscale-exit-node 100.100.100.100 -- curl -4 https://ifconfig.me
|
||||
```
|
||||
|
||||
The exit node must already advertise exit-node capability and be approved in
|
||||
Tailscale admin. ACLs/grants must allow the lease's tags, such as
|
||||
`tag:crabbox`, to access `autogroup:internet` through exit nodes.
|
||||
|
||||
## VNC And SSH
|
||||
|
||||
Crabbox continues to use OpenSSH and per-lease SSH keys. Tailscale SSH is not
|
||||
|
||||
@ -601,6 +601,18 @@ func cloudInitTailscaleBootstrap(cfg Config) string {
|
||||
sshUserGroup := shellQuote(sshUser)
|
||||
sshUserChown := shellQuote(sshUser + ":" + sshUser)
|
||||
tags := strings.Join(cfg.Tailscale.Tags, ",")
|
||||
tailscaleUpArgs := []string{
|
||||
"--auth-key=\"$TS_AUTHKEY\"",
|
||||
"--hostname=" + shellQuote(hostname),
|
||||
"--advertise-tags=" + shellQuote(tags),
|
||||
}
|
||||
exitNode := strings.TrimSpace(cfg.Tailscale.ExitNode)
|
||||
if exitNode != "" {
|
||||
tailscaleUpArgs = append(tailscaleUpArgs, "--exit-node="+shellQuote(exitNode))
|
||||
if cfg.Tailscale.ExitNodeAllowLANAccess {
|
||||
tailscaleUpArgs = append(tailscaleUpArgs, "--exit-node-allow-lan-access")
|
||||
}
|
||||
}
|
||||
if authKey == "" {
|
||||
return ` echo "tailscale requested but no auth key was injected" >&2
|
||||
exit 1`
|
||||
@ -610,7 +622,7 @@ func cloudInitTailscaleBootstrap(cfg Config) string {
|
||||
install -d -m 0750 -o ` + sshUserOwner + ` -g ` + sshUserGroup + ` /var/lib/crabbox
|
||||
set +x
|
||||
TS_AUTHKEY=` + shellQuote(authKey) + `
|
||||
tailscale up --auth-key="$TS_AUTHKEY" --hostname=` + shellQuote(hostname) + ` --advertise-tags=` + shellQuote(tags) + `
|
||||
tailscale up ` + strings.Join(tailscaleUpArgs, " ") + `
|
||||
unset TS_AUTHKEY
|
||||
set -x
|
||||
ts_ip=""
|
||||
@ -622,6 +634,10 @@ func cloudInitTailscaleBootstrap(cfg Config) string {
|
||||
test -n "$ts_ip"
|
||||
printf '%s\n' "$ts_ip" > /var/lib/crabbox/tailscale-ipv4
|
||||
printf '%s\n' ` + shellQuote(hostname) + ` > /var/lib/crabbox/tailscale-hostname
|
||||
if [ -n ` + shellQuote(exitNode) + ` ]; then
|
||||
printf '%s\n' ` + shellQuote(exitNode) + ` > /var/lib/crabbox/tailscale-exit-node
|
||||
printf '%s\n' ` + shellQuote(fmt.Sprint(cfg.Tailscale.ExitNodeAllowLANAccess)) + ` > /var/lib/crabbox/tailscale-exit-node-allow-lan-access
|
||||
fi
|
||||
if tailscale status --json >/var/lib/crabbox/tailscale-status.json 2>/dev/null; then
|
||||
jq -r '.Self.DNSName // empty' /var/lib/crabbox/tailscale-status.json > /var/lib/crabbox/tailscale-fqdn || true
|
||||
fi
|
||||
|
||||
@ -144,12 +144,16 @@ func TestCloudInitTailscaleProfile(t *testing.T) {
|
||||
cfg.Tailscale.AuthKey = "tskey-secret"
|
||||
cfg.Tailscale.Hostname = "crabbox-blue-lobster"
|
||||
cfg.Tailscale.Tags = []string{"tag:crabbox"}
|
||||
cfg.Tailscale.ExitNode = "mac-studio.tailnet.ts.net"
|
||||
cfg.Tailscale.ExitNodeAllowLANAccess = true
|
||||
got := cloudInit(cfg, "ssh-ed25519 test")
|
||||
for _, want := range []string{
|
||||
"https://tailscale.com/install.sh",
|
||||
"install -d -m 0750 -o 'runner' -g 'runner' /var/lib/crabbox",
|
||||
"tailscale up --auth-key=\"$TS_AUTHKEY\" --hostname='crabbox-blue-lobster' --advertise-tags='tag:crabbox'",
|
||||
"tailscale up --auth-key=\"$TS_AUTHKEY\" --hostname='crabbox-blue-lobster' --advertise-tags='tag:crabbox' --exit-node='mac-studio.tailnet.ts.net' --exit-node-allow-lan-access",
|
||||
"printf '%s\\n' 'crabbox-blue-lobster' > /var/lib/crabbox/tailscale-hostname",
|
||||
"printf '%s\\n' 'mac-studio.tailnet.ts.net' > /var/lib/crabbox/tailscale-exit-node",
|
||||
"printf '%s\\n' 'true' > /var/lib/crabbox/tailscale-exit-node-allow-lan-access",
|
||||
"chown 'runner:runner' /var/lib/crabbox/tailscale-* || true",
|
||||
"test -s /var/lib/crabbox/tailscale-ipv4",
|
||||
"grep -Eq '^100\\.' /var/lib/crabbox/tailscale-ipv4",
|
||||
|
||||
@ -411,11 +411,13 @@ type fileIsloConfig struct {
|
||||
}
|
||||
|
||||
type fileTailscaleConfig struct {
|
||||
Enabled *bool `yaml:"enabled,omitempty"`
|
||||
Network string `yaml:"network,omitempty"`
|
||||
Tags []string `yaml:"tags,omitempty"`
|
||||
HostnameTemplate string `yaml:"hostnameTemplate,omitempty"`
|
||||
AuthKeyEnv string `yaml:"authKeyEnv,omitempty"`
|
||||
Enabled *bool `yaml:"enabled,omitempty"`
|
||||
Network string `yaml:"network,omitempty"`
|
||||
Tags []string `yaml:"tags,omitempty"`
|
||||
HostnameTemplate string `yaml:"hostnameTemplate,omitempty"`
|
||||
AuthKeyEnv string `yaml:"authKeyEnv,omitempty"`
|
||||
ExitNode string `yaml:"exitNode,omitempty"`
|
||||
ExitNodeAllowLANAccess *bool `yaml:"exitNodeAllowLanAccess,omitempty"`
|
||||
}
|
||||
|
||||
type fileStaticConfig struct {
|
||||
@ -834,6 +836,12 @@ func applyFileConfig(cfg *Config, file fileConfig) {
|
||||
if file.Tailscale.AuthKeyEnv != "" {
|
||||
cfg.Tailscale.AuthKeyEnv = file.Tailscale.AuthKeyEnv
|
||||
}
|
||||
if file.Tailscale.ExitNode != "" {
|
||||
cfg.Tailscale.ExitNode = strings.TrimSpace(file.Tailscale.ExitNode)
|
||||
}
|
||||
if file.Tailscale.ExitNodeAllowLANAccess != nil {
|
||||
cfg.Tailscale.ExitNodeAllowLANAccess = *file.Tailscale.ExitNodeAllowLANAccess
|
||||
}
|
||||
}
|
||||
if file.Static != nil {
|
||||
if file.Static.ID != "" {
|
||||
@ -982,6 +990,10 @@ func applyEnv(cfg *Config) {
|
||||
}
|
||||
cfg.Tailscale.HostnameTemplate = getenv("CRABBOX_TAILSCALE_HOSTNAME_TEMPLATE", cfg.Tailscale.HostnameTemplate)
|
||||
cfg.Tailscale.AuthKeyEnv = getenv("CRABBOX_TAILSCALE_AUTH_KEY_ENV", cfg.Tailscale.AuthKeyEnv)
|
||||
cfg.Tailscale.ExitNode = getenv("CRABBOX_TAILSCALE_EXIT_NODE", cfg.Tailscale.ExitNode)
|
||||
if value, ok := getenvBool("CRABBOX_TAILSCALE_EXIT_NODE_ALLOW_LAN_ACCESS"); ok {
|
||||
cfg.Tailscale.ExitNodeAllowLANAccess = value
|
||||
}
|
||||
if cfg.Tailscale.AuthKeyEnv != "" {
|
||||
cfg.Tailscale.AuthKey = getenv(cfg.Tailscale.AuthKeyEnv, "")
|
||||
}
|
||||
|
||||
@ -20,6 +20,8 @@ func clearConfigEnv(t *testing.T) {
|
||||
"CRABBOX_TAILSCALE_HOSTNAME_TEMPLATE",
|
||||
"CRABBOX_TAILSCALE_AUTH_KEY_ENV",
|
||||
"CRABBOX_TAILSCALE_AUTH_KEY",
|
||||
"CRABBOX_TAILSCALE_EXIT_NODE",
|
||||
"CRABBOX_TAILSCALE_EXIT_NODE_ALLOW_LAN_ACCESS",
|
||||
"CRABBOX_ACCESS_CLIENT_ID",
|
||||
"CRABBOX_ACCESS_CLIENT_SECRET",
|
||||
"CRABBOX_ACCESS_TOKEN",
|
||||
@ -279,6 +281,8 @@ tailscale:
|
||||
- tag:ci
|
||||
hostnameTemplate: cbx-{slug}
|
||||
authKeyEnv: TEST_TS_AUTH_KEY
|
||||
exitNode: mac-studio.tailnet.ts.net
|
||||
exitNodeAllowLanAccess: true
|
||||
`), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -287,7 +291,7 @@ tailscale:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !cfg.Tailscale.Enabled || cfg.Network != NetworkTailscale || cfg.Tailscale.HostnameTemplate != "cbx-{slug}" || cfg.Tailscale.AuthKeyEnv != "TEST_TS_AUTH_KEY" {
|
||||
if !cfg.Tailscale.Enabled || cfg.Network != NetworkTailscale || cfg.Tailscale.HostnameTemplate != "cbx-{slug}" || cfg.Tailscale.AuthKeyEnv != "TEST_TS_AUTH_KEY" || cfg.Tailscale.ExitNode != "mac-studio.tailnet.ts.net" || !cfg.Tailscale.ExitNodeAllowLANAccess {
|
||||
t.Fatalf("tailscale config not loaded: network=%s tailscale=%#v", cfg.Network, cfg.Tailscale)
|
||||
}
|
||||
if len(cfg.Tailscale.Tags) != 2 || cfg.Tailscale.Tags[1] != "tag:ci" {
|
||||
@ -315,6 +319,8 @@ func TestEnvOverridesConfig(t *testing.T) {
|
||||
t.Setenv("CRABBOX_TAILSCALE_TAGS", "tag:crabbox,tag:ci")
|
||||
t.Setenv("CRABBOX_TAILSCALE_HOSTNAME_TEMPLATE", "lease-{id}")
|
||||
t.Setenv("CRABBOX_TAILSCALE_AUTH_KEY", "tskey-secret")
|
||||
t.Setenv("CRABBOX_TAILSCALE_EXIT_NODE", "mac-studio.tailnet.ts.net")
|
||||
t.Setenv("CRABBOX_TAILSCALE_EXIT_NODE_ALLOW_LAN_ACCESS", "1")
|
||||
t.Setenv("CRABBOX_TARGET", "macos")
|
||||
t.Setenv("CRABBOX_STATIC_HOST", "mac.local")
|
||||
t.Setenv("DAYTONA_API_KEY", "daytona-api-file")
|
||||
@ -370,7 +376,7 @@ func TestEnvOverridesConfig(t *testing.T) {
|
||||
if cfg.TargetOS != targetMacOS || cfg.Static.Host != "mac.local" {
|
||||
t.Fatalf("unexpected target env: target=%s static=%#v", cfg.TargetOS, cfg.Static)
|
||||
}
|
||||
if cfg.Network != NetworkPublic || cfg.Tailscale.AuthKey != "tskey-secret" || cfg.Tailscale.HostnameTemplate != "lease-{id}" {
|
||||
if cfg.Network != NetworkPublic || cfg.Tailscale.AuthKey != "tskey-secret" || cfg.Tailscale.HostnameTemplate != "lease-{id}" || cfg.Tailscale.ExitNode != "mac-studio.tailnet.ts.net" || !cfg.Tailscale.ExitNodeAllowLANAccess {
|
||||
t.Fatalf("unexpected tailscale env: network=%s tailscale=%#v", cfg.Network, cfg.Tailscale)
|
||||
}
|
||||
if len(cfg.Tailscale.Tags) != 2 || cfg.Tailscale.Tags[1] != "tag:ci" {
|
||||
@ -396,12 +402,14 @@ func TestTailscaleEnvOverrides(t *testing.T) {
|
||||
t.Setenv("CRABBOX_TAILSCALE_TAGS", "tag:crabbox,tag:ci")
|
||||
t.Setenv("CRABBOX_TAILSCALE_HOSTNAME_TEMPLATE", "lease-{slug}")
|
||||
t.Setenv("CRABBOX_TAILSCALE_AUTH_KEY", "tskey-secret")
|
||||
t.Setenv("CRABBOX_TAILSCALE_EXIT_NODE", "100.100.100.100")
|
||||
t.Setenv("CRABBOX_TAILSCALE_EXIT_NODE_ALLOW_LAN_ACCESS", "true")
|
||||
|
||||
cfg, err := loadConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cfg.Network != NetworkTailscale || !cfg.Tailscale.Enabled || cfg.Tailscale.AuthKey != "tskey-secret" || cfg.Tailscale.HostnameTemplate != "lease-{slug}" {
|
||||
if cfg.Network != NetworkTailscale || !cfg.Tailscale.Enabled || cfg.Tailscale.AuthKey != "tskey-secret" || cfg.Tailscale.HostnameTemplate != "lease-{slug}" || cfg.Tailscale.ExitNode != "100.100.100.100" || !cfg.Tailscale.ExitNodeAllowLANAccess {
|
||||
t.Fatalf("unexpected tailscale env: network=%s tailscale=%#v", cfg.Network, cfg.Tailscale)
|
||||
}
|
||||
if len(cfg.Tailscale.Tags) != 2 || cfg.Tailscale.Tags[1] != "tag:ci" {
|
||||
|
||||
@ -311,31 +311,33 @@ func (c *CoordinatorClient) CreateLease(ctx context.Context, cfg Config, publicK
|
||||
slug = newLeaseSlug(leaseID)
|
||||
}
|
||||
err := c.do(ctx, http.MethodPost, "/v1/leases", map[string]any{
|
||||
"leaseID": leaseID,
|
||||
"slug": slug,
|
||||
"profile": cfg.Profile,
|
||||
"provider": cfg.Provider,
|
||||
"target": cfg.TargetOS,
|
||||
"windowsMode": cfg.WindowsMode,
|
||||
"desktop": cfg.Desktop,
|
||||
"browser": cfg.Browser,
|
||||
"code": cfg.Code,
|
||||
"tailscale": cfg.Tailscale.Enabled,
|
||||
"tailscaleTags": cfg.Tailscale.Tags,
|
||||
"tailscaleHostname": cfg.Tailscale.Hostname,
|
||||
"class": cfg.Class,
|
||||
"serverType": cfg.ServerType,
|
||||
"serverTypeExplicit": cfg.ServerTypeExplicit,
|
||||
"location": cfg.Location,
|
||||
"image": cfg.Image,
|
||||
"awsRegion": cfg.AWSRegion,
|
||||
"awsAMI": cfg.AWSAMI,
|
||||
"awsSGID": cfg.AWSSGID,
|
||||
"awsSubnetID": cfg.AWSSubnetID,
|
||||
"awsProfile": cfg.AWSProfile,
|
||||
"awsRootGB": cfg.AWSRootGB,
|
||||
"awsSSHCIDRs": cfg.AWSSSHCIDRs,
|
||||
"awsMacHostID": cfg.AWSMacHostID,
|
||||
"leaseID": leaseID,
|
||||
"slug": slug,
|
||||
"profile": cfg.Profile,
|
||||
"provider": cfg.Provider,
|
||||
"target": cfg.TargetOS,
|
||||
"windowsMode": cfg.WindowsMode,
|
||||
"desktop": cfg.Desktop,
|
||||
"browser": cfg.Browser,
|
||||
"code": cfg.Code,
|
||||
"tailscale": cfg.Tailscale.Enabled,
|
||||
"tailscaleTags": cfg.Tailscale.Tags,
|
||||
"tailscaleHostname": cfg.Tailscale.Hostname,
|
||||
"tailscaleExitNode": cfg.Tailscale.ExitNode,
|
||||
"tailscaleExitNodeAllowLanAccess": cfg.Tailscale.ExitNodeAllowLANAccess,
|
||||
"class": cfg.Class,
|
||||
"serverType": cfg.ServerType,
|
||||
"serverTypeExplicit": cfg.ServerTypeExplicit,
|
||||
"location": cfg.Location,
|
||||
"image": cfg.Image,
|
||||
"awsRegion": cfg.AWSRegion,
|
||||
"awsAMI": cfg.AWSAMI,
|
||||
"awsSGID": cfg.AWSSGID,
|
||||
"awsSubnetID": cfg.AWSSubnetID,
|
||||
"awsProfile": cfg.AWSProfile,
|
||||
"awsRootGB": cfg.AWSRootGB,
|
||||
"awsSSHCIDRs": cfg.AWSSSHCIDRs,
|
||||
"awsMacHostID": cfg.AWSMacHostID,
|
||||
"capacity": map[string]any{
|
||||
"market": cfg.Capacity.Market,
|
||||
"strategy": cfg.Capacity.Strategy,
|
||||
|
||||
@ -36,6 +36,9 @@ func (a App) inspect(ctx context.Context, args []string) error {
|
||||
fmt.Fprintf(a.Stdout, "id=%s\nslug=%s\nprovider=%s\ntarget=%s\nwindows_mode=%s\nstate=%s\nserver=%s\nhost=%s\nnetwork=%s\nssh=%s -p %s %s@%s\nssh_fallback_ports=%s\nidle_for=%s\nidle_timeout=%s\nlast_touched=%s\nexpires=%s\n", state.ID, blank(state.Slug, "-"), state.Provider, state.TargetOS, blank(state.WindowsMode, "-"), state.State, state.ServerID, state.Host, state.Network, state.SSHKey, state.SSHPort, state.SSHUser, state.SSHHost, blank(strings.Join(state.SSHFallbackPorts, ","), "-"), blank(state.IdleFor, "-"), blank(state.IdleTimeout, "-"), blank(state.LastTouchedAt, "-"), blank(state.ExpiresAt, "-"))
|
||||
if state.Tailscale != nil && state.Tailscale.Enabled {
|
||||
fmt.Fprintf(a.Stdout, "tailscale.state=%s\ntailscale.hostname=%s\ntailscale.fqdn=%s\ntailscale.ipv4=%s\ntailscale.tags=%s\n", blank(state.Tailscale.State, "-"), blank(state.Tailscale.Hostname, "-"), blank(state.Tailscale.FQDN, "-"), blank(state.Tailscale.IPv4, "-"), blank(strings.Join(state.Tailscale.Tags, ","), "-"))
|
||||
if state.Tailscale.ExitNode != "" {
|
||||
fmt.Fprintf(a.Stdout, "tailscale.exit_node=%s\ntailscale.exit_node_allow_lan_access=%t\n", state.Tailscale.ExitNode, state.Tailscale.ExitNodeAllowLANAccess)
|
||||
}
|
||||
}
|
||||
for key, value := range state.Labels {
|
||||
fmt.Fprintf(a.Stdout, "label.%s=%s\n", key, value)
|
||||
|
||||
@ -17,30 +17,36 @@ const (
|
||||
)
|
||||
|
||||
type TailscaleConfig struct {
|
||||
Enabled bool
|
||||
Tags []string
|
||||
HostnameTemplate string
|
||||
Hostname string
|
||||
AuthKeyEnv string
|
||||
AuthKey string
|
||||
Enabled bool
|
||||
Tags []string
|
||||
HostnameTemplate string
|
||||
Hostname string
|
||||
AuthKeyEnv string
|
||||
AuthKey string
|
||||
ExitNode string
|
||||
ExitNodeAllowLANAccess bool
|
||||
}
|
||||
|
||||
type TailscaleMetadata struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
FQDN string `json:"fqdn,omitempty"`
|
||||
IPv4 string `json:"ipv4,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
FQDN string `json:"fqdn,omitempty"`
|
||||
IPv4 string `json:"ipv4,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
ExitNode string `json:"exitNode,omitempty"`
|
||||
ExitNodeAllowLANAccess bool `json:"exitNodeAllowLanAccess,omitempty"`
|
||||
}
|
||||
|
||||
type networkFlagValues struct {
|
||||
Network *string
|
||||
Tailscale *bool
|
||||
TailscaleTags *string
|
||||
TailscaleHost *string
|
||||
TailscaleKeyEnv *string
|
||||
Network *string
|
||||
Tailscale *bool
|
||||
TailscaleTags *string
|
||||
TailscaleHost *string
|
||||
TailscaleKeyEnv *string
|
||||
TailscaleExitNode *string
|
||||
TailscaleExitNodeAllowLAN *bool
|
||||
}
|
||||
|
||||
type networkModeFlagValues struct {
|
||||
@ -75,11 +81,13 @@ func applyNetworkModeFlagOverride(cfg *Config, fs *flag.FlagSet, values networkM
|
||||
|
||||
func registerNetworkFlags(fs *flag.FlagSet, defaults Config) networkFlagValues {
|
||||
return networkFlagValues{
|
||||
Network: fs.String("network", string(defaults.Network), "network mode: auto, tailscale, or public"),
|
||||
Tailscale: fs.Bool("tailscale", defaults.Tailscale.Enabled, "join new managed leases to the configured tailnet"),
|
||||
TailscaleTags: fs.String("tailscale-tags", strings.Join(defaults.Tailscale.Tags, ","), "comma-separated Tailscale tags for new managed leases"),
|
||||
TailscaleHost: fs.String("tailscale-hostname-template", defaults.Tailscale.HostnameTemplate, "Tailscale hostname template for new managed leases"),
|
||||
TailscaleKeyEnv: fs.String("tailscale-auth-key-env", defaults.Tailscale.AuthKeyEnv, "environment variable containing a direct-provider Tailscale auth key"),
|
||||
Network: fs.String("network", string(defaults.Network), "network mode: auto, tailscale, or public"),
|
||||
Tailscale: fs.Bool("tailscale", defaults.Tailscale.Enabled, "join new managed leases to the configured tailnet"),
|
||||
TailscaleTags: fs.String("tailscale-tags", strings.Join(defaults.Tailscale.Tags, ","), "comma-separated Tailscale tags for new managed leases"),
|
||||
TailscaleHost: fs.String("tailscale-hostname-template", defaults.Tailscale.HostnameTemplate, "Tailscale hostname template for new managed leases"),
|
||||
TailscaleKeyEnv: fs.String("tailscale-auth-key-env", defaults.Tailscale.AuthKeyEnv, "environment variable containing a direct-provider Tailscale auth key"),
|
||||
TailscaleExitNode: fs.String("tailscale-exit-node", defaults.Tailscale.ExitNode, "Tailscale exit node name or 100.x address for new managed leases"),
|
||||
TailscaleExitNodeAllowLAN: fs.Bool("tailscale-exit-node-allow-lan-access", defaults.Tailscale.ExitNodeAllowLANAccess, "allow LAN access while using the Tailscale exit node"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,6 +112,12 @@ func applyNetworkFlagOverrides(cfg *Config, fs *flag.FlagSet, values networkFlag
|
||||
cfg.Tailscale.AuthKeyEnv = strings.TrimSpace(*values.TailscaleKeyEnv)
|
||||
cfg.Tailscale.AuthKey = getenv(cfg.Tailscale.AuthKeyEnv, "")
|
||||
}
|
||||
if flagWasSet(fs, "tailscale-exit-node") {
|
||||
cfg.Tailscale.ExitNode = strings.TrimSpace(*values.TailscaleExitNode)
|
||||
}
|
||||
if flagWasSet(fs, "tailscale-exit-node-allow-lan-access") {
|
||||
cfg.Tailscale.ExitNodeAllowLANAccess = *values.TailscaleExitNodeAllowLAN
|
||||
}
|
||||
return validateNetworkConfig(*cfg)
|
||||
}
|
||||
|
||||
@ -136,6 +150,9 @@ func validateNetworkConfig(cfg Config) error {
|
||||
if strings.TrimSpace(cfg.Tailscale.HostnameTemplate) == "" {
|
||||
return exit(2, "tailscale.hostnameTemplate must not be empty")
|
||||
}
|
||||
if cfg.Tailscale.ExitNodeAllowLANAccess && strings.TrimSpace(cfg.Tailscale.ExitNode) == "" {
|
||||
return exit(2, "tailscale.exitNodeAllowLanAccess requires tailscale.exitNode")
|
||||
}
|
||||
if cfg.TargetOS != targetLinux {
|
||||
return exit(2, "--tailscale managed provisioning currently supports target=linux only")
|
||||
}
|
||||
@ -241,12 +258,14 @@ func tailscaleTargetHost(meta TailscaleMetadata) string {
|
||||
func serverTailscaleMetadata(server Server) TailscaleMetadata {
|
||||
labels := server.Labels
|
||||
meta := TailscaleMetadata{
|
||||
Enabled: labelBool(labels["tailscale"]),
|
||||
Hostname: labels["tailscale_hostname"],
|
||||
FQDN: labels["tailscale_fqdn"],
|
||||
IPv4: labels["tailscale_ipv4"],
|
||||
State: labels["tailscale_state"],
|
||||
Error: labels["tailscale_error"],
|
||||
Enabled: labelBool(labels["tailscale"]),
|
||||
Hostname: labels["tailscale_hostname"],
|
||||
FQDN: labels["tailscale_fqdn"],
|
||||
IPv4: labels["tailscale_ipv4"],
|
||||
State: labels["tailscale_state"],
|
||||
Error: labels["tailscale_error"],
|
||||
ExitNode: labels["tailscale_exit_node"],
|
||||
ExitNodeAllowLANAccess: labelBool(labels["tailscale_exit_node_allow_lan_access"]),
|
||||
}
|
||||
if tags := splitCommaList(labels["tailscale_tags"]); len(tags) > 0 {
|
||||
meta.Tags = tags
|
||||
@ -279,6 +298,12 @@ func applyTailscaleMetadataToServer(server *Server, meta TailscaleMetadata) {
|
||||
if meta.Error != "" {
|
||||
server.Labels["tailscale_error"] = meta.Error
|
||||
}
|
||||
if meta.ExitNode != "" {
|
||||
server.Labels["tailscale_exit_node"] = meta.ExitNode
|
||||
}
|
||||
if meta.ExitNodeAllowLANAccess {
|
||||
server.Labels["tailscale_exit_node_allow_lan_access"] = "true"
|
||||
}
|
||||
}
|
||||
|
||||
func (a App) refreshTailscaleMetadata(ctx context.Context, cfg Config, coord *CoordinatorClient, useCoordinator bool, server *Server, target SSHTarget, leaseID string) {
|
||||
@ -296,6 +321,8 @@ func (a App) refreshTailscaleMetadata(ctx context.Context, cfg Config, coord *Co
|
||||
meta.Hostname = firstNonEmpty(meta.Hostname, existing.Hostname)
|
||||
meta.FQDN = firstNonEmpty(meta.FQDN, existing.FQDN)
|
||||
meta.Tags = appendUniqueStrings(existing.Tags, meta.Tags...)
|
||||
meta.ExitNode = firstNonEmpty(meta.ExitNode, existing.ExitNode)
|
||||
meta.ExitNodeAllowLANAccess = meta.ExitNodeAllowLANAccess || existing.ExitNodeAllowLANAccess
|
||||
}
|
||||
applyTailscaleMetadataToServer(server, meta)
|
||||
if useCoordinator && coord != nil && leaseID != "" {
|
||||
@ -313,7 +340,11 @@ func readRemoteTailscaleMetadata(ctx context.Context, target SSHTarget) (Tailsca
|
||||
printf '\n'
|
||||
if [ -f /var/lib/crabbox/tailscale-hostname ]; then cat /var/lib/crabbox/tailscale-hostname; fi
|
||||
printf '\n'
|
||||
if [ -f /var/lib/crabbox/tailscale-fqdn ]; then cat /var/lib/crabbox/tailscale-fqdn; fi`)
|
||||
if [ -f /var/lib/crabbox/tailscale-fqdn ]; then cat /var/lib/crabbox/tailscale-fqdn; fi
|
||||
printf '\n'
|
||||
if [ -f /var/lib/crabbox/tailscale-exit-node ]; then cat /var/lib/crabbox/tailscale-exit-node; fi
|
||||
printf '\n'
|
||||
if [ -f /var/lib/crabbox/tailscale-exit-node-allow-lan-access ]; then cat /var/lib/crabbox/tailscale-exit-node-allow-lan-access; fi`)
|
||||
if err != nil {
|
||||
return TailscaleMetadata{}, err
|
||||
}
|
||||
@ -328,6 +359,12 @@ if [ -f /var/lib/crabbox/tailscale-fqdn ]; then cat /var/lib/crabbox/tailscale-f
|
||||
if len(lines) > 2 {
|
||||
meta.FQDN = strings.TrimSpace(lines[2])
|
||||
}
|
||||
if len(lines) > 3 {
|
||||
meta.ExitNode = strings.TrimSpace(lines[3])
|
||||
}
|
||||
if len(lines) > 4 {
|
||||
meta.ExitNodeAllowLANAccess = labelBool(strings.TrimSpace(lines[4]))
|
||||
}
|
||||
if meta.IPv4 == "" {
|
||||
return TailscaleMetadata{}, fmt.Errorf("remote tailscale metadata missing ipv4")
|
||||
}
|
||||
|
||||
@ -49,6 +49,10 @@ func directLeaseLabels(cfg Config, leaseID, slug, provider, market string, keep
|
||||
labels["tailscale_state"] = "requested"
|
||||
labels["tailscale_hostname"] = cfg.Tailscale.Hostname
|
||||
labels["tailscale_tags"] = strings.Join(cfg.Tailscale.Tags, ",")
|
||||
if cfg.Tailscale.ExitNode != "" {
|
||||
labels["tailscale_exit_node"] = cfg.Tailscale.ExitNode
|
||||
labels["tailscale_exit_node_allow_lan_access"] = fmt.Sprint(cfg.Tailscale.ExitNodeAllowLANAccess)
|
||||
}
|
||||
}
|
||||
return sanitizeProviderLabels(labels)
|
||||
}
|
||||
|
||||
@ -50,6 +50,8 @@ func TestDirectLeaseLabelsIncludeNonSecretTailscaleMetadata(t *testing.T) {
|
||||
cfg.Tailscale.Hostname = "crabbox-blue-lobster"
|
||||
cfg.Tailscale.Tags = []string{"tag:crabbox"}
|
||||
cfg.Tailscale.AuthKey = "tskey-secret"
|
||||
cfg.Tailscale.ExitNode = "mac-studio.tailnet.ts.net"
|
||||
cfg.Tailscale.ExitNodeAllowLANAccess = true
|
||||
labels := directLeaseLabels(cfg, "cbx_abcdef123456", "blue-lobster", "hetzner", "", true, time.Now())
|
||||
if labels["tailscale"] != "true" || labels["tailscale_state"] != "requested" {
|
||||
t.Fatalf("tailscale labels missing: %#v", labels)
|
||||
@ -57,6 +59,9 @@ func TestDirectLeaseLabelsIncludeNonSecretTailscaleMetadata(t *testing.T) {
|
||||
if labels["tailscale_hostname"] != "crabbox-blue-lobster" || labels["tailscale_tags"] != "tag_crabbox" {
|
||||
t.Fatalf("tailscale metadata labels unexpected: %#v", labels)
|
||||
}
|
||||
if labels["tailscale_exit_node"] != "mac-studio.tailnet.ts.net" || labels["tailscale_exit_node_allow_lan_access"] != "true" {
|
||||
t.Fatalf("tailscale exit-node labels unexpected: %#v", labels)
|
||||
}
|
||||
for key, value := range labels {
|
||||
if strings.Contains(value, "tskey-secret") || strings.Contains(key, "auth") {
|
||||
t.Fatalf("tailscale secret leaked through label %s=%q", key, value)
|
||||
|
||||
@ -475,12 +475,23 @@ function tailscaleBootstrap(config: LeaseConfig): string {
|
||||
exit 1`;
|
||||
}
|
||||
const sshUser = config.sshUser.trim() || "crabbox";
|
||||
const upArgs = [
|
||||
`--auth-key="$TS_AUTHKEY"`,
|
||||
`--hostname=${shellQuote(config.tailscaleHostname)}`,
|
||||
`--advertise-tags=${shellQuote(config.tailscaleTags.join(","))}`,
|
||||
];
|
||||
if (config.tailscaleExitNode) {
|
||||
upArgs.push(`--exit-node=${shellQuote(config.tailscaleExitNode)}`);
|
||||
if (config.tailscaleExitNodeAllowLanAccess) {
|
||||
upArgs.push("--exit-node-allow-lan-access");
|
||||
}
|
||||
}
|
||||
return ` retry sh -c 'curl -fsSL https://tailscale.com/install.sh | sh'
|
||||
systemctl enable --now tailscaled || service tailscaled start || true
|
||||
install -d -m 0750 -o ${shellQuote(sshUser)} -g ${shellQuote(sshUser)} /var/lib/crabbox
|
||||
set +x
|
||||
TS_AUTHKEY=${shellQuote(config.tailscaleAuthKey)}
|
||||
tailscale up --auth-key="$TS_AUTHKEY" --hostname=${shellQuote(config.tailscaleHostname)} --advertise-tags=${shellQuote(config.tailscaleTags.join(","))}
|
||||
tailscale up ${upArgs.join(" ")}
|
||||
unset TS_AUTHKEY
|
||||
set -x
|
||||
ts_ip=""
|
||||
@ -492,6 +503,10 @@ function tailscaleBootstrap(config: LeaseConfig): string {
|
||||
test -n "$ts_ip"
|
||||
printf '%s\\n' "$ts_ip" > /var/lib/crabbox/tailscale-ipv4
|
||||
printf '%s\\n' ${shellQuote(config.tailscaleHostname)} > /var/lib/crabbox/tailscale-hostname
|
||||
if [ -n ${shellQuote(config.tailscaleExitNode)} ]; then
|
||||
printf '%s\\n' ${shellQuote(config.tailscaleExitNode)} > /var/lib/crabbox/tailscale-exit-node
|
||||
printf '%s\\n' ${shellQuote(String(config.tailscaleExitNodeAllowLanAccess))} > /var/lib/crabbox/tailscale-exit-node-allow-lan-access
|
||||
fi
|
||||
if tailscale status --json >/var/lib/crabbox/tailscale-status.json 2>/dev/null; then
|
||||
jq -r '.Self.DNSName // empty' /var/lib/crabbox/tailscale-status.json > /var/lib/crabbox/tailscale-fqdn || true
|
||||
fi
|
||||
|
||||
@ -11,6 +11,8 @@ export interface LeaseConfig {
|
||||
tailscaleTags: string[];
|
||||
tailscaleHostname: string;
|
||||
tailscaleAuthKey: string;
|
||||
tailscaleExitNode: string;
|
||||
tailscaleExitNodeAllowLanAccess: boolean;
|
||||
profile: string;
|
||||
class: string;
|
||||
serverType: string;
|
||||
@ -79,6 +81,11 @@ export function leaseConfig(input: LeaseRequest): LeaseConfig {
|
||||
if (!sshPublicKey) {
|
||||
throw new Error("sshPublicKey is required");
|
||||
}
|
||||
const tailscaleExitNode = input.tailscaleExitNode?.trim() ?? "";
|
||||
const tailscaleExitNodeAllowLanAccess = input.tailscaleExitNodeAllowLanAccess ?? false;
|
||||
if (tailscaleExitNodeAllowLanAccess && !tailscaleExitNode) {
|
||||
throw new Error("tailscaleExitNodeAllowLanAccess requires tailscaleExitNode");
|
||||
}
|
||||
return {
|
||||
provider,
|
||||
target,
|
||||
@ -90,6 +97,8 @@ export function leaseConfig(input: LeaseRequest): LeaseConfig {
|
||||
tailscaleTags: normalizeTailscaleTags(input.tailscaleTags ?? ["tag:crabbox"]),
|
||||
tailscaleHostname: input.tailscaleHostname ?? "",
|
||||
tailscaleAuthKey: "",
|
||||
tailscaleExitNode,
|
||||
tailscaleExitNodeAllowLanAccess,
|
||||
profile: input.profile ?? "default",
|
||||
class: machineClass,
|
||||
serverType,
|
||||
|
||||
@ -486,6 +486,10 @@ export class FleetDurableObject implements DurableObject {
|
||||
tags: config.tailscaleTags,
|
||||
state: "requested",
|
||||
};
|
||||
if (config.tailscaleExitNode) {
|
||||
record.tailscale.exitNode = config.tailscaleExitNode;
|
||||
record.tailscale.exitNodeAllowLanAccess = config.tailscaleExitNodeAllowLanAccess;
|
||||
}
|
||||
}
|
||||
const limitError = enforceCostLimits(leases, record, costLimits(this.env), now);
|
||||
if (limitError) {
|
||||
@ -613,6 +617,13 @@ export class FleetDurableObject implements DurableObject {
|
||||
slug,
|
||||
config.provider,
|
||||
);
|
||||
config.tailscaleExitNode =
|
||||
nonSecretString(input.tailscaleExitNode) || config.tailscaleExitNode;
|
||||
config.tailscaleExitNodeAllowLanAccess =
|
||||
input.tailscaleExitNodeAllowLanAccess ?? config.tailscaleExitNodeAllowLanAccess;
|
||||
if (!config.tailscaleExitNode) {
|
||||
config.tailscaleExitNodeAllowLanAccess = false;
|
||||
}
|
||||
config.tailscaleAuthKey = await createTailscaleAuthKey(this.env, {
|
||||
hostname: config.tailscaleHostname,
|
||||
tags: config.tailscaleTags,
|
||||
@ -2026,6 +2037,7 @@ function mergeTailscaleMetadata(
|
||||
const fqdn = nonSecretString(input.fqdn) || current?.fqdn;
|
||||
const ipv4 = nonSecretString(input.ipv4) || current?.ipv4;
|
||||
const error = nonSecretString(input.error) || current?.error;
|
||||
const exitNode = nonSecretString(input.exitNode) || current?.exitNode;
|
||||
if (hostname) {
|
||||
merged.hostname = hostname;
|
||||
}
|
||||
@ -2038,6 +2050,11 @@ function mergeTailscaleMetadata(
|
||||
if (error) {
|
||||
merged.error = error;
|
||||
}
|
||||
if (exitNode) {
|
||||
merged.exitNode = exitNode;
|
||||
merged.exitNodeAllowLanAccess =
|
||||
input.exitNodeAllowLanAccess ?? current?.exitNodeAllowLanAccess ?? false;
|
||||
}
|
||||
if (merged.state !== "failed") {
|
||||
delete merged.error;
|
||||
}
|
||||
|
||||
@ -46,6 +46,12 @@ export function leaseProviderLabels(
|
||||
labels["tailscale_state"] = "requested";
|
||||
labels["tailscale_hostname"] = config.tailscaleHostname;
|
||||
labels["tailscale_tags"] = config.tailscaleTags.join(",");
|
||||
if (config.tailscaleExitNode) {
|
||||
labels["tailscale_exit_node"] = config.tailscaleExitNode;
|
||||
labels["tailscale_exit_node_allow_lan_access"] = String(
|
||||
config.tailscaleExitNodeAllowLanAccess,
|
||||
);
|
||||
}
|
||||
}
|
||||
return sanitizeLabels({ ...labels, ...extra });
|
||||
}
|
||||
|
||||
@ -54,6 +54,8 @@ export interface LeaseRequest {
|
||||
tailscale?: boolean;
|
||||
tailscaleTags?: string[];
|
||||
tailscaleHostname?: string;
|
||||
tailscaleExitNode?: string;
|
||||
tailscaleExitNodeAllowLanAccess?: boolean;
|
||||
profile?: string;
|
||||
class?: string;
|
||||
serverType?: string;
|
||||
@ -161,6 +163,8 @@ export interface TailscaleMetadata {
|
||||
tags?: string[];
|
||||
state?: "requested" | "ready" | "failed";
|
||||
error?: string;
|
||||
exitNode?: string;
|
||||
exitNodeAllowLanAccess?: boolean;
|
||||
}
|
||||
|
||||
export interface ProvisioningAttempt {
|
||||
|
||||
@ -14,6 +14,8 @@ const config: LeaseConfig = {
|
||||
tailscaleTags: ["tag:crabbox"],
|
||||
tailscaleHostname: "",
|
||||
tailscaleAuthKey: "",
|
||||
tailscaleExitNode: "",
|
||||
tailscaleExitNodeAllowLanAccess: false,
|
||||
profile: "project-check",
|
||||
class: "standard",
|
||||
serverType: "c7a.8xlarge",
|
||||
@ -153,15 +155,23 @@ describe("cloud-init bootstrap", () => {
|
||||
tailscaleTags: ["tag:crabbox"],
|
||||
tailscaleHostname: "crabbox-blue-lobster",
|
||||
tailscaleAuthKey: "tskey-secret",
|
||||
tailscaleExitNode: "mac-studio.tailnet.ts.net",
|
||||
tailscaleExitNodeAllowLanAccess: true,
|
||||
});
|
||||
expect(got).toContain("https://tailscale.com/install.sh");
|
||||
expect(got).toContain("install -d -m 0750 -o 'runner' -g 'runner' /var/lib/crabbox");
|
||||
expect(got).toContain(
|
||||
"tailscale up --auth-key=\"$TS_AUTHKEY\" --hostname='crabbox-blue-lobster' --advertise-tags='tag:crabbox'",
|
||||
"tailscale up --auth-key=\"$TS_AUTHKEY\" --hostname='crabbox-blue-lobster' --advertise-tags='tag:crabbox' --exit-node='mac-studio.tailnet.ts.net' --exit-node-allow-lan-access",
|
||||
);
|
||||
expect(got).toContain(
|
||||
"printf '%s\\n' 'crabbox-blue-lobster' > /var/lib/crabbox/tailscale-hostname",
|
||||
);
|
||||
expect(got).toContain(
|
||||
"printf '%s\\n' 'mac-studio.tailnet.ts.net' > /var/lib/crabbox/tailscale-exit-node",
|
||||
);
|
||||
expect(got).toContain(
|
||||
"printf '%s\\n' 'true' > /var/lib/crabbox/tailscale-exit-node-allow-lan-access",
|
||||
);
|
||||
expect(got).toContain("chown 'runner:runner' /var/lib/crabbox/tailscale-* || true");
|
||||
expect(got).toContain("test -s /var/lib/crabbox/tailscale-ipv4");
|
||||
expect(got).toContain("grep -Eq '^100\\.' /var/lib/crabbox/tailscale-ipv4");
|
||||
|
||||
@ -176,11 +176,15 @@ describe("lease config", () => {
|
||||
tailscale: true,
|
||||
tailscaleTags: ["tag:Crabbox", "tag:ci", "invalid"],
|
||||
tailscaleHostname: "crabbox-blue-lobster",
|
||||
tailscaleExitNode: "mac-studio.tailnet.ts.net",
|
||||
tailscaleExitNodeAllowLanAccess: true,
|
||||
});
|
||||
expect(config.tailscale).toBe(true);
|
||||
expect(config.tailscaleTags).toEqual(["tag:crabbox", "tag:ci"]);
|
||||
expect(config.tailscaleHostname).toBe("crabbox-blue-lobster");
|
||||
expect(config.tailscaleAuthKey).toBe("");
|
||||
expect(config.tailscaleExitNode).toBe("mac-studio.tailnet.ts.net");
|
||||
expect(config.tailscaleExitNodeAllowLanAccess).toBe(true);
|
||||
});
|
||||
|
||||
it("uses AWS defaults when requested", () => {
|
||||
|
||||
@ -109,6 +109,8 @@ describe("fleet lease identity and idle", () => {
|
||||
tailscaleAuthKey?: string;
|
||||
tailscaleHostname?: string;
|
||||
tailscaleTags?: string[];
|
||||
tailscaleExitNode?: string;
|
||||
tailscaleExitNodeAllowLanAccess?: boolean;
|
||||
}
|
||||
| undefined;
|
||||
vi.stubGlobal(
|
||||
@ -150,6 +152,8 @@ describe("fleet lease identity and idle", () => {
|
||||
tailscale: true,
|
||||
tailscaleTags: ["tag:ci"],
|
||||
tailscaleHostname: "crabbox-{slug}",
|
||||
tailscaleExitNode: "mac-studio.tailnet.ts.net",
|
||||
tailscaleExitNodeAllowLanAccess: true,
|
||||
sshPublicKey: "ssh-ed25519 test",
|
||||
},
|
||||
}),
|
||||
@ -161,6 +165,8 @@ describe("fleet lease identity and idle", () => {
|
||||
hostname: "crabbox-blue-lobster",
|
||||
tags: ["tag:ci"],
|
||||
state: "requested",
|
||||
exitNode: "mac-studio.tailnet.ts.net",
|
||||
exitNodeAllowLanAccess: true,
|
||||
});
|
||||
expect(JSON.stringify(lease)).not.toContain("tskey-oneoff");
|
||||
expect(providerConfig).toMatchObject({
|
||||
@ -168,6 +174,8 @@ describe("fleet lease identity and idle", () => {
|
||||
tailscaleAuthKey: "tskey-oneoff",
|
||||
tailscaleHostname: "crabbox-blue-lobster",
|
||||
tailscaleTags: ["tag:ci"],
|
||||
tailscaleExitNode: "mac-studio.tailnet.ts.net",
|
||||
tailscaleExitNodeAllowLanAccess: true,
|
||||
});
|
||||
|
||||
const update = await fleet.fetch(
|
||||
@ -181,6 +189,8 @@ describe("fleet lease identity and idle", () => {
|
||||
hostname: "crabbox-blue-lobster",
|
||||
fqdn: "crabbox-blue-lobster.example.ts.net",
|
||||
ipv4: "100.64.0.10",
|
||||
exitNode: "mac-studio.tailnet.ts.net",
|
||||
exitNodeAllowLanAccess: true,
|
||||
state: "ready",
|
||||
},
|
||||
}),
|
||||
@ -188,6 +198,7 @@ describe("fleet lease identity and idle", () => {
|
||||
expect(update.status).toBe(200);
|
||||
const updated = (await update.json()) as { lease: LeaseRecord };
|
||||
expect(updated.lease.tailscale?.ipv4).toBe("100.64.0.10");
|
||||
expect(updated.lease.tailscale?.exitNode).toBe("mac-studio.tailnet.ts.net");
|
||||
expect(updated.lease.tailscale?.state).toBe("ready");
|
||||
});
|
||||
|
||||
@ -2122,6 +2133,8 @@ function fakeProvider(
|
||||
tailscaleAuthKey?: string;
|
||||
tailscaleHostname?: string;
|
||||
tailscaleTags?: string[];
|
||||
tailscaleExitNode?: string;
|
||||
tailscaleExitNodeAllowLanAccess?: boolean;
|
||||
}) => void,
|
||||
result: {
|
||||
provider?: "hetzner" | "aws";
|
||||
|
||||
@ -15,6 +15,8 @@ describe("provider labels", () => {
|
||||
tailscaleTags: ["tag:crabbox"],
|
||||
tailscaleHostname: "",
|
||||
tailscaleAuthKey: "",
|
||||
tailscaleExitNode: "",
|
||||
tailscaleExitNodeAllowLanAccess: false,
|
||||
profile: "default",
|
||||
class: "beast",
|
||||
serverType: "c7a.48xlarge",
|
||||
@ -70,6 +72,8 @@ describe("provider labels", () => {
|
||||
tailscaleTags: ["tag:crabbox"],
|
||||
tailscaleHostname: "crabbox-blue-lobster",
|
||||
tailscaleAuthKey: "tskey-secret",
|
||||
tailscaleExitNode: "mac-studio.tailnet.ts.net",
|
||||
tailscaleExitNodeAllowLanAccess: true,
|
||||
profile: "default",
|
||||
class: "beast",
|
||||
serverType: "c7a.48xlarge",
|
||||
@ -109,6 +113,8 @@ describe("provider labels", () => {
|
||||
expect(labels.tailscale).toBe("true");
|
||||
expect(labels.tailscale_hostname).toBe("crabbox-blue-lobster");
|
||||
expect(labels.tailscale_tags).toBe("tag_crabbox");
|
||||
expect(labels.tailscale_exit_node).toBe("mac-studio.tailnet.ts.net");
|
||||
expect(labels.tailscale_exit_node_allow_lan_access).toBe("true");
|
||||
expect(Object.values(labels).join(" ")).not.toContain("tskey-secret");
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user