split certificate config from runtime structures
baalajimaestro: Forward port for v2.9+ Signed-off-by: baalajimaestro <me@baalajimaestro.me>
This commit is contained in:
parent
b82f7f6a44
commit
3ae0b9342b
7 changed files with 275 additions and 212 deletions
|
@ -1,22 +1,15 @@
|
||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
"golang.org/x/crypto/ocsp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -51,29 +44,29 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// OCSPConfig configures how OCSP is handled.
|
// OCSPConfig configures how OCSP is handled.
|
||||||
type OCSPConfig struct {
|
type OCSPConfig struct {
|
||||||
DisableStapling bool `json:"disableStapling,omitempty" toml:"disableStapling,omitempty" yaml:"disableStapling,omitempty"`
|
DisableStapling bool `json:"disableStapling,omitempty" toml:"disableStapling,omitempty" yaml:"disableStapling,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// Certificate holds a SSL cert/key pair
|
// Certificate holds a SSL cert/key pair
|
||||||
// Certs and Key could be either a file path, or the file content itself.
|
// Certs and Key could be either a file path, or the file content itself.
|
||||||
type Certificate struct {
|
type Certificate struct {
|
||||||
CertFile FileOrContent `json:"certFile,omitempty" toml:"certFile,omitempty" yaml:"certFile,omitempty"`
|
CertFile FileOrContent `json:"certFile,omitempty" toml:"certFile,omitempty" yaml:"certFile,omitempty"`
|
||||||
KeyFile FileOrContent `json:"keyFile,omitempty" toml:"keyFile,omitempty" yaml:"keyFile,omitempty" loggable:"false"`
|
KeyFile FileOrContent `json:"keyFile,omitempty" toml:"keyFile,omitempty" yaml:"keyFile,omitempty" loggable:"false"`
|
||||||
OCSP OCSPConfig `json:"ocsp,omitempty" toml:"ocsp,omitempty" yaml:"ocsp,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
OCSP OCSPConfig `json:"ocsp,omitempty" toml:"ocsp,omitempty" yaml:"ocsp,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||||
|
|
||||||
Certificate *tls.Certificate `json:"-" toml:"-" yaml:"-"`
|
|
||||||
SANs []string `json:"-" toml:"-" yaml:"-"`
|
SANs []string `json:"-" toml:"-" yaml:"-"`
|
||||||
OCSPServer []string `json:"-" toml:"-" yaml:"-"`
|
|
||||||
OCSPResponse *ocsp.Response `json:"-" toml:"-" yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Certificates defines traefik certificates type
|
// Certificates defines traefik Certificates type
|
||||||
// Certs and Keys could be either a file path, or the file content itself.
|
// Certs and Keys could be either a file path, or the file content itself.
|
||||||
type Certificates []Certificate
|
type Certificates []Certificate
|
||||||
|
|
||||||
// GetCertificates retrieves the certificates as slice of tls.Certificate.
|
// GetCertificates retrieves the Certs as slice of tls.Certificate.
|
||||||
func (c Certificates) GetCertificates() []tls.Certificate {
|
func (c Certificates) GetCertificates() []tls.Certificate {
|
||||||
var certs []tls.Certificate
|
var certs []tls.Certificate
|
||||||
|
|
||||||
|
@ -117,150 +110,6 @@ func (f FileOrContent) Read() ([]byte, error) {
|
||||||
return content, nil
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppendCertificate appends a Certificate to a certificates map keyed by store name.
|
|
||||||
func (c *Certificate) AppendCertificate(certs map[string]map[string]*Certificate, storeName string) error {
|
|
||||||
certContent, err := c.CertFile.Read()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to read CertFile : %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyContent, err := c.KeyFile.Read()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to read KeyFile : %w", err)
|
|
||||||
}
|
|
||||||
tlsCert, err := tls.X509KeyPair(certContent, keyContent)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to generate TLS certificate : %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedCert, _ := x509.ParseCertificate(tlsCert.Certificate[0])
|
|
||||||
|
|
||||||
var SANs []string
|
|
||||||
if parsedCert.Subject.CommonName != "" {
|
|
||||||
SANs = append(SANs, strings.ToLower(parsedCert.Subject.CommonName))
|
|
||||||
}
|
|
||||||
if parsedCert.DNSNames != nil {
|
|
||||||
for _, dnsName := range parsedCert.DNSNames {
|
|
||||||
if dnsName != parsedCert.Subject.CommonName {
|
|
||||||
SANs = append(SANs, strings.ToLower(dnsName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if parsedCert.IPAddresses != nil {
|
|
||||||
for _, ip := range parsedCert.IPAddresses {
|
|
||||||
if ip.String() != parsedCert.Subject.CommonName {
|
|
||||||
SANs = append(SANs, strings.ToLower(ip.String()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Guarantees the order to produce a unique cert key.
|
|
||||||
sort.Strings(SANs)
|
|
||||||
certKey := strings.Join(SANs, ",")
|
|
||||||
|
|
||||||
certExists := false
|
|
||||||
if certs[storeName] == nil {
|
|
||||||
certs[storeName] = make(map[string]*Certificate)
|
|
||||||
} else {
|
|
||||||
for domains := range certs[storeName] {
|
|
||||||
if domains == certKey {
|
|
||||||
certExists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if certExists {
|
|
||||||
log.Debugf("Skipping addition of certificate for domain(s) %q, to TLS Store %s, as it already exists for this store.", certKey, storeName)
|
|
||||||
} else {
|
|
||||||
log.Debugf("Adding certificate for domain(s) %s", certKey)
|
|
||||||
|
|
||||||
certs[storeName][certKey] = &Certificate{
|
|
||||||
Certificate: &tlsCert,
|
|
||||||
SANs: SANs,
|
|
||||||
OCSPServer: parsedCert.OCSPServer,
|
|
||||||
OCSP: OCSPConfig{
|
|
||||||
DisableStapling: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOCSPForCert(certificate *Certificate, issuedCertificate *x509.Certificate, issuerCertificate *x509.Certificate) ([]byte, *ocsp.Response, error) {
|
|
||||||
if len(certificate.OCSPServer) == 0 {
|
|
||||||
return nil, nil, fmt.Errorf("no OCSP server specified in certificate")
|
|
||||||
}
|
|
||||||
|
|
||||||
respURL := certificate.OCSPServer[0]
|
|
||||||
ocspReq, err := ocsp.CreateRequest(issuedCertificate, issuerCertificate, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("creating OCSP request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := bytes.NewReader(ocspReq)
|
|
||||||
req, err := http.Post(respURL, "application/ocsp-request", reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("making OCSP request: %w", err)
|
|
||||||
}
|
|
||||||
defer req.Body.Close()
|
|
||||||
|
|
||||||
ocspResBytes, err := ioutil.ReadAll(io.LimitReader(req.Body, 1024*1024))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("reading OCSP response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCertificate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("parsing OCSP response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ocspResBytes, ocspRes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StapleOCSP populates the ocsp response of the certificate if needed and not disabled by configuration.
|
|
||||||
func (c *Certificate) StapleOCSP() error {
|
|
||||||
if c.OCSP.DisableStapling {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ocspResponse := c.OCSPResponse
|
|
||||||
if ocspResponse != nil && time.Now().Before(ocspResponse.ThisUpdate.Add(ocspResponse.NextUpdate.Sub(ocspResponse.ThisUpdate)/2)) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
leaf, _ := x509.ParseCertificate(c.Certificate.Certificate[0])
|
|
||||||
var issuerCertificate *x509.Certificate
|
|
||||||
if len(c.Certificate.Certificate) == 1 {
|
|
||||||
issuerCertificate = leaf
|
|
||||||
} else {
|
|
||||||
ic, err := x509.ParseCertificate(c.Certificate.Certificate[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot parse issuer certificate for %v: %w", c.SANs, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
issuerCertificate = ic
|
|
||||||
}
|
|
||||||
|
|
||||||
ocspBytes, ocspResp, ocspErr := getOCSPForCert(c, leaf, issuerCertificate)
|
|
||||||
if ocspErr != nil {
|
|
||||||
return fmt.Errorf("no OCSP stapling for %v: %w", c.SANs, ocspErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithoutContext().Debugf("ocsp response: %v", ocspResp)
|
|
||||||
if ocspResp.Status == ocsp.Good {
|
|
||||||
if ocspResp.NextUpdate.After(leaf.NotAfter) {
|
|
||||||
return fmt.Errorf("invalid: OCSP response for %v valid after certificate expiration (%s)", c.SANs, leaf.NotAfter.Sub(ocspResp.NextUpdate))
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Certificate.OCSPStaple = ocspBytes
|
|
||||||
c.OCSPResponse = ocspResp
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCertificate returns a tls.Certificate matching the configured CertFile and KeyFile.
|
// GetCertificate returns a tls.Certificate matching the configured CertFile and KeyFile.
|
||||||
func (c *Certificate) GetCertificate() (tls.Certificate, error) {
|
func (c *Certificate) GetCertificate() (tls.Certificate, error) {
|
||||||
certContent, err := c.CertFile.Read()
|
certContent, err := c.CertFile.Read()
|
||||||
|
@ -304,45 +153,6 @@ func (c *Certificate) GetTruncatedCertificateName() string {
|
||||||
return certName
|
return certName
|
||||||
}
|
}
|
||||||
|
|
||||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
|
||||||
// The String method's output will be used in diagnostics.
|
|
||||||
func (c *Certificates) String() string {
|
|
||||||
if len(*c) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
var result []string
|
|
||||||
for _, certificate := range *c {
|
|
||||||
result = append(result, certificate.CertFile.String()+","+certificate.KeyFile.String())
|
|
||||||
}
|
|
||||||
return strings.Join(result, ";")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
|
||||||
// Set's argument is a string to be parsed to set the flag.
|
|
||||||
// It's a comma-separated list, so we split it.
|
|
||||||
func (c *Certificates) Set(value string) error {
|
|
||||||
certificates := strings.Split(value, ";")
|
|
||||||
for _, certificate := range certificates {
|
|
||||||
files := strings.Split(certificate, ",")
|
|
||||||
if len(files) != 2 {
|
|
||||||
return fmt.Errorf("bad certificates format: %s", value)
|
|
||||||
}
|
|
||||||
*c = append(*c, Certificate{
|
|
||||||
CertFile: FileOrContent(files[0]),
|
|
||||||
KeyFile: FileOrContent(files[1]),
|
|
||||||
OCSP: OCSPConfig{
|
|
||||||
DisableStapling: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type is type of the struct.
|
|
||||||
func (c *Certificates) Type() string {
|
|
||||||
return "certificates"
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyPeerCertificate verifies the chain certificates and their URI.
|
// VerifyPeerCertificate verifies the chain certificates and their URI.
|
||||||
func VerifyPeerCertificate(uri string, cfg *tls.Config, rawCerts [][]byte) error {
|
func VerifyPeerCertificate(uri string, cfg *tls.Config, rawCerts [][]byte) error {
|
||||||
// TODO: Refactor to avoid useless verifyChain (ex: when insecureskipverify is false)
|
// TODO: Refactor to avoid useless verifyChain (ex: when insecureskipverify is false)
|
||||||
|
|
|
@ -63,7 +63,7 @@ func (c CertificateStore) GetAllDomains() []string {
|
||||||
|
|
||||||
// Get dynamic certificates
|
// Get dynamic certificates
|
||||||
if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil {
|
if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil {
|
||||||
for domain := range c.DynamicCerts.Get().(map[string]*Certificate) {
|
for domain := range c.DynamicCerts.Get().(map[string]*Cert) {
|
||||||
allDomains = append(allDomains, domain)
|
allDomains = append(allDomains, domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ func (c CertificateStore) GetAllDomains() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBestCertificate returns the best match certificate, and caches the response.
|
// GetBestCertificate returns the best match certificate, and caches the response.
|
||||||
func (c *CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) *Certificate {
|
func (c *CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) *Cert {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -87,12 +87,12 @@ func (c *CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cert, ok := c.CertCache.Get(serverName); ok {
|
if cert, ok := c.CertCache.Get(serverName); ok {
|
||||||
return cert.(*Certificate)
|
return cert.(*Cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
matchedCerts := map[string]*Certificate{}
|
matchedCerts := map[string]*Cert{}
|
||||||
if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil {
|
if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil {
|
||||||
for domains, cert := range c.DynamicCerts.Get().(map[string]*Certificate) {
|
for domains, cert := range c.DynamicCerts.Get().(map[string]*Cert) {
|
||||||
for _, certDomain := range strings.Split(domains, ",") {
|
for _, certDomain := range strings.Split(domains, ",") {
|
||||||
if matchDomain(serverName, certDomain) {
|
if matchDomain(serverName, certDomain) {
|
||||||
matchedCerts[certDomain] = cert
|
matchedCerts[certDomain] = cert
|
||||||
|
|
|
@ -59,7 +59,7 @@ func TestGetBestCertificate(t *testing.T) {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
dynamicMap := map[string]*Certificate{}
|
dynamicMap := map[string]*Cert{}
|
||||||
|
|
||||||
if test.dynamicCert != "" {
|
if test.dynamicCert != "" {
|
||||||
cert, err := loadTestCert(test.dynamicCert, test.uppercase)
|
cert, err := loadTestCert(test.dynamicCert, test.uppercase)
|
||||||
|
@ -72,7 +72,7 @@ func TestGetBestCertificate(t *testing.T) {
|
||||||
CertCache: cache.New(1*time.Hour, 10*time.Minute),
|
CertCache: cache.New(1*time.Hour, 10*time.Minute),
|
||||||
}
|
}
|
||||||
|
|
||||||
var expected *Certificate
|
var expected *Cert
|
||||||
if test.expectedCert != "" {
|
if test.expectedCert != "" {
|
||||||
cert, err := loadTestCert(test.expectedCert, test.uppercase)
|
cert, err := loadTestCert(test.expectedCert, test.uppercase)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -89,7 +89,7 @@ func TestGetBestCertificate(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadTestCert(certName string, uppercase bool) (*Certificate, error) {
|
func loadTestCert(certName string, uppercase bool) (*Cert, error) {
|
||||||
replacement := "wildcard"
|
replacement := "wildcard"
|
||||||
if uppercase {
|
if uppercase {
|
||||||
replacement = "uppercase_wildcard"
|
replacement = "uppercase_wildcard"
|
||||||
|
@ -103,8 +103,7 @@ func loadTestCert(certName string, uppercase bool) (*Certificate, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Certificate{
|
return &Cert{
|
||||||
Certificate: &staticCert,
|
Certificate: &staticCert,
|
||||||
SANs: []string{},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
217
pkg/tls/tls_certificate.go
Normal file
217
pkg/tls/tls_certificate.go
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/tls/generate"
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cert holds runtime data for runtime TLS certificate handling.
|
||||||
|
type Cert struct {
|
||||||
|
config *Certificate
|
||||||
|
Certificate *tls.Certificate
|
||||||
|
OCSPServer []string
|
||||||
|
OCSPResponse *ocsp.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certs defines traefik Certs type
|
||||||
|
// Certs and Keys could be either a file path, or the file content itself.
|
||||||
|
type Certs []Cert
|
||||||
|
|
||||||
|
// CreateTLSConfig creates a TLS config from Certificate structures.
|
||||||
|
|
||||||
|
// AppendCertificate appends a Cert to a certificates map keyed by entrypoint.
|
||||||
|
func (c *Cert) AppendCertificate(certs map[string]map[string]*Cert, storeName string) error {
|
||||||
|
certContent, err := c.config.CertFile.Read()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read CertFile : %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyContent, err := c.config.KeyFile.Read()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read KeyFile : %w", err)
|
||||||
|
}
|
||||||
|
tlsCert, err := tls.X509KeyPair(certContent, keyContent)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to generate TLS certificate : %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedCert, _ := x509.ParseCertificate(tlsCert.Certificate[0])
|
||||||
|
|
||||||
|
var SANs []string
|
||||||
|
if parsedCert.Subject.CommonName != "" {
|
||||||
|
SANs = append(SANs, strings.ToLower(parsedCert.Subject.CommonName))
|
||||||
|
}
|
||||||
|
if parsedCert.DNSNames != nil {
|
||||||
|
sort.Strings(parsedCert.DNSNames)
|
||||||
|
for _, dnsName := range parsedCert.DNSNames {
|
||||||
|
if dnsName != parsedCert.Subject.CommonName {
|
||||||
|
SANs = append(SANs, strings.ToLower(dnsName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if parsedCert.IPAddresses != nil {
|
||||||
|
for _, ip := range parsedCert.IPAddresses {
|
||||||
|
if ip.String() != parsedCert.Subject.CommonName {
|
||||||
|
SANs = append(SANs, strings.ToLower(ip.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
certKey := strings.Join(SANs, ",")
|
||||||
|
|
||||||
|
certExists := false
|
||||||
|
if certs[storeName] == nil {
|
||||||
|
certs[storeName] = make(map[string]*Cert)
|
||||||
|
} else {
|
||||||
|
for domains := range certs[storeName] {
|
||||||
|
if domains == certKey {
|
||||||
|
certExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if certExists {
|
||||||
|
log.Debugf("Skipping addition of certificate for domain(s) %q, to EntryPoint %s, as it already exists for this Entrypoint.", certKey, storeName)
|
||||||
|
} else {
|
||||||
|
log.Debugf("Adding certificate for domain(s) %s", certKey)
|
||||||
|
|
||||||
|
certs[storeName][certKey] = &Cert{
|
||||||
|
Certificate: &tlsCert,
|
||||||
|
OCSPServer: parsedCert.OCSPServer,
|
||||||
|
config: &Certificate{
|
||||||
|
SANs: SANs,
|
||||||
|
OCSP: OCSPConfig{
|
||||||
|
DisableStapling: c.config.OCSP.DisableStapling,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOCSPForCert(certificate *Cert, issuedCertificate *x509.Certificate, issuerCertificate *x509.Certificate) ([]byte, *ocsp.Response, error) {
|
||||||
|
if len(certificate.OCSPServer) == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("no OCSP server specified in certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
respURL := certificate.OCSPServer[0]
|
||||||
|
ocspReq, err := ocsp.CreateRequest(issuedCertificate, issuerCertificate, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("creating OCSP request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := bytes.NewReader(ocspReq)
|
||||||
|
req, err := http.Post(respURL, "application/ocsp-request", reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("making OCSP request: %w", err)
|
||||||
|
}
|
||||||
|
defer req.Body.Close()
|
||||||
|
|
||||||
|
ocspResBytes, err := ioutil.ReadAll(io.LimitReader(req.Body, 1024*1024))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("reading OCSP response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCertificate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("parsing OCSP response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ocspResBytes, ocspRes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StapleOCSP populates the ocsp response of the certificate if needed and not disabled by configuration.
|
||||||
|
func (c *Cert) StapleOCSP() error {
|
||||||
|
if c.config.OCSP.DisableStapling {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ocspResponse := c.OCSPResponse
|
||||||
|
if ocspResponse != nil && time.Now().Before(ocspResponse.ThisUpdate.Add(ocspResponse.NextUpdate.Sub(ocspResponse.ThisUpdate)/2)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf, _ := x509.ParseCertificate(c.Certificate.Certificate[0])
|
||||||
|
var issuerCertificate *x509.Certificate
|
||||||
|
if len(c.Certificate.Certificate) == 1 {
|
||||||
|
issuerCertificate = leaf
|
||||||
|
} else {
|
||||||
|
ic, err := x509.ParseCertificate(c.Certificate.Certificate[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot parse issuer certificate for %v: %w", c.config.SANs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
issuerCertificate = ic
|
||||||
|
}
|
||||||
|
|
||||||
|
ocspBytes, ocspResp, ocspErr := getOCSPForCert(c, leaf, issuerCertificate)
|
||||||
|
if ocspErr != nil {
|
||||||
|
return fmt.Errorf("no OCSP stapling for %v: %w", c.config.SANs, ocspErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithoutContext().Debugf("ocsp response: %v", ocspResp)
|
||||||
|
if ocspResp.Status == ocsp.Good {
|
||||||
|
if ocspResp.NextUpdate.After(leaf.NotAfter) {
|
||||||
|
return fmt.Errorf("invalid: OCSP response for %v valid after certificate expiration (%s)", c.config.SANs, leaf.NotAfter.Sub(ocspResp.NextUpdate))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Certificate.OCSPStaple = ocspBytes
|
||||||
|
c.OCSPResponse = ocspResp
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||||
|
// The String method's output will be used in diagnostics.
|
||||||
|
func (c *Certs) String() string {
|
||||||
|
if len(*c) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var result []string
|
||||||
|
for _, certificate := range *c {
|
||||||
|
result = append(result, certificate.config.CertFile.String()+","+certificate.config.KeyFile.String())
|
||||||
|
}
|
||||||
|
return strings.Join(result, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||||
|
// Set's argument is a string to be parsed to set the flag.
|
||||||
|
// It's a comma-separated list, so we split it.
|
||||||
|
func (c *Certs) Set(value string) error {
|
||||||
|
TLSCertificates := strings.Split(value, ";")
|
||||||
|
for _, certificate := range TLSCertificates {
|
||||||
|
files := strings.Split(certificate, ",")
|
||||||
|
if len(files) != 2 {
|
||||||
|
return fmt.Errorf("bad Certs format: %s", value)
|
||||||
|
}
|
||||||
|
*c = append(*c, Cert{
|
||||||
|
config: &Certificate{
|
||||||
|
CertFile: FileOrContent(files[0]),
|
||||||
|
KeyFile: FileOrContent(files[1]),
|
||||||
|
OCSP: OCSPConfig{
|
||||||
|
DisableStapling: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type is type of the struct.
|
||||||
|
func (c *Certs) Type() string {
|
||||||
|
return "Certs"
|
||||||
|
}
|
|
@ -83,7 +83,7 @@ func (m *Manager) UpdateConfigs(ctx context.Context, stores map[string]Store, co
|
||||||
m.storesConfig[tlsalpn01.ACMETLS1Protocol] = Store{}
|
m.storesConfig[tlsalpn01.ACMETLS1Protocol] = Store{}
|
||||||
}
|
}
|
||||||
|
|
||||||
storesCertificates := make(map[string]map[string]*tls.Certificate)
|
storesCertificates := make(map[string]map[string]*Cert)
|
||||||
for _, conf := range certs {
|
for _, conf := range certs {
|
||||||
if len(conf.Stores) == 0 {
|
if len(conf.Stores) == 0 {
|
||||||
if log.GetLevel() >= logrus.DebugLevel {
|
if log.GetLevel() >= logrus.DebugLevel {
|
||||||
|
@ -100,7 +100,8 @@ func (m *Manager) UpdateConfigs(ctx context.Context, stores map[string]Store, co
|
||||||
m.storesConfig[store] = Store{}
|
m.storesConfig[store] = Store{}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := conf.Certificate.AppendCertificate(storesCertificates, store)
|
cert := Cert{config: &conf.Certificate}
|
||||||
|
err := cert.AppendCertificate(storesCertificates, store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromContext(ctxStore).Errorf("Unable to append certificate %s to store: %v", conf.Certificate.GetTruncatedCertificateName(), err)
|
log.FromContext(ctxStore).Errorf("Unable to append certificate %s to store: %v", conf.Certificate.GetTruncatedCertificateName(), err)
|
||||||
}
|
}
|
||||||
|
@ -241,7 +242,7 @@ func (m *Manager) GetCertificates() []*x509.Certificate {
|
||||||
// We iterate over all the certificates.
|
// We iterate over all the certificates.
|
||||||
for _, store := range m.stores {
|
for _, store := range m.stores {
|
||||||
if store.DynamicCerts != nil && store.DynamicCerts.Get() != nil {
|
if store.DynamicCerts != nil && store.DynamicCerts.Get() != nil {
|
||||||
for _, cert := range store.DynamicCerts.Get().(map[string]*Certificate) {
|
for _, cert := range store.DynamicCerts.Get().(map[string]*Cert) {
|
||||||
x509Cert, err := x509.ParseCertificate(cert.Certificate.Certificate[0])
|
x509Cert, err := x509.ParseCertificate(cert.Certificate.Certificate[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -79,7 +79,7 @@ func TestTLSInStore(t *testing.T) {
|
||||||
tlsManager := NewManager()
|
tlsManager := NewManager()
|
||||||
tlsManager.UpdateConfigs(context.Background(), nil, nil, dynamicConfigs)
|
tlsManager.UpdateConfigs(context.Background(), nil, nil, dynamicConfigs)
|
||||||
|
|
||||||
certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*Certificate)
|
certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*Cert)
|
||||||
if len(certs) == 0 {
|
if len(certs) == 0 {
|
||||||
t.Fatal("got error: default store must have TLS certificates.")
|
t.Fatal("got error: default store must have TLS certificates.")
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ func TestTLSInvalidStore(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, nil, dynamicConfigs)
|
}, nil, dynamicConfigs)
|
||||||
|
|
||||||
certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*Certificate)
|
certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*Cert)
|
||||||
if len(certs) == 0 {
|
if len(certs) == 0 {
|
||||||
t.Fatal("got error: default store must have TLS certificates.")
|
t.Fatal("got error: default store must have TLS certificates.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,28 @@ func (in *CertAndStores) DeepCopy() *CertAndStores {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Certificate) DeepCopyInto(out *Certificate) {
|
||||||
|
*out = *in
|
||||||
|
out.OCSP = in.OCSP
|
||||||
|
if in.SANs != nil {
|
||||||
|
in, out := &in.SANs, &out.SANs
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Certificate.
|
||||||
|
func (in *Certificate) DeepCopy() *Certificate {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Certificate)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *ClientAuth) DeepCopyInto(out *ClientAuth) {
|
func (in *ClientAuth) DeepCopyInto(out *ClientAuth) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
@ -96,6 +118,20 @@ func (in *GeneratedCert) DeepCopy() *GeneratedCert {
|
||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
func (in *OCSPConfig) DeepCopyInto(out *OCSPConfig) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCSPConfig.
|
||||||
|
func (in *OCSPConfig) DeepCopy() *OCSPConfig {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(OCSPConfig)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Options) DeepCopyInto(out *Options) {
|
func (in *Options) DeepCopyInto(out *Options) {
|
||||||
|
|
Loading…
Reference in a new issue