Skip to content

Commit 6003383

Browse files
authored
ra: Add IdentifierTypes to profiles (#8154)
Add `IdentifierTypes` to validation profiles' config, defaulting to DNS if not set. In `NewOrder`, check that the order's profile permits each identifier's type. Fixes #8137 Depends on #8173
1 parent c9e2f98 commit 6003383

File tree

3 files changed

+141
-6
lines changed

3 files changed

+141
-6
lines changed

ra/ra.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,10 +302,12 @@ type ValidationProfileConfig struct {
302302
// specified, the profile is open to all accounts. If the file
303303
// exists but is empty, the profile is closed to all accounts.
304304
AllowList string `validate:"omitempty"`
305+
// IdentifierTypes is a list of identifier types that may be issued under
306+
// this profile. If none are specified, it defaults to "dns".
307+
IdentifierTypes []identifier.IdentifierType `validate:"omitempty,dive,oneof=dns ip"`
305308
}
306309

307-
// validationProfile holds the order and authz lifetimes and allowlist for a
308-
// given validation profile.
310+
// validationProfile holds the attributes of a given validation profile.
309311
type validationProfile struct {
310312
// pendingAuthzLifetime defines how far in the future an authorization's
311313
// "expires" timestamp is set when it is first created, i.e. how much
@@ -327,6 +329,9 @@ type validationProfile struct {
327329
// allowList holds the set of account IDs allowed to use this profile. If
328330
// nil, the profile is open to all accounts (everyone is allowed).
329331
allowList *allowlist.List[int64]
332+
// identifierTypes is a list of identifier types that may be issued under
333+
// this profile. If none are specified, it defaults to "dns".
334+
identifierTypes []identifier.IdentifierType
330335
}
331336

332337
// validationProfiles provides access to the set of configured profiles,
@@ -379,12 +384,22 @@ func NewValidationProfiles(defaultName string, configs map[string]*ValidationPro
379384
}
380385
}
381386

387+
identifierTypes := config.IdentifierTypes
388+
// If this profile has no identifier types configured, default to DNS.
389+
// This default is temporary, to improve deployability.
390+
//
391+
// TODO(#8184): Remove this default and use config.IdentifierTypes below.
392+
if len(identifierTypes) == 0 {
393+
identifierTypes = []identifier.IdentifierType{identifier.TypeDNS}
394+
}
395+
382396
profiles[name] = &validationProfile{
383397
pendingAuthzLifetime: config.PendingAuthzLifetime.Duration,
384398
validAuthzLifetime: config.ValidAuthzLifetime.Duration,
385399
orderLifetime: config.OrderLifetime.Duration,
386400
maxNames: config.MaxNames,
387401
allowList: allowList,
402+
identifierTypes: identifierTypes,
388403
}
389404
}
390405

@@ -2338,7 +2353,14 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
23382353
"Order cannot contain more than %d identifiers", profile.maxNames)
23392354
}
23402355

