Skip to content

Commit

Permalink
Drop root privileges before reading files from TELEPORT_HOME
Browse files Browse the repository at this point in the history
  • Loading branch information
ravicious committed Jul 29, 2024
1 parent ae5ef05 commit 8484edb
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 28 deletions.
58 changes: 38 additions & 20 deletions lib/vnet/osconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/client/clientcache"
"github.com/gravitational/teleport/lib/vnet/daemon"
)

type osConfig struct {
Expand All @@ -42,14 +43,16 @@ type osConfigurator struct {
clientStore *client.Store
clientCache *clientcache.Cache
clusterConfigCache *ClusterConfigCache
tunName string
tunIPv6 string
dnsAddr string
homePath string
tunIPv4 string
// daemonClientCred are the credentials of the process that contacted the daemon.
daemonClientCred daemon.ClientCred
tunName string
tunIPv6 string
dnsAddr string
homePath string
tunIPv4 string
}

func newOSConfigurator(tunName, ipv6Prefix, dnsAddr, homePath string) (*osConfigurator, error) {
func newOSConfigurator(tunName, ipv6Prefix, dnsAddr, homePath string, daemonClientCred daemon.ClientCred) (*osConfigurator, error) {
if homePath == "" {
// This runs as root so we need to be configured with the user's home path.
return nil, trace.BadParameter("homePath must be passed from unprivileged process")
Expand All @@ -60,11 +63,12 @@ func newOSConfigurator(tunName, ipv6Prefix, dnsAddr, homePath string) (*osConfig
tunIPv6 := ipv6Prefix + "1"

configurator := &osConfigurator{
tunName: tunName,
tunIPv6: tunIPv6,
dnsAddr: dnsAddr,
homePath: homePath,
clientStore: client.NewFSClientStore(homePath),
tunName: tunName,
tunIPv6: tunIPv6,
dnsAddr: dnsAddr,
homePath: homePath,
clientStore: client.NewFSClientStore(homePath),
daemonClientCred: daemonClientCred,
}
configurator.clusterConfigCache = NewClusterConfigCache(clockwork.NewRealClock())

Expand All @@ -88,18 +92,32 @@ func (c *osConfigurator) close() error {
return trace.Wrap(c.clientCache.Clear())
}

// updateOSConfiguration reads tsh profiles out of [c.homePath]. For each profile, it reads the VNet
// config of the root cluster and of each leaf cluster. Then it proceeds to update the OS based on
// information from that config.
//
// For the duration of reading data from clusters, it drops the root privileges, only to regain them
// before configuring the OS.
func (c *osConfigurator) updateOSConfiguration(ctx context.Context) error {
var dnsZones []string
var cidrRanges []string

profileNames, err := profile.ListProfileNames(c.homePath)
if err != nil {
return trace.Wrap(err, "listing user profiles")
}
for _, profileName := range profileNames {
profileDNSZones, profileCIDRRanges := c.getDNSZonesAndCIDRRangesForProfile(ctx, profileName)
dnsZones = append(dnsZones, profileDNSZones...)
cidrRanges = append(cidrRanges, profileCIDRRanges...)
// Drop privileges to ensure that the user who spawned the daemon client has privileges necessary
// to access c.homePath that it sent when starting the daemon.
// Otherwise a client could make the daemon read a profile out of any directory.
if err := c.doWithDroppedRootPrivileges(ctx, func() error {
profileNames, err := profile.ListProfileNames(c.homePath)
if err != nil {
return trace.Wrap(err, "listing user profiles")
}
for _, profileName := range profileNames {
profileDNSZones, profileCIDRRanges := c.getDNSZonesAndCIDRRangesForProfile(ctx, profileName)
dnsZones = append(dnsZones, profileDNSZones...)
cidrRanges = append(cidrRanges, profileCIDRRanges...)
}
return nil
}); err != nil {
return trace.Wrap(err)
}

dnsZones = utils.Deduplicate(dnsZones)
Expand All @@ -113,7 +131,7 @@ func (c *osConfigurator) updateOSConfiguration(ctx context.Context) error {
}
}

err = configureOS(ctx, &osConfig{
err := configureOS(ctx, &osConfig{
tunName: c.tunName,
tunIPv6: c.tunIPv6,
tunIPv4: c.tunIPv4,
Expand Down
33 changes: 33 additions & 0 deletions lib/vnet/osconfig_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"os"
"os/exec"
"path/filepath"
"syscall"

"github.com/gravitational/trace"
)
Expand Down Expand Up @@ -139,3 +140,35 @@ func vnetManagedResolverFiles() (map[string]struct{}, error) {
}
return matchingFiles, nil
}

// doWithDroppedRootPrivileges drops the privileges of the current process to those of the client
// process that called the VNet daemon.
func (c *osConfigurator) doWithDroppedRootPrivileges(ctx context.Context, fn func() error) (err error) {
rootEgid := os.Getegid()
rootEuid := os.Geteuid()

if err := syscall.Setegid(c.daemonClientCred.Egid); err != nil {
return trace.Wrap(err, "setting egid")
}
defer func() {
syscallErr := trace.Wrap(syscall.Setegid(rootEgid), "reverting egid")
err = trace.NewAggregate(err, syscallErr)
}()

if err := syscall.Seteuid(c.daemonClientCred.Euid); err != nil {
return trace.Wrap(err, "setting euid")
}
defer func() {
syscallErr := trace.Wrap(syscall.Seteuid(rootEuid), "reverting euid")
err = trace.NewAggregate(err, syscallErr)
}()

log.InfoContext(ctx, "Temporarily dropping root privileges.", "egid", c.daemonClientCred.Egid, "euid", c.daemonClientCred.Euid)
defer func() {
if err == nil {
log.InfoContext(ctx, "Restored root privileges.", "egid", rootEgid, "euid", rootEuid)
}
}()

return trace.Wrap(fn())
}
28 changes: 28 additions & 0 deletions lib/vnet/osconfig_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Teleport
// Copyright (C) 2024 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

//go:build !darwin
// +build !darwin

package vnet

func configureOS(ctx context.Context, cfg *osConfig) error {
return trace.Wrap(ErrVnetNotImplemented)
}

func (c *osConfigurator) doWithDroppedPrivileges(fn func() error) error {
return trace.Wrap(ErrVnetNotImplemented)
}
12 changes: 8 additions & 4 deletions lib/vnet/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ func (pm *ProcessManager) Close() {
// It also handles host OS configuration that must run as root, and stays alive to keep the host configuration
// up to date. It will stay running until the socket at config.socketPath is deleted or until encountering an
// unrecoverable error.
//
// OS configuration is updated every [osConfigurationInterval]. During the update, it temporarily
// changes egid and euid of the process to that of the client connecting to the daemon.
func AdminSetup(ctx context.Context, config daemon.Config) error {
if err := config.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
Expand All @@ -237,7 +240,7 @@ func AdminSetup(ctx context.Context, config daemon.Config) error {

errCh := make(chan error)
go func() {
errCh <- trace.Wrap(osConfigurationLoop(ctx, tunName, config.IPv6Prefix, config.DNSAddr, config.HomePath))
errCh <- trace.Wrap(osConfigurationLoop(ctx, tunName, config.IPv6Prefix, config.DNSAddr, config.HomePath, *config.ClientCred))
}()

// Stay alive until we get an error on errCh, indicating that the osConfig loop exited.
Expand Down Expand Up @@ -282,8 +285,8 @@ func createAndSendTUNDevice(ctx context.Context, socketPath string) (string, err

// osConfigurationLoop will keep running until [ctx] is canceled or an unrecoverable error is encountered, in
// order to keep the host OS configuration up to date.
func osConfigurationLoop(ctx context.Context, tunName, ipv6Prefix, dnsAddr, homePath string) error {
osConfigurator, err := newOSConfigurator(tunName, ipv6Prefix, dnsAddr, homePath)
func osConfigurationLoop(ctx context.Context, tunName, ipv6Prefix, dnsAddr, homePath string, clientCred daemon.ClientCred) error {
osConfigurator, err := newOSConfigurator(tunName, ipv6Prefix, dnsAddr, homePath, clientCred)
if err != nil {
return trace.Wrap(err, "creating OS configurator")
}
Expand Down Expand Up @@ -314,7 +317,8 @@ func osConfigurationLoop(ctx context.Context, tunName, ipv6Prefix, dnsAddr, home

// Re-configure the host OS every 10 seconds. This will pick up any newly logged-in clusters by
// reading profiles from TELEPORT_HOME.
ticker := time.NewTicker(10 * time.Second)
const osConfigurationInterval = 10 * time.Second
ticker := time.NewTicker(osConfigurationInterval)
defer ticker.Stop()
for {
select {
Expand Down
4 changes: 0 additions & 4 deletions lib/vnet/setup_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ func receiveTUNDevice(socket *net.UnixListener) (tun.Device, error) {
return nil, trace.Wrap(ErrVnetNotImplemented)
}

func configureOS(ctx context.Context, cfg *osConfig) error {
return trace.Wrap(ErrVnetNotImplemented)
}

func execAdminProcess(ctx context.Context, config daemon.Config) error {
return trace.Wrap(ErrVnetNotImplemented)
}
Expand Down

0 comments on commit 8484edb

Please sign in to comment.