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)) az.Challenges = make([]*acme.Challenge, 0, len(chTypes))
for _, typ := range chTypes { for _, typ := range chTypes {
// Make sure the challenge is enabled // 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 continue
} }

View file

@ -71,7 +71,7 @@ type Provisioner interface {
AuthorizeOrderIdentifier(ctx context.Context, identifier provisioner.ACMEIdentifier) error AuthorizeOrderIdentifier(ctx context.Context, identifier provisioner.ACMEIdentifier) error
AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error)
AuthorizeRevoke(ctx context.Context, token string) 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 GetID() string
GetName() string GetName() string
DefaultTLSCertDuration() time.Duration DefaultTLSCertDuration() time.Duration

View file

@ -11,6 +11,35 @@ import (
"github.com/pkg/errors" "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 // ACME is the acme provisioner type, an entity that can authorize the ACME
// provisioning flow. // provisioning flow.
type ACME struct { type ACME struct {
@ -27,7 +56,7 @@ type ACME struct {
// Challenges contains the enabled challenges for this provisioner. If this // 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 // value is not set the default http-01, dns-01 and tls-alpn-01 challenges
// will be enabled, device-attest-01 will be disabled. // will be enabled, device-attest-01 will be disabled.
Challenges []string `json:"challenges,omitempty"` Challenges []ACMEChallenge `json:"challenges,omitempty"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"` Options *Options `json:"options,omitempty"`
@ -88,6 +117,12 @@ func (p *ACME) Init(config Config) (err error) {
return errors.New("provisioner name cannot be empty") 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) p.ctl, err = NewController(p, p.Claims, config, p.Options)
return 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 // 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 // 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. // Challenge provisioner property should have at least one element.
func (p *ACME) AuthorizeChallenge(ctx context.Context, challenge string) error { func (p *ACME) AuthorizeChallenge(ctx context.Context, challenge ACMEChallenge) error {
enabledChallenges := []string{ enabledChallenges := []ACMEChallenge{
"http-01", "dns-01", "tls-alpn-01", HTTP_01, DNS_01, TLS_ALPN_01,
} }
if len(p.Challenges) > 0 { if len(p.Challenges) > 0 {
enabledChallenges = p.Challenges enabledChallenges = p.Challenges
} }
for _, ch := range enabledChallenges { for _, ch := range enabledChallenges {
if strings.EqualFold(ch, challenge) { if strings.EqualFold(string(ch), string(challenge)) {
return nil return nil
} }
} }

View file

@ -13,6 +13,28 @@ import (
"github.com/smallstep/certificates/api/render" "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) { func TestACME_Getters(t *testing.T) {
p, err := generateACME() p, err := generateACME()
assert.FatalError(t, err) assert.FatalError(t, err)
@ -65,11 +87,26 @@ func TestACME_Init(t *testing.T) {
err: errors.New("claims: MinTLSCertDuration must be greater than 0"), 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 { "ok": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{ return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar"}, 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{ config := Config{
@ -208,11 +245,11 @@ func TestACME_AuthorizeSign(t *testing.T) {
func TestACME_AuthorizeChallenge(t *testing.T) { func TestACME_AuthorizeChallenge(t *testing.T) {
ctx := context.Background() ctx := context.Background()
type fields struct { type fields struct {
Challenges []string Challenges []ACMEChallenge
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
challenge string challenge ACMEChallenge
} }
tests := []struct { tests := []struct {
name string name string
@ -220,19 +257,19 @@ func TestACME_AuthorizeChallenge(t *testing.T) {
args args args args
wantErr bool wantErr bool
}{ }{
{"ok http-01", fields{nil}, args{ctx, "http-01"}, false}, {"ok http-01", fields{nil}, args{ctx, HTTP_01}, false},
{"ok dns-01", fields{nil}, args{ctx, "dns-01"}, false}, {"ok dns-01", fields{nil}, args{ctx, DNS_01}, false},
{"ok tls-alpn-01", fields{[]string{}}, args{ctx, "tls-alpn-01"}, false}, {"ok tls-alpn-01", fields{[]ACMEChallenge{}}, args{ctx, TLS_ALPN_01}, false},
{"fail device-attest-01", fields{[]string{}}, args{ctx, "device-attest-01"}, true}, {"fail device-attest-01", fields{[]ACMEChallenge{}}, args{ctx, "device-attest-01"}, true},
{"ok http-01 enabled", fields{[]string{"http-01"}}, args{ctx, "http-01"}, false}, {"ok http-01 enabled", fields{[]ACMEChallenge{"http-01"}}, args{ctx, "HTTP-01"}, false},
{"ok dns-01 enabled", fields{[]string{"http-01", "dns-01"}}, args{ctx, "dns-01"}, false}, {"ok dns-01 enabled", fields{[]ACMEChallenge{"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 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{[]string{"device-attest-01", "dns-01"}}, args{ctx, "device-attest-01"}, false}, {"ok device-attest-01 enabled", fields{[]ACMEChallenge{"device-attest-01", "dns-01"}}, args{ctx, DEVICE_ATTEST_01}, false},
{"fail http-01", fields{[]string{"dns-01"}}, args{ctx, "http-01"}, true}, {"fail http-01", fields{[]ACMEChallenge{"dns-01"}}, args{ctx, "http-01"}, true},
{"fail dns-01", fields{[]string{"http-01", "tls-alpn-01"}}, args{ctx, "dns-01"}, true}, {"fail dns-01", fields{[]ACMEChallenge{"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 tls-alpn-01", fields{[]ACMEChallenge{"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 device-attest-01", fields{[]ACMEChallenge{"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}, {"fail unknown", fields{[]ACMEChallenge{"http-01", "dns-01", "tls-alpn-01", "device-attest-01"}}, args{ctx, "unknown"}, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

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