7 Commits

Author SHA1 Message Date
c89dde186a UserCommands: change field name
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-03-11 15:37:12 -05:00
18a64de0de UserCommands: change field name 2025-03-11 15:36:43 -05:00
99c622b69f UserCommands: add field CreateUserHome 2025-03-11 15:30:07 -05:00
95e85e8b45 UserCommands: add ssh public keys when running locally
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-03-11 15:21:02 -05:00
1a48c7bca5 change: create temp file when modifing password over SSH 2025-03-11 14:55:02 -05:00
5d21764ef1 fix: don't test empty env files
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-03-11 13:42:40 -05:00
c7302f0e17 update docs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-03-10 12:34:33 -05:00
12 changed files with 110 additions and 16 deletions

View File

@ -0,0 +1,3 @@
kind: Added
body: 'UserCommands: add ssh public keys when running locally'
time: 2025-03-11T15:20:28.487596157-05:00

View File

@ -0,0 +1,3 @@
kind: Added
body: 'UserCommands: add field CreateUserHome'
time: 2025-03-11T15:30:26.824884876-05:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: 'UserCommands: create temp file when modifing password over SSH'
time: 2025-03-11T14:54:10.720370135-05:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: 'UserCommands: change field name'
time: 2025-03-11T15:36:19.802011559-05:00

View File

@ -6,16 +6,18 @@ description: This is dedicated to user commands.
This is dedicated to `user` commands. The command `type` field must be `user`. User is a type that allows one to perform user operations. There are several additional options available when `type` is `user`: This is dedicated to `user` commands. The command `type` field must be `user`. User is a type that allows one to perform user operations. There are several additional options available when `type` is `user`:
| name | notes | type | required | | name | notes | type | required | External directive support
| --- | --- | --- | --- | | ----------------| -------------------------------------------------------------| ---------- | ---------| --------------------------|
| `userName` | The name of a user to be configured. | `string` | yes | | `userName` | The name of a user to be configured. | `string` | yes | no |
| `userOperation` | The type of operation to perform. | `string` | yes | | `userOperation` | The type of operation to perform. | `string` | yes | no |
| `userID` | The user ID to use. | `string` | no | | `userID` | The user ID to use. | `string` | no | no |
| `userGroups` | The groups the user should be added to. | `[]string` | no | | `userGroups` | The groups the user should be added to. | `[]string` | no | no |
| `userSshPubKeys` | The keys to add to the user's authorized keys. | `[]string` | no | | `systemUser` | Create a system user. | `bool` | no | no |
| `userShell` | The shell for the user. | `string` | no | | `userCreateHome`| Create the home directory. | `bool` | no | no |
| `userHome` | The user's home directory. | `string` | no | | `userSshPubKeys`| The keys to add to the user's authorized keys. | `[]string` | no | yes |
| `userPassword` | The new password value when using the `password` operation. Can be specified by using external directive. | `string` | no | | `userShell` | The shell for the user. | `string` | no | no |
| `userHome` | The user's home directory. | `string` | no | no |
| `userPassword` | The new password value when using the `password` operation. | `string` | no | yes |
#### example #### example

View File

@ -11,6 +11,7 @@ import (
"io" "io"
"os" "os"
"os/exec" "os/exec"
"strings"
"text/template" "text/template"
"embed" "embed"
@ -194,6 +195,57 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send() cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send()
return outputArr, err return outputArr, err
} }
if command.Type == UserCT {
if command.UserOperation == "add" {
if command.UserSshPubKeys != nil {
var (
f *os.File
err error
userHome []byte
)
cmdCtxLogger.Info().Msg("adding SSH Keys")
localCMD := exec.Command(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username))
userHome, err = localCMD.CombinedOutput()
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err)
}
command.UserHome = strings.TrimSpace(string(userHome))
userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome)
os.MkdirAll(userSshDir, 0700)
if _, err := os.Stat(fmt.Sprintf("%s/authorized_keys", userSshDir)); os.IsNotExist(err) {
_, err := os.Create(fmt.Sprintf("%s/authorized_keys", userSshDir))
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating file %s/authorized_keys: %v", userSshDir, err)
}
}
f, err = os.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), 0700, os.ModeAppend)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
}
defer f.Close()
for _, k := range command.UserSshPubKeys {
buf := bytes.NewBufferString(k)
cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key")
if _, err := f.ReadFrom(buf); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error adding to authorized keys: %v", err)
}
}
localCMD = exec.Command(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome))
_, err = localCMD.CombinedOutput()
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), err
}
}
}
}
} }
return outputArr, nil return outputArr, nil
} }

View File

