go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/appengine/backend/main.go (about)

     1  // Copyright 2017 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  // Binary backend implements HTTP server that handles requests to 'backend'
    16  // module.
    17  package main
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  
    24  	"google.golang.org/protobuf/types/known/emptypb"
    25  
    26  	"go.chromium.org/luci/auth/identity"
    27  	"go.chromium.org/luci/common/errors"
    28  	"go.chromium.org/luci/common/logging"
    29  	"go.chromium.org/luci/grpc/grpcutil"
    30  	"go.chromium.org/luci/server"
    31  	"go.chromium.org/luci/server/auth"
    32  	"go.chromium.org/luci/server/auth/openid"
    33  	"go.chromium.org/luci/server/cron"
    34  	"go.chromium.org/luci/server/router"
    35  
    36  	"go.chromium.org/luci/tokenserver/api/admin/v1"
    37  
    38  	"go.chromium.org/luci/tokenserver/appengine/impl"
    39  	"go.chromium.org/luci/tokenserver/appengine/impl/utils/bq"
    40  )
    41  
    42  func main() {
    43  	impl.Main(func(srv *server.Server, services *impl.Services) error {
    44  		bqInserter, err := bq.NewInserter(srv.Context, srv.Options.CloudProject)
    45  		if err != nil {
    46  			return err
    47  		}
    48  
    49  		// GAE crons.
    50  		cron.RegisterHandler("read-config", func(ctx context.Context) error {
    51  			return readConfigCron(ctx, services.Admin)
    52  		})
    53  		cron.RegisterHandler("fetch-crl", func(ctx context.Context) error {
    54  			return fetchCRLCron(ctx, services.Certs)
    55  		})
    56  
    57  		// PubSub push handler processing messages produced by impl/utils/bq.
    58  		oidcMW := router.NewMiddlewareChain(
    59  			auth.Authenticate(&openid.GoogleIDTokenAuthMethod{
    60  				AudienceCheck: openid.AudienceMatchesHost,
    61  			}),
    62  		)
    63  		// bigquery-log-pubsub@ is a part of the PubSub Push subscription config.
    64  		pusherID := identity.Identity(fmt.Sprintf("user:bigquery-log-pubsub@%s.iam.gserviceaccount.com", srv.Options.CloudProject))
    65  		srv.Routes.POST("/internal/pubsub/bigquery-log", oidcMW, func(c *router.Context) {
    66  			if got := auth.CurrentIdentity(c.Request.Context()); got != pusherID {
    67  				logging.Errorf(c.Request.Context(), "Expecting ID token of %q, got %q", pusherID, got)
    68  				c.Writer.WriteHeader(403)
    69  			} else {
    70  				err := bqInserter.HandlePubSubPush(c.Request.Context(), c.Request.Body)
    71  				if err != nil {
    72  					logging.Errorf(c.Request.Context(), "Failed to process the message: %s", err)
    73  					c.Writer.WriteHeader(500)
    74  				}
    75  			}
    76  		})
    77  
    78  		return nil
    79  	})
    80  }
    81  
    82  // readConfigCron is a handler for the "read-config" cron.
    83  func readConfigCron(ctx context.Context, adminServer admin.AdminServer) error {
    84  	// All config fetching callbacks to call in parallel.
    85  	fetchers := []struct {
    86  		name string
    87  		cb   func(context.Context, *emptypb.Empty) (*admin.ImportedConfigs, error)
    88  	}{
    89  		{"ImportCAConfigs", adminServer.ImportCAConfigs},
    90  		{"ImportDelegationConfigs", adminServer.ImportDelegationConfigs},
    91  		{"ImportProjectIdentityConfigs", adminServer.ImportProjectIdentityConfigs},
    92  		{"ImportProjectOwnedAccountsConfigs", adminServer.ImportProjectOwnedAccountsConfigs},
    93  	}
    94  
    95  	errs := errors.NewLazyMultiError(len(fetchers))
    96  
    97  	wg := sync.WaitGroup{}
    98  	wg.Add(len(fetchers))
    99  	for idx, fetcher := range fetchers {
   100  		idx := idx
   101  		fetcher := fetcher
   102  		go func() {
   103  			defer wg.Done()
   104  			if _, err := fetcher.cb(ctx, nil); err != nil {
   105  				logging.Errorf(ctx, "%s failed - %s", fetcher.name, err)
   106  				errs.Assign(idx, grpcutil.WrapIfTransient(err))
   107  			}
   108  		}()
   109  	}
   110  	wg.Wait()
   111  
   112  	return errs.Get()
   113  }
   114  
   115  // fetchCRLCron is a handler for the "fetch-crl" cron.
   116  func fetchCRLCron(ctx context.Context, caServer admin.CertificateAuthoritiesServer) error {
   117  	list, err := caServer.ListCAs(ctx, nil)
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	errs := errors.NewLazyMultiError(len(list.Cn))
   123  
   124  	// Fetch CRL of each active CA in parallel. In practice there are very few
   125  	// CAs there (~= 1), so the risk of OOM is small.
   126  	wg := sync.WaitGroup{}
   127  	wg.Add(len(list.Cn))
   128  	for idx, cn := range list.Cn {
   129  		idx := idx
   130  		cn := cn
   131  		go func() {
   132  			defer wg.Done()
   133  			if _, err := caServer.FetchCRL(ctx, &admin.FetchCRLRequest{Cn: cn}); err != nil {
   134  				logging.Errorf(ctx, "FetchCRL(%q) failed - %s", cn, err)
   135  				errs.Assign(idx, grpcutil.WrapIfTransient(err))
   136  			}
   137  		}()
   138  	}
   139  	wg.Wait()
   140  
   141  	return errs.Get()
   142  }