mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-21 15:08:06 +00:00
Allow vault and item titles in item path
Items can now be accessed by either vaults/<vault_id>/items/<item_id> or vaults/<vault_title>/items/<item_title>
This commit is contained in:
13
README.md
13
README.md
@@ -77,9 +77,7 @@ kind: OnePasswordItem # {insert_new_name}
|
||||
metadata:
|
||||
name: {item_name} #this name will also be used for naming the generated kubernetes secret
|
||||
spec:
|
||||
item-path: "vaults/{vaultId}/items/{itemId}"
|
||||
# where vaultId is the id of the vault in which to find the item
|
||||
# where itemId is the id of the item that you want to store as a Kubernetes Secret
|
||||
item-path: "vaults/{vault_id_or_title}/items/{item_id_or_title}"
|
||||
```
|
||||
|
||||
Deploy the OnePasswordItem to Kubernetes:
|
||||
@@ -104,7 +102,7 @@ kind: Deployment
|
||||
metadata:
|
||||
name: deployment-example
|
||||
annotations:
|
||||
onepasswordoperator/item-path: "vaults/{vaultId}/items/{itemId}"
|
||||
onepasswordoperator/item-path: "vaults/{vault_id_or_title}/items/{item_id_or_title}"
|
||||
onepasswordoperator/item-name: "{secret_name}"
|
||||
```
|
||||
|
||||
@@ -114,6 +112,13 @@ Note: Deleting the Deployment that you've created will automatically delete the
|
||||
|
||||
If a 1Password Item that is linked to a Kubernetes Secret is updated within the `POLLING_INTERVAL` the associated Kubernetes Secret will be updated. Furthermore, any deployments using that secret will be given a rolling restart.
|
||||
|
||||
|
||||
---
|
||||
**NOTE**
|
||||
|
||||
If multiple 1Password vaults/items have the same `title` when using a title in the access path, the desired action will be performed on the oldest vault/item. Furthermore, titles that include white space characters cannot be used.
|
||||
|
||||
---
|
||||
## Development
|
||||
|
||||
### Running Tests
|
||||
|
@@ -28,7 +28,7 @@ spec:
|
||||
- name: OPERATOR_NAME
|
||||
value: "onepassword-connect-operator"
|
||||
- name: OP_CONNECT_HOST
|
||||
value: "http://secret-service:8080"
|
||||
value: "http://onepassword-connect:8080"
|
||||
- name: POLLING_INTERVAL
|
||||
value: "10"
|
||||
- name: OP_CONNECT_TOKEN
|
||||
|
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module github.com/1Password/onepassword-operator
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/1Password/connect-sdk-go v0.0.1
|
||||
github.com/1Password/connect-sdk-go v0.0.2
|
||||
github.com/go-logr/logr v0.1.0 // indirect
|
||||
github.com/operator-framework/operator-sdk v0.19.0
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
|
2
go.sum
2
go.sum
@@ -21,6 +21,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 v0.0.1 h1:qsFZQDQ+JirZRwSom/p6zzNqkkcYAYx4EXivUyPhvBo=
|
||||
github.com/1Password/connect-sdk-go v0.0.1/go.mod h1:br2BWk2sqgJFnOFK5WSDfBBmwQ6E7hV9LoPqrtHGRNY=
|
||||
github.com/1Password/connect-sdk-go v0.0.2 h1:IBamxGS17zItC9TRwp/0G0Fh1GRV3mqOkcWvpK05Mx8=
|
||||
github.com/1Password/connect-sdk-go v0.0.2/go.mod h1:br2BWk2sqgJFnOFK5WSDfBBmwQ6E7hV9LoPqrtHGRNY=
|
||||
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=
|
||||
|
@@ -5,23 +5,27 @@ import (
|
||||
)
|
||||
|
||||
type TestClient struct {
|
||||
GetVaultsFunc func() ([]onepassword.Vault, error)
|
||||
GetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error)
|
||||
GetItemsFunc func(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
|
||||
GetVaultsFunc func() ([]onepassword.Vault, error)
|
||||
GetVaultsByTitleFunc func(title 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
|
||||
}
|
||||
|
||||
var (
|
||||
GetGetVaultsFunc func() ([]onepassword.Vault, error)
|
||||
GetGetItemFunc func(uuid 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)
|
||||
GetGetVaultsFunc func() ([]onepassword.Vault, error)
|
||||
DoGetVaultsByTitleFunc func(title 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)
|
||||
)
|
||||
|
||||
// Do is the mock client's `Do` func
|
||||
@@ -29,6 +33,10 @@ func (m *TestClient) GetVaults() ([]onepassword.Vault, error) {
|
||||
return GetGetVaultsFunc()
|
||||
}
|
||||
|
||||
func (m *TestClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) {
|
||||
return DoGetVaultsByTitleFunc(title)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
return GetGetItemFunc(uuid, vaultUUID)
|
||||
}
|
||||
@@ -37,6 +45,10 @@ func (m *TestClient) GetItems(vaultUUID string) ([]onepassword.Item, error) {
|
||||
return DoGetItemsFunc(vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItemsByTitle(title, vaultUUID string) ([]onepassword.Item, error) {
|
||||
return DoGetItemsByTitleFunc(title, vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItemByTitle(title string, vaultUUID string) (*onepassword.Item, error) {
|
||||
return DoGetItemByTitleFunc(title, vaultUUID)
|
||||
}
|
||||
|
@@ -6,13 +6,26 @@ import (
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
var logger = logf.Log.WithName("retrieve_item")
|
||||
|
||||
func GetOnePasswordItemByPath(opConnectClient connect.Client, path string) (*onepassword.Item, error) {
|
||||
vaultId, itemId, err := ParseVaultIdAndItemIdFromPath(path)
|
||||
vaultValue, itemValue, err := ParseVaultAndItemFromPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vaultId, err := getVaultId(opConnectClient, vaultValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
itemId, err := getItemId(opConnectClient, itemValue, vaultId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
item, err := opConnectClient.GetItem(itemId, vaultId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -20,10 +33,60 @@ func GetOnePasswordItemByPath(opConnectClient connect.Client, path string) (*one
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func ParseVaultIdAndItemIdFromPath(path string) (string, string, error) {
|
||||
func ParseVaultAndItemFromPath(path string) (string, string, error) {
|
||||
splitPath := strings.Split(path, "/")
|
||||
if len(splitPath) == 4 && splitPath[0] == "vaults" && splitPath[2] == "items" {
|
||||
return splitPath[1], splitPath[3], nil
|
||||
}
|
||||
return "", "", fmt.Errorf("%q is not an acceptable path for One Password item. Must be of the format: `vaults/{vault_id}/items/{item_id}`", path)
|
||||
}
|
||||
|
||||
func getVaultId(client connect.Client, vaultIdentifier string) (string, error) {
|
||||
if !IsValidClientUUID(vaultIdentifier) {
|
||||
vaults, err := client.GetVaultsByTitle(vaultIdentifier)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(vaults) == 0 {
|
||||
return "", fmt.Errorf("No vaults found with identifier %q", vaultIdentifier)
|
||||
}
|
||||
|
||||
oldestVault := vaults[0]
|
||||
if len(vaults) > 1 {
|
||||
for _, returnedVault := range vaults {
|
||||
if returnedVault.CreatedAt.Before(oldestVault.CreatedAt) {
|
||||
oldestVault = returnedVault
|
||||
}
|
||||
}
|
||||
logger.Info(fmt.Sprintf("%v 1Password vaults found with the title %q. Will use vault %q as it is the oldest.", len(vaults), vaultIdentifier, oldestVault.ID))
|
||||
}
|
||||
vaultIdentifier = oldestVault.ID
|
||||
}
|
||||
return vaultIdentifier, nil
|
||||
}
|
||||
|
||||
func getItemId(client connect.Client, itemIdentifier string, vaultId string) (string, error) {
|
||||
if !IsValidClientUUID(itemIdentifier) {
|
||||
items, err := client.GetItemsByTitle(itemIdentifier, vaultId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
return "", fmt.Errorf("No items found with identifier %q", itemIdentifier)
|
||||
}
|
||||
|
||||
oldestItem := items[0]
|
||||
if len(items) > 1 {
|
||||
for _, returnedItem := range items {
|
||||
if returnedItem.CreatedAt.Before(oldestItem.CreatedAt) {
|
||||
oldestItem = returnedItem
|
||||
}
|
||||
}
|
||||
logger.Info(fmt.Sprintf("%v 1Password items found with the title %q. Will use item %q as it is the oldest.", len(items), itemIdentifier, oldestItem.ID))
|
||||
}
|
||||
itemIdentifier = oldestItem.ID
|
||||
}
|
||||
return itemIdentifier, nil
|
||||
}
|
||||
|
20
pkg/onepassword/uuid.go
Normal file
20
pkg/onepassword/uuid.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package onepassword
|
||||
|
||||
// UUIDLength defines the required length of UUIDs
|
||||
const UUIDLength = 26
|
||||
|
||||
// IsValidClientUUID returns true if the given client uuid is valid.
|
||||
func IsValidClientUUID(uuid string) bool {
|
||||
if len(uuid) != UUIDLength {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range uuid {
|
||||
valid := (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
|
||||
if !valid {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
56
vendor/github.com/1Password/connect-sdk-go/connect/client.go
generated
vendored
56
vendor/github.com/1Password/connect-sdk-go/connect/client.go
generated
vendored
@@ -24,8 +24,10 @@ const (
|
||||
// Client Represents an available 1Password Connect API to connect to
|
||||
type Client interface {
|
||||
GetVaults() ([]onepassword.Vault, error)
|
||||
GetVaultsByTitle(uuid string) ([]onepassword.Vault, error)
|
||||
GetItem(uuid string, vaultUUID string) (*onepassword.Item, error)
|
||||
GetItems(vaultUUID string) ([]onepassword.Item, error)
|
||||
GetItemsByTitle(title string, vaultUUID string) ([]onepassword.Item, error)
|
||||
GetItemByTitle(title string, vaultUUID string) (*onepassword.Item, error)
|
||||
CreateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
@@ -127,6 +129,39 @@ func (rs *restClient) GetVaults() ([]onepassword.Vault, error) {
|
||||
return vaults, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) {
|
||||
span := rs.tracer.StartSpan("GetVaultsByTitle")
|
||||
defer span.Finish()
|
||||
|
||||
filter := url.QueryEscape(fmt.Sprintf("title eq \"%s\"", title))
|
||||
itemURL := fmt.Sprintf("/v1/vaults?filter=%s", filter)
|
||||
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := rs.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, itemURL)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vaults := []onepassword.Vault{}
|
||||
if err := json.Unmarshal(body, &vaults); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vaults, nil
|
||||
}
|
||||
|
||||
// GetItem Get a specific Item from the 1Password Connect API
|
||||
func (rs *restClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
span := rs.tracer.StartSpan("GetItem")
|
||||
@@ -163,6 +198,21 @@ func (rs *restClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item,
|
||||
func (rs *restClient) GetItemByTitle(title string, vaultUUID string) (*onepassword.Item, error) {
|
||||
span := rs.tracer.StartSpan("GetItemByTitle")
|
||||
defer span.Finish()
|
||||
items, err := rs.GetItemsByTitle(title, vaultUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(items) != 1 {
|
||||
return nil, fmt.Errorf("Found %d item(s) in vault %q with title %q", len(items), vaultUUID, title)
|
||||
}
|
||||
|
||||
return rs.GetItem(items[0].ID, items[0].Vault.ID)
|
||||
}
|
||||
|
||||
func (rs *restClient) GetItemsByTitle(title string, vaultUUID string) ([]onepassword.Item, error) {
|
||||
span := rs.tracer.StartSpan("GetItemsByTitle")
|
||||
defer span.Finish()
|
||||
|
||||
filter := url.QueryEscape(fmt.Sprintf("title eq \"%s\"", title))
|
||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items?filter=%s", vaultUUID, filter)
|
||||
@@ -190,11 +240,7 @@ func (rs *restClient) GetItemByTitle(title string, vaultUUID string) (*onepasswo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(items) != 1 {
|
||||
return nil, fmt.Errorf("Found %d item(s) in vault %q with title %q", len(items), vaultUUID, title)
|
||||
}
|
||||
|
||||
return rs.GetItem(items[0].ID, items[0].Vault.ID)
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) GetItems(vaultUUID string) ([]onepassword.Item, error) {
|
||||
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -1,6 +1,6 @@
|
||||
# cloud.google.com/go v0.49.0
|
||||
cloud.google.com/go/compute/metadata
|
||||
# github.com/1Password/connect-sdk-go v0.0.1
|
||||
# github.com/1Password/connect-sdk-go v0.0.2
|
||||
github.com/1Password/connect-sdk-go/connect
|
||||
github.com/1Password/connect-sdk-go/onepassword
|
||||
# github.com/Azure/go-autorest/autorest v0.9.3-0.20191028180845-3492b2aff503
|
||||
|
Reference in New Issue
Block a user