1 package ratelimits
2
3 import (
4 "fmt"
5 "os"
6 "strings"
7
8 "github.com/letsencrypt/boulder/config"
9 "github.com/letsencrypt/boulder/core"
10 "github.com/letsencrypt/boulder/strictyaml"
11 )
12
13 type limit struct {
14
15
16 Burst int64
17
18
19
20 Count int64
21
22
23
24 Period config.Duration
25
26
27
28
29
30
31 emissionInterval int64
32
33
34
35
36 burstOffset int64
37
38
39
40 isOverride bool
41 }
42
43 func precomputeLimit(l limit) limit {
44 l.emissionInterval = l.Period.Nanoseconds() / l.Count
45 l.burstOffset = l.emissionInterval * l.Burst
46 return l
47 }
48
49 func validateLimit(l limit) error {
50 if l.Burst <= 0 {
51 return fmt.Errorf("invalid burst '%d', must be > 0", l.Burst)
52 }
53 if l.Count <= 0 {
54 return fmt.Errorf("invalid count '%d', must be > 0", l.Count)
55 }
56 if l.Period.Duration <= 0 {
57 return fmt.Errorf("invalid period '%s', must be > 0", l.Period)
58 }
59 return nil
60 }
61
62 type limits map[string]limit
63
64
65 func loadLimits(path string) (limits, error) {
66 lm := make(limits)
67 data, err := os.ReadFile(path)
68 if err != nil {
69 return nil, err
70 }
71 err = strictyaml.Unmarshal(data, &lm)
72 if err != nil {
73 return nil, err
74 }
75 return lm, nil
76 }
77
78
79 func parseOverrideNameId(key string) (Name, string, error) {
80 if !strings.Contains(key, ":") {
81
82 return Unknown, "", fmt.Errorf("invalid override %q, must be formatted 'name:id'", key)
83 }
84 nameAndId := strings.SplitN(key, ":", 2)
85 nameStr := nameAndId[0]
86 if nameStr == "" {
87 return Unknown, "", fmt.Errorf("empty name in override %q, must be formatted 'name:id'", key)
88 }
89
90 name, ok := stringToName[nameStr]
91 if !ok {
92 return Unknown, "", fmt.Errorf("unrecognized name %q in override limit %q, must be one of %v", nameStr, key, limitNames)
93 }
94 id := nameAndId[1]
95 if id == "" {
96 return Unknown, "", fmt.Errorf("empty id in override %q, must be formatted 'name:id'", key)
97 }
98 return name, id, nil
99 }
100
101
102
103 func loadAndParseOverrideLimits(path string) (limits, error) {
104 fromFile, err := loadLimits(path)
105 if err != nil {
106 return nil, err
107 }
108 parsed := make(limits, len(fromFile))
109
110 for k, v := range fromFile {
111 err = validateLimit(v)
112 if err != nil {
113 return nil, fmt.Errorf("validating override limit %q: %w", k, err)
114 }
115 name, id, err := parseOverrideNameId(k)
116 if err != nil {
117 return nil, fmt.Errorf("parsing override limit %q: %w", k, err)
118 }
119 err = validateIdForName(name, id)
120 if err != nil {
121 return nil, fmt.Errorf(
122 "validating name %s and id %q for override limit %q: %w", name, id, k, err)
123 }
124 if name == CertificatesPerFQDNSetPerAccount {
125
126
127
128 regIdDomains := strings.SplitN(id, ":", 2)
129 if len(regIdDomains) != 2 {
130
131 return nil, fmt.Errorf("invalid override limit %q, must be formatted 'name:id'", k)
132 }
133 regId := regIdDomains[0]
134 domains := strings.Split(regIdDomains[1], ",")
135 fqdnSet := core.HashNames(domains)
136 id = fmt.Sprintf("%s:%s", regId, fqdnSet)
137 }
138 v.isOverride = true
139 parsed[bucketKey(name, id)] = precomputeLimit(v)
140 }
141 return parsed, nil
142 }
143
144
145
146 func loadAndParseDefaultLimits(path string) (limits, error) {
147 fromFile, err := loadLimits(path)
148 if err != nil {
149 return nil, err
150 }
151 parsed := make(limits, len(fromFile))
152
153 for k, v := range fromFile {
154 err := validateLimit(v)
155 if err != nil {
156 return nil, fmt.Errorf("parsing default limit %q: %w", k, err)
157 }
158 name, ok := stringToName[k]
159 if !ok {
160 return nil, fmt.Errorf("unrecognized name %q in default limit, must be one of %v", k, limitNames)
161 }
162 parsed[nameToEnumString(name)] = precomputeLimit(v)
163 }
164 return parsed, nil
165 }
166
View as plain text