server.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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 generator
  14. import (
  15. "context"
  16. "fmt"
  17. "reflect"
  18. "strings"
  19. apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  22. "k8s.io/apimachinery/pkg/runtime"
  23. "k8s.io/apimachinery/pkg/runtime/schema"
  24. "k8s.io/apimachinery/pkg/types"
  25. "sigs.k8s.io/controller-runtime/pkg/client"
  26. genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
  27. genpb "github.com/external-secrets/external-secrets/proto/generator"
  28. )
  29. // Server wraps v1 providers and generators and exposes them as v2 gRPC services.
  30. // This allows existing v1 provider and generator implementations to be used in the v2 architecture.
  31. type Server struct {
  32. genpb.UnimplementedGeneratorProviderServer
  33. kubeClient client.Client
  34. scheme *runtime.Scheme
  35. // we support multiple v1 generators, so we need to map the v2 generator
  36. // with apiVersion+kind to the corresponding v1 generator
  37. generatorMapping Mapping
  38. }
  39. // Mapping maps Kubernetes resources to their generator implementations.
  40. type Mapping map[schema.GroupVersionKind]genv1alpha1.Generator
  41. // NewServer creates a new generator adapter server.
  42. func NewServer(kubeClient client.Client, scheme *runtime.Scheme, generatorMapping Mapping) *Server {
  43. return &Server{
  44. kubeClient: kubeClient,
  45. scheme: scheme,
  46. generatorMapping: generatorMapping,
  47. }
  48. }
  49. // Generate creates a new secret or set of secrets using a v1 generator.
  50. func (s *Server) Generate(ctx context.Context, req *genpb.GenerateRequest) (*genpb.GenerateResponse, error) {
  51. if req == nil || req.GeneratorRef == nil {
  52. return nil, fmt.Errorf("request or generator ref is nil")
  53. }
  54. // Resolve the generator implementation and resource
  55. generator, jsonObj, err := s.getGenerator(ctx, req.GeneratorRef, req.SourceNamespace)
  56. if err != nil {
  57. return nil, fmt.Errorf("failed to get generator: %w", err)
  58. }
  59. // Call the v1 generator's Generate method
  60. secretMap, state, err := generator.Generate(ctx, jsonObj, s.kubeClient, req.SourceNamespace)
  61. if err != nil {
  62. return nil, fmt.Errorf("failed to generate secrets: %w", err)
  63. }
  64. // Convert state to bytes
  65. var stateBytes []byte
  66. if state != nil {
  67. stateBytes = state.Raw
  68. }
  69. return &genpb.GenerateResponse{
  70. Secrets: secretMap,
  71. State: stateBytes,
  72. }, nil
  73. }
  74. // Cleanup deletes any resources created during the Generate phase.
  75. func (s *Server) Cleanup(ctx context.Context, req *genpb.CleanupRequest) (*genpb.CleanupResponse, error) {
  76. if req == nil || req.GeneratorRef == nil {
  77. return nil, fmt.Errorf("request or generator ref is nil")
  78. }
  79. // Resolve the generator implementation and resource
  80. generator, jsonObj, err := s.getGenerator(ctx, req.GeneratorRef, req.SourceNamespace)
  81. if err != nil {
  82. return nil, fmt.Errorf("failed to get generator: %w", err)
  83. }
  84. // Unmarshal the state
  85. var state genv1alpha1.GeneratorProviderState
  86. if len(req.State) > 0 {
  87. state = &apiextensions.JSON{Raw: req.State}
  88. }
  89. // Call the v1 generator's Cleanup method
  90. err = generator.Cleanup(ctx, jsonObj, state, s.kubeClient, req.SourceNamespace)
  91. if err != nil {
  92. return nil, fmt.Errorf("failed to cleanup generator resources: %w", err)
  93. }
  94. return &genpb.CleanupResponse{}, nil
  95. }
  96. // getGenerator retrieves a generator implementation and the generator resource.
  97. // This is similar to runtime/esutils/resolvers/generator.go getGenerator function.
  98. func (s *Server) getGenerator(ctx context.Context, generatorRef *genpb.GeneratorRef, namespace string) (genv1alpha1.Generator, *apiextensions.JSON, error) {
  99. if generatorRef == nil {
  100. return nil, nil, fmt.Errorf("generator reference is nil")
  101. }
  102. // Parse the API version
  103. splitted := strings.Split(generatorRef.ApiVersion, "/")
  104. if len(splitted) != 2 {
  105. return nil, nil, fmt.Errorf("invalid api version: %s", generatorRef.ApiVersion)
  106. }
  107. group := splitted[0]
  108. version := splitted[1]
  109. // Construct the GVK
  110. gvk := schema.GroupVersionKind{
  111. Group: group,
  112. Version: version,
  113. Kind: generatorRef.Kind,
  114. }
  115. // Fail if the GVK does not use the generator group
  116. if gvk.Group != genv1alpha1.Group {
  117. return nil, nil, fmt.Errorf("generatorRef may only reference the generators group, but got %s", gvk.Group)
  118. }
  119. // Lookup the generator implementation from the mapping
  120. generator, ok := s.generatorMapping[gvk]
  121. if !ok {
  122. return nil, nil, fmt.Errorf("generator mapping not found for %q", gvk)
  123. }
  124. // Get a client Object from the GVK
  125. t, exists := s.scheme.AllKnownTypes()[gvk]
  126. if !exists {
  127. return nil, nil, fmt.Errorf("generatorRef references unknown GVK %s", gvk)
  128. }
  129. obj := reflect.New(t).Interface().(client.Object)
  130. // Handle ClusterGenerator specially
  131. if gvk.Kind == genv1alpha1.ClusterGeneratorKind {
  132. clusterGenerator := obj.(*genv1alpha1.ClusterGenerator)
  133. // Get the cluster generator resource from the API
  134. err := s.kubeClient.Get(ctx, client.ObjectKey{Name: generatorRef.Name}, clusterGenerator)
  135. if err != nil {
  136. return nil, nil, fmt.Errorf("failed to get ClusterGenerator: %w", err)
  137. }
  138. // Convert the cluster generator to a virtual namespaced generator object
  139. obj, err = s.clusterGeneratorToVirtual(clusterGenerator)
  140. if err != nil {
  141. return nil, nil, fmt.Errorf("invalid ClusterGenerator: %w", err)
  142. }
  143. } else {
  144. // Get the generator resource from the API
  145. nsName := types.NamespacedName{
  146. Name: generatorRef.Name,
  147. Namespace: namespace,
  148. }
  149. // Use the namespace from the ref if provided (for flexibility)
  150. if generatorRef.Namespace != "" {
  151. nsName.Namespace = generatorRef.Namespace
  152. }
  153. err := s.kubeClient.Get(ctx, nsName, obj)
  154. if err != nil {
  155. return nil, nil, fmt.Errorf("failed to get generator resource: %w", err)
  156. }
  157. }
  158. // Convert the generator to unstructured object
  159. u := &unstructured.Unstructured{}
  160. var unstructuredObj map[string]any
  161. var err error
  162. unstructuredObj, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
  163. if err != nil {
  164. return nil, nil, fmt.Errorf("failed to convert to unstructured: %w", err)
  165. }
  166. u.Object = unstructuredObj
  167. // Convert the unstructured object to JSON
  168. jsonObj, err := u.MarshalJSON()
  169. if err != nil {
  170. return nil, nil, fmt.Errorf("failed to marshal JSON: %w", err)
  171. }
  172. return generator, &apiextensions.JSON{Raw: jsonObj}, nil
  173. }
  174. // clusterGeneratorToVirtual converts a ClusterGenerator to a virtual namespaced generator.
  175. // This is adapted from runtime/esutils/resolvers/generator.go.
  176. func (s *Server) clusterGeneratorToVirtual(gen *genv1alpha1.ClusterGenerator) (client.Object, error) {
  177. switch gen.Spec.Kind {
  178. case genv1alpha1.GeneratorKindFake:
  179. if gen.Spec.Generator.FakeSpec == nil {
  180. return nil, fmt.Errorf("when kind is %s, FakeSpec must be set", gen.Spec.Kind)
  181. }
  182. return &genv1alpha1.Fake{
  183. TypeMeta: metav1.TypeMeta{
  184. APIVersion: genv1alpha1.SchemeGroupVersion.String(),
  185. Kind: genv1alpha1.FakeKind,
  186. },
  187. ObjectMeta: metav1.ObjectMeta{
  188. Name: gen.Name,
  189. },
  190. Spec: *gen.Spec.Generator.FakeSpec,
  191. }, nil
  192. case genv1alpha1.GeneratorKindACRAccessToken,
  193. genv1alpha1.GeneratorKindCloudsmithAccessToken,
  194. genv1alpha1.GeneratorKindECRAuthorizationToken,
  195. genv1alpha1.GeneratorKindGCRAccessToken,
  196. genv1alpha1.GeneratorKindGithubAccessToken,
  197. genv1alpha1.GeneratorKindGrafana,
  198. genv1alpha1.GeneratorKindMFA,
  199. genv1alpha1.GeneratorKindPassword,
  200. genv1alpha1.GeneratorKindQuayAccessToken,
  201. genv1alpha1.GeneratorKindSSHKey,
  202. genv1alpha1.GeneratorKindSTSSessionToken,
  203. genv1alpha1.GeneratorKindUUID,
  204. genv1alpha1.GeneratorKindVaultDynamicSecret,
  205. genv1alpha1.GeneratorKindWebhook:
  206. return nil, fmt.Errorf("unsupported generator kind: %s", gen.Spec.Kind)
  207. default:
  208. return nil, fmt.Errorf("unsupported generator kind: %s", gen.Spec.Kind)
  209. }
  210. }