Skip to content

Commit

Permalink
feat: Add keychain provider for reading text/yaml/json secrets
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry K. Anisimov <[email protected]>
  • Loading branch information
anisimovdk committed Nov 28, 2024
1 parent 5537ae6 commit a14be10
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 5 deletions.
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ It supports various backends including:
- HCP Vault Secrets
- Bitwarden
- HTTP JSON
- Keychain

- Use `vals eval -f refs.yaml` to replace all the `ref`s in the file to actual values and secrets.
- Use `vals exec -f env.yaml -- <COMMAND>` to populate envvars and execute the command.
Expand Down Expand Up @@ -216,6 +217,7 @@ Please see the [relevant unit test cases](https://github.com/helmfile/vals/blob/
- [Google GCS](#google-gcs)
- [SOPS](#sops) powered by [sops](https://github.com/getsops/sops)
- [Terraform (tfstate)](#terraform-tfstate) powered by [tfstate-lookup](https://github.com/fujiwara/tfstate-lookup)
- [Keychain](#keychain)
- [Echo](#echo)
- [File](#file)
- [Azure Key Vault](#azure-key-vault)
Expand Down Expand Up @@ -592,6 +594,18 @@ Examples:
- `ref+sops://path/to/file#/foo/bar` reads `path/to/file` as a `yaml` file and returns the value at `foo.bar`.
- `ref+sops://path/to/file?format=json#/foo/bar` reads `path/to/file` as a `json` file and returns the value at `foo.bar`.
### Keychain
Keychain provider is going to be available on macOS only. It reads a secret from the macOS Keychain.
- `ref+echo://KEY1/[#/path/to/the/value]`
Examples:
- `security add-generic-password -U -a ${USER} -s "secret-name" -D "vals-secret" -w '{"foo":{"bar":"baz"}}'` - will create a secret in the Keychain with the name `secret-name` and the value `{"foo":{"bar":"baz"}}`, `vals-secret` is required to be able to find the secret in the Keychain.
- `echo 'foo: ref+keychain://secret-name' | vals eval -f -` - will read the secret from the Keychain with the name `secret-name` and replace the `foo` with the secret value.
- `echo 'foo: ref+keychain://secret-name#/foo/bar' | vals eval -f -` - will read the secret from the Keychain with the name `secret-name` and replace the `foo` with the value at the path `$.foo.bar`.
### Echo
Echo provider echoes the string for testing purpose. Please read [the original proposal](https://github.com/roboll/helmfile/pull/920#issuecomment-548213738) to get why we might need this.
Expand All @@ -603,7 +617,6 @@ Examples:
- `ref+echo://foo/bar` generates `foo/bar`
- `ref+echo://foo/bar/baz#/foo/bar` generates `baz`. This works by the host and the path part `foo/bar/baz` generating an object `{"foo":{"bar":"baz"}}` and the fragment part `#/foo/bar` results in digging the object to obtain the value at `$.foo.bar`.
### File
File provider reads a local text file, or the value for the specific path in a YAML/JSON file.
Expand Down Expand Up @@ -834,7 +847,7 @@ Example:
### Bitwarden
This provider retrieves the secrets stored in Bitwarden. It uses the [Bitwarden Vault-Management API](https://bitwarden.com/help/vault-management-api/) that is included in the [Bitwarden CLI](https://github.com/bitwarden/clients) by executing `bw serve`.
This provider retrieves the secrets stored in Bitwarden. It uses the [Bitwarden Vault-Management API](https://bitwarden.com/help/vault-management-api/) that is included in the [Bitwarden CLI](https://github.com/bitwarden/clients) by executing `bw serve`.
Environment variables:
Expand All @@ -857,7 +870,7 @@ Examples:
This provider retrieves values stored in JSON hosted by a HTTP frontend.
This provider is built on top of [jsonquery](https://pkg.go.dev/github.com/antchfx/[email protected]) and [xpath](https://pkg.go.dev/github.com/antchfx/[email protected]) packages.
This provider is built on top of [jsonquery](https://pkg.go.dev/github.com/antchfx/[email protected]) and [xpath](https://pkg.go.dev/github.com/antchfx/[email protected]) packages.
Given the diverse array of JSON structures that can be encountered, utilizing jsonquery with XPath presents a more effective approach for handling this variability in data structures.
Expand All @@ -881,7 +894,7 @@ Let's say you want to fetch the below JSON object from https://api.github.com/us
"name": "go-yaml"
}
]
```
```
```
# To get name="chartify" using https protocol you would use:
ref+httpjson://api.github.com/users/helmfile/repos#///*[1]/name
Expand All @@ -904,7 +917,7 @@ Let's say you want to fetch the below JSON object from https://api.github.com/us
"id": 251296379
}
]
```
```
```
# Running the following will return: 2.51296379e+08
ref+httpjson://api.github.com/users/helmfile/repos#///*[1]/id
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ require (
github.com/google/go-jsonnet v0.20.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
Expand Down
67 changes: 67 additions & 0 deletions pkg/providers/keychain/keychain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package keychain

import (
"errors"
"strings"

"gopkg.in/yaml.v3"

"github.com/helmfile/vals/pkg/api"
"github.com/keybase/go-keychain"
)

const keychainKind = "vals-secret"

type provider struct {
}

func New(cfg api.StaticConfig) *provider {
p := &provider{}
return p
}

func getKeychainSecret(key string) ([]byte, error) {
query := keychain.NewItem()
query.SetSecClass(keychain.SecClassGenericPassword)
query.SetLabel(key)
query.SetDescription(keychainKind)
query.SetMatchLimit(keychain.MatchLimitOne)
query.SetReturnData(true)

results, err := keychain.QueryItem(query)
if err != nil {
return nil, err
} else if len(results) == 0 {
return nil, errors.New("not found")
}

return results[0].Data, nil
}

func (p *provider) GetString(key string) (string, error) {
key = strings.TrimSuffix(key, "/")
key = strings.TrimSpace(key)

secret, err := getKeychainSecret(key)
if err != nil {
return "", err
}

return string(secret), err
}

func (p *provider) GetStringMap(key string) (map[string]interface{}, error) {
key = strings.TrimSuffix(key, "/")
key = strings.TrimSpace(key)

secret, err := getKeychainSecret(key)
if err != nil {
return nil, err
}

m := map[string]interface{}{}
if err := yaml.Unmarshal(secret, &m); err != nil {
return nil, err
}
return m, nil
}
6 changes: 6 additions & 0 deletions vals.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/helmfile/vals/pkg/providers/hcpvaultsecrets"
"github.com/helmfile/vals/pkg/providers/httpjson"
"github.com/helmfile/vals/pkg/providers/k8s"
"github.com/helmfile/vals/pkg/providers/keychain"
"github.com/helmfile/vals/pkg/providers/onepassword"
"github.com/helmfile/vals/pkg/providers/onepasswordconnect"
"github.com/helmfile/vals/pkg/providers/pulumi"
Expand Down Expand Up @@ -91,6 +92,7 @@ const (
ProviderTFStateRemote = "tfstateremote"
ProviderAzureKeyVault = "azurekeyvault"
ProviderEnvSubst = "envsubst"
ProviderKeychain = "keychain"
ProviderOnePassword = "op"
ProviderOnePasswordConnect = "onepasswordconnect"
ProviderDoppler = "doppler"
Expand Down Expand Up @@ -245,6 +247,9 @@ func (r *Runtime) prepare() (*expansion.ExpandRegexMatch, error) {
case ProviderKms:
p := awskms.New(conf)
return p, nil
case ProviderKeychain:
p := keychain.New(conf)
return p, nil
case ProviderEnvSubst:
p := envsubst.New(conf)
return p, nil
Expand Down Expand Up @@ -491,6 +496,7 @@ var KnownValuesTypes = []string{
ProviderTFState,
ProviderFile,
ProviderEcho,
ProviderKeychain,
ProviderEnvSubst,
ProviderPulumiStateAPI,
}
Expand Down

0 comments on commit a14be10

Please sign in to comment.