
For people who can't use LetsEncrypt because it's banned. Per https://github.com/tailscale/tailscale/issues/11776#issuecomment-2520955317 This does two things: 1) if you run derper with --certmode=manual and --hostname=$IP_ADDRESS we previously permitted, but now we also: * auto-generate the self-signed cert for you if it doesn't yet exist on disk * print out the derpmap configuration you need to use that self-signed cert 2) teaches derp/derphttp's derp dialer to verify the signature of self-signed TLS certs, if so declared in the existing DERPNode.CertName field, which previously existed for domain fronting, separating out the dial hostname from how certs are validates, so it's not overloaded much; that's what it was meant for. Fixes #11776 Change-Id: Ie72d12f209416bb7e8325fe0838cd2c66342c5cf Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
212 lines
7.5 KiB
Go
212 lines
7.5 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package tailcfg
|
|
|
|
import (
|
|
"net/netip"
|
|
"sort"
|
|
|
|
"tailscale.com/types/key"
|
|
)
|
|
|
|
// DERPMap describes the set of DERP packet relay servers that are available.
|
|
type DERPMap struct {
|
|
// HomeParams, if non-nil, is a change in home parameters.
|
|
//
|
|
// The rest of the DEPRMap fields, if zero, means unchanged.
|
|
HomeParams *DERPHomeParams `json:",omitempty"`
|
|
|
|
// Regions is the set of geographic regions running DERP node(s).
|
|
//
|
|
// It's keyed by the DERPRegion.RegionID.
|
|
//
|
|
// The numbers are not necessarily contiguous.
|
|
Regions map[int]*DERPRegion
|
|
|
|
// OmitDefaultRegions specifies to not use Tailscale's DERP servers, and only use those
|
|
// specified in this DERPMap. If there are none set outside of the defaults, this is a noop.
|
|
//
|
|
// This field is only meaningful if the Regions map is non-nil (indicating a change).
|
|
OmitDefaultRegions bool `json:"omitDefaultRegions,omitempty"`
|
|
}
|
|
|
|
// / RegionIDs returns the sorted region IDs.
|
|
func (m *DERPMap) RegionIDs() []int {
|
|
ret := make([]int, 0, len(m.Regions))
|
|
for rid := range m.Regions {
|
|
ret = append(ret, rid)
|
|
}
|
|
sort.Ints(ret)
|
|
return ret
|
|
}
|
|
|
|
// DERPHomeParams contains parameters from the server related to selecting a
|
|
// DERP home region (sometimes referred to as the "preferred DERP").
|
|
type DERPHomeParams struct {
|
|
// RegionScore scales latencies of DERP regions by a given scaling
|
|
// factor when determining which region to use as the home
|
|
// ("preferred") DERP. Scores in the range (0, 1) will cause this
|
|
// region to be proportionally more preferred, and scores in the range
|
|
// (1, ∞) will penalize a region.
|
|
//
|
|
// If a region is not present in this map, it is treated as having a
|
|
// score of 1.0.
|
|
//
|
|
// Scores should not be 0 or negative; such scores will be ignored.
|
|
//
|
|
// A nil map means no change from the previous value (if any); an empty
|
|
// non-nil map can be sent to reset all scores back to 1.0.
|
|
RegionScore map[int]float64 `json:",omitempty"`
|
|
}
|
|
|
|
// DERPRegion is a geographic region running DERP relay node(s).
|
|
//
|
|
// Client nodes discover which region they're closest to, advertise
|
|
// that "home" DERP region (previously called "home node", when there
|
|
// was only 1 node per region) and maintain a persistent connection
|
|
// that region as long as it's the closest. Client nodes will further
|
|
// connect to other regions as necessary to communicate with peers
|
|
// advertising other regions as their homes.
|
|
type DERPRegion struct {
|
|
// RegionID is a unique integer for a geographic region.
|
|
//
|
|
// It corresponds to the legacy derpN.tailscale.com hostnames
|
|
// used by older clients. (Older clients will continue to resolve
|
|
// derpN.tailscale.com when contacting peers, rather than use
|
|
// the server-provided DERPMap)
|
|
//
|
|
// RegionIDs must be non-zero, positive, and guaranteed to fit
|
|
// in a JavaScript number.
|
|
//
|
|
// RegionIDs in range 900-999 are reserved for end users to run their
|
|
// own DERP nodes.
|
|
RegionID int
|
|
|
|
// RegionCode is a short name for the region. It's usually a popular
|
|
// city or airport code in the region: "nyc", "sf", "sin",
|
|
// "fra", etc.
|
|
RegionCode string
|
|
|
|
// RegionName is a long English name for the region: "New York City",
|
|
// "San Francisco", "Singapore", "Frankfurt", etc.
|
|
RegionName string
|
|
|
|
// Latitude, Longitude are optional geographical coordinates of the DERP region's city, in degrees.
|
|
Latitude float64 `json:",omitempty"`
|
|
Longitude float64 `json:",omitempty"`
|
|
|
|
// Avoid is whether the client should avoid picking this as its home
|
|
// region. The region should only be used if a peer is there.
|
|
// Clients already using this region as their home should migrate
|
|
// away to a new region without Avoid set.
|
|
Avoid bool `json:",omitempty"`
|
|
|
|
// Nodes are the DERP nodes running in this region, in
|
|
// priority order for the current client. Client TLS
|
|
// connections should ideally only go to the first entry
|
|
// (falling back to the second if necessary). STUN packets
|
|
// should go to the first 1 or 2.
|
|
//
|
|
// If nodes within a region route packets amongst themselves,
|
|
// but not to other regions. That said, each user/domain
|
|
// should get a the same preferred node order, so if all nodes
|
|
// for a user/network pick the first one (as they should, when
|
|
// things are healthy), the inter-cluster routing is minimal
|
|
// to zero.
|
|
Nodes []*DERPNode
|
|
}
|
|
|
|
// DERPNode describes a DERP packet relay node running within a DERPRegion.
|
|
type DERPNode struct {
|
|
// Name is a unique node name (across all regions).
|
|
// It is not a host name.
|
|
// It's typically of the form "1b", "2a", "3b", etc. (region
|
|
// ID + suffix within that region)
|
|
Name string
|
|
|
|
// RegionID is the RegionID of the DERPRegion that this node
|
|
// is running in.
|
|
RegionID int
|
|
|
|
// HostName is the DERP node's hostname.
|
|
//
|
|
// It is required but need not be unique; multiple nodes may
|
|
// have the same HostName but vary in configuration otherwise.
|
|
HostName string
|
|
|
|
// CertName optionally specifies the expected TLS cert common
|
|
// name. If empty, HostName is used. If CertName is non-empty,
|
|
// HostName is only used for the TCP dial (if IPv4/IPv6 are
|
|
// not present) + TLS ClientHello.
|
|
//
|
|
// As a special case, if CertName starts with "sha256-raw:",
|
|
// then the rest of the string is a hex-encoded SHA256 of the
|
|
// cert to expect. This is used for self-signed certs.
|
|
// In this case, the HostName field will typically be an IP
|
|
// address literal.
|
|
CertName string `json:",omitempty"`
|
|
|
|
// IPv4 optionally forces an IPv4 address to use, instead of using DNS.
|
|
// If empty, A record(s) from DNS lookups of HostName are used.
|
|
// If the string is not an IPv4 address, IPv4 is not used; the
|
|
// conventional string to disable IPv4 (and not use DNS) is
|
|
// "none".
|
|
IPv4 string `json:",omitempty"`
|
|
|
|
// IPv6 optionally forces an IPv6 address to use, instead of using DNS.
|
|
// If empty, AAAA record(s) from DNS lookups of HostName are used.
|
|
// If the string is not an IPv6 address, IPv6 is not used; the
|
|
// conventional string to disable IPv6 (and not use DNS) is
|
|
// "none".
|
|
IPv6 string `json:",omitempty"`
|
|
|
|
// Port optionally specifies a STUN port to use.
|
|
// Zero means 3478.
|
|
// To disable STUN on this node, use -1.
|
|
STUNPort int `json:",omitempty"`
|
|
|
|
// STUNOnly marks a node as only a STUN server and not a DERP
|
|
// server.
|
|
STUNOnly bool `json:",omitempty"`
|
|
|
|
// DERPPort optionally provides an alternate TLS port number
|
|
// for the DERP HTTPS server.
|
|
//
|
|
// If zero, 443 is used.
|
|
DERPPort int `json:",omitempty"`
|
|
|
|
// InsecureForTests is used by unit tests to disable TLS verification.
|
|
// It should not be set by users.
|
|
InsecureForTests bool `json:",omitempty"`
|
|
|
|
// STUNTestIP is used in tests to override the STUN server's IP.
|
|
// If empty, it's assumed to be the same as the DERP server.
|
|
STUNTestIP string `json:",omitempty"`
|
|
|
|
// CanPort80 specifies whether this DERP node is accessible over HTTP
|
|
// on port 80 specifically. This is used for captive portal checks.
|
|
CanPort80 bool `json:",omitempty"`
|
|
}
|
|
|
|
func (n *DERPNode) IsTestNode() bool {
|
|
return n.STUNTestIP != "" || n.IPv4 == "127.0.0.1"
|
|
}
|
|
|
|
// DotInvalid is a fake DNS TLD used in tests for an invalid hostname.
|
|
const DotInvalid = ".invalid"
|
|
|
|
// DERPAdmitClientRequest is the JSON request body of a POST to derper's
|
|
// --verify-client-url admission controller URL.
|
|
type DERPAdmitClientRequest struct {
|
|
NodePublic key.NodePublic // key to query for admission
|
|
Source netip.Addr // derp client's IP address
|
|
}
|
|
|
|
// DERPAdmitClientResponse is the response to a DERPAdmitClientRequest.
|
|
type DERPAdmitClientResponse struct {
|
|
Allow bool // whether to permit client
|
|
|
|
// TODO(bradfitz,maisem): bandwidth limits, etc?
|
|
}
|