Compare commits

...

6 Commits

Author SHA1 Message Date
Brad Davidson
1c60bf414f Fix deadlock caused by apiserver outage during init
We had similar code to prevent blocking when calling Update(), but not in the init function.

Ref: https://github.com/rancher/rancher/issues/42278
Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
2023-08-15 12:44:26 -07:00
renovate-rancher[bot]
3e7612c2c9 Update module github.com/sirupsen/logrus to v1.9.3 2023-08-15 12:43:48 -07:00
Derek Nola
e6585da47a Merge pull request #78 from knoppiks/multiple-ca-certs
Allow multiple (intermediate) CA certs
2023-08-11 12:43:10 -07:00
Jonas Wagner
6cc9a670e1 Prevent Panic for empty Arrays on Error
Co-authored-by: Brad Davidson <brad@oatmail.org>
Signed-off-by: Jonas Wagner <jwagner@knoppiks.de>
2023-07-14 08:28:48 +02:00
Jonas Wagner
8f13b193a1 Use more Verbose name for Listener
Co-authored-by: Brad Davidson <brad@oatmail.org>
Signed-off-by: Jonas Wagner <jwagner@knoppiks.de
2023-07-14 08:28:47 +02:00
Jonas Wagner
02304047cf Enable intermediate CA Certificates
Signed-off-by: Jonas Wagner <jwagner@knoppiks.de>
2023-07-10 09:42:17 +02:00
8 changed files with 104 additions and 42 deletions

View File

