| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- /*
- Copyright © The ESO Authors
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- https://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package generator
- import (
- "os"
- "strings"
- "time"
- //nolint
- . "github.com/onsi/gomega"
- "sigs.k8s.io/controller-runtime/pkg/client"
- // nolint
- . "github.com/onsi/ginkgo/v2"
- v1 "k8s.io/api/core/v1"
- "k8s.io/utils/ptr"
- "github.com/external-secrets/external-secrets-e2e/framework"
- esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
- genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- grafanaclient "github.com/grafana/grafana-openapi-client-go/client"
- grafanasearch "github.com/grafana/grafana-openapi-client-go/client/search"
- grafanasa "github.com/grafana/grafana-openapi-client-go/client/service_accounts"
- )
- var _ = Describe("grafana generator", Label("grafana"), func() {
- f := framework.New("grafana")
- const grafanaCredsSecretName = "grafana-creds"
- grafanaClient := newGrafanaClient()
- BeforeEach(func() {
- // grafana instance may need to load for a bit
- // we'll wake it up here and wait for it to be ready
- Eventually(func() error {
- _, err := grafanaClient.Search.Search(&grafanasearch.SearchParams{})
- return err
- }).WithPolling(time.Second * 15).WithTimeout(time.Minute * 5).ShouldNot(HaveOccurred())
- })
- AfterEach(func() {
- // ESO does clean up tokens, but not the service accounts.
- accounts, err := grafanaClient.ServiceAccounts.SearchOrgServiceAccountsWithPaging(&grafanasa.SearchOrgServiceAccountsWithPagingParams{
- Perpage: ptr.To(int64(100)),
- Page: ptr.To(int64(1)),
- Query: ptr.To(f.Namespace.Name),
- })
- Expect(err).ToNot(HaveOccurred())
- if accounts.GetPayload().ServiceAccounts != nil && len(accounts.GetPayload().ServiceAccounts) > 0 {
- for _, sa := range accounts.GetPayload().ServiceAccounts {
- _, err := grafanaClient.ServiceAccounts.DeleteServiceAccount(sa.ID)
- Expect(err).ToNot(HaveOccurred())
- }
- }
- })
- setupGenerator := func(tc *testCase) {
- err := f.CRClient.Create(GinkgoT().Context(), &v1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: grafanaCredsSecretName,
- Namespace: f.Namespace.Name,
- },
- Data: map[string][]byte{
- "grafana-token": []byte(os.Getenv("GRAFANA_TOKEN")),
- },
- })
- Expect(err).ToNot(HaveOccurred())
- tc.Generator = &genv1alpha1.Grafana{
- TypeMeta: metav1.TypeMeta{
- APIVersion: genv1alpha1.Group + "/" + genv1alpha1.Version,
- Kind: genv1alpha1.GrafanaKind,
- },
- ObjectMeta: metav1.ObjectMeta{
- Name: generatorName,
- Namespace: f.Namespace.Name,
- },
- Spec: genv1alpha1.GrafanaSpec{
- URL: os.Getenv("GRAFANA_URL"),
- ServiceAccount: genv1alpha1.GrafanaServiceAccount{
- Name: f.Namespace.Name,
- Role: "Viewer",
- },
- Auth: genv1alpha1.GrafanaAuth{
- Token: &genv1alpha1.SecretKeySelector{
- Name: grafanaCredsSecretName,
- Key: "grafana-token",
- },
- },
- },
- }
- tc.ExternalSecret.Spec.DataFrom = []esv1.ExternalSecretDataFromRemoteRef{
- {
- SourceRef: &esv1.StoreGeneratorSourceRef{
- GeneratorRef: &esv1.GeneratorRef{
- Kind: "Grafana",
- Name: generatorName,
- },
- },
- },
- }
- }
- ensureExternalSecretPurgesGeneratorState := func(tc *testCase) {
- // delete ES to trigger cleanup of generator state
- err := f.CRClient.Delete(GinkgoT().Context(), tc.ExternalSecret)
- Expect(err).ToNot(HaveOccurred())
- By("waiting for generator state to be cleaned up")
- // wait for generator state to be cleaned up
- Eventually(func() int {
- generatorStates := &genv1alpha1.GeneratorStateList{}
- err := f.CRClient.List(GinkgoT().Context(), generatorStates, client.InNamespace(f.Namespace.Name))
- if err != nil {
- return -1
- }
- GinkgoLogr.Info("found generator states", "states", generatorStates.Items)
- return len(generatorStates.Items)
- }).WithPolling(time.Second * 1).WithTimeout(time.Minute * 2).Should(BeZero())
- }
- tokenIsUsable := func(tc *testCase) {
- tc.AfterSync = func(secret *v1.Secret) {
- // ensure token exists and is usable
- Expect(string(secret.Data["token"])).ToNot(BeEmpty())
- _, err := grafanaClient.Search.Search(&grafanasearch.SearchParams{
- Query: ptr.To(""),
- })
- Expect(err).ToNot(HaveOccurred())
- ensureExternalSecretPurgesGeneratorState(tc)
- }
- }
- cleanupServiceAccountsAfterDeletion := func(tc *testCase) {
- tc.ExternalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second * 10}
- tc.AfterSync = func(secret *v1.Secret) {
- // Wait for ES to be rotated a couple of times,
- // this should create a couple of service accounts.
- // This allows us to verify that the service accounts are cleaned up
- // after the generator is deleted.
- Eventually(func() bool {
- generatorStates := &genv1alpha1.GeneratorStateList{}
- err := f.CRClient.List(GinkgoT().Context(), generatorStates, client.InNamespace(f.Namespace.Name))
- Expect(err).ToNot(HaveOccurred())
- GinkgoLogr.Info("generator states", "states", generatorStates.Items)
- return len(generatorStates.Items) > 2
- }).WithPolling(time.Second * 10).WithTimeout(time.Minute * 5).Should(BeTrue())
- ensureExternalSecretPurgesGeneratorState(tc)
- // ensure service accounts are cleaned up
- saList, err := grafanaClient.ServiceAccounts.SearchOrgServiceAccountsWithPaging(&grafanasa.SearchOrgServiceAccountsWithPagingParams{
- Perpage: ptr.To(int64(100)),
- Page: ptr.To(int64(1)),
- Query: ptr.To(f.Namespace.Name),
- })
- Expect(err).ToNot(HaveOccurred())
- Expect(saList.GetPayload().ServiceAccounts).To(HaveLen(1))
- tokens, err := grafanaClient.ServiceAccounts.ListTokensWithParams(&grafanasa.ListTokensParams{
- ServiceAccountID: saList.GetPayload().ServiceAccounts[0].ID,
- })
- Expect(err).ToNot(HaveOccurred())
- Expect(tokens.GetPayload()).To(BeEmpty())
- }
- }
- DescribeTable("generate secrets with grafana generator", generatorTableFunc,
- Entry("should generate a token that can be used to access the API", f, setupGenerator, tokenIsUsable),
- Entry("deleting a generator should cleanup the generated service accounts", f, setupGenerator, cleanupServiceAccountsAfterDeletion),
- )
- })
- func newGrafanaClient() *grafanaclient.GrafanaHTTPAPI {
- url := strings.TrimPrefix(os.Getenv("GRAFANA_URL"), "https://")
- return grafanaclient.NewHTTPClientWithConfig(nil, &grafanaclient.TransportConfig{
- Host: url,
- BasePath: "/api",
- Schemes: []string{"https"},
- APIKey: os.Getenv("GRAFANA_TOKEN"),
- NumRetries: 15,
- RetryTimeout: time.Second * 6,
- })
- }
|