diff options
| author | Adriano Sela Aviles <adriano@tailscale.com> | 2026-04-21 13:13:05 -0700 |
|---|---|---|
| committer | Adriano Sela Aviles <adriano@tailscale.com> | 2026-04-22 15:58:49 -0700 |
| commit | ce158b7bab421ab61f2730994618197cd7dd74d0 (patch) | |
| tree | 3cee36673d6d2db46d83e389f303cd403592f1fc | |
| parent | a7d8aeb8aebc4bb01066eb6ffa69b9d8fe178b81 (diff) | |
| download | tailscale-adrianosela/corp-40648-extend-svcs-for-client-app-actions.tar.xz tailscale-adrianosela/corp-40648-extend-svcs-for-client-app-actions.zip | |
tailcfg: extend services model for client application actionsadrianosela/corp-40648-extend-svcs-for-client-app-actions
Updates: tailscale/corp#40648
Signed-off-by: Adriano Sela Aviles <adriano@tailscale.com>
| -rw-r--r-- | tailcfg/tailcfg.go | 52 | ||||
| -rw-r--r-- | tailcfg/tailcfg_test.go | 27 |
2 files changed, 79 insertions, 0 deletions
diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index da218837a..2ad9c7139 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -3349,17 +3349,69 @@ const LBHeader = "Ts-Lb" // this client is hosting can be ignored. type ServiceIPMappings map[ServiceName][]netip.Addr +// AppProto is an application-layer protocol identifier used in [Action]. +// It drives icon selection and client application matching in Tailscale clients. +type AppProto string + +const ( + AppProtoCockroachDB AppProto = "cockroachdb" + AppProtoHTTP AppProto = "http" + AppProtoKubernetes AppProto = "kubernetes" + AppProtoMongoDB AppProto = "mongodb" + AppProtoMySQL AppProto = "mysql" + AppProtoPostgreSQL AppProto = "postgresql" + AppProtoSSH AppProto = "ssh" + AppProtoTCP AppProto = "tcp" +) + +// Valid reports whether a is a known application protocol. +func (a AppProto) Valid() bool { + switch a { + case AppProtoCockroachDB, AppProtoHTTP, AppProtoKubernetes, + AppProtoMongoDB, AppProtoMySQL, AppProtoPostgreSQL, + AppProtoSSH, AppProtoTCP: + return true + } + return false +} + +// Action describes an application-level action that a Tailscale client can +// invoke for a [ServiceDetails]. +type Action struct { + // Label is the human-readable label shown in client menus. + Label string `json:",omitzero"` + + // ApplicationProto is the application-layer protocol identifier. + // It drives icon selection and client application matching. + ApplicationProto AppProto + + // Port is the target TCP port for this action. It must match one of + // the specific (non-range) TCP ports listed in the enclosing + // [ServiceDetails.Ports]. + Port uint16 +} + // 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 + // DisplayName is an optional human-readable label for the service. + // If empty, Name is used as a fallback by clients. + DisplayName string `json:",omitzero"` + // 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"` + + // Actions is an optional list of actions for this service. Each action + // maps an application protocol to a port; the port must be one of the + // entries in Ports. Not every port needs a corresponding action. When + // Actions is empty, clients may infer a default action from Ports. + Actions []Action `json:",omitzero"` } // ClientAuditAction represents an auditable action that a client can report to the diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 8dd9191b6..9cf125d8f 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -905,6 +905,33 @@ func TestCheckTag(t *testing.T) { } } +func TestAppProtoValid(t *testing.T) { + tests := []struct { + proto AppProto + want bool + }{ + {AppProtoCockroachDB, true}, + {AppProtoHTTP, true}, + {AppProtoKubernetes, true}, + {AppProtoMongoDB, true}, + {AppProtoMySQL, true}, + {AppProtoPostgreSQL, true}, + {AppProtoSSH, true}, + {AppProtoTCP, true}, + {"", false}, + {"ftp", false}, + {"https", false}, + {"unknown", false}, + } + for _, tt := range tests { + t.Run(string(tt.proto), func(t *testing.T) { + if got := tt.proto.Valid(); got != tt.want { + t.Errorf("AppProto(%q).Valid() = %v, want %v", tt.proto, got, tt.want) + } + }) + } +} + func TestDisplayMessageEqual(t *testing.T) { type test struct { name string |
