Skip to content

Commit

Permalink
RA: Clean up deprecated validation configuration (#7992)
Browse files Browse the repository at this point in the history
Remove the RA's deprecated top-level config keys which used to control
order and authz lifetimes. Make the new profile-based config keys which
replaced them required.

Since configuring a profile and default profile name is now mandatory,
always supply a profile name to the CA when requesting issuance.

Fixes #7986
  • Loading branch information
aarongable authored Feb 11, 2025
1 parent a9e3ad1 commit 3e4bc16
Show file tree
Hide file tree
Showing 3 changed files with 8 additions and 122 deletions.
50 changes: 3 additions & 47 deletions cmd/boulder-ra/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"flag"
"os"
"time"

akamaipb "github.com/letsencrypt/boulder/akamai/proto"
"github.com/letsencrypt/boulder/allowlist"
Expand Down Expand Up @@ -81,37 +80,18 @@ type Config struct {
// configurations.
MaxNames int `validate:"required,min=1,max=100"`

// AuthorizationLifetimeDays defines how long authorizations will be
// considered valid for. Given a value of 300 days when used with a 90-day
// cert lifetime, this allows creation of certs that will cover a whole
// year, plus a grace period of a month.
//
// Deprecated: use ValidationProfiles.[profile].ValidAuthzLifetime instead.
// TODO(#7986): Remove this.
AuthorizationLifetimeDays int `validate:"omitempty,required_without=ValidationProfiles,min=1,max=397"`

// PendingAuthorizationLifetimeDays defines how long authorizations may be in
// the pending state. If you can't respond to a challenge this quickly, then
// you need to request a new challenge.
//
// Deprecated: use ValidationProfiles.[profile].PendingAuthzLifetime instead.
// TODO(#7986): Remove this.
PendingAuthorizationLifetimeDays int `validate:"omitempty,required_without=ValidationProfiles,min=1,max=29"`

// ValidationProfiles is a map of validation profiles to their
// respective issuance allow lists. If a profile is not included in this
// mapping, it cannot be used by any account. If this field is left
// empty, all profiles are open to all accounts.
// TODO(#7986): Make this field required.
ValidationProfiles map[string]ra.ValidationProfileConfig `validate:"omitempty"`
ValidationProfiles map[string]ra.ValidationProfileConfig `validate:"required"`

// DefaultProfileName sets the profile to use if one wasn't provided by the
// client in the new-order request. Must match a configured validation
// profile or the RA will fail to start. Must match a certificate profile
// configured in the CA or finalization will fail for orders using this
// default.
// TODO(#7986): Make this field unconditionally required.
DefaultProfileName string `validate:"required_with=ValidationProfiles"`
DefaultProfileName string `validate:"required"`

// MustStapleAllowList specifies the path to a YAML file containing a
// list of account IDs permitted to request certificates with the OCSP
Expand All @@ -123,13 +103,6 @@ type Config struct {
// GoodKey is an embedded config stanza for the goodkey library.
GoodKey goodkey.Config

// OrderLifetime is how far in the future an Order's expiration date should
// be set when it is first created.
//
// Deprecated: Use ValidationProfiles.[profile].OrderLifetime instead.
// TODO(#7986): Remove this.
OrderLifetime config.Duration `validate:"omitempty,required_without=ValidationProfiles"`

// FinalizeTimeout is how long the RA is willing to wait for the Order
// finalization process to take. This config parameter only has an effect
// if the AsyncFinalization feature flag is enabled. Any systems which
Expand Down Expand Up @@ -266,25 +239,8 @@ func main() {

ctp = ctpolicy.New(pubc, sctLogs, infoLogs, finalLogs, c.RA.CTLogs.Stagger.Duration, logger, scope)

// TODO(#7986): Remove this fallback, error out if no default is configured.
if c.RA.DefaultProfileName == "" {
c.RA.DefaultProfileName = ra.UnconfiguredDefaultProfileName
}
logger.Infof("Configured default profile name set to: %s", c.RA.DefaultProfileName)

// TODO(#7986): Remove this fallback, error out if no profiles are configured.
if len(c.RA.ValidationProfiles) == 0 {
c.RA.ValidationProfiles = map[string]ra.ValidationProfileConfig{
c.RA.DefaultProfileName: {
PendingAuthzLifetime: config.Duration{
Duration: time.Duration(c.RA.PendingAuthorizationLifetimeDays) * 24 * time.Hour},
ValidAuthzLifetime: config.Duration{
Duration: time.Duration(c.RA.AuthorizationLifetimeDays) * 24 * time.Hour},
OrderLifetime: c.RA.OrderLifetime,
// Leave the allowlist empty, so all accounts have access to this
// default profile.
},
}
cmd.Fail("At least one profile must be configured")
}

validationProfiles, err := ra.NewValidationProfiles(c.RA.DefaultProfileName, c.RA.ValidationProfiles)
Expand Down
19 changes: 5 additions & 14 deletions ra/ra.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,6 @@ func NewRegistrationAuthorityImpl(
return ra
}

// UnconfiguredDefaultProfileName is a unique string which the RA can use to
// identify a profile, but also detect that no profiles were explicitly
// configured, and therefore should not be assumed to exist outside the RA.
// TODO(#7986): Remove this when the defaultProfileName config is required.
const UnconfiguredDefaultProfileName = "unconfiguredDefaultProfileName"

// ValidationProfileConfig is a config struct which can be used to create a
// ValidationProfile.
type ValidationProfileConfig struct {
Expand Down Expand Up @@ -340,6 +334,10 @@ type validationProfiles struct {
// configs and default name. It enforces that the given authorization lifetimes
// are within the bounds mandated by the Baseline Requirements.
func NewValidationProfiles(defaultName string, configs map[string]ValidationProfileConfig) (*validationProfiles, error) {
if defaultName == "" {
return nil, errors.New("default profile name must be configured")
}

profiles := make(map[string]*validationProfile, len(configs))

for name, config := range configs {
Expand Down Expand Up @@ -391,9 +389,6 @@ func NewValidationProfiles(defaultName string, configs map[string]ValidationProf
}

func (vp *validationProfiles) get(name string) (*validationProfile, error) {
if vp.defaultName == UnconfiguredDefaultProfileName {
return vp.byName[vp.defaultName], nil
}
if name == "" {
name = vp.defaultName
}
Expand Down Expand Up @@ -1191,12 +1186,8 @@ func (ra *RegistrationAuthorityImpl) issueCertificateOuter(
logEvent.PreviousCertificateIssued = timestamps.Timestamps[0].AsTime()
}

// If the order didn't request a specific profile and we have a default
// configured, provide it to the CA so we can stop relying on the CA's
// configured default.
// TODO(#7309): Make this unconditional.
profileName := order.CertificateProfileName
if profileName == "" && ra.profiles.defaultName != UnconfiguredDefaultProfileName {
if profileName == "" {
profileName = ra.profiles.defaultName
}

Expand Down
61 changes: 0 additions & 61 deletions ra/ra_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1763,67 +1763,6 @@ func TestNewOrder_ValidationProfiles(t *testing.T) {
}
}

func TestNewOrder_UnconfiguredValidationProfiles(t *testing.T) {
_, _, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()

ra.profiles = &validationProfiles{
defaultName: UnconfiguredDefaultProfileName,
byName: map[string]*validationProfile{
UnconfiguredDefaultProfileName: {
pendingAuthzLifetime: 1 * 24 * time.Hour,
validAuthzLifetime: 1 * 24 * time.Hour,
orderLifetime: 1 * 24 * time.Hour,
},
},
}

for _, tc := range []struct {
name string
profile string
wantExpires time.Time
}{
{
// A request with no profile should get an order and authzs with one-day lifetimes.
name: "no profile specified",
profile: "",
wantExpires: ra.clk.Now().Add(1 * 24 * time.Hour),
},
{
// A request for a specific profile should get the same lifetimes.
name: "profile specified",
profile: "test",
wantExpires: ra.clk.Now().Add(1 * 24 * time.Hour),
},
} {
t.Run(tc.name, func(t *testing.T) {
order, err := ra.NewOrder(context.Background(), &rapb.NewOrderRequest{
RegistrationID: Registration.Id,
DnsNames: []string{randomDomain()},
CertificateProfileName: tc.profile,
})
if err != nil {
t.Fatalf("creating order: %s", err)
}
gotExpires := order.Expires.AsTime()
if gotExpires != tc.wantExpires {
t.Errorf("NewOrder(profile: %q).Expires = %s, expected %s", tc.profile, gotExpires, tc.wantExpires)
}

authz, err := ra.GetAuthorization(context.Background(), &rapb.GetAuthorizationRequest{
Id: order.V2Authorizations[0],
})
if err != nil {
t.Fatalf("fetching test authz: %s", err)
}
gotExpires = authz.Expires.AsTime()
if gotExpires != tc.wantExpires {
t.Errorf("GetAuthorization(profile: %q).Expires = %s, expected %s", tc.profile, gotExpires, tc.wantExpires)
}
})
}
}

func TestNewOrder_ProfileSelectionAllowList(t *testing.T) {
_, _, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()
Expand Down

0 comments on commit 3e4bc16

Please sign in to comment.