Skip to content

Commit 239bf9a

Browse files
Roland Bracewell Shoemakerjsha
authored andcommitted
Very basic feature flag impl (letsencrypt#1705)
Updates letsencrypt#1699. Adds a new package, `features`, which exposes methods to set and check if various internal features are enabled. The implementation uses global state to store the features so that services embedded in another service do not each require their own features map in order to check if something is enabled. Requires a `boulder-tools` image update to include `golang.org/x/tools/cmd/stringer`.
1 parent e1bc1e5 commit 239bf9a

File tree

26 files changed

+194
-34
lines changed

26 files changed

+194
-34
lines changed

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ Note that there are some config fields that we want to be a hard requirement. To
5050

5151
In general, we would like our deploy process to be: deploy new code + old config; then immediately after deploy the same code + new config. This makes deploys cheaper so we can do them more often, and allows us to more readily separate deploy-triggered problems from config-triggered problems.
5252

53+
## Flag-gating features
54+
55+
When adding significant new features or replacing existing RPCs the `boulder/features` package should be used to gate its usage. To add a flag a new `const FeatureFlag` should be added and its default value specified in `features.features`in `features/features.go`. In order to test if the flag is enabled elsewhere in the codebase you can use `features.Enabled(features.ExampleFeatureName)` which returns a `bool` indicating if the flag is enabled or not.
56+
57+
Each service should include a `map[string]features.FeatureFlag` named `Features` in its configuration object at the top level and call `features.Set` with that map immediately after parsing the configuration.
58+
59+
Feature flags are meant to be used temporarily and should not be used for permanent boolean configuration options. Once a feature has been enabled in both staging and production the flag should be removed making the previously gated functionality the default in future deployments.
60+
5361
## Flag-gated RPCs
5462

5563
When you add a new RPC to a Boulder service (e.g. `SA.GetFoo()`), all components that call that RPC should wrap those calls in some flag. Generally this will be a boolean config field `UseFoo`. Since `UseFoo`'s zero value is false, a deploy with the existing config will not call `SA.GetFoo()`. Then, once the deploy is complete and we know that all SA instances support the `GetFoo()` RPC, we do a followup config deploy that sets `UseFoo()` to true.

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# To minimize the fetching of various layers this image and tag should
22
# be used as the base of the bhsm container in boulder/docker-compose.yml
3-
FROM letsencrypt/boulder-tools:2016-09-09
3+
FROM letsencrypt/boulder-tools:2016-09-16
44

55
# Boulder exposes its web application at port TCP 4000
66
EXPOSE 4000 4002 4003 8053 8055

bdns/dns.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import (
1010
"time"
1111

1212
"github.com/jmhodges/clock"
13-
"github.com/letsencrypt/boulder/metrics"
1413
"github.com/miekg/dns"
1514
"golang.org/x/net/context"
15+
16+
"github.com/letsencrypt/boulder/metrics"
1617
)
1718

1819
func parseCidr(network string, comment string) net.IPNet {

ca/ca.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,14 @@ func (ca *CertificateAuthorityImpl) GenerateOCSP(ctx context.Context, xferObj co
374374
func (ca *CertificateAuthorityImpl) IssueCertificate(ctx context.Context, csr x509.CertificateRequest, regID int64) (core.Certificate, error) {
375375
emptyCert := core.Certificate{}
376376

377-
if err := csrlib.VerifyCSR(&csr, ca.maxNames, &ca.keyPolicy, ca.PA, ca.forceCNFromSAN, regID); err != nil {
377+
if err := csrlib.VerifyCSR(
378+
&csr,
379+
ca.maxNames,
380+
&ca.keyPolicy,
381+
ca.PA,
382+
ca.forceCNFromSAN,
383+
regID,
384+
); err != nil {
378385
ca.log.AuditErr(err.Error())
379386
return emptyCert, core.MalformedRequestError(err.Error())
380387
}

ca/ca_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,6 @@ func setup(t *testing.T) *testCtx {
182182
Expiry: "8760h",
183183
LifespanOCSP: cmd.ConfigDuration{Duration: 45 * time.Minute},
184184
MaxNames: 2,
185-
DoNotForceCN: true,
186185
CFSSL: cfsslConfig.Config{
187186
Signing: &cfsslConfig.Signing{
188187
Profiles: map[string]*cfsslConfig.SigningProfile{
@@ -284,6 +283,7 @@ func TestIssueCertificate(t *testing.T) {
284283
testCtx.keyPolicy,
285284
testCtx.logger)
286285
test.AssertNotError(t, err, "Failed to create CA")
286+
ca.forceCNFromSAN = false
287287
ca.Publisher = &mocks.Publisher{}
288288
ca.PA = testCtx.pa
289289
sa := &mockSA{}
@@ -557,6 +557,7 @@ func TestAllowNoCN(t *testing.T) {
557557
testCtx.keyPolicy,
558558
testCtx.logger)
559559
test.AssertNotError(t, err, "Couldn't create new CA")
560+
ca.forceCNFromSAN = false
560561
ca.Publisher = &mocks.Publisher{}
561562
ca.PA = testCtx.pa
562563
ca.SA = &mockSA{}
@@ -721,13 +722,13 @@ func TestExtensions(t *testing.T) {
721722
return cert
722723
}
723724

724-
// With enableMustStaple = false, should issue successfully and not add
725+
// With ca.enableMustStaple = false, should issue successfully and not add
725726
// Must Staple.
726727
stats.EXPECT().Inc(metricCSRExtensionTLSFeature, int64(1)).Return(nil)
727728
noStapleCert := sign(mustStapleCSR)
728729
test.AssertEquals(t, countMustStaple(t, noStapleCert), 0)
729730

730-
// With enableMustStaple = true, a TLS feature extension should put a must-staple
731+
// With ca.enableMustStaple = true, a TLS feature extension should put a must-staple
731732
// extension into the cert
732733
ca.enableMustStaple = true
733734
stats.EXPECT().Inc(metricCSRExtensionTLSFeature, int64(1)).Return(nil)

cmd/admin-revoker/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func main() {
147147
}
148148

149149
var c config
150-
err = cmd.ReadJSONFile(*configFile, &c)
150+
err = cmd.ReadConfigFile(*configFile, &c)
151151
cmd.FailOnError(err, "Reading JSON config file into config structure")
152152

153153
ctx := context.Background()

cmd/boulder-ca/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ func main() {
127127
}
128128

129129
var c config
130-
err := cmd.ReadJSONFile(*configFile, &c)
130+
err := cmd.ReadConfigFile(*configFile, &c)
131131
cmd.FailOnError(err, "Reading JSON config file into config structure")
132132

133133
go cmd.DebugServer(c.CA.DebugAddr)

cmd/boulder-publisher/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func main() {
4545
}
4646

4747
var c config
48-
err := cmd.ReadJSONFile(*configFile, &c)
48+
err := cmd.ReadConfigFile(*configFile, &c)
4949
cmd.FailOnError(err, "Reading JSON config file into config structure")
5050

5151
go cmd.DebugServer(c.Publisher.DebugAddr)

cmd/boulder-ra/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func main() {
8686
}
8787

8888
var c config
89-
err := cmd.ReadJSONFile(*configFile, &c)
89+
err := cmd.ReadConfigFile(*configFile, &c)
9090
cmd.FailOnError(err, "Reading JSON config file into config structure")
9191

9292
go cmd.DebugServer(c.RA.DebugAddr)

cmd/boulder-sa/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func main() {
3636
}
3737

3838
var c config
39-
err := cmd.ReadJSONFile(*configFile, &c)
39+
err := cmd.ReadConfigFile(*configFile, &c)
4040
cmd.FailOnError(err, "Reading JSON config file into config structure")
4141

4242
go cmd.DebugServer(c.SA.DebugAddr)

0 commit comments

Comments
 (0)