| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- /*
- 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 mysterybox
- import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- lru "github.com/hashicorp/golang-lru"
- "github.com/nebius/gosdk/auth"
- "golang.org/x/sync/singleflight"
- "k8s.io/utils/clock"
- "github.com/external-secrets/external-secrets/providers/v1/nebius/common/sdk/iam"
- )
- const (
- errInvalidSubjectCreds = "invalid subject credentials: malformed JSON"
- )
- // TokenGetter is an interface for generating and retrieving authentication tokens.
- type TokenGetter interface {
- GetToken(ctx context.Context, apiDomain, subjectCreds string, caCert []byte) (string, error)
- }
- type tokenCacheKey struct {
- APIDomain string
- PublicKeyID string
- ServiceAccountID string
- PrivateKeyHash string
- }
- func (k *tokenCacheKey) String() string {
- return k.APIDomain + "|" + k.PublicKeyID + "|" + k.ServiceAccountID + "|" + k.PrivateKeyHash
- }
- // CachedTokenGetter is responsible for managing Nebius IAM token caching and token exchange processes.
- type CachedTokenGetter struct {
- TokenExchanger iam.TokenExchanger
- Clock clock.Clock
- tokenCache *lru.Cache
- sf singleflight.Group
- }
- // NewCachedTokenGetter initializes a CachedTokenGetter with the specified cache size, token exchanger, and clock.
- // Returns a CachedTokenGetter instance and an error if LRU cache creation fails.
- func NewCachedTokenGetter(cacheSize int, tokenExchanger iam.TokenExchanger, clock clock.Clock) (*CachedTokenGetter, error) {
- cache, err := lru.New(cacheSize)
- if err != nil {
- return nil, err
- }
- return &CachedTokenGetter{
- tokenCache: cache,
- TokenExchanger: tokenExchanger,
- Clock: clock,
- }, nil
- }
- func isTokenExpired(token *iam.Token, clk clock.Clock) bool {
- now := clk.Now()
- if token.ExpiresAt.After(now) {
- total := token.ExpiresAt.Sub(token.IssuedAt)
- remaining := token.ExpiresAt.Sub(now)
- if remaining > total/10 {
- return false
- }
- }
- return true
- }
- // GetToken retrieves an IAM token for the given API domain and subject credentials, using a cache to optimize requests.
- // It exchanges credentials for a new token if no valid cached token exists or the cached token is nearing expiration.
- func (c *CachedTokenGetter) GetToken(ctx context.Context, apiDomain, subjectCreds string, caCert []byte) (string, error) {
- byteCreds := []byte(subjectCreds)
- cacheKey, err := buildTokenCacheKey(byteCreds, apiDomain)
- if err != nil {
- return "", err
- }
- value, ok := c.tokenCache.Get(*cacheKey)
- if ok {
- token := value.(*iam.Token)
- tokenExpired := isTokenExpired(token, c.Clock)
- if !tokenExpired {
- return token.Token, nil
- }
- }
- tokenCacheKeyString := cacheKey.String()
- token, err, _ := c.sf.Do(tokenCacheKeyString, func() (any, error) {
- if v, ok := c.tokenCache.Get(*cacheKey); ok {
- tok := v.(*iam.Token)
- if !isTokenExpired(tok, c.Clock) {
- return tok.Token, nil
- }
- }
- newToken, err := c.TokenExchanger.ExchangeIamToken(ctx, apiDomain, subjectCreds, c.Clock.Now(), caCert)
- if err != nil {
- return "", fmt.Errorf("could not exchange creds to iam token: %w", MapGrpcErrors("create token", err))
- }
- c.tokenCache.Add(*cacheKey, newToken)
- return newToken.Token, nil
- })
- if err != nil {
- return "", err
- }
- return token.(string), nil
- }
- func buildTokenCacheKey(subjectCreds []byte, apiDomain string) (*tokenCacheKey, error) {
- parsedSubjectCreds := &auth.ServiceAccountCredentials{}
- err := json.Unmarshal(subjectCreds, parsedSubjectCreds)
- if err != nil {
- return nil, errors.New(errInvalidSubjectCreds)
- }
- return &tokenCacheKey{
- APIDomain: apiDomain,
- PublicKeyID: parsedSubjectCreds.SubjectCredentials.KeyID,
- ServiceAccountID: parsedSubjectCreds.SubjectCredentials.Subject,
- PrivateKeyHash: HashBytes([]byte(parsedSubjectCreds.SubjectCredentials.PrivateKey)),
- }, nil
- }
- var _ TokenGetter = &CachedTokenGetter{}
|