1 package credentials
2 3 import (
4 "crypto/rsa"
5 "errors"
6 "fmt"
7 "time"
8 9 "github.com/golang-jwt/jwt/v4"
10 11 "github.com/yandex-cloud/go-sdk/v2/pkg/iamkey"
12 )
13 14 // newServiceAccountJWTBuilder creates a JWT builder for a given IAM service account key.
15 // Returns an error if the key validation or parsing of the private key fails.
16 func newServiceAccountJWTBuilder(key *iamkey.Key) (*serviceAccountJWTBuilder, error) {
17 err := validateServiceAccountKey(key)
18 if err != nil {
19 return nil, fmt.Errorf("key validation failed: %w", err)
20 }
21 22 rsaPrivateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(key.PrivateKey))
23 if err != nil {
24 return nil, fmt.Errorf("private key parsing failed: %w", err)
25 }
26 27 return &serviceAccountJWTBuilder{
28 key: key,
29 rsaPrivateKey: rsaPrivateKey,
30 }, nil
31 }
32 33 // validateServiceAccountKey validates an IAM key to ensure it has an ID and is issued for a service account.
34 func validateServiceAccountKey(key *iamkey.Key) error {
35 if key.Id == "" {
36 return errors.New("key id is missing")
37 }
38 39 if key.GetServiceAccountId() == "" {
40 return fmt.Errorf("key should be issued for service account, but subject is %#v", key.Subject)
41 }
42 43 return nil
44 }
45 46 // serviceAccountJWTBuilder is used to generate signed JWT tokens for service account authorization.
47 // It relies on a service account key and an associated RSA private key.
48 type serviceAccountJWTBuilder struct {
49 key *iamkey.Key
50 rsaPrivateKey *rsa.PrivateKey
51 }
52 53 // SignedToken generates a signed JWT token using the service account's private RSA key and returns it as a string.
54 func (b *serviceAccountJWTBuilder) SignedToken() (string, error) {
55 return b.issueToken().SignedString(b.rsaPrivateKey)
56 }
57 58 // issueToken creates a new JWT token with claims using the service account's ID and sets the token's header information.
59 func (b *serviceAccountJWTBuilder) issueToken() *jwt.Token {
60 issuedAt := time.Now()
61 token := jwt.NewWithClaims(jwt.SigningMethodPS256, jwt.RegisteredClaims{
62 Issuer: b.key.GetServiceAccountId(),
63 IssuedAt: jwt.NewNumericDate(issuedAt),
64 ExpiresAt: jwt.NewNumericDate(issuedAt.Add(time.Hour)),
65 Audience: jwt.ClaimStrings{"https://iam.api.cloud.yandex.net/iam/v1/tokens"},
66 })
67 token.Header["kid"] = b.key.Id
68 69 return token
70 }
71