diff --git a/hscontrol/policy/acls.go b/hscontrol/policy/acls.go index 2ccc56b4..0b78898b 100644 --- a/hscontrol/policy/acls.go +++ b/hscontrol/policy/acls.go @@ -343,6 +343,28 @@ func (pol *ACLPolicy) generateSSHRules( continue } + recs := make([]netip.AddrPort, 0) + for innerIndex, rec := range sshACL.Recorder { + recSet, err := pol.ExpandAlias(append(peers, node), rec) + if err != nil { + log.Error(). + Msgf("Error parsing SSH %d, Recorder %d", index, innerIndex) + return nil, err + } + // ExpandAlias has expanded possible subnets; all prefixes are single IPs + for _, rec := range recSet.Prefixes() { + recs = append(recs, netip.AddrPortFrom(rec.Addr(), 80)) + } + } + action.Recorders = recs + + if sshACL.EnforceRecorder { + action.OnRecordingFailure = &tailcfg.SSHRecorderFailureAction{ + RejectSessionWithMessage: "# Failed to start session recording.", + TerminateSessionWithMessage: "# Failed to start session recording.", + } + } + principals := make([]*tailcfg.SSHPrincipal, 0, len(sshACL.Sources)) for innerIndex, rawSrc := range sshACL.Sources { if isWildcard(rawSrc) { diff --git a/hscontrol/policy/acls_test.go b/hscontrol/policy/acls_test.go index ff18dd05..41a3a843 100644 --- a/hscontrol/policy/acls_test.go +++ b/hscontrol/policy/acls_test.go @@ -2956,7 +2956,11 @@ func TestSSHRules(t *testing.T) { SSHUsers: map[string]string{ "autogroup:nonroot": "=", }, - Action: &tailcfg.SSHAction{Accept: true, AllowLocalPortForwarding: true}, + Action: &tailcfg.SSHAction{ + Accept: true, + AllowLocalPortForwarding: true, + Recorders: []netip.AddrPort{}, + }, }, { SSHUsers: map[string]string{ @@ -2967,7 +2971,11 @@ func TestSSHRules(t *testing.T) { Any: true, }, }, - Action: &tailcfg.SSHAction{Accept: true, AllowLocalPortForwarding: true}, + Action: &tailcfg.SSHAction{ + Accept: true, + AllowLocalPortForwarding: true, + Recorders: []netip.AddrPort{}, + }, }, { Principals: []*tailcfg.SSHPrincipal{ @@ -2978,7 +2986,11 @@ func TestSSHRules(t *testing.T) { SSHUsers: map[string]string{ "autogroup:nonroot": "=", }, - Action: &tailcfg.SSHAction{Accept: true, AllowLocalPortForwarding: true}, + Action: &tailcfg.SSHAction{ + Accept: true, + AllowLocalPortForwarding: true, + Recorders: []netip.AddrPort{}, + }, }, { SSHUsers: map[string]string{ @@ -2989,7 +3001,11 @@ func TestSSHRules(t *testing.T) { Any: true, }, }, - Action: &tailcfg.SSHAction{Accept: true, AllowLocalPortForwarding: true}, + Action: &tailcfg.SSHAction{ + Accept: true, + AllowLocalPortForwarding: true, + Recorders: []netip.AddrPort{}, + }, }, }, }, diff --git a/hscontrol/policy/acls_types.go b/hscontrol/policy/acls_types.go index e9c44909..c68fe76a 100644 --- a/hscontrol/policy/acls_types.go +++ b/hscontrol/policy/acls_types.go @@ -53,11 +53,13 @@ type AutoApprovers struct { // SSH controls who can ssh into which machines. type SSH struct { - Action string `json:"action" yaml:"action"` - Sources []string `json:"src" yaml:"src"` - Destinations []string `json:"dst" yaml:"dst"` - Users []string `json:"users" yaml:"users"` - CheckPeriod string `json:"checkPeriod,omitempty" yaml:"checkPeriod,omitempty"` + Action string `json:"action" yaml:"action"` + Sources []string `json:"src" yaml:"src"` + Destinations []string `json:"dst" yaml:"dst"` + Users []string `json:"users" yaml:"users"` + CheckPeriod string `json:"checkPeriod,omitempty" yaml:"checkPeriod,omitempty"` + Recorder []string `json:"recorder,omitempty" yaml:"recorder,omitempty"` + EnforceRecorder bool `json:"enforceRecorder,omitempty" yaml:"enforceRecorder,omitempty"` } // UnmarshalJSON allows to parse the Hosts directly into netip objects.