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

Add option to select algorithm to generate ACME certificates #3319

Merged
merged 5 commits into from
May 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion acme/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"time"

"github.com/containous/traefik/log"
acmeprovider "github.com/containous/traefik/provider/acme"
"github.com/containous/traefik/types"
acme "github.com/xenolf/lego/acmev2"
)
Expand All @@ -23,6 +24,7 @@ type Account struct {
Email string
Registration *acme.RegistrationResource
PrivateKey []byte
KeyType acme.KeyType
DomainsCertificate DomainsCertificates
ChallengeCerts map[string]*ChallengeCert
HTTPChallenge map[string]map[string][]byte
Expand Down Expand Up @@ -63,7 +65,9 @@ func (a *Account) Init() error {
}

// NewAccount creates an account
func NewAccount(email string, certs []*DomainsCertificate) (*Account, error) {
func NewAccount(email string, certs []*DomainsCertificate, keyTypeValue string) (*Account, error) {
keyType := acmeprovider.GetKeyType(keyTypeValue)

// Create a user. New accounts need an email and private key to start
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
Expand All @@ -79,6 +83,7 @@ func NewAccount(email string, certs []*DomainsCertificate) (*Account, error) {
return &Account{
Email: email,
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
KeyType: keyType,
DomainsCertificate: DomainsCertificates{Certs: domainsCerts.Certs},
ChallengeCerts: map[string]*ChallengeCert{}}, nil
}
Expand Down
5 changes: 3 additions & 2 deletions acme/acme.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type ACME struct {
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
CAServer string `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. Default to 'RSA4096'"`
DNSChallenge *acmeprovider.DNSChallenge `description:"Activate DNS-01 Challenge"`
HTTPChallenge *acmeprovider.HTTPChallenge `description:"Activate HTTP-01 Challenge"`
DNSProvider string `description:"(Deprecated) Activate DNS-01 Challenge"` // Deprecated
Expand Down Expand Up @@ -186,7 +187,7 @@ func (a *ACME) leadershipListener(elected bool) error {
domainsCerts = account.DomainsCertificate
}

account, err = NewAccount(a.Email, domainsCerts.Certs)
account, err = NewAccount(a.Email, domainsCerts.Certs, a.KeyType)
if err != nil {
return err
}
Expand Down Expand Up @@ -395,7 +396,7 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
if len(a.CAServer) > 0 {
caServer = a.CAServer
}
client, err := acme.NewClient(caServer, account, acme.RSA4096)
client, err := acme.NewClient(caServer, account, account.KeyType)
if err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions acme/localStore.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func RemoveAccountV1Values(account *Account) error {
account.Email = ""
account.Registration = nil
account.PrivateKey = nil
account.KeyType = "RSA4096"
}
}
return nil
Expand Down Expand Up @@ -113,6 +114,7 @@ func ConvertToNewFormat(fileName string) {
PrivateKey: account.PrivateKey,
Registration: account.Registration,
Email: account.Email,
KeyType: account.KeyType,
}

var newCertificates []*acme.Certificate
Expand Down Expand Up @@ -167,6 +169,7 @@ func FromNewToOldFormat(fileName string) (*Account, error) {
PrivateKey: storeAccount.PrivateKey,
Registration: storeAccount.Registration,
DomainsCertificate: DomainsCertificates{},
KeyType: storeAccount.KeyType,
}
}

Expand Down
1 change: 1 addition & 0 deletions configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ func (gc *GlobalConfiguration) InitACMEProvider() *acmeprovider.Provider {
if gc.Cluster == nil {
provider := &acmeprovider.Provider{}
provider.Configuration = &acmeprovider.Configuration{
KeyType: gc.ACME.KeyType,
OnHostRule: gc.ACME.OnHostRule,
OnDemand: gc.ACME.OnDemand,
Email: gc.ACME.Email,
Expand Down
9 changes: 9 additions & 0 deletions docs/configuration/acme.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ entryPoint = "https"
#
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"

# KeyType to use.
#
# Optional
# Default: "RSA4096"
#
# Available values : "EC256", "EC384", "RSA2048", "RSA4096", "RSA8192"
#
# KeyType = "RSA4096"

# Domains list.
# Only domains defined here can generate wildcard certificates.
#
Expand Down
6 changes: 3 additions & 3 deletions examples/acme/manage_acme_docker_environment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ readonly doc_file=$basedir"/docker-compose.yml"
down_environment() {
echo "STOP Docker environment"
! docker-compose -f $doc_file down -v &>/dev/null && \
echo "[ERROR] Impossible to stop the Docker environment" && exit 11
echo "[ERROR] Unable to stop the Docker environment" && exit 11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😉

}

# Create and start Docker-compose environment or subpart of its services (if services are listed)
# $@ : List of services to start (optional)
up_environment() {
echo "START Docker environment"
! docker-compose -f $doc_file up -d $@ &>/dev/null && \
echo "[ERROR] Impossible to start Docker environment" && exit 21
echo "[ERROR] Unable to start Docker environment" && exit 21
}

# Init the environment : get IP address and create needed files
Expand All @@ -40,7 +40,7 @@ start_boulder() {
sleep 5
let waiting_counter-=1
if [[ $waiting_counter -eq 0 ]]; then
echo "[ERROR] Impossible to start boulder container in the allowed time, the Docker environment will be stopped"
echo "[ERROR] Unable to start boulder container in the allowed time, the Docker environment will be stopped"
down_environment
exit 41
fi
Expand Down
63 changes: 50 additions & 13 deletions integration/acme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package integration

import (
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"os"
Expand All @@ -24,6 +25,7 @@ type AcmeTestCase struct {
onDemand bool
traefikConfFilePath string
domainToCheck string
algorithm x509.PublicKeyAlgorithm
}

const (
Expand Down Expand Up @@ -60,7 +62,8 @@ func (s *AcmeSuite) TestACMEProviderAtStart(c *check.C) {
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/provideracme/acme.toml",
onDemand: false,
domainToCheck: acmeDomain}
domainToCheck: acmeDomain,
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}
Expand All @@ -70,7 +73,8 @@ func (s *AcmeSuite) TestACMEProviderAtStartInSAN(c *check.C) {
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/provideracme/acme_insan.toml",
onDemand: false,
domainToCheck: "acme.wtf"}
domainToCheck: "acme.wtf",
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}
Expand All @@ -80,7 +84,30 @@ func (s *AcmeSuite) TestACMEProviderOnHost(c *check.C) {
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/provideracme/acme_onhost.toml",
onDemand: false,
domainToCheck: acmeDomain}
domainToCheck: acmeDomain,
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}

// Test ACME provider with certificate at start ECDSA algo
func (s *AcmeSuite) TestACMEProviderOnHostECDSA(c *check.C) {
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/provideracme/acme_onhost_ecdsa.toml",
onDemand: false,
domainToCheck: acmeDomain,
algorithm: x509.ECDSA}

s.retrieveAcmeCertificate(c, testCase)
}

// Test ACME provider with certificate at start invalid algo default RSA
func (s *AcmeSuite) TestACMEProviderOnHostInvalidAlgo(c *check.C) {
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/provideracme/acme_onhost_invalid_algo.toml",
onDemand: false,
domainToCheck: acmeDomain,
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}
Expand All @@ -90,7 +117,8 @@ func (s *AcmeSuite) TestACMEProviderOnHostWithNoACMEChallenge(c *check.C) {
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/acme/no_challenge_acme.toml",
onDemand: false,
domainToCheck: traefikDefaultDomain}
domainToCheck: traefikDefaultDomain,
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}
Expand All @@ -100,7 +128,8 @@ func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateHTTP01(c *check.C) {
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_http01.toml",
onDemand: true,
domainToCheck: acmeDomain}
domainToCheck: acmeDomain,
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}
Expand All @@ -110,7 +139,8 @@ func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificateHTTP01(c *check.C) {
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_http01.toml",
onDemand: false,
domainToCheck: acmeDomain}
domainToCheck: acmeDomain,
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}
Expand All @@ -120,7 +150,8 @@ func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificateHTTP01WithPath(c *check
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_http01_web.toml",
onDemand: false,
domainToCheck: acmeDomain}
domainToCheck: acmeDomain,
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}
Expand All @@ -130,7 +161,8 @@ func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateWithWildcard(c *check.C)
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_provided.toml",
onDemand: true,
domainToCheck: wildcardDomain}
domainToCheck: wildcardDomain,
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}
Expand All @@ -140,7 +172,8 @@ func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificateWithWildcard(c *check.C
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_provided.toml",
onDemand: false,
domainToCheck: wildcardDomain}
domainToCheck: wildcardDomain,
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}
Expand All @@ -150,7 +183,8 @@ func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateWithDynamicWildcard(c *ch
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_provided_dynamic.toml",
onDemand: true,
domainToCheck: wildcardDomain}
domainToCheck: wildcardDomain,
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}
Expand All @@ -160,7 +194,8 @@ func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificateWithDynamicWildcard(c *
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_provided_dynamic.toml",
onDemand: false,
domainToCheck: wildcardDomain}
domainToCheck: wildcardDomain,
algorithm: x509.RSA}

