dsm.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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 dsm
  13. import (
  14. "context"
  15. "crypto/tls"
  16. "encoding/json"
  17. "errors"
  18. "io"
  19. "net/http"
  20. "net/url"
  21. corev1 "k8s.io/api/core/v1"
  22. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  23. senhaseguraAuth "github.com/external-secrets/external-secrets/pkg/provider/senhasegura/auth"
  24. )
  25. type clientDSMInterface interface {
  26. FetchSecrets() (respObj IsoDappResponse, err error)
  27. }
  28. // https://github.com/external-secrets/external-secrets/issues/644
  29. var _ esv1.SecretsClient = &DSM{}
  30. /*
  31. DSM service for SenhaseguraProvider.
  32. */
  33. type DSM struct {
  34. isoSession *senhaseguraAuth.SenhaseguraIsoSession
  35. dsmClient clientDSMInterface
  36. }
  37. /*
  38. IsoDappResponse is a response object from senhasegura /iso/dapp/response (DevOps Secrets Management API endpoint)
  39. Contains information about API request and Secrets linked with authorization.
  40. */
  41. type IsoDappResponse struct {
  42. Response struct {
  43. Status int `json:"status"`
  44. Message string `json:"message"`
  45. Error bool `json:"error"`
  46. ErrorCode int `json:"error_code"`
  47. } `json:"response"`
  48. Application struct {
  49. Name string `json:"name"`
  50. Description string `json:"description"`
  51. Tags []string `json:"tags"`
  52. System string `json:"system"`
  53. Environment string `json:"Environment"`
  54. Secrets []struct {
  55. SecretID string `json:"secret_id"`
  56. SecretName string `json:"secret_name"`
  57. Identity string `json:"identity"`
  58. Version string `json:"version"`
  59. ExpirationDate string `json:"expiration_date"`
  60. Engine string `json:"engine"`
  61. Data []map[string]string `json:"data"`
  62. } `json:"secrets"`
  63. } `json:"application"`
  64. }
  65. var (
  66. errCannotCreateRequest = errors.New("cannot create request to senhasegura resource /iso/dapp/application")
  67. errCannotDoRequest = errors.New("cannot do request in senhasegura, SSL certificate is valid ?")
  68. errInvalidResponseBody = errors.New("invalid HTTP response body received from senhasegura")
  69. errInvalidHTTPCode = errors.New("received invalid HTTP code from senhasegura")
  70. errApplicationError = errors.New("received application error from senhasegura")
  71. errNotImplemented = errors.New("not implemented")
  72. )
  73. /*
  74. New creates an senhasegura DSM client based on ISO session.
  75. */
  76. func New(isoSession *senhaseguraAuth.SenhaseguraIsoSession) (*DSM, error) {
  77. return &DSM{
  78. isoSession: isoSession,
  79. dsmClient: &DSM{},
  80. }, nil
  81. }
  82. func (dsm *DSM) DeleteSecret(_ context.Context, _ esv1.PushSecretRemoteRef) error {
  83. return errNotImplemented
  84. }
  85. func (dsm *DSM) SecretExists(_ context.Context, _ esv1.PushSecretRemoteRef) (bool, error) {
  86. return false, errNotImplemented
  87. }
  88. // Not Implemented PushSecret.
  89. func (dsm *DSM) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1.PushSecretData) error {
  90. return errNotImplemented
  91. }
  92. /*
  93. GetSecret implements ESO interface and get a single secret from senhasegura provider with DSM service.
  94. */
  95. func (dsm *DSM) GetSecret(_ context.Context, ref esv1.ExternalSecretDataRemoteRef) (resp []byte, err error) {
  96. appSecrets, err := dsm.FetchSecrets()
  97. if err != nil {
  98. return []byte(""), err
  99. }
  100. for _, v := range appSecrets.Application.Secrets {
  101. if ref.Key == v.Identity {
  102. // Return whole data content in json-encoded when ref.Property is empty
  103. if ref.Property == "" {
  104. jsonStr, err := json.Marshal(v.Data)
  105. if err != nil {
  106. return nil, err
  107. }
  108. return jsonStr, nil
  109. }
  110. // Return raw data content when ref.Property is provided
  111. for _, v2 := range v.Data {
  112. for k, v3 := range v2 {
  113. if k == ref.Property {
  114. resp = []byte(v3)
  115. return resp, nil
  116. }
  117. }
  118. }
  119. }
  120. }
  121. return []byte(""), esv1.NoSecretErr
  122. }
  123. /*
  124. GetSecretMap implements ESO interface and returns miltiple k/v pairs from senhasegura provider with DSM service.
  125. */
  126. func (dsm *DSM) GetSecretMap(_ context.Context, ref esv1.ExternalSecretDataRemoteRef) (secretData map[string][]byte, err error) {
  127. secretData = make(map[string][]byte)
  128. appSecrets, err := dsm.FetchSecrets()
  129. if err != nil {
  130. return secretData, err
  131. }
  132. for _, v := range appSecrets.Application.Secrets {
  133. if v.Identity == ref.Key {
  134. for _, v2 := range v.Data {
  135. for k, v3 := range v2 {
  136. secretData[k] = []byte(v3)
  137. }
  138. }
  139. }
  140. }
  141. return secretData, nil
  142. }
  143. /*
  144. GetAllSecrets implements ESO interface and returns multiple secrets from senhasegura provider with DSM service
  145. TODO: GetAllSecrets functionality is to get secrets from either regexp-matching against the names or via metadata label matching.
  146. https://github.com/external-secrets/external-secrets/pull/830#discussion_r858657107
  147. */
  148. func (dsm *DSM) GetAllSecrets(_ context.Context, _ esv1.ExternalSecretFind) (secretData map[string][]byte, err error) {
  149. return nil, errNotImplemented
  150. }
  151. /*
  152. fetchSecrets calls senhasegura DSM /iso/dapp/application API endpoint
  153. Return an IsoDappResponse with all related information from senhasegura provider with DSM service and error.
  154. */
  155. func (dsm *DSM) FetchSecrets() (respObj IsoDappResponse, err error) {
  156. u, _ := url.ParseRequestURI(dsm.isoSession.URL)
  157. u.Path = "/iso/dapp/application"
  158. tr := &http.Transport{
  159. //nolint
  160. TLSClientConfig: &tls.Config{InsecureSkipVerify: dsm.isoSession.IgnoreSslCertificate},
  161. }
  162. client := &http.Client{Transport: tr}
  163. r, err := http.NewRequest("GET", u.String(), http.NoBody)
  164. if err != nil {
  165. return respObj, errCannotCreateRequest
  166. }
  167. r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  168. r.Header.Set("Authorization", "Bearer "+dsm.isoSession.Token)
  169. resp, err := client.Do(r)
  170. if err != nil {
  171. return respObj, errCannotDoRequest
  172. }
  173. defer func() {
  174. _ = resp.Body.Close()
  175. }()
  176. if resp.StatusCode != 200 {
  177. return respObj, errInvalidHTTPCode
  178. }
  179. respData, err := io.ReadAll(resp.Body)
  180. if err != nil {
  181. return respObj, errInvalidResponseBody
  182. }
  183. err = json.Unmarshal(respData, &respObj)
  184. if err != nil {
  185. return respObj, errInvalidResponseBody
  186. }
  187. if respObj.Response.Error {
  188. return respObj, errApplicationError
  189. }
  190. return respObj, nil
  191. }
  192. /*
  193. Close implements ESO interface and do nothing in senhasegura.
  194. */
  195. func (dsm *DSM) Close(_ context.Context) error {
  196. return nil
  197. }
  198. // Validate if has valid connection with senhasegura, credentials, authorization using fetchSecrets method
  199. // fetchSecrets method implement required check about request
  200. // https://github.com/external-secrets/external-secrets/pull/830#discussion_r833275463
  201. func (dsm *DSM) Validate() (esv1.ValidationResult, error) {
  202. _, err := dsm.FetchSecrets()
  203. if err != nil {
  204. return esv1.ValidationResultError, err
  205. }
  206. return esv1.ValidationResultReady, nil
  207. }