github.com/letsencrypt/boulder@v0.20251208.0/cmd/admin/overrides_add.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"flag"
     7  	"fmt"
     8  	"net/netip"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/letsencrypt/boulder/config"
    13  	"github.com/letsencrypt/boulder/identifier"
    14  	"github.com/letsencrypt/boulder/policy"
    15  	rl "github.com/letsencrypt/boulder/ratelimits"
    16  	sapb "github.com/letsencrypt/boulder/sa/proto"
    17  	"google.golang.org/protobuf/types/known/durationpb"
    18  )
    19  
    20  type subcommandAddOverride struct {
    21  	limit            string
    22  	regId            int64
    23  	singleIdentifier string
    24  	setOfIdentifiers string
    25  	subscriberIP     string
    26  	count            int64
    27  	burst            int64
    28  	period           string
    29  	comment          string
    30  }
    31  
    32  func (*subcommandAddOverride) Desc() string {
    33  	return "Add or update a rate limit override. New overrides are enabled by default. Updates to existing overrides will not change the enabled state."
    34  }
    35  
    36  func (c *subcommandAddOverride) Flags(f *flag.FlagSet) {
    37  	f.StringVar(&c.limit, "limit", "", "ratelimit name (required)")
    38  	f.Int64Var(&c.regId, "regId", 0, "a single registration/account ID")
    39  	f.StringVar(&c.singleIdentifier, "singleIdentifier", "", "a single identifier (e.g. example.com or www.example.com or 55.66.77.88 or 2602:80a:6000::1)")
    40  	f.StringVar(&c.setOfIdentifiers, "setOfIdentifiers", "", "comma-separated list of unique identifiers (e.g. example.com,www.example.com,55.66.77.88,2602:80a:6000::1)")
    41  	f.StringVar(&c.subscriberIP, "subscriberIP", "", "a single IPv4/IPv6 address the subscriber uses for requests (e.g. 55.66.77.88 or 2602:80a:6000::1)")
    42  
    43  	f.Int64Var(&c.count, "count", 0, "allowed requests per period (required)")
    44  	f.Int64Var(&c.burst, "burst", 0, "burst size (required)")
    45  	f.StringVar(&c.period, "period", "", "period duration (e.g. 1h, 168h) (required)")
    46  	f.StringVar(&c.comment, "comment", "", "comment for the override (required)")
    47  }
    48  
    49  // validateIdentifiers checks that the provided identifiers are valid according policy.
    50  func validateIdentifiers(idents ...identifier.ACMEIdentifier) error {
    51  	for _, ident := range idents {
    52  		switch ident.Type {
    53  		case identifier.TypeDNS:
    54  			err := policy.ValidDomain(ident.Value)
    55  			if err != nil {
    56  				return fmt.Errorf("invalid domain %s: %s", ident.Value, err)
    57  			}
    58  		case identifier.TypeIP:
    59  			err := policy.ValidIP(ident.Value)
    60  			if err != nil {
    61  				return fmt.Errorf("invalid IP address %s", ident.Value)
    62  			}
    63  		}
    64  	}
    65  	return nil
    66  }
    67  
    68  func (c *subcommandAddOverride) Run(ctx context.Context, a *admin) error {
    69  	if c.limit == "" {
    70  		return errors.New("--limit is required")
    71  	}
    72  	if c.count == 0 || c.burst == 0 || c.period == "" || c.comment == "" {
    73  		return errors.New("all of --count, --burst, --period, and --comment are required")
    74  	}
    75  
    76  	name, ok := rl.StringToName[c.limit]
    77  	if !ok {
    78  		return fmt.Errorf("unknown limit name %q, must be one in %s", c.limit, rl.LimitNames)
    79  	}
    80  
    81  	dur, err := time.ParseDuration(c.period)
    82  	if err != nil {
    83  		return fmt.Errorf("invalid period value: %s", err)
    84  	}
    85  
    86  	var subscriberIP netip.Addr
    87  	if c.subscriberIP != "" {
    88  		subscriberIP, err = netip.ParseAddr(c.subscriberIP)
    89  		if err != nil {
    90  			return fmt.Errorf("invalid subscriberIP %q", err)
    91  		}
    92  		err := policy.ValidIP(c.subscriberIP)
    93  		if err != nil {
    94  			return fmt.Errorf("invalid subscriberIP %q: %w", c.subscriberIP, err)
    95  		}
    96  	}
    97  
    98  	singleIdent := identifier.FromString(c.singleIdentifier)
    99  	err = validateIdentifiers(singleIdent)
   100  	if err != nil {
   101  		return fmt.Errorf("invalid singleIdentifier: %w", err)
   102  	}
   103  
   104  	var setOfIdents identifier.ACMEIdentifiers
   105  	if c.setOfIdentifiers != "" {
   106  		setOfIdents = identifier.FromStringSlice(strings.Split(c.setOfIdentifiers, ","))
   107  		err := validateIdentifiers(setOfIdents...)
   108  		if err != nil {
   109  			return fmt.Errorf("invalid setOfIdentifiers: %w", err)
   110  		}
   111  	}
   112  
   113  	bucketKey, err := rl.BuildBucketKey(name, c.regId, singleIdent, setOfIdents, subscriberIP)
   114  	if err != nil {
   115  		return fmt.Errorf("building bucket key for limit %s: %s", name, err)
   116  	}
   117  
   118  	err = rl.ValidateLimit(&rl.Limit{
   119  		Name:   name,
   120  		Count:  c.count,
   121  		Burst:  c.burst,
   122  		Period: config.Duration{Duration: dur},
   123  	})
   124  	if err != nil {
   125  		return fmt.Errorf("validating override for limit %s key %q: %s", name, bucketKey, err)
   126  	}
   127  
   128  	resp, err := a.sac.AddRateLimitOverride(ctx, &sapb.AddRateLimitOverrideRequest{
   129  		Override: &sapb.RateLimitOverride{
   130  			LimitEnum: int64(name),
   131  			BucketKey: bucketKey,
   132  			Count:     c.count,
   133  			Burst:     c.burst,
   134  			Period:    durationpb.New(dur),
   135  			Comment:   c.comment,
   136  		},
   137  	})
   138  	if err != nil {
   139  		return fmt.Errorf("adding override for limit %s key %q: %s", name, bucketKey, err)
   140  	}
   141  
   142  	status := "disabled"
   143  	if resp.Enabled {
   144  		status = "enabled"
   145  	}
   146  
   147  	if resp.Inserted {
   148  		a.log.Infof("Added new override for limit %s key %q, status=[%s]\n", name, bucketKey, status)
   149  	} else {
   150  		a.log.Infof("Updated existing override for limit %s key %q, status=[%s]\n", name, bucketKey, status)
   151  	}
   152  	return nil
   153  }