From 4f9be621035b231e629d84655bc972236674f145 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 22 Jan 2026 13:12:27 -0800 Subject: [PATCH] Support multiple certificates for S3 HTTPS via comma-separated flags --- weed/command/s3.go | 59 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/weed/command/s3.go b/weed/command/s3.go index e84a4f45d..25a75babb 100644 --- a/weed/command/s3.go +++ b/weed/command/s3.go @@ -55,7 +55,7 @@ type S3Options struct { localFilerSocket *string dataCenter *string localSocket *string - certProvider certprovider.Provider + certProviders []certprovider.Provider idleTimeout *int concurrentUploadLimitMB *int concurrentFileUploadLimit *int @@ -208,12 +208,37 @@ func runS3(cmd *Command, args []string) bool { } // GetCertificateWithUpdate Auto refreshing TSL certificate -func (s3opt *S3Options) GetCertificateWithUpdate(*tls.ClientHelloInfo) (*tls.Certificate, error) { - certs, err := s3opt.certProvider.KeyMaterial(context.Background()) - if certs == nil { - return nil, err +func (s3opt *S3Options) GetCertificateWithUpdate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + for _, provider := range s3opt.certProviders { + certs, err := provider.KeyMaterial(context.Background()) + if err != nil || certs == nil || len(certs.Certs) == 0 { + continue + } + if hello == nil || hello.ServerName == "" { + return &certs.Certs[0], nil + } + cert := certs.Certs[0] + // parse leaf certificate + leaf := cert.Leaf + if leaf == nil { + var err error + leaf, err = x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + continue + } + } + if err := leaf.VerifyHostname(hello.ServerName); err == nil { + return &cert, nil + } } - return &certs.Certs[0], err + // fallback to the first certificate + if len(s3opt.certProviders) > 0 { + certs, err := s3opt.certProviders[0].KeyMaterial(context.Background()) + if certs != nil && len(certs.Certs) > 0 { + return &certs.Certs[0], err + } + } + return nil, fmt.Errorf("no certificate found for %s", hello.ServerName) } func (s3opt *S3Options) startS3Server() bool { @@ -357,13 +382,23 @@ func (s3opt *S3Options) startS3Server() bool { glog.Fatalf("S3 API Server error: -s3.port.https (%d) cannot be the same as -s3.port (%d)", *s3opt.portHttps, *s3opt.port) } - pemfileOptions := pemfile.Options{ - CertFile: *s3opt.tlsCertificate, - KeyFile: *s3opt.tlsPrivateKey, - RefreshDuration: security.CredRefreshingInterval, + certFiles := strings.Split(*s3opt.tlsCertificate, ",") + keyFiles := strings.Split(*s3opt.tlsPrivateKey, ",") + if len(certFiles) != len(keyFiles) { + glog.Fatalf("S3 API Server error: number of certificates and keys do not match") } - if s3opt.certProvider, err = pemfile.NewProvider(pemfileOptions); err != nil { - glog.Fatalf("pemfile.NewProvider(%v) failed: %v", pemfileOptions, err) + + for i, certFile := range certFiles { + pemfileOptions := pemfile.Options{ + CertFile: certFile, + KeyFile: keyFiles[i], + RefreshDuration: security.CredRefreshingInterval, + } + provider, err := pemfile.NewProvider(pemfileOptions) + if err != nil { + glog.Fatalf("pemfile.NewProvider(%v) failed: %v", pemfileOptions, err) + } + s3opt.certProviders = append(s3opt.certProviders, provider) } caCertPool := x509.NewCertPool()