summaryrefslogtreecommitdiffhomepage
path: root/ipn/conf.go
blob: de127a28a0d7bd1bce096e602e15ebaaebfec0ff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

package ipn

import (
	"errors"
	"fmt"
	"net/netip"

	"tailscale.com/tailcfg"
	"tailscale.com/types/opt"
	"tailscale.com/types/preftype"
)

// ConfigVAlpha is the config file format for the "alpha0" version.
type ConfigVAlpha struct {
	Version string   // "alpha0" for now
	Locked  opt.Bool `json:",omitempty"` // whether the config is locked from being changed by 'tailscale set'; it defaults to true

	ServerURL *string  `json:",omitempty"` // defaults to https://controlplane.tailscale.com
	AuthKey   *string  `json:",omitempty"` // as needed if NeedsLogin. either key or path to a file (if prefixed with "file:")
	Enabled   opt.Bool `json:",omitempty"` // wantRunning; empty string defaults to true

	OperatorUser *string `json:",omitempty"` // local user name who is allowed to operate tailscaled without being root or using sudo
	Hostname     *string `json:",omitempty"`

	AcceptDNS    opt.Bool `json:"acceptDNS,omitempty"`    // --accept-dns
	AcceptRoutes opt.Bool `json:"acceptRoutes,omitempty"` // --accept-routes defaults to true

	ExitNode                   *string  `json:"exitNode,omitempty"` // IP, StableID, or MagicDNS base name
	AllowLANWhileUsingExitNode opt.Bool `json:"allowLANWhileUsingExitNode,omitempty"`

	AdvertiseRoutes []netip.Prefix `json:",omitempty"`
	DisableSNAT     opt.Bool       `json:",omitempty"`

	AdvertiseServices []string `json:",omitempty"`

	AppConnector *AppConnectorPrefs `json:",omitempty"` // advertise app connector; defaults to false (if nil or explicitly set to false)

	NetfilterMode       *string  `json:",omitempty"` // "on", "off", "nodivert"
	NoStatefulFiltering opt.Bool `json:",omitempty"`

	PostureChecking opt.Bool         `json:",omitempty"`
	RunSSHServer    opt.Bool         `json:",omitempty"` // Tailscale SSH
	RunWebClient    opt.Bool         `json:",omitempty"`
	ShieldsUp       opt.Bool         `json:",omitempty"`
	AutoUpdate      *AutoUpdatePrefs `json:",omitempty"`
	ServeConfigTemp *ServeConfig     `json:",omitempty"` // TODO(bradfitz,maisem): make separate stable type for this

	// StaticEndpoints are additional, user-defined endpoints that this node
	// should advertise amongst its wireguard endpoints.
	StaticEndpoints []netip.AddrPort `json:",omitempty"`

	// TODO(bradfitz,maisem): future something like:
	// Profile map[string]*Config // keyed by alice@gmail.com, corp.com (TailnetSID)
}

func (c *ConfigVAlpha) ToPrefs() (MaskedPrefs, error) {
	var mp MaskedPrefs
	if c == nil {
		return mp, nil
	}

	mp.WantRunning = !c.Enabled.EqualBool(false)
	mp.WantRunningSet = mp.WantRunning || c.Enabled != ""
	if c.ServerURL != nil {
		mp.ControlURL = *c.ServerURL
		mp.ControlURLSet = true
	}
	if c.AuthKey != nil && *c.AuthKey != "" {
		mp.LoggedOut = false
		mp.LoggedOutSet = true
	}
	if c.OperatorUser != nil {
		mp.OperatorUser = *c.OperatorUser
		mp.OperatorUserSet = true
	}
	if c.Hostname != nil {
		mp.Hostname = *c.Hostname
		mp.HostnameSet = true
	}
	if c.AcceptDNS != "" {
		mp.CorpDNS = c.AcceptDNS.EqualBool(true)
		mp.CorpDNSSet = true
	}
	if c.AcceptRoutes != "" {
		mp.RouteAll = c.AcceptRoutes.EqualBool(true)
		mp.RouteAllSet = true
	}
	if c.ExitNode != nil {
		ip, err := netip.ParseAddr(*c.ExitNode)
		if err == nil {
			mp.ExitNodeIP = ip
			mp.ExitNodeIPSet = true
		} else {
			mp.ExitNodeID = tailcfg.StableNodeID(*c.ExitNode)
			mp.ExitNodeIDSet = true
		}
	}
	if c.AllowLANWhileUsingExitNode != "" {
		mp.ExitNodeAllowLANAccess = c.AllowLANWhileUsingExitNode.EqualBool(true)
		mp.ExitNodeAllowLANAccessSet = true
	}
	if c.AdvertiseRoutes != nil {
		var routeErrs []error
		for _, route := range c.AdvertiseRoutes {
			if route != route.Masked() {
				routeErrs = append(routeErrs, fmt.Errorf("route %s has non-address bits set; expected %s", route, route.Masked()))
			}
		}
		if err := errors.Join(routeErrs...); err != nil {
			return mp, err
		}
		mp.AdvertiseRoutes = c.AdvertiseRoutes
		mp.AdvertiseRoutesSet = true
	}
	if c.DisableSNAT != "" {
		mp.NoSNAT = c.DisableSNAT.EqualBool(true)
		mp.NoSNATSet = true
	}
	if c.NoStatefulFiltering != "" {
		mp.NoStatefulFiltering = c.NoStatefulFiltering
		mp.NoStatefulFilteringSet = true
	}

	if c.NetfilterMode != nil {
		m, err := preftype.ParseNetfilterMode(*c.NetfilterMode)
		if err != nil {
			return mp, err
		}
		mp.NetfilterMode = m
		mp.NetfilterModeSet = true
	}
	if c.PostureChecking != "" {
		mp.PostureChecking = c.PostureChecking.EqualBool(true)
		mp.PostureCheckingSet = true
	}
	if c.RunSSHServer != "" {
		mp.RunSSH = c.RunSSHServer.EqualBool(true)
		mp.RunSSHSet = true
	}
	if c.RunWebClient != "" {
		mp.RunWebClient = c.RunWebClient.EqualBool(true)
		mp.RunWebClientSet = true
	}
	if c.ShieldsUp != "" {
		mp.ShieldsUp = c.ShieldsUp.EqualBool(true)
		mp.ShieldsUpSet = true
	}
	if c.AutoUpdate != nil {
		mp.AutoUpdate = *c.AutoUpdate
		mp.AutoUpdateSet = AutoUpdatePrefsMask{ApplySet: true, CheckSet: true}
	}
	if c.AppConnector != nil {
		mp.AppConnector = *c.AppConnector
		mp.AppConnectorSet = true
	}
	// Configfile should be the source of truth for whether this node
	// advertises any services.  We need to ensure that each reload updates
	// currently advertised services as else the transition from 'some
	// services are advertised' to 'advertised services are empty/unset in
	// conffile' would have no effect (especially given that an empty
	// service slice would be omitted from the JSON config).
	mp.AdvertiseServicesSet = true
	if c.AdvertiseServices != nil {
		mp.AdvertiseServices = c.AdvertiseServices
	}
	return mp, nil
}