Compare commits

...

12 Commits

Author SHA1 Message Date
Liyi Meng
e425269f0e Use chinese crypto 2022-10-08 22:38:44 +02:00
Caleb Bron
401fafb7e6 Merge pull request #64 from w13915984028/fix63
fix63 use sleep instead of force scheduling
2022-07-28 13:43:07 -07:00
Jian Wang
bad953b9f0 fix63 use sleep instead of force scheduling 2022-07-27 08:59:22 +02:00
Brad Davidson
8ebd77f8a4 Raise default ExpirationDaysCheck to 90 and extend into cert factory
Most of our products actually renew at 90 days, so make that the default.

Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
2022-07-21 14:08:16 -07:00
Brad Davidson
fdf983a935 Don't merge expired certs over the top of an unexpired cert
Fixes an issue where an expired Kubernetes secret would replace the renewed locally-cached cert after cluster startup.

Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
2022-07-21 14:08:16 -07:00
Flavio Grossi
7b5997cee9 always use CATTLE_NEW_SIGNED_CERT_EXPIRATION_DAYS when generating a certificate 2022-07-20 12:07:31 -07:00
Lucas Ramage
42d72c2ef2 Merge pull request #56 from rancher/fossa
Implement drone-plugin-fossa
2022-07-01 10:58:54 -04:00
Brad Davidson
d2b7e2aaa6 We support IPv6 now, don't skip adding IPv6 address SANs
Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
2022-05-20 12:21:30 -07:00
Brad Davidson
a30741bb53 Send complete certificate chain, not just the leaf cert
Also, print a warning when signing may change the issuer.

Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
2022-05-20 12:21:30 -07:00
Brad Davidson
4df376813d Improve log messages and warn if no cert is available
Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
2022-05-20 12:21:30 -07:00
Brad Davidson
9b92d13bcb Fix initial secret not being written to Kubernetes
Updates to the secret that occurred before the controller was done
syncing were not being written to Kubernetes. Subsequent updates to the
secret would eventually get it written, but Rancher requires that the
cert be written immediately. This was probably an unnecessary
optimization anyway, so back it out in favor of just checking to see if
the secrets controller is available.

Also fixed improper handling of multiple goroutines attempting to create
the Kubernetes secret at the same time; this was also handled eventually
but caused an unnecessary round of extra writes to the secret.

Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
2022-05-20 12:21:30 -07:00
Lucas Ramage
5e81b14c1f Implement drone-plugin-fossa 2022-03-31 16:28:22 -04:00
10 changed files with 228 additions and 64 deletions

15
.drone.yml Normal file
View File

@@ -0,0 +1,15 @@
---
kind: pipeline
name: fossa
steps:
- name: fossa
image: rancher/drone-fossa:latest
settings:
api_key:
from_secret: FOSSA_API_KEY
when:
instance:
- drone-publish.rancher.io

View File

