From: Dan Fuhry Date: Sun, 15 Mar 2026 00:08:57 +0000 (-0400) Subject: [mint] support new mtls config provider API X-Git-Url: https://go.fuhry.dev/?a=commitdiff_plain;h=7bc16083ae4bde8e47bd65c7858fe673b2d59841;p=runtime.git [mint] support new mtls config provider API --- diff --git a/mint/BUILD.bazel b/mint/BUILD.bazel index d5a9662..84460a4 100644 --- a/mint/BUILD.bazel +++ b/mint/BUILD.bazel @@ -18,5 +18,6 @@ go_library( "//utils/log", "//utils/option", "@com_github_quic_go_quic_go//:quic-go", + "@in_gopkg_yaml_v3//:yaml_v3", ], ) diff --git a/mint/client.go b/mint/client.go index 932e544..6306b96 100644 --- a/mint/client.go +++ b/mint/client.go @@ -49,6 +49,8 @@ type clientImpl struct { type Client interface { GetCertificate(service string) (*MintCertificate, error) GetRootCertificate() (*x509.Certificate, error) + + Logger() log.Logger } type ClientOption = option.Option[*clientImpl] @@ -59,11 +61,14 @@ var mintQuicConfig = &quic.Config{ 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 != "" { @@ -206,14 +211,7 @@ func (c *clientImpl) GetCertificate(subject string) (*MintCertificate, error) { 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 } @@ -269,6 +267,70 @@ retry: 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() @@ -291,7 +353,9 @@ func (m *MintCertificate) LeafCertificate() (*x509.Certificate, error) { 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 diff --git a/mint/mtls_driver.go b/mint/mtls_driver.go index 9512e05..d47a809 100644 --- a/mint/mtls_driver.go +++ b/mint/mtls_driver.go @@ -6,8 +6,11 @@ import ( "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) { @@ -35,6 +38,11 @@ func tryGetMintIdentity(cls mtls.PrincipalClass, name string) (mtls.CertificateP 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{}) } diff --git a/mint/servicer/BUILD.bazel b/mint/servicer/BUILD.bazel index 874482c..416ed0f 100644 --- a/mint/servicer/BUILD.bazel +++ b/mint/servicer/BUILD.bazel @@ -4,6 +4,7 @@ go_library( name = "servicer", srcs = [ "acl.go", + "client.go", "servicer.go", "signer.go", ], @@ -13,6 +14,7 @@ go_library( "//config_watcher", "//ephs", "//grpc", + "//mint", "//mint/remote_signer", "//mtls", "//mtls/certutil", diff --git a/mint/servicer/client.go b/mint/servicer/client.go new file mode 100644 index 0000000..c0c4c48 --- /dev/null +++ b/mint/servicer/client.go @@ -0,0 +1,29 @@ +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 +}