mirror of
https://github.com/juanfont/headscale.git
synced 2026-05-23 10:42:30 +09:00
config, types: move randomize_client_port from server config to policy file
Tailscale models the randomize-client-port toggle as a top-level field on the ACL policy. Headscale now matches that shape: the server-config randomize_client_port key is removed, the toggle lives in the policy file as randomizeClientPort, and per-node opt-in via nodeAttrs is also supported. Operators upgrading from a config-set randomize_client_port hit depr.fatalWithHint at startup, which prints the deprecation message and points at the new policy field rather than silently dropping the toggle. The default carries over (false) so operators who never set it are unaffected. config-example.yaml ships a REMOVED stanza showing the migration. types/node.go drops the cfg.RandomizeClientPort read from TailNode -- the cap is now policy-driven through compileNodeAttrs and the tail_test.go expectations follow.
This commit is contained in:
@@ -73,5 +73,4 @@ func TestConfigLoading(t *testing.T) {
|
||||
assert.Equal(t, "HTTP-01", viper.GetString("tls_letsencrypt_challenge_type"))
|
||||
assert.Equal(t, fs.FileMode(0o770), util.GetFileMode("unix_socket_permission"))
|
||||
assert.False(t, viper.GetBool("logtail.enabled"))
|
||||
assert.False(t, viper.GetBool("randomize_client_port"))
|
||||
}
|
||||
|
||||
@@ -452,18 +452,15 @@ logtail:
|
||||
# disabled by default. Enabling this will make your clients send logs to Tailscale Inc.
|
||||
enabled: false
|
||||
|
||||
# Enabling this option makes devices prefer a random port for WireGuard traffic over the
|
||||
# default static port 41641. This option is intended as a workaround for some buggy
|
||||
# firewall devices. See https://tailscale.com/docs/integrations/firewalls for more information.
|
||||
randomize_client_port: false
|
||||
|
||||
# Taildrop configuration
|
||||
# Taildrop is the file sharing feature of Tailscale, allowing nodes to send files to each other.
|
||||
# Taildrop is the file sharing feature of Tailscale, allowing nodes to
|
||||
# send files to each other.
|
||||
# https://tailscale.com/docs/features/taildrop
|
||||
taildrop:
|
||||
# Enable or disable Taildrop for all nodes.
|
||||
# When enabled, nodes can send files to other nodes owned by the same user.
|
||||
# Tagged devices and cross-user transfers are not permitted by Tailscale clients.
|
||||
# Enable or disable Taildrop tailnet-wide. When disabled, headscale
|
||||
# withholds `https://tailscale.com/cap/file-sharing` from every node's
|
||||
# CapMap, matching the admin-console "Send Files" toggle on the
|
||||
# Tailscale-hosted control plane.
|
||||
enabled: true
|
||||
# Advanced performance tuning parameters.
|
||||
# The defaults are carefully chosen and should rarely need adjustment.
|
||||
|
||||
@@ -74,9 +74,9 @@ func TestTailNode(t *testing.T) {
|
||||
MachineAuthorized: true,
|
||||
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrsTaildriveShare: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrsTaildriveAccess: []tailcfg.RawMessage{},
|
||||
},
|
||||
@@ -165,9 +165,9 @@ func TestTailNode(t *testing.T) {
|
||||
MachineAuthorized: true,
|
||||
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrsTaildriveShare: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrsTaildriveAccess: []tailcfg.RawMessage{},
|
||||
},
|
||||
@@ -192,9 +192,9 @@ func TestTailNode(t *testing.T) {
|
||||
MachineAuthorized: true,
|
||||
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrsTaildriveShare: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrsTaildriveAccess: []tailcfg.RawMessage{},
|
||||
},
|
||||
@@ -209,10 +209,9 @@ func TestTailNode(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := &types.Config{
|
||||
BaseDomain: tt.baseDomain,
|
||||
TailcfgDNSConfig: tt.dnsConfig,
|
||||
RandomizeClientPort: false,
|
||||
Taildrop: types.TaildropConfig{Enabled: true},
|
||||
BaseDomain: tt.baseDomain,
|
||||
TailcfgDNSConfig: tt.dnsConfig,
|
||||
Taildrop: types.TaildropConfig{Enabled: true},
|
||||
}
|
||||
|
||||
// Stub primary-route lookup: tt.node owns its SubnetRoutes,
|
||||
|
||||
@@ -130,9 +130,8 @@ type Config struct {
|
||||
|
||||
OIDC OIDCConfig
|
||||
|
||||
LogTail LogTailConfig
|
||||
RandomizeClientPort bool
|
||||
Taildrop TaildropConfig
|
||||
LogTail LogTailConfig
|
||||
Taildrop TaildropConfig
|
||||
|
||||
CLI CLIConfig
|
||||
|
||||
@@ -428,7 +427,6 @@ func LoadConfig(path string, isFile bool) error {
|
||||
viper.SetDefault("oidc.email_verified_required", true)
|
||||
|
||||
viper.SetDefault("logtail.enabled", false)
|
||||
viper.SetDefault("randomize_client_port", false)
|
||||
viper.SetDefault("taildrop.enabled", true)
|
||||
|
||||
viper.SetDefault("node.expiry", "0")
|
||||
@@ -530,6 +528,16 @@ func validateServerConfig() error {
|
||||
depr.fatal("oidc.strip_email_domain")
|
||||
depr.fatal("oidc.map_legacy_users")
|
||||
|
||||
// Removed since v0.29.0: `randomize_client_port` moved to the ACL
|
||||
// policy as a top-level `randomizeClientPort` field, matching the
|
||||
// Tailscale-hosted control plane schema. Per-node `nodeAttrs`
|
||||
// entries granting `https://tailscale.com/cap/randomize-client-port`
|
||||
// also work.
|
||||
depr.fatalWithHint("randomize_client_port",
|
||||
`Set "randomizeClientPort": true at the top level of your policy file `+
|
||||
`(see policy.path / policy.mode), or grant the cap per-node via a `+
|
||||
`"nodeAttrs" entry. See CHANGELOG.md (BREAKING / Configuration).`)
|
||||
|
||||
// Deprecated: ephemeral_node_inactivity_timeout -> node.ephemeral.inactivity_timeout
|
||||
depr.warnNoAlias("node.ephemeral.inactivity_timeout", "ephemeral_node_inactivity_timeout")
|
||||
|
||||
@@ -1120,7 +1128,6 @@ func LoadServerConfig() (*Config, error) {
|
||||
|
||||
derpConfig := derpConfig()
|
||||
logTailConfig := logtailConfig()
|
||||
randomizeClientPort := viper.GetBool("randomize_client_port")
|
||||
|
||||
oidcClientSecret := viper.GetString("oidc.client_secret")
|
||||
|
||||
@@ -1219,8 +1226,7 @@ func LoadServerConfig() (*Config, error) {
|
||||
},
|
||||
},
|
||||
|
||||
LogTail: logTailConfig,
|
||||
RandomizeClientPort: randomizeClientPort,
|
||||
LogTail: logTailConfig,
|
||||
Taildrop: TaildropConfig{
|
||||
Enabled: viper.GetBool("taildrop.enabled"),
|
||||
},
|
||||
@@ -1330,6 +1336,22 @@ func (d *deprecator) fatal(oldKey string) {
|
||||
}
|
||||
}
|
||||
|
||||
// fatalWithHint behaves like fatal but appends a remediation pointer to
|
||||
// the message so operators see exactly what to do without leaving the
|
||||
// terminal. Use it when the removed key has a clean replacement on the
|
||||
// policy side.
|
||||
func (d *deprecator) fatalWithHint(oldKey, hint string) {
|
||||
if viper.IsSet(oldKey) {
|
||||
d.fatals.Add(
|
||||
fmt.Sprintf(
|
||||
"The %q configuration key has been removed. %s",
|
||||
oldKey,
|
||||
hint,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// fatalIfNewKeyIsNotUsed deprecates and adds an entry to the fatal list of options if the oldKey is set and the new key is _not_ set.
|
||||
// If the new key is set, a warning is emitted instead.
|
||||
func (d *deprecator) fatalIfNewKeyIsNotUsed(newKey, oldKey string) {
|
||||
|
||||
@@ -1181,24 +1181,29 @@ func (nv NodeView) TailNode(
|
||||
}
|
||||
}
|
||||
|
||||
// Baseline caps every node receives regardless of the ACL policy.
|
||||
// The set matches what Tailscale SaaS emits with default tailnet
|
||||
// settings, anchored against captured netmaps in
|
||||
// hscontrol/policy/v2/testdata/nodeattrs_results — including the
|
||||
// nodeattrs-tailnet-* probe captures which exercise individual
|
||||
// tailnet-settings overlays (devices_auto_updates_on, magic_dns)
|
||||
// against the SaaS API and confirm the CapMap shape stays stable.
|
||||
// Where headscale exposes an equivalent operator knob it gates
|
||||
// the cap accordingly: cfg.Taildrop.Enabled gates
|
||||
// CapabilityFileSharing, matching the SaaS admin-console
|
||||
// "Send Files" toggle. Policy nodeAttrs add to this baseline in
|
||||
// the mapper; they cannot remove from it.
|
||||
capMap := tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
}
|
||||
if cfg.RandomizeClientPort {
|
||||
capMap[tailcfg.NodeAttrRandomizeClientPort] = []tailcfg.RawMessage{}
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrsTaildriveShare: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrsTaildriveAccess: []tailcfg.RawMessage{},
|
||||
}
|
||||
|
||||
if cfg.Taildrop.Enabled {
|
||||
capMap[tailcfg.CapabilityFileSharing] = []tailcfg.RawMessage{}
|
||||
}
|
||||
|
||||
// Enable Taildrive sharing and access on all nodes. The actual
|
||||
// access control is enforced by cap/drive grants in FilterRules;
|
||||
// without a matching grant these attributes alone do nothing.
|
||||
capMap[tailcfg.NodeAttrsTaildriveShare] = []tailcfg.RawMessage{}
|
||||
capMap[tailcfg.NodeAttrsTaildriveAccess] = []tailcfg.RawMessage{}
|
||||
|
||||
tNode := tailcfg.Node{
|
||||
//nolint:gosec // G115: NodeID values are within int64 range
|
||||
ID: tailcfg.NodeID(nv.ID()),
|
||||
|
||||
Reference in New Issue
Block a user