From 4428ba8273d4d7d2acd705e352589c76ebda511b Mon Sep 17 00:00:00 2001 From: BOIS Pierre-Antoine Date: Tue, 1 Aug 2023 12:08:17 +0200 Subject: [PATCH 01/10] feat: create ansible vault plugin --- plugins/ansiblevault/ansible_vault.go | 25 ++++++++ plugins/ansiblevault/password.go | 61 +++++++++++++++++++ plugins/ansiblevault/password_test.go | 46 ++++++++++++++ plugins/ansiblevault/plugin.go | 22 +++++++ .../ansiblevault/test-fixtures/.ansible-vault | 1 + 5 files changed, 155 insertions(+) create mode 100644 plugins/ansiblevault/ansible_vault.go create mode 100644 plugins/ansiblevault/password.go create mode 100644 plugins/ansiblevault/password_test.go create mode 100644 plugins/ansiblevault/plugin.go create mode 100644 plugins/ansiblevault/test-fixtures/.ansible-vault diff --git a/plugins/ansiblevault/ansible_vault.go b/plugins/ansiblevault/ansible_vault.go new file mode 100644 index 000000000..35c17fc53 --- /dev/null +++ b/plugins/ansiblevault/ansible_vault.go @@ -0,0 +1,25 @@ +package ansiblevault + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" +) + +func AnsibleVaultCLI() schema.Executable { + return schema.Executable{ + Name: "Ansible Vault CLI", + Runs: []string{"ansible-vault"}, + DocsURL: sdk.URL("https://docs.ansible.com/ansible/latest/cli/ansible-vault.html"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWithoutArgs(), + ), + Uses: []schema.CredentialUsage{ + { + Name: credname.Credential, + }, + }, + } +} diff --git a/plugins/ansiblevault/password.go b/plugins/ansiblevault/password.go new file mode 100644 index 000000000..ed78e736a --- /dev/null +++ b/plugins/ansiblevault/password.go @@ -0,0 +1,61 @@ +package ansiblevault + +import ( + "context" + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/importer" + "github.com/1Password/shell-plugins/sdk/provision" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" + "os" +) + +func Password() schema.CredentialType { + return schema.CredentialType{ + Name: credname.Credential, + DocsURL: sdk.URL("https://docs.ansible.com/ansible/latest/cli/ansible-vault.html"), + ManagementURL: nil, + Fields: []schema.CredentialField{ + { + Name: fieldname.Password, + MarkdownDescription: "Password to use when encrypting or decrypting.", + Secret: true, + }, + }, + DefaultProvisioner: provision.TempFile( + passwordFile, + provision.Filename(".ansible-vault"), + provision.SetPathAsEnvVar("ANSIBLE_VAULT_PASSWORD_FILE"), + ), + Importer: importer.TryAll( + TryPasswordFile(), + )} +} + +func passwordFile(in sdk.ProvisionInput) ([]byte, error) { + contents := "" + if password, ok := in.ItemFields[fieldname.Password]; ok { + contents += password + } + return []byte(contents), nil +} + +func TryPasswordFile() sdk.Importer { + file := os.Getenv("ANSIBLE_VAULT_PASSWORD_FILE") + if file == "" { + file = "~/.ansible-vault" + } + return importer.TryFile(file, func(ctx context.Context, contents importer.FileContents, in sdk.ImportInput, out *sdk.ImportAttempt) { + password := contents.ToString() + if password == "" { + return + } + + out.AddCandidate(sdk.ImportCandidate{ + Fields: map[sdk.FieldName]string{ + fieldname.Password: password, + }, + }) + }) +} diff --git a/plugins/ansiblevault/password_test.go b/plugins/ansiblevault/password_test.go new file mode 100644 index 000000000..930a75ed0 --- /dev/null +++ b/plugins/ansiblevault/password_test.go @@ -0,0 +1,46 @@ +package ansiblevault + +import ( + "testing" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/plugintest" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func TestPasswordProvisioner(t *testing.T) { + plugintest.TestProvisioner(t, Password().DefaultProvisioner, map[string]plugintest.ProvisionCase{ + "default": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Password: "Sl9WhfECfUn43o2qEcN2GnAw2EXAMPLE", + }, + ExpectedOutput: sdk.ProvisionOutput{ + Files: map[string]sdk.OutputFile{ + ".ansible-vault": { + Contents: []byte(plugintest.LoadFixture(t, ".ansible-vault")), + }, + }, + Environment: map[string]string{ + "ANSIBLE_VAULT_PASSWORD_FILE": ".ansible-vault", + }, + }, + }, + }) +} + +func TestPasswordImporter(t *testing.T) { + plugintest.TestImporter(t, Password().Importer, map[string]plugintest.ImportCase{ + "default": { + Files: map[string]string{ + ".ansible-vault": plugintest.LoadFixture(t, ".ansible-vault"), + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.Password: "Sl9WhfECfUn43o2qEcN2GnAw2EXAMPLE", + }, + }, + }, + }, + }) +} diff --git a/plugins/ansiblevault/plugin.go b/plugins/ansiblevault/plugin.go new file mode 100644 index 000000000..21b9cb49b --- /dev/null +++ b/plugins/ansiblevault/plugin.go @@ -0,0 +1,22 @@ +package ansiblevault + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func New() schema.Plugin { + return schema.Plugin{ + Name: "ansiblevault", + Platform: schema.PlatformInfo{ + Name: "Ansible Vault", + Homepage: sdk.URL("https://docs.ansible.com/ansible/latest/cli/ansible-vault.html"), + }, + Credentials: []schema.CredentialType{ + Password(), + }, + Executables: []schema.Executable{ + AnsibleVaultCLI(), + }, + } +} diff --git a/plugins/ansiblevault/test-fixtures/.ansible-vault b/plugins/ansiblevault/test-fixtures/.ansible-vault new file mode 100644 index 000000000..aa3d4e425 --- /dev/null +++ b/plugins/ansiblevault/test-fixtures/.ansible-vault @@ -0,0 +1 @@ +Sl9WhfECfUn43o2qEcN2GnAw2EXAMPLE \ No newline at end of file From 694cd6ddc8ee6b4bd7b2d89e842b6ee5a2c3efbb Mon Sep 17 00:00:00 2001 From: BOIS Pierre-Antoine Date: Fri, 1 Sep 2023 07:28:27 +0200 Subject: [PATCH 02/10] feat: add extra line to text file --- plugins/ansiblevault/test-fixtures/.ansible-vault | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ansiblevault/test-fixtures/.ansible-vault b/plugins/ansiblevault/test-fixtures/.ansible-vault index aa3d4e425..93d8f6f3b 100644 --- a/plugins/ansiblevault/test-fixtures/.ansible-vault +++ b/plugins/ansiblevault/test-fixtures/.ansible-vault @@ -1 +1 @@ -Sl9WhfECfUn43o2qEcN2GnAw2EXAMPLE \ No newline at end of file +Sl9WhfECfUn43o2qEcN2GnAw2EXAMPLE From 472dba19441dd181074f5f66cc7e2261a092ed79 Mon Sep 17 00:00:00 2001 From: BOIS Pierre-Antoine Date: Fri, 1 Sep 2023 07:35:34 +0200 Subject: [PATCH 03/10] feat: throw an error if password field is not present --- plugins/ansiblevault/password.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/ansiblevault/password.go b/plugins/ansiblevault/password.go index ed78e736a..9916647eb 100644 --- a/plugins/ansiblevault/password.go +++ b/plugins/ansiblevault/password.go @@ -2,6 +2,7 @@ package ansiblevault import ( "context" + "fmt" "github.com/1Password/shell-plugins/sdk" "github.com/1Password/shell-plugins/sdk/importer" "github.com/1Password/shell-plugins/sdk/provision" @@ -37,6 +38,8 @@ func passwordFile(in sdk.ProvisionInput) ([]byte, error) { contents := "" if password, ok := in.ItemFields[fieldname.Password]; ok { contents += password + } else { + return nil, fmt.Errorf("unable to find password field") } return []byte(contents), nil } From 0ea287cb0a8d2f987e2342202f4436ce8d7d1d93 Mon Sep 17 00:00:00 2001 From: Simon Barendse Date: Thu, 21 Sep 2023 14:48:06 +0200 Subject: [PATCH 04/10] Refactor passwordFile function to simplify Remove the need for string manipulation and instead return the password field directly, to simplify the code for readability. --- plugins/ansiblevault/password.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/ansiblevault/password.go b/plugins/ansiblevault/password.go index 9916647eb..f6431ec0b 100644 --- a/plugins/ansiblevault/password.go +++ b/plugins/ansiblevault/password.go @@ -35,13 +35,10 @@ func Password() schema.CredentialType { } func passwordFile(in sdk.ProvisionInput) ([]byte, error) { - contents := "" if password, ok := in.ItemFields[fieldname.Password]; ok { - contents += password - } else { - return nil, fmt.Errorf("unable to find password field") + return []byte(password), nil } - return []byte(contents), nil + return nil, fmt.Errorf("unable to find password field") } func TryPasswordFile() sdk.Importer { From c304012a2f265cb876d020351d0d89c206ed65c9 Mon Sep 17 00:00:00 2001 From: Simon Barendse Date: Thu, 21 Sep 2023 14:49:56 +0200 Subject: [PATCH 05/10] Fix lint error --- plugins/ansiblevault/password.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ansiblevault/password.go b/plugins/ansiblevault/password.go index f6431ec0b..af6f36d4b 100644 --- a/plugins/ansiblevault/password.go +++ b/plugins/ansiblevault/password.go @@ -3,6 +3,7 @@ package ansiblevault import ( "context" "fmt" + "github.com/1Password/shell-plugins/sdk" "github.com/1Password/shell-plugins/sdk/importer" "github.com/1Password/shell-plugins/sdk/provision" From b93c98aa5e5b3ed95ef66a24a0332e525c747414 Mon Sep 17 00:00:00 2001 From: Simon Barendse Date: Fri, 22 Sep 2023 14:21:20 +0200 Subject: [PATCH 06/10] Sort imports This should fix the lint CI job as well. --- plugins/ansiblevault/password.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ansiblevault/password.go b/plugins/ansiblevault/password.go index af6f36d4b..f6989524a 100644 --- a/plugins/ansiblevault/password.go +++ b/plugins/ansiblevault/password.go @@ -3,6 +3,7 @@ package ansiblevault import ( "context" "fmt" + "os" "github.com/1Password/shell-plugins/sdk" "github.com/1Password/shell-plugins/sdk/importer" @@ -10,7 +11,6 @@ import ( "github.com/1Password/shell-plugins/sdk/schema" "github.com/1Password/shell-plugins/sdk/schema/credname" "github.com/1Password/shell-plugins/sdk/schema/fieldname" - "os" ) func Password() schema.CredentialType { From 9f8af594ff609e122da765d46c4df78ee1878e25 Mon Sep 17 00:00:00 2001 From: Simon Barendse Date: Fri, 22 Sep 2023 14:32:39 +0200 Subject: [PATCH 07/10] Expect password file for ansible-vault in /tmp/ directory --- plugins/ansiblevault/password_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ansiblevault/password_test.go b/plugins/ansiblevault/password_test.go index 930a75ed0..8f212c774 100644 --- a/plugins/ansiblevault/password_test.go +++ b/plugins/ansiblevault/password_test.go @@ -16,12 +16,12 @@ func TestPasswordProvisioner(t *testing.T) { }, ExpectedOutput: sdk.ProvisionOutput{ Files: map[string]sdk.OutputFile{ - ".ansible-vault": { + "/tmp/.ansible-vault": { Contents: []byte(plugintest.LoadFixture(t, ".ansible-vault")), }, }, Environment: map[string]string{ - "ANSIBLE_VAULT_PASSWORD_FILE": ".ansible-vault", + "ANSIBLE_VAULT_PASSWORD_FILE": "/tmp/.ansible-vault", }, }, }, From 6c791bfb6da4a0fd06331c2f857fc9c2f276a09d Mon Sep 17 00:00:00 2001 From: Simon Barendse Date: Fri, 22 Sep 2023 14:35:47 +0200 Subject: [PATCH 08/10] Add missing newline to ansible-vault password file This was caught by the test which failed because of this missing newline. --- plugins/ansiblevault/password.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ansiblevault/password.go b/plugins/ansiblevault/password.go index f6989524a..d30069f43 100644 --- a/plugins/ansiblevault/password.go +++ b/plugins/ansiblevault/password.go @@ -37,7 +37,7 @@ func Password() schema.CredentialType { func passwordFile(in sdk.ProvisionInput) ([]byte, error) { if password, ok := in.ItemFields[fieldname.Password]; ok { - return []byte(password), nil + return []byte(password + "\n"), nil } return nil, fmt.Errorf("unable to find password field") } From 16e3a8c65261b346ffbe207ded0f6eb013572aaf Mon Sep 17 00:00:00 2001 From: Simon Barendse Date: Fri, 22 Sep 2023 14:40:51 +0200 Subject: [PATCH 09/10] Read ansible-vault password from homedir in test This is where the importer expects the file as well --- plugins/ansiblevault/password_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ansiblevault/password_test.go b/plugins/ansiblevault/password_test.go index 8f212c774..1433ae6a3 100644 --- a/plugins/ansiblevault/password_test.go +++ b/plugins/ansiblevault/password_test.go @@ -32,7 +32,7 @@ func TestPasswordImporter(t *testing.T) { plugintest.TestImporter(t, Password().Importer, map[string]plugintest.ImportCase{ "default": { Files: map[string]string{ - ".ansible-vault": plugintest.LoadFixture(t, ".ansible-vault"), + "~/.ansible-vault": plugintest.LoadFixture(t, ".ansible-vault"), }, ExpectedCandidates: []sdk.ImportCandidate{ { From 04ff354d28aec4c5cc15bf34400cc3a5ad13f087 Mon Sep 17 00:00:00 2001 From: Simon Barendse Date: Fri, 22 Sep 2023 15:10:46 +0200 Subject: [PATCH 10/10] Fix: Remove trailing newline from imported ansible vault password --- plugins/ansiblevault/password.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/ansiblevault/password.go b/plugins/ansiblevault/password.go index d30069f43..c8d1f92f9 100644 --- a/plugins/ansiblevault/password.go +++ b/plugins/ansiblevault/password.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "strings" "github.com/1Password/shell-plugins/sdk" "github.com/1Password/shell-plugins/sdk/importer" @@ -48,7 +49,7 @@ func TryPasswordFile() sdk.Importer { file = "~/.ansible-vault" } return importer.TryFile(file, func(ctx context.Context, contents importer.FileContents, in sdk.ImportInput, out *sdk.ImportAttempt) { - password := contents.ToString() + password := strings.TrimSuffix(contents.ToString(), "\n") if password == "" { return }