mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-28 03:33:45 +08:00
Merge branch 'master' into acme-database
This commit is contained in:
commit
5245045f44
|
@ -5,13 +5,9 @@ import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -48,36 +44,10 @@ func TestACMEServerWithDefaults(t *testing.T) {
|
||||||
}
|
}
|
||||||
`, "caddyfile")
|
`, "caddyfile")
|
||||||
|
|
||||||
datadir := caddy.AppDataDir()
|
|
||||||
rootCertsGlob := filepath.Join(datadir, "pki", "authorities", "local", "*.crt")
|
|
||||||
matches, err := filepath.Glob(rootCertsGlob)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not find root certs: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
for _, m := range matches {
|
|
||||||
certPem, err := os.ReadFile(m)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("reading cert file '%s' error: %s", m, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !certPool.AppendCertsFromPEM(certPem) {
|
|
||||||
t.Errorf("failed to append the cert: %s", m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client := acmez.Client{
|
client := acmez.Client{
|
||||||
Client: &acme.Client{
|
Client: &acme.Client{
|
||||||
Directory: "https://acme.localhost:9443/acme/local/directory",
|
Directory: "https://acme.localhost:9443/acme/local/directory",
|
||||||
HTTPClient: &http.Client{
|
HTTPClient: tester.Client,
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
RootCAs: certPool,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
},
|
},
|
||||||
ChallengeSolvers: map[string]acmez.Solver{
|
ChallengeSolvers: map[string]acmez.Solver{
|
||||||
|
@ -143,36 +113,10 @@ func TestACMEServerWithMismatchedChallenges(t *testing.T) {
|
||||||
}
|
}
|
||||||
`, "caddyfile")
|
`, "caddyfile")
|
||||||
|
|
||||||
datadir := caddy.AppDataDir()
|
|
||||||
rootCertsGlob := filepath.Join(datadir, "pki", "authorities", "local", "*.crt")
|
|
||||||
matches, err := filepath.Glob(rootCertsGlob)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not find root certs: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
for _, m := range matches {
|
|
||||||
certPem, err := os.ReadFile(m)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("reading cert file '%s' error: %s", m, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !certPool.AppendCertsFromPEM(certPem) {
|
|
||||||
t.Errorf("failed to append the cert: %s", m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client := acmez.Client{
|
client := acmez.Client{
|
||||||
Client: &acme.Client{
|
Client: &acme.Client{
|
||||||
Directory: "https://acme.localhost:9443/acme/local/directory",
|
Directory: "https://acme.localhost:9443/acme/local/directory",
|
||||||
HTTPClient: &http.Client{
|
HTTPClient: tester.Client,
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
RootCAs: certPool,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
},
|
},
|
||||||
ChallengeSolvers: map[string]acmez.Solver{
|
ChallengeSolvers: map[string]acmez.Solver{
|
||||||
|
@ -224,12 +168,13 @@ type naiveHTTPSolver struct {
|
||||||
func (s *naiveHTTPSolver) Present(ctx context.Context, challenge acme.Challenge) error {
|
func (s *naiveHTTPSolver) Present(ctx context.Context, challenge acme.Challenge) error {
|
||||||
smallstepacme.InsecurePortHTTP01 = acmeChallengePort
|
smallstepacme.InsecurePortHTTP01 = acmeChallengePort
|
||||||
s.srv = &http.Server{
|
s.srv = &http.Server{
|
||||||
Addr: fmt.Sprintf("localhost:%d", acmeChallengePort),
|
Addr: fmt.Sprintf(":%d", acmeChallengePort),
|
||||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
host, _, err := net.SplitHostPort(r.Host)
|
host, _, err := net.SplitHostPort(r.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
host = r.Host
|
host = r.Host
|
||||||
}
|
}
|
||||||
|
s.logger.Info("received request on challenge server", zap.String("path", r.URL.Path))
|
||||||
if r.Method == "GET" && r.URL.Path == challenge.HTTP01ResourcePath() && strings.EqualFold(host, challenge.Identifier.Value) {
|
if r.Method == "GET" && r.URL.Path == challenge.HTTP01ResourcePath() && strings.EqualFold(host, challenge.Identifier.Value) {
|
||||||
w.Header().Add("Content-Type", "text/plain")
|
w.Header().Add("Content-Type", "text/plain")
|
||||||
w.Write([]byte(challenge.KeyAuthorization))
|
w.Write([]byte(challenge.KeyAuthorization))
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddytest"
|
"github.com/caddyserver/caddy/v2/caddytest"
|
||||||
|
"github.com/mholt/acmez"
|
||||||
|
"github.com/mholt/acmez/acme"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestACMEServerDirectory(t *testing.T) {
|
func TestACMEServerDirectory(t *testing.T) {
|
||||||
|
@ -31,3 +39,171 @@ func TestACMEServerDirectory(t *testing.T) {
|
||||||
`{"newNonce":"https://acme.localhost:9443/acme/local/new-nonce","newAccount":"https://acme.localhost:9443/acme/local/new-account","newOrder":"https://acme.localhost:9443/acme/local/new-order","revokeCert":"https://acme.localhost:9443/acme/local/revoke-cert","keyChange":"https://acme.localhost:9443/acme/local/key-change"}
|
`{"newNonce":"https://acme.localhost:9443/acme/local/new-nonce","newAccount":"https://acme.localhost:9443/acme/local/new-account","newOrder":"https://acme.localhost:9443/acme/local/new-order","revokeCert":"https://acme.localhost:9443/acme/local/revoke-cert","keyChange":"https://acme.localhost:9443/acme/local/key-change"}
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestACMEServerAllowPolicy(t *testing.T) {
|
||||||
|
tester := caddytest.NewTester(t)
|
||||||
|
tester.InitServer(`
|
||||||
|
{
|
||||||
|
skip_install_trust
|
||||||
|
local_certs
|
||||||
|
admin localhost:2999
|
||||||
|
http_port 9080
|
||||||
|
https_port 9443
|
||||||
|
pki {
|
||||||
|
ca local {
|
||||||
|
name "Caddy Local Authority"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acme.localhost {
|
||||||
|
acme_server {
|
||||||
|
challenges http-01
|
||||||
|
allow {
|
||||||
|
domains localhost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, "caddyfile")
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
logger, err := zap.NewDevelopment()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := acmez.Client{
|
||||||
|
Client: &acme.Client{
|
||||||
|
Directory: "https://acme.localhost:9443/acme/local/directory",
|
||||||
|
HTTPClient: tester.Client,
|
||||||
|
Logger: logger,
|
||||||
|
},
|
||||||
|
ChallengeSolvers: map[string]acmez.Solver{
|
||||||
|
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
accountPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("generating account key: %v", err)
|
||||||
|
}
|
||||||
|
account := acme.Account{
|
||||||
|
Contact: []string{"mailto:you@example.com"},
|
||||||
|
TermsOfServiceAgreed: true,
|
||||||
|
PrivateKey: accountPrivateKey,
|
||||||
|
}
|
||||||
|
account, err = client.NewAccount(ctx, account)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("new account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every certificate needs a key.
|
||||||
|
certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("generating certificate key: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
{
|
||||||
|
certs, err := client.ObtainCertificate(
|
||||||
|
ctx,
|
||||||
|
account,
|
||||||
|
certPrivateKey,
|
||||||
|
[]string{"localhost"},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("obtaining certificate for allowed domain: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACME servers should usually give you the entire certificate chain
|
||||||
|
// in PEM format, and sometimes even alternate chains! It's up to you
|
||||||
|
// which one(s) to store and use, but whatever you do, be sure to
|
||||||
|
// store the certificate and key somewhere safe and secure, i.e. don't
|
||||||
|
// lose them!
|
||||||
|
for _, cert := range certs {
|
||||||
|
t.Logf("Certificate %q:\n%s\n\n", cert.URL, cert.ChainPEM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
_, err := client.ObtainCertificate(ctx, account, certPrivateKey, []string{"not-matching.localhost"})
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("obtaining certificate for 'not-matching.localhost' domain")
|
||||||
|
} else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
|
||||||
|
t.Logf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestACMEServerDenyPolicy(t *testing.T) {
|
||||||
|
tester := caddytest.NewTester(t)
|
||||||
|
tester.InitServer(`
|
||||||
|
{
|
||||||
|
skip_install_trust
|
||||||
|
local_certs
|
||||||
|
admin localhost:2999
|
||||||
|
http_port 9080
|
||||||
|
https_port 9443
|
||||||
|
pki {
|
||||||
|
ca local {
|
||||||
|
name "Caddy Local Authority"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acme.localhost {
|
||||||
|
acme_server {
|
||||||
|
deny {
|
||||||
|
domains deny.localhost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, "caddyfile")
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
logger, err := zap.NewDevelopment()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := acmez.Client{
|
||||||
|
Client: &acme.Client{
|
||||||
|
Directory: "https://acme.localhost:9443/acme/local/directory",
|
||||||
|
HTTPClient: tester.Client,
|
||||||
|
Logger: logger,
|
||||||
|
},
|
||||||
|
ChallengeSolvers: map[string]acmez.Solver{
|
||||||
|
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
accountPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("generating account key: %v", err)
|
||||||
|
}
|
||||||
|
account := acme.Account{
|
||||||
|
Contact: []string{"mailto:you@example.com"},
|
||||||
|
TermsOfServiceAgreed: true,
|
||||||
|
PrivateKey: accountPrivateKey,
|
||||||
|
}
|
||||||
|
account, err = client.NewAccount(ctx, account)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("new account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every certificate needs a key.
|
||||||
|
certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("generating certificate key: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
{
|
||||||
|
_, err := client.ObtainCertificate(ctx, account, certPrivateKey, []string{"deny.localhost"})
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("obtaining certificate for 'deny.localhost' domain")
|
||||||
|
} else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
|
||||||
|
t.Logf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -101,6 +101,9 @@ type Handler struct {
|
||||||
// "http-01", "dns-01", "tls-alpn-01"
|
// "http-01", "dns-01", "tls-alpn-01"
|
||||||
Challenges ACMEChallenges `json:"challenges,omitempty" `
|
Challenges ACMEChallenges `json:"challenges,omitempty" `
|
||||||
|
|
||||||
|
// The policy to use for issuing certificates
|
||||||
|
Policy *Policy `json:"policy,omitempty"`
|
||||||
|
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
resolvers []caddy.NetworkAddress
|
resolvers []caddy.NetworkAddress
|
||||||
ctx caddy.Context
|
ctx caddy.Context
|
||||||
|
@ -176,6 +179,9 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
|
||||||
&provisioner.ACME{
|
&provisioner.ACME{
|
||||||
Name: ash.CA,
|
Name: ash.CA,
|
||||||
Challenges: ash.Challenges.toSmallstepType(),
|
Challenges: ash.Challenges.toSmallstepType(),
|
||||||
|
Options: &provisioner.Options{
|
||||||
|
X509: ash.Policy.normalizeRules(),
|
||||||
|
},
|
||||||
Type: provisioner.TypeACME.String(),
|
Type: provisioner.TypeACME.String(),
|
||||||
Claims: &provisioner.Claims{
|
Claims: &provisioner.Claims{
|
||||||
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
|
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
|
||||||
|
|
|
@ -33,6 +33,15 @@ func init() {
|
||||||
// lifetime <duration>
|
// lifetime <duration>
|
||||||
// resolvers <addresses...>
|
// resolvers <addresses...>
|
||||||
// challenges <challenges...>
|
// challenges <challenges...>
|
||||||
|
// allow_wildcard_names
|
||||||
|
// allow {
|
||||||
|
// domains <domains...>
|
||||||
|
// ip_ranges <addresses...>
|
||||||
|
// }
|
||||||
|
// deny {
|
||||||
|
// domains <domains...>
|
||||||
|
// ip_ranges <addresses...>
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
|
func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
|
||||||
h.Next() // consume directive name
|
h.Next() // consume directive name
|
||||||
|
@ -60,7 +69,6 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
|
||||||
ca = new(caddypki.CA)
|
ca = new(caddypki.CA)
|
||||||
}
|
}
|
||||||
ca.ID = acmeServer.CA
|
ca.ID = acmeServer.CA
|
||||||
|
|
||||||
case "lifetime":
|
case "lifetime":
|
||||||
if !h.NextArg() {
|
if !h.NextArg() {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
|
@ -70,7 +78,6 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if d := time.Duration(ca.IntermediateLifetime); d > 0 && dur > d {
|
if d := time.Duration(ca.IntermediateLifetime); d > 0 && dur > d {
|
||||||
return nil, h.Errf("certificate lifetime (%s) exceeds intermediate certificate lifetime (%s)", dur, d)
|
return nil, h.Errf("certificate lifetime (%s) exceeds intermediate certificate lifetime (%s)", dur, d)
|
||||||
}
|
}
|
||||||
|
@ -82,6 +89,53 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
|
||||||
}
|
}
|
||||||
case "challenges":
|
case "challenges":
|
||||||
acmeServer.Challenges = append(acmeServer.Challenges, stringToChallenges(h.RemainingArgs())...)
|
acmeServer.Challenges = append(acmeServer.Challenges, stringToChallenges(h.RemainingArgs())...)
|
||||||
|
case "allow_wildcard_names":
|
||||||
|
if acmeServer.Policy == nil {
|
||||||
|
acmeServer.Policy = &Policy{}
|
||||||
|
}
|
||||||
|
acmeServer.Policy.AllowWildcardNames = true
|
||||||
|
case "allow":
|
||||||
|
r := &RuleSet{}
|
||||||
|
for h.Next() {
|
||||||
|
for h.NextBlock(h.Nesting() - 1) {
|
||||||
|
if h.CountRemainingArgs() == 0 {
|
||||||
|
return nil, h.ArgErr() // TODO:
|
||||||
|
}
|
||||||
|
switch h.Val() {
|
||||||
|
case "domains":
|
||||||
|
r.Domains = append(r.Domains, h.RemainingArgs()...)
|
||||||
|
case "ip_ranges":
|
||||||
|
r.IPRanges = append(r.IPRanges, h.RemainingArgs()...)
|
||||||
|
default:
|
||||||
|
return nil, h.Errf("unrecognized 'allow' subdirective: %s", h.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if acmeServer.Policy == nil {
|
||||||
|
acmeServer.Policy = &Policy{}
|
||||||
|
}
|
||||||
|
acmeServer.Policy.Allow = r
|
||||||
|
case "deny":
|
||||||
|
r := &RuleSet{}
|
||||||
|
for h.Next() {
|
||||||
|
for h.NextBlock(h.Nesting() - 1) {
|
||||||
|
if h.CountRemainingArgs() == 0 {
|
||||||
|
return nil, h.ArgErr() // TODO:
|
||||||
|
}
|
||||||
|
switch h.Val() {
|
||||||
|
case "domains":
|
||||||
|
r.Domains = append(r.Domains, h.RemainingArgs()...)
|
||||||
|
case "ip_ranges":
|
||||||
|
r.IPRanges = append(r.IPRanges, h.RemainingArgs()...)
|
||||||
|
default:
|
||||||
|
return nil, h.Errf("unrecognized 'deny' subdirective: %s", h.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if acmeServer.Policy == nil {
|
||||||
|
acmeServer.Policy = &Policy{}
|
||||||
|
}
|
||||||
|
acmeServer.Policy.Deny = r
|
||||||
default:
|
default:
|
||||||
return nil, h.Errf("unrecognized ACME server directive: %s", h.Val())
|
return nil, h.Errf("unrecognized ACME server directive: %s", h.Val())
|
||||||
}
|
}
|
||||||
|
|
83
modules/caddypki/acmeserver/policy.go
Normal file
83
modules/caddypki/acmeserver/policy.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package acmeserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/smallstep/certificates/authority/policy"
|
||||||
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Policy defines the criteria for the ACME server
|
||||||
|
// of when to issue a certificate. Refer to the
|
||||||
|
// [Certificate Issuance Policy](https://smallstep.com/docs/step-ca/policies/)
|
||||||
|
// on Smallstep website for the evaluation criteria.
|
||||||
|
type Policy struct {
|
||||||
|
// If a rule set is configured to allow a certain type of name,
|
||||||
|
// all other types of names are automatically denied.
|
||||||
|
Allow *RuleSet `json:"allow,omitempty"`
|
||||||
|
|
||||||
|
// If a rule set is configured to deny a certain type of name,
|
||||||
|
// all other types of names are still allowed.
|
||||||
|
Deny *RuleSet `json:"deny,omitempty"`
|
||||||
|
|
||||||
|
// If set to true, the ACME server will allow issuing wildcard certificates.
|
||||||
|
AllowWildcardNames bool `json:"allow_wildcard_names,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuleSet is the specific set of SAN criteria for a certificate
|
||||||
|
// to be issued or denied.
|
||||||
|
type RuleSet struct {
|
||||||
|
// Domains is a list of DNS domains that are allowed to be issued.
|
||||||
|
// It can be in the form of FQDN for specific domain name, or
|
||||||
|
// a wildcard domain name format, e.g. *.example.com, to allow
|
||||||
|
// sub-domains of a domain.
|
||||||
|
Domains []string `json:"domains,omitempty"`
|
||||||
|
|
||||||
|
// IP ranges in the form of CIDR notation or specific IP addresses
|
||||||
|
// to be approved or denied for certificates. Non-CIDR IP addresses
|
||||||
|
// are matched exactly.
|
||||||
|
IPRanges []string `json:"ip_ranges,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeAllowRules returns `nil` if policy is nil, the `Allow` rule is `nil`,
|
||||||
|
// or all rules within the `Allow` rule are empty. Otherwise, it returns the X509NameOptions
|
||||||
|
// with the content of the `Allow` rule.
|
||||||
|
func (p *Policy) normalizeAllowRules() *policy.X509NameOptions {
|
||||||
|
if (p == nil) || (p.Allow == nil) || (len(p.Allow.Domains) == 0 && len(p.Allow.IPRanges) == 0) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &policy.X509NameOptions{
|
||||||
|
DNSDomains: p.Allow.Domains,
|
||||||
|
IPRanges: p.Allow.IPRanges,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeDenyRules returns `nil` if policy is nil, the `Deny` rule is `nil`,
|
||||||
|
// or all rules within the `Deny` rule are empty. Otherwise, it returns the X509NameOptions
|
||||||
|
// with the content of the `Deny` rule.
|
||||||
|
func (p *Policy) normalizeDenyRules() *policy.X509NameOptions {
|
||||||
|
if (p == nil) || (p.Deny == nil) || (len(p.Deny.Domains) == 0 && len(p.Deny.IPRanges) == 0) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &policy.X509NameOptions{
|
||||||
|
DNSDomains: p.Deny.Domains,
|
||||||
|
IPRanges: p.Deny.IPRanges,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeRules returns `nil` if policy is nil, the `Allow` and `Deny` rules are `nil`,
|
||||||
|
func (p *Policy) normalizeRules() *provisioner.X509Options {
|
||||||
|
if p == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
allow := p.normalizeAllowRules()
|
||||||
|
deny := p.normalizeDenyRules()
|
||||||
|
if allow == nil && deny == nil && !p.AllowWildcardNames {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &provisioner.X509Options{
|
||||||
|
AllowedNames: allow,
|
||||||
|
DeniedNames: deny,
|
||||||
|
AllowWildcardNames: p.AllowWildcardNames,
|
||||||
|
}
|
||||||
|
}
|
176
modules/caddypki/acmeserver/policy_test.go
Normal file
176
modules/caddypki/acmeserver/policy_test.go
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
package acmeserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/authority/policy"
|
||||||
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPolicyNormalizeAllowRules(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Allow *RuleSet
|
||||||
|
Deny *RuleSet
|
||||||
|
AllowWildcardNames bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want *policy.X509NameOptions
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "providing no rules results in 'nil'",
|
||||||
|
fields: fields{},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "providing 'nil' Allow rules results in 'nil', regardless of Deny rules",
|
||||||
|
fields: fields{
|
||||||
|
Allow: nil,
|
||||||
|
Deny: &RuleSet{},
|
||||||
|
AllowWildcardNames: true,
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "providing empty Allow rules results in 'nil', regardless of Deny rules",
|
||||||
|
fields: fields{
|
||||||
|
Allow: &RuleSet{
|
||||||
|
Domains: []string{},
|
||||||
|
IPRanges: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rules configured in Allow are returned in X509NameOptions",
|
||||||
|
fields: fields{
|
||||||
|
Allow: &RuleSet{
|
||||||
|
Domains: []string{"example.com"},
|
||||||
|
IPRanges: []string{"127.0.0.1/32"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &policy.X509NameOptions{
|
||||||
|
DNSDomains: []string{"example.com"},
|
||||||
|
IPRanges: []string{"127.0.0.1/32"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
p := &Policy{
|
||||||
|
Allow: tt.fields.Allow,
|
||||||
|
Deny: tt.fields.Deny,
|
||||||
|
AllowWildcardNames: tt.fields.AllowWildcardNames,
|
||||||
|
}
|
||||||
|
if got := p.normalizeAllowRules(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Policy.normalizeAllowRules() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicy_normalizeDenyRules(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Allow *RuleSet
|
||||||
|
Deny *RuleSet
|
||||||
|
AllowWildcardNames bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want *policy.X509NameOptions
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "providing no rules results in 'nil'",
|
||||||
|
fields: fields{},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "providing 'nil' Deny rules results in 'nil', regardless of Allow rules",
|
||||||
|
fields: fields{
|
||||||
|
Deny: nil,
|
||||||
|
Allow: &RuleSet{},
|
||||||
|
AllowWildcardNames: true,
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "providing empty Deny rules results in 'nil', regardless of Allow rules",
|
||||||
|
fields: fields{
|
||||||
|
Deny: &RuleSet{
|
||||||
|
Domains: []string{},
|
||||||
|
IPRanges: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rules configured in Deny are returned in X509NameOptions",
|
||||||
|
fields: fields{
|
||||||
|
Deny: &RuleSet{
|
||||||
|
Domains: []string{"example.com"},
|
||||||
|
IPRanges: []string{"127.0.0.1/32"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &policy.X509NameOptions{
|
||||||
|
DNSDomains: []string{"example.com"},
|
||||||
|
IPRanges: []string{"127.0.0.1/32"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
p := &Policy{
|
||||||
|
Allow: tt.fields.Allow,
|
||||||
|
Deny: tt.fields.Deny,
|
||||||
|
AllowWildcardNames: tt.fields.AllowWildcardNames,
|
||||||
|
}
|
||||||
|
if got := p.normalizeDenyRules(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Policy.normalizeDenyRules() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicy_normalizeRules(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
policy *Policy
|
||||||
|
want *provisioner.X509Options
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "'nil' policy results in 'nil' options",
|
||||||
|
policy: nil,
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "'nil' Allow/Deny rules and disallowing wildcard names result in 'nil' X509Options",
|
||||||
|
policy: &Policy{
|
||||||
|
Allow: nil,
|
||||||
|
Deny: nil,
|
||||||
|
AllowWildcardNames: false,
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "'nil' Allow/Deny rules and allowing wildcard names result in 'nil' Allow/Deny rules in X509Options but allowing wildcard names in X509Options",
|
||||||
|
policy: &Policy{
|
||||||
|
Allow: nil,
|
||||||
|
Deny: nil,
|
||||||
|
AllowWildcardNames: true,
|
||||||
|
},
|
||||||
|
want: &provisioner.X509Options{
|
||||||
|
AllowWildcardNames: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.policy.normalizeRules(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Policy.normalizeRules() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user