Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(providers): Add support for 1Password using service account tokens #378

Merged
merged 2 commits into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ It supports various backends including:
- [Google Sheets](#google-sheets)
- [SOPS](https://github.com/getsops/sops)-encrypted files
- Terraform State
- 1Password
- 1Password Connect
- [Doppler](https://doppler.com/)
- CredHub(Coming soon)
Expand Down Expand Up @@ -220,6 +221,7 @@ Please see the [relevant unit test cases](https://github.com/helmfile/vals/blob/
- [Azure Key Vault](#azure-key-vault)
- [EnvSubst](#envsubst)
- [GitLab](#gitlab)
- [1Password](#1password)
- [1Password Connect](#1password-connect)
- [Doppler](#doppler)
- [Pulumi State](#pulumi-state)
Expand Down Expand Up @@ -664,6 +666,28 @@ Examples:
- `ref+gitlab://gitlab.com/11111/password`
- `ref+gitlab://my-gitlab.org/11111/password?ssl_verify=true&scheme=https`

### 1Password

For this provider to work a working [service account token](https://developer.1password.com/docs/service-accounts/get-started/) is required.
The following env var has to be configured:
- `OP_SERVICE_ACCOUNT_TOKEN`

1Password is organized in vaults and items.
An item can have multiple fields with or without a section. Labels can be set on fields and sections.
Vaults, items, sections and labels can be accessed by ID or by label/name (and IDs and labels can be mixed and matched in one URL).

If a section does not have a label the field is only accessible via the section ID. This does not hold true for some default fields which may have no section at all (e.g.username and password for a `Login` item).

See [Secret reference syntax](https://developer.1password.com/docs/cli/secrets-reference-syntax/) for more information.

*Caution: vals-expressions are parsed as URIs. For the 1Password provider the host component of the URI identifies the vault. Therefore vaults containing certain characters not allowed in the host component (e.g. whitespaces, see [RFC-3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2) for details) can only be accessed by ID.*

Examples:

- `ref+op://VAULT_NAME/ITEM_NAME/FIELD_NAME`
- `ref+op://VAULT_ID/ITEM_NAME/FIELD_NAME`
- `ref+op://VAULT_NAME/ITEM_NAME/[SECTION_NAME/]FIELD_NAME`

### 1Password Connect

For this provider to work you require a working and accessible [1Password connect server](https://developer.1password.com/docs/connect).
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
cloud.google.com/go/secretmanager v1.13.0
cloud.google.com/go/storage v1.41.0
github.com/1Password/connect-sdk-go v1.5.3
github.com/1password/onepassword-sdk-go v0.1.0-beta.7
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0
Expand Down Expand Up @@ -38,16 +39,19 @@ require (
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/extism/go-sdk v1.2.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.0 // indirect
github.com/go-openapi/loads v0.22.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/strfmt v0.23.0 // indirect
github.com/go-openapi/validate v0.24.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/tetratelabs/wazero v1.7.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect
)
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg=
filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE=
github.com/1Password/connect-sdk-go v1.5.3 h1:KyjJ+kCKj6BwB2Y8tPM1Ixg5uIS6HsB0uWA8U38p/Uk=
github.com/1Password/connect-sdk-go v1.5.3/go.mod h1:5rSymY4oIYtS4G3t0oMkGAXBeoYiukV3vkqlnEjIDJs=
github.com/1password/onepassword-sdk-go v0.1.0-beta.7 h1:gBF2LhDTzGqwFZk2a1GZoA+8Hz7J406W7uNk84uV5I4=
github.com/1password/onepassword-sdk-go v0.1.0-beta.7/go.mod h1:yxhCMMeJs6seB45snoI2IwNvFZIuhC4xsJioZ2vnmHI=
github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw=
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
Expand Down Expand Up @@ -178,6 +180,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/extism/go-sdk v1.2.0 h1:A0DnIMthdP8h6K9NbRpRs1PIXHOUlb/t/TZWk5eUzx4=
github.com/extism/go-sdk v1.2.0/go.mod h1:xUfKSEQndAvHBc1Ohdre0e+UdnRzUpVfbA8QLcx4fbY=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
Expand Down Expand Up @@ -220,6 +224,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
Expand Down Expand Up @@ -438,6 +444,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8=
github.com/tetratelabs/wazero v1.7.1/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
Expand Down
52 changes: 52 additions & 0 deletions pkg/providers/onepassword/onepassword.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package onepassword

import (
"context"
"fmt"
"os"

"github.com/1password/onepassword-sdk-go"
"github.com/helmfile/vals/pkg/api"
yxxhero marked this conversation as resolved.
Show resolved Hide resolved
)

type provider struct {
client *onepassword.Client
}

// New creates a new 1Password provider
func New(cfg api.StaticConfig) *provider {
p := &provider{}

return p
}

// Get secret string from 1Password
func (p *provider) GetString(key string) (string, error) {
var err error

ctx := context.Background()
token := os.Getenv("OP_SERVICE_ACCOUNT_TOKEN")

client, err := onepassword.NewClient(
ctx,
onepassword.WithServiceAccountToken(token),
onepassword.WithIntegrationInfo("Vals op integration", "v1.0.0"),
)
if err != nil {
return "", fmt.Errorf("storage.NewClient: %v", err)
}

p.client = client

prefixedKey := fmt.Sprintf("op://%s", key)
item, err := p.client.Secrets.Resolve(ctx, prefixedKey)
if err != nil {
return "", fmt.Errorf("error retrieving item: %v", err)
}

return item, nil
}

func (p *provider) GetStringMap(key string) (map[string]interface{}, error) {
return nil, fmt.Errorf("path fragment is not supported for 1password provider")
}
3 changes: 3 additions & 0 deletions pkg/stringprovider/stringprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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/onepassword"
"github.com/helmfile/vals/pkg/providers/onepasswordconnect"
"github.com/helmfile/vals/pkg/providers/pulumi"
"github.com/helmfile/vals/pkg/providers/s3"
Expand Down Expand Up @@ -60,6 +61,8 @@ func New(l *log.Logger, provider api.StaticConfig) (api.LazyLoadedStringProvider
return azurekeyvault.New(provider), nil
case "gitlab":
return gitlab.New(provider), nil
case "onepassword":
return onepassword.New(provider), nil
case "onepasswordconnect":
return onepasswordconnect.New(provider), nil
case "doppler":
Expand Down
5 changes: 5 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/onepassword"
"github.com/helmfile/vals/pkg/providers/onepasswordconnect"
"github.com/helmfile/vals/pkg/providers/pulumi"
"github.com/helmfile/vals/pkg/providers/s3"
Expand Down Expand Up @@ -90,6 +91,7 @@ const (
ProviderTFStateRemote = "tfstateremote"
ProviderAzureKeyVault = "azurekeyvault"
ProviderEnvSubst = "envsubst"
ProviderOnePassword = "op"
yxxhero marked this conversation as resolved.
Show resolved Hide resolved
ProviderOnePasswordConnect = "onepasswordconnect"
ProviderDoppler = "doppler"
ProviderPulumiStateAPI = "pulumistateapi"
Expand Down Expand Up @@ -246,6 +248,9 @@ func (r *Runtime) prepare() (*expansion.ExpandRegexMatch, error) {
case ProviderEnvSubst:
p := envsubst.New(conf)
return p, nil
case ProviderOnePassword:
p := onepassword.New(conf)
return p, nil
case ProviderOnePasswordConnect:
p := onepasswordconnect.New(conf)
return p, nil
Expand Down
60 changes: 60 additions & 0 deletions vals_onepassword_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package vals

import (
"fmt"
"os"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestValues_OnePassword_EvalTemplate(t *testing.T) {
// TODO
// 1. Create vault and item for testing and a service account
// op vault create vals-test
// op item create --vault vals-test --title=vals-test [email protected] password=secret --category=login
// op service-account create "Vals Test Service Account" --expires-in 24h --vault vals-test:read_items

// 2. Set up the new service account token as environment variable:
// export OP_SERVICE_ACCOUNT_TOKEN=ops_xxxxxxxxx
if os.Getenv("SKIP_TESTS") != "" {
t.Skip("Skipping tests")
}

type testcase struct {
template map[string]interface{}
expected map[string]interface{}
}
vaultName := "vals-test"
itemName := "vals-test"

testcases := []testcase{
{
template: map[string]interface{}{
"foo": "FOO",
"username": fmt.Sprintf("ref+op://%s/%s/username", vaultName, itemName),
"password": fmt.Sprintf("ref+op://%s/%s/password", vaultName, itemName),
},
expected: map[string]interface{}{
"foo": "FOO",
"username": "[email protected]",
"password": "secret",
},
},
}

for i := range testcases {
tc := testcases[i]
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
vals, err := Eval(tc.template)
if err != nil {
t.Fatalf("%v", err)
}

diff := cmp.Diff(tc.expected, vals)
if diff != "" {
t.Errorf("unxpected diff: %s", diff)
}
})
}
}
Loading