feat(ansible): support seeded password lookups
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
28ef1f3d85
commit
c276c343bc
2 changed files with 57 additions and 6 deletions
46
executor.go
46
executor.go
|
|
@ -3,11 +3,14 @@ package ansible
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"maps"
|
"maps"
|
||||||
|
mathrand "math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -3628,7 +3631,7 @@ func (e *Executor) lookupValue(expr string, host string, task *Task) (any, bool)
|
||||||
return value, true
|
return value, true
|
||||||
}
|
}
|
||||||
case "password":
|
case "password":
|
||||||
if value, ok := e.lookupPassword(arg); ok {
|
if value, ok := e.lookupPassword(arg, host, task); ok {
|
||||||
return value, true
|
return value, true
|
||||||
}
|
}
|
||||||
case "first_found":
|
case "first_found":
|
||||||
|
|
@ -3746,18 +3749,22 @@ func firstFoundTerms(value any) ([]string, []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) lookupPassword(arg string) (string, bool) {
|
func (e *Executor) lookupPassword(arg string, host string, task *Task) (string, bool) {
|
||||||
spec := parsePasswordLookupSpec(arg)
|
spec := parsePasswordLookupSpec(arg)
|
||||||
|
spec.path = e.resolveLookupPasswordValue(spec.path, host, task)
|
||||||
|
spec.seed = e.resolveLookupPasswordValue(spec.seed, host, task)
|
||||||
if spec.path == "" {
|
if spec.path == "" {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvedPath := e.resolveLocalPath(spec.path)
|
resolvedPath := e.resolveLocalPath(spec.path)
|
||||||
|
if resolvedPath != "/dev/null" {
|
||||||
if data, err := coreio.Local.Read(resolvedPath); err == nil {
|
if data, err := coreio.Local.Read(resolvedPath); err == nil {
|
||||||
return strings.TrimRight(data, "\r\n"), true
|
return strings.TrimRight(data, "\r\n"), true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
password, err := generatePassword(spec.length, spec.chars)
|
password, err := generatePassword(spec.length, spec.chars, spec.seed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
@ -3774,10 +3781,28 @@ func (e *Executor) lookupPassword(arg string) (string, bool) {
|
||||||
return password, true
|
return password, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Executor) resolveLookupPasswordValue(value string, host string, task *Task) string {
|
||||||
|
value = corexTrimSpace(value)
|
||||||
|
if value == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if resolved, ok := e.lookupConditionValue(value, host, task, nil); ok {
|
||||||
|
return sprintf("%v", resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(value, "{{") {
|
||||||
|
return e.templateString(value, host, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
type passwordLookupSpec struct {
|
type passwordLookupSpec struct {
|
||||||
path string
|
path string
|
||||||
length int
|
length int
|
||||||
chars string
|
chars string
|
||||||
|
seed string
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePasswordLookupSpec(arg string) passwordLookupSpec {
|
func parsePasswordLookupSpec(arg string) passwordLookupSpec {
|
||||||
|
|
@ -3799,6 +3824,8 @@ func parsePasswordLookupSpec(arg string) passwordLookupSpec {
|
||||||
if chars := passwordLookupCharset(value); chars != "" {
|
if chars := passwordLookupCharset(value); chars != "" {
|
||||||
spec.chars = chars
|
spec.chars = chars
|
||||||
}
|
}
|
||||||
|
case "seed":
|
||||||
|
spec.seed = value
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -3848,7 +3875,7 @@ func passwordLookupCharset(value string) string {
|
||||||
return chars.String()
|
return chars.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func generatePassword(length int, chars string) (string, error) {
|
func generatePassword(length int, chars, seed string) (string, error) {
|
||||||
if length <= 0 {
|
if length <= 0 {
|
||||||
length = 20
|
length = 20
|
||||||
}
|
}
|
||||||
|
|
@ -3856,6 +3883,17 @@ func generatePassword(length int, chars string) (string, error) {
|
||||||
chars = passwordLookupCharset("ascii_letters,digits")
|
chars = passwordLookupCharset("ascii_letters,digits")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if seed != "" {
|
||||||
|
sum := sha256.Sum256([]byte(seed))
|
||||||
|
seedValue := int64(binary.LittleEndian.Uint64(sum[:8]))
|
||||||
|
r := mathrand.New(mathrand.NewSource(seedValue))
|
||||||
|
buf := make([]byte, length)
|
||||||
|
for i := range buf {
|
||||||
|
buf[i] = chars[r.Intn(len(chars))]
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
buf := make([]byte, length)
|
buf := make([]byte, length)
|
||||||
if _, err := rand.Read(buf); err != nil {
|
if _, err := rand.Read(buf); err != nil {
|
||||||
return "", coreerr.E("Executor.lookupPassword", "generate password", err)
|
return "", coreerr.E("Executor.lookupPassword", "generate password", err)
|
||||||
|
|
|
||||||
|
|
@ -947,6 +947,19 @@ func TestExecutorExtra_HandleLookup_Good_PasswordLookupCreatesFile(t *testing.T)
|
||||||
assert.Len(t, content, 12)
|
assert.Len(t, content, 12)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExecutorExtra_HandleLookup_Good_PasswordLookupHonoursSeed(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
e := NewExecutor(dir)
|
||||||
|
|
||||||
|
first := e.handleLookup("lookup('password', '/dev/null length=16 chars=ascii_lowercase seed=inventory_hostname')", "host1", nil)
|
||||||
|
second := e.handleLookup("lookup('password', '/dev/null length=16 chars=ascii_lowercase seed=inventory_hostname')", "host1", nil)
|
||||||
|
other := e.handleLookup("lookup('password', '/dev/null length=16 chars=ascii_lowercase seed=inventory_hostname')", "host2", nil)
|
||||||
|
|
||||||
|
assert.Len(t, first, 16)
|
||||||
|
assert.Equal(t, first, second)
|
||||||
|
assert.NotEqual(t, first, other)
|
||||||
|
}
|
||||||
|
|
||||||
func TestExecutorExtra_HandleLookup_Good_FirstFoundLookupReturnsFirstExistingPath(t *testing.T) {
|
func TestExecutorExtra_HandleLookup_Good_FirstFoundLookupReturnsFirstExistingPath(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
require.NoError(t, writeTestFile(joinPath(dir, "defaults", "common.yml"), []byte("common: true\n"), 0644))
|
require.NoError(t, writeTestFile(joinPath(dir, "defaults", "common.yml"), []byte("common: true\n"), 0644))
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue