passbolt.go 8.2 KB

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