@@ -25,18 +25,28 @@ func GenCA() (*x509.Certificate, crypto.Signer, error) {
return caCert, caKey, nil
}
// Deprecated: Use LoadOrGenCAChain instead as it supports intermediate CAs
func LoadOrGenCA() (*x509.Certificate, crypto.Signer, error) {
cert, key, err := loadCA()
if err == nil {
return cert, key, nil
}
cert, key, err = GenCA()
chain, signer, err := LoadOrGenCAChain()
if err != nil {
return nil, nil, err
}
return chain[0], signer, err
}
certBytes, keyBytes, err := Marshal(cert, key)
func LoadOrGenCAChain() ([]*x509.Certificate, crypto.Signer, error) {
certs, key, err := loadCA()
if err == nil {
return certs, key, nil
}
cert, key, err := GenCA()
if err != nil {
return nil, nil, err
}
certs = []*x509.Certificate{cert}
certBytes, keyBytes, err := MarshalChain(key, certs...)
if err != nil {
return nil, nil, err
}
@@ -53,14 +63,22 @@ func LoadOrGenCA() (*x509.Certificate, crypto.Signer, error) {
return nil, nil, err
}
return cert, key, nil
return certs, key, nil
}
func loadCA() (*x509.Certificate, crypto.Signer, error) {
return LoadCerts("./certs/ca.pem", "./certs/ca.key")
func loadCA() ([]*x509.Certificate, crypto.Signer, error) {
return LoadCertsChain("./certs/ca.pem", "./certs/ca.key")
}
func LoadCA(caPem, caKey []byte) (*x509.Certificate, crypto.Signer, error) {
chain, signer, err := LoadCAChain(caPem, caKey)
if err != nil {
return nil, nil, err
}
return chain[0], signer, nil
}
func LoadCAChain(caPem, caKey []byte) ([]*x509.Certificate, crypto.Signer, error) {
key, err := cert.ParsePrivateKeyPEM(caKey)
if err != nil {
return nil, nil, err
@@ -70,15 +88,24 @@ func LoadCA(caPem, caKey []byte) (*x509.Certificate, crypto.Signer, error) {
return nil, nil, fmt.Errorf("key is not a crypto.Signer")
}
cert, err := ParseCertPEM(caPem)
certs, err := cert.ParseCertsPEM(caPem)
if err != nil {
return nil, nil, err
}
return cert, signer, nil
return certs, signer, nil
}
// Deprecated: Use LoadCertsChain instead as it supports intermediate CAs
func LoadCerts(certFile, keyFile string) (*x509.Certificate, crypto.Signer, error) {
chain, signer, err := LoadCertsChain(certFile, keyFile)
if err != nil {
return nil, nil, err
}
return chain[0], signer, err
}
func LoadCertsChain(certFile, keyFile string) ([]*x509.Certificate, crypto.Signer, error) {
caPem, err := ioutil.ReadFile(certFile)
if err != nil {
return nil, nil, err
@@ -88,5 +115,5 @@ func LoadCerts(certFile, keyFile string) (*x509.Certificate, crypto.Signer, erro
return nil, nil, err
}
return LoadCA(caPem, caKey)
return LoadCAChain(caPem, caKey)
}

View File

@@ -33,7 +33,7 @@ var (
)
type TLS struct {
CACert *x509.Certificate
CACert []*x509.Certificate
CAKey crypto.Signer
CN string
Organization []string
@@ -178,7 +178,7 @@ func (t *TLS) generateCert(secret *v1.Secret, cn ...string) (*v1.Secret, bool, e
return nil, false, err
}
keyBytes, certBytes, err := MarshalChain(privateKey, newCert, t.CACert)
keyBytes, certBytes, err := MarshalChain(privateKey, append([]*x509.Certificate{newCert}, t.CACert...)...)
if err != nil {
return nil, false, err
}
@@ -226,14 +226,16 @@ func (t *TLS) Verify(secret *v1.Secret) error {
x509.ExtKeyUsageAny,
},
}
verifyOpts.Roots.AddCert(t.CACert)
for _, c := range t.CACert {
verifyOpts.Roots.AddCert(c)
}
_, 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)
return NewSignedCert(privateKey, t.CACert[0], t.CAKey, t.CN, t.Organization, domains, ips)
}
func populateCN(secret *v1.Secret, cn ...string) *v1.Secret {

2
go.mod
View File

@@ -4,7 +4,7 @@ go 1.19
require (
github.com/rancher/wrangler v1.1.0
github.com/sirupsen/logrus v1.8.1
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.1
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
k8s.io/api v0.25.4

7
go.sum
View File

@@ -236,8 +236,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -249,6 +249,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
@@ -366,7 +367,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -391,6 +391,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@@ -34,7 +34,12 @@ type SetFactory interface {
SetFactory(tls TLSFactory)
}
// Deprecated: Use NewListenerWithChain instead as it supports intermediate CAs
func NewListener(l net.Listener, storage TLSStorage, caCert *x509.Certificate, caKey crypto.Signer, config Config) (net.Listener, http.Handler, error) {
return NewListenerWithChain(l, storage, []*x509.Certificate{caCert}, caKey, config)
}
func NewListenerWithChain(l net.Listener, storage TLSStorage, caCert []*x509.Certificate, caKey crypto.Signer, config Config) (net.Listener, http.Handler, error) {
if config.CN == "" {
config.CN = "dynamic"
}

View File

@@ -21,6 +21,8 @@ import (
)
type ListenOpts struct {
CAChain []*x509.Certificate
// Deprecated: Use CAChain instead
CA *x509.Certificate
CAKey crypto.Signer
Storage dynamiclistener.TLSStorage
@@ -132,7 +134,7 @@ func getTLSListener(ctx context.Context, tcp net.Listener, handler http.Handler,
return nil, nil, err
}
listener, dynHandler, err := dynamiclistener.NewListener(tcp, storage, caCert, caKey, opts.TLSListenerConfig)
listener, dynHandler, err := dynamiclistener.NewListener2(tcp, storage, caCert, caKey, opts.TLSListenerConfig)
if err != nil {
return nil, nil, err
}
@@ -140,13 +142,17 @@ func getTLSListener(ctx context.Context, tcp net.Listener, handler http.Handler,
return listener, wrapHandler(dynHandler, handler), nil
}
func getCA(opts ListenOpts) (*x509.Certificate, crypto.Signer, error) {
if opts.CA != nil && opts.CAKey != nil {
return opts.CA, opts.CAKey, nil
func getCA(opts ListenOpts) ([]*x509.Certificate, crypto.Signer, error) {
if opts.CAKey != nil {
if opts.CAChain != nil {
return opts.CAChain, opts.CAKey, nil
} else if opts.CA != nil {
return []*x509.Certificate{opts.CA}, opts.CAKey, nil
}
}
if opts.Secrets == nil {
return factory.LoadOrGenCA()
return factory.LoadOrGenCAChain()
}
if opts.CAName == "" {
@@ -161,7 +167,7 @@ func getCA(opts ListenOpts) (*x509.Certificate, crypto.Signer, error) {
opts.CANamespace = "kube-system"
}
return kubernetes.LoadOrGenCA(opts.Secrets, opts.CANamespace, opts.CAName)
return kubernetes.LoadOrGenCAChain(opts.Secrets, opts.CANamespace, opts.CAName)
}
func newStorage(ctx context.Context, opts ListenOpts) dynamiclistener.TLSStorage {

View File

@@ -11,12 +11,21 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Deprecated: Use LoadOrGenCAChain instead as it supports intermediate CAs
func LoadOrGenCA(secrets v1controller.SecretClient, namespace, name string) (*x509.Certificate, crypto.Signer, error) {
chain, signer, err := LoadOrGenCAChain(secrets, namespace, name)
if err != nil {
return nil, nil, err
}
return chain[0], signer, err
}
func LoadOrGenCAChain(secrets v1controller.SecretClient, namespace, name string) ([]*x509.Certificate, crypto.Signer, error) {
secret, err := getSecret(secrets, namespace, name)
if err != nil {
return nil, nil, err
}
return factory.LoadCA(secret.Data[v1.TLSCertKey], secret.Data[v1.TLSPrivateKeyKey])
return factory.LoadCAChain(secret.Data[v1.TLSCertKey], secret.Data[v1.TLSPrivateKeyKey])
}
func LoadOrGenClient(secrets v1controller.SecretClient, namespace, name, cn string, ca *x509.Certificate, key crypto.Signer) (*x509.Certificate, crypto.Signer, error) {

View File

@@ -14,6 +14,7 @@ import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
)
@@ -40,19 +41,13 @@ func New(ctx context.Context, core CoreGetter, namespace, name string, backing d
// lazy init
go func() {
for {
wait.PollImmediateUntilWithContext(ctx, time.Second, func(cxt context.Context) (bool, error) {
if coreFactory := core(); coreFactory != nil {
storage.init(coreFactory.Core().V1().Secret())
_ = start.All(ctx, 5, coreFactory)
return
return true, start.All(ctx, 5, coreFactory)
}
select {
case <-ctx.Done():
return
case <-time.After(time.Second):
}
}
return false, nil
})
}()
return storage
@@ -66,6 +61,7 @@ type storage struct {
secrets v1controller.SecretController
ctx context.Context
tls dynamiclistener.TLSFactory
initialized bool
}
func (s *storage) SetFactory(tls dynamiclistener.TLSFactory) {
@@ -92,7 +88,17 @@ func (s *storage) init(secrets v1controller.SecretController) {
})
s.secrets = secrets
secret, err := s.storage.Get()
// Asynchronously sync the backing storage to the Kubernetes secret, as doing so inline may
// block the listener from accepting new connections if the apiserver becomes unavailable
// after the Secrets controller has been initialized. We're not passing around any contexts
// here, nor does the controller accept any, so there's no good way to soft-fail with a
// reasonable timeout.
go s.syncStorage()
}
func (s *storage) syncStorage() {
var updateStorage bool
secret, err := s.Get()
if err == nil && cert.IsValidTLSSecret(secret) {
// local storage had a cached secret, ensure that it exists in Kubernetes
_, err := s.secrets.Create(&v1.Secret{
@@ -109,14 +115,20 @@ func (s *storage) init(secrets v1controller.SecretController) {
}
} else {
// local storage was empty, try to populate it
secret, err := s.secrets.Get(s.namespace, s.name, metav1.GetOptions{})
secret, err = s.secrets.Get(s.namespace, s.name, metav1.GetOptions{})
if err != nil {
if !errors.IsNotFound(err) {
logrus.Warnf("Failed to init Kubernetes secret: %v", err)
}
return
} else {
updateStorage = true
}
}
s.Lock()
defer s.Unlock()
s.initialized = true
if updateStorage {
if err := s.storage.Update(secret); err != nil {
logrus.Warnf("Failed to init backing storage secret: %v", err)
}
@@ -234,5 +246,5 @@ func (s *storage) update(secret *v1.Secret) (err error) {
func (s *storage) initComplete() bool {
s.RLock()
defer s.RUnlock()
return s.secrets != nil
return s.initialized
}