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:
Aaron Gable 2024-07-22 15:31:08 -07:00 committed by GitHub
parent 848a9ea696
commit 48439e4532
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 27 additions and 25 deletions

View File

@ -155,11 +155,11 @@ type Config struct {
// the CA and RA configurations.
MaxNames int `validate:"min=0,max=100"`
// CertificateProfileNames is the list of acceptable certificate profile
// names for newOrder requests. Requests with a profile name not in this
// list will be rejected. This field is optional; if unset, no profile
// names are accepted.
CertificateProfileNames []string `validate:"omitempty,dive,alphanum,min=1,max=32"`
// CertProfiles is a map of acceptable certificate profile names to
// descriptions (perhaps including URLs) of those profiles. NewOrder
// Requests with a profile name not present in this map will be rejected.
// This field is optional; if unset, no profile names are accepted.
CertProfiles map[string]string `validate:"omitempty,dive,keys,alphanum,min=1,max=32,endkeys"`
}
Syslog cmd.SyslogConfig
@ -355,7 +355,7 @@ func main() {
limiter,
txnBuilder,
maxNames,
c.WFE.CertificateProfileNames,
c.WFE.CertProfiles,
)
cmd.FailOnError(err, "Unable to create WFE")

View File

@ -131,9 +131,9 @@
"TrackReplacementCertificatesARI": true,
"CheckRenewalExemptionAtWFE": true
},
"certificateProfileNames": [
"defaultBoulderCertificateProfile"
]
"certProfiles": {
"defaultBoulderCertificateProfile": "The normal profile you know and love"
}
},
"syslog": {
"stdoutlevel": 4,

View File

@ -21,6 +21,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/trace"
"golang.org/x/exp/maps"
"google.golang.org/protobuf/types/known/emptypb"
"github.com/letsencrypt/boulder/core"
@ -29,18 +30,16 @@ import (
"github.com/letsencrypt/boulder/features"
"github.com/letsencrypt/boulder/goodkey"
bgrpc "github.com/letsencrypt/boulder/grpc"
"github.com/letsencrypt/boulder/policy"
"github.com/letsencrypt/boulder/ratelimits"
// 'grpc/noncebalancer' is imported for its init function.
_ "github.com/letsencrypt/boulder/grpc/noncebalancer"
_ "github.com/letsencrypt/boulder/grpc/noncebalancer" // imported for its init function.
"github.com/letsencrypt/boulder/identifier"
"github.com/letsencrypt/boulder/issuance"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/metrics/measured_http"
"github.com/letsencrypt/boulder/nonce"
"github.com/letsencrypt/boulder/policy"
"github.com/letsencrypt/boulder/probs"
rapb "github.com/letsencrypt/boulder/ra/proto"
"github.com/letsencrypt/boulder/ratelimits"
"github.com/letsencrypt/boulder/revocation"
sapb "github.com/letsencrypt/boulder/sa/proto"
"github.com/letsencrypt/boulder/web"
@ -165,10 +164,10 @@ type WebFrontEndImpl struct {
txnBuilder *ratelimits.TransactionBuilder
maxNames int
// certificateProfileNames is a list of profile names that are allowed to be
// passed to the newOrder endpoint. If a profile name is not in this list,
// the request will be rejected as malformed.
certificateProfileNames []string
// certProfiles is a map of acceptable certificate profile names to
// descriptions (perhaps including URLs) of those profiles. NewOrder
// Requests with a profile name not present in this map will be rejected.
certProfiles map[string]string
}
// NewWebFrontEndImpl constructs a web service for Boulder
@ -192,7 +191,7 @@ func NewWebFrontEndImpl(
limiter *ratelimits.Limiter,
txnBuilder *ratelimits.TransactionBuilder,
maxNames int,
certificateProfileNames []string,
certProfiles map[string]string,
) (WebFrontEndImpl, error) {
if len(issuerCertificates) == 0 {
return WebFrontEndImpl{}, errors.New("must provide at least one issuer certificate")
@ -230,7 +229,7 @@ func NewWebFrontEndImpl(
limiter: limiter,
txnBuilder: txnBuilder,
maxNames: maxNames,
certificateProfileNames: certificateProfileNames,
certProfiles: certProfiles,
}
return wfe, nil
@ -550,6 +549,9 @@ func (wfe *WebFrontEndImpl) Directory(
wfe.DirectoryCAAIdentity,
}
}
if len(wfe.certProfiles) != 0 {
metaMap["profiles"] = wfe.certProfiles
}
// The "meta" directory entry may also include a string with a website URL
if wfe.DirectoryWebsite != "" {
metaMap["website"] = wfe.DirectoryWebsite
@ -2190,7 +2192,7 @@ func (wfe *WebFrontEndImpl) validateCertificateProfileName(profile string) error
// No profile name is specified.
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.
return errors.New("not a recognized profile name")
}

View File

@ -407,7 +407,7 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) {
limiter,
txnBuilder,
100,
[]string{""},
nil,
)
test.AssertNotError(t, err, "Unable to create WFE")
@ -3816,7 +3816,7 @@ func TestNewOrderWithProfile(t *testing.T) {
expectProfileName := "test-profile"
wfe.ra = &mockRA{expectProfileName: expectProfileName}
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
// profile is specified.
@ -3855,8 +3855,8 @@ func TestNewOrderWithProfile(t *testing.T) {
test.AssertNotError(t, err, "Failed to unmarshal order response")
test.AssertEquals(t, errorResp1["status"], "valid")
// Set the acceptable profiles to an empty list, the WFE should no longer accept any profiles.
wfe.certificateProfileNames = []string{}
// Set the acceptable profiles to the empty set, the WFE should no longer accept any profiles.
wfe.certProfiles = map[string]string{}
responseWriter = httptest.NewRecorder()
r = signAndPost(signer, newOrderPath, "http://localhost"+newOrderPath, validOrderBody)
mux.ServeHTTP(responseWriter, r)