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
parent
b486448ab9
commit
57115e923e
|
@ -39,6 +39,7 @@ type mapSession struct {
|
|||
lastDERPMap *tailcfg.DERPMap
|
||||
lastUserProfile map[tailcfg.UserID]tailcfg.UserProfile
|
||||
lastParsedPacketFilter []filter.Match
|
||||
lastSSHPolicy *tailcfg.SSHPolicy
|
||||
collectServices bool
|
||||
previousPeers []*tailcfg.Node // for delta-purposes
|
||||
lastDomain string
|
||||
|
@ -97,6 +98,9 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
|
|||
if c := resp.DNSConfig; c != nil {
|
||||
ms.lastDNSConfig = c
|
||||
}
|
||||
if p := resp.SSHPolicy; p != nil {
|
||||
ms.lastSSHPolicy = p
|
||||
}
|
||||
|
||||
if v, ok := resp.CollectServices.Get(); ok {
|
||||
ms.collectServices = v
|
||||
|
@ -117,6 +121,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
|
|||
Domain: ms.lastDomain,
|
||||
DNS: *ms.lastDNSConfig,
|
||||
PacketFilter: ms.lastParsedPacketFilter,
|
||||
SSHPolicy: ms.lastSSHPolicy,
|
||||
CollectServices: ms.collectServices,
|
||||
DERPMap: ms.lastDERPMap,
|
||||
Debug: resp.Debug,
|
||||
|
|
|
@ -1157,6 +1157,10 @@ type MapResponse struct {
|
|||
// sees.
|
||||
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
|
||||
// is setting debug settings on a node.
|
||||
Debug *Debug `json:",omitempty"`
|
||||
|
@ -1369,3 +1373,81 @@ type SetDNSRequest struct {
|
|||
// Value is the value to add.
|
||||
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
|
||||
Hostinfo tailcfg.Hostinfo
|
||||
PacketFilter []filter.Match
|
||||
SSHPolicy *tailcfg.SSHPolicy // or nil, if not enabled/allowed
|
||||
|
||||
// CollectServices reports whether this node's Tailnet has
|
||||
// requested that info about services be included in HostInfo.
|
||||
|
|
Loading…
Reference in New Issue