@ -248,6 +248,9 @@ func unmarshalConfig(k *koanf.Koanf, key string, target interface{}, log zerolog
func getCommandEnvironments(opts *ConfigOpts) { func getCommandEnvironments(opts *ConfigOpts) {
for cmdName, cmdConf := range opts.Cmds { for cmdName, cmdConf := range opts.Cmds {
if cmdConf.Env == "" {
continue
}
opts.Logger.Debug().Str("env file", cmdConf.Env).Str("cmd", cmdName).Send() opts.Logger.Debug().Str("env file", cmdConf.Env).Str("cmd", cmdName).Send()
if err := testFile(cmdConf.Env); err != nil { if err := testFile(cmdConf.Env); err != nil {
logging.ExitWithMSG("Could not open file"+cmdConf.Env+": "+err.Error(), 1, &opts.Logger) logging.ExitWithMSG("Could not open file"+cmdConf.Env+": "+err.Error(), 1, &opts.Logger)

View File

@ -15,6 +15,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/google/uuid"
"github.com/kevinburke/ssh_config" "github.com/kevinburke/ssh_config"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/pkg/sftp" "github.com/pkg/sftp"
@ -509,9 +510,25 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
if command.Type == UserCT && command.UserOperation == "password" { if command.Type == UserCT && command.UserOperation == "password" {
// cmdCtxLogger.Debug().Msgf("adding stdin") // cmdCtxLogger.Debug().Msgf("adding stdin")
userNamePass := fmt.Sprintf("%s:%s", command.Username, command.UserPassword)
ArgsStr = fmt.Sprintf("echo %s | chpasswd", userNamePass)
userNamePass := fmt.Sprintf("%s:%s", command.Username, command.UserPassword)
client, err := sftp.NewClient(command.RemoteHost.SshClient)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating sftp client: %v", err)
}
uuidFile := uuid.New()
passFilePath := fmt.Sprintf("/tmp/%s", uuidFile.String())
passFile, passFileErr := client.Create(passFilePath)
if passFileErr != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating file /tmp/%s: %v", uuidFile.String(), passFileErr)
}
passFile.Write([]byte(userNamePass))
ArgsStr = fmt.Sprintf("cat %s | chpasswd", passFilePath)
defer passFile.Close()
defer client.Remove(passFilePath)
// commandSession.Stdin = command.stdin // commandSession.Stdin = command.stdin
} }
if err := commandSession.Run(ArgsStr); err != nil { if err := commandSession.Run(ArgsStr); err != nil {

View File

@ -115,7 +115,9 @@ type (
UserShell string `yaml:"userShell,omitempty"` UserShell string `yaml:"userShell,omitempty"`
SystemUser bool `yaml:"systemUser,omitempty"` UserCreateHome bool `yaml:"userCreateHome,omitempty"`
UserIsSystem bool `yaml:"userIsSystem,omitempty"`
UserPassword string `yaml:"userPassword,omitempty"` UserPassword string `yaml:"userPassword,omitempty"`

View File

@ -293,7 +293,8 @@ func getCommandTypeAndSetCommandInfo(command *Command) *Command {
command.Username, command.Username,
command.UserHome, command.UserHome,
command.UserShell, command.UserShell,
command.SystemUser, command.UserIsSystem,
command.UserCreateHome,
command.UserGroups, command.UserGroups,
command.Args) command.Args)
case "modify": case "modify":

View File

@ -15,7 +15,7 @@ func (l LinuxUserManager) NewLinuxManager() *LinuxUserManager {
} }
// AddUser adds a new user to the system. // AddUser adds a new user to the system.
func (l LinuxUserManager) AddUser(username, homeDir, shell string, isSystem bool, groups, args []string) (string, []string) { func (l LinuxUserManager) AddUser(username, homeDir, shell string, createHome, isSystem bool, groups, args []string) (string, []string) {
baseArgs := []string{} baseArgs := []string{}
if isSystem { if isSystem {
@ -38,6 +38,11 @@ func (l LinuxUserManager) AddUser(username, homeDir, shell string, isSystem bool
baseArgs = append(baseArgs, args...) baseArgs = append(baseArgs, args...)
} }
if createHome {
baseArgs = append(baseArgs, "-m")
}
args = append(baseArgs, username) args = append(baseArgs, username)
cmd := "useradd" cmd := "useradd"

View File

@ -10,7 +10,7 @@ import (
// UserManager defines the interface for user management operations. // UserManager defines the interface for user management operations.
// All functions but one return a string for the command and any args. // All functions but one return a string for the command and any args.
type UserManager interface { type UserManager interface {
AddUser(username, homeDir, shell string, isSystem bool, groups, args []string) (string, []string) AddUser(username, homeDir, shell string, createHome, isSystem bool, groups, args []string) (string, []string)
RemoveUser(username string) (string, []string) RemoveUser(username string) (string, []string)
ModifyUser(username, homeDir, shell string, groups []string) (string, []string) ModifyUser(username, homeDir, shell string, groups []string) (string, []string)
// Modify password uses chpasswd for Linux systems to build the command to change the password // Modify password uses chpasswd for Linux systems to build the command to change the password