onepassword.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package onepassword
  13. import (
  14. "context"
  15. "fmt"
  16. "net/url"
  17. "sort"
  18. "github.com/1Password/connect-sdk-go/connect"
  19. "github.com/1Password/connect-sdk-go/onepassword"
  20. corev1 "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/types"
  22. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  23. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  24. "github.com/external-secrets/external-secrets/pkg/find"
  25. "github.com/external-secrets/external-secrets/pkg/utils"
  26. )
  27. const (
  28. userAgent = "external-secrets"
  29. errOnePasswordStore = "received invalid 1Password SecretStore resource: %w"
  30. errOnePasswordStoreNilSpec = "nil spec"
  31. errOnePasswordStoreNilSpecProvider = "nil spec.provider"
  32. errOnePasswordStoreNilSpecProviderOnePassword = "nil spec.provider.onepassword"
  33. errOnePasswordStoreMissingRefName = "missing: spec.provider.onepassword.auth.secretRef.connectTokenSecretRef.name"
  34. errOnePasswordStoreMissingRefKey = "missing: spec.provider.onepassword.auth.secretRef.connectTokenSecretRef.key"
  35. errOnePasswordStoreAtLeastOneVault = "must be at least one vault: spec.provider.onepassword.vaults"
  36. errOnePasswordStoreInvalidConnectHost = "unable to parse URL: spec.provider.onepassword.connectHost: %w"
  37. errOnePasswordStoreNonUniqueVaultNumbers = "vault order numbers must be unique"
  38. errFetchK8sSecret = "could not fetch ConnectToken Secret: %w"
  39. errMissingToken = "missing Secret Token"
  40. errGetVault = "error finding 1Password Vault: %w"
  41. errExpectedOneItem = "expected one 1Password Item matching %w"
  42. errGetItem = "error finding 1Password Item: %w"
  43. errKeyNotFound = "key not found in 1Password Vaults: %w"
  44. errDocumentNotFound = "error finding 1Password Document: %w"
  45. errExpectedOneField = "expected one 1Password ItemField matching %w"
  46. errTagsNotImplemented = "'find.tags' is not implemented in the 1Password provider"
  47. errVersionNotImplemented = "'remoteRef.version' is not implemented in the 1Password provider"
  48. documentCategory = "DOCUMENT"
  49. fieldsWithLabelFormat = "'%s' in '%s', got %d"
  50. incorrectCountFormat = "'%s', got %d"
  51. )
  52. // ProviderOnePassword is a provider for 1Password.
  53. type ProviderOnePassword struct {
  54. vaults map[string]int
  55. client connect.Client
  56. }
  57. // https://github.com/external-secrets/external-secrets/issues/644
  58. var _ esv1beta1.SecretsClient = &ProviderOnePassword{}
  59. var _ esv1beta1.Provider = &ProviderOnePassword{}
  60. // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
  61. func (provider *ProviderOnePassword) Capabilities() esv1beta1.SecretStoreCapabilities {
  62. return esv1beta1.SecretStoreReadOnly
  63. }
  64. // NewClient constructs a 1Password Provider.
  65. func (provider *ProviderOnePassword) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
  66. config := store.GetSpec().Provider.OnePassword
  67. credentialsSecret := &corev1.Secret{}
  68. objectKey := types.NamespacedName{
  69. Name: config.Auth.SecretRef.ConnectToken.Name,
  70. Namespace: namespace,
  71. }
  72. // only ClusterSecretStore is allowed to set namespace (and then it's required)
  73. if store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
  74. objectKey.Namespace = *config.Auth.SecretRef.ConnectToken.Namespace
  75. }
  76. err := kube.Get(ctx, objectKey, credentialsSecret)
  77. if err != nil {
  78. return nil, fmt.Errorf(errFetchK8sSecret, err)
  79. }
  80. token := credentialsSecret.Data[config.Auth.SecretRef.ConnectToken.Key]
  81. if (token == nil) || (len(token) == 0) {
  82. return nil, fmt.Errorf(errMissingToken)
  83. }
  84. provider.client = connect.NewClientWithUserAgent(config.ConnectHost, string(token), userAgent)
  85. provider.vaults = config.Vaults
  86. return provider, nil
  87. }
  88. // ValidateStore checks if the provided store is valid.
  89. func (provider *ProviderOnePassword) ValidateStore(store esv1beta1.GenericStore) error {
  90. return validateStore(store)
  91. }
  92. func validateStore(store esv1beta1.GenericStore) error {
  93. // check nils
  94. storeSpec := store.GetSpec()
  95. if storeSpec == nil {
  96. return fmt.Errorf(errOnePasswordStore, fmt.Errorf(errOnePasswordStoreNilSpec))
  97. }
  98. if storeSpec.Provider == nil {
  99. return fmt.Errorf(errOnePasswordStore, fmt.Errorf(errOnePasswordStoreNilSpecProvider))
  100. }
  101. if storeSpec.Provider.OnePassword == nil {
  102. return fmt.Errorf(errOnePasswordStore, fmt.Errorf(errOnePasswordStoreNilSpecProviderOnePassword))
  103. }
  104. // check mandatory fields
  105. config := storeSpec.Provider.OnePassword
  106. if config.Auth.SecretRef.ConnectToken.Name == "" {
  107. return fmt.Errorf(errOnePasswordStore, fmt.Errorf(errOnePasswordStoreMissingRefName))
  108. }
  109. if config.Auth.SecretRef.ConnectToken.Key == "" {
  110. return fmt.Errorf(errOnePasswordStore, fmt.Errorf(errOnePasswordStoreMissingRefKey))
  111. }
  112. // check namespace compared to kind
  113. if err := utils.ValidateSecretSelector(store, config.Auth.SecretRef.ConnectToken); err != nil {
  114. return fmt.Errorf(errOnePasswordStore, err)
  115. }
  116. // check at least one vault
  117. if len(config.Vaults) == 0 {
  118. return fmt.Errorf(errOnePasswordStore, fmt.Errorf(errOnePasswordStoreAtLeastOneVault))
  119. }
  120. // ensure vault numbers are unique
  121. if !hasUniqueVaultNumbers(config.Vaults) {
  122. return fmt.Errorf(errOnePasswordStore, fmt.Errorf(errOnePasswordStoreNonUniqueVaultNumbers))
  123. }
  124. // check valid URL
  125. if _, err := url.Parse(config.ConnectHost); err != nil {
  126. return fmt.Errorf(errOnePasswordStore, fmt.Errorf(errOnePasswordStoreInvalidConnectHost, err))
  127. }
  128. return nil
  129. }
  130. func (provider *ProviderOnePassword) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
  131. return fmt.Errorf("not implemented")
  132. }
  133. // Not Implemented PushSecret.
  134. func (provider *ProviderOnePassword) PushSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
  135. return fmt.Errorf("not implemented")
  136. }
  137. // GetSecret returns a single secret from the provider.
  138. func (provider *ProviderOnePassword) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  139. if ref.Version != "" {
  140. return nil, fmt.Errorf(errVersionNotImplemented)
  141. }
  142. item, err := provider.findItem(ref.Key)
  143. if err != nil {
  144. return nil, err
  145. }
  146. // handle files
  147. if item.Category == documentCategory {
  148. // default to the first file when ref.Property is empty
  149. return provider.getFile(item, ref.Property)
  150. }
  151. // handle fields
  152. return provider.getField(item, ref.Property)
  153. }
  154. // Validate checks if the client is configured correctly
  155. // to be able to retrieve secrets from the provider.
  156. func (provider *ProviderOnePassword) Validate() (esv1beta1.ValidationResult, error) {
  157. for vaultName := range provider.vaults {
  158. _, err := provider.client.GetVaultByTitle(vaultName)
  159. if err != nil {
  160. return esv1beta1.ValidationResultError, err
  161. }
  162. }
  163. return esv1beta1.ValidationResultReady, nil
  164. }
  165. // GetSecretMap returns multiple k/v pairs from the provider, for dataFrom.extract.
  166. func (provider *ProviderOnePassword) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  167. if ref.Version != "" {
  168. return nil, fmt.Errorf(errVersionNotImplemented)
  169. }
  170. item, err := provider.findItem(ref.Key)
  171. if err != nil {
  172. return nil, err
  173. }
  174. // handle files
  175. if item.Category == documentCategory {
  176. return provider.getFiles(item, ref.Property)
  177. }
  178. // handle fields
  179. return provider.getFields(item, ref.Property)
  180. }
  181. // GetAllSecrets syncs multiple 1Password Items into a single Kubernetes Secret, for dataFrom.find.
  182. func (provider *ProviderOnePassword) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  183. if ref.Tags != nil {
  184. return nil, fmt.Errorf(errTagsNotImplemented)
  185. }
  186. secretData := make(map[string][]byte)
  187. sortedVaults := sortVaults(provider.vaults)
  188. for _, vaultName := range sortedVaults {
  189. vault, err := provider.client.GetVaultByTitle(vaultName)
  190. if err != nil {
  191. return nil, fmt.Errorf(errGetVault, err)
  192. }
  193. err = provider.getAllForVault(vault.ID, ref, secretData)
  194. if err != nil {
  195. return nil, err
  196. }
  197. }
  198. return secretData, nil
  199. }
  200. // Close closes the client connection.
  201. func (provider *ProviderOnePassword) Close(ctx context.Context) error {
  202. return nil
  203. }
  204. func (provider *ProviderOnePassword) findItem(name string) (*onepassword.Item, error) {
  205. sortedVaults := sortVaults(provider.vaults)
  206. for _, vaultName := range sortedVaults {
  207. vault, err := provider.client.GetVaultByTitle(vaultName)
  208. if err != nil {
  209. return nil, fmt.Errorf(errGetVault, err)
  210. }
  211. // use GetItemsByTitle instead of GetItemByTitle in order to handle length cases
  212. items, err := provider.client.GetItemsByTitle(name, vault.ID)
  213. if err != nil {
  214. return nil, fmt.Errorf(errGetItem, err)
  215. }
  216. switch {
  217. case len(items) == 1:
  218. return provider.client.GetItemByUUID(items[0].ID, items[0].Vault.ID)
  219. case len(items) > 1:
  220. return nil, fmt.Errorf(errExpectedOneItem, fmt.Errorf(incorrectCountFormat, name, len(items)))
  221. }
  222. }
  223. return nil, fmt.Errorf(errKeyNotFound, fmt.Errorf("%s in: %v", name, provider.vaults))
  224. }
  225. func (provider *ProviderOnePassword) getField(item *onepassword.Item, property string) ([]byte, error) {
  226. // default to a field labeled "password"
  227. fieldLabel := "password"
  228. if property != "" {
  229. fieldLabel = property
  230. }
  231. if length := countFieldsWithLabel(fieldLabel, item.Fields); length != 1 {
  232. return nil, fmt.Errorf(errExpectedOneField, fmt.Errorf(fieldsWithLabelFormat, fieldLabel, item.Title, length))
  233. }
  234. // caution: do not use client.GetValue here because it has undesirable behavior on keys with a dot in them
  235. value := ""
  236. for _, field := range item.Fields {
  237. if field.Label == fieldLabel {
  238. value = field.Value
  239. break
  240. }
  241. }
  242. return []byte(value), nil
  243. }
  244. func (provider *ProviderOnePassword) getFields(item *onepassword.Item, property string) (map[string][]byte, error) {
  245. secretData := make(map[string][]byte)
  246. for _, field := range item.Fields {
  247. if property != "" && field.Label != property {
  248. continue
  249. }
  250. if length := countFieldsWithLabel(field.Label, item.Fields); length != 1 {
  251. return nil, fmt.Errorf(errExpectedOneField, fmt.Errorf(fieldsWithLabelFormat, field.Label, item.Title, length))
  252. }
  253. // caution: do not use client.GetValue here because it has undesirable behavior on keys with a dot in them
  254. secretData[field.Label] = []byte(field.Value)
  255. }
  256. return secretData, nil
  257. }
  258. func (provider *ProviderOnePassword) getAllFields(item onepassword.Item, ref esv1beta1.ExternalSecretFind, secretData map[string][]byte) error {
  259. i, err := provider.client.GetItemByUUID(item.ID, item.Vault.ID)
  260. if err != nil {
  261. return fmt.Errorf(errGetItem, err)
  262. }
  263. item = *i
  264. for _, field := range item.Fields {
  265. if length := countFieldsWithLabel(field.Label, item.Fields); length != 1 {
  266. return fmt.Errorf(errExpectedOneField, fmt.Errorf(fieldsWithLabelFormat, field.Label, item.Title, length))
  267. }
  268. if ref.Name != nil {
  269. matcher, err := find.New(*ref.Name)
  270. if err != nil {
  271. return err
  272. }
  273. if !matcher.MatchName(field.Label) {
  274. continue
  275. }
  276. }
  277. if _, ok := secretData[field.Label]; !ok {
  278. secretData[field.Label] = []byte(field.Value)
  279. }
  280. }
  281. return nil
  282. }
  283. func (provider *ProviderOnePassword) getFile(item *onepassword.Item, property string) ([]byte, error) {
  284. for _, file := range item.Files {
  285. // default to the first file when ref.Property is empty
  286. if file.Name == property || property == "" {
  287. contents, err := provider.client.GetFileContent(file)
  288. if err != nil {
  289. return nil, err
  290. }
  291. return contents, nil
  292. }
  293. }
  294. return nil, fmt.Errorf(errDocumentNotFound, fmt.Errorf("'%s', '%s'", item.Title, property))
  295. }
  296. func (provider *ProviderOnePassword) getFiles(item *onepassword.Item, property string) (map[string][]byte, error) {
  297. secretData := make(map[string][]byte)
  298. for _, file := range item.Files {
  299. if property != "" && file.Name != property {
  300. continue
  301. }
  302. contents, err := provider.client.GetFileContent(file)
  303. if err != nil {
  304. return nil, err
  305. }
  306. secretData[file.Name] = contents
  307. }
  308. return secretData, nil
  309. }
  310. func (provider *ProviderOnePassword) getAllFiles(item onepassword.Item, ref esv1beta1.ExternalSecretFind, secretData map[string][]byte) error {
  311. for _, file := range item.Files {
  312. if ref.Name != nil {
  313. matcher, err := find.New(*ref.Name)
  314. if err != nil {
  315. return err
  316. }
  317. if !matcher.MatchName(file.Name) {
  318. continue
  319. }
  320. }
  321. if _, ok := secretData[file.Name]; !ok {
  322. contents, err := provider.client.GetFileContent(file)
  323. if err != nil {
  324. return err
  325. }
  326. secretData[file.Name] = contents
  327. }
  328. }
  329. return nil
  330. }
  331. func (provider *ProviderOnePassword) getAllForVault(vaultID string, ref esv1beta1.ExternalSecretFind, secretData map[string][]byte) error {
  332. items, err := provider.client.GetItems(vaultID)
  333. if err != nil {
  334. return fmt.Errorf(errGetItem, err)
  335. }
  336. for _, item := range items {
  337. if ref.Path != nil && *ref.Path != item.Title {
  338. continue
  339. }
  340. // handle files
  341. if item.Category == documentCategory {
  342. err = provider.getAllFiles(item, ref, secretData)
  343. if err != nil {
  344. return err
  345. }
  346. continue
  347. }
  348. // handle fields
  349. err = provider.getAllFields(item, ref, secretData)
  350. if err != nil {
  351. return err
  352. }
  353. }
  354. return nil
  355. }
  356. func countFieldsWithLabel(fieldLabel string, fields []*onepassword.ItemField) int {
  357. count := 0
  358. for _, field := range fields {
  359. if field.Label == fieldLabel {
  360. count++
  361. }
  362. }
  363. return count
  364. }
  365. type orderedVault struct {
  366. Name string
  367. Order int
  368. }
  369. type orderedVaultList []orderedVault
  370. func (list orderedVaultList) Len() int { return len(list) }
  371. func (list orderedVaultList) Swap(i, j int) { list[i], list[j] = list[j], list[i] }
  372. func (list orderedVaultList) Less(i, j int) bool { return list[i].Order < list[j].Order }
  373. func sortVaults(vaults map[string]int) []string {
  374. list := make(orderedVaultList, len(vaults))
  375. index := 0
  376. for key, value := range vaults {
  377. list[index] = orderedVault{key, value}
  378. index++
  379. }
  380. sort.Sort(list)
  381. sortedVaults := []string{}
  382. for _, item := range list {
  383. sortedVaults = append(sortedVaults, item.Name)
  384. }
  385. return sortedVaults
  386. }
  387. func hasUniqueVaultNumbers(vaults map[string]int) bool {
  388. unique := make([]int, 0, len(vaults))
  389. tracker := make(map[int]bool)
  390. for _, number := range vaults {
  391. if _, ok := tracker[number]; !ok {
  392. tracker[number] = true
  393. unique = append(unique, number)
  394. }
  395. }
  396. return len(vaults) == len(unique)
  397. }
  398. func init() {
  399. esv1beta1.Register(&ProviderOnePassword{}, &esv1beta1.SecretStoreProvider{
  400. OnePassword: &esv1beta1.OnePasswordProvider{},
  401. })
  402. }