diff --git a/CHANGELOG.md b/CHANGELOG.md index c45db6a770d3d..3369289d75676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Makefile b/Makefile index 9808e4cca0260..8a7122b12ff8e 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/common.mk b/common.mk index 2088e9971fd86..1f9f9ec20f413 100644 --- a/common.mk +++ b/common.mk @@ -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 diff --git a/constants.go b/constants.go index fc05d3ae29081..d1a47d9de45e5 100644 --- a/constants.go +++ b/constants.go @@ -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" diff --git a/fuzz/oss-fuzz-build.sh b/fuzz/oss-fuzz-build.sh index 2ac1f954c0956..34001912c9cdc 100755 --- a/fuzz/oss-fuzz-build.sh +++ b/fuzz/oss-fuzz-build.sh @@ -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 diff --git a/integration/utmp_integration_test.go b/integration/utmp_integration_test.go index 6b72407f7e503..c5d420987ce2f 100644 --- a/integration/utmp_integration_test.go +++ b/integration/utmp_integration_test.go @@ -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), diff --git a/lib/bpf/bpf.go b/lib/bpf/bpf.go index 6bdfbd170fb8a..7eea97c5cba4e 100644 --- a/lib/bpf/bpf.go +++ b/lib/bpf/bpf.go @@ -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. diff --git a/lib/bpf/bpf_nop.go b/lib/bpf/bpf_nop.go index 95a4cb14cfbd2..1866b46a32fd5 100644 --- a/lib/bpf/bpf_nop.go +++ b/lib/bpf/bpf_nop.go @@ -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 } diff --git a/lib/bpf/bpf_test.go b/lib/bpf/bpf_test.go index 320a11f2d0457..69d2abf208f4f 100644 --- a/lib/bpf/bpf_test.go +++ b/lib/bpf/bpf_test.go @@ -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() { diff --git a/lib/config/configuration.go b/lib/config/configuration.go index f80d0bdf24281..d4f68c4afce53 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -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() diff --git a/lib/config/fileconf.go b/lib/config/fileconf.go index b70a9aec9f557..081724fcec6d8 100644 --- a/lib/config/fileconf.go +++ b/lib/config/fileconf.go @@ -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. diff --git a/lib/restrictedsession/audit.go b/lib/restrictedsession/audit.go deleted file mode 100644 index 2bef185f7b21d..0000000000000 --- a/lib/restrictedsession/audit.go +++ /dev/null @@ -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 . - */ - -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) -} diff --git a/lib/restrictedsession/bytecode/README.md b/lib/restrictedsession/bytecode/README.md deleted file mode 100644 index a71a135de3b5f..0000000000000 --- a/lib/restrictedsession/bytecode/README.md +++ /dev/null @@ -1,3 +0,0 @@ -## BPF Bytecode - -After builds, this directory contains CO-RE BFP bytecode that is embedded within Teleport. diff --git a/lib/restrictedsession/config.go b/lib/restrictedsession/config.go deleted file mode 100644 index 53b5f00f6a6ad..0000000000000 --- a/lib/restrictedsession/config.go +++ /dev/null @@ -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 . - */ - -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 -} diff --git a/lib/restrictedsession/fuzz_test.go b/lib/restrictedsession/fuzz_test.go deleted file mode 100644 index d1c3b15737288..0000000000000 --- a/lib/restrictedsession/fuzz_test.go +++ /dev/null @@ -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 . - */ - -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) - }) - }) -} diff --git a/lib/restrictedsession/manager.go b/lib/restrictedsession/manager.go deleted file mode 100644 index 9965b9dcaa523..0000000000000 --- a/lib/restrictedsession/manager.go +++ /dev/null @@ -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 . - */ - -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() { -} diff --git a/lib/restrictedsession/network.go b/lib/restrictedsession/network.go deleted file mode 100644 index 336d85abd128e..0000000000000 --- a/lib/restrictedsession/network.go +++ /dev/null @@ -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 . - */ - -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() -} diff --git a/lib/restrictedsession/nop.go b/lib/restrictedsession/nop.go deleted file mode 100644 index 020fbc8cde040..0000000000000 --- a/lib/restrictedsession/nop.go +++ /dev/null @@ -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 . - */ - -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 -} diff --git a/lib/restrictedsession/restricted.go b/lib/restrictedsession/restricted.go deleted file mode 100644 index 5c0264a72d7e9..0000000000000 --- a/lib/restrictedsession/restricted.go +++ /dev/null @@ -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 . - */ - -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 -} diff --git a/lib/restrictedsession/restricted_test.go b/lib/restrictedsession/restricted_test.go deleted file mode 100644 index f01866602c60c..0000000000000 --- a/lib/restrictedsession/restricted_test.go +++ /dev/null @@ -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 . - */ - -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") != "" -} diff --git a/lib/restrictedsession/watcher.go b/lib/restrictedsession/watcher.go deleted file mode 100644 index 38c31a0cb6d9c..0000000000000 --- a/lib/restrictedsession/watcher.go +++ /dev/null @@ -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 . - */ - -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 -} diff --git a/lib/service/service.go b/lib/service/service.go index 0c4cfc0dceb15..f033d42792f7c 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -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), diff --git a/lib/service/servicecfg/bpf.go b/lib/service/servicecfg/bpf.go index 1969a97d74033..c7ec8713ba678 100644 --- a/lib/service/servicecfg/bpf.go +++ b/lib/service/servicecfg/bpf.go @@ -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 -} diff --git a/lib/service/servicecfg/config.go b/lib/service/servicecfg/config.go index df0cd8391d7e6..48c1ce673c9e4 100644 --- a/lib/service/servicecfg/config.go +++ b/lib/service/servicecfg/config.go @@ -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 diff --git a/lib/service/servicecfg/ssh.go b/lib/service/servicecfg/ssh.go index 94998cd37cab6..2b98d6fe802dc 100644 --- a/lib/service/servicecfg/ssh.go +++ b/lib/service/servicecfg/ssh.go @@ -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 diff --git a/lib/srv/ctx.go b/lib/srv/ctx.go index 161b164d3ed74..dd2939209a4e1 100644 --- a/lib/srv/ctx.go +++ b/lib/srv/ctx.go @@ -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 diff --git a/lib/srv/forward/sshserver.go b/lib/srv/forward/sshserver.go index a39ceff08b9ad..8de88aad84757 100644 --- a/lib/srv/forward/sshserver.go +++ b/lib/srv/forward/sshserver.go @@ -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{ diff --git a/lib/srv/mock.go b/lib/srv/mock.go index 9b90104df0168..fa0859cdded69 100644 --- a/lib/srv/mock.go +++ b/lib/srv/mock.go @@ -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() diff --git a/lib/srv/regular/sshserver.go b/lib/srv/regular/sshserver.go index ed6998e9c0133..4ef41c2467709 100644 --- a/lib/srv/regular/sshserver.go +++ b/lib/srv/regular/sshserver.go @@ -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 diff --git a/lib/srv/regular/sshserver_test.go b/lib/srv/regular/sshserver_test.go index 20103409f1dfa..7c135796c5e4b 100644 --- a/lib/srv/regular/sshserver_test.go +++ b/lib/srv/regular/sshserver_test.go @@ -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), diff --git a/lib/srv/sess.go b/lib/srv/sess.go index 22533b402cec5..d942c9ad97dcd 100644 --- a/lib/srv/sess.go +++ b/lib/srv/sess.go @@ -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) diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index 7b3f49a94eb01..c3abf02251e09 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -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),