Skip to content

Commit

Permalink
pam: trigger pam_authenticate on login (#3966)
Browse files Browse the repository at this point in the history
* pam: trigger pam_authenticate on login

This will trigger any "auth" PAM modules configured on the system for
teleport. For example, Duo 2FA prompt on each connection.
The module will be able to interact with the user (e.g. print prompts).

Also, make PAM env var propagation consistent for port forwarding
sessions.

Fixes #3929

* Revamp PAM testing stack

- update PAM policies and module for "auth" step
- use pam_teleport.so from the repo directory instead of guessing
  OS-specific global path
- add tests covering all failure scenarios and generally refactor PAM
  tests

* Build pam_teleport.so during buildbox build inside docker

This removes the need for libpam-devel on the host and reliably compiles
pam_teleport.so in our CI pipeline.
As part of this, combine build.assets/pam/ and modules/pam_teleport to
avoid the need to sync them.
  • Loading branch information
Andrew Lytvynov authored Jul 10, 2020
1 parent 3034a59 commit 78c2a31
Show file tree
Hide file tree
Showing 27 changed files with 220 additions and 125 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ darwin
# Compiled binaries, Object files, Static and Dynamic libs (Shared Objects)
out
build
*.o#
*.o
*.a
*.so

Expand Down
4 changes: 2 additions & 2 deletions build.assets/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
./teleport
./tctl
/teleport
/tctl
5 changes: 3 additions & 2 deletions build.assets/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ RUN (curl -L https://github.com/golangci/golangci-lint/releases/download/v1.24.0
cp golangci-lint-1.24.0-$(go env GOOS)-$(go env GOARCH)/golangci-lint /bin/ ;\
rm -r golangci-lint*)

COPY pam/pam_teleport.so /lib/x86_64-linux-gnu/security
COPY pam/teleport-* /etc/pam.d/
# Install PAM module and policies for testing.
COPY pam/ /opt/pam_teleport/
RUN make -C /opt/pam_teleport install

VOLUME ["/go/src/github.com/gravitational/teleport"]
EXPOSE 6600 2379 2380
4 changes: 4 additions & 0 deletions build.assets/Dockerfile-centos6
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,9 @@ ENV GOPATH="/go" \
COPY pam/pam_teleport.so /lib/x86_64-linux-gnu/security
COPY pam/teleport-* /etc/pam.d/

# Install PAM module and policies for testing.
COPY pam/ /opt/pam_teleport/
RUN make -C /opt/pam_teleport install

VOLUME ["/go/src/github.com/gravitational/teleport"]
EXPOSE 6600 2379 2380
4 changes: 4 additions & 0 deletions build.assets/Dockerfile-centos6-fips
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,9 @@ ENV GOPATH="/go" \
COPY pam/pam_teleport.so /lib/x86_64-linux-gnu/security
COPY pam/teleport-* /etc/pam.d/

# Install PAM module and policies for testing.
COPY pam/ /opt/pam_teleport/
RUN make -C /opt/pam_teleport install

VOLUME ["/go/src/github.com/gravitational/teleport"]
EXPOSE 6600 2379 2380
4 changes: 4 additions & 0 deletions build.assets/Dockerfile-fips
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,9 @@ ENV GOPATH="/go" \
COPY pam/pam_teleport.so /lib/x86_64-linux-gnu/security
COPY pam/teleport-* /etc/pam.d/

# Install PAM module and policies for testing.
COPY pam/ /opt/pam_teleport/
RUN make -C /opt/pam_teleport install

VOLUME ["/go/src/github.com/gravitational/teleport"]
EXPOSE 6600 2379 2380
37 changes: 37 additions & 0 deletions build.assets/pam/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
UNAME := $(shell uname -s)

# PAM_POLICY_PATH is where the PAM policy for PAM-aware applications is
# defined.
PAM_POLICY_PATH = /etc/pam.d/

PAM_MODULE_PATH_ESCAPED = $(shell pwd | sed 's/\//\\\//g')

# LD and LD_FLAGS controls the linker and linker flags to use and are
# determined by the OS.
LD = ld
LD_FLAGS = -lpam --shared -x
ifeq ($(UNAME),Darwin)
LD = clang
LD_FLAGS = -lpam -shared
endif

all: pam_teleport.so

install: pam_teleport.so
# Copy all PAM policy files over to /etc/pam.d
for file in $(shell ls policy/); do \
# Replace the module path in these files with the absolute path to \
# pam_teleport.so in current directory. That way we don't depend on the \
# OS-specific PAM module locations. \
sh -c "sed 's/pam_teleport.so/$(PAM_MODULE_PATH_ESCAPED)\/pam_teleport.so/' policy/$${file} >$(PAM_POLICY_PATH)$${file}"; \
done

pam_teleport.so: pam_teleport.o
$(LD) $(LD_FLAGS) -o pam_teleport.so pam_teleport.o
chmod 644 pam_teleport.so

pam_teleport.o: clean pam_teleport.c
gcc -fPIC -c pam_teleport.c

clean:
rm -f pam_teleport.o pam_teleport.so
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
#include <sys/types.h>
#endif

// For all PAM hooks, argument "0" triggers failure, other arguments mean
// success. Certain special per-callback argument values have special meaning.

int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
// If the "echo" command is requested that will echo out the value of
Expand All @@ -20,45 +23,48 @@ int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
pam_info(pamh, "%s", getenv("TELEPORT_USERNAME"));
pam_info(pamh, "%s", getenv("TELEPORT_LOGIN"));
pam_info(pamh, "%s", getenv("TELEPORT_ROLES"));
return PAM_SUCCESS;
}

if (argc > 0 && argv[0][0] == '0') {
} else if (argc > 0 && argv[0][0] == '0') {
return PAM_ACCT_EXPIRED;
}

