Skip to content

Commit 1bf39e3

Browse files
breadyzhangFreddy Zhangonlywork1984flavio
authored
Fix flaky CT submission bug (#1085)
* Create additional conditions to determine log results and if a log should be submitted when minInclusions > 0. * modify chromeLike unit test to better fit the actual use case and refine how safeSubmissionState decides which SCTs to insert in the results * update changelog * Remove MaxSubmissions and set maxSubmissionsPerGroup from the minGroups value * Set max submissions per operator in ctpolicy package * Removed to reduce complexity and confusion. * Resolving comments - move base into switch expression - change maxSubmissionsPerGroup to maxSubmissionsPerOperator * Use MinDistinctOperators instead of MaxSubmissionsPerOperator to reduce confusion * Add zOS build support (#1088) * Add support for WASI port (#1089) Fix building when the new `wasip1` port is being used. This is a new target that will be introduced by go 1.21. For more details golang/go#58141 Signed-off-by: Flavio Castelli <[email protected]> * update changelog * fix changelog merge issues * My merge conflict mishap reverted the change to have groups be a map[string]int so I am reverting it back to the updated state * replace groupNeeds with minSubmissions and change the name of groups to groupsSubmitted groupNeeds was used for the old chrome policy when we required SCTs from specific groups. It's not necessary anymore with the new policies so a single integer (minSubmissions) should be suffice. groups is changed to groupsSubmitted to make it easier to understand upon a glance. * change minSubmissions since it gets changed after initialization * Change dayDuration to use time.Hour for easier understanding * Resolve comments * add comments to clarify reservedSubmissions --------- Co-authored-by: Freddy Zhang <[email protected]> Co-authored-by: onlywork1984 <[email protected]> Co-authored-by: Flavio Castelli <[email protected]>
1 parent b379604 commit 1bf39e3

File tree

6 files changed

+66
-84
lines changed

6 files changed

+66
-84
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## HEAD
44

5+
### Fix flaky submission failures
6+
* #1084: CT flaky submission failures
7+
58
### Add support for WASI port
69
* Add build tags for wasip1 GOOS
710

ctpolicy/chromepolicy.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import (
2222
)
2323

2424
const (
25-
minOperators = 2 // minimum number of distinct CT log operators that issue an SCT.
26-
dayDuration = 86400 * time.Second // time.Duration of one day
25+
dayDuration = 24 * time.Hour // time.Duration of one day
26+
minDistinctOperators = 2 // Number of distinct CT log operators that submit an SCT
2727
)
2828

2929
// ChromeCTPolicy implements logic for complying with Chrome's CT log policy
@@ -56,7 +56,7 @@ func (chromeP ChromeCTPolicy) LogsByGroup(cert *x509.Certificate, approved *logl
5656
if err != nil {
5757
return nil, err
5858
}
59-
baseGroup.MinOperators = minOperators
59+
baseGroup.MinDistinctOperators = minDistinctOperators
6060
groups[baseGroup.Name] = baseGroup
6161
return groups, nil
6262
}

ctpolicy/chromepolicy_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ func wantedGroups(base int, minusBob bool) LogPolicyData {
6161
"https://ct.googleapis.com/racketeer/": true,
6262
"https://log.bob.io": true,
6363
},
64-
MinInclusions: base,
65-
MinOperators: minOperators,
66-
IsBase: true,
64+
MinInclusions: base,
65+
MinDistinctOperators: minDistinctOperators,
66+
IsBase: true,
6767
LogWeights: map[string]float32{
6868
"https://ct.googleapis.com/logs/argon2020/": 1.0,
6969
"https://ct.googleapis.com/aviator/": 1.0,

ctpolicy/ctpolicy.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ const (
3131

3232
// LogGroupInfo holds information on a single group of logs specified by Policy.
3333
type LogGroupInfo struct {
34-
Name string
35-
LogURLs map[string]bool // set of members
36-
MinInclusions int // Required number of submissions.
37-
MinOperators int // Required number of distinct CT log operators.
38-
IsBase bool // True only for Log-group covering all logs.
39-
LogWeights map[string]float32 // weights used for submission, default weight is 1
40-
wMu sync.RWMutex // guards weights
34+
Name string
35+
LogURLs map[string]bool // set of members
36+
MinInclusions int // Required number of submissions.
37+
MinDistinctOperators int // Required number of distinct CT log operators that submit an SCT.
38+
IsBase bool // True only for Log-group covering all logs.
39+
LogWeights map[string]float32 // weights used for submission, default weight is 1
40+
wMu sync.RWMutex // guards weights
4141
}
4242

4343
func (group *LogGroupInfo) setMinInclusions(i int) error {

submission/races.go

+33-54
Original file line numberDiff line numberDiff line change
@@ -52,27 +52,24 @@ type groupState struct {
5252
// When some group is complete cancels all requests that are not needed by any
5353
// group.
5454
type safeSubmissionState struct {
55-
mu sync.Mutex
56-
logToGroups map[string]ctpolicy.GroupSet
57-
groupNeeds map[string]int // number of logs that need to be submitted for each group.
58-
minGroups int // minimum number of distinct groups that need a log submitted.
55+
mu sync.Mutex
56+
logToGroups map[string]ctpolicy.GroupSet
57+
remainingSubmissions int // number of logs that still need to be submitted.
58+
minDistinctGroups int // number of groups that need a submission
59+
groupsSubmitted map[string]bool // number of logs submitted to each group.
5960

60-
groups map[string]bool // set of groups that have a log submitted.
6161
results map[string]*submissionResult
6262
cancels map[string]context.CancelFunc
6363
}
6464

6565
func newSafeSubmissionState(groups ctpolicy.LogPolicyData) *safeSubmissionState {
6666
var s safeSubmissionState
6767
s.logToGroups = ctpolicy.GroupByLogs(groups)
68-
s.groupNeeds = make(map[string]int)
69-
for _, g := range groups {
70-
s.groupNeeds[g.Name] = g.MinInclusions
71-
}
7268
if baseGroup, ok := groups[ctpolicy.BaseName]; ok {
73-
s.minGroups = baseGroup.MinOperators
69+
s.remainingSubmissions = baseGroup.MinInclusions
70+
s.minDistinctGroups = baseGroup.MinDistinctOperators
7471
}
75-
s.groups = make(map[string]bool)
72+
s.groupsSubmitted = make(map[string]bool)
7673
s.results = make(map[string]*submissionResult)
7774
s.cancels = make(map[string]context.CancelFunc)
7875
return &s
@@ -88,15 +85,7 @@ func (sub *safeSubmissionState) request(logURL string, cancel context.CancelFunc
8885
return false
8986
}
9087
sub.results[logURL] = &submissionResult{}
91-
isAwaited := false
92-
for g := range sub.logToGroups[logURL] {
93-
if sub.groupNeeds[g] > 0 {
94-
isAwaited = true
95-
break
96-
}
97-
}
98-
if !isAwaited {
99-
// No groups expecting result from this Log.
88+
if sub.remainingSubmissions <= 0 {
10089
return false
10190
}
10291
sub.cancels[logURL] = cancel
@@ -113,69 +102,59 @@ func (sub *safeSubmissionState) setResult(logURL string, sct *ct.SignedCertifica
113102
sub.results[logURL] = &submissionResult{sct: sct, err: err}
114103
return
115104
}
105+
// group name associated with logURL outside of BaseName.
106+
// (this assumes the logURL is associated with only one group ignoring BaseName)
116107
// If at least one group needs that SCT, result is set. Otherwise dumped.
117108
for groupName := range sub.logToGroups[logURL] {
118109
// Ignore the base group (All-logs) here to check separately.
119110
if groupName == ctpolicy.BaseName {
120111
continue
121112
}
122-
if sub.groupNeeds[groupName] > 0 {
113+
// Set the result if the group does not have a submission.
114+
if !sub.groupsSubmitted[groupName] {
123115
sub.results[logURL] = &submissionResult{sct: sct, err: err}
116+
sub.groupsSubmitted[groupName] = true
124117
}
125-
sub.groups[groupName] = true
126-
sub.groupNeeds[groupName]--
127118
}
128119

129120
// Check the base group (All-logs) only
130121
if sub.logToGroups[logURL][ctpolicy.BaseName] {
131122
if sub.results[logURL].sct != nil {
132-
// It is already processed in a non-base group, so we can reduce the groupNeeds for the base group as well.
133-
sub.groupNeeds[ctpolicy.BaseName]--
134-
} else if sub.groupNeeds[ctpolicy.BaseName] > 0 {
135-
minInclusionsForOtherGroup := 0
136-
for g, cnt := range sub.groupNeeds {
137-
if g != ctpolicy.BaseName && cnt > 0 {
138-
minInclusionsForOtherGroup += cnt
139-
}
140-
}
123+
// The cert has been observed in a non-base group, so account for it.
124+
sub.remainingSubmissions--
125+
} else if sub.remainingSubmissions > 0 {
126+
// Arriving at this portion of the code implies that the result contains an SCT from
127+
// the same log operator.
128+
// reservedSubmissions represents the number of submissions that still need to be
129+
// submitted from different log operators.
130+
reservedSubmissions := sub.minDistinctGroups - len(sub.groupsSubmitted)
141131
// Set the result only if the base group still needs SCTs more than total counts
142132
// of minimum inclusions for other groups.
143-
if sub.groupNeeds[ctpolicy.BaseName] > minInclusionsForOtherGroup {
133+
if sub.remainingSubmissions > reservedSubmissions {
144134
sub.results[logURL] = &submissionResult{sct: sct, err: err}
145-
sub.groupNeeds[ctpolicy.BaseName]--
135+
sub.remainingSubmissions--
146136
}
147137
}
148138
}
149139

150140
// Cancel any pending Log-requests for which there're no more awaiting
151141
// Log-groups.
152-
for logURL, groupSet := range sub.logToGroups {
153-
isAwaited := false
154-
for g := range groupSet {
155-
if sub.groupNeeds[g] > 0 {
156-
isAwaited = true
157-
break
158-
}
159-
}
160-
if !isAwaited && sub.cancels[logURL] != nil {
142+
for logURL := range sub.logToGroups {
143+
if sub.remainingSubmissions <= 0 && sub.cancels[logURL] != nil {
161144
sub.cancels[logURL]()
162145
sub.cancels[logURL] = nil
163146
}
164147
}
165148
}
166149

167150
// groupComplete returns true iff the specified group has all the SCTs it needs.
168-
func (sub *safeSubmissionState) groupComplete(groupName string) bool {
151+
func (sub *safeSubmissionState) groupComplete() bool {
169152
sub.mu.Lock()
170153
defer sub.mu.Unlock()
171-
needs, ok := sub.groupNeeds[groupName]
172-
if !ok {
173-
return true
174-
}
175-
if len(sub.groups) < sub.minGroups {
154+
if len(sub.groupsSubmitted) < sub.minDistinctGroups {
176155
return false
177156
}
178-
return needs <= 0
157+
return sub.remainingSubmissions <= 0
179158
}
180159

181160
func (sub *safeSubmissionState) collectSCTs() []*AssignedSCT {
@@ -226,7 +205,7 @@ func groupRace(ctx context.Context, chain []ct.ASN1Cert, asPreChain bool,
226205
return
227206
case <-timeoutchan:
228207
}
229-
if state.groupComplete(group.Name) {
208+
if state.groupComplete() {
230209
cancel()
231210
return
232211
}
@@ -243,14 +222,14 @@ func groupRace(ctx context.Context, chain []ct.ASN1Cert, asPreChain bool,
243222
for range session {
244223
select {
245224
case <-ctx.Done():
246-
return groupState{Name: group.Name, Success: state.groupComplete(group.Name)}
225+
return groupState{Name: group.Name, Success: state.groupComplete()}
247226
case <-counter:
248-
if state.groupComplete(group.Name) {
227+
if state.groupComplete() {
249228
return groupState{Name: group.Name, Success: true}
250229
}
251230
}
252231
}
253-
return groupState{Name: group.Name, Success: state.groupComplete(group.Name)}
232+
return groupState{Name: group.Name, Success: state.groupComplete()}
254233
}
255234

256235
func parallelNums(groups ctpolicy.LogPolicyData) map[string]int {

submission/races_test.go

+17-17
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func TestGetSCTs(t *testing.T) {
9393
name: "singleGroupOneSCT",
9494
sbMock: &mockSubmitter{fixedDelay: map[byte]time.Duration{'a': 0}, firstLetterURLReqNumber: make(map[byte]int)},
9595
groups: ctpolicy.LogPolicyData{
96-
"a": {
96+
ctpolicy.BaseName: {
9797
Name: "a",
9898
LogURLs: map[string]bool{"a1.com": true, "a2.com": true},
9999
MinInclusions: 1,
@@ -107,7 +107,7 @@ func TestGetSCTs(t *testing.T) {
107107
name: "singleGroupMultiSCT",
108108
sbMock: &mockSubmitter{fixedDelay: map[byte]time.Duration{'a': 0}, firstLetterURLReqNumber: make(map[byte]int)},
109109
groups: ctpolicy.LogPolicyData{
110-
"a": {
110+
ctpolicy.BaseName: {
111111
Name: "a",
112112
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true, "a5.com": true},
113113
MinInclusions: 3,
@@ -124,27 +124,27 @@ func TestGetSCTs(t *testing.T) {
124124
"a": {
125125
Name: "a",
126126
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true},
127-
MinInclusions: 1,
127+
MinInclusions: 0,
128128
IsBase: false,
129129
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0},
130130
},
131131
"b": {
132132
Name: "b",
133133
LogURLs: map[string]bool{"b1.com": true, "b2.com": true, "b3.com": true, "b4.com": true},
134-
MinInclusions: 1,
134+
MinInclusions: 0,
135135
IsBase: false,
136136
LogWeights: map[string]float32{"b1.com": 1.0, "b2.com": 1.0, "b3.com": 1.0, "b4.com": 1.0},
137137
},
138138
ctpolicy.BaseName: {
139-
Name: ctpolicy.BaseName,
140-
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true, "b1.com": true, "b2.com": true, "b3.com": true, "b4.com": true},
141-
MinInclusions: 5,
142-
MinOperators: 2,
143-
IsBase: true,
144-
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0, "b1.com": 1.0, "b2.com": 1.0, "b3.com": 1.0, "b4.com": 1.0},
139+
Name: ctpolicy.BaseName,
140+
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true, "b1.com": true, "b2.com": true, "b3.com": true, "b4.com": true},
141+
MinInclusions: 2,
142+
MinDistinctOperators: 2,
143+
IsBase: true,
144+
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0, "b1.com": 1.0, "b2.com": 1.0, "b3.com": 1.0, "b4.com": 1.0},
145145
},
146146
},
147-
resultTrail: map[string]int{"a": 1, "b": 1, ctpolicy.BaseName: 5},
147+
resultTrail: map[string]int{"a": 1, "b": 1, ctpolicy.BaseName: 2},
148148
},
149149
{
150150
name: "notEnoughDistinctOperators",
@@ -158,12 +158,12 @@ func TestGetSCTs(t *testing.T) {
158158
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0},
159159
},
160160
ctpolicy.BaseName: {
161-
Name: ctpolicy.BaseName,
162-
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true},
163-
MinInclusions: 2,
164-
MinOperators: 2,
165-
IsBase: true,
166-
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0},
161+
Name: ctpolicy.BaseName,
162+
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true},
163+
MinInclusions: 2,
164+
MinDistinctOperators: 2,
165+
IsBase: true,
166+
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0},
167167
},
168168
},
169169
errRegexp: regexp.MustCompile("didn't receive enough SCTs"),

0 commit comments

Comments
 (0)