@@ -33,7 +33,9 @@ import (
"math"
"math/big"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"time"
@@ -64,11 +66,16 @@ type AltNames struct {
IPs []net.IP
}
// NewPrivateKey creates an RSA private key
func NewPrivateKey() (*rsa.PrivateKey, error) {
// NewRSAPrivateKey creates an RSA private key
func NewRSAPrivateKey() (*rsa.PrivateKey, error) {
return rsa.GenerateKey(cryptorand.Reader, rsaKeySize)
}
// NewPrivateKey creates an RSA private key
func NewPrivateKey() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(elliptic.P256Sm2(), cryptorand.Reader)
}
// NewSelfSignedCACert creates a CA certificate
func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, error) {
now := time.Now()
@@ -109,11 +116,18 @@ func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKe
if len(cfg.Usages) == 0 {
return nil, errors.New("must specify at least one ExtKeyUsage")
}
var expiresAt time.Duration
expiresAt := duration365d
if cfg.ExpiresAt > 0 {
expiresAt = time.Duration(cfg.ExpiresAt)
} else {
expiresAt = duration365d
envExpirationDays := os.Getenv("CATTLE_NEW_SIGNED_CERT_EXPIRATION_DAYS")
if envExpirationDays != "" {
if envExpirationDaysInt, err := strconv.Atoi(envExpirationDays); err != nil {
logrus.Infof("[NewSignedCert] expiration days from ENV (%s) could not be converted to int (falling back to default value: %d)", envExpirationDays, expiresAt)
} else {
expiresAt = time.Hour * 24 * time.Duration(envExpirationDaysInt)
}
}
}
certTmpl := x509.Certificate{
@@ -144,7 +158,7 @@ func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKe
// MakeEllipticPrivateKeyPEM creates an ECDSA private key
func MakeEllipticPrivateKeyPEM() ([]byte, error) {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader)
privateKey, err := ecdsa.GenerateKey(elliptic.P256Sm2(), cryptorand.Reader)
if err != nil {
return nil, err
}
@@ -195,7 +209,7 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
maxAge = 100 * time.Hour * 24 * 365 // 100 years fixtures
}
caKey, err := rsa.GenerateKey(cryptorand.Reader, 2048)
caKey, err := NewPrivateKey()
if err != nil {
return nil, nil, err
}
@@ -223,7 +237,7 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
return nil, nil, err
}
priv, err := rsa.GenerateKey(cryptorand.Reader, 2048)
priv, err := NewPrivateKey()
if err != nil {
return nil, nil, err
}
@@ -266,7 +280,12 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
// Generate key
keyBuffer := bytes.Buffer{}
if err := pem.Encode(&keyBuffer, &pem.Block{Type: RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
privBuf, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, nil, err
}
if err := pem.Encode(&keyBuffer, &pem.Block{Type: ECPrivateKeyBlockType, Bytes: privBuf}); err != nil {
return nil, nil, err
}

39
cert/cert_test.go Normal file
View File

@@ -0,0 +1,39 @@
package cert
import (
"crypto/ecdsa"
"fmt"
"os"
"testing"
)
func TestCreateAndReadCert(t *testing.T) {
kFile := "service.key"
defer os.Remove(kFile)
key, err := NewPrivateKey()
if err != nil {
t.Errorf("failed to create private key: %v", err)
}
if err := WriteKey(kFile, EncodePrivateKeyPEM(key)); err != nil {
t.Errorf("failed to encode private key to pem: %v", err)
}
keyR, err := PrivateKeyFromFile(kFile)
if err != nil {
t.Errorf("failed to load private key from file: %v", err)
}
switch k := keyR.(type) {
case *ecdsa.PrivateKey:
fmt.Println("loaded back ecdsa private key")
default:
t.Errorf("load back a wrong private key %v", k)
}
buf := EncodePrivateKeyPEM(key)
_, err = ParsePrivateKeyPEM(buf)
if err != nil {
t.Errorf("failed to parse private key from pem: %v", err)
}
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
package cert
import (
"crypto/ecdsa"
cryptorand "crypto/rand"
"crypto/rsa"
"crypto/x509"
@@ -60,8 +61,11 @@ func MakeCSRFromTemplate(privateKey interface{}, template *x509.CertificateReque
func sigType(privateKey interface{}) x509.SignatureAlgorithm {
// Customize the signature for RSA keys, depending on the key size
if privateKey, ok := privateKey.(*rsa.PrivateKey); ok {
keySize := privateKey.N.BitLen()
switch privK := privateKey.(type) {
case *ecdsa.PrivateKey:
return x509.ECDSAWithSHA256
case *rsa.PrivateKey:
keySize := privK.N.BitLen()
switch {
case keySize >= 4096:
return x509.SHA512WithRSA

View File

@@ -41,7 +41,7 @@ const (
)
// EncodePublicKeyPEM returns PEM-encoded public data
func EncodePublicKeyPEM(key *rsa.PublicKey) ([]byte, error) {
func EncodePublicKeyPEM(key interface{}) ([]byte, error) {
der, err := x509.MarshalPKIXPublicKey(key)
if err != nil {
return []byte{}, err
@@ -54,12 +54,25 @@ func EncodePublicKeyPEM(key *rsa.PublicKey) ([]byte, error) {
}
// EncodePrivateKeyPEM returns PEM-encoded private key data
func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte {
block := pem.Block{
Type: RSAPrivateKeyBlockType,
Bytes: x509.MarshalPKCS1PrivateKey(key),
func EncodePrivateKeyPEM(key interface{}) []byte {
var block *pem.Block
switch privKey := key.(type) {
case *ecdsa.PrivateKey:
derBytes, _ := x509.MarshalECPrivateKey(privKey)
block = &pem.Block{
Type: ECPrivateKeyBlockType,
Bytes: derBytes,
}
case *rsa.PrivateKey:
block = &pem.Block{
Type: RSAPrivateKeyBlockType,
Bytes: x509.MarshalPKCS1PrivateKey(privKey),
}
}
return pem.EncodeToMemory(&block)
if block != nil {
return pem.EncodeToMemory(block)
}
return []byte{}
}
// EncodeCertPEM returns PEM-endcoded certificate data

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"os"
"time"
"github.com/rancher/dynamiclistener/cert"
)
@@ -16,7 +17,7 @@ func GenCA() (*x509.Certificate, crypto.Signer, error) {
return nil, nil, err
}
caCert, err := NewSelfSignedCACert(caKey, "dynamiclistener-ca", "dynamiclistener-org")
caCert, err := NewSelfSignedCACert(caKey, fmt.Sprintf("dynamiclistener-ca@%d", time.Now().Unix()), "dynamiclistener-org")
if err != nil {
return nil, nil, err
}

View File

@@ -15,6 +15,7 @@ import (
"regexp"
"sort"
"strings"
"time"
"github.com/rancher/dynamiclistener/cert"
"github.com/sirupsen/logrus"
@@ -32,11 +33,12 @@ var (
)
type TLS struct {
CACert *x509.Certificate
CAKey crypto.Signer
CN string
Organization []string
FilterCN func(...string) []string
CACert *x509.Certificate
CAKey crypto.Signer
CN string
Organization []string
FilterCN func(...string) []string
ExpirationDaysCheck int
}
func cns(secret *v1.Secret) (cns []string) {
@@ -72,7 +74,8 @@ func collectCNs(secret *v1.Secret) (domains []string, ips []net.IP, err error) {
// Merge combines the SAN lists from the target and additional Secrets, and
// returns a potentially modified Secret, along with a bool indicating if the
// returned Secret is not the same as the target Secret.
// returned Secret is not the same as the target Secret. Secrets with expired
// certificates will never be returned.
//
// If the merge would not add any CNs to the additional Secret, the additional
// Secret is returned, to allow for certificate rotation/regeneration.
@@ -93,17 +96,17 @@ func (t *TLS) Merge(target, additional *v1.Secret) (*v1.Secret, bool, error) {
// if the additional secret already has all the CNs, use it in preference to the
// current one. This behavior is required to allow for renewal or regeneration.
if !NeedsUpdate(0, additional, mergedCNs...) {
if !NeedsUpdate(0, additional, mergedCNs...) && !t.IsExpired(additional) {
return additional, true, nil
}
// if the target secret already has all the CNs, continue using it. The additional
// cert had only a subset of the current CNs, so nothing needs to be added.
if !NeedsUpdate(0, target, mergedCNs...) {
if !NeedsUpdate(0, target, mergedCNs...) && !t.IsExpired(target) {
return target, false, nil
}
// neither cert currently has all the necessary CNs; generate a new one.
// neither cert currently has all the necessary CNs or is unexpired; generate a new one.
return t.generateCert(target, mergedCNs...)
}
@@ -154,6 +157,10 @@ func (t *TLS) generateCert(secret *v1.Secret, cn ...string) (*v1.Secret, bool, e
secret = &v1.Secret{}
}
if err := t.Verify(secret); err != nil {
logrus.Warnf("unable to verify existing certificate: %v - signing operation may change certificate issuer", err)
}
secret = populateCN(secret, cn...)
privateKey, err := getPrivateKey(secret)
@@ -171,7 +178,7 @@ func (t *TLS) generateCert(secret *v1.Secret, cn ...string) (*v1.Secret, bool, e
return nil, false, err
}
certBytes, keyBytes, err := Marshal(newCert, privateKey)
keyBytes, certBytes, err := MarshalChain(privateKey, newCert, t.CACert)
if err != nil {
return nil, false, err
}
@@ -187,6 +194,44 @@ func (t *TLS) generateCert(secret *v1.Secret, cn ...string) (*v1.Secret, bool, e
return secret, true, nil
}
func (t *TLS) IsExpired(secret *v1.Secret) bool {
certsPem := secret.Data[v1.TLSCertKey]
if len(certsPem) == 0 {
return false
}
certificates, err := cert.ParseCertsPEM(certsPem)
if err != nil || len(certificates) == 0 {
return false
}
expirationDays := time.Duration(t.ExpirationDaysCheck) * time.Hour * 24
return time.Now().Add(expirationDays).After(certificates[0].NotAfter)
}
func (t *TLS) Verify(secret *v1.Secret) error {
certsPem := secret.Data[v1.TLSCertKey]
if len(certsPem) == 0 {
return nil
}
certificates, err := cert.ParseCertsPEM(certsPem)
if err != nil || len(certificates) == 0 {
return err
}
verifyOpts := x509.VerifyOptions{
Roots: x509.NewCertPool(),
KeyUsages: []x509.ExtKeyUsage{
x509.ExtKeyUsageAny,
},
}
verifyOpts.Roots.AddCert(t.CACert)
_, err = certificates[0].Verify(verifyOpts)
return err
}
func (t *TLS) newCert(domains []string, ips []net.IP, privateKey crypto.Signer) (*x509.Certificate, error) {
return NewSignedCert(privateKey, t.CACert, t.CAKey, t.CN, t.Organization, domains, ips)
}
@@ -250,14 +295,33 @@ func getPrivateKey(secret *v1.Secret) (crypto.Signer, error) {
return NewPrivateKey()
}
// MarshalChain returns given key and certificates as byte slices.
func MarshalChain(privateKey crypto.Signer, certs ...*x509.Certificate) (keyBytes, certChainBytes []byte, err error) {
keyBytes, err = cert.MarshalPrivateKeyToPEM(privateKey)
if err != nil {
return nil, nil, err
}
for _, cert := range certs {
if cert != nil {
certBlock := pem.Block{
Type: CertificateBlockType,
Bytes: cert.Raw,
}
certChainBytes = append(certChainBytes, pem.EncodeToMemory(&certBlock)...)
}
}
return keyBytes, certChainBytes, nil
}
// Marshal returns the given cert and key as byte slices.
func Marshal(x509Cert *x509.Certificate, privateKey crypto.Signer) ([]byte, []byte, error) {
func Marshal(x509Cert *x509.Certificate, privateKey crypto.Signer) (certBytes, keyBytes []byte, err error) {
certBlock := pem.Block{
Type: CertificateBlockType,
Bytes: x509Cert.Raw,
}
keyBytes, err := cert.MarshalPrivateKeyToPEM(privateKey)
keyBytes, err = cert.MarshalPrivateKeyToPEM(privateKey)
if err != nil {
return nil, nil, err
}

View File

@@ -7,7 +7,6 @@ import (
"crypto/x509"
"net"
"net/http"
"runtime"
"strings"
"sync"
"time"
@@ -45,14 +44,18 @@ func NewListener(l net.Listener, storage TLSStorage, caCert *x509.Certificate, c
if config.TLSConfig == nil {
config.TLSConfig = &tls.Config{}
}
if config.ExpirationDaysCheck == 0 {
config.ExpirationDaysCheck = 90
}
dynamicListener := &listener{
factory: &factory.TLS{
CACert: caCert,
CAKey: caKey,
CN: config.CN,
Organization: config.Organization,
FilterCN: allowDefaultSANs(config.SANs, config.FilterCN),
CACert: caCert,
CAKey: caKey,
CN: config.CN,
Organization: config.Organization,
FilterCN: allowDefaultSANs(config.SANs, config.FilterCN),
ExpirationDaysCheck: config.ExpirationDaysCheck,
},
Listener: l,
storage: &nonNil{storage: storage},
@@ -82,10 +85,6 @@ func NewListener(l net.Listener, storage TLSStorage, caCert *x509.Certificate, c
}
}
if config.ExpirationDaysCheck == 0 {
config.ExpirationDaysCheck = 30
}
tlsListener := tls.NewListener(dynamicListener.WrapExpiration(config.ExpirationDaysCheck), dynamicListener.tlsConfig)
return tlsListener, dynamicListener.cacheHandler(), nil
@@ -163,15 +162,15 @@ type listener struct {
func (l *listener) WrapExpiration(days int) net.Listener {
ctx, cancel := context.WithCancel(context.Background())
go func() {
// busy-wait for certificate preload to complete
// loop on short sleeps until certificate preload completes
for l.cert == nil {
runtime.Gosched()
time.Sleep(time.Millisecond)
}
for {
wait := 6 * time.Hour
if err := l.checkExpiration(days); err != nil {
logrus.Errorf("failed to check and renew dynamic cert: %v", err)
logrus.Errorf("dynamiclistener %s: failed to check and renew dynamic cert: %v", l.Addr(), err)
// Don't go into short retry loop if we're using a static (user-provided) cert.
// We will still check and print an error every six hours until the user updates the secret with
// a cert that is not about to expire. Hopefully this will prompt them to take action.
@@ -263,12 +262,18 @@ func (l *listener) Accept() (net.Conn, error) {
l.init.Do(func() {
if len(l.sans) > 0 {
if err := l.updateCert(l.sans...); err != nil {
logrus.Errorf("failed to update cert with configured SANs: %v", err)
logrus.Errorf("dynamiclistener %s: failed to update cert with configured SANs: %v", l.Addr(), err)
return
}
if _, err := l.loadCert(nil); err != nil {
logrus.Errorf("failed to preload certificate: %v", err)
}
}
if cert, err := l.loadCert(nil); err != nil {
logrus.Errorf("dynamiclistener %s: failed to preload certificate: %v", l.Addr(), err)
} else if cert == nil {
// This should only occur on the first startup when no SANs are configured in the listener config, in which
// case no certificate can be created, as dynamiclistener will not create certificates until at least one IP
// or DNS SAN is set. It will also occur when using the Kubernetes storage without a local File cache.
// For reliable serving of requests, callers should configure a local cache and/or a default set of SANs.
logrus.Warnf("dynamiclistener %s: no cached certificate available for preload - deferring certificate load until storage initialization or first client request", l.Addr())
}
})
@@ -284,14 +289,12 @@ func (l *listener) Accept() (net.Conn, error) {
host, _, err := net.SplitHostPort(addr.String())
if err != nil {
logrus.Errorf("failed to parse network %s: %v", addr.Network(), err)
logrus.Errorf("dynamiclistener %s: failed to parse connection local address %s: %v", l.Addr(), addr, err)
return conn, nil
}
if !strings.Contains(host, ":") {
if err := l.updateCert(host); err != nil {
logrus.Errorf("failed to update cert with listener address: %v", err)
}
if err := l.updateCert(host); err != nil {
logrus.Errorf("dynamiclistener %s: failed to update cert with connection local address: %v", l.Addr(), err)
}
if l.conns != nil {
@@ -338,7 +341,7 @@ func (l *listener) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
newConn := hello.Conn
if hello.ServerName != "" {
if err := l.updateCert(hello.ServerName); err != nil {
logrus.Errorf("failed to update cert with TLS ServerName: %v", err)
logrus.Errorf("dynamiclistener %s: failed to update cert with TLS ServerName: %v", l.Addr(), err)
return nil, err
}
}
@@ -455,7 +458,7 @@ func (l *listener) cacheHandler() http.Handler {
}
if err := l.updateCert(h); err != nil {
logrus.Errorf("failed to update cert with HTTP request Host header: %v", err)
logrus.Errorf("dynamiclistener %s: failed to update cert with HTTP request Host header: %v", l.Addr(), err)
}
}
})

View File

@@ -56,7 +56,7 @@ func createAndStoreClientCert(secrets v1controller.SecretClient, namespace strin
return nil, err
}
certPem, keyPem, err := factory.Marshal(cert, key)
keyPem, certPem, err := factory.MarshalChain(key, cert, caCert)
if err != nil {
return nil, err
}

View File

@@ -69,6 +69,8 @@ type storage struct {
}
func (s *storage) SetFactory(tls dynamiclistener.TLSFactory) {
s.Lock()
defer s.Unlock()
s.tls = tls
}
@@ -109,7 +111,9 @@ func (s *storage) init(secrets v1controller.SecretController) {
// local storage was empty, try to populate it
secret, err := s.secrets.Get(s.namespace, s.name, metav1.GetOptions{})
if err != nil {
logrus.Warnf("Failed to init Kubernetes secret: %v", err)
if !errors.IsNotFound(err) {
logrus.Warnf("Failed to init Kubernetes secret: %v", err)
}
return
}
@@ -144,7 +148,7 @@ func (s *storage) targetSecret() (*v1.Secret, error) {
}
func (s *storage) saveInK8s(secret *v1.Secret) (*v1.Secret, error) {
if !s.controllerHasSynced() {
if !s.initComplete() {
return secret, nil
}
@@ -206,9 +210,14 @@ func (s *storage) Update(secret *v1.Secret) error {
return nil
}
func isConflictOrAlreadyExists(err error) bool {
return errors.IsConflict(err) || errors.IsAlreadyExists(err)
}
func (s *storage) update(secret *v1.Secret) (err error) {
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
secret, err = s.saveInK8s(secret)
var newSecret *v1.Secret
err = retry.OnError(retry.DefaultRetry, isConflictOrAlreadyExists, func() error {
newSecret, err = s.saveInK8s(secret)
return err
})
@@ -219,14 +228,11 @@ func (s *storage) update(secret *v1.Secret) (err error) {
// Only hold the lock while updating underlying storage
s.Lock()
defer s.Unlock()
return s.storage.Update(secret)
return s.storage.Update(newSecret)
}
func (s *storage) controllerHasSynced() bool {
func (s *storage) initComplete() bool {
s.RLock()
defer s.RUnlock()
if s.secrets == nil {
return false
}
return s.secrets.Informer().HasSynced()
return s.secrets != nil
}