Skip to content

Commit

Permalink
prevent writing to sudoers or creating host users when running as a p…
Browse files Browse the repository at this point in the history
…roxy server (#45960)
  • Loading branch information
eriktate authored Aug 28, 2024
1 parent 6d81058 commit 862c643
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 3 deletions.
8 changes: 7 additions & 1 deletion lib/srv/regular/sshserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,8 @@ func (s *Server) GetLockWatcher() *services.LockWatcher {
// GetCreateHostUser determines whether users should be created on the
// host automatically
func (s *Server) GetCreateHostUser() bool {
return s.createHostUser
// we shouldn't allow creating host users on a proxy server
return !s.proxyMode && s.createHostUser
}

// GetHostUsers returns the HostUsers instance being used to manage
Expand All @@ -315,6 +316,11 @@ func (s *Server) GetHostUsers() srv.HostUsers {
// GetHostSudoers returns the HostSudoers instance being used to manage
// sudoers file provisioning
func (s *Server) GetHostSudoers() srv.HostSudoers {
// we shouldn't allow modifying sudoers on a proxy server
if s.proxyMode {
return nil
}

if s.sudoers == nil {
return &srv.HostSudoersNotImplemented{}
}
Expand Down
175 changes: 175 additions & 0 deletions lib/srv/regular/sshserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2975,3 +2975,178 @@ func newSigner(t *testing.T, ctx context.Context, testServer *auth.TestServer) s
// https://github.com/afborchert/pipebuf
// https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer
const maxPipeSize = 65536 + 1

func TestHostUserCreationProxy(t *testing.T) {
f := newFixtureWithoutDiskBasedLogging(t)
ctx := context.Background()

proxyClient, _ := newProxyClient(t, f.testSrv)
nodeClient, _ := newNodeClient(t, f.testSrv)

logger := logrus.WithField("test", "TestHostUserCreationProxy")
listener, reverseTunnelAddress := mustListen(t)
defer listener.Close()
lockWatcher := newLockWatcher(ctx, t, proxyClient)
nodeWatcher := newNodeWatcher(ctx, t, proxyClient)
caWatcher := newCertAuthorityWatcher(ctx, t, proxyClient)

reverseTunnelServer, err := reversetunnel.NewServer(reversetunnel.Config{
ClusterName: f.testSrv.ClusterName(),
ClientTLS: proxyClient.TLSConfig(),
ID: hostID,
Listener: listener,
HostSigners: []ssh.Signer{f.signer},
LocalAuthClient: proxyClient,
LocalAccessPoint: proxyClient,
NewCachingAccessPoint: noCache,
DataDir: t.TempDir(),
Emitter: proxyClient,
Log: logger,
LockWatcher: lockWatcher,
NodeWatcher: nodeWatcher,
CertAuthorityWatcher: caWatcher,
CircuitBreakerConfig: breaker.NoopBreakerConfig(),
})
require.NoError(t, err)
logger.WithField("tun-addr", reverseTunnelAddress.String()).Info("Created reverse tunnel server.")

require.NoError(t, reverseTunnelServer.Start())
defer reverseTunnelServer.Close()

router, err := libproxy.NewRouter(libproxy.RouterConfig{
ClusterName: f.testSrv.ClusterName(),
Log: utils.NewLoggerForTests().WithField(trace.Component, "test"),
RemoteClusterGetter: proxyClient,
SiteGetter: reverseTunnelServer,
TracerProvider: tracing.NoopProvider(),
})
require.NoError(t, err)

sessionController, err := srv.NewSessionController(srv.SessionControllerConfig{
Semaphores: proxyClient,
AccessPoint: proxyClient,
LockEnforcer: lockWatcher,
Emitter: proxyClient,
Component: teleport.ComponentNode,
ServerID: hostID,
})
require.NoError(t, err)

proxy, err := New(
ctx,
utils.NetAddr{AddrNetwork: "tcp", Addr: "localhost:0"},
f.testSrv.ClusterName(),
[]ssh.Signer{f.signer},
proxyClient,
t.TempDir(),
"",
utils.NetAddr{},
proxyClient,
SetProxyMode("", reverseTunnelServer, proxyClient, router),
SetEmitter(nodeClient),
SetNamespace(apidefaults.Namespace),
SetPAMConfig(&servicecfg.PAMConfig{Enabled: false}),
SetBPF(&bpf.NOP{}),
SetClock(f.clock),
SetLockWatcher(lockWatcher),
SetSessionController(sessionController),
)
require.NoError(t, err)

sudoers := &fakeHostSudoers{}
proxy.sudoers = sudoers

usersBackend := &fakeHostUsersBackend{}
proxy.users = usersBackend

// Explicitly enabled host user creation on the proxy, even though this
// should never happen, to test that the proxy will not honor this setting.
proxy.createHostUser = true
proxy.proxyMode = true

reg, err := srv.NewSessionRegistry(srv.SessionRegistryConfig{Srv: proxy, SessionTrackerService: proxyClient})
require.NoError(t, err)

_, err = reg.TryWriteSudoersFile(srv.IdentityContext{
AccessChecker: &fakeAccessChecker{},
})
assert.NoError(t, err)
assert.Equal(t, 0, sudoers.writeAttempts)

_, _, err = reg.TryCreateHostUser(srv.IdentityContext{
AccessChecker: &fakeAccessChecker{},
})
assert.NoError(t, err)
assert.Empty(t, usersBackend.calls, 0)
}

type fakeAccessChecker struct {
services.AccessChecker
}

func (f *fakeAccessChecker) HostSudoers(srv types.Server) ([]string, error) {
return []string{"test1", "test2", "test3"}, nil
}

func (f *fakeAccessChecker) HostUsers(srv types.Server) (*services.HostUsersInfo, error) {
return &services.HostUsersInfo{}, nil
}

type fakeHostSudoers struct {
writeAttempts int
}

func (f *fakeHostSudoers) WriteSudoers(name string, sudoers []string) error {
f.writeAttempts++
return nil
}

func (f *fakeHostSudoers) RemoveSudoers(name string) error {
return nil
}

type fakeHostUsersBackend struct {
srv.HostUsers

calls map[string]int
}

func (f *fakeHostUsersBackend) functionCalled(name string) {
if f.calls == nil {
f.calls = make(map[string]int)
}

f.calls[name]++
}

func (f *fakeHostUsersBackend) UpsertUser(name string, hostRoleInfo services.HostUsersInfo) (io.Closer, error) {
f.functionCalled("UpsertUser")
return nil, trace.NotImplemented("")
}

func (f *fakeHostUsersBackend) DeleteUser(name, gid string) error {
f.functionCalled("DeleteUser")
return trace.NotImplemented("")
}

func (f *fakeHostUsersBackend) DeleteAllUsers() error {
f.functionCalled("DeleteAllUsers")
return trace.NotImplemented("")
}

func (f *fakeHostUsersBackend) UserCleanup() {
f.functionCalled("UserCleanup")
}

func (f *fakeHostUsersBackend) Shutdown() {
f.functionCalled("ShutDown")
}

func (f *fakeHostUsersBackend) UserExists(name string) error {
f.functionCalled("UserExists")
return trace.NotImplemented("")
}

func (f *fakeHostUsersBackend) SetHostUserDeletionGrace(grace time.Duration) {
f.functionCalled("SetHostUserDeletionGrace")
}
9 changes: 7 additions & 2 deletions lib/srv/sess.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,12 @@ func (sc *sudoersCloser) Close() error {
// file, if any. If the returned closer is not nil, it must be called at the
// end of the session to cleanup the sudoers file.
func (s *SessionRegistry) TryWriteSudoersFile(identityContext IdentityContext) (io.Closer, error) {
if s.sudoers == nil {
// Pulling sudoers directly from the Srv so TryWriteSudoersFile always
// respects the invariant that we shouldn't write sudoers on proxy servers.
// This might invalidate the cached sudoers field on SessionRegistry, so
// we may be able to remove that in a future PR
sudoWriter := s.Srv.GetHostSudoers()
if sudoWriter == nil {
return nil, nil
}

Expand All @@ -250,7 +255,7 @@ func (s *SessionRegistry) TryWriteSudoersFile(identityContext IdentityContext) (
// not an error, sudoers may not be configured.
return nil, nil
}
if err := s.sudoers.WriteSudoers(identityContext.Login, sudoers); err != nil {
if err := sudoWriter.WriteSudoers(identityContext.Login, sudoers); err != nil {
return nil, trace.Wrap(err)
}

Expand Down

0 comments on commit 862c643

Please sign in to comment.