mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-05 12:16:44 +02:00
tailcfg,types/netmap: visible services in netmap
ipn/localapi: serve service list over localapi cmd/tailscale: new service list command Updates <FIXME(@adrianosela)>
This commit is contained in:
parent
0e8ae9d60c
commit
1f6a4389b6
@ -1422,3 +1422,13 @@ func (lc *Client) GetAppConnectorRouteInfo(ctx context.Context) (appctype.RouteI
|
||||
}
|
||||
return decodeJSON[appctype.RouteInfo](body)
|
||||
}
|
||||
|
||||
// GetServices returns the Services visible to this node,
|
||||
// including their names, IP addresses, and ports, keyed by service name.
|
||||
func (lc *Client) GetServices(ctx context.Context) (map[tailcfg.ServiceName]tailcfg.ServiceDetails, error) {
|
||||
body, err := lc.get200(ctx, "/localapi/v0/services")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeJSON[map[tailcfg.ServiceName]tailcfg.ServiceDetails](body)
|
||||
}
|
||||
|
||||
@ -269,6 +269,7 @@ change in the future.
|
||||
nilOrCall(maybeNetlockCmd),
|
||||
licensesCmd,
|
||||
exitNodeCmd(),
|
||||
servicesCmd(),
|
||||
nilOrCall(maybeUpdateCmd),
|
||||
whoisCmd,
|
||||
debugCmd(),
|
||||
|
||||
68
cmd/tailscale/cli/services.go
Normal file
68
cmd/tailscale/cli/services.go
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
)
|
||||
|
||||
func servicesCmd() *ffcli.Command {
|
||||
return &ffcli.Command{
|
||||
Name: "service",
|
||||
ShortUsage: "tailscale service <subcommand>",
|
||||
ShortHelp: "Manage and view VIP services on your tailnet",
|
||||
Subcommands: []*ffcli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
ShortUsage: "tailscale service list",
|
||||
ShortHelp: "List VIP services visible to this node",
|
||||
Exec: runServicesList,
|
||||
},
|
||||
},
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
return flag.ErrHelp
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runServicesList(ctx context.Context, args []string) error {
|
||||
if len(args) > 0 {
|
||||
return errors.New("unexpected non-flag arguments to 'tailscale service list'")
|
||||
}
|
||||
services, err := localClient.GetServices(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(services) == 0 {
|
||||
return errors.New("no services found")
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(Stdout, 10, 5, 5, ' ', 0)
|
||||
defer w.Flush()
|
||||
fmt.Fprintf(w, "\n %s\t%s\t%s\t", "SERVICE", "ADDRS", "PORTS")
|
||||
for _, svc := range services {
|
||||
addrs := make([]string, len(svc.Addrs))
|
||||
for i, a := range svc.Addrs {
|
||||
addrs[i] = a.String()
|
||||
}
|
||||
ports := make([]string, len(svc.Ports))
|
||||
for i, p := range svc.Ports {
|
||||
ports[i] = p.String()
|
||||
}
|
||||
fmt.Fprintf(w, "\n %s\t%s\t%s\t",
|
||||
svc.Name,
|
||||
strings.Join(addrs, ", "),
|
||||
strings.Join(ports, ", "),
|
||||
)
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
return nil
|
||||
}
|
||||
@ -82,6 +82,7 @@ var handler = map[string]LocalAPIHandler{
|
||||
"prefs": (*Handler).servePrefs,
|
||||
"reload-config": (*Handler).reloadConfig,
|
||||
"reset-auth": (*Handler).serveResetAuth,
|
||||
"services": (*Handler).serveServices,
|
||||
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
|
||||
"shutdown": (*Handler).serveShutdown,
|
||||
"start": (*Handler).serveStart,
|
||||
@ -1707,6 +1708,20 @@ func (h *Handler) serveShutdown(w http.ResponseWriter, r *http.Request) {
|
||||
eventbus.Publish[Shutdown](ec).Publish(Shutdown{})
|
||||
}
|
||||
|
||||
func (h *Handler) serveServices(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != httpm.GET {
|
||||
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
nm := h.b.NetMap()
|
||||
if nm == nil {
|
||||
http.Error(w, "no netmap", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(nm.Services())
|
||||
}
|
||||
|
||||
func (h *Handler) serveGetAppcRouteInfo(w http.ResponseWriter, r *http.Request) {
|
||||
if !buildfeatures.HasAppConnectors {
|
||||
http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
|
||||
|
||||
@ -2447,6 +2447,18 @@ type Oauth2Token struct {
|
||||
// These are also referred to as "Node Attributes" in the ACL policy file.
|
||||
type NodeCapability string
|
||||
|
||||
// NodeCapabilityPrefix is a prefix for [NodeCapMap] keys that share a common
|
||||
// namespace, where each entry represents a distinct named instance (e.g. one
|
||||
// per service). The full key is formed by concatenating the prefix with the
|
||||
// instance name.
|
||||
type NodeCapabilityPrefix string
|
||||
|
||||
// ToAttribute returns the full [NodeCapability] key for the given value under
|
||||
// this prefix, of the form prefix+value.
|
||||
func (p NodeCapabilityPrefix) ToAttribute(value string) NodeCapability {
|
||||
return NodeCapability(string(p) + value)
|
||||
}
|
||||
|
||||
const (
|
||||
CapabilityFileSharing NodeCapability = "https://tailscale.com/cap/file-sharing"
|
||||
CapabilityAdmin NodeCapability = "https://tailscale.com/cap/is-admin"
|
||||
@ -2780,6 +2792,14 @@ const (
|
||||
NodeAttrCacheNetworkMaps NodeCapability = "cache-network-maps"
|
||||
)
|
||||
|
||||
const (
|
||||
// NodeAttrPrefixServices is the prefix for per-service [NodeCapMap]
|
||||
// entries describing Services visible (accessible) to this node. The full
|
||||
// key for a service named "svc:foo" is NodeAttrPrefixServices+"foo".
|
||||
// Each value under such a key is of type [ServiceDetails].
|
||||
NodeAttrPrefixServices NodeCapabilityPrefix = "services/"
|
||||
)
|
||||
|
||||
// SetDNSRequest is a request to add a DNS record.
|
||||
//
|
||||
// This is used to let tailscaled clients complete their ACME DNS-01 challenges
|
||||
@ -3318,6 +3338,19 @@ const LBHeader = "Ts-Lb"
|
||||
// this client is hosting can be ignored.
|
||||
type ServiceIPMappings map[ServiceName][]netip.Addr
|
||||
|
||||
// ServiceDetails describes a Service visible to this node.
|
||||
// It is the value type stored under [NodeAttrPrefixServices]+serviceName keys in [NodeCapMap].
|
||||
type ServiceDetails struct {
|
||||
// Name is the name of the Service, of the form "svc:dns-label".
|
||||
Name ServiceName
|
||||
|
||||
// Addrs are the IP addresses (IPv4 and IPv6) assigned to this Service.
|
||||
Addrs []netip.Addr `json:",omitempty"`
|
||||
|
||||
// Ports are the protocol/port combinations the Service accepts.
|
||||
Ports []ProtoPortRange `json:",omitempty"`
|
||||
}
|
||||
|
||||
// ClientAuditAction represents an auditable action that a client can report to the
|
||||
// control plane. These actions must correspond to the supported actions
|
||||
// in the control plane.
|
||||
|
||||
@ -146,6 +146,27 @@ func (nm *NetworkMap) GetIPVIPServiceMap() IPServiceMappings {
|
||||
return res
|
||||
}
|
||||
|
||||
// Services returns the Services visible (accessible) to this node,
|
||||
// decoded from [tailcfg.NodeAttrPrefixServices]+serviceName entries in the
|
||||
// self node's CapMap. Returns nil if nm is nil or SelfNode is invalid.
|
||||
func (nm *NetworkMap) Services() map[tailcfg.ServiceName]tailcfg.ServiceDetails {
|
||||
if nm == nil || !nm.SelfNode.Valid() {
|
||||
return nil
|
||||
}
|
||||
result := make(map[tailcfg.ServiceName]tailcfg.ServiceDetails)
|
||||
for cap := range nm.SelfNode.CapMap().All() {
|
||||
if !strings.HasPrefix(string(cap), string(tailcfg.NodeAttrPrefixServices)) {
|
||||
continue
|
||||
}
|
||||
svcs, err := tailcfg.UnmarshalNodeCapViewJSON[tailcfg.ServiceDetails](nm.SelfNode.CapMap(), cap)
|
||||
if err != nil || len(svcs) < 1 {
|
||||
continue
|
||||
}
|
||||
result[svcs[0].Name] = svcs[0]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// SelfNodeOrZero returns the self node, or a zero value if nm is nil.
|
||||
func (nm *NetworkMap) SelfNodeOrZero() tailcfg.NodeView {
|
||||
if nm == nil {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user