type Client interface {
GetCertificate(service string) (*MintCertificate, error)
GetRootCertificate() (*x509.Certificate, error)
+
+ Logger() log.Logger
}
type ClientOption = option.Option[*clientImpl]
KeepAlivePeriod: 5 * time.Second,
}
-var _ mtls.CertificateProvider = &MintCertificate{}
-var DefaultClientContext context.Context
-var defaultIdentityStr string
-var defaultClient Client
-var defaultClientMu sync.Mutex
+var (
+ _ mtls.CertificateProvider = &MintCertificate{}
+ DefaultClientContext context.Context
+ defaultIdentityStr string
+ defaultClient Client
+ defaultClientMu sync.Mutex
+ renewRetryInterval = 15 * time.Second
+)
func defaultIdentity() mtls.Identity {
if defaultIdentityStr != "" {
c.log.Noticef("GetCertificate(%q): ok: obtained cert with serial %s valid until %s",
subject, leaf.SerialNumber.String(), leaf.NotAfter.UTC().Format(time.RFC3339))
- mc := &MintCertificate{
- client: c,
- service: subject,
- private: privateKey,
- leaf: leaf,
- chain: chain,
- root: root,
- }
+ mc := NewCertificate(c, subject, privateKey, leaf, chain, root)
c.cache[subject] = mc
return mc, nil
}
return mintCl, nil
}
+func (c *clientImpl) Logger() log.Logger {
+ return c.log
+}
+
+func NewCertificate(client Client, service string, privateKey crypto.PrivateKey, leaf *x509.Certificate, chain []*x509.Certificate, root *x509.Certificate) *MintCertificate {
+ mc := &MintCertificate{
+ client: client,
+ service: service,
+ private: privateKey,
+ leaf: leaf,
+ chain: chain,
+ root: root,
+ }
+ mc.scheduleRenew()
+
+ return mc
+}
+
+func (m *MintCertificate) renew() error {
+ newCert, err := m.client.GetCertificate(m.service)
+ if err != nil {
+ return err
+ }
+
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ m.root = newCert.root
+ m.leaf = newCert.leaf
+ m.chain = newCert.chain
+ m.private = newCert.private
+ m.scheduleRenew()
+
+ return nil
+}
+
+func (m *MintCertificate) scheduleRenew() {
+ log := m.client.Logger()
+
+ now := time.Now()
+ expires := now
+ if m.leaf != nil {
+ expires = m.leaf.NotAfter
+ }
+
+ // renew in 2/3 of time between now and expiration time
+ renewIn := ((2 * expires.Sub(now)) / 3).Round(100 * time.Millisecond)
+ if now.Equal(expires) || now.After(expires) {
+ // but if already expired, renew immediately
+ renewIn = 0
+ }
+
+ go (func() {
+ log.Infof("certificate %q will be renewed in %s", m.service, renewIn.String())
+ time.Sleep(renewIn)
+ err := m.renew()
+ if err != nil {
+ log.Errorf("error renewing certificate for service %q, retrying in %s", m.service, renewRetryInterval)
+ time.Sleep(renewRetryInterval)
+ m.scheduleRenew()
+ }
+ })()
+}
+
// RootCertificate implements mtls.CertificateProvider.
func (m *MintCertificate) RootCertificate() (*x509.Certificate, error) {
m.mu.RLock()
defer m.mu.RUnlock()
if err := certutil.ValidNow(m.leaf); err != nil {
- return nil, err
+ if err = m.renew(); err != nil {
+ return nil, err
+ }
}
return m.leaf, nil
"go.fuhry.dev/runtime/mtls"
"go.fuhry.dev/runtime/utils/log"
+ "gopkg.in/yaml.v3"
)
+type mintIdentityProviderFactory struct{}
+
var depth atomic.Int32
func tryGetMintIdentity(cls mtls.PrincipalClass, name string) (mtls.CertificateProvider, error) {
return cert, nil
}
+func (f *mintIdentityProviderFactory) New(node *yaml.Node) (mtls.IdentityLoaderFunc, error) {
+ return tryGetMintIdentity, nil
+}
+
func init() {
mtls.RegisterIdentityDriver("mint", tryGetMintIdentity)
+ mtls.RegisterProviderFactory("mint", &mintIdentityProviderFactory{})
}
--- /dev/null
+package servicer
+
+import (
+ "crypto/x509"
+ "errors"
+
+ "go.fuhry.dev/runtime/mint"
+ "go.fuhry.dev/runtime/mtls"
+ "go.fuhry.dev/runtime/utils/log"
+)
+
+type mintServerClient struct {
+ serverId mtls.Identity
+ log log.Logger
+}
+
+var _ mint.Client = &mintServerClient{}
+
+func (c *mintServerClient) GetCertificate(service string) (*mint.MintCertificate, error) {
+ return nil, errors.New("not implemented")
+}
+
+func (c *mintServerClient) GetRootCertificate() (*x509.Certificate, error) {
+ return c.serverId.RootCertificate()
+}
+
+func (c *mintServerClient) Logger() log.Logger {
+ return c.log
+}