go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/config_service/internal/common/auth.go (about) 1 // Copyright 2023 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package common 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "net/http" 22 "strings" 23 "time" 24 25 credentials "cloud.google.com/go/iam/credentials/apiv1" 26 "cloud.google.com/go/iam/credentials/apiv1/credentialspb" 27 "google.golang.org/api/option" 28 29 luciauth "go.chromium.org/luci/auth" 30 "go.chromium.org/luci/common/clock" 31 "go.chromium.org/luci/common/errors" 32 "go.chromium.org/luci/common/gcloud/iam" 33 "go.chromium.org/luci/server/auth" 34 ) 35 36 // GetSelfSignedJWTTransport returns a transport that add self signed jwt token 37 // as authorization header. 38 func GetSelfSignedJWTTransport(ctx context.Context, aud string) (http.RoundTripper, error) { 39 tr, err := auth.GetRPCTransport(ctx, auth.NoAuth) 40 if err != nil { 41 return nil, fmt.Errorf("failed to create RPC transport: %w", err) 42 } 43 signer := auth.GetSigner(ctx) 44 info, err := signer.ServiceInfo(ctx) 45 switch { 46 case err != nil: 47 return nil, fmt.Errorf("failed to get service account for the service: %w", err) 48 case info.ServiceAccountName == "": 49 return nil, errors.New("the current service account is empty") 50 } 51 serviceAccount := info.ServiceAccountName 52 return luciauth.NewModifyingTransport(tr, func(req *http.Request) error { 53 ts, err := auth.GetTokenSource(ctx, auth.AsSelf, auth.WithScopes(auth.CloudOAuthScopes...)) 54 if err != nil { 55 return fmt.Errorf("failed to get OAuth2 token source: %w", err) 56 } 57 cc, err := credentials.NewIamCredentialsClient(ctx, option.WithTokenSource(ts)) 58 if err != nil { 59 return fmt.Errorf("failed to create IAM Credentials client: %w", err) 60 } 61 defer func() { _ = cc.Close() }() 62 63 now := clock.Now(ctx).UTC() 64 cs := &iam.ClaimSet{ 65 Iss: serviceAccount, 66 Scope: strings.Join(auth.CloudOAuthScopes, " "), 67 Aud: aud, 68 Exp: now.Add(2 * time.Minute).Unix(), 69 Iat: now.Unix(), 70 } 71 payload, err := json.Marshal(cs) 72 if err != nil { 73 return fmt.Errorf("failed to marshall claim set to JSON: %w", err) 74 } 75 res, err := cc.SignJwt(ctx, &credentialspb.SignJwtRequest{ 76 Name: fmt.Sprintf("projects/-/serviceAccounts/%s", serviceAccount), 77 Payload: string(payload), 78 }) 79 if err != nil { 80 return fmt.Errorf("failed to signJwt: %w", err) 81 } 82 req.Header.Set("Authorization", "Bearer "+res.SignedJwt) 83 return nil 84 }), nil 85 }