Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RA: Allow profile selection to be gated on account-based allow lists #7959

Merged
merged 12 commits into from
Jan 24, 2025
Merged
8 changes: 8 additions & 0 deletions allowlist/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
)

func TestNewFromYAML(t *testing.T) {
t.Parallel()

tests := []struct {
name string
yamlData string
Expand Down Expand Up @@ -37,6 +39,8 @@ func TestNewFromYAML(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

list, err := NewFromYAML[string]([]byte(tt.yamlData))
if (err != nil) != tt.expectErr {
t.Fatalf("NewFromYAML() error = %v, expectErr = %v", err, tt.expectErr)
Expand All @@ -55,6 +59,8 @@ func TestNewFromYAML(t *testing.T) {
}

func TestNewList(t *testing.T) {
t.Parallel()

tests := []struct {
name string
members []string
Expand Down Expand Up @@ -89,6 +95,8 @@ func TestNewList(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

list := NewList[string](tt.members)
beautifulentropy marked this conversation as resolved.
Show resolved Hide resolved
for i, item := range tt.check {
got := list.Contains(item)
Expand Down
11 changes: 4 additions & 7 deletions cmd/boulder-ra/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,17 +270,14 @@ func main() {
if c.RA.ValidationProfiles != nil {
validationProfiles = make(map[string]*ra.ValidationProfile)
for profileName, v := range c.RA.ValidationProfiles {
if v.AllowList == "" {
// No allow list file is specified, this profile is open to all accounts.
validationProfiles[profileName] = ra.NewValidationProfile(nil)
} else {
var allowList *allowlist.List[int64]
if v.AllowList != "" {
data, err := os.ReadFile(v.AllowList)
cmd.FailOnError(err, fmt.Sprintf("Failed to read allow list for profile %q", profileName))
allowList, err := allowlist.NewFromYAML[int64](data)
allowList, err = allowlist.NewFromYAML[int64](data)
cmd.FailOnError(err, fmt.Sprintf("Failed to parse allow list for profile %q", profileName))
// Use of this profile is restricted to the accounts listed in the allow list.
validationProfiles[profileName] = ra.NewValidationProfile(allowList)
}
validationProfiles[profileName] = ra.NewValidationProfile(allowList)
}
}

Expand Down
2 changes: 1 addition & 1 deletion ra/ra.go
Original file line number Diff line number Diff line change
Expand Up @@ -2146,7 +2146,7 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
req.CertificateProfileName,
)
}
if ok && vp.allowList != nil && !vp.allowList.Contains(req.RegistrationID) {
if vp.allowList != nil && !vp.allowList.Contains(req.RegistrationID) {
return nil, berrors.UnauthorizedError("account ID %d is not permitted to use certificate profile %q",
req.RegistrationID,
req.CertificateProfileName,
Expand Down
91 changes: 50 additions & 41 deletions ra/ra_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1669,56 +1669,65 @@ func TestNewOrder_AuthzReuse_NoPending(t *testing.T) {
}

func TestNewOrder_ProfileSelectionAllowList(t *testing.T) {
beautifulentropy marked this conversation as resolved.
Show resolved Hide resolved
t.Parallel()

_, _, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()

// Set up a ValidationProfile with an allowList that is nil, indicating all
// accounts are allowed.
ra.validationProfiles = map[string]*ValidationProfile{
"test": NewValidationProfile(nil),
}

// Issuance should succeed.
orderReq := &rapb.NewOrderRequest{
RegistrationID: Registration.Id,
DnsNames: []string{"a.example.com"},
CertificateProfileName: "test",
testCases := []struct {
name string
allowList *allowlist.List[int64]
expectErr bool
expectErrContains string
}{
{
name: "Allow All Account IDs",
beautifulentropy marked this conversation as resolved.
Show resolved Hide resolved
allowList: nil,
expectErr: false,
},
{
name: "Deny All But Account ID 1337",
allowList: allowlist.NewList([]int64{1337}),
expectErr: true,
expectErrContains: "not permitted to use certificate profile",
},
{
name: "Deny All",
allowList: allowlist.NewList([]int64{}),
expectErr: true,
expectErrContains: "not permitted to use certificate profile",
},
{
name: "Allow Registration ID",
allowList: allowlist.NewList([]int64{Registration.Id}),
expectErr: false,
},
}
_, err := ra.NewOrder(context.Background(), orderReq)
test.AssertNotError(t, err, "NewOrder for account ID that is on the allowlist failed")

// Set up a ValidationProfile with an allowList that doesn't include
// Registration.Id.
ra.validationProfiles = map[string]*ValidationProfile{
"test": NewValidationProfile(allowlist.NewList([]int64{1337})),
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

// Issuance should fail with an unauthorized error regarding the certificate
// profile.
orderReq = &rapb.NewOrderRequest{
RegistrationID: Registration.Id,
DnsNames: []string{"b.example.com"},
CertificateProfileName: "test",
}
_, err = ra.NewOrder(context.Background(), orderReq)
test.AssertError(t, err, "NewOrder with invalid profile did not error")
test.AssertErrorIs(t, err, berrors.Unauthorized)
test.AssertContains(t, err.Error(), "not permitted to use certificate profile")
ra.validationProfiles = map[string]*ValidationProfile{
"test": NewValidationProfile(tc.allowList),
}

// Set up a ValidationProfile with an allowList that does include
// Registration.Id.
ra.validationProfiles = map[string]*ValidationProfile{
"test": NewValidationProfile(allowlist.NewList([]int64{Registration.Id})),
}
orderReq := &rapb.NewOrderRequest{
RegistrationID: Registration.Id,
DnsNames: []string{randomDomain()},
CertificateProfileName: "test",
}
_, err := ra.NewOrder(context.Background(), orderReq)

// Issuance should succeed.
orderReq = &rapb.NewOrderRequest{
RegistrationID: Registration.Id,
DnsNames: []string{"c.example.com"},
CertificateProfileName: "test",
if tc.expectErr {
test.AssertError(t, err, "NewOrder did not error")
test.AssertErrorIs(t, err, berrors.Unauthorized)
beautifulentropy marked this conversation as resolved.
Show resolved Hide resolved
test.AssertContains(t, err.Error(), tc.expectErrContains)
} else {
test.AssertNotError(t, err, "NewOrder failed")
}
})
}
_, err = ra.NewOrder(context.Background(), orderReq)
test.AssertNotError(t, err, "NewOrder for account ID that is on the allowlist failed")
}

// mockSAWithAuthzs has a GetAuthorizations2 method that returns the protobuf
Expand Down
Loading