Skip to content

Commit

Permalink
Added PAM support to Teleport.
Browse files Browse the repository at this point in the history
  • Loading branch information
russjones committed Mar 20, 2018
1 parent 1f464c7 commit 785967e
Show file tree
Hide file tree
Showing 31 changed files with 1,211 additions and 34 deletions.
39 changes: 21 additions & 18 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ BUILDFLAGS ?= $(ADDFLAGS) -ldflags '-w -s'

ARCH=`go env GOOS`-`go env GOARCH`
RELEASE=teleport-$(GITTAG)-$(ARCH)-bin
BINARIES=$(BUILDDIR)/tsh $(BUILDDIR)/teleport $(BUILDDIR)/tctl
BINARIES=$(BUILDDIR)/teleport $(BUILDDIR)/tctl $(BUILDDIR)/tsh

VERSRC = version.go gitref.go
LIBS = $(shell find lib -type f -name '*.go') *.go
Expand All @@ -34,29 +34,37 @@ TELEPORTSRC = $(shell find tool/teleport -type f -name '*.go')
TSHSRC = $(shell find tool/tsh -type f -name '*.go')
TELEPORTVENDOR = $(shell find vendor -type f -name '*.go')

# PAM support will only be built into Teleport if headers exist at build time.
PAM_MESSAGE = "Building Teleport without PAM support."
ifneq ("$(wildcard /usr/include/security/pam_appl.h)","")
PAMFLAGS = -tags pam
PAM_MESSAGE = "Building Teleport with PAM support."
endif

#
# 'make all' builds all 3 executables and plaaces them in a current directory
#
#
# IMPORTANT: the binaries will not contain the web UI assets and `teleport`
# won't start without setting the environment variable DEBUG=1
# This is the default build target for convenience of working on
# a web UI.

.PHONY: all
all: $(VERSRC)
go install $(BUILDFLAGS) ./lib/... ./tool/...
@echo $(PAM_MESSAGE)
$(MAKE) -s -j 3 $(BINARIES)

$(BUILDDIR)/tctl: $(LIBS) $(TELEPORTSRC) $(TELEPORTVENDOR)
go build -o $(BUILDDIR)/tctl -i $(BUILDFLAGS) ./tool/tctl
go build $(PAMFLAGS) -o $(BUILDDIR)/tctl -i $(BUILDFLAGS) ./tool/tctl

$(BUILDDIR)/teleport: $(LIBS) $(TELEPORTSRC) $(TELEPORTVENDOR)
go build -o $(BUILDDIR)/teleport -i $(BUILDFLAGS) ./tool/teleport
go build $(PAMFLAGS) -o $(BUILDDIR)/teleport -i $(BUILDFLAGS) ./tool/teleport

$(BUILDDIR)/tsh: $(LIBS) $(TELEPORTSRC) $(TELEPORTVENDOR)
go build -o $(BUILDDIR)/tsh -i $(BUILDFLAGS) ./tool/tsh
go build $(PAMFLAGS) -o $(BUILDDIR)/tsh -i $(BUILDFLAGS) ./tool/tsh

#
# make full - builds the binary with the built-in web assets and places it
# make full - builds the binary with the built-in web assets and places it
# into $(BUILDDIR)
#
.PHONY:full
Expand All @@ -66,7 +74,6 @@ full: all $(BUILDDIR)/webassets.zip
zip -q -A $(BUILDDIR)/teleport
if [ -f e/Makefile ]; then $(MAKE) -C e full; fi


.PHONY: clean
clean:
rm -rf $(BUILDDIR)
Expand All @@ -78,9 +85,9 @@ clean:
@if [ -f e/Makefile ]; then $(MAKE) -C e clean; fi

#
# make release - produces a binary release tarball
#
.PHONY:
# make release - produces a binary release tarball
#
.PHONY:
export
release: clean full
mkdir teleport
Expand Down Expand Up @@ -126,8 +133,8 @@ test: $(VERSRC)
# integration tests. need a TTY to work and not compatible with a race detector
#
.PHONY: integration
integration:
go test -v ./integration/... -check.v
integration:
go test $(PAMFLAGS) -v ./integration/... -check.v

# This rule triggers re-generation of version.go and gitref.go if Makefile changes
$(VERSRC): Makefile
Expand Down Expand Up @@ -158,10 +165,6 @@ test-package: remove-temp-files
test-grep-package: remove-temp-files
go test -v ./$(p) -check.f=$(e)

.PHONY: test-dynamo
test-dynamo:
go test -v ./lib/... -tags dynamodb

