auth_oidc.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  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 doppler
  14. import (
  15. "context"
  16. "encoding/json"
  17. "fmt"
  18. "os"
  19. "time"
  20. typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
  21. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  22. "github.com/external-secrets/external-secrets/runtime/oidc"
  23. )
  24. const dopplerOIDCPath = "/v3/auth/oidc"
  25. // OIDCTokenManager manages OIDC token exchange with Doppler.
  26. // It embeds the shared BaseTokenManager and implements the TokenExchanger interface.
  27. type OIDCTokenManager struct {
  28. *oidc.BaseTokenManager
  29. identity string
  30. }
  31. // NewOIDCTokenManager creates a new OIDCTokenManager for handling Doppler OIDC authentication.
  32. func NewOIDCTokenManager(
  33. corev1 typedcorev1.CoreV1Interface,
  34. store *esv1.DopplerProvider,
  35. namespace string,
  36. storeKind string,
  37. storeName string,
  38. ) *OIDCTokenManager {
  39. if store == nil || store.Auth == nil || store.Auth.OIDCConfig == nil {
  40. return nil
  41. }
  42. oidcAuth := store.Auth.OIDCConfig
  43. baseURL := "https://api.doppler.com"
  44. if customURL := os.Getenv(customBaseURLEnvVar); customURL != "" {
  45. baseURL = customURL
  46. }
  47. // Resource-specific audience binds the SA token to a specific
  48. // SecretStore/ClusterSecretStore, preventing token reuse across stores.
  49. var resourceAudience string
  50. if storeKind == esv1.ClusterSecretStoreKind {
  51. resourceAudience = fmt.Sprintf("clusterSecretStore:%s", storeName)
  52. } else {
  53. resourceAudience = fmt.Sprintf("secretStore:%s:%s", namespace, storeName)
  54. }
  55. btm := oidc.NewBaseTokenManager(corev1, namespace, storeKind, baseURL, oidcAuth.ServiceAccountRef)
  56. btm.ExtraAudiences = []string{resourceAudience}
  57. btm.ExpirationSeconds = oidcAuth.ExpirationSeconds
  58. manager := &OIDCTokenManager{
  59. identity: oidcAuth.Identity,
  60. BaseTokenManager: btm,
  61. }
  62. manager.Exchanger = manager
  63. return manager
  64. }
  65. // ExchangeToken exchanges a ServiceAccount token for a Doppler API token.
  66. func (m *OIDCTokenManager) ExchangeToken(ctx context.Context, saToken string) (string, time.Time, error) {
  67. url := m.BaseURL + dopplerOIDCPath
  68. requestBody := map[string]string{
  69. "identity": m.identity,
  70. "token": saToken,
  71. }
  72. body, err := oidc.PostJSONRequest(ctx, url, requestBody, "Doppler")
  73. if err != nil {
  74. return "", time.Time{}, err
  75. }
  76. var response struct {
  77. Success bool `json:"success"`
  78. Token string `json:"token"`
  79. ExpiresAt string `json:"expires_at"`
  80. }
  81. if err := json.Unmarshal(body, &response); err != nil {
  82. return "", time.Time{}, fmt.Errorf("failed to parse response: %w", err)
  83. }
  84. if !response.Success {
  85. return "", time.Time{}, fmt.Errorf("Doppler OIDC auth failed: %s", string(body))
  86. }
  87. expiresAt, err := time.Parse(time.RFC3339, response.ExpiresAt)
  88. if err != nil {
  89. return "", time.Time{}, fmt.Errorf("failed to parse expiration time: %w", err)
  90. }
  91. return response.Token, expiresAt, nil
  92. }