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
|
||||
}
|
||||
|
||||
// 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
|
||||
// platform equivalent) is not answering localapi connections.
|
||||
//
|
||||
|
@ -65,7 +65,7 @@ func (src *ServeConfig) Clone() *ServeConfig {
|
||||
dst := new(ServeConfig)
|
||||
*dst = *src
|
||||
if dst.TCP != nil {
|
||||
dst.TCP = map[int]*TCPPortHandler{}
|
||||
dst.TCP = map[uint16]*TCPPortHandler{}
|
||||
for k, v := range src.TCP {
|
||||
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.
|
||||
var _ServeConfigCloneNeedsRegeneration = ServeConfig(struct {
|
||||
TCP map[int]*TCPPortHandler
|
||||
TCP map[uint16]*TCPPortHandler
|
||||
Web map[HostPort]*WebServerConfig
|
||||
AllowIngress map[HostPort]bool
|
||||
}{})
|
||||
|
@ -164,7 +164,7 @@ func (v *ServeConfigView) UnmarshalJSON(b []byte) error {
|
||||
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 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.
|
||||
var _ServeConfigViewNeedsRegeneration = ServeConfig(struct {
|
||||
TCP map[int]*TCPPortHandler
|
||||
TCP map[uint16]*TCPPortHandler
|
||||
Web map[HostPort]*WebServerConfig
|
||||
AllowIngress map[HostPort]bool
|
||||
}{})
|
||||
|
@ -11,7 +11,6 @@
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@ -3522,8 +3521,8 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked() {
|
||||
}
|
||||
}
|
||||
if b.serveConfig.Valid() {
|
||||
b.serveConfig.TCP().Range(func(port int, _ ipn.TCPPortHandlerView) bool {
|
||||
if port > 0 && port <= math.MaxUint16 {
|
||||
b.serveConfig.TCP().Range(func(port uint16, _ ipn.TCPPortHandlerView) bool {
|
||||
if port > 0 {
|
||||
handlePorts = append(handlePorts, uint16(port))
|
||||
}
|
||||
return true
|
||||
@ -3534,6 +3533,46 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked() {
|
||||
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
|
||||
// empty string if none.
|
||||
func (b *LocalBackend) operatorUserName() string {
|
||||
|
@ -82,7 +82,7 @@ func (b *LocalBackend) HandleInterceptedTCPConn(dport uint16, srcAddr netip.Addr
|
||||
return
|
||||
}
|
||||
|
||||
tcph, ok := sc.TCP().GetOk(int(dport))
|
||||
tcph, ok := sc.TCP().GetOk(dport)
|
||||
if !ok {
|
||||
b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr)
|
||||
sendRST()
|
||||
|
@ -72,6 +72,7 @@
|
||||
"ping": (*Handler).servePing,
|
||||
"prefs": (*Handler).servePrefs,
|
||||
"pprof": (*Handler).servePprof,
|
||||
"serve-config": (*Handler).serveServeConfig,
|
||||
"set-dns": (*Handler).serveSetDNS,
|
||||
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
|
||||
"status": (*Handler).serveStatus,
|
||||
@ -455,6 +456,41 @@ func (h *Handler) servePprof(w http.ResponseWriter, r *http.Request) {
|
||||
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) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "IP forwarding check access denied", http.StatusForbidden)
|
||||
|
@ -76,7 +76,7 @@ func ServeConfigKey(profileID string) StateKey {
|
||||
type ServeConfig struct {
|
||||
// TCP are the list of TCP port numbers that tailscaled should handle for
|
||||
// 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
|
||||
// keyed by mount point ("/", "/foo", etc)
|
||||
@ -84,7 +84,7 @@ type ServeConfig struct {
|
||||
|
||||
// AllowIngress is the set of SNI:port values for which ingress
|
||||
// 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.
|
||||
|
Loading…
Reference in New Issue
Block a user