2341-
// Validate that our policy allows issuing for each of the names in the order
2356+
for _, ident := range idents {
2357+
if !slices.Contains(profile.identifierTypes, ident.Type) {
2358+
return nil, berrors.RejectedIdentifierError("Profile %q does not permit %s type identifiers", req.CertificateProfileName, ident.Type)
2359+
}
2360+
}
2361+
2362+
// Validate that our policy allows issuing for each of the identifiers in
2363+
// the order
23422364
err = ra.PA.WillingToIssue(idents)
23432365
if err != nil {
23442366
return nil, err

ra/ra_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"crypto/x509"
1111
"crypto/x509/pkix"
1212
"encoding/asn1"
13+
"encoding/hex"
1314
"encoding/json"
1415
"encoding/pem"
1516
"errors"
@@ -77,6 +78,27 @@ func randomDomain() string {
7778
return fmt.Sprintf("%x.example.com", bytes[:])
7879
}
7980

81+
// randomIPv6 creates a random IPv6 netip.Addr for testing. It uses a real IPv6
82+
// address range, not a test/documentation range.
83+
//
84+
// panics if crypto/rand.Rand.Read or netip.AddrFromSlice fails.
85+
func randomIPv6() netip.Addr {
86+
var ipBytes [10]byte
87+
_, err := rand.Read(ipBytes[:])
88+
if err != nil {
89+
panic(err)
90+
}
91+
ipPrefix, err := hex.DecodeString("2602080a600f")
92+
if err != nil {
93+
panic(err)
94+
}
95+
ip, ok := netip.AddrFromSlice(bytes.Join([][]byte{ipPrefix, ipBytes[:]}, nil))
96+
if !ok {
97+
panic("Couldn't parse random IP to netip.Addr")
98+
}
99+
return ip
100+
}
101+
80102
func createPendingAuthorization(t *testing.T, sa sapb.StorageAuthorityClient, ident identifier.ACMEIdentifier, exp time.Time) *corepb.Authorization {
81103
t.Helper()
82104

@@ -354,6 +376,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, sapb.StorageAutho
354376
validAuthzLifetime: 300 * 24 * time.Hour,
355377
orderLifetime: 7 * 24 * time.Hour,
356378
maxNames: 100,
379+
identifierTypes: []identifier.IdentifierType{identifier.TypeDNS},
357380
}},
358381
}
359382

