Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pam: also set teleport-specific env vars via pam_putenv #3725

Merged
merged 2 commits into from
May 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/pam/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions lib/pam/pam.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions lib/pam/pam.go
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -43,7 +44,9 @@ import "C"

import (
"bufio"
"fmt"
"io"
"os"
"strings"
"sync"
"syscall"
Expand Down Expand Up @@ -280,6 +283,22 @@ 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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C.CString does a malloc under the hood. Will PAM free this memory? If not you'll need to call C.free yourself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, nice catch, added C.free.
First time using cgo, appreciate all the advice!

// 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put pam_putenv have any maximum size? If so you might want to cap the length here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

http://www.linux-pam.org/Linux-PAM-html/adg-interface-by-app-expected.html#adg-pam_putenv doesn't mention any limits and their source code seems to allocate as much as needed.

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.
Expand Down
20 changes: 11 additions & 9 deletions lib/srv/reexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down