diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 1dafe90634..12ac6ec308 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -13,6 +13,7 @@ resources: - bases/configuration.konghq.com_kongplugins.yaml - bases/configuration.konghq.com_ingressclassparameterses.yaml - bases/configuration.konghq.com_kongupstreampolicies.yaml +- bases/configuration.konghq.com_kongvaults.yaml #+kubebuilder:scaffold:crdkustomizeresource # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index dc6f6f02b6..43e48dd44a 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -153,6 +153,22 @@ rules: - get - patch - update +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults + verbs: + - get + - list + - watch +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults/status + verbs: + - get + - patch + - update - apiGroups: - configuration.konghq.com resources: diff --git a/docs/cli-arguments.md b/docs/cli-arguments.md index 79036e7566..86a0abec43 100644 --- a/docs/cli-arguments.md +++ b/docs/cli-arguments.md @@ -27,6 +27,7 @@ | `--enable-controller-ingress-networkingv1` | `bool` | Enable the networking.k8s.io/v1 Ingress controller. | `true` | | `--enable-controller-kong-service-facade` | `bool` | Enable the KongServiceFacade controller. | `true` | | `--enable-controller-kong-upstream-policy` | `bool` | Enable the KongUpstreamPolicy controller. | `true` | +| `--enable-controller-kong-vault` | `bool` | Enable the KongVault controller. | `true` | | `--enable-controller-kongclusterplugin` | `bool` | Enable the KongClusterPlugin controller. | `true` | | `--enable-controller-kongconsumer` | `bool` | Enable the KongConsumer controller. | `true` | | `--enable-controller-kongingress` | `bool` | Enable the KongIngress controller. | `true` | diff --git a/hack/generators/controllers/networking/main.go b/hack/generators/controllers/networking/main.go index a0b1cc75ba..3adcc5ef1d 100644 --- a/hack/generators/controllers/networking/main.go +++ b/hack/generators/controllers/networking/main.go @@ -247,6 +247,23 @@ var inputControllersNeeded = &typesNeeded{ AcceptsIngressClassNameAnnotation: true, RBACVerbs: []string{"get", "list", "watch"}, }, + typeNeeded{ + Group: "configuration.konghq.com", + Version: "v1alpha1", + Kind: "KongVault", + PackageImportAlias: "kongv1alpha1", + PackageAlias: "KongV1Alpha1", + Package: kongv1alpha1, + Plural: "kongvaults", + CacheType: "KongVault", + NeedsStatusPermissions: true, + ConfigStatusNotificationsEnabled: true, + ProgrammedCondition: ProgrammedConditionConfiguration{ + UpdatesEnabled: true, + }, + AcceptsIngressClassNameAnnotation: true, + RBACVerbs: []string{"get", "list", "watch"}, + }, } var inputRBACPermissionsNeeded = &rbacsNeeded{ diff --git a/internal/controllers/configuration/zz_generated_controllers.go b/internal/controllers/configuration/zz_generated_controllers.go index ed4c3a8986..fadccec529 100644 --- a/internal/controllers/configuration/zz_generated_controllers.go +++ b/internal/controllers/configuration/zz_generated_controllers.go @@ -1903,6 +1903,176 @@ func (r *IncubatorV1Alpha1KongServiceFacadeReconciler) Reconcile(ctx context.Con return ctrl.Result{}, nil } +// ----------------------------------------------------------------------------- +// KongV1Alpha1 KongVault - Reconciler +// ----------------------------------------------------------------------------- + +// KongV1Alpha1KongVaultReconciler reconciles KongVault resources +type KongV1Alpha1KongVaultReconciler struct { + client.Client + + Log logr.Logger + Scheme *runtime.Scheme + DataplaneClient controllers.DataPlane + CacheSyncTimeout time.Duration + StatusQueue *status.Queue + + IngressClassName string + DisableIngressClassLookups bool +} + +var _ controllers.Reconciler = &KongV1Alpha1KongVaultReconciler{} + +// SetupWithManager sets up the controller with the Manager. +func (r *KongV1Alpha1KongVaultReconciler) SetupWithManager(mgr ctrl.Manager) error { + c, err := controller.New("KongV1Alpha1KongVault", mgr, controller.Options{ + Reconciler: r, + LogConstructor: func(_ *reconcile.Request) logr.Logger { + return r.Log + }, + CacheSyncTimeout: r.CacheSyncTimeout, + }) + if err != nil { + return err + } + // if configured, start the status updater controller + if r.StatusQueue != nil { + if err := c.Watch( + &source.Channel{Source: r.StatusQueue.Subscribe(schema.GroupVersionKind{ + Group: "configuration.konghq.com", + Version: "v1alpha1", + Kind: "KongVault", + })}, + &handler.EnqueueRequestForObject{}, + ); err != nil { + return err + } + } + if !r.DisableIngressClassLookups { + err = c.Watch( + source.Kind(mgr.GetCache(), &netv1.IngressClass{}), + handler.EnqueueRequestsFromMapFunc(r.listClassless), + predicate.NewPredicateFuncs(ctrlutils.IsDefaultIngressClass), + ) + if err != nil { + return err + } + } + preds := ctrlutils.GeneratePredicateFuncsForIngressClassFilter(r.IngressClassName) + return c.Watch( + source.Kind(mgr.GetCache(), &kongv1alpha1.KongVault{}), + &handler.EnqueueRequestForObject{}, + preds, + ) +} + +// listClassless finds and reconciles all objects without ingress class information +func (r *KongV1Alpha1KongVaultReconciler) listClassless(ctx context.Context, obj client.Object) []reconcile.Request { + resourceList := &kongv1alpha1.KongVaultList{} + if err := r.Client.List(ctx, resourceList); err != nil { + r.Log.Error(err, "Failed to list classless kongvaults") + return nil + } + var recs []reconcile.Request + for i, resource := range resourceList.Items { + if ctrlutils.IsIngressClassEmpty(&resourceList.Items[i]) { + recs = append(recs, reconcile.Request{ + NamespacedName: k8stypes.NamespacedName{ + Namespace: resource.Namespace, + Name: resource.Name, + }, + }) + } + } + return recs +} + +// SetLogger sets the logger. +func (r *KongV1Alpha1KongVaultReconciler) SetLogger(l logr.Logger) { + r.Log = l +} + +//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongvaults,verbs=get;list;watch +//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongvaults/status,verbs=get;update;patch + +// Reconcile processes the watched objects +func (r *KongV1Alpha1KongVaultReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := r.Log.WithValues("KongV1Alpha1KongVault", req.NamespacedName) + + // get the relevant object + obj := new(kongv1alpha1.KongVault) + + if err := r.Get(ctx, req.NamespacedName, obj); err != nil { + if apierrors.IsNotFound(err) { + obj.Namespace = req.Namespace + obj.Name = req.Name + + return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj) + } + return ctrl.Result{}, err + } + log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name) + + // clean the object up if it's being deleted + if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) { + log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongVault", "namespace", req.Namespace, "name", req.Name) + + objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj) + if err != nil { + return ctrl.Result{}, err + } + if objectExistsInCache { + if err := r.DataplaneClient.DeleteObject(obj); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache + } + return ctrl.Result{}, nil + } + + class := new(netv1.IngressClass) + if !r.DisableIngressClassLookups { + if err := r.Get(ctx, k8stypes.NamespacedName{Name: r.IngressClassName}, class); err != nil { + // we log this without taking action to support legacy configurations that only set ingressClassName or + // used the class annotation and did not create a corresponding IngressClass. We only need this to determine + // if the IngressClass is default or to configure default settings, and can assume no/no additional defaults + // if none exists. + log.V(util.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName) + } + } + // if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache + if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) { + log.V(util.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration", + "namespace", req.Namespace, "name", req.Name, "class", r.IngressClassName) + return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj) + } else { + log.V(util.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name, + "class", r.IngressClassName) + } + + // update the kong Admin API with the changes + if err := r.DataplaneClient.UpdateObject(obj); err != nil { + return ctrl.Result{}, err + } + // if status updates are enabled report the status for the object + if r.DataplaneClient.AreKubernetesObjectReportsEnabled() { + log.V(util.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name) + configurationStatus := r.DataplaneClient.KubernetesObjectConfigurationStatus(obj) + conditions, updateNeeded := ctrlutils.EnsureProgrammedCondition( + configurationStatus, + obj.Generation, + obj.Status.Conditions, + ) + obj.Status.Conditions = conditions + if updateNeeded { + return ctrl.Result{}, r.Status().Update(ctx, obj) + } + log.V(util.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name) + } + + return ctrl.Result{}, nil +} + // ----------------------------------------------------------------------------- // API Group "" resource nodes // ----------------------------------------------------------------------------- diff --git a/internal/dataplane/deckgen/generate.go b/internal/dataplane/deckgen/generate.go index 382bb1f7f5..8445c68e75 100644 --- a/internal/dataplane/deckgen/generate.go +++ b/internal/dataplane/deckgen/generate.go @@ -203,6 +203,18 @@ func ToDeckContent( sort.SliceStable(content.Consumers, func(i, j int) bool { return strings.Compare(*content.Consumers[i].Username, *content.Consumers[j].Username) > 0 }) + + // convert vaults. + for _, v := range k8sState.Vaults { + vault := file.FVault{ + Vault: v.Vault, + } + content.Vaults = append(content.Vaults, vault) + } + sort.SliceStable(content.Vaults, func(i, j int) bool { + return (*content.Vaults[i].Prefix) > (*content.Vaults[j].Prefix) + }) + if len(params.SelectorTags) > 0 { content.Info = &file.Info{ SelectorTags: params.SelectorTags, diff --git a/internal/dataplane/kongstate/kongstate.go b/internal/dataplane/kongstate/kongstate.go index e582c95d42..38f6a90e1c 100644 --- a/internal/dataplane/kongstate/kongstate.go +++ b/internal/dataplane/kongstate/kongstate.go @@ -31,6 +31,7 @@ type KongState struct { Plugins []Plugin Consumers []Consumer ConsumerGroups []ConsumerGroup + Vaults []Vault } // SanitizedCopy returns a shallow copy with sensitive values redacted best-effort. @@ -59,6 +60,7 @@ func (ks *KongState) SanitizedCopy() *KongState { return }(), ConsumerGroups: ks.ConsumerGroups, + Vaults: ks.Vaults, } } @@ -239,6 +241,35 @@ func (ks *KongState) FillUpstreamOverrides( } } +func (ks *KongState) FillVaults( + logger logr.Logger, + s store.Storer, + failuresCollector *failures.ResourceFailuresCollector, +) { + for _, vault := range s.ListKongVaults() { + config, err := rawConfigToConfiguration(vault.Spec.Config.Raw) + if err != nil { + logger.Error(err, "failed to parse configuration of vault to JSON", "name", vault.Name) + failuresCollector.PushResourceFailure( + fmt.Sprintf("failed to parse configuration of vault %s to JSON: %v", vault.Name, err), + vault, + ) + continue + } + logger.V(util.DebugLevel).Info("add vault to kongstate", "name", vault.Name) + ks.Vaults = append(ks.Vaults, Vault{ + Vault: kong.Vault{ + Name: kong.String(vault.Spec.Backend), + Description: kong.String(vault.Spec.Description), + Prefix: kong.String(vault.Spec.Prefix), + Config: config, + Tags: util.GenerateTagsForObject(vault), + }, + K8sKongVault: vault.DeepCopy(), + }) + } +} + func (ks *KongState) getPluginRelations() map[string]util.ForeignRelations { // KongPlugin key (KongPlugin's name:namespace) to corresponding associations pluginRels := map[string]util.ForeignRelations{} @@ -487,6 +518,8 @@ func (ks *KongState) FillIDs(logger logr.Logger) { ks.ConsumerGroups[consumerGroupIndex] = consumerGroup } } + + // TODO: Add FillID() for vaults in go-kong to fill IDs for vaults. } // maybeLogKongIngressDeprecationError iterates over services and logs a deprecation error if a service diff --git a/internal/dataplane/kongstate/kongstate_test.go b/internal/dataplane/kongstate/kongstate_test.go index 90c069e38b..affbcd8707 100644 --- a/internal/dataplane/kongstate/kongstate_test.go +++ b/internal/dataplane/kongstate/kongstate_test.go @@ -55,6 +55,13 @@ func TestKongState_SanitizedCopy(t *testing.T) { ConsumerGroups: []ConsumerGroup{{ ConsumerGroup: kong.ConsumerGroup{ID: kong.String("1"), Name: kong.String("consumer-group")}, }}, + Vaults: []Vault{ + { + Vault: kong.Vault{ + Name: kong.String("test-vault"), Prefix: kong.String("test-vault"), + }, + }, + }, }, want: KongState{ Services: []Service{{Service: kong.Service{ID: kong.String("1")}}}, @@ -69,6 +76,13 @@ func TestKongState_SanitizedCopy(t *testing.T) { ConsumerGroups: []ConsumerGroup{{ ConsumerGroup: kong.ConsumerGroup{ID: kong.String("1"), Name: kong.String("consumer-group")}, }}, + Vaults: []Vault{ + { + Vault: kong.Vault{ + Name: kong.String("test-vault"), Prefix: kong.String("test-vault"), + }, + }, + }, }, }, } { diff --git a/internal/dataplane/kongstate/vault.go b/internal/dataplane/kongstate/vault.go new file mode 100644 index 0000000000..4a53d9a0fb --- /dev/null +++ b/internal/dataplane/kongstate/vault.go @@ -0,0 +1,13 @@ +package kongstate + +import ( + "github.com/kong/go-kong/kong" + + kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1" +) + +type Vault struct { + kong.Vault + + K8sKongVault *kongv1alpha1.KongVault +} diff --git a/internal/dataplane/translator/translator.go b/internal/dataplane/translator/translator.go index ba239733d9..8bc338f8d4 100644 --- a/internal/dataplane/translator/translator.go +++ b/internal/dataplane/translator/translator.go @@ -180,6 +180,11 @@ func (t *Translator) BuildKongConfig() KongConfigBuildingResult { t.registerSuccessfullyTranslatedObject(&result.Consumers[i].K8sKongConsumer) } + result.FillVaults(t.logger, t.storer, t.failuresCollector) + for i := range result.Vaults { + t.registerSuccessfullyTranslatedObject(result.Vaults[i].K8sKongVault) + } + // process consumer groups result.FillConsumerGroups(t.logger, t.storer) for i := range result.ConsumerGroups { diff --git a/internal/manager/config.go b/internal/manager/config.go index 5122be0935..ae40c81b9e 100644 --- a/internal/manager/config.go +++ b/internal/manager/config.go @@ -109,6 +109,7 @@ type Config struct { ServiceEnabled bool KongUpstreamPolicyEnabled bool KongServiceFacadeEnabled bool + KongVaultEnabled bool // Gateway API toggling. GatewayAPIGatewayController bool @@ -249,6 +250,7 @@ func (c *Config) FlagSet() *pflag.FlagSet { flagSet.BoolVar(&c.GatewayAPIHTTPRouteController, "enable-controller-gwapi-httproute", true, "Enable the Gateway API HTTPRoute controller.") flagSet.BoolVar(&c.GatewayAPIReferenceGrantController, "enable-controller-gwapi-reference-grant", true, "Enable the Gateway API ReferenceGrant controller.") flagSet.BoolVar(&c.KongServiceFacadeEnabled, "enable-controller-kong-service-facade", true, "Enable the KongServiceFacade controller.") + flagSet.BoolVar(&c.KongVaultEnabled, "enable-controller-kong-vault", true, "Enable the KongVault controller.") // Admission Webhook server config flagSet.StringVar(&c.AdmissionServer.ListenAddr, "admission-webhook-listen", "off", diff --git a/internal/manager/controllerdef.go b/internal/manager/controllerdef.go index 5c64fcdedb..ed201983ea 100644 --- a/internal/manager/controllerdef.go +++ b/internal/manager/controllerdef.go @@ -285,6 +285,19 @@ func setupControllers( StatusQueue: kubernetesStatusQueue, }, }, + { + Enabled: c.KongVaultEnabled, + Controller: &configuration.KongV1Alpha1KongVaultReconciler{ + Client: mgr.GetClient(), + Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("KongVault"), + Scheme: mgr.GetScheme(), + DataplaneClient: dataplaneClient, + CacheSyncTimeout: c.CacheSyncTimeout, + IngressClassName: c.IngressClassName, + DisableIngressClassLookups: !c.IngressClassNetV1Enabled, + StatusQueue: kubernetesStatusQueue, + }, + }, // --------------------------------------------------------------------------- // Gateway API Controllers // --------------------------------------------------------------------------- diff --git a/internal/store/cache_stores.go b/internal/store/cache_stores.go index 289e526d9c..a4483fb2c2 100644 --- a/internal/store/cache_stores.go +++ b/internal/store/cache_stores.go @@ -50,6 +50,7 @@ type CacheStores struct { KongUpstreamPolicy cache.Store IngressClassParametersV1alpha1 cache.Store KongServiceFacade cache.Store + KongVault cache.Store l *sync.RWMutex } @@ -82,6 +83,7 @@ func NewCacheStores() CacheStores { KongUpstreamPolicy: cache.NewStore(keyFunc), IngressClassParametersV1alpha1: cache.NewStore(keyFunc), KongServiceFacade: cache.NewStore(keyFunc), + KongVault: cache.NewStore(clusterResourceKeyFunc), l: &sync.RWMutex{}, } @@ -188,6 +190,8 @@ func (c CacheStores) Get(obj runtime.Object) (item interface{}, exists bool, err return c.IngressClassParametersV1alpha1.Get(obj) case *incubatorv1alpha1.KongServiceFacade: return c.KongServiceFacade.Get(obj) + case *kongv1alpha1.KongVault: + return c.KongVault.Get(obj) } return nil, false, fmt.Errorf("%T is not a supported cache object type", obj) } @@ -252,6 +256,8 @@ func (c CacheStores) Add(obj runtime.Object) error { return c.IngressClassParametersV1alpha1.Add(obj) case *incubatorv1alpha1.KongServiceFacade: return c.KongServiceFacade.Add(obj) + case *kongv1alpha1.KongVault: + return c.KongVault.Add(obj) default: return fmt.Errorf("cannot add unsupported kind %q to the store", obj.GetObjectKind().GroupVersionKind()) } @@ -317,6 +323,8 @@ func (c CacheStores) Delete(obj runtime.Object) error { return c.IngressClassParametersV1alpha1.Delete(obj) case *incubatorv1alpha1.KongServiceFacade: return c.KongServiceFacade.Delete(obj) + case *kongv1alpha1.KongVault: + return c.KongVault.Delete(obj) default: return fmt.Errorf("cannot delete unsupported kind %q from the store", obj.GetObjectKind().GroupVersionKind()) } diff --git a/internal/store/fake_store.go b/internal/store/fake_store.go index 11e0ef1343..8df30fd15a 100644 --- a/internal/store/fake_store.go +++ b/internal/store/fake_store.go @@ -64,6 +64,7 @@ type FakeObjects struct { KongConsumerGroups []*kongv1beta1.KongConsumerGroup KongUpstreamPolicies []*kongv1beta1.KongUpstreamPolicy KongServiceFacades []*incubatorv1alpha1.KongServiceFacade + KongVaults []*kongv1alpha1.KongVault } // NewFakeStore creates a store backed by the objects passed in as arguments. @@ -218,6 +219,13 @@ func NewFakeStore( return nil, err } } + kongVaultStore := cache.NewStore(clusterResourceKeyFunc) + for _, v := range objects.KongVaults { + err := kongVaultStore.Add(v) + if err != nil { + return nil, err + } + } s = Store{ stores: CacheStores{ @@ -243,6 +251,7 @@ func NewFakeStore( IngressClassParametersV1alpha1: IngressClassParametersV1alpha1Store, KongUpstreamPolicy: kongUpstreamPolicyStore, KongServiceFacade: kongServiceFacade, + KongVault: kongVaultStore, }, ingressClass: annotations.DefaultIngressClass, isValidIngressClass: annotations.IngressClassValidatorFuncFromObjectMeta(annotations.DefaultIngressClass), @@ -279,6 +288,7 @@ func (objects FakeObjects) MarshalToYAML() ([]byte, error) { reflect.TypeOf(&kongv1.KongIngress{}): kongv1.SchemeGroupVersion.WithKind("KongIngress"), reflect.TypeOf(&kongv1.KongConsumer{}): kongv1.SchemeGroupVersion.WithKind("KongConsumer"), reflect.TypeOf(&kongv1beta1.KongConsumerGroup{}): kongv1beta1.SchemeGroupVersion.WithKind("KongConsumerGroup"), + reflect.TypeOf(&kongv1alpha1.KongVault{}): kongv1alpha1.SchemeGroupVersion.WithKind(kongv1alpha1.KongVaultKind), } out := &bytes.Buffer{} @@ -321,6 +331,7 @@ func (objects FakeObjects) MarshalToYAML() ([]byte, error) { allObjects = append(allObjects, lo.ToAnySlice(objects.KongIngresses)...) allObjects = append(allObjects, lo.ToAnySlice(objects.KongConsumers)...) allObjects = append(allObjects, lo.ToAnySlice(objects.KongConsumerGroups)...) + allObjects = append(allObjects, lo.ToAnySlice(objects.KongVaults)...) for _, obj := range allObjects { if err := fillGVKAndAppendToBuffer(obj.(runtime.Object)); err != nil { diff --git a/internal/store/store.go b/internal/store/store.go index b7dea5a348..56eb4fe1ef 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -69,6 +69,7 @@ type Storer interface { GetGateway(namespace string, name string) (*gatewayapi.Gateway, error) GetKongUpstreamPolicy(namespace, name string) (*kongv1beta1.KongUpstreamPolicy, error) GetKongServiceFacade(namespace, name string) (*incubatorv1alpha1.KongServiceFacade, error) + GetKongVault(name string) (*kongv1alpha1.KongVault, error) ListIngressesV1() []*netv1.Ingress ListIngressClassesV1() []*netv1.IngressClass @@ -88,6 +89,7 @@ type Storer interface { ListKongConsumers() []*kongv1.KongConsumer ListKongConsumerGroups() []*kongv1beta1.KongConsumerGroup ListCACerts() ([]*corev1.Secret, error) + ListKongVaults() []*kongv1alpha1.KongVault } // Store implements Storer and can be used to list Ingress, Services @@ -571,6 +573,18 @@ func (s Store) GetGateway(namespace string, name string) (*gatewayapi.Gateway, e return obj.(*gatewayapi.Gateway), nil } +// GetKongVault returns kongvault resource having specified name. +func (s Store) GetKongVault(name string) (*kongv1alpha1.KongVault, error) { + p, exists, err := s.stores.KongVault.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, NotFoundError{fmt.Sprintf("KongVault %v not found", name)} + } + return p.(*kongv1alpha1.KongVault), nil +} + // ListKongConsumers returns all KongConsumers filtered by the ingress.class // annotation. func (s Store) ListKongConsumers() []*kongv1.KongConsumer { @@ -670,6 +684,18 @@ func (s Store) ListCACerts() ([]*corev1.Secret, error) { return secrets, nil } +func (s Store) ListKongVaults() []*kongv1alpha1.KongVault { + var kongVaults []*kongv1alpha1.KongVault + for _, obj := range s.stores.KongVault.List() { + kongVault, ok := obj.(*kongv1alpha1.KongVault) + if ok && s.isValidIngressClass(&kongVault.ObjectMeta, annotations.IngressClassKey, s.getIngressClassHandling()) { + kongVaults = append(kongVaults, kongVault) + } + } + s.logger.V(util.DebugLevel).Info("listed kong vaults", "number", len(kongVaults)) + return kongVaults +} + // getIngressClassHandling returns annotations.ExactOrEmptyClassMatch if an IngressClass is the default class, or // annotations.ExactClassMatch if the IngressClass is not default or does not exist. func (s Store) getIngressClassHandling() annotations.ClassMatching { diff --git a/test/e2e/manifests/all-in-one-dbless-k4k8s-enterprise.yaml b/test/e2e/manifests/all-in-one-dbless-k4k8s-enterprise.yaml index 117abe0bd4..1acbbc511c 100644 --- a/test/e2e/manifests/all-in-one-dbless-k4k8s-enterprise.yaml +++ b/test/e2e/manifests/all-in-one-dbless-k4k8s-enterprise.yaml @@ -1981,6 +1981,184 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: kongvaults.configuration.konghq.com +spec: + group: configuration.konghq.com + names: + categories: + - kong-ingress-controller + kind: KongVault + listKind: KongVaultList + plural: kongvaults + shortNames: + - kv + singular: kongvault + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Name of the backend of the vault + jsonPath: .spec.backend + name: Backend Type + type: string + - description: Prefix of vault URI to reference the values in the vault + jsonPath: .spec.prefix + name: Prefix + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Description + jsonPath: .spec.description + name: Description + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: 'KongVault is the schema for kongvaults API which defines a custom + Kong vault. A Kong vault is a storage to store sensitive data, where the + values can be referenced in configuration of plugins. See: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: KongVaultSpec defines specification of a custom Kong vault. + properties: + backend: + description: 'Backend is the type of the backend storing the secrets + in the vault. The supported backends of Kong is listed here: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/backends/' + minLength: 1 + type: string + config: + description: Config is the configuration of the vault. Varies for + different backends. + x-kubernetes-preserve-unknown-fields: true + description: + description: Description is the additional information about the vault. + type: string + prefix: + description: Prefix is the prefix of vault URI for referencing values + in the vault. + minLength: 1 + type: string + required: + - backend + - prefix + type: object + status: + description: KongVaultStatus represents the current status of the KongVault + resource. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the KongVaultStatus. + \n Known condition types are: \n * \"Programmed\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + required: + - conditions + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.13.0 @@ -2538,6 +2716,22 @@ rules: - get - patch - update +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults + verbs: + - get + - list + - watch +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults/status + verbs: + - get + - patch + - update - apiGroups: - configuration.konghq.com resources: diff --git a/test/e2e/manifests/all-in-one-dbless-konnect-enterprise.yaml b/test/e2e/manifests/all-in-one-dbless-konnect-enterprise.yaml index 688c3f2d63..9c7f36a143 100644 --- a/test/e2e/manifests/all-in-one-dbless-konnect-enterprise.yaml +++ b/test/e2e/manifests/all-in-one-dbless-konnect-enterprise.yaml @@ -1981,6 +1981,184 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: kongvaults.configuration.konghq.com +spec: + group: configuration.konghq.com + names: + categories: + - kong-ingress-controller + kind: KongVault + listKind: KongVaultList + plural: kongvaults + shortNames: + - kv + singular: kongvault + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Name of the backend of the vault + jsonPath: .spec.backend + name: Backend Type + type: string + - description: Prefix of vault URI to reference the values in the vault + jsonPath: .spec.prefix + name: Prefix + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Description + jsonPath: .spec.description + name: Description + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: 'KongVault is the schema for kongvaults API which defines a custom + Kong vault. A Kong vault is a storage to store sensitive data, where the + values can be referenced in configuration of plugins. See: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: KongVaultSpec defines specification of a custom Kong vault. + properties: + backend: + description: 'Backend is the type of the backend storing the secrets + in the vault. The supported backends of Kong is listed here: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/backends/' + minLength: 1 + type: string + config: + description: Config is the configuration of the vault. Varies for + different backends. + x-kubernetes-preserve-unknown-fields: true + description: + description: Description is the additional information about the vault. + type: string + prefix: + description: Prefix is the prefix of vault URI for referencing values + in the vault. + minLength: 1 + type: string + required: + - backend + - prefix + type: object + status: + description: KongVaultStatus represents the current status of the KongVault + resource. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the KongVaultStatus. + \n Known condition types are: \n * \"Programmed\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + required: + - conditions + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.13.0 @@ -2538,6 +2716,22 @@ rules: - get - patch - update +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults + verbs: + - get + - list + - watch +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults/status + verbs: + - get + - patch + - update - apiGroups: - configuration.konghq.com resources: diff --git a/test/e2e/manifests/all-in-one-dbless-konnect.yaml b/test/e2e/manifests/all-in-one-dbless-konnect.yaml index c52ce3400b..81dc2c9d12 100644 --- a/test/e2e/manifests/all-in-one-dbless-konnect.yaml +++ b/test/e2e/manifests/all-in-one-dbless-konnect.yaml @@ -1981,6 +1981,184 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: kongvaults.configuration.konghq.com +spec: + group: configuration.konghq.com + names: + categories: + - kong-ingress-controller + kind: KongVault + listKind: KongVaultList + plural: kongvaults + shortNames: + - kv + singular: kongvault + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Name of the backend of the vault + jsonPath: .spec.backend + name: Backend Type + type: string + - description: Prefix of vault URI to reference the values in the vault + jsonPath: .spec.prefix + name: Prefix + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Description + jsonPath: .spec.description + name: Description + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: 'KongVault is the schema for kongvaults API which defines a custom + Kong vault. A Kong vault is a storage to store sensitive data, where the + values can be referenced in configuration of plugins. See: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: KongVaultSpec defines specification of a custom Kong vault. + properties: + backend: + description: 'Backend is the type of the backend storing the secrets + in the vault. The supported backends of Kong is listed here: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/backends/' + minLength: 1 + type: string + config: + description: Config is the configuration of the vault. Varies for + different backends. + x-kubernetes-preserve-unknown-fields: true + description: + description: Description is the additional information about the vault. + type: string + prefix: + description: Prefix is the prefix of vault URI for referencing values + in the vault. + minLength: 1 + type: string + required: + - backend + - prefix + type: object + status: + description: KongVaultStatus represents the current status of the KongVault + resource. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the KongVaultStatus. + \n Known condition types are: \n * \"Programmed\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + required: + - conditions + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.13.0 @@ -2538,6 +2716,22 @@ rules: - get - patch - update +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults + verbs: + - get + - list + - watch +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults/status + verbs: + - get + - patch + - update - apiGroups: - configuration.konghq.com resources: diff --git a/test/e2e/manifests/all-in-one-dbless.yaml b/test/e2e/manifests/all-in-one-dbless.yaml index b96fde0160..5ce0728a7b 100644 --- a/test/e2e/manifests/all-in-one-dbless.yaml +++ b/test/e2e/manifests/all-in-one-dbless.yaml @@ -1981,6 +1981,184 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: kongvaults.configuration.konghq.com +spec: + group: configuration.konghq.com + names: + categories: + - kong-ingress-controller + kind: KongVault + listKind: KongVaultList + plural: kongvaults + shortNames: + - kv + singular: kongvault + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Name of the backend of the vault + jsonPath: .spec.backend + name: Backend Type + type: string + - description: Prefix of vault URI to reference the values in the vault + jsonPath: .spec.prefix + name: Prefix + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Description + jsonPath: .spec.description + name: Description + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: 'KongVault is the schema for kongvaults API which defines a custom + Kong vault. A Kong vault is a storage to store sensitive data, where the + values can be referenced in configuration of plugins. See: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: KongVaultSpec defines specification of a custom Kong vault. + properties: + backend: + description: 'Backend is the type of the backend storing the secrets + in the vault. The supported backends of Kong is listed here: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/backends/' + minLength: 1 + type: string + config: + description: Config is the configuration of the vault. Varies for + different backends. + x-kubernetes-preserve-unknown-fields: true + description: + description: Description is the additional information about the vault. + type: string + prefix: + description: Prefix is the prefix of vault URI for referencing values + in the vault. + minLength: 1 + type: string + required: + - backend + - prefix + type: object + status: + description: KongVaultStatus represents the current status of the KongVault + resource. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the KongVaultStatus. + \n Known condition types are: \n * \"Programmed\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + required: + - conditions + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.13.0 @@ -2538,6 +2716,22 @@ rules: - get - patch - update +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults + verbs: + - get + - list + - watch +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults/status + verbs: + - get + - patch + - update - apiGroups: - configuration.konghq.com resources: diff --git a/test/e2e/manifests/all-in-one-postgres-enterprise.yaml b/test/e2e/manifests/all-in-one-postgres-enterprise.yaml index 3ecd9a22b4..e419a35359 100644 --- a/test/e2e/manifests/all-in-one-postgres-enterprise.yaml +++ b/test/e2e/manifests/all-in-one-postgres-enterprise.yaml @@ -1981,6 +1981,184 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: kongvaults.configuration.konghq.com +spec: + group: configuration.konghq.com + names: + categories: + - kong-ingress-controller + kind: KongVault + listKind: KongVaultList + plural: kongvaults + shortNames: + - kv + singular: kongvault + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Name of the backend of the vault + jsonPath: .spec.backend + name: Backend Type + type: string + - description: Prefix of vault URI to reference the values in the vault + jsonPath: .spec.prefix + name: Prefix + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Description + jsonPath: .spec.description + name: Description + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: 'KongVault is the schema for kongvaults API which defines a custom + Kong vault. A Kong vault is a storage to store sensitive data, where the + values can be referenced in configuration of plugins. See: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: KongVaultSpec defines specification of a custom Kong vault. + properties: + backend: + description: 'Backend is the type of the backend storing the secrets + in the vault. The supported backends of Kong is listed here: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/backends/' + minLength: 1 + type: string + config: + description: Config is the configuration of the vault. Varies for + different backends. + x-kubernetes-preserve-unknown-fields: true + description: + description: Description is the additional information about the vault. + type: string + prefix: + description: Prefix is the prefix of vault URI for referencing values + in the vault. + minLength: 1 + type: string + required: + - backend + - prefix + type: object + status: + description: KongVaultStatus represents the current status of the KongVault + resource. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the KongVaultStatus. + \n Known condition types are: \n * \"Programmed\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + required: + - conditions + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.13.0 @@ -2538,6 +2716,22 @@ rules: - get - patch - update +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults + verbs: + - get + - list + - watch +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults/status + verbs: + - get + - patch + - update - apiGroups: - configuration.konghq.com resources: diff --git a/test/e2e/manifests/all-in-one-postgres-multiple-gateways.yaml b/test/e2e/manifests/all-in-one-postgres-multiple-gateways.yaml index de95096ea6..da4e0f37b9 100644 --- a/test/e2e/manifests/all-in-one-postgres-multiple-gateways.yaml +++ b/test/e2e/manifests/all-in-one-postgres-multiple-gateways.yaml @@ -1981,6 +1981,184 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: kongvaults.configuration.konghq.com +spec: + group: configuration.konghq.com + names: + categories: + - kong-ingress-controller + kind: KongVault + listKind: KongVaultList + plural: kongvaults + shortNames: + - kv + singular: kongvault + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Name of the backend of the vault + jsonPath: .spec.backend + name: Backend Type + type: string + - description: Prefix of vault URI to reference the values in the vault + jsonPath: .spec.prefix + name: Prefix + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Description + jsonPath: .spec.description + name: Description + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: 'KongVault is the schema for kongvaults API which defines a custom + Kong vault. A Kong vault is a storage to store sensitive data, where the + values can be referenced in configuration of plugins. See: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: KongVaultSpec defines specification of a custom Kong vault. + properties: + backend: + description: 'Backend is the type of the backend storing the secrets + in the vault. The supported backends of Kong is listed here: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/backends/' + minLength: 1 + type: string + config: + description: Config is the configuration of the vault. Varies for + different backends. + x-kubernetes-preserve-unknown-fields: true + description: + description: Description is the additional information about the vault. + type: string + prefix: + description: Prefix is the prefix of vault URI for referencing values + in the vault. + minLength: 1 + type: string + required: + - backend + - prefix + type: object + status: + description: KongVaultStatus represents the current status of the KongVault + resource. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the KongVaultStatus. + \n Known condition types are: \n * \"Programmed\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + required: + - conditions + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.13.0 @@ -2538,6 +2716,22 @@ rules: - get - patch - update +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults + verbs: + - get + - list + - watch +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults/status + verbs: + - get + - patch + - update - apiGroups: - configuration.konghq.com resources: diff --git a/test/e2e/manifests/all-in-one-postgres.yaml b/test/e2e/manifests/all-in-one-postgres.yaml index 9b239680fa..060959a1e5 100644 --- a/test/e2e/manifests/all-in-one-postgres.yaml +++ b/test/e2e/manifests/all-in-one-postgres.yaml @@ -1981,6 +1981,184 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: kongvaults.configuration.konghq.com +spec: + group: configuration.konghq.com + names: + categories: + - kong-ingress-controller + kind: KongVault + listKind: KongVaultList + plural: kongvaults + shortNames: + - kv + singular: kongvault + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Name of the backend of the vault + jsonPath: .spec.backend + name: Backend Type + type: string + - description: Prefix of vault URI to reference the values in the vault + jsonPath: .spec.prefix + name: Prefix + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Description + jsonPath: .spec.description + name: Description + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: 'KongVault is the schema for kongvaults API which defines a custom + Kong vault. A Kong vault is a storage to store sensitive data, where the + values can be referenced in configuration of plugins. See: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: KongVaultSpec defines specification of a custom Kong vault. + properties: + backend: + description: 'Backend is the type of the backend storing the secrets + in the vault. The supported backends of Kong is listed here: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/backends/' + minLength: 1 + type: string + config: + description: Config is the configuration of the vault. Varies for + different backends. + x-kubernetes-preserve-unknown-fields: true + description: + description: Description is the additional information about the vault. + type: string + prefix: + description: Prefix is the prefix of vault URI for referencing values + in the vault. + minLength: 1 + type: string + required: + - backend + - prefix + type: object + status: + description: KongVaultStatus represents the current status of the KongVault + resource. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the KongVaultStatus. + \n Known condition types are: \n * \"Programmed\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + required: + - conditions + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.13.0 @@ -2538,6 +2716,22 @@ rules: - get - patch - update +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults + verbs: + - get + - list + - watch +- apiGroups: + - configuration.konghq.com + resources: + - kongvaults/status + verbs: + - get + - patch + - update - apiGroups: - configuration.konghq.com resources: diff --git a/test/integration/suite_test.go b/test/integration/suite_test.go index 69bc0251c0..ec6a3e9d81 100644 --- a/test/integration/suite_test.go +++ b/test/integration/suite_test.go @@ -60,6 +60,8 @@ func TestMain(m *testing.M) { if testenv.KongImage() != "" && testenv.KongTag() != "" { fmt.Printf("INFO: custom kong image specified via env: %s:%s\n", testenv.KongImage(), testenv.KongTag()) } + // add env for vaults. + kongbuilder.WithProxyEnvVar("vault_test_add_header_1", "h1:v1") // Pin the Helm chart version. kongbuilder.WithHelmChartVersion(testenv.KongHelmChartVersion()) diff --git a/test/integration/vault_test.go b/test/integration/vault_test.go new file mode 100644 index 0000000000..3b346cb9f3 --- /dev/null +++ b/test/integration/vault_test.go @@ -0,0 +1,141 @@ +//go:build integration_tests + +package integration + +import ( + "bytes" + "context" + "fmt" + "net/http" + "strings" + "testing" + + "github.com/kong/go-kong/kong" + "github.com/kong/kubernetes-testing-framework/pkg/clusters" + "github.com/kong/kubernetes-testing-framework/pkg/utils/kubernetes/generators" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kong/kubernetes-ingress-controller/v3/internal/annotations" + dpconf "github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/config" + kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1" + kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1" + "github.com/kong/kubernetes-ingress-controller/v3/pkg/clientset" + "github.com/kong/kubernetes-ingress-controller/v3/test" + "github.com/kong/kubernetes-ingress-controller/v3/test/consts" + "github.com/kong/kubernetes-ingress-controller/v3/test/internal/helpers" +) + +func TestCustomVault(t *testing.T) { + t.Parallel() + + RunWhenKongEnterprise(t) + // TODO: run hcv vault to enable test with DBMode + RunWhenKongDBMode(t, dpconf.DBModeOff, "Skipping because DBMode cannot support env vault") + + ctx := context.Background() + ns, cleaner := helpers.Setup(ctx, t, env) + + t.Log("deploying a minimal HTTP container deployment to test Ingress routes") + container := generators.NewContainer("httpbin", test.HTTPBinImage, test.HTTPBinPort) + deployment := generators.NewDeploymentForContainer(container) + deployment, err := env.Cluster().Client().AppsV1().Deployments(ns.Name).Create(ctx, deployment, metav1.CreateOptions{}) + require.NoError(t, err) + cleaner.Add(deployment) + + t.Logf("exposing deployment %s via service", deployment.Name) + service := generators.NewServiceForDeployment(deployment, corev1.ServiceTypeLoadBalancer) + _, err = env.Cluster().Client().CoreV1().Services(ns.Name).Create(ctx, service, metav1.CreateOptions{}) + require.NoError(t, err) + cleaner.Add(service) + + t.Logf("creating an ingress for service %s with ingress.class %s", service.Name, consts.IngressClass) + ingress := generators.NewIngressForService("/test_custom_vault", map[string]string{ + "konghq.com/strip-path": "true", + }, service) + ingress.Spec.IngressClassName = kong.String(consts.IngressClass) + require.NoError(t, clusters.DeployIngress(ctx, env.Cluster(), ns.Name, ingress)) + cleaner.Add(ingress) + + t.Log("waiting for routes from Ingress to be operational") + require.Eventually(t, func() bool { + resp, err := helpers.DefaultHTTPClient().Get(fmt.Sprintf("%s/test_custom_vault", proxyURL)) + if err != nil { + t.Logf("WARNING: error while waiting for %s: %v", proxyURL, err) + return false + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusOK { + b := new(bytes.Buffer) + n, err := b.ReadFrom(resp.Body) + require.NoError(t, err) + require.True(t, n > 0) + return strings.Contains(b.String(), "