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

     1  // Copyright 2021 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 main is the main point of entry for the backend module.
    16  //
    17  // It handles task queue tasks and cron jobs.
    18  package main
    19  
    20  // Disable linting for imports, because of the dependency on config
    21  // validation globals.
    22  //nolint:all
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"time"
    27  
    28  	"go.chromium.org/luci/auth_service/impl"
    29  	"go.chromium.org/luci/auth_service/impl/model"
    30  
    31  	// Ensure registration of validation rules.
    32  	// NOTE: this must go before anything that depends on validation globals,
    33  	// e.g. cfgcache.Register in srvcfg files in allowlistcfg/ or oauthcfg/.
    34  	"go.chromium.org/luci/auth_service/internal/configs/srvcfg/allowlistcfg"
    35  	"go.chromium.org/luci/auth_service/internal/configs/srvcfg/oauthcfg"
    36  	"go.chromium.org/luci/auth_service/internal/configs/srvcfg/permissionscfg"
    37  	"go.chromium.org/luci/auth_service/internal/configs/srvcfg/securitycfg"
    38  	"go.chromium.org/luci/auth_service/internal/configs/srvcfg/settingscfg"
    39  	"go.chromium.org/luci/auth_service/internal/configs/validation"
    40  
    41  	"go.chromium.org/luci/auth_service/internal/permissions"
    42  	"go.chromium.org/luci/auth_service/internal/pubsub"
    43  	"go.chromium.org/luci/auth_service/internal/realmsinternals"
    44  
    45  	"go.chromium.org/luci/common/errors"
    46  	"go.chromium.org/luci/common/logging"
    47  	"go.chromium.org/luci/server"
    48  	"go.chromium.org/luci/server/cron"
    49  	"go.chromium.org/luci/server/module"
    50  )
    51  
    52  func main() {
    53  	modules := []module.Module{
    54  		cron.NewModuleFromFlags(),
    55  	}
    56  
    57  	// Parse flags from environment variables.
    58  	dryRunCronConfig := model.ParseDryRunEnvVar(model.DryRunCronConfigEnvVar)
    59  	dryRunCronRealms := model.ParseDryRunEnvVar(model.DryRunCronRealmsEnvVar)
    60  	dryRunCronStaleAuth := model.ParseDryRunEnvVar(model.DryRunCronStaleAuthEnvVar)
    61  
    62  	impl.Main(modules, func(srv *server.Server) error {
    63  		cron.RegisterHandler("update-config", func(ctx context.Context) error {
    64  			historicalComment := "Updated from update-config cron"
    65  
    66  			// ip_allowlist.cfg handling.
    67  			if err := allowlistcfg.Update(ctx); err != nil {
    68  				return err
    69  			}
    70  			cfg, err := allowlistcfg.Get(ctx)
    71  			if err != nil {
    72  				return err
    73  			}
    74  			subnets, err := validation.GetSubnets(cfg.IpAllowlists)
    75  			if err != nil {
    76  				return err
    77  			}
    78  			if err := model.UpdateAllowlistEntities(ctx, subnets, dryRunCronConfig, historicalComment); err != nil {
    79  				return err
    80  			}
    81  
    82  			// oauth.cfg handling.
    83  			if err := oauthcfg.Update(ctx); err != nil {
    84  				return err
    85  			}
    86  			oauthcfg, err := oauthcfg.Get(ctx)
    87  			if err != nil {
    88  				return err
    89  			}
    90  
    91  			// security.cfg handling.
    92  			if err := securitycfg.Update(ctx); err != nil {
    93  				return err
    94  			}
    95  			securitycfg, err := securitycfg.Get(ctx)
    96  			if err != nil {
    97  				return err
    98  			}
    99  
   100  			if err := model.UpdateAuthGlobalConfig(ctx, oauthcfg, securitycfg, dryRunCronConfig, historicalComment); err != nil {
   101  				return err
   102  			}
   103  
   104  			// settings.cfg handling
   105  			if err := settingscfg.Update(ctx); err != nil {
   106  				return err
   107  			}
   108  
   109  			return nil
   110  		})
   111  
   112  		cron.RegisterHandler("update-realms", func(ctx context.Context) error {
   113  			historicalComment := "Updated from update-realms cron"
   114  
   115  			// permissions.cfg handling.
   116  			if err := permissionscfg.Update(ctx); err != nil {
   117  				return err
   118  			}
   119  			permsCfg, permsMeta, err := permissionscfg.GetWithMetadata(ctx)
   120  			if err != nil {
   121  				return err
   122  			}
   123  			if err := model.UpdateAuthRealmsGlobals(ctx, permsCfg, dryRunCronRealms, historicalComment); err != nil {
   124  				return err
   125  			}
   126  
   127  			// Make the PermissionsDB for realms expansion.
   128  			permsDB := permissions.NewPermissionsDB(permsCfg, permsMeta)
   129  
   130  			// realms.cfg handling.
   131  			latestRealms, storedRealms, err := realmsinternals.GetConfigs(ctx)
   132  			if err != nil {
   133  				logging.Errorf(ctx, "aborting realms update - failed to fetch latest for all configs: %v", err)
   134  				return err
   135  			}
   136  			jobs, err := realmsinternals.CheckConfigChanges(ctx, permsDB, latestRealms, storedRealms, dryRunCronRealms, historicalComment)
   137  			if err != nil {
   138  				return err
   139  			}
   140  			if !executeJobs(ctx, jobs, 2*time.Second) {
   141  				return fmt.Errorf("not all jobs succeeded when refreshing realms")
   142  			}
   143  
   144  			return nil
   145  		})
   146  
   147  		cron.RegisterHandler("revoke-stale-authorization", func(ctx context.Context) error {
   148  			// Only members of the below trusted group are eligible to:
   149  			// * be authorized to subscribe to PubSub notifications of AuthDB changes
   150  			// * be authorized to read the AuthDB from Google Storage.
   151  			// This cron revokes all stale authorizations for accounts that are no
   152  			// longer in the trusted group.
   153  			trustedGroup := model.TrustedServicesGroup
   154  
   155  			if err := pubsub.RevokeStaleAuthorization(ctx, trustedGroup, dryRunCronStaleAuth); err != nil {
   156  				err = errors.Annotate(err, "error revoking stale PubSub authorizations").Err()
   157  				logging.Errorf(ctx, err.Error())
   158  				return err
   159  			}
   160  
   161  			if err := model.RevokeStaleReaderAccess(ctx, trustedGroup, dryRunCronStaleAuth); err != nil {
   162  				err = errors.Annotate(err, "error revoking stale AuthDB reader access").Err()
   163  				logging.Errorf(ctx, err.Error())
   164  				return err
   165  			}
   166  
   167  			return nil
   168  		})
   169  
   170  		// TODO: Remove comparison code once we have fully rolled out Auth
   171  		// Service v2 (b/321019030).
   172  		cron.RegisterHandler("auth-service-v2-validation", func(ctx context.Context) error {
   173  			logging.Infof(ctx, "starting comparison of V2 entities for validation")
   174  			return model.CompareV2Entities(ctx)
   175  		})
   176  
   177  		return nil
   178  	})
   179  }
   180  
   181  // executeJobs executes the callbacks, sleeping the set amount of time
   182  // between each. Note: all callbacks will be run, even if a previous job
   183  // returned an error.
   184  //
   185  // Returns whether any job returned an error.
   186  func executeJobs(ctx context.Context, jobs []func() error, sleepTime time.Duration) bool {
   187  	success := true
   188  	for i, job := range jobs {
   189  		if i > 0 {
   190  			time.Sleep(sleepTime)
   191  		}
   192  		if err := job(); err != nil {
   193  			logging.Errorf(ctx, "job %d out of %d failed: %s", i+1, len(jobs), err)
   194  			success = false
   195  		}
   196  	}
   197  	return success
   198  }