@@ -1801,12 +1824,14 @@ func TestNewOrder_ValidationProfiles(t *testing.T) {
18011824
validAuthzLifetime: 1 * 24 * time.Hour,
18021825
orderLifetime: 1 * 24 * time.Hour,
18031826
maxNames: 10,
1827+
identifierTypes: []identifier.IdentifierType{identifier.TypeDNS},
18041828
},
18051829
"two": {
18061830
pendingAuthzLifetime: 2 * 24 * time.Hour,
18071831
validAuthzLifetime: 2 * 24 * time.Hour,
18081832
orderLifetime: 2 * 24 * time.Hour,
18091833
maxNames: 10,
1834+
identifierTypes: []identifier.IdentifierType{identifier.TypeDNS},
18101835
},
18111836
},
18121837
}
@@ -1900,6 +1925,7 @@ func TestNewOrder_ProfileSelectionAllowList(t *testing.T) {
19001925
for _, tc := range testCases {
19011926
t.Run(tc.name, func(t *testing.T) {
19021927
tc.profile.maxNames = 1
1928+
tc.profile.identifierTypes = []identifier.IdentifierType{identifier.TypeDNS}
19031929
ra.profiles.byName = map[string]*validationProfile{
19041930
"test": &tc.profile,
19051931
}
@@ -1921,6 +1947,83 @@ func TestNewOrder_ProfileSelectionAllowList(t *testing.T) {
19211947
}
19221948
}
19231949

1950+
func TestNewOrder_ProfileIdentifierTypes(t *testing.T) {
1951+
_, _, ra, _, _, cleanUp := initAuthorities(t)
1952+
defer cleanUp()
1953+
1954+
testCases := []struct {
1955+
name string
1956+
identTypes []identifier.IdentifierType
1957+
idents []*corepb.Identifier
1958+
expectErr string
1959+
}{
1960+
{
1961+
name: "Permit DNS, provide DNS names",
1962+
identTypes: []identifier.IdentifierType{identifier.TypeDNS},
1963+
idents: []*corepb.Identifier{identifier.NewDNS(randomDomain()).ToProto(), identifier.NewDNS(randomDomain()).ToProto()},
1964+
},
1965+
{
1966+
name: "Permit IP, provide IPs",
1967+
identTypes: []identifier.IdentifierType{identifier.TypeIP},
1968+
idents: []*corepb.Identifier{identifier.NewIP(randomIPv6()).ToProto(), identifier.NewIP(randomIPv6()).ToProto()},
1969+
},
1970+
{
1971+
name: "Permit DNS & IP, provide DNS & IP",
1972+
identTypes: []identifier.IdentifierType{identifier.TypeDNS, identifier.TypeIP},
1973+
idents: []*corepb.Identifier{identifier.NewIP(randomIPv6()).ToProto(), identifier.NewDNS(randomDomain()).ToProto()},
1974+
},
1975+
{
1976+
name: "Permit DNS, provide IP",
1977+
identTypes: []identifier.IdentifierType{identifier.TypeDNS},
1978+
idents: []*corepb.Identifier{identifier.NewIP(randomIPv6()).ToProto()},
1979+
expectErr: "Profile \"test\" does not permit ip type identifiers",
1980+
},
1981+
{
1982+
name: "Permit DNS, provide DNS & IP",
1983+
identTypes: []identifier.IdentifierType{identifier.TypeDNS},
1984+
idents: []*corepb.Identifier{identifier.NewDNS(randomDomain()).ToProto(), identifier.NewIP(randomIPv6()).ToProto()},
1985+
expectErr: "Profile \"test\" does not permit ip type identifiers",
1986+
},
1987+
{
1988+
name: "Permit IP, provide DNS",
1989+
identTypes: []identifier.IdentifierType{identifier.TypeIP},
1990+
idents: []*corepb.Identifier{identifier.NewDNS(randomDomain()).ToProto()},
1991+
expectErr: "Profile \"test\" does not permit dns type identifiers",
1992+
},
1993+
{
1994+
name: "Permit IP, provide DNS & IP",
1995+
identTypes: []identifier.IdentifierType{identifier.TypeIP},
1996+
idents: []*corepb.Identifier{identifier.NewIP(randomIPv6()).ToProto(), identifier.NewDNS(randomDomain()).ToProto()},
1997+
expectErr: "Profile \"test\" does not permit dns type identifiers",
1998+
},
1999+
}
2000+
2001+
for _, tc := range testCases {
2002+
t.Run(tc.name, func(t *testing.T) {
2003+
var profile validationProfile
2004+
profile.maxNames = 2
2005+
profile.identifierTypes = tc.identTypes
2006+
ra.profiles.byName = map[string]*validationProfile{
2007+
"test": &profile,
2008+
}
2009+
2010+
orderReq := &rapb.NewOrderRequest{
2011+
RegistrationID: Registration.Id,
2012+
Identifiers: tc.idents,
2013+
CertificateProfileName: "test",
2014+
}
2015+
_, err := ra.NewOrder(context.Background(), orderReq)
2016+
2017+
if tc.expectErr != "" {
2018+
test.AssertErrorIs(t, err, berrors.RejectedIdentifier)
2019+
test.AssertContains(t, err.Error(), tc.expectErr)
2020+
} else {
2021+
test.AssertNotError(t, err, "NewOrder failed")
2022+
}
2023+
})
2024+
}
2025+
}
2026+
19242027
// mockSAWithAuthzs has a GetAuthorizations2 method that returns the protobuf
19252028
// version of its authzs struct member. It also has a fake GetOrderForNames
19262029
// which always fails, and a fake NewOrderAndAuthzs which always succeeds, to

test/config-next/ra.json

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,29 @@
4141
"pendingAuthzLifetime": "168h",
4242
"validAuthzLifetime": "720h",
4343
"orderLifetime": "168h",
44-
"maxNames": 100
44+
"maxNames": 100,
45+
"identifierTypes": [
46+
"dns"
47+
]
4548
},
4649
"modern": {
4750
"pendingAuthzLifetime": "7h",
4851
"validAuthzLifetime": "7h",
4952
"orderLifetime": "7h",
50-
"maxNames": 10
53+
"maxNames": 10,
54+
"identifierTypes": [
55+
"dns"
56+
]
5157
},
5258
"shortlived": {
5359
"pendingAuthzLifetime": "7h",
5460
"validAuthzLifetime": "7h",
5561
"orderLifetime": "7h",
56-
"maxNames": 10
62+
"maxNames": 10,
63+
"identifierTypes": [
64+
"dns",
65+
"ip"
66+
]
5767
}
5868
},
5969
"defaultProfileName": "legacy",

0 commit comments

Comments
 (0)