.PHONY: cover-package
cover-package: remove-temp-files
go test -v ./$(p) -coverprofile=/tmp/coverage.out
Expand Down Expand Up @@ -227,7 +230,7 @@ goinstall:
github.com/gravitational/teleport/tool/teleport \
github.com/gravitational/teleport/tool/tctl

# make install will installs system-wide teleport
# make install will installs system-wide teleport
.PHONY: install
install: build
@echo "\n** Make sure to run 'make install' as root! **\n"
Expand Down
10 changes: 8 additions & 2 deletions build.assets/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ ENV DEBIAN_FRONTEND noninteractive
ADD locale.gen /etc/locale.gen
ADD profile /etc/profile

COPY pam/pam_teleport.so /lib/x86_64-linux-gnu/security
COPY pam/teleport-acct-failure /etc/pam.d
COPY pam/teleport-session-failure /etc/pam.d
COPY pam/teleport-success /etc/pam.d

RUN (apt-get clean; \
apt-get -q -y update --fix-missing; \
apt-get -q -y update; \
Expand All @@ -27,15 +32,16 @@ RUN apt-get install -q -y \
make \
git \
libc6-dev \
libpam-dev \
gcc \
tar \
gzip \
python \
python-pip \
libyaml-dev \
python-dev \
nginx \
zip; \
nginx \
zip; \
apt-get -y autoclean; apt-get -y clean

# Install mkDocs
Expand Down
6 changes: 3 additions & 3 deletions build.assets/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ build-binaries: bbox
make -C $(SRCDIR) ADDFLAGS='$(ADDFLAGS)' all

#
# Builds a Docker container which is used for building official Teleport
# Builds a Docker container which is used for building official Teleport
# binaries and docs
#
.PHONY:bbox
bbox:
docker build --build-arg UID=$$(id -u) --build-arg GID=$$(id -g) --tag $(BBOX) .

#
# Runs tests inside a build container
# Runs tests inside a build container
#
.PHONY:test
test: bbox
Expand Down Expand Up @@ -82,7 +82,7 @@ enter: bbox
# Create a teleport package using the build container
#
.PHONY:release
release:
release:
docker run $(DOCKERFLAGS) -i $(NOROOT) $(BBOX) \
/usr/bin/make release -e ADDFLAGS="$(ADDFLAGS)"

Binary file added build.assets/pam/pam_teleport.so
Binary file not shown.
2 changes: 2 additions & 0 deletions build.assets/pam/teleport-acct-failure
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
account required pam_teleport.so 0
session required pam_teleport.so 1
2 changes: 2 additions & 0 deletions build.assets/pam/teleport-session-failure
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
account required pam_teleport.so 1
session required pam_teleport.so 0
2 changes: 2 additions & 0 deletions build.assets/pam/teleport-success
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
account required pam_teleport.so 1
session required pam_teleport.so 1
3 changes: 3 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ const (
// ComponentDynamoDB represents dynamodb clients
ComponentDynamoDB = "dynamodb"

// Component pluggable authentication module (PAM)
ComponentPAM = "pam"

// DebugEnvVar tells tests to use verbose debug output
DebugEnvVar = "DEBUG"

Expand Down
144 changes: 143 additions & 1 deletion integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/pam"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/session"
Expand All @@ -60,7 +61,7 @@ const (
HostID = "00000000-0000-0000-0000-000000000000"
Site = "local-site"

AllocatePortsNum = 200
AllocatePortsNum = 300
)

type IntSuite struct {
Expand Down Expand Up @@ -1783,6 +1784,128 @@ func (s *IntSuite) TestAuditOff(c *check.C) {
c.Assert(err, check.NotNil)
}

// TestPAM checks that Teleport PAM integration works correctly. In this case
// that means if the account and session modules return success, the user
// should be allowed to log in. If either the account or session module does
// not return success, the user should not be able to log in.
func (s *IntSuite) TestPAM(c *check.C) {
// Check if TestPAM can run. For PAM tests to run, the binary must have been
// built with PAM support and the system running the tests must have libpam
// installed, and have the policy files installed. This test is always run
// in a container as part of the CI/CD pipeline. To run this test locally,
// install the pam_teleport.so module by running 'make && sudo make install'
// from the modules/pam_teleport directory. This will install the PAM module
// as well as the policy files.
if !pam.BuildHasPAM() || !pam.SystemHasPAM() || !hasPAMPolicy() {
skipMessage := "Skipping TestPAM: no policy found. To run PAM tests run " +
"'make && sudo make install' from the modules/pam_teleport directory."
c.Skip(skipMessage)
}

var tests = []struct {
inEnabled bool
inServiceName string
outContains []string
outError bool
}{
// 0 - No PAM support, session should work but no PAM related output.
{
inEnabled: false,
inServiceName: "",
outContains: []string{},
outError: false,
},
// 1 - PAM enabled, module account and session functions return success.
{
inEnabled: true,
inServiceName: "teleport-success",
outContains: []string{
"Account opened successfully.",
"Session open successfully.",
},
outError: false,
},
// 2 - PAM enabled, module account functions fail.
{
inEnabled: true,
inServiceName: "teleport-acct-failure",
outContains: []string{},
outError: true,
},
// 3 - PAM enabled, module session functions fail.
{
inEnabled: true,
inServiceName: "teleport-session-failure",
outContains: []string{},
outError: true,
},
}

for _, tt := range tests {
// Create a teleport instance with auth, proxy, and node.
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
tconf := service.MakeDefaultConfig()
tconf.Console = nil
tconf.Auth.Enabled = true

tconf.Proxy.Enabled = true
tconf.Proxy.DisableWebService = true
tconf.Proxy.DisableWebInterface = true

tconf.SSH.Enabled = true
tconf.SSH.PAM.Enabled = tt.inEnabled
tconf.SSH.PAM.ServiceName = tt.inServiceName

return c, nil, nil, tconf
}
t := s.newTeleportWithConfig(makeConfig())
defer t.Stop(true)

termSession := NewTerminal(250)

// Create an interactive session and write something to the terminal.
ctx, cancel := context.WithCancel(context.Background())
go func() {
cl, err := t.NewClient(ClientConfig{
Login: s.me.Username,
Cluster: Site,
Host: Host,
Port: t.GetPortSSHInt(),
})
c.Assert(err, check.IsNil)

cl.Stdout = &termSession
cl.Stdin = &termSession

termSession.Type("\aecho hi\n\r\aexit\n\r\a")
err = cl.SSH(context.TODO(), []string{}, false)

// If an error is expected (for example PAM does not allow a session to be
// created), this failure needs to be checked here.
if tt.outError {
c.Assert(err, check.NotNil)
} else {
c.Assert(err, check.IsNil)
}

cancel()
}()

// Wait for the session to end or timeout after 10 seconds.
select {
case <-time.After(10 * time.Second):
c.Fatalf("Timeout exceeded waiting for session to complete.")
case <-ctx.Done():
}

// If any output is expected, check to make sure it was output.
for _, expectedOutput := range tt.outContains {
output := string(termSession.Output(100))
c.Assert(strings.Contains(output, expectedOutput), check.Equals, true)
}
}
}

// runCommand is a shortcut for running SSH command, it creates a client
// connected to proxy of the passed in instance, runs the command, and returns
// the result. If multiple attempts are requested, a 250 millisecond delay is
Expand Down Expand Up @@ -1878,3 +2001,22 @@ func waitFor(c chan interface{}, timeout time.Duration) error {
return fmt.Errorf("timeout waiting for event")
}
}

// hasPAMPolicy checks if the three policy files needed for tests exists. If
// they do it returns true, otherwise returns false.
func hasPAMPolicy() bool {
pamPolicyFiles := []string{
"/etc/pam.d/teleport-acct-failure",
"/etc/pam.d/teleport-session-failure",
"/etc/pam.d/teleport-success",
}

for _, fileName := range pamPolicyFiles {
_, err := os.Stat(fileName)
if os.IsNotExist(err) {
return false
}
}

return true
}
22 changes: 22 additions & 0 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/pam"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
Expand Down Expand Up @@ -463,6 +464,27 @@ func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error {
if fc.SSH.PermitUserEnvironment {
cfg.SSH.PermitUserEnvironment = true
}
if fc.SSH.PAM != nil {
cfg.SSH.PAM = fc.SSH.PAM.Parse()

// If PAM is enabled, make sure that Teleport was built with PAM support
// and the PAM library was found at runtime.
if cfg.SSH.PAM.Enabled {
if !pam.BuildHasPAM() {
errorMessage := "Unable to start Teleport: PAM was enabled in file configuration but this \n" +
"Teleport binary was built without PAM support. To continue either download a \n" +
"Teleport binary build with PAM support from https://gravitational.com/teleport \n" +
"or disable PAM in file configuration."
return trace.BadParameter(errorMessage)
}
if !pam.SystemHasPAM() {
errorMessage := "Unable to start Teleport: PAM was enabled in file configuration but this \n" +
"system does not have the needed PAM library installed. To continue either \n" +
"install libpam or disable PAM in file configuration."
return trace.BadParameter(errorMessage)
}
}
}

return nil
}
Expand Down
Loading

0 comments on commit 785967e

Please sign in to comment.