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