dsm.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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. "fmt"
  19. "io/ioutil"
  20. "net/http"
  21. "net/url"
  22. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  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 _ esv1beta1.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. )
  72. /*
  73. New creates an senhasegura DSM client based on ISO session
  74. */
  75. func New(isoSession *senhaseguraAuth.SenhaseguraIsoSession) (*DSM, error) {
  76. return &DSM{
  77. isoSession: isoSession,
  78. dsmClient: &DSM{},
  79. }, nil
  80. }
  81. /*
  82. GetSecret implements ESO interface and get a single secret from senhasegura provider with DSM service
  83. */
  84. func (dsm *DSM) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (resp []byte, err error) {
  85. appSecrets, err := dsm.FetchSecrets()
  86. if err != nil {
  87. return []byte(""), err
  88. }
  89. for _, v := range appSecrets.Application.Secrets {
  90. if ref.Key == v.Identity {
  91. // Return whole data content in json-encoded when ref.Property is empty
  92. if ref.Property == "" {
  93. jsonStr, err := json.Marshal(v.Data)
  94. if err != nil {
  95. return nil, err
  96. }
  97. return jsonStr, nil
  98. }
  99. // Return raw data content when ref.Property is provided
  100. for _, v2 := range v.Data {
  101. for k, v3 := range v2 {
  102. if k == ref.Property {
  103. resp = []byte(v3)
  104. return resp, nil
  105. }
  106. }
  107. }
  108. }
  109. }
  110. return []byte(""), esv1beta1.NoSecretErr
  111. }
  112. /*
  113. GetSecretMap implements ESO interface and returns miltiple k/v pairs from senhasegura provider with DSM service
  114. */
  115. func (dsm *DSM) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (secretData map[string][]byte, err error) {
  116. secretData = make(map[string][]byte)
  117. appSecrets, err := dsm.FetchSecrets()
  118. if err != nil {
  119. return secretData, err
  120. }
  121. for _, v := range appSecrets.Application.Secrets {
  122. if v.Identity == ref.Key {
  123. for _, v2 := range v.Data {
  124. for k, v3 := range v2 {
  125. secretData[k] = []byte(v3)
  126. }
  127. }
  128. }
  129. }
  130. return secretData, nil
  131. }
  132. /*
  133. GetAllSecrets implements ESO interface and returns multiple secrets from senhasegura provider with DSM service
  134. TODO: GetAllSecrets functionality is to get secrets from either regexp-matching against the names or via metadata label matching.
  135. https://github.com/external-secrets/external-secrets/pull/830#discussion_r858657107
  136. */
  137. func (dsm *DSM) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (secretData map[string][]byte, err error) {
  138. return nil, fmt.Errorf("GetAllSecrets not implemented yet")
  139. }
  140. /*
  141. fetchSecrets calls senhasegura DSM /iso/dapp/application API endpoint
  142. Return an IsoDappResponse with all related information from senhasegura provider with DSM service and error
  143. */
  144. func (dsm *DSM) FetchSecrets() (respObj IsoDappResponse, err error) {
  145. u, _ := url.ParseRequestURI(dsm.isoSession.URL)
  146. u.Path = "/iso/dapp/application"
  147. tr := &http.Transport{
  148. // nolint
  149. TLSClientConfig: &tls.Config{InsecureSkipVerify: dsm.isoSession.IgnoreSslCertificate},
  150. }
  151. client := &http.Client{Transport: tr}
  152. r, err := http.NewRequest("GET", u.String(), nil)
  153. if err != nil {
  154. return respObj, errCannotCreateRequest
  155. }
  156. r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  157. r.Header.Set("Authorization", "Bearer "+dsm.isoSession.Token)
  158. resp, err := client.Do(r)
  159. if err != nil {
  160. return respObj, errCannotDoRequest
  161. }
  162. defer resp.Body.Close()
  163. if resp.StatusCode != 200 {
  164. return respObj, errInvalidHTTPCode
  165. }
  166. respData, err := ioutil.ReadAll(resp.Body)
  167. if err != nil {
  168. return respObj, errInvalidResponseBody
  169. }
  170. err = json.Unmarshal(respData, &respObj)
  171. if err != nil {
  172. return respObj, errInvalidResponseBody
  173. }
  174. if respObj.Response.Error {
  175. return respObj, errApplicationError
  176. }
  177. return respObj, nil
  178. }
  179. /*
  180. Close implements ESO interface and do nothing in senhasegura
  181. */
  182. func (dsm *DSM) Close(ctx context.Context) error {
  183. return nil
  184. }
  185. // Validate if has valid connection with senhasegura, credentials, authorization using fetchSecrets method
  186. // fetchSecrets method implement required check about request
  187. // https://github.com/external-secrets/external-secrets/pull/830#discussion_r833275463
  188. func (dsm *DSM) Validate() (esv1beta1.ValidationResult, error) {
  189. _, err := dsm.FetchSecrets()
  190. if err != nil {
  191. return esv1beta1.ValidationResultError, err
  192. }
  193. return esv1beta1.ValidationResultReady, nil
  194. }