grafana.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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 grafana provides functionality for generating Grafana service account tokens.
  14. package grafana
  15. import (
  16. "context"
  17. "encoding/json"
  18. "fmt"
  19. "net/url"
  20. "strings"
  21. "github.com/google/uuid"
  22. grafanaclient "github.com/grafana/grafana-openapi-client-go/client"
  23. grafanasa "github.com/grafana/grafana-openapi-client-go/client/service_accounts"
  24. "github.com/grafana/grafana-openapi-client-go/models"
  25. apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  26. "sigs.k8s.io/controller-runtime/pkg/client"
  27. genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
  28. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  29. "github.com/external-secrets/external-secrets/runtime/esutils/resolvers"
  30. )
  31. // Grafana implements token generation for Grafana service accounts.
  32. type Grafana struct{}
  33. // Generate creates a new Grafana service account token using the provided configuration.
  34. func (w *Grafana) Generate(ctx context.Context, jsonSpec *apiextensions.JSON, kclient client.Client, ns string) (map[string][]byte, genv1alpha1.GeneratorProviderState, error) {
  35. gen, err := parseSpec(jsonSpec.Raw)
  36. if err != nil {
  37. return nil, nil, err
  38. }
  39. cl, err := newClient(ctx, gen, kclient, ns)
  40. if err != nil {
  41. return nil, nil, err
  42. }
  43. state, err := createOrGetServiceAccount(cl, gen)
  44. if err != nil {
  45. return nil, nil, err
  46. }
  47. // create new token
  48. res, err := cl.ServiceAccounts.CreateToken(&grafanasa.CreateTokenParams{
  49. ServiceAccountID: *state.ServiceAccount.ServiceAccountID,
  50. Body: &models.AddServiceAccountTokenCommand{
  51. Name: uuid.New().String(),
  52. },
  53. }, nil)
  54. if err != nil {
  55. return nil, nil, err
  56. }
  57. state.ServiceAccount.ServiceAccountTokenID = new(res.Payload.ID)
  58. return tokenResponse(state, res.Payload.Key)
  59. }
  60. // Cleanup handles any necessary cleanup after token generation.
  61. func (w *Grafana) Cleanup(ctx context.Context, jsonSpec *apiextensions.JSON, previousStatus genv1alpha1.GeneratorProviderState, kclient client.Client, ns string) error {
  62. if previousStatus == nil {
  63. return fmt.Errorf("missing previous status")
  64. }
  65. status, err := parseStatus(previousStatus.Raw)
  66. if err != nil {
  67. return err
  68. }
  69. gen, err := parseSpec(jsonSpec.Raw)
  70. if err != nil {
  71. return err
  72. }
  73. cl, err := newClient(ctx, gen, kclient, ns)
  74. if err != nil {
  75. return err
  76. }
  77. _, err = cl.ServiceAccounts.DeleteToken(*status.ServiceAccount.ServiceAccountTokenID, *status.ServiceAccount.ServiceAccountID)
  78. if err != nil && !strings.Contains(err.Error(), "service account token not found") {
  79. return err
  80. }
  81. return nil
  82. }
  83. func newClient(ctx context.Context, gen *genv1alpha1.Grafana, kclient client.Client, ns string) (*grafanaclient.GrafanaHTTPAPI, error) {
  84. parsedURL, err := url.Parse(gen.Spec.URL)
  85. if err != nil {
  86. return nil, err
  87. }
  88. cfg := &grafanaclient.TransportConfig{
  89. Host: parsedURL.Host,
  90. BasePath: parsedURL.JoinPath("/api").Path,
  91. Schemes: []string{parsedURL.Scheme},
  92. }
  93. if err := setGrafanaClientCredentials(ctx, gen, kclient, ns, cfg); err != nil {
  94. return nil, err
  95. }
  96. return grafanaclient.NewHTTPClientWithConfig(nil, cfg), nil
  97. }
  98. func setGrafanaClientCredentials(ctx context.Context, gen *genv1alpha1.Grafana, kclient client.Client, ns string, cfg *grafanaclient.TransportConfig) error {
  99. // First try to use service account auth
  100. if gen.Spec.Auth.Token != nil {
  101. serviceAccountAPIKey, err := resolvers.SecretKeyRef(ctx, kclient, resolvers.EmptyStoreKind, ns, &esmeta.SecretKeySelector{
  102. Namespace: &ns,
  103. Name: gen.Spec.Auth.Token.Name,
  104. Key: gen.Spec.Auth.Token.Key,
  105. })
  106. if err != nil {
  107. return err
  108. }
  109. cfg.APIKey = serviceAccountAPIKey
  110. return nil
  111. }
  112. // Next try to use basic auth
  113. if gen.Spec.Auth.Basic != nil {
  114. basicAuthPassword, err := resolvers.SecretKeyRef(ctx, kclient, resolvers.EmptyStoreKind, ns, &esmeta.SecretKeySelector{
  115. Namespace: &ns,
  116. Name: gen.Spec.Auth.Basic.Password.Name,
  117. Key: gen.Spec.Auth.Basic.Password.Key,
  118. })
  119. if err != nil {
  120. return err
  121. }
  122. cfg.BasicAuth = url.UserPassword(gen.Spec.Auth.Basic.Username, basicAuthPassword)
  123. return nil
  124. }
  125. // No auth found, fail
  126. return fmt.Errorf("no auth configuration found")
  127. }
  128. func createOrGetServiceAccount(cl *grafanaclient.GrafanaHTTPAPI, gen *genv1alpha1.Grafana) (*genv1alpha1.GrafanaServiceAccountTokenState, error) {
  129. saList, err := cl.ServiceAccounts.SearchOrgServiceAccountsWithPaging(&grafanasa.SearchOrgServiceAccountsWithPagingParams{
  130. Query: new(gen.Spec.ServiceAccount.Name),
  131. })
  132. if err != nil {
  133. return nil, err
  134. }
  135. for _, sa := range saList.Payload.ServiceAccounts {
  136. if sa.Name == gen.Spec.ServiceAccount.Name {
  137. return &genv1alpha1.GrafanaServiceAccountTokenState{
  138. ServiceAccount: genv1alpha1.GrafanaStateServiceAccount{
  139. ServiceAccountID: &sa.ID,
  140. ServiceAccountLogin: &sa.Login,
  141. },
  142. }, nil
  143. }
  144. }
  145. res, err := cl.ServiceAccounts.CreateServiceAccount(&grafanasa.CreateServiceAccountParams{
  146. Body: &models.CreateServiceAccountForm{
  147. Name: gen.Spec.ServiceAccount.Name,
  148. Role: gen.Spec.ServiceAccount.Role,
  149. },
  150. }, nil)
  151. if err != nil {
  152. return nil, err
  153. }
  154. return &genv1alpha1.GrafanaServiceAccountTokenState{
  155. ServiceAccount: genv1alpha1.GrafanaStateServiceAccount{
  156. ServiceAccountID: new(res.Payload.ID),
  157. ServiceAccountLogin: &res.Payload.Login,
  158. },
  159. }, nil
  160. }
  161. func tokenResponse(state *genv1alpha1.GrafanaServiceAccountTokenState, token string) (map[string][]byte, genv1alpha1.GeneratorProviderState, error) {
  162. newStateJSON, err := json.Marshal(state)
  163. if err != nil {
  164. return nil, nil, err
  165. }
  166. return map[string][]byte{
  167. "login": []byte(*state.ServiceAccount.ServiceAccountLogin),
  168. "token": []byte(token),
  169. }, &apiextensions.JSON{Raw: newStateJSON}, nil
  170. }
  171. func parseSpec(data []byte) (*genv1alpha1.Grafana, error) {
  172. var spec genv1alpha1.Grafana
  173. err := json.Unmarshal(data, &spec)
  174. return &spec, err
  175. }
  176. func parseStatus(data []byte) (*genv1alpha1.GrafanaServiceAccountTokenState, error) {
  177. var state genv1alpha1.GrafanaServiceAccountTokenState
  178. err := json.Unmarshal(data, &state)
  179. if err != nil {
  180. return nil, err
  181. }
  182. return &state, err
  183. }
  184. // NewGenerator creates a new Generator instance.
  185. func NewGenerator() genv1alpha1.Generator {
  186. return &Grafana{}
  187. }
  188. // Kind returns the generator kind.
  189. func Kind() string {
  190. return string(genv1alpha1.GeneratorKindGrafana)
  191. }