From d09f5adfb06ca6d1d97e2bbe4ae2947b6d9ff611 Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Fri, 15 May 2020 16:16:03 -0700 Subject: [PATCH 1/2] pam: also set teleport-specific env vars via pam_putenv Using `pam_putenv` from `libpam` exposes these env vars to `pam_exec.so` and possibly other built-in PAM modules. Keep setting them via `os.Setenv` too, for `pam_script.so` to use. Updates #3692 --- lib/pam/config.go | 3 +++ lib/pam/pam.c | 12 ++++++++++++ lib/pam/pam.go | 17 +++++++++++++++++ lib/srv/reexec.go | 20 +++++++++++--------- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/lib/pam/config.go b/lib/pam/config.go index 1ba1a2f8648ef..d90002da06ad7 100644 --- a/lib/pam/config.go +++ b/lib/pam/config.go @@ -36,6 +36,9 @@ type Config struct { // Login is the *nix login that that is being used. Login string `json:"login"` + // Env is a list of extra environment variables to pass to the PAM modules. + Env map[string]string + // Stdin is the input stream which the conversation function will use to // obtain data from the user. Stdin io.Reader diff --git a/lib/pam/pam.c b/lib/pam/pam.c index aa93308942c61..4b8567c504ff4 100644 --- a/lib/pam/pam.c +++ b/lib/pam/pam.c @@ -159,6 +159,18 @@ int _pam_end(void *handle, pam_handle_t *pamh, int pam_status) return (f)(pamh, pam_status); } +int _pam_putenv(void *handle, pam_handle_t *pamh, const char *name_value) +{ + int (*f)(pam_handle_t *, const char *); + + f = dlsym(handle, "pam_putenv"); + if (f == NULL) { + return PAM_ABORT; + } + + return (f)(pamh, name_value); +} + int _pam_authenticate(void *handle, pam_handle_t *pamh, int flags) { int (*f)(pam_handle_t *, int); diff --git a/lib/pam/pam.go b/lib/pam/pam.go index 7856045e529b1..243f04eaf7714 100644 --- a/lib/pam/pam.go +++ b/lib/pam/pam.go @@ -30,6 +30,7 @@ package pam // extern void writeCallback(int n, int s, char* c); // extern struct pam_conv *make_pam_conv(int); // extern int _pam_start(void *, const char *, const char *, const struct pam_conv *, pam_handle_t **); +// extern int _pam_putenv(void *, pam_handle_t *, const char *); // extern int _pam_end(void *, pam_handle_t *, int); // extern int _pam_authenticate(void *, pam_handle_t *, int); // extern int _pam_acct_mgmt(void *, pam_handle_t *, int); @@ -43,7 +44,9 @@ import "C" import ( "bufio" + "fmt" "io" + "os" "strings" "sync" "syscall" @@ -280,6 +283,20 @@ func Open(config *Config) (*PAM, error) { return nil, p.codeToError(p.retval) } + for k, v := range config.Env { + // Set a regular OS env var on this process which should be available + // to child PAM processes. + os.Setenv(k, v) + + // Also set it via PAM-specific pam_putenv, which is respected by + // pam_exec (and possibly others), where parent env vars are not. + kv := C.CString(fmt.Sprintf("%s=%s", k, v)) + retval := C._pam_putenv(pamHandle, p.pamh, kv) + if retval != C.PAM_SUCCESS { + return nil, p.codeToError(retval) + } + } + // 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. diff --git a/lib/srv/reexec.go b/lib/srv/reexec.go index f2352267e35b8..4fda0caf647b0 100644 --- a/lib/srv/reexec.go +++ b/lib/srv/reexec.go @@ -157,19 +157,21 @@ func RunCommand() (io.Writer, int, error) { stderr = ioutil.Discard } - // Set Teleport specific environment variables that PAM modules like - // pam_script.so can pick up to potentially customize the account/session. - os.Setenv("TELEPORT_USERNAME", c.Username) - os.Setenv("TELEPORT_LOGIN", c.Login) - os.Setenv("TELEPORT_ROLES", strings.Join(c.Roles, " ")) - // Open the PAM context. pamContext, err := pam.Open(&pam.Config{ ServiceName: c.ServiceName, Login: c.Login, - Stdin: stdin, - Stdout: stdout, - Stderr: stderr, + // Set Teleport specific environment variables that PAM modules + // like pam_script.so can pick up to potentially customize the + // account/session. + Env: map[string]string{ + "TELEPORT_USERNAME": c.Username, + "TELEPORT_LOGIN": c.Login, + "TELEPORT_ROLES": strings.Join(c.Roles, " "), + }, + Stdin: stdin, + Stdout: stdout, + Stderr: stderr, }) if err != nil { return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) From 355019072ee09080087c3a79ddd77a209a43e054 Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Mon, 18 May 2020 12:36:41 -0700 Subject: [PATCH 2/2] pam: free the CString allocated for pam_putenv --- lib/pam/pam.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pam/pam.go b/lib/pam/pam.go index 243f04eaf7714..65f92bfa0b6ac 100644 --- a/lib/pam/pam.go +++ b/lib/pam/pam.go @@ -291,6 +291,8 @@ func Open(config *Config) (*PAM, error) { // Also set it via PAM-specific pam_putenv, which is respected by // pam_exec (and possibly others), where parent env vars are not. kv := C.CString(fmt.Sprintf("%s=%s", k, v)) + // pam_putenv makes a copy of kv, so we can free it right away. + defer C.free(unsafe.Pointer(kv)) retval := C._pam_putenv(pamHandle, p.pamh, kv) if retval != C.PAM_SUCCESS { return nil, p.codeToError(retval)