mirror of
https://github.com/semaphoreui/semaphore.git
synced 2025-01-20 15:29:28 +01:00
Use Stdin to pass secrets to ansible-playbook (#1911)
* feat: pass secrets via stdin * feat: use pty * feat(pty): logs * feat(secrets): works * fix(secrets): use correct ask flag of ansible playbook * test(secrets): change tests
This commit is contained in:
parent
205fe71bcb
commit
3d571c0319
@ -67,6 +67,8 @@ const (
|
||||
type AccessKeyInstallation struct {
|
||||
InstallationKey int64
|
||||
SshAgent *lib.SshAgent
|
||||
Login string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (key AccessKeyInstallation) Destroy() error {
|
||||
@ -115,8 +117,6 @@ func (key *AccessKey) Install(usage AccessKeyRole, logger lib.Logger) (installat
|
||||
return
|
||||
}
|
||||
|
||||
installationPath := installation.GetPath()
|
||||
|
||||
err = key.DeserializeSecret()
|
||||
|
||||
if err != nil {
|
||||
@ -132,44 +132,26 @@ func (key *AccessKey) Install(usage AccessKeyRole, logger lib.Logger) (installat
|
||||
installation.SshAgent = &agent
|
||||
}
|
||||
case AccessKeyRoleAnsiblePasswordVault:
|
||||
switch key.Type {
|
||||
case AccessKeyLoginPassword:
|
||||
err = os.WriteFile(installationPath, []byte(key.LoginPassword.Password), 0600)
|
||||
}
|
||||
case AccessKeyRoleAnsibleBecomeUser:
|
||||
switch key.Type {
|
||||
case AccessKeyLoginPassword:
|
||||
content := make(map[string]string)
|
||||
if len(key.LoginPassword.Login) > 0 {
|
||||
content["ansible_become_user"] = key.LoginPassword.Login
|
||||
}
|
||||
content["ansible_become_password"] = key.LoginPassword.Password
|
||||
var bytes []byte
|
||||
bytes, err = json.Marshal(content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = os.WriteFile(installationPath, bytes, 0600)
|
||||
default:
|
||||
if key.Type != AccessKeyLoginPassword {
|
||||
err = fmt.Errorf("access key type not supported for ansible user")
|
||||
}
|
||||
installation.Password = key.LoginPassword.Password
|
||||
case AccessKeyRoleAnsibleBecomeUser:
|
||||
if key.Type != AccessKeyLoginPassword {
|
||||
err = fmt.Errorf("access key type not supported for ansible user")
|
||||
}
|
||||
installation.Login = key.LoginPassword.Login
|
||||
installation.Password = key.LoginPassword.Password
|
||||
case AccessKeyRoleAnsibleUser:
|
||||
switch key.Type {
|
||||
case AccessKeySSH:
|
||||
var agent lib.SshAgent
|
||||
agent, err = key.startSshAgent(logger)
|
||||
installation.SshAgent = &agent
|
||||
installation.Login = key.LoginPassword.Login
|
||||
case AccessKeyLoginPassword:
|
||||
content := make(map[string]string)
|
||||
content["ansible_user"] = key.LoginPassword.Login
|
||||
content["ansible_password"] = key.LoginPassword.Password
|
||||
var bytes []byte
|
||||
bytes, err = json.Marshal(content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = os.WriteFile(installationPath, bytes, 0600)
|
||||
|
||||
installation.Login = key.LoginPassword.Login
|
||||
installation.Password = key.LoginPassword.Password
|
||||
default:
|
||||
err = fmt.Errorf("access key type not supported for ansible user")
|
||||
}
|
||||
|
@ -60,8 +60,8 @@ func (t *AnsibleApp) SetLogger(logger lib.Logger) lib.Logger {
|
||||
return logger
|
||||
}
|
||||
|
||||
func (t *AnsibleApp) Run(args []string, environmentVars *[]string, cb func(*os.Process)) error {
|
||||
return t.Playbook.RunPlaybook(args, environmentVars, cb)
|
||||
func (t *AnsibleApp) Run(args []string, environmentVars *[]string, inputs map[string]string, cb func(*os.Process)) error {
|
||||
return t.Playbook.RunPlaybook(args, environmentVars, inputs, cb)
|
||||
}
|
||||
|
||||
func (t *AnsibleApp) Log(msg string) {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/ansible-semaphore/semaphore/db"
|
||||
"github.com/ansible-semaphore/semaphore/lib"
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
"github.com/creack/pty"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@ -53,14 +54,41 @@ func (p AnsiblePlaybook) runCmd(command string, args []string) error {
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (p AnsiblePlaybook) RunPlaybook(args []string, environmentVars *[]string, cb func(*os.Process)) error {
|
||||
func (p AnsiblePlaybook) RunPlaybook(args []string, environmentVars *[]string, inputs map[string]string, cb func(*os.Process)) error {
|
||||
cmd := p.makeCmd("ansible-playbook", args, environmentVars)
|
||||
p.Logger.LogCmd(cmd)
|
||||
cmd.Stdin = strings.NewReader("")
|
||||
err := cmd.Start()
|
||||
|
||||
ptmx, err := pty.Start(cmd)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
||||
b := make([]byte, 100)
|
||||
|
||||
var e error
|
||||
|
||||
for {
|
||||
var n int
|
||||
n, e = ptmx.Read(b)
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
|
||||
s := strings.TrimSpace(string(b[0:n]))
|
||||
|
||||
for k, v := range inputs {
|
||||
if strings.HasPrefix(s, k) {
|
||||
_, _ = ptmx.WriteString(v + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
defer func() { _ = ptmx.Close() }()
|
||||
cb(cmd.Process)
|
||||
return cmd.Wait()
|
||||
}
|
||||
|
@ -8,5 +8,5 @@ import (
|
||||
type LocalApp interface {
|
||||
SetLogger(logger lib.Logger) lib.Logger
|
||||
InstallRequirements() error
|
||||
Run(args []string, environmentVars *[]string, cb func(*os.Process)) error
|
||||
Run(args []string, environmentVars *[]string, inputs map[string]string, cb func(*os.Process)) error
|
||||
}
|
||||
|
1
go.mod
1
go.mod
@ -5,6 +5,7 @@ go 1.21
|
||||
require (
|
||||
github.com/Masterminds/squirrel v1.5.4
|
||||
github.com/coreos/go-oidc/v3 v3.9.0
|
||||
github.com/creack/pty v1.1.21
|
||||
github.com/go-git/go-git/v5 v5.11.0
|
||||
github.com/go-gorp/gorp/v3 v3.1.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.6
|
||||
|
2
go.sum
2
go.sum
@ -22,6 +22,8 @@ github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBS
|
||||
github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
|
||||
github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -175,7 +175,11 @@ func (t *LocalJob) getTerraformArgs(username string, incomingVersion *string) (a
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func (t *LocalJob) getPlaybookArgs(username string, incomingVersion *string) (args []string, err error) {
|
||||
func (t *LocalJob) getPlaybookArgs(username string, incomingVersion *string) (args []string, inputs map[string]string, err error) {
|
||||
|
||||
inputMap := make(map[db.AccessKeyRole]string)
|
||||
inputs = make(map[string]string)
|
||||
|
||||
playbookName := t.Task.Playbook
|
||||
if playbookName == "" {
|
||||
playbookName = t.Template.Playbook
|
||||
@ -202,12 +206,17 @@ func (t *LocalJob) getPlaybookArgs(username string, incomingVersion *string) (ar
|
||||
if t.Inventory.SSHKeyID != nil {
|
||||
switch t.Inventory.SSHKey.Type {
|
||||
case db.AccessKeySSH:
|
||||
//args = append(args, "--extra-vars={\"ansible_ssh_private_key_file\": \""+t.inventory.SSHKey.GetPath()+"\"}")
|
||||
if t.Inventory.SSHKey.SshKey.Login != "" {
|
||||
args = append(args, "--extra-vars={\"ansible_user\": \""+t.Inventory.SSHKey.SshKey.Login+"\"}")
|
||||
if t.sshKeyInstallation.Login != "" {
|
||||
args = append(args, "--user", t.sshKeyInstallation.Login)
|
||||
}
|
||||
case db.AccessKeyLoginPassword:
|
||||
args = append(args, "--extra-vars=@"+t.sshKeyInstallation.GetPath())
|
||||
if t.sshKeyInstallation.Login != "" {
|
||||
args = append(args, "--user", t.sshKeyInstallation.Login)
|
||||
}
|
||||
if t.sshKeyInstallation.Password != "" {
|
||||
args = append(args, "--ask-pass")
|
||||
inputMap[db.AccessKeyRoleAnsibleUser] = t.sshKeyInstallation.Password
|
||||
}
|
||||
case db.AccessKeyNone:
|
||||
default:
|
||||
err = fmt.Errorf("access key does not suite for inventory's user credentials")
|
||||
@ -218,7 +227,13 @@ func (t *LocalJob) getPlaybookArgs(username string, incomingVersion *string) (ar
|
||||
if t.Inventory.BecomeKeyID != nil {
|
||||
switch t.Inventory.BecomeKey.Type {
|
||||
case db.AccessKeyLoginPassword:
|
||||
args = append(args, "--extra-vars=@"+t.becomeKeyInstallation.GetPath())
|
||||
if t.sshKeyInstallation.Login != "" {
|
||||
args = append(args, "--user", t.becomeKeyInstallation.Login)
|
||||
}
|
||||
if t.becomeKeyInstallation.Password != "" {
|
||||
args = append(args, "--ask-become-pass")
|
||||
inputMap[db.AccessKeyRoleAnsibleBecomeUser] = t.sshKeyInstallation.Password
|
||||
}
|
||||
case db.AccessKeyNone:
|
||||
default:
|
||||
err = fmt.Errorf("access key does not suite for inventory's sudo user credentials")
|
||||
@ -239,7 +254,8 @@ func (t *LocalJob) getPlaybookArgs(username string, incomingVersion *string) (ar
|
||||
}
|
||||
|
||||
if t.Template.VaultKeyID != nil {
|
||||
args = append(args, "--vault-password-file", t.vaultFileInstallation.GetPath())
|
||||
args = append(args, "--ask-vault-pass")
|
||||
inputMap[db.AccessKeyRoleAnsiblePasswordVault] = t.vaultFileInstallation.Password
|
||||
}
|
||||
|
||||
extraVars, err := t.getEnvironmentExtraVarsJSON(username, incomingVersion)
|
||||
@ -277,6 +293,18 @@ func (t *LocalJob) getPlaybookArgs(username string, incomingVersion *string) (ar
|
||||
args = append(args, taskExtraArgs...)
|
||||
args = append(args, playbookName)
|
||||
|
||||
if line, ok := inputMap[db.AccessKeyRoleAnsibleUser]; ok {
|
||||
inputs["SSH password:"] = line
|
||||
}
|
||||
|
||||
if line, ok := inputMap[db.AccessKeyRoleAnsibleBecomeUser]; ok {
|
||||
inputs["BECOME password"] = line
|
||||
}
|
||||
|
||||
if line, ok := inputMap[db.AccessKeyRoleAnsiblePasswordVault]; ok {
|
||||
inputs["Vault password:"] = line
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -294,10 +322,11 @@ func (t *LocalJob) Run(username string, incomingVersion *string) (err error) {
|
||||
}()
|
||||
|
||||
var args []string
|
||||
var inputs map[string]string
|
||||
|
||||
switch t.Template.App {
|
||||
case db.TemplateAnsible:
|
||||
args, err = t.getPlaybookArgs(username, incomingVersion)
|
||||
args, inputs, err = t.getPlaybookArgs(username, incomingVersion)
|
||||
default:
|
||||
panic("unknown template app")
|
||||
}
|
||||
@ -315,7 +344,7 @@ func (t *LocalJob) Run(username string, incomingVersion *string) (err error) {
|
||||
environmentVariables = append(environmentVariables, fmt.Sprintf("SSH_AUTH_SOCK=%s", t.sshKeyInstallation.SshAgent.SocketFile))
|
||||
}
|
||||
|
||||
return t.App.Run(args, &environmentVariables, func(p *os.Process) {
|
||||
return t.App.Run(args, &environmentVariables, inputs, func(p *os.Process) {
|
||||
t.Process = p
|
||||
})
|
||||
|
||||
|
@ -299,7 +299,7 @@ func TestTaskGetPlaybookArgs(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
args, err := tsk.job.(*LocalJob).getPlaybookArgs("", nil)
|
||||
args, _, err := tsk.job.(*LocalJob).getPlaybookArgs("", nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -355,14 +355,14 @@ func TestTaskGetPlaybookArgs2(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
args, err := tsk.job.(*LocalJob).getPlaybookArgs("", nil)
|
||||
args, _, err := tsk.job.(*LocalJob).getPlaybookArgs("", nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res := strings.Join(args, " ")
|
||||
if res != "-i /tmp/inventory_0 --extra-vars=@/tmp/access_key_0 --extra-vars {\"semaphore_vars\":{\"task_details\":{\"id\":0,\"username\":\"\"}}} test.yml" {
|
||||
if res != "-i /tmp/inventory_0 --extra-vars {\"semaphore_vars\":{\"task_details\":{\"id\":0,\"username\":\"\"}}} test.yml" {
|
||||
t.Fatal("incorrect result")
|
||||
}
|
||||
}
|
||||
@ -411,14 +411,14 @@ func TestTaskGetPlaybookArgs3(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
args, err := tsk.job.(*LocalJob).getPlaybookArgs("", nil)
|
||||
args, _, err := tsk.job.(*LocalJob).getPlaybookArgs("", nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res := strings.Join(args, " ")
|
||||
if res != "-i /tmp/inventory_0 --extra-vars=@/tmp/access_key_0 --extra-vars {\"semaphore_vars\":{\"task_details\":{\"id\":0,\"username\":\"\"}}} test.yml" {
|
||||
if res != "-i /tmp/inventory_0 --extra-vars {\"semaphore_vars\":{\"task_details\":{\"id\":0,\"username\":\"\"}}} test.yml" {
|
||||
t.Fatal("incorrect result")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user