Skip to content

Commit 0046c34

Browse files
committed
Add first cut of KMS implementation and tweak IAM implementation a bit
1 parent 4d36d60 commit 0046c34

File tree

8 files changed

+292
-39
lines changed

8 files changed

+292
-39
lines changed

config.go

+27-7
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,10 @@ var (
2626
)
2727

2828
type iamConfigKey struct{}
29+
type kmsConfigKey struct{}
2930

30-
// gcpConfig common config elements between signing methods - not to be used on its own
31-
type gcpConfig struct {
32-
// ProjectID is the project id that contains the service account you want to sign with. Defaults to "-" to infer the project from the account
33-
ProjectID string
34-
31+
// GCPConfig common config elements between signing methods - not to be used on its own
32+
type GCPConfig struct {
3533
// OAuth2HTTPClient is a user provided oauth2 authenticated *http.Client to use, google.DefaultClient used otherwise
3634
// Used for signing requests
3735
OAuth2HTTPClient *http.Client
@@ -47,20 +45,31 @@ type gcpConfig struct {
4745

4846
// InjectKeyID will overwrite the provided header with one that contains the Key ID of the key used to sign the JWT.
4947
// Note that the IAM JWT signing method does this on its own and this is only applicable for the IAM Blob and Cloud KMS
50-
// signing methods.
48+
// signing methods. For CloudKMS, this will be a hash of the KeyPath configured.
5149
InjectKeyID bool
5250
}
5351

5452
// IAMConfig is relevant for both the signBlob and signJWT IAM API use-cases
5553
type IAMConfig struct {
54+
// ProjectID is the project id that contains the service account you want to sign with. Defaults to "-" to infer the project from the account
55+
ProjectID string
56+
5657
// Service account can be the email address or the uniqueId of the service account used to sign the JWT with
5758
ServiceAccount string
5859

5960
// IAMType is a helper used to help clarify which IAM signing method this config is meant for.
6061
// Used for the jwtmiddleware and oauth2 packages.
6162
IAMType iamType
6263

63-
gcpConfig
64+
GCPConfig
65+
}
66+
67+
// KMSConfig is used to sign/verify JWTs with Google Cloud KMS
68+
type KMSConfig struct {
69+
// KeyPath is the name of the key to use in the format of '/projects/-/locations/...'
70+
KeyPath string
71+
72+
GCPConfig
6473
}
6574

6675
// NewIAMContext returns a new context.Context that carries a provided IAMConfig value
@@ -74,6 +83,17 @@ func IAMFromContext(ctx context.Context) (*IAMConfig, bool) {
7483
return val, ok
7584
}
7685

86+
// NewKMSContext returns a new context.Context that carries a provided KMSConfig value
87+
func NewKMSContext(parent context.Context, val *KMSConfig) context.Context {
88+
return context.WithValue(parent, kmsConfigKey{}, val)
89+
}
90+
91+
// KMSFromContext extracts a KMSConfig from a context.Context
92+
func KMSFromContext(ctx context.Context) (*KMSConfig, bool) {
93+
val, ok := ctx.Value(kmsConfigKey{}).(*KMSConfig)
94+
return val, ok
95+
}
96+
7797
func getDefaultOauthClient(ctx context.Context) (*http.Client, error) {
7898
return google.DefaultClient(ctx, iam.CloudPlatformScope)
7999
}

iam.go

+15-25
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,32 @@ import (
1111

1212
type signingMethodIAM struct {
1313
alg string
14-
sign func(iamService *iam.Service, config *IAMConfig, signingString string) (string, error)
14+
sign func(ctx context.Context, iamService *iam.Service, config *IAMConfig, signingString string) (string, error)
1515
}
1616

17-
func getConfigFromKey(key interface{}) (context.Context, *IAMConfig, error) {
17+
func (s *signingMethodIAM) Alg() string {
18+
return s.alg
19+
}
20+
21+
// Sign implements the Sign method from jwt.SigningMethod
22+
// For this signing method, a valid context.Context must be
23+
// passed as the key containing a IAMConfig value
24+
// NOTE: The HEADER IS IGNORED for the signJWT API as the API will add its own
25+
func (s *signingMethodIAM) Sign(signingString string, key interface{}) (string, error) {
1826
var ctx context.Context
1927

2028
// check to make sure the key is a context.Context
2129
switch k := key.(type) {
2230
case context.Context:
2331
ctx = k
2432
default:
25-
return nil, nil, jwt.ErrInvalidKey
33+
return "", jwt.ErrInvalidKey
2634
}
2735

2836
// Get the IAMConfig from the context
2937
config, ok := IAMFromContext(ctx)
3038
if !ok {
31-
return ctx, nil, ErrMissingConfig
32-
}
33-
34-
return ctx, config, nil
35-
}
36-
37-
func (s *signingMethodIAM) Alg() string {
38-
return s.alg
39-
}
40-
41-
// Sign implements the Sign method from jwt.SigningMethod
42-
// For this signing method, a valid context.Context must be
43-
// passed as the key containing a IAMConfig value
44-
// NOTE: The HEADER IS IGNORED for the signJWT API as the API will add its own
45-
func (s *signingMethodIAM) Sign(signingString string, key interface{}) (string, error) {
46-
// Process the key
47-
ctx, config, err := getConfigFromKey(key)
48-
if err != nil {
49-
return "", err
39+
return "", ErrMissingConfig
5040
}
5141

5242
// Default config.OAuth2HTTPClient is a google.DefaultClient
@@ -70,12 +60,12 @@ func (s *signingMethodIAM) Sign(signingString string, key interface{}) (string,
7060
return "", err
7161
}
7262

73-
return s.sign(iamService, config, signingString)
63+
return s.sign(ctx, iamService, config, signingString)
7464
}
7565

76-
// VerfiyKeyfunc is a helper meant that returns a jwt.Keyfunc. It will handle pulling and selecting the certificates
66+
// IAMVerfiyKeyfunc is a helper meant that returns a jwt.Keyfunc. It will handle pulling and selecting the certificates
7767
// to verify signatures with, caching when enabled.
78-
func VerfiyKeyfunc(ctx context.Context, config *IAMConfig) jwt.Keyfunc {
68+
func IAMVerfiyKeyfunc(ctx context.Context, config *IAMConfig) jwt.Keyfunc {
7969
return func(token *jwt.Token) (interface{}, error) {
8070
// Make sure we have the proper header alg
8171
if _, ok := token.Method.(*signingMethodIAM); !ok {

iam_blob.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package gcpjwt
22

33
import (
4+
"context"
45
"encoding/base64"
56
"fmt"
67
"net/http"
@@ -34,15 +35,15 @@ func OverrideRS256WithIAMBlob() {
3435
})
3536
}
3637

37-
func signBlob(iamService *iam.Service, config *IAMConfig, signingString string) (string, error) {
38+
func signBlob(ctx context.Context, iamService *iam.Service, config *IAMConfig, signingString string) (string, error) {
3839
// Prepare the call
3940
signReq := &iam.SignBlobRequest{
4041
BytesToSign: base64.StdEncoding.EncodeToString([]byte(signingString)),
4142
}
4243
name := fmt.Sprintf("projects/%s/serviceAccounts/%s", config.ProjectID, config.ServiceAccount)
4344

4445
// Do the call
45-
signResp, err := iamService.Projects.ServiceAccounts.SignBlob(name, signReq).Do()
46+
signResp, err := iamService.Projects.ServiceAccounts.SignBlob(name, signReq).Context(ctx).Do()
4647
if err != nil {
4748
return "", err
4849
}

iam_jwt.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package gcpjwt
22

33
import (
4+
"context"
45
"fmt"
56
"net/http"
67
"strings"
@@ -34,7 +35,7 @@ func OverrideRS256WithIAMJWT() {
3435
})
3536
}
3637

37-
func signJwt(iamService *iam.Service, config *IAMConfig, signingString string) (string, error) {
38+
func signJwt(ctx context.Context, iamService *iam.Service, config *IAMConfig, signingString string) (string, error) {
3839
// Prepare the call
3940
// First decode the JSON string and discard the header
4041
parts := strings.Split(signingString, ".")
@@ -50,7 +51,7 @@ func signJwt(iamService *iam.Service, config *IAMConfig, signingString string) (
5051
name := fmt.Sprintf("projects/%s/serviceAccounts/%s", config.ProjectID, config.ServiceAccount)
5152

5253
// Do the call
53-
signResp, err := iamService.Projects.ServiceAccounts.SignJwt(name, signReq).Do()
54+
signResp, err := iamService.Projects.ServiceAccounts.SignJwt(name, signReq).Context(ctx).Do()
5455
if err != nil {
5556
return "", err
5657
}

iam_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func TestIAMSignAndVerify(t *testing.T) {
100100
}
101101

102102
token.Method = method
103-
keyFunc := VerfiyKeyfunc(c, config)
103+
keyFunc := IAMVerfiyKeyfunc(c, config)
104104
key, err := keyFunc(token)
105105
if err != nil {
106106
t.Fatalf("[%v] Error getting key: %v", data.name, err)

jwtmiddleware/appengine.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func NewHandler(_ context.Context, config *gcpjwt.IAMConfig, audience string) fu
2525
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2626
ctx := gcpjwt.NewIAMContext(appengine.NewContext(r), config)
2727

28-
keyFunc := gcpjwt.VerfiyKeyfunc(ctx, config)
28+
keyFunc := gcpjwt.IAMVerfiyKeyfunc(ctx, config)
2929
claims := &jwt.StandardClaims{}
3030

3131
token, err := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor, keyFunc, request.WithClaims(claims))

jwtmiddleware/middleware.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
func NewHandler(ctx context.Context, config *gcpjwt.IAMConfig, audience string) func(http.Handler) http.Handler {
2424
ctx = gcpjwt.NewIAMContext(ctx, config)
2525

26-
keyFunc := gcpjwt.VerfiyKeyfunc(ctx, config)
26+
keyFunc := gcpjwt.IAMVerfiyKeyfunc(ctx, config)
2727

2828
return func(h http.Handler) http.Handler {
2929
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

0 commit comments

Comments
 (0)