passbolt.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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 passbolt
  13. import (
  14. "context"
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "net/url"
  19. "regexp"
  20. "github.com/passbolt/go-passbolt/api"
  21. corev1 "k8s.io/api/core/v1"
  22. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  23. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  24. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  25. esmeta "github.com/external-secrets/external-secrets/apis/meta/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() esv1beta1.SecretStoreCapabilities {
  44. return esv1beta1.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) ApplyReferent(spec kclient.Object, _ esmeta.ReferentCallOrigin, _ string) (kclient.Object, error) {
  57. return spec, nil
  58. }
  59. func (provider *ProviderPassbolt) Convert(_ esv1beta1.GenericStore) (kclient.Object, error) {
  60. return nil, nil
  61. }
  62. func (provider *ProviderPassbolt) NewClientFromObj(_ context.Context, _ kclient.Object, _ kclient.Client, _ string) (esv1beta1.SecretsClient, error) {
  63. return nil, fmt.Errorf("not implemented")
  64. }
  65. func (provider *ProviderPassbolt) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
  66. config := store.GetSpec().Provider.Passbolt
  67. password, err := resolvers.SecretKeyRef(
  68. ctx,
  69. kube,
  70. store.GetKind(),
  71. namespace,
  72. config.Auth.PasswordSecretRef,
  73. )
  74. if err != nil {
  75. return nil, err
  76. }
  77. privateKey, err := resolvers.SecretKeyRef(
  78. ctx,
  79. kube,
  80. store.GetKind(),
  81. namespace,
  82. config.Auth.PrivateKeySecretRef,
  83. )
  84. if err != nil {
  85. return nil, err
  86. }
  87. client, err := api.NewClient(nil, "", config.Host, privateKey, password)
  88. if err != nil {
  89. return nil, err
  90. }
  91. provider.client = client
  92. return provider, nil
  93. }
  94. func (provider *ProviderPassbolt) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
  95. return false, errors.New(errNotImplemented)
  96. }
  97. func (provider *ProviderPassbolt) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  98. if err := assureLoggedIn(ctx, provider.client); err != nil {
  99. return nil, err
  100. }
  101. secret, err := provider.getPassboltSecret(ctx, ref.Key)
  102. if err != nil {
  103. return nil, err
  104. }
  105. if ref.Property == "" {
  106. return utils.JSONMarshal(secret)
  107. }
  108. return secret.GetProp(ref.Property)
  109. }
  110. func (provider *ProviderPassbolt) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
  111. return errors.New(errNotImplemented)
  112. }
  113. func (provider *ProviderPassbolt) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
  114. return errors.New(errNotImplemented)
  115. }
  116. func (provider *ProviderPassbolt) Validate() (esv1beta1.ValidationResult, error) {
  117. return esv1beta1.ValidationResultUnknown, nil
  118. }
  119. func (provider *ProviderPassbolt) GetSecretMap(_ context.Context, _ esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  120. return nil, errors.New(errNotImplemented)
  121. }
  122. func (provider *ProviderPassbolt) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  123. res := make(map[string][]byte)
  124. if ref.Name == nil || ref.Name.RegExp == "" {
  125. return res, errors.New(errPassboltExternalSecretMissingFindNameRegExp)
  126. }
  127. if err := assureLoggedIn(ctx, provider.client); err != nil {
  128. return nil, err
  129. }
  130. resources, err := provider.client.GetResources(ctx, &api.GetResourcesOptions{})
  131. if err != nil {
  132. return nil, err
  133. }
  134. nameRegexp, err := regexp.Compile(ref.Name.RegExp)
  135. if err != nil {
  136. return nil, err
  137. }
  138. for _, resource := range resources {
  139. if !nameRegexp.MatchString(resource.Name) {
  140. continue
  141. }
  142. secret, err := provider.getPassboltSecret(ctx, resource.ID)
  143. if err != nil {
  144. return nil, err
  145. }
  146. marshaled, err := utils.JSONMarshal(secret)
  147. if err != nil {
  148. return nil, err
  149. }
  150. res[resource.ID] = marshaled
  151. }
  152. return res, nil
  153. }
  154. func (provider *ProviderPassbolt) Close(ctx context.Context) error {
  155. return provider.client.Logout(ctx)
  156. }
  157. func (provider *ProviderPassbolt) ValidateStore(store esv1beta1.GenericStore) (admission.Warnings, error) {
  158. config := store.GetSpec().Provider.Passbolt
  159. if config == nil {
  160. return nil, errors.New(errPassboltStoreMissingProvider)
  161. }
  162. if config.Auth == nil {
  163. return nil, errors.New(errPassboltStoreMissingAuth)
  164. }
  165. if config.Auth.PasswordSecretRef == nil || config.Auth.PasswordSecretRef.Name == "" || config.Auth.PasswordSecretRef.Key == "" {
  166. return nil, errors.New(errPassboltStoreMissingAuthPassword)
  167. }
  168. if config.Auth.PrivateKeySecretRef == nil || config.Auth.PrivateKeySecretRef.Name == "" || config.Auth.PrivateKeySecretRef.Key == "" {
  169. return nil, errors.New(errPassboltStoreMissingAuthPrivateKey)
  170. }
  171. if config.Host == "" {
  172. return nil, errors.New(errPassboltStoreMissingHost)
  173. }
  174. host, err := url.Parse(config.Host)
  175. if err != nil {
  176. return nil, err
  177. }
  178. if host.Scheme != "https" {
  179. return nil, errors.New(errPassboltStoreHostSchemeNotHTTPS)
  180. }
  181. return nil, nil
  182. }
  183. func init() {
  184. esv1beta1.Register(&ProviderPassbolt{}, &esv1beta1.SecretStoreProvider{
  185. Passbolt: &esv1beta1.PassboltProvider{},
  186. })
  187. }
  188. type Secret struct {
  189. Name string `json:"name"`
  190. Username string `json:"username"`
  191. Password string `json:"password"`
  192. URI string `json:"uri"`
  193. Description string `json:"description"`
  194. }
  195. func (ps Secret) GetProp(key string) ([]byte, error) {
  196. switch key {
  197. case "name":
  198. return []byte(ps.Name), nil
  199. case "username":
  200. return []byte(ps.Username), nil
  201. case "uri":
  202. return []byte(ps.URI), nil
  203. case "password":
  204. return []byte(ps.Password), nil
  205. case "description":
  206. return []byte(ps.Description), nil
  207. default:
  208. return nil, errors.New(errPassboltSecretPropertyInvalid)
  209. }
  210. }
  211. func (provider *ProviderPassbolt) getPassboltSecret(ctx context.Context, id string) (*Secret, error) {
  212. resource, err := provider.client.GetResource(ctx, id)
  213. if err != nil {
  214. return nil, err
  215. }
  216. secret, err := provider.client.GetSecret(ctx, resource.ID)
  217. if err != nil {
  218. return nil, err
  219. }
  220. res := Secret{
  221. Name: resource.Name,
  222. Username: resource.Username,
  223. URI: resource.URI,
  224. Description: resource.Description,
  225. }
  226. raw, err := provider.client.DecryptMessage(secret.Data)
  227. if err != nil {
  228. return nil, err
  229. }
  230. resourceType, err := provider.client.GetResourceType(ctx, resource.ResourceTypeID)
  231. if err != nil {
  232. return nil, err
  233. }
  234. switch resourceType.Slug {
  235. case "password-string":
  236. res.Password = raw
  237. case "password-and-description", "password-description-totp":
  238. var pwAndDesc api.SecretDataTypePasswordAndDescription
  239. if err := json.Unmarshal([]byte(raw), &pwAndDesc); err != nil {
  240. return nil, err
  241. }
  242. res.Password = pwAndDesc.Password
  243. res.Description = pwAndDesc.Description
  244. case "totp":
  245. default:
  246. return nil, fmt.Errorf("UnknownPassboltResourceType: %q", resourceType)
  247. }
  248. return &res, nil
  249. }
  250. func assureLoggedIn(ctx context.Context, client Client) error {
  251. if client.CheckSession(ctx) {
  252. return nil
  253. }
  254. return client.Login(ctx)
  255. }