-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathcredentials.go
158 lines (135 loc) · 5.04 KB
/
credentials.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package dcsdk
import (
"context"
"crypto"
"crypto/rsa"
"errors"
"fmt" //nolint:staticcheck
"time"
"github.com/doublecloud/go-sdk/iamkey"
"github.com/doublecloud/go-sdk/pkg/sdkerrors"
jwt "github.com/golang-jwt/jwt/v4"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// Credentials is an abstraction of API authorization credentials.
// See https://double.cloud/docs/en/public-api/get-iam-token for details.
// Note that functions that return Credentials may return different Credentials implementation
// in next SDK version, and this is not considered breaking change.
type Credentials interface {
DCAPICredentials()
}
// ExchangeableCredentials can be exchanged for IAM Token in IAM Token Service, that can be used
// to authorize API calls.
// See https://double.cloud/docs/en/public-api/get-iam-token for details.
type ExchangeableCredentials interface {
Credentials
// IAMTokenRequest returns request for fresh IAM token or error.
IAMTokenRequest() (*iamkey.CreateIamTokenRequest, error)
// NonExchangeableCredentials
// IAMToken(ctx context.Context) (*iamkey.CreateIamTokenResponse, error)
}
// NonExchangeableCredentials allows to get IAM Token without calling IAM Token Service.
type NonExchangeableCredentials interface {
Credentials
// IAMToken returns IAM Token.
IAMToken(ctx context.Context) (*iamkey.CreateIamTokenResponse, error)
}
// ServiceAccountKey returns credentials for the given IAM Key. The key is used to sign JWT tokens.
// JWT tokens are exchanged for IAM Tokens used to authorize API calls.
// This authorization method is not supported for IAM Keys issued for User Accounts.
func ServiceAccountKey(key *iamkey.Key) (Credentials, error) {
jwtBuilder, err := newServiceAccountJWTBuilder(key)
if err != nil {
return nil, err
}
return exchangeableCredentialsFunc(func() (*iamkey.CreateIamTokenRequest, error) {
signedJWT, err := jwtBuilder.SignedToken()
if err != nil {
return nil, sdkerrors.WithMessage(err, "JWT sign failed")
}
return &iamkey.CreateIamTokenRequest{
Identity: &iamkey.CreateIamTokenRequest_Jwt{
Jwt: signedJWT,
},
}, nil
}), nil
}
func newServiceAccountJWTBuilder(key *iamkey.Key) (*serviceAccountJWTBuilder, error) {
err := validateServiceAccountKey(key)
if err != nil {
return nil, sdkerrors.WithMessage(err, "key validation failed")
}
rsaPrivateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(key.PrivateKey))
if err != nil {
return nil, sdkerrors.WithMessage(err, "private key parsing failed")
}
return &serviceAccountJWTBuilder{
key: key,
rsaPrivateKey: rsaPrivateKey,
}, nil
}
func validateServiceAccountKey(key *iamkey.Key) error {
if key.Id == "" {
return errors.New("key id is missing")
}
if key.GetServiceAccountId() == "" {
return fmt.Errorf("key should de issued for service account, but subject is %#v", key.Subject)
}
return nil
}
type serviceAccountJWTBuilder struct {
key *iamkey.Key
rsaPrivateKey *rsa.PrivateKey
}
func (b *serviceAccountJWTBuilder) SignedToken() (string, error) {
return b.issueToken().SignedString(b.rsaPrivateKey)
}
func (b *serviceAccountJWTBuilder) issueToken() *jwt.Token {
issuedAt := time.Now()
token := jwt.NewWithClaims(jwtSigningMethodPS256WithSaltLengthEqualsHash, jwt.RegisteredClaims{
Issuer: b.key.GetServiceAccountId(),
Subject: b.key.GetServiceAccountId(),
IssuedAt: jwt.NewNumericDate(issuedAt),
ExpiresAt: jwt.NewNumericDate(issuedAt.Add(time.Hour)),
Audience: jwt.ClaimStrings{tokenURL()},
})
token.Header["kid"] = b.key.Id
return token
}
// Should be removed after https://github.com/dgrijalva/jwt-go/issues/285 fix.
var jwtSigningMethodPS256WithSaltLengthEqualsHash = &jwt.SigningMethodRSAPSS{
SigningMethodRSA: jwt.SigningMethodPS256.SigningMethodRSA,
Options: &rsa.PSSOptions{
Hash: crypto.SHA256,
SaltLength: rsa.PSSSaltLengthEqualsHash,
},
}
type exchangeableCredentialsFunc func() (iamTokenReq *iamkey.CreateIamTokenRequest, err error)
var _ ExchangeableCredentials = (exchangeableCredentialsFunc)(nil)
func (exchangeableCredentialsFunc) DCAPICredentials() {}
func (f exchangeableCredentialsFunc) IAMTokenRequest() (iamTokenReq *iamkey.CreateIamTokenRequest, err error) {
return f()
}
// NoCredentials implements Credentials, it allows to create unauthenticated connections
type NoCredentials struct{}
func (creds NoCredentials) DCAPICredentials() {}
// IAMToken always returns gRPC error with status UNAUTHENTICATED
func (creds NoCredentials) IAMToken(ctx context.Context) (*iamkey.CreateIamTokenResponse, error) {
return nil, status.Error(codes.Unauthenticated, "unauthenticated connection")
}
// IAMTokenCredentials implements Credentials with IAM token as-is
type IAMTokenCredentials struct {
iamToken string
}
func (creds IAMTokenCredentials) DCAPICredentials() {}
func (creds IAMTokenCredentials) IAMToken(ctx context.Context) (*iamkey.CreateIamTokenResponse, error) {
return &iamkey.CreateIamTokenResponse{
IamToken: creds.iamToken,
}, nil
}
func NewIAMTokenCredentials(iamToken string) NonExchangeableCredentials {
return &IAMTokenCredentials{
iamToken: iamToken,
}
}