Skip to content

Commit

Permalink
Set permissions on TTY.
Browse files Browse the repository at this point in the history
  • Loading branch information
russjones committed Feb 11, 2019
1 parent f887551 commit ae2da0a
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 9 deletions.
96 changes: 87 additions & 9 deletions lib/srv/term.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"io"
"os"
"os/exec"
"os/user"
"strconv"
"sync"
"syscall"

Expand All @@ -36,6 +38,12 @@ import (
log "github.com/sirupsen/logrus"
)

// LookupUser is used to mock the value returned by user.Lookup(string).
type LookupUser func(string) (*user.User, error)

// LookupGroup is used to mock the value returned by user.LookupGroup(string).
type LookupGroup func(string) (*user.Group, error)

// Terminal defines an interface of handy functions for managing a (local or
// remote) PTY, such as resizing windows, executing commands with a PTY, and
// cleaning up.
Expand Down Expand Up @@ -118,19 +126,30 @@ type terminal struct {

// NewLocalTerminal creates and returns a local PTY.
func newLocalTerminal(ctx *ServerContext) (*terminal, error) {
pty, tty, err := pty.Open()
if err != nil {
log.Warnf("Could not start PTY %v", err)
return nil, err
}
return &terminal{
var err error

t := &terminal{
log: log.WithFields(log.Fields{
trace.Component: teleport.ComponentLocalTerm,
}),
ctx: ctx,
pty: pty,
tty: tty,
}, nil
}

// Open PTY and corresponding TTY.
t.pty, t.tty, err = pty.Open()
if err != nil {
log.Warnf("Could not start PTY %v", err)
return nil, err
}

// Set the TTY owner. Failure is not fatal, for example Teleport is running
// on a read-only filesystem, but logging is useful for diagnostic purposes.
err = t.setOwner()
if err != nil {
log.Debugf("Unable to set TTY owner: %v.\n", err)
}

return t, nil
}

// AddParty adds another participant to this terminal. We will keep the
Expand Down Expand Up @@ -294,6 +313,65 @@ func (t *terminal) SetTerminalModes(termModes ssh.TerminalModes) {
return
}

// getOwner determines the uid, gid, and mode of the TTY similar to OpenSSH:
// https://github.com/openssh/openssh-portable/blob/ddc0f38/sshpty.c#L164-L215
func getOwner(login string, lookupUser LookupUser, lookupGroup LookupGroup) (int, int, os.FileMode, error) {
var err error
var uid int
var gid int
var mode os.FileMode

// Lookup the Unix login for the UID and fallback GID.
u, err := lookupUser(login)
if err != nil {
return 0, 0, 0, trace.Wrap(err)
}
uid, err = strconv.Atoi(u.Uid)
if err != nil {
return 0, 0, 0, trace.Wrap(err)
}

// If the tty group exists, use that as the gid of the TTY and set mode to
// be u+rw. Otherwise use the group of the user with mode u+rw g+w.
group, err := lookupGroup("tty")
if err != nil {
gid, err = strconv.Atoi(u.Gid)
if err != nil {
return 0, 0, 0, trace.Wrap(err)
}
mode = 0620
} else {
gid, err = strconv.Atoi(group.Gid)
if err != nil {
return 0, 0, 0, trace.Wrap(err)
}
mode = 0600
}

return uid, gid, mode, nil
}

// setOwner changes the owner and mode of the TTY.
func (t *terminal) setOwner() error {
uid, gid, mode, err := getOwner(t.ctx.Identity.Login, user.Lookup, user.LookupGroup)
if err != nil {
return trace.Wrap(err)
}

err = os.Chown(t.tty.Name(), uid, gid)
if err != nil {
return trace.Wrap(err)
}
err = os.Chmod(t.tty.Name(), mode)
if err != nil {
return trace.Wrap(err)
}

log.Debugf("Set permissions on %v to %v:%v with mode %v.", t.tty.Name(), uid, gid, mode)

return nil
}

type remoteTerminal struct {
wg sync.WaitGroup
mu sync.Mutex
Expand Down
95 changes: 95 additions & 0 deletions lib/srv/term_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
Copyright 2019 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package srv

import (
"fmt"
"os"
"os/user"
"testing"

"github.com/gravitational/teleport/lib/utils"

"github.com/gravitational/trace"

"gopkg.in/check.v1"
)

type TermSuite struct {
}

var _ = check.Suite(&TermSuite{})
var _ = fmt.Printf

func (s *TermSuite) SetUpSuite(c *check.C) {
utils.InitLoggerForTests(testing.Verbose())
}
func (s *TermSuite) TearDownSuite(c *check.C) {}
func (s *TermSuite) SetUpTest(c *check.C) {}
func (s *TermSuite) TearDownTest(c *check.C) {}

func (s *TermSuite) TestGetOwner(c *check.C) {
tests := []struct {
inUserLookup LookupUser
inGroupLookup LookupGroup
outUid int
outGid int
outMode os.FileMode
}{
// Group "tty" exists.
{
inUserLookup: func(s string) (*user.User, error) {
return &user.User{
Uid: "1000",
Gid: "1000",
}, nil
},
inGroupLookup: func(s string) (*user.Group, error) {
return &user.Group{
Gid: "5",
}, nil
},
outUid: 1000,
outGid: 5,
outMode: 0600,
},
// Group "tty" does not exist.
{
inUserLookup: func(s string) (*user.User, error) {
return &user.User{
Uid: "1000",
Gid: "1000",
}, nil
},
inGroupLookup: func(s string) (*user.Group, error) {
return &user.Group{}, trace.BadParameter("")
},
outUid: 1000,
outGid: 1000,
outMode: 0620,
},
}

for _, tt := range tests {
uid, gid, mode, err := getOwner("", tt.inUserLookup, tt.inGroupLookup)
c.Assert(err, check.IsNil)

c.Assert(uid, check.Equals, tt.outUid)
c.Assert(gid, check.Equals, tt.outGid)
c.Assert(mode, check.Equals, tt.outMode)
}
}

0 comments on commit ae2da0a

Please sign in to comment.