passbolt.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package passbolt
  14. import (
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "net/url"
  20. "regexp"
  21. "github.com/passbolt/go-passbolt/api"
  22. corev1 "k8s.io/api/core/v1"
  23. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  24. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  25. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  26. "github.com/external-secrets/external-secrets/pkg/utils"
  27. "github.com/external-secrets/external-secrets/pkg/utils/resolvers"
  28. )
  29. const (
  30. errPassboltStoreMissingProvider = "missing: spec.provider.passbolt"
  31. errPassboltStoreMissingAuth = "missing: spec.provider.passbolt.auth"
  32. errPassboltStoreMissingAuthPassword = "missing: spec.provider.passbolt.auth.passwordSecretRef"
  33. errPassboltStoreMissingAuthPrivateKey = "missing: spec.provider.passbolt.auth.privateKeySecretRef"
  34. errPassboltStoreMissingHost = "missing: spec.provider.passbolt.host"
  35. errPassboltExternalSecretMissingFindNameRegExp = "missing: find.name.regexp"
  36. errPassboltStoreHostSchemeNotHTTPS = "host Url has to be https scheme"
  37. errPassboltSecretPropertyInvalid = "property must be one of name, username, uri, password or description"
  38. errNotImplemented = "not implemented"
  39. )
  40. type ProviderPassbolt struct {
  41. client Client
  42. }
  43. func (provider *ProviderPassbolt) Capabilities() esv1.SecretStoreCapabilities {
  44. return esv1.SecretStoreReadOnly
  45. }
  46. type Client interface {
  47. CheckSession(ctx context.Context) bool
  48. Login(ctx context.Context) error
  49. Logout(ctx context.Context) error
  50. GetResource(ctx context.Context, resourceID string) (*api.Resource, error)
  51. GetResources(ctx context.Context, opts *api.GetResourcesOptions) ([]api.Resource, error)
  52. GetResourceType(ctx context.Context, typeID string) (*api.ResourceType, error)
  53. DecryptMessage(message string) (string, error)
  54. GetSecret(ctx context.Context, resourceID string) (*api.Secret, error)
  55. }
  56. func (provider *ProviderPassbolt) NewClient(ctx context.Context, store esv1.GenericStore, kube kclient.Client, namespace string) (esv1.SecretsClient, error) {
  57. config := store.GetSpec().Provider.Passbolt
  58. password, err := resolvers.SecretKeyRef(
  59. ctx,
  60. kube,
  61. store.GetKind(),
  62. namespace,
  63. config.Auth.PasswordSecretRef,
  64. )
  65. if err != nil {
  66. return nil, err
  67. }
  68. privateKey, err := resolvers.SecretKeyRef(
  69. ctx,
  70. kube,
  71. store.GetKind(),
  72. namespace,
  73. config.Auth.PrivateKeySecretRef,
  74. )
  75. if err != nil {
  76. return nil, err
  77. }
  78. client, err := api.NewClient(nil, "", config.Host, privateKey, password)
  79. if err != nil {
  80. return nil, err
  81. }
  82. provider.client = client
  83. return provider, nil
  84. }
  85. func (provider *ProviderPassbolt) SecretExists(_ context.Context, _ esv1.PushSecretRemoteRef) (bool, error) {
  86. return false, errors.New(errNotImplemented)
  87. }
  88. func (provider *ProviderPassbolt) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  89. if err := assureLoggedIn(ctx, provider.client); err != nil {
  90. return nil, err
  91. }
  92. secret, err := provider.getPassboltSecret(ctx, ref.Key)
  93. if err != nil {
  94. return nil, err
  95. }
  96. if ref.Property == "" {
  97. return utils.JSONMarshal(secret)
  98. }
  99. return secret.GetProp(ref.Property)
  100. }
  101. func (provider *ProviderPassbolt) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1.PushSecretData) error {
  102. return errors.New(errNotImplemented)
  103. }
  104. func (provider *ProviderPassbolt) DeleteSecret(_ context.Context, _ esv1.PushSecretRemoteRef) error {
  105. return errors.New(errNotImplemented)
  106. }
  107. func (provider *ProviderPassbolt) Validate() (esv1.ValidationResult, error) {
  108. return esv1.ValidationResultUnknown, nil
  109. }
  110. func (provider *ProviderPassbolt) GetSecretMap(_ context.Context, _ esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  111. return nil, errors.New(errNotImplemented)
  112. }
  113. func (provider *ProviderPassbolt) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  114. res := make(map[string][]byte)
  115. if ref.Name == nil || ref.Name.RegExp == "" {
  116. return res, errors.New(errPassboltExternalSecretMissingFindNameRegExp)
  117. }
  118. if err := assureLoggedIn(ctx, provider.client); err != nil {
  119. return nil, err
  120. }
  121. resources, err := provider.client.GetResources(ctx, &api.GetResourcesOptions{})
  122. if err != nil {
  123. return nil, err
  124. }
  125. nameRegexp, err := regexp.Compile(ref.Name.RegExp)
  126. if err != nil {
  127. return nil, err
  128. }
  129. for _, resource := range resources {
  130. if !nameRegexp.MatchString(resource.Name) {
  131. continue
  132. }
  133. secret, err := provider.getPassboltSecret(ctx, resource.ID)
  134. if err != nil {
  135. return nil, err
  136. }
  137. marshaled, err := utils.JSONMarshal(secret)
  138. if err != nil {
  139. return nil, err
  140. }
  141. res[resource.ID] = marshaled
  142. }
  143. return res, nil
  144. }
  145. func (provider *ProviderPassbolt) Close(ctx context.Context) error {
  146. return provider.client.Logout(ctx)
  147. }
  148. func (provider *ProviderPassbolt) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
  149. config := store.GetSpec().Provider.Passbolt
  150. if config == nil {
  151. return nil, errors.New(errPassboltStoreMissingProvider)
  152. }
  153. if config.Auth == nil {
  154. return nil, errors.New(errPassboltStoreMissingAuth)
  155. }
  156. if config.Auth.PasswordSecretRef == nil || config.Auth.PasswordSecretRef.Name == "" || config.Auth.PasswordSecretRef.Key == "" {
  157. return nil, errors.New(errPassboltStoreMissingAuthPassword)
  158. }
  159. if config.Auth.PrivateKeySecretRef == nil || config.Auth.PrivateKeySecretRef.Name == "" || config.Auth.PrivateKeySecretRef.Key == "" {
  160. return nil, errors.New(errPassboltStoreMissingAuthPrivateKey)
  161. }
  162. if config.Host == "" {
  163. return nil, errors.New(errPassboltStoreMissingHost)
  164. }
  165. host, err := url.Parse(config.Host)
  166. if err != nil {
  167. return nil, err
  168. }
  169. if host.Scheme != "https" {
  170. return nil, errors.New(errPassboltStoreHostSchemeNotHTTPS)
  171. }
  172. return nil, nil
  173. }
  174. func init() {
  175. esv1.Register(&ProviderPassbolt{}, &esv1.SecretStoreProvider{
  176. Passbolt: &esv1.PassboltProvider{},
  177. }, esv1.MaintenanceStatusNotMaintained)
  178. }
  179. type Secret struct {
  180. Name string `json:"name"`
  181. Username string `json:"username"`
  182. Password string `json:"password"`
  183. URI string `json:"uri"`
  184. Description string `json:"description"`
  185. }
  186. func (ps Secret) GetProp(key string) ([]byte, error) {
  187. switch key {
  188. case "name":
  189. return []byte(ps.Name), nil
  190. case "username":
  191. return []byte(ps.Username), nil
  192. case "uri":
  193. return []byte(ps.URI), nil
  194. case "password":
  195. return []byte(ps.Password), nil
  196. case "description":
  197. return []byte(ps.Description), nil
  198. default:
  199. return nil, errors.New(errPassboltSecretPropertyInvalid)
  200. }
  201. }
  202. func (provider *ProviderPassbolt) getPassboltSecret(ctx context.Context, id string) (*Secret, error) {
  203. resource, err := provider.client.GetResource(ctx, id)
  204. if err != nil {
  205. return nil, err
  206. }
  207. secret, err := provider.client.GetSecret(ctx, resource.ID)
  208. if err != nil {
  209. return nil, err
  210. }
  211. res := Secret{
  212. Name: resource.Name,
  213. Username: resource.Username,
  214. URI: resource.URI,
  215. Description: resource.Description,
  216. }
  217. raw, err := provider.client.DecryptMessage(secret.Data)
  218. if err != nil {
  219. return nil, err
  220. }
  221. resourceType, err := provider.client.GetResourceType(ctx, resource.ResourceTypeID)
  222. if err != nil {
  223. return nil, err
  224. }
  225. switch resourceType.Slug {
  226. case "password-string":
  227. res.Password = raw
  228. case "password-and-description", "password-description-totp":
  229. var pwAndDesc api.SecretDataTypePasswordAndDescription
  230. if err := json.Unmarshal([]byte(raw), &pwAndDesc); err != nil {
  231. return nil, err
  232. }
  233. res.Password = pwAndDesc.Password
  234. res.Description = pwAndDesc.Description
  235. case "totp":
  236. default:
  237. return nil, fmt.Errorf("UnknownPassboltResourceType: %q", resourceType)
  238. }
  239. return &res, nil
  240. }
  241. func assureLoggedIn(ctx context.Context, client Client) error {
  242. if client.CheckSession(ctx) {
  243. return nil
  244. }
  245. return client.Login(ctx)
  246. }