tailcfg,ipn,appc: add c2n endpoint for appc domain routes

This change introduces a c2n endpoint that returns a map of domains to a
slice of resolved IP addresses for the domain.

Fixes tailscale/corp#15657

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
This commit is contained in:
Charlotte Brandhorst-Satzkorn 2023-11-07 13:34:52 -08:00 committed by Charlotte Brandhorst-Satzkorn
parent 63062abadc
commit f937cb6794
3 changed files with 54 additions and 0 deletions

View File

@ -81,6 +81,20 @@ func (e *AppConnector) Domains() views.Slice[string] {
return views.SliceOf(xmaps.Keys(e.domains)) return views.SliceOf(xmaps.Keys(e.domains))
} }
// DomainRoutes returns a map of domains to resolved IP
// addresses.
func (e *AppConnector) DomainRoutes() map[string][]netip.Addr {
e.mu.Lock()
defer e.mu.Unlock()
drCopy := make(map[string][]netip.Addr)
for k, v := range e.domains {
copy(drCopy[k], v)
}
return drCopy
}
// ObserveDNSResponse is a callback invoked by the DNS resolver when a DNS // ObserveDNSResponse is a callback invoked by the DNS resolver when a DNS
// response is being returned over the PeerAPI. The response is parsed and // response is being returned over the PeerAPI. The response is parsed and
// matched against the configured domains, if matched the routeAdvertiser is // matched against the configured domains, if matched the routeAdvertiser is

View File

@ -121,6 +121,15 @@ func (b *LocalBackend) handleC2N(w http.ResponseWriter, r *http.Request) {
return return
} }
writeJSON(w, res) writeJSON(w, res)
case "/appconnector/routes":
switch r.Method {
case httpm.GET:
b.handleC2NAppConnectorDomainRoutesGet(w, r)
return
default:
http.Error(w, "bad method", http.StatusMethodNotAllowed)
return
}
case "/sockstats": case "/sockstats":
if r.Method != "POST" { if r.Method != "POST" {
http.Error(w, "bad method", http.StatusMethodNotAllowed) http.Error(w, "bad method", http.StatusMethodNotAllowed)
@ -139,6 +148,26 @@ func (b *LocalBackend) handleC2N(w http.ResponseWriter, r *http.Request) {
} }
} }
// handleC2NAppConnectorDomainRoutesGet handles returning the domains
// that the app connector is responsible for, as well as the resolved
// IP addresses for each domain. If the node is not configured as
// an app connector, an empty map is returned.
func (b *LocalBackend) handleC2NAppConnectorDomainRoutesGet(w http.ResponseWriter, r *http.Request) {
b.logf("c2n: GET /appconnector/routes received")
var res tailcfg.C2NAppConnectorDomainRoutesResponse
if b.appConnector == nil {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
return
}
res.Domains = b.appConnector.DomainRoutes()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}
func (b *LocalBackend) handleC2NUpdateGet(w http.ResponseWriter, r *http.Request) { func (b *LocalBackend) handleC2NUpdateGet(w http.ResponseWriter, r *http.Request) {
b.logf("c2n: GET /update received") b.logf("c2n: GET /update received")

View File

@ -5,6 +5,8 @@
package tailcfg package tailcfg
import "net/netip"
// C2NSSHUsernamesRequest is the request for the /ssh/usernames. // C2NSSHUsernamesRequest is the request for the /ssh/usernames.
// A GET request without a request body is equivalent to the zero value of this type. // A GET request without a request body is equivalent to the zero value of this type.
// Otherwise, a POST request with a JSON-encoded request body is expected. // Otherwise, a POST request with a JSON-encoded request body is expected.
@ -64,3 +66,12 @@ type C2NPostureIdentityResponse struct {
// device posture collection. // device posture collection.
PostureDisabled bool `json:",omitempty"` PostureDisabled bool `json:",omitempty"`
} }
// C2NAppConnectorDomainRoutesResponse contains a map of domains to
// slice of addresses, indicating what IP addresses have been resolved
// for each domain.
type C2NAppConnectorDomainRoutesResponse struct {
// Domains is a map of lower case domain names with no trailing dot,
// to a list of resolved IP addresses.
Domains map[string][]netip.Addr
}