Remove restricted sessions (#36814)

* Remove restricted sessions

* Remove missing reference to restricted session

* Update CHANGELOG.md to remove outdated SSH feature

The commit removes the reference to the deprecated and removed SSH restricted sessions feature from the CHANGELOG.md file. The restricted session feature had been phased out since Teleport 14, and this update reflects its removal in Teleport 15.

* Rephrase the changelog note that it's recommends the implementation of network restrictions outside of Teleport like iptables and security groups.
This commit is contained in:
Jakub Nyckowski 2024-01-18 12:28:22 -05:00 committed by GitHub
parent b37d06a301
commit 9accc2f9ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 15 additions and 2019 deletions

View file

@ -72,6 +72,12 @@ by `insecure-drop`, which still creates temporary users but does not create a
home directory. Users who need home directory creation should either wrap `useradd`/`userdel`
or use PAM.
#### Remove restricted sessions for SSH
The restricted session feature for SSH has been deprecated since Teleport 14 and
has been removed in Teleport 15. We recommend implementing network restrictions
outside of Teleport (iptables, security groups, etc).
#### Packages no longer published to legacy Debian and RPM repos
`deb.releases.teleport.dev` and `rpm.releases.teleport.dev` were deprecated in

View file

@ -321,27 +321,16 @@ ifeq ("$(with_bpf)","yes")
$(ER_BPF_BUILDDIR):
mkdir -p $(ER_BPF_BUILDDIR)
$(RS_BPF_BUILDDIR):
mkdir -p $(RS_BPF_BUILDDIR)
# Build BPF code
$(ER_BPF_BUILDDIR)/%.bpf.o: bpf/enhancedrecording/%.bpf.c $(wildcard bpf/*.h) | $(ER_BPF_BUILDDIR)
$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(KERNEL_ARCH) -I/usr/libbpf-${LIBBPF_VER}/include $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) -c $(filter %.c,$^) -o $@
$(LLVM_STRIP) -g $@ # strip useless DWARF info
# Build BPF code
$(RS_BPF_BUILDDIR)/%.bpf.o: bpf/restrictedsession/%.bpf.c $(wildcard bpf/*.h) | $(RS_BPF_BUILDDIR)
$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(KERNEL_ARCH) -I/usr/libbpf-${LIBBPF_VER}/include $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) -c $(filter %.c,$^) -o $@
$(LLVM_STRIP) -g $@ # strip useless DWARF info
.PHONY: bpf-rs-bytecode
bpf-rs-bytecode: $(RS_BPF_BUILDDIR)/restricted.bpf.o
.PHONY: bpf-er-bytecode
bpf-er-bytecode: $(ER_BPF_BUILDDIR)/command.bpf.o $(ER_BPF_BUILDDIR)/disk.bpf.o $(ER_BPF_BUILDDIR)/network.bpf.o $(ER_BPF_BUILDDIR)/counter_test.bpf.o
.PHONY: bpf-bytecode
bpf-bytecode: bpf-er-bytecode bpf-rs-bytecode
bpf-bytecode: bpf-er-bytecode
# Generate vmlinux.h based on the installed kernel
.PHONY: update-vmlinux-h
@ -410,9 +399,6 @@ clean-build:
# Check if the variable is set to prevent calling remove on the root directory.
ifneq ($(ER_BPF_BUILDDIR),)
rm -f $(ER_BPF_BUILDDIR)/*.o
endif
ifneq ($(RS_BPF_BUILDDIR),)
rm -f $(RS_BPF_BUILDDIR)/*.o
endif
-cargo clean
-go clean -cache

View file

@ -28,7 +28,6 @@ LLVM_STRIP ?= $(shell which llvm-strip || which llvm-strip-12)
KERNEL_ARCH := $(shell uname -m | sed 's/x86_64/x86/g; s/aarch64/arm64/g')
INCLUDES :=
ER_BPF_BUILDDIR := lib/bpf/bytecode
RS_BPF_BUILDDIR := lib/restrictedsession/bytecode
# Get Clang's default includes on this system. We'll explicitly add these dirs
# to the includes list when compiling with `-target bpf` because otherwise some

View file

@ -240,9 +240,6 @@ const (
// ComponentBPF is the eBPF packagae.
ComponentBPF = "bpf"
// ComponentRestrictedSession is restriction of user access to kernel objects
ComponentRestrictedSession = "restrictedsess"
// ComponentCgroup is the cgroup package.
ComponentCgroup = "cgroups"

View file

@ -26,9 +26,6 @@ build_teleport_fuzzers() {
compile_native_go_fuzzer $TELEPORT_PREFIX/lib/srv/desktop/tdp \
FuzzDecode fuzz_decode
compile_native_go_fuzzer $TELEPORT_PREFIX/lib/restrictedsession \
FuzzParseIPSpec fuzz_parse_ip_spec
compile_native_go_fuzzer $TELEPORT_PREFIX/lib/services \
FuzzParseRefs fuzz_parse_refs

View file

@ -42,7 +42,6 @@ import (
"github.com/gravitational/teleport/lib/auth/testauthority"
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/bpf"
restricted "github.com/gravitational/teleport/lib/restrictedsession"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/srv"
@ -336,7 +335,6 @@ func newSrvCtx(ctx context.Context, t *testing.T) *SrvCtx {
}, nil,
),
regular.SetBPF(&bpf.NOP{}),
regular.SetRestrictedSessionManager(&restricted.NOP{}),
regular.SetClock(s.clock),
regular.SetUserAccountingPaths(utmpPath, wtmpPath, btmpPath),
regular.SetLockWatcher(lockWatcher),

View file

@ -117,15 +117,11 @@ type Service struct {
}
// New creates a BPF service.
func New(config *servicecfg.BPFConfig, restrictedSession *servicecfg.RestrictedSessionConfig) (BPF, error) {
func New(config *servicecfg.BPFConfig) (BPF, error) {
if err := config.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
if err := restrictedSession.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
// If BPF-based auditing is not enabled, don't configure anything return
// right away.
if !config.Enabled {
@ -185,10 +181,8 @@ func New(config *servicecfg.BPFConfig, restrictedSession *servicecfg.RestrictedS
}
log.Debugf("Started enhanced session recording with buffer sizes (command=%v, "+
"disk=%v, network=%v), restricted session (bufferSize=%v) "+
"and cgroup mount path: %v. Took %v.",
"disk=%v, network=%v) and cgroup mount path: %v. Took %v.",
*s.CommandBufferSize, *s.DiskBufferSize, *s.NetworkBufferSize,
*restrictedSession.EventsBufferSize,
s.CgroupPath, time.Since(start))
go s.processNetworkEvents()
@ -590,7 +584,7 @@ func (s *Service) emit6NetworkEvent(eventBytes []byte) {
func ipv4HostToIP(addr uint32) net.IP {
val := make([]byte, 4)
binary.LittleEndian.PutUint32(val, addr)
return net.IP(val)
return val
}
func ipv6HostToIP(addr [4]uint32) net.IP {
@ -599,7 +593,7 @@ func ipv6HostToIP(addr [4]uint32) net.IP {
binary.LittleEndian.PutUint32(val[4:], addr[1])
binary.LittleEndian.PutUint32(val[8:], addr[2])
binary.LittleEndian.PutUint32(val[12:], addr[3])
return net.IP(val)
return val
}
// unmarshalEvent will unmarshal the perf event.

View file

@ -30,7 +30,7 @@ type Service struct {
}
// New returns a new NOP service. Note this function does nothing.
func New(_ *servicecfg.BPFConfig, _ *servicecfg.RestrictedSessionConfig) (BPF, error) {
func New(_ *servicecfg.BPFConfig) (BPF, error) {
return &NOP{}, nil
}

View file

@ -154,7 +154,7 @@ func TestRootWatch(t *testing.T) {
service, err := New(&servicecfg.BPFConfig{
Enabled: true,
CgroupPath: cgroupPath,
}, &servicecfg.RestrictedSessionConfig{})
})
require.NoError(t, err)
t.Cleanup(func() {

View file

@ -1458,14 +1458,7 @@ func applySSHConfig(fc *FileConfig, cfg *servicecfg.Config) (err error) {
cfg.SSH.BPF = fc.SSH.BPF.Parse()
}
if fc.SSH.RestrictedSession != nil {
rs, err := fc.SSH.RestrictedSession.Parse()
if err != nil {
return trace.Wrap(err)
}
cfg.SSH.RestrictedSession = rs
log.Warnf("Restricted Sessions for SSH were deprecated in Teleport 14 " +
"and will be removed in Teleport 15.")
log.Error("Restricted Sessions for SSH were removed in Teleport 15.")
}
cfg.SSH.AllowTCPForwarding = fc.SSH.AllowTCPForwarding()

View file

@ -1524,19 +1524,6 @@ type RestrictedSession struct {
EventsBufferSize *int `yaml:"events_buffer_size,omitempty"`
}
// Parse will parse the enhanced session recording configuration.
func (r *RestrictedSession) Parse() (*servicecfg.RestrictedSessionConfig, error) {
enabled, err := apiutils.ParseBool(r.Enabled)
if err != nil {
return nil, trace.Wrap(err)
}
return &servicecfg.RestrictedSessionConfig{
Enabled: enabled,
EventsBufferSize: r.EventsBufferSize,
}, nil
}
// X11 is a configuration for X11 forwarding
type X11 struct {
// Enabled controls whether X11 forwarding requests can be granted by the server.

View file

@ -1,160 +0,0 @@
//go:build bpf && !386
// +build bpf,!386
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package restrictedsession
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"unsafe"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/lib/bpf"
api "github.com/gravitational/teleport/lib/events"
)
const (
BlockedIP4 = iota
BlockedIP6
)
//
// Audit Event types communicated between the kernel and userspace
//
// auditEventHeader matches audit_event_header in the C file
type auditEventHeader struct {
CGroupID uint64
PID uint32
EventType int32
Command [bpf.CommMax]byte
}
// auditEventBlockedIPv4 matches audit_event_blocked_ipv4 in the C file
type auditEventBlockedIPv4 struct {
SrcIP [4]byte
DstIP [4]byte
DstPort uint16
Op uint8
}
// auditEventBlockedIPv6 matches audit_event_blocked_ipv6 in the C file
type auditEventBlockedIPv6 struct {
SrcIP [16]byte
DstIP [16]byte
DstPort uint16
Op uint8
}
// newNetworkAuditEvent creates events.SessionNetwork, filling in common fields
// from the SessionContext
func newNetworkAuditEvent(ctx *bpf.SessionContext, hdr *auditEventHeader) events.SessionNetwork {
return events.SessionNetwork{
Metadata: events.Metadata{
Type: api.SessionNetworkEvent,
Code: api.SessionNetworkCode,
},
ServerMetadata: events.ServerMetadata{
ServerID: ctx.ServerID,
ServerHostname: ctx.ServerHostname,
ServerNamespace: ctx.Namespace,
},
SessionMetadata: events.SessionMetadata{
SessionID: ctx.SessionID,
},
UserMetadata: events.UserMetadata{
User: ctx.User,
Login: ctx.Login,
},
BPFMetadata: events.BPFMetadata{
CgroupID: hdr.CGroupID,
Program: bpf.ConvertString(unsafe.Pointer(&hdr.Command)),
PID: uint64(hdr.PID),
},
}
}
// parseAuditEventHeader parse the header portion of the event.
// buf is consumed so that only body bytes remain.
func parseAuditEventHeader(buf *bytes.Buffer) (auditEventHeader, error) {
var hdr auditEventHeader
err := binary.Read(buf, binary.LittleEndian, &hdr)
if err != nil {
return auditEventHeader{}, trace.BadParameter("corrupt event header: %v", err)
}
return hdr, nil
}
// ip6String is similar to IP.String but retains mapped addresses
// in IPv6 form.
func ip6String(ip net.IP) string {
var prefix string
if ip.To4() != nil {
// IP4 mapped address
prefix = "::ffff:"
}
return prefix + ip.String()
}
// parseAuditEvent parses the body of the audit event
func parseAuditEvent(buf *bytes.Buffer, hdr *auditEventHeader, ctx *bpf.SessionContext) (events.AuditEvent, error) {
switch hdr.EventType {
case BlockedIP4:
var body auditEventBlockedIPv4
if err := binary.Read(buf, binary.LittleEndian, &body); err != nil {
return nil, trace.Wrap(err)
}
event := newNetworkAuditEvent(ctx, hdr)
event.DstPort = int32(body.DstPort)
event.DstAddr = net.IP(body.DstIP[:]).String()
event.SrcAddr = net.IP(body.SrcIP[:]).String()
event.TCPVersion = 4
event.Operation = events.SessionNetwork_NetworkOperation(body.Op)
event.Action = events.EventAction_DENIED
return &event, nil
case BlockedIP6:
var body auditEventBlockedIPv6
if err := binary.Read(buf, binary.LittleEndian, &body); err != nil {
return nil, trace.Wrap(err)
}
event := newNetworkAuditEvent(ctx, hdr)
event.DstPort = int32(body.DstPort)
event.DstAddr = ip6String(net.IP(body.DstIP[:]))
event.SrcAddr = ip6String(net.IP(body.SrcIP[:]))
event.TCPVersion = 6
event.Operation = events.SessionNetwork_NetworkOperation(body.Op)
event.Action = events.EventAction_DENIED
return &event, nil
}
return nil, fmt.Errorf("received unknown event type: %v", hdr.EventType)
}

View file

@ -1,3 +0,0 @@
## BPF Bytecode
After builds, this directory contains CO-RE BFP bytecode that is embedded within Teleport.

View file

@ -1,68 +0,0 @@
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package restrictedsession
import (
"net"
"github.com/gravitational/trace"
)
func compactIP(ip net.IP) net.IP {
if ipv4 := ip.To4(); ipv4 != nil {
return ipv4
}
return ip
}
// ParseIPSpec takes in either a CIDR format (e.g. 192.168.1.2/16 or fe::/8)
// or a single IP address (e.g. 10.1.2.3 or fe::1) and returns *net.IPNet.
// In case of a single IP address, the associated network length is either
// /32 for IPv4 or /128 for IPv6.
func ParseIPSpec(cidr string) (*net.IPNet, error) {
_, ipnet, err := net.ParseCIDR(cidr)
if err == nil {
return ipnet, nil
}
// not in CIDR format, try as a plain IP
ip := net.ParseIP(cidr)
if ip == nil {
return nil, trace.BadParameter("%q is not an IP nor CIDR", cidr)
}
ip = compactIP(ip)
bits := len(ip) * 8
return &net.IPNet{
IP: ip,
Mask: net.CIDRMask(bits, bits),
}, nil
}
// NetworkRestrictions specifies which addresses should be blocked.
type NetworkRestrictions struct {
// Enabled controls if restrictions are enforced.
Enabled bool
// Allow holds a list of IPs (with masks) to allow, overriding deny list
Allow []net.IPNet
// Deny holds a list of IPs (with masks) to deny (block)
Deny []net.IPNet
}

View file

@ -1,41 +0,0 @@
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package restrictedsession
import (
"testing"
"github.com/stretchr/testify/require"
)
func FuzzParseIPSpec(f *testing.F) {
f.Add("127.0.0.111")
f.Add("127.0.0.111/8")
f.Add("192.168.0.0/16")
f.Add("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
f.Add("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64")
f.Add("2001:db8::ff00:42:8329")
f.Add("2001:db8::ff00:42:8329/48")
f.Fuzz(func(t *testing.T, cidr string) {
require.NotPanics(t, func() {
ParseIPSpec(cidr)
})
})
}

View file

@ -1,58 +0,0 @@
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package restrictedsession
import (
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/bpf"
"github.com/gravitational/teleport/lib/services"
)
// RestrictionsWatcherClient is used by changeset to fetch a list
// of proxies and subscribe to updates
type RestrictionsWatcherClient interface {
services.Restrictions
types.Events
}
// Manager starts and stop enforcing restrictions for a given session.
type Manager interface {
// OpenSession starts enforcing restrictions for a cgroup with cgroupID
OpenSession(ctx *bpf.SessionContext, cgroupID uint64)
// CloseSession stops enforcing restrictions for a cgroup with cgroupID
CloseSession(ctx *bpf.SessionContext, cgroupID uint64)
// Close stops the manager, cleaning up any resources
Close()
}
// Stubbed out Manager interface for cases where the real thing is not used.
type NOP struct{}
func (NOP) OpenSession(ctx *bpf.SessionContext, cgroupID uint64) {
}
func (NOP) CloseSession(ctx *bpf.SessionContext, cgroupID uint64) {
}
func (NOP) UpdateNetworkRestrictions(r *NetworkRestrictions) error {
return nil
}
func (NOP) Close() {
}

View file

@ -1,342 +0,0 @@
//go:build bpf && !386
// +build bpf,!386
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package restrictedsession
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"sort"
"strings"
"sync"
"unsafe"
"github.com/aquasecurity/libbpfgo"
"github.com/gravitational/trace"
)
var (
wildcard4 = net.IPNet{
IP: net.IP{0, 0, 0, 0},
Mask: net.IPMask{0, 0, 0, 0},
}
wildcard6 = net.IPNet{
IP: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Mask: net.IPMask{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
}
)
// ipTrie wraps BPF LSM map to work with net.IPNet types
type ipTrie struct {
bpfMap *libbpfgo.BPFMap
}
func newIPTrie(m *libbpfgo.Module, name string) (ipTrie, error) {
t, err := m.GetMap(name)
if err != nil {
return ipTrie{}, trace.Wrap(err)
}
return ipTrie{
bpfMap: t,
}, nil
}
func (t *ipTrie) toKey(n net.IPNet) []byte {
prefixLen, _ := n.Mask.Size()
// Key format: Prefix length (4 bytes) followed by prefix
key := make([]byte, 4+len(n.IP))
binary.LittleEndian.PutUint32(key[0:4], uint32(prefixLen))
copy(key[4:], n.IP)
return key
}
// Add upserts (prefixLen, prefix) -> value entry in BPF trie
func (t *ipTrie) add(n net.IPNet) error {
key := t.toKey(n)
return t.bpfMap.Update(unsafe.Pointer(&key[0]), unsafe.Pointer(&unit[0]))
}
// Remove removes the entry for the given network
func (t *ipTrie) remove(n net.IPNet) error {
key := t.toKey(n)
return t.bpfMap.DeleteKey(unsafe.Pointer(&key[0]))
}
// cmp is 3-way integral compare
func cmp(x, y int) int {
switch {
case x < y:
return -1
case x > y:
return 1
default:
return 0
}
}
// prefixLen returns the length of network prefix
// based on IPv6 encoding
func prefixLen(m net.IPMask) int {
ones, bits := m.Size()
if bits == 32 {
ones += (128 - 32)
}
return ones
}
// compareIPNets performs a 3-way compare of two IPNet
// objects. This induces a total order but it doesn't
// matter what order that is.
func compareIPNets(x, y *net.IPNet) int {
x.IP = x.IP.To16()
y.IP = y.IP.To16()
if ret := bytes.Compare(x.IP, y.IP); ret != 0 {
return ret
}
xPrefix := prefixLen(x.Mask)
yPrefix := prefixLen(y.Mask)
return cmp(xPrefix, yPrefix)
}
// ipNets is sort.Interface impl to sort []net.IPNet
type ipNets []net.IPNet
func (s ipNets) Len() int {
return len(s)
}
func (s ipNets) Less(i, j int) bool {
x := &s[i]
y := &s[j]
return compareIPNets(x, y) < 0
}
func (s ipNets) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// diffIPNets computes the differences between prev
// and next sets of net.IPNet objects. The diffs are
// returned as a tuple of (additions, deletions)
func diffIPNets(prev, next ipNets) (ipNets, ipNets) {
// Sorts []net.IPNet according to some
// criteria. The ordering used is immaterial, as long
// as total order is induced.
sort.Sort(prev)
sort.Sort(next)
adds := ipNets{}
dels := ipNets{}
i := 0
j := 0
for i < len(prev) && j < len(next) {
switch compareIPNets(&prev[i], &next[j]) {
case -1:
dels = append(dels, prev[i])
i++
case 0:
i++
j++
case 1:
adds = append(adds, next[j])
j++
}
}
// handle the tails (at most one of the lists still has a tail)
dels = append(dels, prev[i:]...)
adds = append(adds, next[j:]...)
return adds, dels
}
// network restricts IPv4 and IPv6 related operations.
type network struct {
mu sync.Mutex
mod *libbpfgo.Module
deny4 ipTrie
allow4 ipTrie
deny6 ipTrie
allow6 ipTrie
restrictions *NetworkRestrictions
}
func newNetwork(mod *libbpfgo.Module) (*network, error) {
deny4, err := newIPTrie(mod, "ip4_denylist")
if err != nil {
return nil, trace.Wrap(err)
}
allow4, err := newIPTrie(mod, "ip4_allowlist")
if err != nil {
return nil, trace.Wrap(err)
}
deny6, err := newIPTrie(mod, "ip6_denylist")
if err != nil {
return nil, trace.Wrap(err)
}
allow6, err := newIPTrie(mod, "ip6_allowlist")
if err != nil {
return nil, trace.Wrap(err)
}
n := network{
mod: mod,
deny4: deny4,
allow4: allow4,
deny6: deny6,
allow6: allow6,
restrictions: &NetworkRestrictions{},
}
if err = n.start(); err != nil {
return nil, trace.Wrap(err)
}
return &n, err
}
func (n *network) start() error {
hooks := []string{"socket_connect", "socket_sendmsg"}
for _, hook := range hooks {
if err := attachLSM(n.mod, hook); err != nil {
return trace.Wrap(err)
}
}
return nil
}
func ipv4MappedIPNet(ipnet net.IPNet) net.IPNet {
ipnet.IP = ipnet.IP.To16()
ones, _ := ipnet.Mask.Size()
// IPv4 mapped address has a 96-bit fixed prefix
ipnet.Mask = net.CIDRMask(96+ones, 128)
return ipnet
}
func (n *network) apply(nets []net.IPNet, fn4, fn6 func(net.IPNet) error) error {
for _, ipnet := range nets {
ip := ipnet.IP.To4()
if ip != nil {
// IPv4 address
ipnet.IP = ip
if err := fn4(ipnet); err != nil {
return trace.Wrap(err)
}
// Also add it to IPv6 trie as a mapped address.
// Needed in case an AF_INET6 socket is used with
// IPv4 translated address. The IPv6 stack will forward
// it to IPv4 stack but that happens much lower than
// the LSM hook.
ipnet = ipv4MappedIPNet(ipnet)
if err := fn6(ipnet); err != nil {
return trace.Wrap(err)
}
} else {
ip = ipnet.IP.To16()
if ip == nil {
return fmt.Errorf("%q is not an IPv4 or IPv6 address", ip.String())
}
if err := fn6(ipnet); err != nil {
return trace.Wrap(err)
}
}
}
return nil
}
func (n *network) update(newRestrictions *NetworkRestrictions) error {
n.mu.Lock()
defer n.mu.Unlock()
if !newRestrictions.Enabled {
newRestrictions.Allow = []net.IPNet{wildcard4, wildcard6}
newRestrictions.Deny = nil
}
// Compute the diff between the previous and new configs
// as a set of additions and deletions. Then apply these
// changes to the BPF maps.
// The deny list
denyAdds, denyDels := diffIPNets(n.restrictions.Deny, newRestrictions.Deny)
// Do the deletions
if err := n.apply(denyDels, n.deny4.remove, n.deny6.remove); err != nil {
return trace.Wrap(err)
}
// Do the additions
if err := n.apply(denyAdds, n.deny4.add, n.deny6.add); err != nil {
return trace.Wrap(err)
}
// The allow list
allowAdds, allowDels := diffIPNets(n.restrictions.Allow, newRestrictions.Allow)
// Do the deletions
if err := n.apply(allowDels, n.allow4.remove, n.allow6.remove); err != nil {
return trace.Wrap(err)
}
// Do the additions
if err := n.apply(allowAdds, n.allow4.add, n.allow6.add); err != nil {
return trace.Wrap(err)
}
n.restrictions = newRestrictions
log.Infof("New network restrictions applied: allow=[%v], deny=[%v]",
ipNetsToString(n.restrictions.Allow),
ipNetsToString(n.restrictions.Deny))
return nil
}
func (n *network) close() {
}
func ipNetsToString(ns []net.IPNet) string {
b := strings.Builder{}
for i, n := range ns {
if i != 0 {
b.WriteString(", ")
}
b.WriteString(n.String())
}
return b.String()
}

View file

@ -1,29 +0,0 @@
//go:build !bpf || 386
// +build !bpf 386
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package restrictedsession
import "github.com/gravitational/teleport/lib/service/servicecfg"
// New returns a new NOP service. Note this function does nothing.
func New(config *servicecfg.RestrictedSessionConfig, wc RestrictionsWatcherClient) (Manager, error) {
return &NOP{}, nil
}

View file

@ -1,346 +0,0 @@
//go:build bpf && !386
// +build bpf,!386
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package restrictedsession
import (
"bytes"
"embed"
"encoding/binary"
"os"
"sync"
"unsafe"
"github.com/aquasecurity/libbpfgo"
"github.com/gravitational/trace"
"github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/bpf"
"github.com/gravitational/teleport/lib/service/servicecfg"
)
var log = logrus.WithFields(logrus.Fields{
trace.Component: teleport.ComponentRestrictedSession,
})
var (
lostRestrictedEvents = prometheus.NewCounter(
prometheus.CounterOpts{
Name: teleport.MetricLostRestrictedEvents,
Help: "Number of lost restricted events.",
},
)
)
//go:embed bytecode
var embedFS embed.FS
func init() {
prometheus.MustRegister(lostRestrictedEvents)
}
var unit = make([]byte, 1)
// sessionMgr implements restrctedsession.Manager interface
// by enforcing the rules via LSM BPF hooks
type sessionMgr struct {
// mod is the handle to the BPF loaded module
mod *libbpfgo.Module
// watch keeps the set of cgroups being enforced
watch bpf.SessionWatch
// cgroups for which enforcement is active
restrictedCGroups *libbpfgo.BPFMap
// network blocking subsystem
nw *network
// eventLoop pumps the audit messages from the kernel
// to the audit subsystem
eventLoop *auditEventLoop
// updateLoop listens for restriction updates and applies them
// to the audit subsystem
updateLoop *restrictionsUpdateLoop
}
// New creates a RestrictedSession service.
func New(config *servicecfg.RestrictedSessionConfig, wc RestrictionsWatcherClient) (Manager, error) {
err := config.CheckAndSetDefaults()
if err != nil {
return nil, trace.Wrap(err)
}
// If BPF-based auditing is not enabled, don't configure anything
// right away.
if !config.Enabled {
log.Debugf("Restricted session is not enabled, skipping.")
return &NOP{}, nil
}
// Before proceeding, check that eBPF based LSM is enabled in the kernel
if err = checkBpfLsm(); err != nil {
return nil, trace.Wrap(err)
}
log.Debugf("Starting restricted session.")
restrictedBPF, err := embedFS.ReadFile("bytecode/restricted.bpf.o")
if err != nil {
return nil, trace.Wrap(err)
}
mod, err := libbpfgo.NewModuleFromBuffer(restrictedBPF, "restricted")
if err != nil {
return nil, trace.Wrap(err)
}
// Load into the kernel
if err = mod.BPFLoadObject(); err != nil {
return nil, trace.Wrap(err)
}
nw, err := newNetwork(mod)
if err != nil {
return nil, trace.Wrap(err)
}
cgroups, err := mod.GetMap("restricted_cgroups")
if err != nil {
return nil, trace.Wrap(err)
}
m := &sessionMgr{
mod: mod,
watch: bpf.NewSessionWatch(),
restrictedCGroups: cgroups,
nw: nw,
}
m.eventLoop, err = newAuditEventLoop(mod, &m.watch)
if err != nil {
return nil, trace.Wrap(err)
}
m.updateLoop, err = newRestrictionsUpdateLoop(nw, wc)
if err != nil {
return nil, trace.Wrap(err)
}
log.Info("Started restricted session management")
return m, nil
}
// Close will stop any running BPF programs. Note this is only for a graceful
// shutdown, from the man page for BPF: "Generally, eBPF programs are loaded
// by the user process and automatically unloaded when the process exits."
func (m *sessionMgr) Close() {
// Close the updater loop
m.updateLoop.close()
// Signal the loop pulling events off the perf buffer to shutdown.
m.eventLoop.close()
}
// OpenSession inserts the cgroupID into the BPF hash map to enable
// enforcement by the kernel
func (m *sessionMgr) OpenSession(ctx *bpf.SessionContext, cgroupID uint64) {
m.watch.Add(cgroupID, ctx)
key := make([]byte, 8)
binary.LittleEndian.PutUint64(key, cgroupID)
m.restrictedCGroups.Update(unsafe.Pointer(&key[0]), unsafe.Pointer(&unit[0]))
log.Debugf("CGroup %v registered", cgroupID)
}
// CloseSession removes the cgroupID from the BPF hash map to enable
// enforcement by the kernel
func (m *sessionMgr) CloseSession(ctx *bpf.SessionContext, cgroupID uint64) {
key := make([]byte, 8)
binary.LittleEndian.PutUint64(key, cgroupID)
m.restrictedCGroups.DeleteKey(unsafe.Pointer(&key[0]))
m.watch.Remove(cgroupID)
log.Debugf("CGroup %v unregistered", cgroupID)
}
type restrictionsUpdateLoop struct {
nw *network
watcher *RestrictionsWatcher
// Notifies that loop goroutine is done
wg sync.WaitGroup
}
func newRestrictionsUpdateLoop(nw *network, wc RestrictionsWatcherClient) (*restrictionsUpdateLoop, error) {
w, err := NewRestrictionsWatcher(RestrictionsWatcherConfig{
Client: wc,
RestrictionsC: make(chan *NetworkRestrictions, 10),
})
if err != nil {
return nil, trace.Wrap(err)
}
l := &restrictionsUpdateLoop{
nw: nw,
watcher: w,
}
l.wg.Add(1)
go l.loop()
return l, nil
}
func (l *restrictionsUpdateLoop) close() {
l.watcher.Close()
l.wg.Wait()
}
func (l *restrictionsUpdateLoop) loop() {
defer l.wg.Done()
for r := range l.watcher.RestrictionsC {
l.nw.update(r)
}
}
type auditEventLoop struct {
// Maps the cgroup to the session
watch *bpf.SessionWatch
// BPF ring buffer for reported audit (blocked) events
events *bpf.RingBuffer
// Keeps track of the number of lost audit events
lost *bpf.Counter
// Notifies that loop goroutine is done
wg sync.WaitGroup
}
// loop pulls events off the perf ring buffer, parses them, and emits them to
// the audit log.
func newAuditEventLoop(mod *libbpfgo.Module, w *bpf.SessionWatch) (*auditEventLoop, error) {
events, err := bpf.NewRingBuffer(mod, "audit_events")
if err != nil {
return nil, trace.Wrap(err)
}
lost, err := bpf.NewCounter(mod, "lost", lostRestrictedEvents)
if err != nil {
return nil, trace.Wrap(err)
}
l := &auditEventLoop{
watch: w,
events: events,
lost: lost,
}
l.wg.Add(1)
go l.loop()
return l, nil
}
func (l *auditEventLoop) loop() {
defer l.wg.Done()
for eventBytes := range l.events.EventCh {
buf := bytes.NewBuffer(eventBytes)
hdr, err := parseAuditEventHeader(buf)
if err != nil {
log.Error(err.Error())
continue
}
ctx, ok := l.watch.Get(hdr.CGroupID)
if !ok {
log.Errorf("Blocked event for unknown cgroup ID (%v)", hdr.CGroupID)
continue
}
event, err := parseAuditEvent(buf, &hdr, ctx)
if err != nil {
log.WithError(err).Error("Failed to parse network event.")
continue
}
if err = ctx.Emitter.EmitAuditEvent(ctx.Context, event); err != nil {
log.WithError(err).Warn("Failed to emit network event.")
}
}
}
func (l *auditEventLoop) close() {
// Cleanup
l.events.Close()
l.lost.Close()
l.wg.Wait()
}
// checkBpfLsm checks that eBPF is one of the enabled
// LSM "modules".
func checkBpfLsm() error {
const lsmInfo = "/sys/kernel/security/lsm"
csv, err := os.ReadFile(lsmInfo)
if err != nil {
return trace.Wrap(err)
}
for _, mod := range bytes.Split(csv, []byte(",")) {
if bytes.Equal(mod, []byte("bpf")) {
return nil
}
}
return trace.Errorf(`%s does not contain bpf entry, indicating that the kernel
is not enabled for eBPF based LSM enforcement. Make sure the kernel is compiled with
CONFIG_BPF_LSM=y and enabled via CONFIG_LSM or lsm= boot option`, lsmInfo)
}
// attachLSM attaches the LSM programs in the module to
// kernel hook points.
func attachLSM(mod *libbpfgo.Module, name string) error {
prog, err := mod.GetProgram(name)
if err != nil {
return trace.Wrap(err)
}
_, err = prog.AttachLSM()
if err != nil {
return trace.Wrap(err)
}
return nil
}

View file

@ -1,515 +0,0 @@
//go:build bpf && !386
// +build bpf,!386
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package restrictedsession
import (
"context"
"errors"
"fmt"
"net"
"os"
"regexp"
"syscall"
"testing"
"time"
gocmp "github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
apidefaults "github.com/gravitational/teleport/api/defaults"
api "github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/lib/bpf"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/events/eventstest"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
)
type blockAction int
const (
allowed = iota
denied
)
type blockedRange struct {
ver int // 4 or 6
deny string // Denied IP range in CIDR format or a lone IP
allow string // Allowed IP range in CIDR format or a lone IP
probe map[string]blockAction // IP to test the blocked range (needs to be within range)
}
const (
testPort = 8888
)
var testRanges = []blockedRange{
{
ver: 4,
allow: "39.156.69.70/28",
deny: "39.156.69.71",
probe: map[string]blockAction{
"39.156.69.64": allowed,
"39.156.69.79": allowed,
"39.156.69.71": denied,
"39.156.69.63": denied,
"39.156.69.80": denied,
"72.156.69.80": denied,
},
},
{
ver: 4,
allow: "77.88.55.88",
probe: map[string]blockAction{
"77.88.55.88": allowed,
"77.88.55.87": denied,
"77.88.55.86": denied,
"67.88.55.86": denied,
},
},
{
ver: 6,
allow: "39.156.68.48/28",
deny: "39.156.68.48/31",
probe: map[string]blockAction{
"::ffff:39.156.68.48": denied,
"::ffff:39.156.68.49": denied,
"::ffff:39.156.68.50": allowed,
"::ffff:39.156.68.63": allowed,
"::ffff:39.156.68.47": denied,
"::ffff:39.156.68.64": denied,
"::ffff:72.156.68.80": denied,
},
},
{
ver: 6,
allow: "fc80::/64",
deny: "fc80::10/124",
probe: map[string]blockAction{
"fc80::": allowed,
"fc80::ffff:ffff:ffff:ffff": allowed,
"fc80::10": denied,
"fc80::1f": denied,
"fc7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff": denied,
"fc60:0:0:1::": denied,
},
},
{
ver: 6,
allow: "2607:f8b0:4005:80a::200e",
probe: map[string]blockAction{
"2607:f8b0:4005:80a::200e": allowed,
"2607:f8b0:4005:80a::200d": denied,
"2607:f8b0:4005:80a::200f": denied,
"2607:f8b0:4005:80a::300f": denied,
},
},
}
type bpfContext struct {
cgroupDir string
cgroupID uint64
ctx *bpf.SessionContext
enhancedRecorder bpf.BPF
restrictedMgr Manager
srcAddrs map[int]string
// Audit events emitted by us
emitter eventstest.MockRecorderEmitter
expectedAuditEvents []apievents.AuditEvent
}
func setupBPFContext(t *testing.T) *bpfContext {
utils.InitLoggerForTests()
// This test must be run as root and the host has to be capable of running
// BPF programs.
if !isRoot() {
t.Skip("Tests for package restrictedsession can only be run as root.")
}
// Create temporary directory where cgroup2 hierarchy will be mounted.
// DO NOT MOVE THIS LINE.
// t.TempDir() creates t.Cleanup() action. If this hook is called before
// we mount cgroup in Close() this test will fail.
cgroupDir := t.TempDir()
bpfCtx := bpfContext{
cgroupDir: cgroupDir,
}
t.Cleanup(func() { bpfCtx.Close(t) })
bpfCtx.srcAddrs = map[int]string{
4: "0.0.0.0",
6: "::",
}
config := &servicecfg.RestrictedSessionConfig{
Enabled: true,
}
var err error
// Create BPF service since we piggyback on it
bpfCtx.enhancedRecorder, err = bpf.New(&servicecfg.BPFConfig{
Enabled: true,
CgroupPath: bpfCtx.cgroupDir,
}, config)
require.NoError(t, err)
// Create the SessionContext used by both enhanced recording and us (restricted session)
bpfCtx.ctx = &bpf.SessionContext{
Namespace: apidefaults.Namespace,
SessionID: uuid.New().String(),
ServerID: uuid.New().String(),
ServerHostname: "ip-172-31-11-148",
Login: "foo",
User: "foo@example.com",
PID: os.Getpid(),
Emitter: &bpfCtx.emitter,
Events: map[string]bool{},
}
// Create enhanced recording session to piggyback on.
bpfCtx.cgroupID, err = bpfCtx.enhancedRecorder.OpenSession(bpfCtx.ctx)
require.NoError(t, err)
require.Equal(t, bpfCtx.cgroupID > 0, true)
deny := []api.AddressCondition{}
allow := []api.AddressCondition{}
for _, r := range testRanges {
if len(r.deny) > 0 {
deny = append(deny, api.AddressCondition{CIDR: r.deny})
}
if len(r.allow) > 0 {
allow = append(allow, api.AddressCondition{CIDR: r.allow})
}
}
restrictions := api.NewNetworkRestrictions()
restrictions.SetAllow(allow)
restrictions.SetDeny(deny)
client := &mockClient{
restrictions: restrictions,
Fanout: *services.NewFanout(),
}
bpfCtx.restrictedMgr, err = New(config, client)
require.NoError(t, err)
client.Fanout.SetInit([]api.WatchKind{{Kind: api.KindNetworkRestrictions}})
time.Sleep(100 * time.Millisecond)
return &bpfCtx
}
func (tt *bpfContext) Close(t *testing.T) {
if tt.cgroupID > 0 {
tt.restrictedMgr.CloseSession(tt.ctx, tt.cgroupID)
}
if tt.restrictedMgr != nil {
tt.restrictedMgr.Close()
}
if tt.enhancedRecorder != nil && tt.ctx != nil {
err := tt.enhancedRecorder.CloseSession(tt.ctx)
require.NoError(t, err)
const restarting = false
err = tt.enhancedRecorder.Close(restarting)
require.NoError(t, err)
}
if tt.cgroupDir != "" {
err := os.RemoveAll(tt.cgroupDir)
require.NoError(t, err)
}
}
func (tt *bpfContext) openSession(t *testing.T) {
// Create the restricted session
tt.restrictedMgr.OpenSession(tt.ctx, tt.cgroupID)
}
func (tt *bpfContext) closeSession(t *testing.T) {
// Close the restricted session
tt.restrictedMgr.CloseSession(tt.ctx, tt.cgroupID)
}
func (tt *bpfContext) dialExpectAllow(t *testing.T, ver int, ip string) {
if err := dialTCP(ver, mustParseIP(ip)); err != nil {
// Other than EPERM or EINVAL is OK
if errors.Is(err, syscall.EPERM) || errors.Is(err, syscall.EINVAL) {
t.Fatalf("Dial %v was not allowed: %v", ip, err)
}
}
}
func (tt *bpfContext) sendExpectAllow(t *testing.T, ver int, ip string) {
err := sendUDP(ver, mustParseIP(ip))
// Other than EPERM or EINVAL is OK
if errors.Is(err, syscall.EPERM) || errors.Is(err, syscall.EINVAL) {
t.Fatalf("Send %v: failed with %v", ip, err)
}
}
func (tt *bpfContext) dialExpectDeny(t *testing.T, ver int, ip string) {
// Only EPERM is expected
err := dialTCP(ver, mustParseIP(ip))
if err == nil {
t.Fatalf("Dial %v: did not expect to succeed", ip)
}
if !errors.Is(err, syscall.EPERM) {
t.Fatalf("Dial %v: EPERM expected, got: %v", ip, err)
}
ev := tt.expectedAuditEvent(ver, ip, apievents.SessionNetwork_CONNECT)
tt.expectedAuditEvents = append(tt.expectedAuditEvents, ev)
}
func (tt *bpfContext) sendExpectDeny(t *testing.T, ver int, ip string) {
err := sendUDP(ver, mustParseIP(ip))
if !errors.Is(err, syscall.EPERM) {
t.Fatalf("Send %v: was not denied: %v", ip, err)
}
ev := tt.expectedAuditEvent(ver, ip, apievents.SessionNetwork_SEND)
tt.expectedAuditEvents = append(tt.expectedAuditEvents, ev)
}
func (tt *bpfContext) expectedAuditEvent(ver int, ip string, op apievents.SessionNetwork_NetworkOperation) apievents.AuditEvent {
return &apievents.SessionNetwork{
Metadata: apievents.Metadata{
Type: events.SessionNetworkEvent,
Code: events.SessionNetworkCode,
},
ServerMetadata: apievents.ServerMetadata{
ServerID: tt.ctx.ServerID,
ServerHostname: tt.ctx.ServerHostname,
ServerNamespace: tt.ctx.Namespace,
},
SessionMetadata: apievents.SessionMetadata{
SessionID: tt.ctx.SessionID,
},
UserMetadata: apievents.UserMetadata{
User: tt.ctx.User,
Login: tt.ctx.Login,
},
BPFMetadata: apievents.BPFMetadata{
CgroupID: tt.cgroupID,
Program: "restrictedsessi",
PID: uint64(tt.ctx.PID),
},
DstPort: testPort,
DstAddr: ip,
SrcAddr: tt.srcAddrs[ver],
TCPVersion: int32(ver),
Operation: op,
Action: apievents.EventAction_DENIED,
}
}
func TestRootNetwork(t *testing.T) {
if !bpfTestEnabled() {
t.Skip("BPF testing is disabled")
}
tt := setupBPFContext(t)
type testCase struct {
ver int
ip string
expected blockAction
}
tests := []testCase{}
for _, r := range testRanges {
for ip, expected := range r.probe {
tests = append(tests, testCase{
ver: r.ver,
ip: ip,
expected: expected,
})
}
}
// Restricted session is not yet open, all these should be allowed
for _, tc := range tests {
tt.dialExpectAllow(t, tc.ver, tc.ip)
tt.sendExpectAllow(t, tc.ver, tc.ip)
}
// Nothing should be reported to the audit log
time.Sleep(100 * time.Millisecond)
require.Empty(t, tt.emitter.Events())
// Open the restricted session
tt.openSession(t)
// Now the policy should be enforced
for _, tc := range tests {
if tc.expected == denied {
tt.dialExpectDeny(t, tc.ver, tc.ip)
tt.sendExpectDeny(t, tc.ver, tc.ip)
} else {
tt.dialExpectAllow(t, tc.ver, tc.ip)
tt.sendExpectAllow(t, tc.ver, tc.ip)
}
}
time.Sleep(100 * time.Millisecond)
// Close the restricted session
tt.closeSession(t)
// Check that the emitted audit events are correct
actualAuditEvents := tt.emitter.Events()
require.Empty(t, gocmp.Diff(tt.expectedAuditEvents, actualAuditEvents),
"Audit events mismatch (-want +got)")
// Clear out the expected and actual events
tt.expectedAuditEvents = nil
tt.emitter.Reset()
// Restricted session is now closed, all these should be allowed
for _, tc := range tests {
tt.dialExpectAllow(t, tc.ver, tc.ip)
tt.sendExpectAllow(t, tc.ver, tc.ip)
}
// Nothing should be reported to the audit log
time.Sleep(100 * time.Millisecond)
require.Empty(t, tt.emitter.Events())
}
type mockClient struct {
restrictions api.NetworkRestrictions
services.Fanout
}
func (mc *mockClient) GetNetworkRestrictions(context.Context) (api.NetworkRestrictions, error) {
return mc.restrictions, nil
}
func (_ *mockClient) SetNetworkRestrictions(context.Context, api.NetworkRestrictions) error {
return nil
}
func (_ *mockClient) DeleteNetworkRestrictions(context.Context) error {
return nil
}
var ip4Regex = regexp.MustCompile(`^\d+\.\d+\.\d+\.\d+$`)
// mustParseIP parses the IP and also converts IPv4 addresses
// to 4 byte representation. IPv4 mapped (into IPv6) addresses
// are kept in 16 byte encoding
func mustParseIP(addr string) net.IP {
is4 := ip4Regex.MatchString(addr)
ip := net.ParseIP(addr)
if is4 {
return ip.To4()
}
return ip.To16()
}
func testSocket(ver, typ int, ip net.IP) (int, syscall.Sockaddr, error) {
var domain int
var src syscall.Sockaddr
var dst syscall.Sockaddr
if ver == 4 {
domain = syscall.AF_INET
src = &syscall.SockaddrInet4{}
dst4 := &syscall.SockaddrInet4{
Port: testPort,
}
copy(dst4.Addr[:], ip)
dst = dst4
} else {
domain = syscall.AF_INET6
src = &syscall.SockaddrInet6{}
dst6 := &syscall.SockaddrInet6{
Port: testPort,
}
copy(dst6.Addr[:], ip)
dst = dst6
}
fd, err := syscall.Socket(domain, typ, 0)
if err != nil {
return 0, nil, fmt.Errorf("socket() failed: %v", err)
}
if err = syscall.Bind(fd, src); err != nil {
return 0, nil, fmt.Errorf("bind() failed: %v", err)
}
return fd, dst, nil
}
func dialTCP(ver int, ip net.IP) error {
fd, dst, err := testSocket(ver, syscall.SOCK_STREAM, ip)
if err != nil {
return err
}
defer syscall.Close(fd)
tv := syscall.Timeval{
Usec: 1000,
}
err = syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &tv)
if err != nil {
return fmt.Errorf("setsockopt(SO_SNDTIMEO) failed: %v", err)
}
return syscall.Connect(fd, dst)
}
func sendUDP(ver int, ip net.IP) error {
fd, dst, err := testSocket(ver, syscall.SOCK_DGRAM, ip)
if err != nil {
return err
}
defer syscall.Close(fd)
return syscall.Sendto(fd, []byte("abc"), 0, dst)
}
// isRoot returns a boolean if the test is being run as root or not. Tests
// for this package must be run as root.
func isRoot() bool {
return os.Geteuid() == 0
}
// bpfTestEnabled returns true if BPF/LSM tests should run. Tests can be enabled by
// setting TELEPORT_BPF_LSM_TEST environment variable to any value.
func bpfTestEnabled() bool {
return os.Getenv("TELEPORT_BPF_LSM_TEST") != ""
}

View file

@ -1,305 +0,0 @@
//go:build bpf && !386
// +build bpf,!386
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package restrictedsession
import (
"context"
"net"
"sync"
"time"
"github.com/gravitational/trace"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/retryutils"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/utils"
)
// NewRestrictionsWatcher returns a new instance of changeset
func NewRestrictionsWatcher(cfg RestrictionsWatcherConfig) (*RestrictionsWatcher, error) {
if err := cfg.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
retry, err := retryutils.NewLinear(retryutils.LinearConfig{
First: utils.HalfJitter(cfg.MaxRetryPeriod / 10),
Step: cfg.MaxRetryPeriod / 5,
Max: cfg.MaxRetryPeriod,
Jitter: retryutils.NewHalfJitter(),
})
if err != nil {
return nil, trace.Wrap(err)
}
ctx, cancelFn := context.WithCancel(context.Background())
w := &RestrictionsWatcher{
retry: retry,
resetC: make(chan struct{}),
cancelFn: cancelFn,
RestrictionsWatcherConfig: cfg,
}
w.wg.Add(1)
go w.watchRestrictions(ctx)
return w, nil
}
// RestrictionsWatcher is a resource built on top of the events,
// it monitors the changes to restrictions
type RestrictionsWatcher struct {
RestrictionsWatcherConfig
resetC chan struct{}
// retry is used to manage backoff logic for watches
retry retryutils.Retry
wg sync.WaitGroup
cancelFn context.CancelFunc
}
// RestrictionsWatcherConfig configures restrictions watcher
type RestrictionsWatcherConfig struct {
// MaxRetryPeriod is the maximum retry period on failed watchers
MaxRetryPeriod time.Duration
// ReloadPeriod is a failed period on failed watches
ReloadPeriod time.Duration
// Client is used by changeset to monitor restrictions updates
Client RestrictionsWatcherClient
// RestrictionsC is a channel that will be used
// by the watcher to push updated list,
// it will always receive a fresh list on the start
// and the subsequent list of new values
// whenever an addition or deletion to the list is detected
RestrictionsC chan *NetworkRestrictions
}
// CheckAndSetDefaults checks parameters and sets default values
func (cfg *RestrictionsWatcherConfig) CheckAndSetDefaults() error {
if cfg.Client == nil {
return trace.BadParameter("missing parameter Client")
}
if cfg.RestrictionsC == nil {
return trace.BadParameter("missing parameter RestrictionsC")
}
if cfg.MaxRetryPeriod == 0 {
cfg.MaxRetryPeriod = defaults.MaxWatcherBackoff
}
if cfg.ReloadPeriod == 0 {
cfg.ReloadPeriod = defaults.LowResPollingPeriod
}
return nil
}
// Reset returns a channel which notifies of internal
// watcher resets (used in tests).
func (w *RestrictionsWatcher) Reset() <-chan struct{} {
return w.resetC
}
// Close closes proxy watcher and cancels all the functions
func (w *RestrictionsWatcher) Close() error {
w.cancelFn()
w.wg.Wait()
close(w.RestrictionsC)
return nil
}
// watchProxies watches new proxies added and removed to the cluster
// and when this happens, notifies all connected agents
// about the proxy set change via discovery requests
func (w *RestrictionsWatcher) watchRestrictions(ctx context.Context) {
defer w.wg.Done()
for {
// Reload period is here to protect against
// unknown cache going out of sync problems
// that we did not predict.
if err := w.watch(ctx); err != nil {
log.Warningf("Re-init the watcher on error: %v.", trace.Unwrap(err))
}
log.Debugf("Reloading %v.", w.retry)
select {
case w.resetC <- struct{}{}:
default:
}
select {
case <-w.retry.After():
w.retry.Inc()
case <-ctx.Done():
log.Debugf("Closed, returning from update loop.")
return
}
}
}
func (w *RestrictionsWatcher) getNetworkRestrictions(ctx context.Context) (*NetworkRestrictions, error) {
resource, err := w.Client.GetNetworkRestrictions(ctx)
if err != nil {
if !trace.IsNotFound(err) {
return nil, trace.Wrap(err)
}
return &NetworkRestrictions{}, nil
}
restrictions, err := protoToNetworkRestrictions(resource)
if err != nil {
return nil, trace.Wrap(err)
}
return restrictions, nil
}
// watch sets up the watch on proxies
func (w *RestrictionsWatcher) watch(ctx context.Context) error {
watcher, err := w.Client.NewWatcher(ctx, types.Watch{
Name: teleport.ComponentRestrictedSession,
Kinds: []types.WatchKind{
{
Kind: types.KindNetworkRestrictions,
},
},
MetricComponent: teleport.ComponentRestrictedSession,
})
if err != nil {
return trace.Wrap(err)
}
defer watcher.Close()
reloadC := time.After(w.ReloadPeriod)
// before fetch, make sure watcher is synced by receiving init event,
// to avoid the scenario:
// 1. Cache process: w = NewWatcher()
// 2. Cache process: c.fetch()
// 3. Backend process: addItem()
// 4. Cache process: <- w.Events()
//
// If there is a way that NewWatcher() on line 1 could
// return without subscription established first,
// Code line 3 could execute and line 4 could miss event,
// wrapping up with out of sync replica.
// To avoid this, before doing fetch,
// cache process makes sure the connection is established
// by receiving init event first.
select {
case <-reloadC:
log.Debugf("Triggering scheduled reload.")
return nil
case <-ctx.Done():
return nil
case event := <-watcher.Events():
if event.Type != types.OpInit {
return trace.BadParameter("expected init event, got %v instead", event.Type)
}
}
restrictions, err := w.getNetworkRestrictions(ctx)
if err != nil {
return trace.Wrap(err)
}
w.retry.Reset()
select {
case w.RestrictionsC <- restrictions:
case <-ctx.Done():
return nil
}
for {
select {
case <-reloadC:
log.Debugf("Triggering scheduled reload.")
return nil
case <-ctx.Done():
return nil
case event := <-watcher.Events():
if restrictions := w.processEvent(event); restrictions != nil {
select {
case w.RestrictionsC <- restrictions:
case <-ctx.Done():
return nil
}
}
}
}
}
// processEvent updates proxy map and returns true if the proxies list have been modified -
// the proxy has been either added or deleted
func (w *RestrictionsWatcher) processEvent(event types.Event) *NetworkRestrictions {
if event.Resource.GetKind() != types.KindNetworkRestrictions {
log.Warningf("Unexpected event: %v.", event.Resource.GetKind())
return nil
}
switch event.Type {
case types.OpDelete:
return &NetworkRestrictions{}
case types.OpPut:
resource, ok := event.Resource.(types.NetworkRestrictions)
if !ok {
log.Warningf("unexpected type %T", event.Resource)
return nil
}
restrictions, err := protoToNetworkRestrictions(resource)
if err != nil {
log.Warningf("Bad network restrictions %#v.", resource)
return nil
}
return restrictions
default:
log.Warningf("Skipping unsupported event type %v.", event.Type)
return nil
}
}
func protoToIPNets(protoAddrs []types.AddressCondition) ([]net.IPNet, error) {
nets := []net.IPNet{}
for _, a := range protoAddrs {
n, err := ParseIPSpec(a.CIDR)
if err != nil {
return nil, trace.Wrap(err)
}
nets = append(nets, *n)
}
return nets, nil
}
func protoToNetworkRestrictions(proto types.NetworkRestrictions) (*NetworkRestrictions, error) {
deny, err := protoToIPNets(proto.GetDeny())
if err != nil {
return nil, trace.Wrap(err)
}
allow, err := protoToIPNets(proto.GetAllow())
if err != nil {
return nil, trace.Wrap(err)
}
return &NetworkRestrictions{
Enabled: true,
Deny: deny,
Allow: allow,
}, nil
}

View file

@ -121,7 +121,6 @@ import (
"github.com/gravitational/teleport/lib/proxy"
"github.com/gravitational/teleport/lib/proxy/clusterdial"
"github.com/gravitational/teleport/lib/proxy/peer"
restricted "github.com/gravitational/teleport/lib/restrictedsession"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/reversetunnelclient"
"github.com/gravitational/teleport/lib/service/servicecfg"
@ -2505,12 +2504,6 @@ func (process *TeleportProcess) initSSH() error {
"the cluster level, then restart Teleport.")
}
// Restricted session requires BPF (enhanced recording)
if cfg.SSH.RestrictedSession.Enabled && !cfg.SSH.BPF.Enabled {
return trace.BadParameter("restricted_session requires enhanced_recording " +
"to be enabled")
}
// If BPF is enabled in file configuration, but the operating system does
// not support enhanced session recording (like macOS), exit right away.
if cfg.SSH.BPF.Enabled && !bpf.SystemHasBPF() {
@ -2522,21 +2515,12 @@ func (process *TeleportProcess) initSSH() error {
// Start BPF programs. This is blocking and if the BPF programs fail to
// load, the node will not start. If BPF is not enabled, this will simply
// return a NOP struct that can be used to discard BPF data.
ebpf, err := bpf.New(cfg.SSH.BPF, cfg.SSH.RestrictedSession)
ebpf, err := bpf.New(cfg.SSH.BPF)
if err != nil {
return trace.Wrap(err)
}
defer func() { warnOnErr(ebpf.Close(restartingOnGracefulShutdown), log) }()
// Start access control programs. This is blocking and if the BPF programs fail to
// load, the node will not start. If access control is not enabled, this will simply
// return a NOP struct.
rm, err := restricted.New(cfg.SSH.RestrictedSession, conn.Client)
if err != nil {
return trace.Wrap(err)
}
// TODO: are we missing rm.Close()
// make sure the default namespace is used
if ns := cfg.SSH.Namespace; ns != "" && ns != apidefaults.Namespace {
return trace.BadParameter("cannot start with custom namespace %q, custom namespaces are deprecated. "+
@ -2640,7 +2624,6 @@ func (process *TeleportProcess) initSSH() error {
regular.SetUseTunnel(conn.UseTunnel()),
regular.SetFIPS(cfg.FIPS),
regular.SetBPF(ebpf),
regular.SetRestrictedSessionManager(rm),
regular.SetOnHeartbeat(process.OnHeartbeat(teleport.ComponentNode)),
regular.SetAllowTCPForwarding(cfg.SSH.AllowTCPForwarding),
regular.SetLockWatcher(lockWatcher),

View file

@ -58,23 +58,3 @@ func (c *BPFConfig) CheckAndSetDefaults() error {
return nil
}
// RestrictedSessionConfig holds configuration for the RestrictedSession service.
type RestrictedSessionConfig struct {
// Enabled if this service will try and install BPF programs on this system.
Enabled bool
// EventsBufferSize is the size (in pages) of the perf buffer for events.
EventsBufferSize *int
}
// CheckAndSetDefaults checks BPF configuration.
func (c *RestrictedSessionConfig) CheckAndSetDefaults() error {
var perfBufferPageCount = defaults.PerfBufferPageCount
if c.EventsBufferSize == nil {
c.EventsBufferSize = &perfBufferPageCount
}
return nil
}

View file

@ -568,7 +568,6 @@ func ApplyDefaults(cfg *Config) {
defaults.ConfigureLimiter(&cfg.SSH.Limiter)
cfg.SSH.PAM = &PAMConfig{Enabled: false}
cfg.SSH.BPF = &BPFConfig{Enabled: false}
cfg.SSH.RestrictedSession = &RestrictedSessionConfig{Enabled: false}
cfg.SSH.AllowTCPForwarding = true
cfg.SSH.AllowFileCopying = true

View file

@ -45,9 +45,6 @@ type SSHConfig struct {
// BPF holds BPF configuration for Teleport.
BPF *BPFConfig
// RestrictedSession holds kernel objects restrictions for Teleport.
RestrictedSession *RestrictedSessionConfig
// AllowTCPForwarding indicates that TCP port forwarding is allowed on this node
AllowTCPForwarding bool

View file

@ -46,7 +46,6 @@ import (
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/bpf"
"github.com/gravitational/teleport/lib/events"
restricted "github.com/gravitational/teleport/lib/restrictedsession"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
rsession "github.com/gravitational/teleport/lib/session"
@ -163,9 +162,6 @@ type Server interface {
// GetBPF returns the BPF service used for enhanced session recording.
GetBPF() bpf.BPF
// GetRestrictedSessionManager returns the manager for restricting user activity
GetRestrictedSessionManager() restricted.Manager
// Context returns server shutdown context
Context() context.Context

View file

@ -47,7 +47,6 @@ import (
"github.com/gravitational/teleport/lib/bpf"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/integrations/awsoidc"
restricted "github.com/gravitational/teleport/lib/restrictedsession"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/srv"
@ -505,13 +504,6 @@ func (s *Server) GetHostSudoers() srv.HostSudoers {
return &srv.HostSudoersNotImplemented{}
}
// GetRestrictedSessionManager returns a NOP manager since for a
// forwarding server it makes no sense (it has to run on the actual
// node).
func (s *Server) GetRestrictedSessionManager() restricted.Manager {
return &restricted.NOP{}
}
// GetInfo returns a services.Server that represents this server.
func (s *Server) GetInfo() types.Server {
return &types.ServerV2{

View file

@ -44,7 +44,6 @@ import (
"github.com/gravitational/teleport/lib/bpf"
"github.com/gravitational/teleport/lib/events/eventstest"
"github.com/gravitational/teleport/lib/fixtures"
restricted "github.com/gravitational/teleport/lib/restrictedsession"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/sshutils"
@ -255,11 +254,6 @@ func (m *mockServer) GetBPF() bpf.BPF {
return &bpf.NOP{}
}
// GetRestrictedSessionManager returns the manager for restricting user activity
func (m *mockServer) GetRestrictedSessionManager() restricted.Manager {
return &restricted.NOP{}
}
// Context returns server shutdown context
func (m *mockServer) Context() context.Context {
return context.Background()

View file

@ -60,7 +60,6 @@ import (
"github.com/gravitational/teleport/lib/labels"
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/proxy"
restricted "github.com/gravitational/teleport/lib/restrictedsession"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/reversetunnelclient"
"github.com/gravitational/teleport/lib/service/servicecfg"
@ -178,9 +177,6 @@ type Server struct {
// ebpf is the service used for enhanced session recording.
ebpf bpf.BPF
// restrictedMgr is the service used for restricting access to kernel objects
restrictedMgr restricted.Manager
// onHeartbeat is a callback for heartbeat status.
onHeartbeat func(error)
@ -298,11 +294,6 @@ func (s *Server) GetBPF() bpf.BPF {
return s.ebpf
}
// GetRestrictedSessionManager returns the manager for restricting user activity.
func (s *Server) GetRestrictedSessionManager() restricted.Manager {
return s.restrictedMgr
}
// GetLockWatcher gets the server's lock watcher.
func (s *Server) GetLockWatcher() *services.LockWatcher {
return s.lockWatcher
@ -607,13 +598,6 @@ func SetBPF(ebpf bpf.BPF) ServerOption {
}
}
func SetRestrictedSessionManager(m restricted.Manager) ServerOption {
return func(s *Server) error {
s.restrictedMgr = m
return nil
}
}
func SetOnHeartbeat(fn func(error)) ServerOption {
return func(s *Server) error {
s.onHeartbeat = fn

View file

@ -66,7 +66,6 @@ import (
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/observability/tracing"
libproxy "github.com/gravitational/teleport/lib/proxy"
restricted "github.com/gravitational/teleport/lib/restrictedsession"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
@ -229,7 +228,6 @@ func newCustomFixture(t *testing.T, mutateCfg func(*auth.TestServerConfig), sshO
}, nil,
),
SetBPF(&bpf.NOP{}),
SetRestrictedSessionManager(&restricted.NOP{}),
SetClock(clock),
SetLockWatcher(lockWatcher),
SetX11ForwardingConfig(&x11.ServerConfig{}),
@ -1480,7 +1478,6 @@ func TestProxyRoundRobin(t *testing.T) {
SetNamespace(apidefaults.Namespace),
SetPAMConfig(&servicecfg.PAMConfig{Enabled: false}),
SetBPF(&bpf.NOP{}),
SetRestrictedSessionManager(&restricted.NOP{}),
SetClock(f.clock),
SetLockWatcher(lockWatcher),
SetNodeWatcher(nodeWatcher),
@ -1621,7 +1618,6 @@ func TestProxyDirectAccess(t *testing.T) {
SetNamespace(apidefaults.Namespace),
SetPAMConfig(&servicecfg.PAMConfig{Enabled: false}),
SetBPF(&bpf.NOP{}),
SetRestrictedSessionManager(&restricted.NOP{}),
SetClock(f.clock),
SetLockWatcher(lockWatcher),
SetNodeWatcher(nodeWatcher),
@ -1862,7 +1858,6 @@ func TestLimiter(t *testing.T) {
SetNamespace(apidefaults.Namespace),
SetPAMConfig(&servicecfg.PAMConfig{Enabled: false}),
SetBPF(&bpf.NOP{}),
SetRestrictedSessionManager(&restricted.NOP{}),
SetClock(f.clock),
SetLockWatcher(lockWatcher),
SetSessionController(sessionController),
@ -2341,7 +2336,6 @@ func TestParseSubsystemRequest(t *testing.T) {
SetNamespace(apidefaults.Namespace),
SetPAMConfig(&servicecfg.PAMConfig{Enabled: false}),
SetBPF(&bpf.NOP{}),
SetRestrictedSessionManager(&restricted.NOP{}),
SetClock(f.clock),
SetLockWatcher(lockWatcher),
SetNodeWatcher(nodeWatcher),
@ -2603,7 +2597,6 @@ func TestIgnorePuTTYSimpleChannel(t *testing.T) {
SetNamespace(apidefaults.Namespace),
SetPAMConfig(&servicecfg.PAMConfig{Enabled: false}),
SetBPF(&bpf.NOP{}),
SetRestrictedSessionManager(&restricted.NOP{}),
SetClock(f.clock),
SetLockWatcher(lockWatcher),
SetNodeWatcher(nodeWatcher),
@ -2758,7 +2751,6 @@ func TestTargetMetadata(t *testing.T) {
}, nil,
),
SetBPF(&bpf.NOP{}),
SetRestrictedSessionManager(&restricted.NOP{}),
SetLockWatcher(lockWatcher),
SetX11ForwardingConfig(&x11.ServerConfig{}),
SetSessionController(sessionController),

View file

@ -1286,11 +1286,9 @@ func (s *session) startInteractive(ctx context.Context, scx *ServerContext, p *p
} else if cgroupID > 0 {
// If a cgroup ID was assigned then enhanced session recording was enabled.
s.setHasEnhancedRecording(true)
scx.srv.GetRestrictedSessionManager().OpenSession(sessionContext, cgroupID)
go func() {
// Close the BPF recording session once the session is closed
<-s.stopC
scx.srv.GetRestrictedSessionManager().CloseSession(sessionContext, cgroupID)
err = scx.srv.GetBPF().CloseSession(sessionContext)
if err != nil {
s.log.WithError(err).Error("Failed to close enhanced recording (interactive) session")
@ -1459,7 +1457,6 @@ func (s *session) startExec(ctx context.Context, channel ssh.Channel, scx *Serve
// If a cgroup ID was assigned then enhanced session recording was enabled.
if cgroupID > 0 {
s.setHasEnhancedRecording(true)
scx.srv.GetRestrictedSessionManager().OpenSession(sessionContext, cgroupID)
}
// Process has been placed in a cgroup, continue execution.
@ -1480,8 +1477,6 @@ func (s *session) startExec(ctx context.Context, channel ssh.Channel, scx *Serve
// BPF session so everything can be recorded.
time.Sleep(2 * time.Second)
scx.srv.GetRestrictedSessionManager().CloseSession(sessionContext, cgroupID)
// Close the BPF recording session. If BPF was not configured, not available,
// or running in a recording proxy, this is simply a NOP.
err = scx.srv.GetBPF().CloseSession(sessionContext)

View file

@ -120,7 +120,6 @@ import (
"github.com/gravitational/teleport/lib/multiplexer"
"github.com/gravitational/teleport/lib/observability/tracing"
"github.com/gravitational/teleport/lib/proxy"
restricted "github.com/gravitational/teleport/lib/restrictedsession"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/reversetunnelclient"
"github.com/gravitational/teleport/lib/secret"
@ -341,7 +340,6 @@ func newWebSuiteWithConfig(t *testing.T, cfg webSuiteConfig) *WebSuite {
regular.SetEmitter(nodeClient),
regular.SetPAMConfig(&servicecfg.PAMConfig{Enabled: false}),
regular.SetBPF(&bpf.NOP{}),
regular.SetRestrictedSessionManager(&restricted.NOP{}),
regular.SetClock(s.clock),
regular.SetLockWatcher(nodeLockWatcher),
regular.SetSessionController(nodeSessionController),
@ -446,7 +444,6 @@ func newWebSuiteWithConfig(t *testing.T, cfg webSuiteConfig) *WebSuite {
regular.SetEmitter(s.proxyClient),
regular.SetNamespace(apidefaults.Namespace),
regular.SetBPF(&bpf.NOP{}),
regular.SetRestrictedSessionManager(&restricted.NOP{}),
regular.SetClock(s.clock),
regular.SetLockWatcher(proxyLockWatcher),
regular.SetNodeWatcher(proxyNodeWatcher),
@ -613,7 +610,6 @@ func (s *WebSuite) addNode(t *testing.T, uuid string, hostname string, address s
regular.SetEmitter(nodeClient),
regular.SetPAMConfig(&servicecfg.PAMConfig{Enabled: false}),
regular.SetBPF(&bpf.NOP{}),
regular.SetRestrictedSessionManager(&restricted.NOP{}),
regular.SetClock(s.clock),
regular.SetLockWatcher(nodeLockWatcher),
regular.SetSessionController(nodeSessionController),
@ -7625,7 +7621,6 @@ func newWebPack(t *testing.T, numProxies int, opts ...proxyOption) *webPack {
regular.SetEmitter(nodeClient),
regular.SetPAMConfig(&servicecfg.PAMConfig{Enabled: false}),
regular.SetBPF(&bpf.NOP{}),
regular.SetRestrictedSessionManager(&restricted.NOP{}),
regular.SetClock(clock),
regular.SetLockWatcher(nodeLockWatcher),
regular.SetSessionController(nodeSessionController),
@ -7870,7 +7865,6 @@ func createProxy(ctx context.Context, t *testing.T, proxyID string, node *regula
regular.SetEmitter(client),
regular.SetNamespace(apidefaults.Namespace),
regular.SetBPF(&bpf.NOP{}),
regular.SetRestrictedSessionManager(&restricted.NOP{}),
regular.SetClock(clock),
regular.SetLockWatcher(proxyLockWatcher),
regular.SetNodeWatcher(proxyNodeWatcher),