selfsignedjwt.go raw

   1  // Copyright 2023 Google LLC
   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 credentials
  16  
  17  import (
  18  	"context"
  19  	"crypto"
  20  	"errors"
  21  	"fmt"
  22  	"log/slog"
  23  	"strings"
  24  	"time"
  25  
  26  	"cloud.google.com/go/auth"
  27  	"cloud.google.com/go/auth/internal"
  28  	"cloud.google.com/go/auth/internal/credsfile"
  29  	"cloud.google.com/go/auth/internal/jwt"
  30  )
  31  
  32  var (
  33  	// for testing
  34  	now func() time.Time = time.Now
  35  )
  36  
  37  // configureSelfSignedJWT uses the private key in the service account to create
  38  // a JWT without making a network call.
  39  func configureSelfSignedJWT(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
  40  	if len(opts.scopes()) == 0 && opts.Audience == "" {
  41  		return nil, errors.New("credentials: both scopes and audience are empty")
  42  	}
  43  	signer, err := internal.ParseKey([]byte(f.PrivateKey))
  44  	if err != nil {
  45  		return nil, fmt.Errorf("credentials: could not parse key: %w", err)
  46  	}
  47  	return &selfSignedTokenProvider{
  48  		email:    f.ClientEmail,
  49  		audience: opts.Audience,
  50  		scopes:   opts.scopes(),
  51  		signer:   signer,
  52  		pkID:     f.PrivateKeyID,
  53  		logger:   opts.logger(),
  54  	}, nil
  55  }
  56  
  57  type selfSignedTokenProvider struct {
  58  	email    string
  59  	audience string
  60  	scopes   []string
  61  	signer   crypto.Signer
  62  	pkID     string
  63  	logger   *slog.Logger
  64  }
  65  
  66  func (tp *selfSignedTokenProvider) Token(context.Context) (*auth.Token, error) {
  67  	iat := now()
  68  	exp := iat.Add(time.Hour)
  69  	scope := strings.Join(tp.scopes, " ")
  70  	c := &jwt.Claims{
  71  		Iss:   tp.email,
  72  		Sub:   tp.email,
  73  		Aud:   tp.audience,
  74  		Scope: scope,
  75  		Iat:   iat.Unix(),
  76  		Exp:   exp.Unix(),
  77  	}
  78  	h := &jwt.Header{
  79  		Algorithm: jwt.HeaderAlgRSA256,
  80  		Type:      jwt.HeaderType,
  81  		KeyID:     string(tp.pkID),
  82  	}
  83  	tok, err := jwt.EncodeJWS(h, c, tp.signer)
  84  	if err != nil {
  85  		return nil, fmt.Errorf("credentials: could not encode JWT: %w", err)
  86  	}
  87  	tp.logger.Debug("created self-signed JWT", "token", tok)
  88  	return &auth.Token{Value: tok, Type: internal.TokenTypeBearer, Expiry: exp}, nil
  89  }
  90