tailcfg: add start of SSH policy to be sent from control plane to nodes

Updates #3802

Change-Id: Iec58f35d445aaa267d0f7e7e2f30c049c1df4c0e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/3955/head
Brad Fitzpatrick 3 years ago committed by Brad Fitzpatrick
parent b486448ab9
commit 57115e923e

@ -39,6 +39,7 @@ type mapSession struct {
lastDERPMap *tailcfg.DERPMap lastDERPMap *tailcfg.DERPMap
lastUserProfile map[tailcfg.UserID]tailcfg.UserProfile lastUserProfile map[tailcfg.UserID]tailcfg.UserProfile
lastParsedPacketFilter []filter.Match lastParsedPacketFilter []filter.Match
lastSSHPolicy *tailcfg.SSHPolicy
collectServices bool collectServices bool
previousPeers []*tailcfg.Node // for delta-purposes previousPeers []*tailcfg.Node // for delta-purposes
lastDomain string lastDomain string
@ -97,6 +98,9 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
if c := resp.DNSConfig; c != nil { if c := resp.DNSConfig; c != nil {
ms.lastDNSConfig = c ms.lastDNSConfig = c
} }
if p := resp.SSHPolicy; p != nil {
ms.lastSSHPolicy = p
}
if v, ok := resp.CollectServices.Get(); ok { if v, ok := resp.CollectServices.Get(); ok {
ms.collectServices = v ms.collectServices = v
@ -117,6 +121,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
Domain: ms.lastDomain, Domain: ms.lastDomain,
DNS: *ms.lastDNSConfig, DNS: *ms.lastDNSConfig,
PacketFilter: ms.lastParsedPacketFilter, PacketFilter: ms.lastParsedPacketFilter,
SSHPolicy: ms.lastSSHPolicy,
CollectServices: ms.collectServices, CollectServices: ms.collectServices,
DERPMap: ms.lastDERPMap, DERPMap: ms.lastDERPMap,
Debug: resp.Debug, Debug: resp.Debug,

@ -1157,6 +1157,10 @@ type MapResponse struct {
// sees. // sees.
Health []string `json:",omitempty"` Health []string `json:",omitempty"`
// SSHPolicy, if non-nil, updates the SSH policy for how incoming
// SSH connections should be handled.
SSHPolicy *SSHPolicy `json:",omitempty"`
// Debug is normally nil, except for when the control server // Debug is normally nil, except for when the control server
// is setting debug settings on a node. // is setting debug settings on a node.
Debug *Debug `json:",omitempty"` Debug *Debug `json:",omitempty"`
@ -1369,3 +1373,81 @@ type SetDNSRequest struct {
// Value is the value to add. // Value is the value to add.
Value string Value string
} }
// SSHPolicy is the policy for how to handle incoming SSH connections
// over Tailscale.
type SSHPolicy struct {
// Rules are the rules to process for an incoming SSH
// connection. The first matching rule takes its action and
// stops processing further rules.
Rules []*SSHRule `json:"rules"`
}
// An SSH rule is a match predicate and associated action for an incoming SSH connection.
type SSHRule struct {
// RuleExpires, if non-nil, is when this rule expires.
//
// For example, a (principal,sshuser) tuple might be granted
// prompt-free SSH access for N minutes, so this rule would be
// before a expiration-free rule for the same principal that
// required an auth prompt. This permits the control plane to
// be out of the path for already-authorized SSH pairs.
//
// Once a rule matches, the lifetime of any accepting connection
// is subject to the SSHAction.SessionExpires time, if any.
RuleExpires *time.Time `json:"ruleExpires,omitempty"`
// Principals matches an incoming connection. If the connection
// matches anything in this list and also matches SSHUsers,
// then Action is applied.
Principals []*SSHPrincipal `json:"principals"`
// SSHUsers are the SSH users that this rule matches. It is a
// map from either ssh-user|"*" => local-user. The map must
// contain a key for either ssh-user or, as a fallback, "*" to
// match anything. If it does, the map entry's value is the
// actual user that's logged in.
SSHUsers map[string]string `json:"sshUsers"`
// Action is the outcome to task.
// A nil or invalid action means to deny.
Action *SSHAction `json:"action"`
}
// SSHPrincipal is either a particular node or a user on any node.
// At most one field should be non-zero specified.
type SSHPrincipal struct {
Node StableNodeID `json:"node,omitempty"`
NodeIP string `json:"nodeIP,omitempty"`
UserLogin string `json:"userLogin,omitempty"` // email-ish: foo@example.com, bar@github
// TODO(bradfitz): add StableUserID, once that exists
}
// SSHAction is how to handle an incoming connection.
// At most one field should be non-zero.
type SSHAction struct {
// Message, if non-empty, is shown to the user before the
// action occurs.
Message string `json:"message,omitempty"`
// Reject, if true, terminates the connection. This action
// has higher priority that Accept, if given.
// The reason this is exists is primarily so a response
// from HoldAndDelegate has a way to stop the poll.
Reject bool `json:"reject,omitempty"`
// Accept, if true, accepts the connection immediately
// without further prompts.
Accept bool `json:"accept,omitempty"`
// SesssionExpires, if non-nil, is the time at which this
// session should forcefully terminate.
SesssionExpires *time.Time `json:"sessionExpires,omitempty"`
// HoldAndDelegate, if non-empty, is a URL that serves an outcome verdict.
// The connection will be accepted and will block until the
// provided long-polling URL serves a new SSHAction JSON
// value.
HoldAndDelegate string `json:"holdAndDelegate,omitempty"`
}

@ -39,6 +39,7 @@ type NetworkMap struct {
DNS tailcfg.DNSConfig DNS tailcfg.DNSConfig
Hostinfo tailcfg.Hostinfo Hostinfo tailcfg.Hostinfo
PacketFilter []filter.Match PacketFilter []filter.Match
SSHPolicy *tailcfg.SSHPolicy // or nil, if not enabled/allowed
// CollectServices reports whether this node's Tailnet has // CollectServices reports whether this node's Tailnet has
// requested that info about services be included in HostInfo. // requested that info about services be included in HostInfo.

Loading…
Cancel
Save