Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/containerd/server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ type GRPCConfig struct {
GID int `toml:"gid"`
MaxRecvMsgSize int `toml:"max_recv_message_size"`
MaxSendMsgSize int `toml:"max_send_message_size"`
TCPTLSCName string `toml:"tcp_tls_common_name"`
}

// TTRPCConfig provides TTRPC configuration for the socket
Expand Down
20 changes: 17 additions & 3 deletions cmd/containerd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import (
sbproxy "github.com/containerd/containerd/v2/core/sandbox/proxy"
ssproxy "github.com/containerd/containerd/v2/core/snapshots/proxy"
"github.com/containerd/containerd/v2/defaults"
"github.com/containerd/containerd/v2/internal/wintls"
"github.com/containerd/containerd/v2/pkg/dialer"
"github.com/containerd/containerd/v2/pkg/sys"
"github.com/containerd/containerd/v2/pkg/timeout"
Expand Down Expand Up @@ -200,7 +201,19 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
tlsConfig.ClientCAs = caCertPool
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}

tcpServerOpts = append(tcpServerOpts, grpc.Creds(credentials.NewTLS(tlsConfig)))
} else if config.GRPC.TCPTLSCName != "" {
tlsConfig, CA, res, err :=
wintls.SetupTLSFromWindowsCertStore(ctx, config.GRPC.TCPTLSCName)
if err != nil {
return nil, fmt.Errorf("failed to setup TLS from Windows cert store: %w", err)
}
// Cache resource for cleanup (Windows only)
setTLSResource(res)
if CA != nil {
tlsConfig.ClientCAs = CA
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
tcpServerOpts = append(tcpServerOpts, grpc.Creds(credentials.NewTLS(tlsConfig)))
}

Expand All @@ -226,8 +239,7 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
grpcServices []grpcService
tcpServices []tcpService
ttrpcServices []ttrpcService

s = &Server{
s = &Server{
prometheusServerMetrics: prometheusServerMetrics,
grpcServer: grpcServer,
tcpServer: tcpServer,
Expand Down Expand Up @@ -437,6 +449,8 @@ func (s *Server) ServeDebug(l net.Listener) error {
// Stop the containerd server canceling any open connections
func (s *Server) Stop() {
s.grpcServer.Stop()
// Clean up TLS resources (Windows only)
cleanupTLSResources()
for i := len(s.plugins) - 1; i >= 0; i-- {
p := s.plugins[i]
instance, err := p.Instance()
Expand Down
5 changes: 5 additions & 0 deletions cmd/containerd/server/server_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
cgroup1 "github.com/containerd/cgroups/v3/cgroup1"
cgroupsv2 "github.com/containerd/cgroups/v3/cgroup2"
srvconfig "github.com/containerd/containerd/v2/cmd/containerd/server/config"
"github.com/containerd/containerd/v2/internal/wintls"
"github.com/containerd/containerd/v2/pkg/sys"
"github.com/containerd/log"
"github.com/containerd/otelttrpc"
Expand Down Expand Up @@ -72,3 +73,7 @@ func newTTRPCServer() (*ttrpc.Server, error) {
ttrpc.WithUnaryServerInterceptor(otelttrpc.UnaryServerInterceptor()),
)
}

// TLS resource helpers are no-ops on Linux.
func setTLSResource(r wintls.CertResource) {}
func cleanupTLSResources() {}
14 changes: 14 additions & 0 deletions cmd/containerd/server/server_solaris.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,22 @@ import (
"context"

srvconfig "github.com/containerd/containerd/v2/cmd/containerd/server/config"
"github.com/containerd/containerd/v2/internal/wintls"
"github.com/containerd/otelttrpc"
"github.com/containerd/ttrpc"
)

func apply(_ context.Context, _ *srvconfig.Config) error {
return nil
}

// TLS resource helpers are no-ops on Solaris.
func setTLSResource(r wintls.CertResource) {}
func cleanupTLSResources() {}

// newTTRPCServer provides the ttrpc server for Solaris builds.
func newTTRPCServer() (*ttrpc.Server, error) {
return ttrpc.NewServer(
ttrpc.WithUnaryServerInterceptor(otelttrpc.UnaryServerInterceptor()),
)
}
6 changes: 6 additions & 0 deletions cmd/containerd/server/server_unsupported.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//go:build !linux && !windows && !solaris
// +build !linux,!windows,!solaris

/*
Copyright The containerd Authors.
Expand All @@ -22,6 +23,7 @@ import (
"context"

srvconfig "github.com/containerd/containerd/v2/cmd/containerd/server/config"
"github.com/containerd/containerd/v2/internal/wintls"
"github.com/containerd/ttrpc"
)

Expand All @@ -32,3 +34,7 @@ func apply(_ context.Context, _ *srvconfig.Config) error {
func newTTRPCServer() (*ttrpc.Server, error) {
return ttrpc.NewServer()
}

// TLS resource helpers are no-ops on other unsupported platforms.
func setTLSResource(r wintls.CertResource) {}
func cleanupTLSResources() {}
19 changes: 19 additions & 0 deletions cmd/containerd/server/server_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,29 @@ import (
"context"

srvconfig "github.com/containerd/containerd/v2/cmd/containerd/server/config"
"github.com/containerd/containerd/v2/internal/wintls"
"github.com/containerd/log"
"github.com/containerd/otelttrpc"
"github.com/containerd/ttrpc"
)

// tlsResource holds Windows-specific TLS resources for cleanup on Stop.
var tlsResource wintls.CertResource

// setTLSResource caches the resource for later cleanup.
func setTLSResource(r wintls.CertResource) { tlsResource = r }

// cleanupTLSResources releases any cached TLS resources; safe to call multiple times.
func cleanupTLSResources() {
if tlsResource != nil {
if err := tlsResource.Close(); err != nil {
log.L.WithError(err).Error("failed to cleanup TLS resources")
}
tlsResource = nil
}
}

// Windows-specific apply and TTRPC server constructor
func apply(_ context.Context, _ *srvconfig.Config) error {
return nil
}
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ require (
github.com/docker/go-units v0.5.0
github.com/emicklei/go-restful/v3 v3.13.0
github.com/fsnotify/fsnotify v1.9.0
github.com/google/certtostore v1.0.6
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0
Expand Down Expand Up @@ -89,6 +90,7 @@ require (
)

require (
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
Expand All @@ -100,13 +102,16 @@ require (
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/deck v0.0.0-20230104221208-105ad94aa8ae // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knqyf263/go-plugin v0.9.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
Expand Down
12 changes: 11 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.14.0-rc.1 h1:qAPXKwGOkVn8LlqgBN8GS0bxZ83hOJpcjxzmlQKxKsQ=
github.com/Microsoft/hcsshim v0.14.0-rc.1/go.mod h1:hTKFGbnDtQb1wHiOWv4v0eN+7boSWAHyK/tNAaYZL0c=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
Expand Down Expand Up @@ -114,6 +116,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
Expand Down Expand Up @@ -147,6 +151,10 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/certtostore v1.0.6 h1:LlCIgyTvDxTlcncMPTSYZGo6lCsiHzO6Dy7ff6ltk/0=
github.com/google/certtostore v1.0.6/go.mod h1:2N0ZPLkGvQWhYvXaiBGq02r71fnSLfq78VKIWQHr1wo=
github.com/google/deck v0.0.0-20230104221208-105ad94aa8ae h1:Iy1Ad7L9qPtNAFJad+Ch2kwDXrcwu7QUBR0bfChjnEM=
github.com/google/deck v0.0.0-20230104221208-105ad94aa8ae/go.mod h1:DoDv8G58DuLNZF0KysYn0bA/6ZWhmRW3fZE2VnGEH0w=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
Expand Down Expand Up @@ -174,8 +182,9 @@ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwn
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/intel/goresctrl v0.9.0 h1:IKI4ZrPTazLyFgdnWEkR9LS+DDATapOgoBtGxVMHePs=
Expand Down Expand Up @@ -455,6 +464,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
41 changes: 41 additions & 0 deletions internal/wintls/wintls_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//go:build !windows
// +build !windows

/*
Copyright The containerd Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package wintls

import (
"context"
"crypto/tls"
"crypto/x509"
"io"
)

type CertResource = io.Closer

// NoopCertResource implements CertResource for non-Windows platforms
type NoopCertResource struct{}

func (n *NoopCertResource) Close() error {
return nil
}

// Stub for non-Windows platforms
func SetupTLSFromWindowsCertStore(ctx context.Context, commonName string) (*tls.Config, *x509.CertPool, io.Closer, error) {
return nil, nil, &NoopCertResource{}, nil
}
127 changes: 127 additions & 0 deletions internal/wintls/wintls_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//go:build windows
// +build windows

/*
Copyright The containerd Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package wintls

import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"

"github.com/google/certtostore"
"golang.org/x/sys/windows"
)

type CertResource = io.Closer

type WindowsCertResource struct {
store *certtostore.WinCertStore
certContext *windows.CertContext
}

// NewWindowsCertResource creates a new WindowsCertResource for managing Windows certificate resources
func NewWindowsCertResource(store *certtostore.WinCertStore, certContext *windows.CertContext) *WindowsCertResource {
return &WindowsCertResource{
store: store,
certContext: certContext,
}
}

func (w *WindowsCertResource) Close() error {
var result error
if w.certContext != nil {
if err := certtostore.FreeCertContext(w.certContext); err != nil {
result = err
}
w.certContext = nil
}
if w.store != nil {
if err := w.store.Close(); err != nil {
result = err
}
w.store = nil
}
return result
}

// Returns tls.Config, certPool, CertResource for caller-managed cleanup
func SetupTLSFromWindowsCertStore(ctx context.Context, commonName string) (*tls.Config, *x509.CertPool, io.Closer, error) {
// Open the Windows Certificate Store (My store)
store, err := certtostore.OpenWinCertStore(certtostore.ProviderMSSoftware, "My", []string{}, nil, false)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to open Windows Certificate Store (My): %w", err)
}

leafCert, certContext, certChains, err := store.CertByCommonName(commonName)
if err != nil {
store.Close()
return nil, nil, nil, fmt.Errorf("failed to find certificate with context: %w", err)
}
if leafCert == nil {
store.Close()
return nil, nil, nil, fmt.Errorf("leaf certificate is nil for common name: %v", commonName)
}
if certContext == nil {
store.Close()
return nil, nil, nil, fmt.Errorf("certificate context is nil for common name: %v", commonName)
}

// Retrieve the private key from certtostore
key, err := store.CertKey(certContext)
if err != nil {
certtostore.FreeCertContext(certContext)
store.Close()
return nil, nil, nil, fmt.Errorf("failed to retrieve private key: %w", err)
}
if key == nil {
certtostore.FreeCertContext(certContext)
store.Close()
return nil, nil, nil, fmt.Errorf("retrieved private key is nil")
}

// Convert the x509 certificate chain to a CertPool and chain bytes
certPool := x509.NewCertPool()
var validIntermediates [][]byte
for _, cert := range certChains {
for _, c := range cert {
// Remove leaf certificate from the chain.
if bytes.Equal(c.Raw, leafCert.Raw) {
continue // Skip the leaf certificate
}
certPool.AddCert(c)
validIntermediates = append(validIntermediates, c.Raw)
}
}
// Create a TLS certificate using the retrieved certificate and private key
tlsCert := tls.Certificate{
PrivateKey: key,
Leaf: leafCert,
Certificate: append([][]byte{leafCert.Raw}, validIntermediates...),
}
// Create TLS configuration
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{tlsCert},
}
// Create the resource manager for cleanup
certResource := NewWindowsCertResource(store, certContext)
return tlsConfig, certPool, certResource, nil
}
Loading
Loading