From 9accc2f9ff7150e91717e7b560b765b2cb7e65d5 Mon Sep 17 00:00:00 2001 From: Jakub Nyckowski Date: Thu, 18 Jan 2024 12:28:22 -0500 Subject: [PATCH] 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. --- CHANGELOG.md | 6 + Makefile | 16 +- common.mk | 1 - constants.go | 3 - fuzz/oss-fuzz-build.sh | 3 - integration/utmp_integration_test.go | 2 - lib/bpf/bpf.go | 14 +- lib/bpf/bpf_nop.go | 2 +- lib/bpf/bpf_test.go | 2 +- lib/config/configuration.go | 9 +- lib/config/fileconf.go | 13 - lib/restrictedsession/audit.go | 160 ------- lib/restrictedsession/bytecode/README.md | 3 - lib/restrictedsession/config.go | 68 --- lib/restrictedsession/fuzz_test.go | 41 -- lib/restrictedsession/manager.go | 58 --- lib/restrictedsession/network.go | 342 --------------- lib/restrictedsession/nop.go | 29 -- lib/restrictedsession/restricted.go | 346 --------------- lib/restrictedsession/restricted_test.go | 515 ----------------------- lib/restrictedsession/watcher.go | 305 -------------- lib/service/service.go | 19 +- lib/service/servicecfg/bpf.go | 20 - lib/service/servicecfg/config.go | 1 - lib/service/servicecfg/ssh.go | 3 - lib/srv/ctx.go | 4 - lib/srv/forward/sshserver.go | 8 - lib/srv/mock.go | 6 - lib/srv/regular/sshserver.go | 16 - lib/srv/regular/sshserver_test.go | 8 - lib/srv/sess.go | 5 - lib/web/apiserver_test.go | 6 - 32 files changed, 15 insertions(+), 2019 deletions(-) delete mode 100644 lib/restrictedsession/audit.go delete mode 100644 lib/restrictedsession/bytecode/README.md delete mode 100644 lib/restrictedsession/config.go delete mode 100644 lib/restrictedsession/fuzz_test.go delete mode 100644 lib/restrictedsession/manager.go delete mode 100644 lib/restrictedsession/network.go delete mode 100644 lib/restrictedsession/nop.go delete mode 100644 lib/restrictedsession/restricted.go delete mode 100644 lib/restrictedsession/restricted_test.go delete mode 100644 lib/restrictedsession/watcher.go 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),