Use a type for acme challenges

This commit is contained in:
Mariano Cano 2022-09-08 12:34:06 -07:00
parent a89bea701d
commit 59c5219a07
5 changed files with 119 additions and 47 deletions

View file

@ -259,7 +259,7 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error {
az.Challenges = make([]*acme.Challenge, 0, len(chTypes))
for _, typ := range chTypes {
// Make sure the challenge is enabled
if err := prov.AuthorizeChallenge(ctx, string(typ)); err != nil {
if err := prov.AuthorizeChallenge(ctx, provisioner.ACMEChallenge(typ)); err != nil {
continue
}

View file

@ -71,7 +71,7 @@ type Provisioner interface {
AuthorizeOrderIdentifier(ctx context.Context, identifier provisioner.ACMEIdentifier) error
AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error)
AuthorizeRevoke(ctx context.Context, token string) error
AuthorizeChallenge(ctx context.Context, challenge string) error
AuthorizeChallenge(ctx context.Context, challenge provisioner.ACMEChallenge) error
GetID() string
GetName() string
DefaultTLSCertDuration() time.Duration

View file

@ -11,6 +11,35 @@ import (
"github.com/pkg/errors"
)
// ACMEChallenge represents the supported acme challenges.
type ACMEChallenge string
const (
// HTTP_01 is the http-01 ACME challenge.
HTTP_01 ACMEChallenge = "http-01"
// DNS_01 is the dns-01 ACME challenge.
DNS_01 ACMEChallenge = "dns-01"
// TLS_ALPN_01 is the tls-alpn-01 ACME challenge.
TLS_ALPN_01 ACMEChallenge = "tls-alpn-01"
// DEVICE_ATTEST_01 is the device-attest-01 ACME challenge.
DEVICE_ATTEST_01 ACMEChallenge = "device-attest-01"
)
// String returns a normalized version of the challenge.
func (c ACMEChallenge) String() string {
return strings.ToLower(string(c))
}
// Validate returns an error if the acme challenge is not a valid one.
func (c ACMEChallenge) Validate() error {
switch ACMEChallenge(c.String()) {
case HTTP_01, DNS_01, TLS_ALPN_01, DEVICE_ATTEST_01:
return nil
default:
return fmt.Errorf("acme challenge %q is not supported", c)
}
}
// ACME is the acme provisioner type, an entity that can authorize the ACME
// provisioning flow.
type ACME struct {
@ -27,9 +56,9 @@ type ACME struct {
// Challenges contains the enabled challenges for this provisioner. If this
// value is not set the default http-01, dns-01 and tls-alpn-01 challenges
// will be enabled, device-attest-01 will be disabled.
Challenges []string `json:"challenges,omitempty"`
Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
Challenges []ACMEChallenge `json:"challenges,omitempty"`
Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
ctl *Controller
}
@ -88,6 +117,12 @@ func (p *ACME) Init(config Config) (err error) {
return errors.New("provisioner name cannot be empty")
}
for _, c := range p.Challenges {
if err := c.Validate(); err != nil {
return err
}
}
p.ctl, err = NewController(p, p.Claims, config, p.Options)
return
}
@ -172,15 +207,15 @@ func (p *ACME) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error
// AuthorizeChallenge checks if the given challenge is enabled. By default
// http-01, dns-01 and tls-alpn-01 are enabled, to disable any of them the
// Challenge provisioner property should have at least one element.
func (p *ACME) AuthorizeChallenge(ctx context.Context, challenge string) error {
enabledChallenges := []string{
"http-01", "dns-01", "tls-alpn-01",
func (p *ACME) AuthorizeChallenge(ctx context.Context, challenge ACMEChallenge) error {
enabledChallenges := []ACMEChallenge{
HTTP_01, DNS_01, TLS_ALPN_01,
}
if len(p.Challenges) > 0 {
enabledChallenges = p.Challenges
}
for _, ch := range enabledChallenges {
if strings.EqualFold(ch, challenge) {
if strings.EqualFold(string(ch), string(challenge)) {
return nil
}
}

View file

@ -13,6 +13,28 @@ import (
"github.com/smallstep/certificates/api/render"
)
func TestACMEChallenge_Validate(t *testing.T) {
tests := []struct {
name string
c ACMEChallenge
wantErr bool
}{
{"http-01", HTTP_01, false},
{"dns-01", DNS_01, false},
{"tls-alpn-01", TLS_ALPN_01, false},
{"device-attest-01", DEVICE_ATTEST_01, false},
{"uppercase", "HTTP-01", false},
{"fail", "http-02", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.c.Validate(); (err != nil) != tt.wantErr {
t.Errorf("ACMEChallenge.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestACME_Getters(t *testing.T) {
p, err := generateACME()
assert.FatalError(t, err)
@ -65,11 +87,26 @@ func TestACME_Init(t *testing.T) {
err: errors.New("claims: MinTLSCertDuration must be greater than 0"),
}
},
"fail-bad-challenge": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar", Challenges: []ACMEChallenge{HTTP_01, "zar"}},
err: errors.New("acme challenge \"zar\" is not supported"),
}
},
"ok": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar"},
}
},
"ok with challenges": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Name: "foo",
Type: "bar",
Challenges: []ACMEChallenge{DNS_01, DEVICE_ATTEST_01},
},
}
},
}
config := Config{
@ -208,11 +245,11 @@ func TestACME_AuthorizeSign(t *testing.T) {
func TestACME_AuthorizeChallenge(t *testing.T) {
ctx := context.Background()
type fields struct {
Challenges []string
Challenges []ACMEChallenge
}
type args struct {
ctx context.Context
challenge string
challenge ACMEChallenge
}
tests := []struct {
name string
@ -220,19 +257,19 @@ func TestACME_AuthorizeChallenge(t *testing.T) {
args args
wantErr bool
}{
{"ok http-01", fields{nil}, args{ctx, "http-01"}, false},
{"ok dns-01", fields{nil}, args{ctx, "dns-01"}, false},
{"ok tls-alpn-01", fields{[]string{}}, args{ctx, "tls-alpn-01"}, false},
{"fail device-attest-01", fields{[]string{}}, args{ctx, "device-attest-01"}, true},
{"ok http-01 enabled", fields{[]string{"http-01"}}, args{ctx, "http-01"}, false},
{"ok dns-01 enabled", fields{[]string{"http-01", "dns-01"}}, args{ctx, "dns-01"}, false},
{"ok tls-alpn-01 enabled", fields{[]string{"http-01", "dns-01", "tls-alpn-01"}}, args{ctx, "tls-alpn-01"}, false},
{"ok device-attest-01 enabled", fields{[]string{"device-attest-01", "dns-01"}}, args{ctx, "device-attest-01"}, false},
{"fail http-01", fields{[]string{"dns-01"}}, args{ctx, "http-01"}, true},
{"fail dns-01", fields{[]string{"http-01", "tls-alpn-01"}}, args{ctx, "dns-01"}, true},
{"fail tls-alpn-01", fields{[]string{"http-01", "dns-01", "device-attest-01"}}, args{ctx, "tls-alpn-01"}, true},
{"fail device-attest-01", fields{[]string{"http-01", "dns-01"}}, args{ctx, "device-attest-01"}, true},
{"fail unknown", fields{[]string{"http-01", "dns-01", "tls-alpn-01", "device-attest-01"}}, args{ctx, "unknown"}, true},
{"ok http-01", fields{nil}, args{ctx, HTTP_01}, false},
{"ok dns-01", fields{nil}, args{ctx, DNS_01}, false},
{"ok tls-alpn-01", fields{[]ACMEChallenge{}}, args{ctx, TLS_ALPN_01}, false},
{"fail device-attest-01", fields{[]ACMEChallenge{}}, args{ctx, "device-attest-01"}, true},
{"ok http-01 enabled", fields{[]ACMEChallenge{"http-01"}}, args{ctx, "HTTP-01"}, false},
{"ok dns-01 enabled", fields{[]ACMEChallenge{"http-01", "dns-01"}}, args{ctx, DNS_01}, false},
{"ok tls-alpn-01 enabled", fields{[]ACMEChallenge{"http-01", "dns-01", "tls-alpn-01"}}, args{ctx, TLS_ALPN_01}, false},
{"ok device-attest-01 enabled", fields{[]ACMEChallenge{"device-attest-01", "dns-01"}}, args{ctx, DEVICE_ATTEST_01}, false},
{"fail http-01", fields{[]ACMEChallenge{"dns-01"}}, args{ctx, "http-01"}, true},
{"fail dns-01", fields{[]ACMEChallenge{"http-01", "tls-alpn-01"}}, args{ctx, "dns-01"}, true},
{"fail tls-alpn-01", fields{[]ACMEChallenge{"http-01", "dns-01", "device-attest-01"}}, args{ctx, "tls-alpn-01"}, true},
{"fail device-attest-01", fields{[]ACMEChallenge{"http-01", "dns-01"}}, args{ctx, "device-attest-01"}, true},
{"fail unknown", fields{[]ACMEChallenge{"http-01", "dns-01", "tls-alpn-01", "device-attest-01"}}, args{ctx, "unknown"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View file

@ -1125,39 +1125,39 @@ func parseInstanceAge(age string) (provisioner.Duration, error) {
return instanceAge, nil
}
func challengesToCertificates(challenges []linkedca.ACMEProvisioner_ChallengeType) []string {
ret := make([]string, len(challenges))
for i, ch := range challenges {
// challengesToCertificates converts linkedca challenges to provisioner ones
// skipping the unknown ones.
func challengesToCertificates(challenges []linkedca.ACMEProvisioner_ChallengeType) []provisioner.ACMEChallenge {
ret := make([]provisioner.ACMEChallenge, 0, len(challenges))
for _, ch := range challenges {
switch ch {
case linkedca.ACMEProvisioner_HTTP_01:
ret[i] = "http-01"
ret = append(ret, provisioner.HTTP_01)
case linkedca.ACMEProvisioner_DNS_01:
ret[i] = "dns-01"
ret = append(ret, provisioner.DNS_01)
case linkedca.ACMEProvisioner_TLS_ALPN_O1:
ret[i] = "tls-alpn-01"
ret = append(ret, provisioner.TLS_ALPN_01)
case linkedca.ACMEProvisioner_DEVICE_ATTEST_01:
ret[i] = "device-attest-01"
default:
ret[i] = "unknown"
ret = append(ret, provisioner.DEVICE_ATTEST_01)
}
}
return ret
}
func challengesToLinkedca(challenges []string) []linkedca.ACMEProvisioner_ChallengeType {
ret := make([]linkedca.ACMEProvisioner_ChallengeType, len(challenges))
for i, ch := range challenges {
switch ch {
case "http-01":
ret[i] = linkedca.ACMEProvisioner_HTTP_01
case "dns-01":
ret[i] = linkedca.ACMEProvisioner_DNS_01
case "tls-alpn-01":
ret[i] = linkedca.ACMEProvisioner_TLS_ALPN_O1
case "device-attest-01":
ret[i] = linkedca.ACMEProvisioner_DEVICE_ATTEST_01
default:
ret[i] = linkedca.ACMEProvisioner_UNKNOWN
// challengesToLinkedca converts provisioner challenges to linkedca ones
// skipping the unknown ones.
func challengesToLinkedca(challenges []provisioner.ACMEChallenge) []linkedca.ACMEProvisioner_ChallengeType {
ret := make([]linkedca.ACMEProvisioner_ChallengeType, 0, len(challenges))
for _, ch := range challenges {
switch provisioner.ACMEChallenge(ch.String()) {
case provisioner.HTTP_01:
ret = append(ret, linkedca.ACMEProvisioner_HTTP_01)
case provisioner.DNS_01:
ret = append(ret, linkedca.ACMEProvisioner_DNS_01)
case provisioner.TLS_ALPN_01:
ret = append(ret, linkedca.ACMEProvisioner_TLS_ALPN_O1)
case provisioner.DEVICE_ATTEST_01:
ret = append(ret, linkedca.ACMEProvisioner_DEVICE_ATTEST_01)
}
}
return ret