mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
ipn/localapi: introduce get/set config for serve (#6243)
Updates tailscale/corp#7515 Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
This commit is contained in:
parent
7b5866ac0a
commit
e3a66e4d2f
@ -859,6 +859,33 @@ type signRequest struct {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetServeConfig sets or replaces the serving settings.
|
||||||
|
// If config is nil, settings are cleared and serving is disabled.
|
||||||
|
func (lc *LocalClient) SetServeConfig(ctx context.Context, config *ipn.ServeConfig) error {
|
||||||
|
b, err := json.Marshal(&config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encoding config: %w", err)
|
||||||
|
}
|
||||||
|
_, err = lc.send(ctx, "POST", "/localapi/v0/serve-config", 200, bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sending serve config: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServeConfig return the current serve config.
|
||||||
|
func (lc *LocalClient) GetServeConfig(ctx context.Context) (*ipn.ServeConfig, error) {
|
||||||
|
body, err := lc.send(ctx, "GET", "/localapi/v0/serve-config", 200, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting serve config: %w", err)
|
||||||
|
}
|
||||||
|
sc := new(ipn.ServeConfig)
|
||||||
|
if err := json.Unmarshal(body, sc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sc, nil
|
||||||
|
}
|
||||||
|
|
||||||
// tailscaledConnectHint gives a little thing about why tailscaled (or
|
// tailscaledConnectHint gives a little thing about why tailscaled (or
|
||||||
// platform equivalent) is not answering localapi connections.
|
// platform equivalent) is not answering localapi connections.
|
||||||
//
|
//
|
||||||
|
@ -65,7 +65,7 @@ func (src *ServeConfig) Clone() *ServeConfig {
|
|||||||
dst := new(ServeConfig)
|
dst := new(ServeConfig)
|
||||||
*dst = *src
|
*dst = *src
|
||||||
if dst.TCP != nil {
|
if dst.TCP != nil {
|
||||||
dst.TCP = map[int]*TCPPortHandler{}
|
dst.TCP = map[uint16]*TCPPortHandler{}
|
||||||
for k, v := range src.TCP {
|
for k, v := range src.TCP {
|
||||||
dst.TCP[k] = v.Clone()
|
dst.TCP[k] = v.Clone()
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ func (src *ServeConfig) Clone() *ServeConfig {
|
|||||||
|
|
||||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
var _ServeConfigCloneNeedsRegeneration = ServeConfig(struct {
|
var _ServeConfigCloneNeedsRegeneration = ServeConfig(struct {
|
||||||
TCP map[int]*TCPPortHandler
|
TCP map[uint16]*TCPPortHandler
|
||||||
Web map[HostPort]*WebServerConfig
|
Web map[HostPort]*WebServerConfig
|
||||||
AllowIngress map[HostPort]bool
|
AllowIngress map[HostPort]bool
|
||||||
}{})
|
}{})
|
||||||
|
@ -164,7 +164,7 @@ func (v *ServeConfigView) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v ServeConfigView) TCP() views.MapFn[int, *TCPPortHandler, TCPPortHandlerView] {
|
func (v ServeConfigView) TCP() views.MapFn[uint16, *TCPPortHandler, TCPPortHandlerView] {
|
||||||
return views.MapFnOf(v.ж.TCP, func(t *TCPPortHandler) TCPPortHandlerView {
|
return views.MapFnOf(v.ж.TCP, func(t *TCPPortHandler) TCPPortHandlerView {
|
||||||
return t.View()
|
return t.View()
|
||||||
})
|
})
|
||||||
@ -182,7 +182,7 @@ func (v ServeConfigView) AllowIngress() views.Map[HostPort, bool] {
|
|||||||
|
|
||||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
var _ServeConfigViewNeedsRegeneration = ServeConfig(struct {
|
var _ServeConfigViewNeedsRegeneration = ServeConfig(struct {
|
||||||
TCP map[int]*TCPPortHandler
|
TCP map[uint16]*TCPPortHandler
|
||||||
Web map[HostPort]*WebServerConfig
|
Web map[HostPort]*WebServerConfig
|
||||||
AllowIngress map[HostPort]bool
|
AllowIngress map[HostPort]bool
|
||||||
}{})
|
}{})
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@ -3522,8 +3521,8 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if b.serveConfig.Valid() {
|
if b.serveConfig.Valid() {
|
||||||
b.serveConfig.TCP().Range(func(port int, _ ipn.TCPPortHandlerView) bool {
|
b.serveConfig.TCP().Range(func(port uint16, _ ipn.TCPPortHandlerView) bool {
|
||||||
if port > 0 && port <= math.MaxUint16 {
|
if port > 0 {
|
||||||
handlePorts = append(handlePorts, uint16(port))
|
handlePorts = append(handlePorts, uint16(port))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -3534,6 +3533,46 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked() {
|
|||||||
b.setTCPPortsIntercepted(handlePorts)
|
b.setTCPPortsIntercepted(handlePorts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetServeConfig establishes or replaces the current serve config.
|
||||||
|
func (b *LocalBackend) SetServeConfig(config *ipn.ServeConfig) error {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
nm := b.netMap
|
||||||
|
if nm == nil {
|
||||||
|
return errors.New("netMap is nil")
|
||||||
|
}
|
||||||
|
if nm.SelfNode == nil {
|
||||||
|
return errors.New("netMap SelfNode is nil")
|
||||||
|
}
|
||||||
|
profileID := fmt.Sprintf("node-%s", nm.SelfNode.StableID) // TODO(maisem,bradfitz): something else?
|
||||||
|
confKey := ipn.ServeConfigKey(profileID)
|
||||||
|
|
||||||
|
var bs []byte
|
||||||
|
if config != nil {
|
||||||
|
j, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encoding serve config: %w", err)
|
||||||
|
}
|
||||||
|
bs = j
|
||||||
|
}
|
||||||
|
if err := b.store.WriteState(confKey, bs); err != nil {
|
||||||
|
return fmt.Errorf("writing ServeConfig to StateStore: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeConfig provides a view of the current serve mappings.
|
||||||
|
// If serving is not configured, the returned view is not Valid.
|
||||||
|
func (b *LocalBackend) ServeConfig() ipn.ServeConfigView {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
return b.serveConfig
|
||||||
|
}
|
||||||
|
|
||||||
// operatorUserName returns the current pref's OperatorUser's name, or the
|
// operatorUserName returns the current pref's OperatorUser's name, or the
|
||||||
// empty string if none.
|
// empty string if none.
|
||||||
func (b *LocalBackend) operatorUserName() string {
|
func (b *LocalBackend) operatorUserName() string {
|
||||||
|
@ -82,7 +82,7 @@ func (b *LocalBackend) HandleInterceptedTCPConn(dport uint16, srcAddr netip.Addr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tcph, ok := sc.TCP().GetOk(int(dport))
|
tcph, ok := sc.TCP().GetOk(dport)
|
||||||
if !ok {
|
if !ok {
|
||||||
b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr)
|
b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr)
|
||||||
sendRST()
|
sendRST()
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
"ping": (*Handler).servePing,
|
"ping": (*Handler).servePing,
|
||||||
"prefs": (*Handler).servePrefs,
|
"prefs": (*Handler).servePrefs,
|
||||||
"pprof": (*Handler).servePprof,
|
"pprof": (*Handler).servePprof,
|
||||||
|
"serve-config": (*Handler).serveServeConfig,
|
||||||
"set-dns": (*Handler).serveSetDNS,
|
"set-dns": (*Handler).serveSetDNS,
|
||||||
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
|
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
|
||||||
"status": (*Handler).serveStatus,
|
"status": (*Handler).serveStatus,
|
||||||
@ -455,6 +456,41 @@ func (h *Handler) servePprof(w http.ResponseWriter, r *http.Request) {
|
|||||||
servePprofFunc(w, r)
|
servePprofFunc(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) serveServeConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !h.PermitWrite {
|
||||||
|
http.Error(w, "serve config denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
switch r.Method {
|
||||||
|
case "GET":
|
||||||
|
config := h.b.ServeConfig()
|
||||||
|
json.NewEncoder(w).Encode(config)
|
||||||
|
case "POST":
|
||||||
|
configIn := new(ipn.ServeConfig)
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(configIn); err != nil {
|
||||||
|
json.NewEncoder(w).Encode(struct {
|
||||||
|
Error error
|
||||||
|
}{
|
||||||
|
Error: fmt.Errorf("decoding config: %w", err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := h.b.SetServeConfig(configIn)
|
||||||
|
if err != nil {
|
||||||
|
json.NewEncoder(w).Encode(struct {
|
||||||
|
Error error
|
||||||
|
}{
|
||||||
|
Error: fmt.Errorf("updating config: %w", err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) serveCheckIPForwarding(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) serveCheckIPForwarding(w http.ResponseWriter, r *http.Request) {
|
||||||
if !h.PermitRead {
|
if !h.PermitRead {
|
||||||
http.Error(w, "IP forwarding check access denied", http.StatusForbidden)
|
http.Error(w, "IP forwarding check access denied", http.StatusForbidden)
|
||||||
|
@ -76,7 +76,7 @@ func ServeConfigKey(profileID string) StateKey {
|
|||||||
type ServeConfig struct {
|
type ServeConfig struct {
|
||||||
// TCP are the list of TCP port numbers that tailscaled should handle for
|
// TCP are the list of TCP port numbers that tailscaled should handle for
|
||||||
// the Tailscale IP addresses. (not subnet routers, etc)
|
// the Tailscale IP addresses. (not subnet routers, etc)
|
||||||
TCP map[int]*TCPPortHandler `json:",omitempty"`
|
TCP map[uint16]*TCPPortHandler `json:",omitempty"`
|
||||||
|
|
||||||
// Web maps from "$SNI_NAME:$PORT" to a set of HTTP handlers
|
// Web maps from "$SNI_NAME:$PORT" to a set of HTTP handlers
|
||||||
// keyed by mount point ("/", "/foo", etc)
|
// keyed by mount point ("/", "/foo", etc)
|
||||||
@ -84,7 +84,7 @@ type ServeConfig struct {
|
|||||||
|
|
||||||
// AllowIngress is the set of SNI:port values for which ingress
|
// AllowIngress is the set of SNI:port values for which ingress
|
||||||
// traffic is allowed, from trusted ingress peers.
|
// traffic is allowed, from trusted ingress peers.
|
||||||
AllowIngress map[HostPort]bool
|
AllowIngress map[HostPort]bool `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HostPort is an SNI name and port number, joined by a colon.
|
// HostPort is an SNI name and port number, joined by a colon.
|
||||||
|
Loading…
Reference in New Issue
Block a user