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 }