Skip to content

Commit

Permalink
Merge pull request #93 from 1Password/feature/file-support
Browse files Browse the repository at this point in the history
Support loading file fields into Kubernetes secrets
  • Loading branch information
Marton6 authored Mar 24, 2022
2 parents 5cddc9d + 8fa4413 commit bd9922f
Show file tree
Hide file tree
Showing 19 changed files with 986 additions and 113 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ metadata:
Applying this yaml file will create a Kubernetes Secret with the name `<secret_name>` and contents from the location specified at the specified Item Path.

The contents of the Kubernetes secret will be key-value pairs in which the keys are the fields of the 1Password item and the values are the corresponding values stored in 1Password.
In case of fields that store files, the file's contents will be used as the value.

Within an item, if both a field storing a file and a field of another type have the same name, the file field will be ignored and the other field will take precedence.

Note: Deleting the Deployment that you've created will automatically delete the created Kubernetes Secret only if the deployment is still annotated with `operator.1password.io/item-path` and `operator.1password.io/item-name` and no other deployment is using the secret.

If a 1Password Item that is linked to a Kubernetes Secret is updated within the POLLING_INTERVAL the associated Kubernetes Secret will be updated. However, if you do not want a specific secret to be updated you can add the tag `operator.1password.io:ignore-secret` to the item stored in 1Password. While this tag is in place, any updates made to an item will not trigger an update to the associated secret in Kubernetes.
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ module github.com/1Password/onepassword-operator
go 1.13

require (
github.com/1Password/connect-sdk-go v1.0.1
github.com/1Password/connect-sdk-go v1.2.0
github.com/operator-framework/operator-sdk v0.19.0
github.com/prometheus/common v0.14.0 // indirect
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
github.com/stretchr/testify v1.7.0
k8s.io/api v0.18.2
k8s.io/apimachinery v0.18.2
k8s.io/client-go v12.0.0+incompatible
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeS
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/1Password/connect-sdk-go v1.0.1 h1:BOeMIxVk6/ISmLNWUkSxEbVI7tNr5+aNXIobMM0/I0U=
github.com/1Password/connect-sdk-go v1.0.1/go.mod h1:br2BWk2sqgJFnOFK5WSDfBBmwQ6E7hV9LoPqrtHGRNY=
github.com/1Password/connect-sdk-go v1.2.0 h1:WbIvmbDUpA89nyH0l3LF2iRSFJAv86d2D7IjVNjw6iw=
github.com/1Password/connect-sdk-go v1.2.0/go.mod h1:qK2bF/GweAq812xj+HGfbauaE6cKX1MXfKhpAvoHEq8=
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
Expand Down Expand Up @@ -885,6 +887,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/thanos-io/thanos v0.11.0/go.mod h1:N/Yes7J68KqvmY+xM6J5CJqEvWIvKSR5sqGtmuD6wDc=
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
Expand Down
22 changes: 20 additions & 2 deletions pkg/kubernetessecrets/kubernetes_secrets_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
errs "errors"

"github.com/1Password/connect-sdk-go/onepassword"

"github.com/1Password/onepassword-operator/pkg/utils"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -94,19 +95,36 @@ func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotation
Annotations: annotations,
Labels: labels,
},
Data: BuildKubernetesSecretData(item.Fields),
Data: BuildKubernetesSecretData(item.Fields, item.Files),
Type: corev1.SecretType(secretType),
}
}

func BuildKubernetesSecretData(fields []*onepassword.ItemField) map[string][]byte {
func BuildKubernetesSecretData(fields []*onepassword.ItemField, files []*onepassword.File) map[string][]byte {
secretData := map[string][]byte{}
for i := 0; i < len(fields); i++ {
if fields[i].Value != "" {
key := formatSecretDataName(fields[i].Label)
secretData[key] = []byte(fields[i].Value)
}
}

// populate unpopulated fields from files
for _, file := range files {
content, err := file.Content()
if err != nil {
log.Error(err, "Could not load contents of file %s", file.Name)
continue
}
if content != nil {
key := file.Name
if secretData[key] == nil {
secretData[key] = content
} else {
log.Info(fmt.Sprintf("File '%s' ignored because of a field with the same name", file.Name))
}
}
}
return secretData
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/kubernetessecrets/kubernetes_secrets_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
func TestBuildKubernetesSecretData(t *testing.T) {
fields := generateFields(5)

secretData := BuildKubernetesSecretData(fields)
secretData := BuildKubernetesSecretData(fields, nil)
if len(secretData) != len(fields) {
t.Errorf("Unexpected number of secret fields returned. Expected 3, got %v", len(secretData))
}
Expand Down
18 changes: 18 additions & 0 deletions pkg/mocks/mocksecretserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,31 @@ import (
type TestClient struct {
GetVaultsFunc func() ([]onepassword.Vault, error)
GetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error)
GetVaultFunc func(uuid string) (*onepassword.Vault, error)
GetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error)
GetItemsFunc func(vaultUUID string) ([]onepassword.Item, error)
GetItemsByTitleFunc func(title string, vaultUUID string) ([]onepassword.Item, error)
GetItemByTitleFunc func(title string, vaultUUID string) (*onepassword.Item, error)
CreateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
UpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
DeleteItemFunc func(item *onepassword.Item, vaultUUID string) error
GetFileFunc func(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error)
GetFileContentFunc func(file *onepassword.File) ([]byte, error)
}

var (
GetGetVaultsFunc func() ([]onepassword.Vault, error)
DoGetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error)
DoGetVaultFunc func(uuid string) (*onepassword.Vault, error)
GetGetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error)
DoGetItemsByTitleFunc func(title string, vaultUUID string) ([]onepassword.Item, error)
DoGetItemByTitleFunc func(title string, vaultUUID string) (*onepassword.Item, error)
DoCreateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
DoDeleteItemFunc func(item *onepassword.Item, vaultUUID string) error
DoGetItemsFunc func(vaultUUID string) ([]onepassword.Item, error)
DoUpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
DoGetFileFunc func(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error)
DoGetFileContentFunc func(file *onepassword.File) ([]byte, error)
)

// Do is the mock client's `Do` func
Expand All @@ -37,6 +43,10 @@ func (m *TestClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error)
return DoGetVaultsByTitleFunc(title)
}

func (m *TestClient) GetVault(uuid string) (*onepassword.Vault, error) {
return DoGetVaultFunc(uuid)
}

func (m *TestClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) {
return GetGetItemFunc(uuid, vaultUUID)
}
Expand Down Expand Up @@ -64,3 +74,11 @@ func (m *TestClient) DeleteItem(item *onepassword.Item, vaultUUID string) error
func (m *TestClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) {
return DoUpdateItemFunc(item, vaultUUID)
}

func (m *TestClient) GetFile(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) {
return DoGetFileFunc(uuid, itemUUID, vaultUUID)
}

func (m *TestClient) GetFileContent(file *onepassword.File) ([]byte, error) {
return DoGetFileContentFunc(file)
}
8 changes: 8 additions & 0 deletions pkg/onepassword/items.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ func GetOnePasswordItemByPath(opConnectClient connect.Client, path string) (*one
if err != nil {
return nil, err
}

for _, file := range item.Files {
_, err := opConnectClient.GetFileContent(file)
if err != nil {
return nil, err
}
}

return item, nil
}

Expand Down
21 changes: 21 additions & 0 deletions vendor/github.com/1Password/connect-sdk-go/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit bd9922f

Please sign in to comment.