auth.go raw

   1  package authentication
   2  
   3  import (
   4  	"context"
   5  	"fmt"
   6  
   7  	"go.uber.org/zap"
   8  	"google.golang.org/grpc/metadata"
   9  
  10  	iampb "github.com/yandex-cloud/go-genproto/yandex/cloud/iam/v1"
  11  	"github.com/yandex-cloud/go-sdk/v2/credentials"
  12  	"github.com/yandex-cloud/go-sdk/v2/pkg/endpoints"
  13  	"github.com/yandex-cloud/go-sdk/v2/pkg/errors"
  14  	"github.com/yandex-cloud/go-sdk/v2/pkg/transport"
  15  	iamsdk "github.com/yandex-cloud/go-sdk/v2/services/iam/v1"
  16  )
  17  
  18  // Authenticator provides methods for generating IAM tokens for an authenticated entity or service account.
  19  type Authenticator interface {
  20  	CreateIAMToken(ctx context.Context) (IamToken, error)
  21  	CreateIAMTokenForServiceAccount(ctx context.Context, serviceAccountID string) (IamToken, error)
  22  }
  23  
  24  // AuthenticatorImpl provides functionality for generating and managing IAM tokens using supplied credentials and IAM client.
  25  type AuthenticatorImpl struct {
  26  	creds          credentials.Credentials
  27  	iamTokenClient iamsdk.IamTokenClient
  28  	logger         *zap.Logger
  29  }
  30  
  31  // NewAuthenticatorFromEndpoint creates a new AuthenticatorImpl using provided credentials and endpoint configuration.
  32  // Returns the constructed AuthenticatorImpl instance or an error if the connector initialization fails.
  33  func NewAuthenticatorFromEndpoint(logger *zap.Logger, creds credentials.Credentials, endpoint *endpoints.Endpoint) (*AuthenticatorImpl, error) {
  34  	return NewAuthenticator(logger, creds, iamsdk.NewIamTokenClient(transport.NewSingleConnector(endpoint.Addr, endpoint.DialOptions...))), nil
  35  }
  36  
  37  // NewAuthenticator creates and returns a new instance of AuthenticatorImpl using the provided credentials and IamTokenClient.
  38  func NewAuthenticator(logger *zap.Logger, creds credentials.Credentials, iamTokenClient iamsdk.IamTokenClient) *AuthenticatorImpl {
  39  	return &AuthenticatorImpl{
  40  		creds:          creds,
  41  		iamTokenClient: iamTokenClient,
  42  		logger:         logger,
  43  	}
  44  }
  45  
  46  // CreateIAMToken generates an IAM token using the provided credentials in the `AuthenticatorImpl` instance.
  47  func (a *AuthenticatorImpl) CreateIAMToken(ctx context.Context) (IamToken, error) {
  48  	a.logger.Debug("Creating IAM token")
  49  	switch creds := a.creds.(type) {
  50  	case credentials.ExchangeableCredentials:
  51  		a.logger.Debug("Using exchangeable credentials")
  52  		return a.createTokenFromExchangeable(ctx, creds)
  53  	case credentials.NonExchangeableCredentials:
  54  		a.logger.Debug("Using non-exchangeable credentials")
  55  		return a.createTokenFromNonExchangeable(ctx, creds)
  56  	default:
  57  		err := fmt.Errorf("unsupported credentials type %T", creds)
  58  		a.logger.Error("Failed to create IAM token", zap.Error(err))
  59  		return nil, &errors.AuthError{Err: err}
  60  	}
  61  }
  62  
  63  // createTokenFromExchangeable generates an IAM token using exchangeable credentials, handling request creation and errors.
  64  func (a *AuthenticatorImpl) createTokenFromExchangeable(ctx context.Context, creds credentials.ExchangeableCredentials) (IamToken, error) {
  65  	a.logger.Debug("Generating IAM token request")
  66  	req, err := creds.IAMTokenRequest()
  67  	if err != nil {
  68  		a.logger.Error("Failed to create IAM token request", zap.Error(err))
  69  		return nil, &errors.AuthError{Err: err}
  70  	}
  71  
  72  	tokenReq := createIamTokenRequestFromCredential(req)
  73  	if tokenReq == nil {
  74  		err := fmt.Errorf("invalid identity type")
  75  		a.logger.Error("Failed to create token request from credentials", zap.Error(err))
  76  		return nil, &errors.AuthError{Err: err}
  77  	}
  78  
  79  	a.logger.Debug("Creating IAM token from request")
  80  	tokenResp, err := a.iamTokenClient.Create(ctx, tokenReq)
  81  	if err != nil {
  82  		a.logger.Error("Failed to create IAM token", zap.Error(err))
  83  		return nil, &errors.AuthError{Err: err}
  84  	}
  85  
  86  	a.logger.Debug("Successfully created IAM token")
  87  	return NewIamToken(tokenResp.IamToken, tokenResp.ExpiresAt.AsTime()), nil
  88  }
  89  
  90  // createTokenFromNonExchangeable generates an IAM token using NonExchangeableCredentials without calling the token service.
  91  // Returns the generated IamToken or an error if token retrieval fails.
  92  func (a *AuthenticatorImpl) createTokenFromNonExchangeable(ctx context.Context, creds credentials.NonExchangeableCredentials) (IamToken, error) {
  93  	a.logger.Debug("Retrieving IAM token from non-exchangeable credentials")
  94  	tokenResp, err := creds.IAMToken(ctx)
  95  	if err != nil {
  96  		a.logger.Error("Failed to get IAM token from non-exchangeable credentials", zap.Error(err))
  97  		return nil, &errors.AuthError{Err: err}
  98  	}
  99  	a.logger.Debug("Successfully retrieved IAM token")
 100  	return NewIamToken(tokenResp.Token, tokenResp.ExpiresAt), nil
 101  }
 102  
 103  // CreateIAMTokenForServiceAccount generates a new IAM token for the provided service account ID using the IAM token client.
 104  func (a *AuthenticatorImpl) CreateIAMTokenForServiceAccount(ctx context.Context, serviceAccountID string) (IamToken, error) {
 105  	a.logger.Debug("Creating IAM token for service account", zap.String("serviceAccountID", serviceAccountID))
 106  	token, err := a.CreateIAMToken(ctx)
 107  	if err != nil {
 108  		a.logger.Error("Failed to create IAM token", zap.Error(err))
 109  		return nil, &errors.AuthError{Err: err}
 110  	}
 111  	a.logger.Debug("Successfully created IAM token")
 112  	authctx := metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token.GetIamToken())
 113  	tokenResp, err := a.iamTokenClient.CreateForServiceAccount(authctx,
 114  		&iampb.CreateIamTokenForServiceAccountRequest{
 115  			ServiceAccountId: serviceAccountID,
 116  		},
 117  	)
 118  	if err != nil {
 119  		a.logger.Error("Failed to create IAM token for service account",
 120  			zap.String("serviceAccountID", serviceAccountID),
 121  			zap.Error(err))
 122  		return nil, &errors.AuthError{Err: err}
 123  	}
 124  	a.logger.Debug("Successfully created IAM token for service account",
 125  		zap.String("serviceAccountID", serviceAccountID))
 126  	return NewIamToken(tokenResp.IamToken, tokenResp.ExpiresAt.AsTime()), nil
 127  }
 128  
 129  // createIamTokenRequestFromCredential converts a CredentialsTokenRequest into an iampb.CreateIamTokenRequest.
 130  func createIamTokenRequestFromCredential(req *credentials.CredentialsTokenRequest) *iampb.CreateIamTokenRequest {
 131  	switch req.Identity {
 132  	case credentials.CredentialsIdentityYandexPassportOauthToken:
 133  		return &iampb.CreateIamTokenRequest{
 134  			Identity: &iampb.CreateIamTokenRequest_YandexPassportOauthToken{
 135  				YandexPassportOauthToken: req.Token},
 136  		}
 137  	case credentials.CredentialsIdentityJWT:
 138  		return &iampb.CreateIamTokenRequest{
 139  			Identity: &iampb.CreateIamTokenRequest_Jwt{Jwt: req.Token}}
 140  	}
 141  	return nil
 142  }
 143