pam_info(pamh, "Account opened successfully.");
pam_info(pamh, "pam_sm_acct_mgmt OK");
return PAM_SUCCESS;
}

int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
int pam_err;


// If the "set_env" command is requested, set the PAM environment variable.
if (argc > 0 && strcmp(argv[0], "set_env") == 0) {
int pam_err;
pam_err = pam_putenv(pamh, argv[1]);
if (pam_err < 0) {
return PAM_SYSTEM_ERR;
}
return PAM_SUCCESS;
} else if (argc > 0 && argv[0][0] == '0') {
return PAM_SESSION_ERR;
}

pam_info(pamh, "pam_sm_open_session OK");
return PAM_SUCCESS;
}

int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
if (argc > 0 && argv[0][0] == '0') {
return PAM_SESSION_ERR;
}

pam_info(pamh, "Session open successfully.");
pam_info(pamh, "pam_sm_close_session OK");
return PAM_SUCCESS;
}

int pam_sm_close_session (pam_handle_t *pamh, int flags, int argc, const char **argv)
int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
if (argc > 0 && argv[0][0] == '0') {
return PAM_SESSION_ERR;
return PAM_AUTH_ERR;
}

pam_info(pamh, "Session closed successfully.");
pam_info(pamh, "pam_sm_authenticate OK");
return PAM_SUCCESS;
}
Binary file removed build.assets/pam/pam_teleport.so
Binary file not shown.
3 changes: 3 additions & 0 deletions build.assets/pam/policy/teleport-acct-echo
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
account required pam_teleport.so echo
auth required pam_teleport.so 1
session required pam_teleport.so 1
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
account required pam_teleport.so 0
auth required pam_teleport.so 1
session required pam_teleport.so 1
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
account required pam_teleport.so 1
auth required pam_teleport.so 0
session required pam_teleport.so 1
3 changes: 3 additions & 0 deletions build.assets/pam/policy/teleport-session-environment
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
account required pam_teleport.so 1
auth required pam_teleport.so 1
session required pam_teleport.so set_env foo=bar
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
account required pam_teleport.so 1
auth required pam_teleport.so 1
session required pam_teleport.so 0
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
account required pam_teleport.so 1
auth required pam_teleport.so 1
session required pam_teleport.so 1
1 change: 0 additions & 1 deletion build.assets/pam/teleport-acct-echo

This file was deleted.

2 changes: 0 additions & 2 deletions build.assets/pam/teleport-acct-failure

This file was deleted.

1 change: 0 additions & 1 deletion build.assets/pam/teleport-session-environment

This file was deleted.

25 changes: 14 additions & 11 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2914,16 +2914,16 @@ func (s *IntSuite) TestPAM(c *check.C) {
tr := utils.NewTracer(utils.ThisFunction()).Start()
defer tr.Stop()

// 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.
// 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 'sudo make
// install' from the build.assets/pam/ 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."
"'sudo make install' from the build.assets/pam/ directory."
c.Skip(skipMessage)
}

Expand All @@ -2943,8 +2943,10 @@ func (s *IntSuite) TestPAM(c *check.C) {
inEnabled: true,
inServiceName: "teleport-success",
outContains: []string{
"Account opened successfully.",
"Session open successfully.",
"pam_sm_acct_mgmt OK",
"pam_sm_authenticate OK",
"pam_sm_open_session OK",
"pam_sm_close_session OK",
},
},
// 2 - PAM enabled, module account functions fail.
Expand Down Expand Up @@ -3014,7 +3016,8 @@ func (s *IntSuite) TestPAM(c *check.C) {
// If any output is expected, check to make sure it was output.
if len(tt.outContains) > 0 {
for _, expectedOutput := range tt.outContains {
output := termSession.Output(100)
output := termSession.Output(1024)
c.Logf("got output: %q; want output to contain: %q", output, expectedOutput)
c.Assert(strings.Contains(output, expectedOutput), check.Equals, true)
}
}
Expand Down
17 changes: 16 additions & 1 deletion lib/pam/pam.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ func Open(config *Config) (*PAM, error) {
}
}

// Trigger the "account" PAM hooks for this login.
//
// Check that the *nix account is valid. Checking an account varies based off
// the PAM modules used in the account stack. Typically this consists of
// checking if the account is expired or has access restrictions.
Expand All @@ -309,6 +311,17 @@ func Open(config *Config) (*PAM, error) {
return nil, p.codeToError(retval)
}

// Trigger the "auth" PAM hooks for this login.
//
// These would perform any extra authentication steps configured in the PAM
// stack, like per-session 2FA.
retval = C._pam_authenticate(pamHandle, p.pamh, 0)
if retval != C.PAM_SUCCESS {
return nil, p.codeToError(retval)
}

// Trigger the "session" PAM hooks for this login.
//
// Open a user session. Opening a session varies based off the PAM modules
// used in the "session" stack. Opening a session typically consists of
// printing the MOTD, mounting a home directory, updating auth.log.
Expand Down Expand Up @@ -414,7 +427,9 @@ func (p *PAM) writeStream(stream int, s string) (int, error) {
// TODO(russjones): At some point in the future if this becomes an issue, we
// should consider supporting echo = false.
func (p *PAM) readStream(echo bool) (string, error) {
reader := bufio.NewReader(p.stdin)
// Limit the reader in case stdin is from /dev/zero or other infinite
// source.
reader := bufio.NewReader(io.LimitReader(p.stdin, int64(C.PAM_MAX_RESP_SIZE)-1))
text, err := reader.ReadString('\n')
if err != nil {
return "", trace.Wrap(err)
Expand Down
Loading

0 comments on commit 78c2a31

Please sign in to comment.