go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/appengine/gaeauth/server/gaesigner/signer.go (about)

     1  // Copyright 2015 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 gaesigner implements signing.Signer interface using GAE App Identity
    16  // API.
    17  package gaesigner
    18  
    19  import (
    20  	"context"
    21  	"runtime"
    22  	"strings"
    23  	"time"
    24  
    25  	"go.chromium.org/luci/gae/service/info"
    26  
    27  	"go.chromium.org/luci/common/clock"
    28  	"go.chromium.org/luci/server/auth/signing"
    29  	"go.chromium.org/luci/server/caching"
    30  )
    31  
    32  // Signer implements signing.Signer using GAE App Identity API.
    33  //
    34  // Deprecated: use GetSigner from go.chromium.org/luci/server/auth instead.
    35  type Signer struct{}
    36  
    37  // SignBytes signs the blob with some active private key.
    38  //
    39  // Returns the signature and name of the key used.
    40  func (Signer) SignBytes(ctx context.Context, blob []byte) (keyName string, signature []byte, err error) {
    41  	return info.SignBytes(ctx, blob)
    42  }
    43  
    44  // Certificates returns a bundle with public certificates for all active keys.
    45  func (Signer) Certificates(ctx context.Context) (*signing.PublicCertificates, error) {
    46  	return getCachedCerts(ctx)
    47  }
    48  
    49  // ServiceInfo returns information about the current service.
    50  //
    51  // It includes app ID and the service account name (that ultimately owns the
    52  // signing private key).
    53  func (Signer) ServiceInfo(ctx context.Context) (*signing.ServiceInfo, error) {
    54  	return getCachedInfo(ctx)
    55  }
    56  
    57  ////
    58  
    59  var (
    60  	certCache = caching.RegisterCacheSlot()
    61  	infoCache = caching.RegisterCacheSlot()
    62  )
    63  
    64  // cachedCerts caches this app certs in local memory for 1 hour.
    65  func getCachedCerts(ctx context.Context) (*signing.PublicCertificates, error) {
    66  	v, err := certCache.Fetch(ctx, func(any) (any, time.Duration, error) {
    67  		aeCerts, err := info.PublicCertificates(ctx)
    68  		if err != nil {
    69  			return nil, 0, err
    70  		}
    71  		certs := make([]signing.Certificate, len(aeCerts))
    72  		for i, ac := range aeCerts {
    73  			certs[i] = signing.Certificate{
    74  				KeyName:            ac.KeyName,
    75  				X509CertificatePEM: string(ac.Data),
    76  			}
    77  		}
    78  		inf, err := getCachedInfo(ctx)
    79  		if err != nil {
    80  			return nil, 0, err
    81  		}
    82  		return &signing.PublicCertificates{
    83  			AppID:              inf.AppID,
    84  			ServiceAccountName: inf.ServiceAccountName,
    85  			Certificates:       certs,
    86  			Timestamp:          signing.JSONTime(clock.Now(ctx)),
    87  		}, time.Hour, nil
    88  	})
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	return v.(*signing.PublicCertificates), nil
    93  }
    94  
    95  // getCachedINfo caches this app service info in local memory forever.
    96  //
    97  // This info is static during lifetime of the process.
    98  func getCachedInfo(ctx context.Context) (*signing.ServiceInfo, error) {
    99  	v, err := infoCache.Fetch(ctx, func(any) (any, time.Duration, error) {
   100  		account, err := info.ServiceAccount(ctx)
   101  		if err != nil {
   102  			return nil, 0, err
   103  		}
   104  		return &signing.ServiceInfo{
   105  			AppID:              info.AppID(ctx),
   106  			AppRuntime:         "go",
   107  			AppRuntimeVersion:  runtime.Version(),
   108  			AppVersion:         strings.Split(info.VersionID(ctx), ".")[0],
   109  			ServiceAccountName: account,
   110  		}, 0, nil
   111  	})
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	return v.(*signing.ServiceInfo), nil
   116  }