mirror of
https://github.com/Bornholm/formidable.git
synced 2024-12-22 23:09:33 +01:00
feat: ansible-vault values auto retagging in yaml format
Using the YAML encoder, Formidable is now capable of detecting et retagging ansible-vault [1] encrypted values. You can use the query parameter 'ansible_vault=no' to disable this behavior. [1] https://docs.ansible.com/ansible/latest/user_guide/vault.html
This commit is contained in:
parent
bd70c1b91a
commit
e6258f37ac
9
Makefile
9
Makefile
@ -7,6 +7,7 @@ GORELEASER_VERSION ?= v1.8.3
|
||||
GORELEASER_ARGS ?= --auto-snapshot --rm-dist
|
||||
GITCHLOG_ARGS ?=
|
||||
SHELL := /bin/bash
|
||||
RUN_INSTALL_TESTS ?= yes
|
||||
|
||||
.PHONY: help
|
||||
help: ## Display this help
|
||||
@ -15,8 +16,12 @@ help: ## Display this help
|
||||
watch: deps ## Watching updated files - live reload
|
||||
( set -o allexport && source .env && set +o allexport && go run -mod=readonly github.com/cortesi/modd/cmd/modd@latest )
|
||||
|
||||
.PHONY: help
|
||||
test: test-go test-install-script ## Executing tests
|
||||
.PHONY: test
|
||||
test: test-go ## Executing tests
|
||||
|
||||
ifeq ($(RUN_INSTALL_TESTS), yes)
|
||||
test: test-install-script
|
||||
endif
|
||||
|
||||
test-go: deps
|
||||
( set -o allexport && source .env && set +o allexport && go test -v -race -count=1 $(GOTEST_ARGS) ./... )
|
||||
|
@ -6,12 +6,15 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"forge.cadoles.com/wpetit/formidable/internal/data/format"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const YAMLTagAnsibleVaultValuesQueryParam = "ansible_vault"
|
||||
|
||||
type EncoderHandler struct{}
|
||||
|
||||
func (d *EncoderHandler) Match(url *url.URL) bool {
|
||||
@ -22,17 +25,69 @@ func (d *EncoderHandler) Match(url *url.URL) bool {
|
||||
}
|
||||
|
||||
func (d *EncoderHandler) Encode(url *url.URL, data interface{}) (io.Reader, error) {
|
||||
var buf bytes.Buffer
|
||||
var output bytes.Buffer
|
||||
|
||||
encoder := yaml.NewEncoder(&buf)
|
||||
encoder := yaml.NewEncoder(&output)
|
||||
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return &buf, nil
|
||||
if shouldTransformAnsibleVault(url) {
|
||||
if err := tagAnsibleVaultValues(&output); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return &output, nil
|
||||
}
|
||||
|
||||
func NewEncoderHandler() *EncoderHandler {
|
||||
return &EncoderHandler{}
|
||||
}
|
||||
|
||||
func shouldTransformAnsibleVault(url *url.URL) bool {
|
||||
return !url.Query().Has(YAMLTagAnsibleVaultValuesQueryParam) || url.Query().Get(YAMLTagAnsibleVaultValuesQueryParam) == "yes"
|
||||
}
|
||||
|
||||
func tagAnsibleVaultValues(buf *bytes.Buffer) error {
|
||||
decoder := yaml.NewDecoder(buf)
|
||||
|
||||
var node yaml.Node
|
||||
|
||||
if err := decoder.Decode(&node); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
walkNodeTree(&node, func(node *yaml.Node) {
|
||||
isAnsibleVaultNode := node.Tag == "!!str" && strings.HasPrefix(strings.TrimSpace(node.Value), "$ANSIBLE_VAULT")
|
||||
if !isAnsibleVaultNode {
|
||||
return
|
||||
}
|
||||
|
||||
node.Tag = "!vault"
|
||||
node.Style = yaml.LiteralStyle | yaml.TaggedStyle
|
||||
})
|
||||
|
||||
buf.Reset()
|
||||
|
||||
encoder := yaml.NewEncoder(buf)
|
||||
|
||||
if err := encoder.Encode(node.Content[0]); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func walkNodeTree(node *yaml.Node, fn func(node *yaml.Node)) {
|
||||
fn(node)
|
||||
|
||||
if node.Content == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, sub := range node.Content {
|
||||
walkNodeTree(sub, fn)
|
||||
}
|
||||
}
|
||||
|
88
internal/data/format/yaml/encoder_test.go
Normal file
88
internal/data/format/yaml/encoder_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// YAML string containing an ansible-vault encrypted variable
|
||||
const ansibleVaultYAML = `
|
||||
unencrypted: foo
|
||||
encrypted: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
63393636613562663937383964323839376239663230366130386566393131313963386265303632
|
||||
3133356532346437653338343032303732646530303431660a383862353766326334306138613734
|
||||
36313438626564623435373365616531353533663765663335616134656430323134323537336661
|
||||
3437653863343331370a393136653735643333373962633631663539653664313936303964303866
|
||||
3933
|
||||
`
|
||||
|
||||
func TestEncoderAnsibleVault(t *testing.T) {
|
||||
_, err := exec.LookPath("ansible")
|
||||
if err != nil {
|
||||
t.Skip("The 'ansible' command seems not to be available on this system. Skipping.")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var data interface{}
|
||||
|
||||
if err := yaml.Unmarshal([]byte(ansibleVaultYAML), &data); err != nil {
|
||||
t.Fatal(errors.WithStack(err))
|
||||
}
|
||||
|
||||
encoder := NewEncoderHandler()
|
||||
|
||||
url, err := url.Parse("stdout://local.yml?ansible_vault=yes")
|
||||
if err != nil {
|
||||
t.Fatal(errors.WithStack(err))
|
||||
}
|
||||
|
||||
reader, err := encoder.Encode(url, data)
|
||||
if err != nil {
|
||||
t.Fatal(errors.WithStack(err))
|
||||
}
|
||||
|
||||
temp, err := os.CreateTemp(os.TempDir(), "formidable_test_*.yml")
|
||||
if err != nil {
|
||||
t.Fatal(errors.WithStack(err))
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := os.Remove(temp.Name()); err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
}()
|
||||
|
||||
t.Logf("Writing encoded YAML content in file '%s'...", temp.Name())
|
||||
|
||||
if _, err := io.Copy(temp, reader); err != nil {
|
||||
t.Fatal(errors.WithStack(err))
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"localhost",
|
||||
"-m", "debug",
|
||||
"--vault-password-file", "./testdata/vault.txt",
|
||||
"-e", fmt.Sprintf("@%s", temp.Name()),
|
||||
"-a", "var=encrypted",
|
||||
}
|
||||
|
||||
t.Logf("Running command 'ansible %s'", strings.Join(args, " "))
|
||||
|
||||
cmd := exec.Command("ansible", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatal(errors.WithStack(err))
|
||||
}
|
||||
}
|
1
internal/data/format/yaml/testdata/vault.txt
vendored
Normal file
1
internal/data/format/yaml/testdata/vault.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
formidable
|
@ -1,9 +1,13 @@
|
||||
{{define "form_input_string"}}
|
||||
{{ $fullProperty := getFullProperty .Parent .Property }}
|
||||
{{ $value := getValue .Defaults .Values $fullProperty }}
|
||||
<input type="text"
|
||||
{{/* <input type="text"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
name="{{ $fullProperty }}"
|
||||
id="{{ $fullProperty }}"
|
||||
value="{{ $value }}" />
|
||||
value="{{ $value }}" /> */}}
|
||||
<textarea
|
||||
name="{{ $fullProperty }}"
|
||||
id="{{ $fullProperty }}"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">{{ $value }}</textarea>
|
||||
{{end}}
|
Loading…
Reference in New Issue
Block a user