s.retrieveAcmeCertificate(c, testCase)
}
Expand All @@ -181,8 +216,9 @@ func (s *AcmeSuite) TestNoValidLetsEncryptServer(c *check.C) {
// Doing an HTTPS request and test the response certificate
func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase AcmeTestCase) {
file := s.adaptFile(c, testCase.traefikConfFilePath, struct {
BoulderHost string
OnDemand, OnHostRule bool
BoulderHost string
OnDemand bool
OnHostRule bool
}{
BoulderHost: s.boulderIP,
OnDemand: testCase.onDemand,
Expand Down Expand Up @@ -251,4 +287,5 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase AcmeTestCase) {
c.Assert(resp.StatusCode, checker.Equals, http.StatusOK)
// Check Domain into response certificate
c.Assert(resp.TLS.PeerCertificates[0].Subject.CommonName, checker.Equals, testCase.domainToCheck)
c.Assert(resp.TLS.PeerCertificates[0].PublicKeyAlgorithm, checker.Equals, testCase.algorithm)
}
2 changes: 1 addition & 1 deletion integration/fixtures/provideracme/acme_onhost.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defaultEntryPoints = ["http", "https"]

[acme]
email = "test@traefik.io"
storage = "/tmp/acme.jsonl"
storage = "/tmp/acme.json"
entryPoint = "https"
onDemand = {{.OnDemand}}
onHostRule = {{.OnHostRule}}
Expand Down
38 changes: 38 additions & 0 deletions integration/fixtures/provideracme/acme_onhost_ecdsa.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
logLevel = "DEBUG"

defaultEntryPoints = ["http", "https"]

[entryPoints]
[entryPoints.http]
address = ":5002"
[entryPoints.https]
address = ":5001"
[entryPoints.https.tls]


[acme]
email = "test@traefik.io"
storage = "/tmp/acme.json"
entryPoint = "https"
onDemand = {{.OnDemand}}
onHostRule = {{.OnHostRule}}
caServer = "http://{{.BoulderHost}}:4001/directory"
keyType = "EC384"
[acme.httpChallenge]
entryPoint="http"

[api]

[file]

[backends]
[backends.backend]
[backends.backend.servers.server1]
url = "http://127.0.0.1:9010"
weight = 1

[frontends]
[frontends.frontend]
backend = "backend"
[frontends.frontend.routes.test]
rule = "Host:traefik.acme.wtf"
38 changes: 38 additions & 0 deletions integration/fixtures/provideracme/acme_onhost_invalid_algo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
logLevel = "DEBUG"

defaultEntryPoints = ["http", "https"]

[entryPoints]
[entryPoints.http]
address = ":5002"
[entryPoints.https]
address = ":5001"
[entryPoints.https.tls]


[acme]
email = "test@traefik.io"
storage = "/tmp/acme.json"
entryPoint = "https"
onDemand = {{.OnDemand}}
onHostRule = {{.OnHostRule}}
caServer = "http://{{.BoulderHost}}:4001/directory"
keyType = "INVALID"
[acme.httpChallenge]
entryPoint="http"

[api]

[file]

[backends]
[backends.backend]
[backends.backend.servers.server1]
url = "http://127.0.0.1:9010"
weight = 1

[frontends]
[frontends.frontend]
backend = "backend"
[frontends.frontend.routes.test]
rule = "Host:traefik.acme.wtf"