Advertise available profiles in directory resource (#7603)
Change the way profiles are configured at the WFE to allow them to be accompanied by descriptive strings. Augment the construction of the directory resource's "meta" sub-object to include these profile names and descriptions. This config swap is safe, since no Boulder WFE instance is configured with `CertificateProfileNames` yet. Fixes https://github.com/letsencrypt/boulder/issues/7602
This commit is contained in:
parent
848a9ea696
commit
48439e4532
|
|
@ -155,11 +155,11 @@ type Config struct {
|
||||||
// the CA and RA configurations.
|
// the CA and RA configurations.
|
||||||
MaxNames int `validate:"min=0,max=100"`
|
MaxNames int `validate:"min=0,max=100"`
|
||||||
|
|
||||||
// CertificateProfileNames is the list of acceptable certificate profile
|
// CertProfiles is a map of acceptable certificate profile names to
|
||||||
// names for newOrder requests. Requests with a profile name not in this
|
// descriptions (perhaps including URLs) of those profiles. NewOrder
|
||||||
// list will be rejected. This field is optional; if unset, no profile
|
// Requests with a profile name not present in this map will be rejected.
|
||||||
// names are accepted.
|
// This field is optional; if unset, no profile names are accepted.
|
||||||
CertificateProfileNames []string `validate:"omitempty,dive,alphanum,min=1,max=32"`
|
CertProfiles map[string]string `validate:"omitempty,dive,keys,alphanum,min=1,max=32,endkeys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
Syslog cmd.SyslogConfig
|
Syslog cmd.SyslogConfig
|
||||||
|
|
@ -355,7 +355,7 @@ func main() {
|
||||||
limiter,
|
limiter,
|
||||||
txnBuilder,
|
txnBuilder,
|
||||||
maxNames,
|
maxNames,
|
||||||
c.WFE.CertificateProfileNames,
|
c.WFE.CertProfiles,
|
||||||
)
|
)
|
||||||
cmd.FailOnError(err, "Unable to create WFE")
|
cmd.FailOnError(err, "Unable to create WFE")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,9 +131,9 @@
|
||||||
"TrackReplacementCertificatesARI": true,
|
"TrackReplacementCertificatesARI": true,
|
||||||
"CheckRenewalExemptionAtWFE": true
|
"CheckRenewalExemptionAtWFE": true
|
||||||
},
|
},
|
||||||
"certificateProfileNames": [
|
"certProfiles": {
|
||||||
"defaultBoulderCertificateProfile"
|
"defaultBoulderCertificateProfile": "The normal profile you know and love"
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"syslog": {
|
"syslog": {
|
||||||
"stdoutlevel": 4,
|
"stdoutlevel": 4,
|
||||||
|
|
|
||||||
26
wfe2/wfe.go
26
wfe2/wfe.go
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
|
||||||
"github.com/letsencrypt/boulder/core"
|
"github.com/letsencrypt/boulder/core"
|
||||||
|
|
@ -29,18 +30,16 @@ import (
|
||||||
"github.com/letsencrypt/boulder/features"
|
"github.com/letsencrypt/boulder/features"
|
||||||
"github.com/letsencrypt/boulder/goodkey"
|
"github.com/letsencrypt/boulder/goodkey"
|
||||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||||
"github.com/letsencrypt/boulder/policy"
|
_ "github.com/letsencrypt/boulder/grpc/noncebalancer" // imported for its init function.
|
||||||
"github.com/letsencrypt/boulder/ratelimits"
|
|
||||||
|
|
||||||
// 'grpc/noncebalancer' is imported for its init function.
|
|
||||||
_ "github.com/letsencrypt/boulder/grpc/noncebalancer"
|
|
||||||
"github.com/letsencrypt/boulder/identifier"
|
"github.com/letsencrypt/boulder/identifier"
|
||||||
"github.com/letsencrypt/boulder/issuance"
|
"github.com/letsencrypt/boulder/issuance"
|
||||||
blog "github.com/letsencrypt/boulder/log"
|
blog "github.com/letsencrypt/boulder/log"
|
||||||
"github.com/letsencrypt/boulder/metrics/measured_http"
|
"github.com/letsencrypt/boulder/metrics/measured_http"
|
||||||
"github.com/letsencrypt/boulder/nonce"
|
"github.com/letsencrypt/boulder/nonce"
|
||||||
|
"github.com/letsencrypt/boulder/policy"
|
||||||
"github.com/letsencrypt/boulder/probs"
|
"github.com/letsencrypt/boulder/probs"
|
||||||
rapb "github.com/letsencrypt/boulder/ra/proto"
|
rapb "github.com/letsencrypt/boulder/ra/proto"
|
||||||
|
"github.com/letsencrypt/boulder/ratelimits"
|
||||||
"github.com/letsencrypt/boulder/revocation"
|
"github.com/letsencrypt/boulder/revocation"
|
||||||
sapb "github.com/letsencrypt/boulder/sa/proto"
|
sapb "github.com/letsencrypt/boulder/sa/proto"
|
||||||
"github.com/letsencrypt/boulder/web"
|
"github.com/letsencrypt/boulder/web"
|
||||||
|
|
@ -165,10 +164,10 @@ type WebFrontEndImpl struct {
|
||||||
txnBuilder *ratelimits.TransactionBuilder
|
txnBuilder *ratelimits.TransactionBuilder
|
||||||
maxNames int
|
maxNames int
|
||||||
|
|
||||||
// certificateProfileNames is a list of profile names that are allowed to be
|
// certProfiles is a map of acceptable certificate profile names to
|
||||||
// passed to the newOrder endpoint. If a profile name is not in this list,
|
// descriptions (perhaps including URLs) of those profiles. NewOrder
|
||||||
// the request will be rejected as malformed.
|
// Requests with a profile name not present in this map will be rejected.
|
||||||
certificateProfileNames []string
|
certProfiles map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWebFrontEndImpl constructs a web service for Boulder
|
// NewWebFrontEndImpl constructs a web service for Boulder
|
||||||
|
|
@ -192,7 +191,7 @@ func NewWebFrontEndImpl(
|
||||||
limiter *ratelimits.Limiter,
|
limiter *ratelimits.Limiter,
|
||||||
txnBuilder *ratelimits.TransactionBuilder,
|
txnBuilder *ratelimits.TransactionBuilder,
|
||||||
maxNames int,
|
maxNames int,
|
||||||
certificateProfileNames []string,
|
certProfiles map[string]string,
|
||||||
) (WebFrontEndImpl, error) {
|
) (WebFrontEndImpl, error) {
|
||||||
if len(issuerCertificates) == 0 {
|
if len(issuerCertificates) == 0 {
|
||||||
return WebFrontEndImpl{}, errors.New("must provide at least one issuer certificate")
|
return WebFrontEndImpl{}, errors.New("must provide at least one issuer certificate")
|
||||||
|
|
@ -230,7 +229,7 @@ func NewWebFrontEndImpl(
|
||||||
limiter: limiter,
|
limiter: limiter,
|
||||||
txnBuilder: txnBuilder,
|
txnBuilder: txnBuilder,
|
||||||
maxNames: maxNames,
|
maxNames: maxNames,
|
||||||
certificateProfileNames: certificateProfileNames,
|
certProfiles: certProfiles,
|
||||||
}
|
}
|
||||||
|
|
||||||
return wfe, nil
|
return wfe, nil
|
||||||
|
|
@ -550,6 +549,9 @@ func (wfe *WebFrontEndImpl) Directory(
|
||||||
wfe.DirectoryCAAIdentity,
|
wfe.DirectoryCAAIdentity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(wfe.certProfiles) != 0 {
|
||||||
|
metaMap["profiles"] = wfe.certProfiles
|
||||||
|
}
|
||||||
// The "meta" directory entry may also include a string with a website URL
|
// The "meta" directory entry may also include a string with a website URL
|
||||||
if wfe.DirectoryWebsite != "" {
|
if wfe.DirectoryWebsite != "" {
|
||||||
metaMap["website"] = wfe.DirectoryWebsite
|
metaMap["website"] = wfe.DirectoryWebsite
|
||||||
|
|
@ -2190,7 +2192,7 @@ func (wfe *WebFrontEndImpl) validateCertificateProfileName(profile string) error
|
||||||
// No profile name is specified.
|
// No profile name is specified.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !slices.Contains(wfe.certificateProfileNames, profile) {
|
if !slices.Contains(maps.Keys(wfe.certProfiles), profile) {
|
||||||
// The profile name is not in the list of configured profiles.
|
// The profile name is not in the list of configured profiles.
|
||||||
return errors.New("not a recognized profile name")
|
return errors.New("not a recognized profile name")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -407,7 +407,7 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) {
|
||||||
limiter,
|
limiter,
|
||||||
txnBuilder,
|
txnBuilder,
|
||||||
100,
|
100,
|
||||||
[]string{""},
|
nil,
|
||||||
)
|
)
|
||||||
test.AssertNotError(t, err, "Unable to create WFE")
|
test.AssertNotError(t, err, "Unable to create WFE")
|
||||||
|
|
||||||
|
|
@ -3816,7 +3816,7 @@ func TestNewOrderWithProfile(t *testing.T) {
|
||||||
expectProfileName := "test-profile"
|
expectProfileName := "test-profile"
|
||||||
wfe.ra = &mockRA{expectProfileName: expectProfileName}
|
wfe.ra = &mockRA{expectProfileName: expectProfileName}
|
||||||
mux := wfe.Handler(metrics.NoopRegisterer)
|
mux := wfe.Handler(metrics.NoopRegisterer)
|
||||||
wfe.certificateProfileNames = []string{expectProfileName}
|
wfe.certProfiles = map[string]string{expectProfileName: "description"}
|
||||||
|
|
||||||
// Test that the newOrder endpoint returns the proper error if an invalid
|
// Test that the newOrder endpoint returns the proper error if an invalid
|
||||||
// profile is specified.
|
// profile is specified.
|
||||||
|
|
@ -3855,8 +3855,8 @@ func TestNewOrderWithProfile(t *testing.T) {
|
||||||
test.AssertNotError(t, err, "Failed to unmarshal order response")
|
test.AssertNotError(t, err, "Failed to unmarshal order response")
|
||||||
test.AssertEquals(t, errorResp1["status"], "valid")
|
test.AssertEquals(t, errorResp1["status"], "valid")
|
||||||
|
|
||||||
// Set the acceptable profiles to an empty list, the WFE should no longer accept any profiles.
|
// Set the acceptable profiles to the empty set, the WFE should no longer accept any profiles.
|
||||||
wfe.certificateProfileNames = []string{}
|
wfe.certProfiles = map[string]string{}
|
||||||
responseWriter = httptest.NewRecorder()
|
responseWriter = httptest.NewRecorder()
|
||||||
r = signAndPost(signer, newOrderPath, "http://localhost"+newOrderPath, validOrderBody)
|
r = signAndPost(signer, newOrderPath, "http://localhost"+newOrderPath, validOrderBody)
|
||||||
mux.ServeHTTP(responseWriter, r)
|
mux.ServeHTTP(responseWriter, r)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue