-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Remove expirations for managed users #47774
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |
| package host | ||
|
|
||
| import ( | ||
| "bufio" | ||
| "bytes" | ||
| "errors" | ||
| "os" | ||
|
|
@@ -195,6 +196,82 @@ func GetAllUsers() ([]string, int, error) { | |
| return users, -1, nil | ||
| } | ||
|
|
||
| // UserHasExpirations determines if the given username has an expired password, inactive password, or expired account | ||
| // by parsing the output of 'chage -l <username>'. | ||
| func UserHasExpirations(username string) (bool bool, exitCode int, err error) { | ||
|
eriktate marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like this is following the pattern of other functions in this file of returning an exit code when shelling out to an external binary. However, I wonder if that makes sense from an API perspective. These functions don't expose to users which binaries were being invoked, and are instead meant to abstract away the fact that some other binary is being used. By returning the exit code, we leak that information to callers, without giving them much insight into what the values of the exit codes might mean.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I agree that the exit codes aren't especially useful for any of these functions. I'll submit a separate PR updating |
||
| chageBin, err := exec.LookPath("chage") | ||
| if err != nil { | ||
| return false, -1, trace.NotFound("cannot find chage binary: %s", err) | ||
| } | ||
|
|
||
| stdout := new(bytes.Buffer) | ||
| stderr := new(bytes.Buffer) | ||
| cmd := exec.Command(chageBin, "-l", username) | ||
| cmd.Stdout = stdout | ||
|
eriktate marked this conversation as resolved.
|
||
| cmd.Stderr = stderr | ||
| if err := cmd.Run(); err != nil { | ||
| return false, cmd.ProcessState.ExitCode(), trace.WrapWithMessage(err, "running chage: %s", stderr.String()) | ||
| } | ||
|
|
||
| scanner := bufio.NewScanner(stdout) | ||
| for scanner.Scan() { | ||
| line := scanner.Text() | ||
| if line == "" { | ||
| // ignore empty lines | ||
| continue | ||
| } | ||
|
|
||
| key, value, validLine := strings.Cut(line, ":") | ||
| if !validLine { | ||
| return false, -1, trace.Errorf("chage output invalid") | ||
| } | ||
|
|
||
| if strings.TrimSpace(value) == "never" { | ||
| continue | ||
| } | ||
|
|
||
| switch strings.TrimSpace(key) { | ||
| case "Password expires", "Password inactive", "Account expires": | ||
| return true, 0, nil | ||
| } | ||
| } | ||
|
|
||
| return false, cmd.ProcessState.ExitCode(), nil | ||
| } | ||
|
|
||
| // RemoveUserExpirations uses chage to remove any future or past expirations associated with the given username. It also uses usermod to remove any account locks that may have been placed. | ||
| func RemoveUserExpirations(username string) (exitCode int, err error) { | ||
|
eriktate marked this conversation as resolved.
|
||
| chageBin, err := exec.LookPath("chage") | ||
| if err != nil { | ||
| return -1, trace.NotFound("cannot find chage binary: %s", err) | ||
| } | ||
|
|
||
| usermodBin, err := exec.LookPath("usermod") | ||
| if err != nil { | ||
| return -1, trace.NotFound("cannot find usermod binary: %s", err) | ||
| } | ||
|
|
||
| // remove all expirations from user | ||
| // chage -E -1 -I -1 <username> | ||
| cmd := exec.Command(chageBin, "-E", "-1", "-I", "-1", "-M", "-1", username) | ||
| var errs []error | ||
| if err := cmd.Run(); err != nil { | ||
| errs = append(errs, trace.Wrap(err, "removing expirations with chage")) | ||
| } | ||
|
|
||
| // unlock user password if locked | ||
| cmd = exec.Command(usermodBin, "-U", username) | ||
| if err := cmd.Run(); err != nil { | ||
| errs = append(errs, trace.Wrap(err, "removing lock with usermod")) | ||
| } | ||
|
|
||
| if len(errs) > 0 { | ||
| return cmd.ProcessState.ExitCode(), trace.NewAggregate(errs...) | ||
| } | ||
|
|
||
| return cmd.ProcessState.ExitCode(), nil | ||
| } | ||
|
|
||
| var ErrInvalidSudoers = errors.New("visudo: invalid sudoers file") | ||
|
|
||
| // CheckSudoers tests a suders file using `visudo`. The contents | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.