validation.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /*
  2. Copyright © The ESO Authors
  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 mysterybox
  14. import (
  15. "errors"
  16. "fmt"
  17. "net"
  18. "regexp"
  19. "strings"
  20. "golang.org/x/net/idna"
  21. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  22. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  23. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  24. "github.com/external-secrets/external-secrets/runtime/esutils"
  25. )
  26. const (
  27. errNilStore = "found nil store"
  28. errMissingStoreSpec = "store is missing spec"
  29. errMissingProvider = "storeSpec is missing provider"
  30. errInvalidProvider = "invalid provider spec. Missing nebiusmysterybox field in store %s"
  31. errMissingAuthOptions = "invalid auth configuration: none provided"
  32. errInvalidAuthConfig = "invalid auth configuration: exactly one must be specified"
  33. errInvalidTokenAuthConfig = "invalid token auth configuration: no secret key specified"
  34. errInvalidSACredsAuthConfig = "invalid ServiceAccount creds auth configuration: no secret key specified"
  35. errFailedToRetrieveToken = "failed to retrieve iam token by credentials: %w"
  36. errMissingAPIDomain = "API domain must be set"
  37. errInvalidAPIDomain = "API domain is not valid"
  38. errInvalidAPIDomainWithError = errInvalidAPIDomain + ": %w"
  39. errInvalidCertificateConfigNoNameSpecified = "invalid certificate configuration: no name specified"
  40. errInvalidCertificateConfigNoKeySpecified = "invalid certificate configuration: no key specified"
  41. )
  42. var labelRe = regexp.MustCompile(`^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$`)
  43. var numericRe = regexp.MustCompile(`^\d+$`)
  44. // ValidateStore validates the given Store and its associated properties for correctness and configuration integrity.
  45. func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
  46. provider, err := getNebiusMysteryboxProvider(store)
  47. if err != nil {
  48. return nil, err
  49. }
  50. err = p.validateAPIDomain(provider.APIDomain)
  51. if err != nil {
  52. return nil, err
  53. }
  54. if err := validateProviderAuth(provider); err != nil {
  55. return nil, err
  56. }
  57. var selectors []*esmeta.SecretKeySelector
  58. if provider.Auth.Token.Name != "" {
  59. selectors = append(selectors, &provider.Auth.Token)
  60. }
  61. if provider.Auth.ServiceAccountCreds.Name != "" {
  62. selectors = append(selectors, &provider.Auth.ServiceAccountCreds)
  63. }
  64. if provider.CAProvider != nil {
  65. err := validateCertificate(&provider.CAProvider.Certificate)
  66. if err != nil {
  67. return nil, err
  68. }
  69. selectors = append(selectors, &provider.CAProvider.Certificate)
  70. }
  71. for _, selector := range selectors {
  72. if err := esutils.ValidateSecretSelector(store, *selector); err != nil {
  73. return nil, err
  74. }
  75. if err := esutils.ValidateReferentSecretSelector(store, *selector); err != nil {
  76. return nil, err
  77. }
  78. }
  79. return nil, nil
  80. }
  81. func getNebiusMysteryboxProvider(store esv1.GenericStore) (*esv1.NebiusMysteryboxProvider, error) {
  82. if store == nil {
  83. return nil, errors.New(errNilStore)
  84. }
  85. spc := store.GetSpec()
  86. if spc == nil {
  87. return nil, errors.New(errMissingStoreSpec)
  88. }
  89. if spc.Provider == nil {
  90. return nil, errors.New(errMissingProvider)
  91. }
  92. provider := spc.Provider.NebiusMysterybox
  93. if provider == nil {
  94. return nil, fmt.Errorf(errInvalidProvider, store.GetNamespacedName())
  95. }
  96. return provider, nil
  97. }
  98. func validateProviderAuth(provider *esv1.NebiusMysteryboxProvider) error {
  99. if provider.Auth.Token.Name == "" && provider.Auth.ServiceAccountCreds.Name == "" {
  100. return errors.New(errMissingAuthOptions)
  101. }
  102. if provider.Auth.Token.Name != "" && provider.Auth.ServiceAccountCreds.Name != "" {
  103. return errors.New(errInvalidAuthConfig)
  104. }
  105. if provider.Auth.Token.Name != "" && provider.Auth.Token.Key == "" {
  106. return errors.New(errInvalidTokenAuthConfig)
  107. }
  108. if provider.Auth.ServiceAccountCreds.Name != "" && provider.Auth.ServiceAccountCreds.Key == "" {
  109. return errors.New(errInvalidSACredsAuthConfig)
  110. }
  111. return nil
  112. }
  113. func (p *Provider) validateAPIDomain(apiDomain string) error {
  114. if strings.TrimSpace(apiDomain) == "" {
  115. return errors.New(errMissingAPIDomain)
  116. }
  117. if strings.Contains(apiDomain, "://") {
  118. return fmt.Errorf(errInvalidAPIDomainWithError, errors.New("scheme is not allowed"))
  119. }
  120. var err error
  121. host := apiDomain
  122. // API Domain is allowed without port, it will be added in sdk in case it's missed
  123. if strings.Contains(apiDomain, ":") {
  124. host, _, err = net.SplitHostPort(apiDomain)
  125. if err != nil {
  126. p.Logger.Info("Error parsing apiDomain", "err", err)
  127. return fmt.Errorf(errInvalidAPIDomainWithError, err)
  128. }
  129. }
  130. err = validateDomainByRFC(host)
  131. if err != nil {
  132. p.Logger.Info("Error validating apiDomain", "err", err)
  133. return fmt.Errorf(errInvalidAPIDomainWithError, err)
  134. }
  135. return nil
  136. }
  137. func validateDomainByRFC(domain string) error {
  138. if strings.HasSuffix(domain, ".") {
  139. return errors.New("domain must not end with a dot")
  140. }
  141. ascii, err := idna.Lookup.ToASCII(domain)
  142. if err != nil {
  143. return err
  144. }
  145. if ascii == "" || len(ascii) > 253 {
  146. return errors.New("domain length out of range")
  147. }
  148. labels := strings.Split(ascii, ".")
  149. if len(labels) < 2 {
  150. return errors.New("domain must contain at least one dot")
  151. }
  152. for _, l := range labels {
  153. if len(l) < 1 || len(l) > 63 {
  154. return errors.New("label length out of range")
  155. }
  156. if !labelRe.MatchString(l) {
  157. return errors.New("invalid label: " + l)
  158. }
  159. }
  160. tld := labels[len(labels)-1]
  161. if numericRe.MatchString(tld) {
  162. return errors.New("numeric TLD is not allowed")
  163. }
  164. return nil
  165. }
  166. func validateCertificate(certificate *esmeta.SecretKeySelector) error {
  167. if certificate.Name == "" {
  168. return errors.New(errInvalidCertificateConfigNoNameSpecified)
  169. }
  170. if certificate.Key == "" {
  171. return errors.New(errInvalidCertificateConfigNoKeySpecified)
  172. }
  173. return nil
  174. }