From 60a58af376f35a05f6ac50ab24c8775c5ea64566 Mon Sep 17 00:00:00 2001 From: Vlad Ungureanu Date: Fri, 3 May 2019 00:54:47 -0400 Subject: [PATCH] Add TLS auth registry support Signed-off-by: Vlad Ungureanu --- docs/registry.md | 16 +++++++++++ pkg/config/config.go | 10 +++++++ pkg/server/image_pull.go | 60 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/docs/registry.md b/docs/registry.md index 5b084128a..4e7bf7238 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -22,6 +22,22 @@ from a registry, containerd will try these endpoint URLs one by one, and use the After modify this config, you need restart the `containerd` service. +## Configure Registry TLS Communication +`cri` plugin also supports configuring TLS settings when communicating with a registry. + +To configure the TLS settings for a specific registry, create/modify the `/ec/containerd/config.toml` as follows: +```toml +[plugins.cri.registry.tls_configs] + [plugins.cri.registry.tls_configs."my.custom.registry"] + ca_file = "ca.pem" + cert_file = "cert.pem" + key_file = "key.pem" +``` + +In the config example shown above, TLS mutual authentication will be used for communications with the registry endpoint located at https://my.custom.registry. +`ca_file` is file name of the certificate authority (CA) certificate used to authenticate the x509 certificate/key pair specified by the files respectively pointed to by `cert_file` and `key_file`. + + ## Configure Registry Credentials `cri` plugin also supports docker like registry credential config. diff --git a/pkg/config/config.go b/pkg/config/config.go index 85cfa2aa2..41aabfefb 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -106,6 +106,13 @@ type AuthConfig struct { IdentityToken string `toml:"identitytoken" json:"identitytoken"` } +// TLSConfig contains the CA/Cert/Key used for a registry +type TLSConfig struct { + CAFile string `toml:"ca_file" json:"caFile"` + CertFile string `toml:"cert_file" json:"certFile"` + KeyFile string `toml:"key_file" json:"keyFile"` +} + // Registry is registry settings configured type Registry struct { // Mirrors are namespace to mirror mapping for all namespaces. @@ -113,6 +120,9 @@ type Registry struct { // Auths are registry endpoint to auth config mapping. The registry endpoint must // be a valid url with host specified. Auths map[string]AuthConfig `toml:"auths" json:"auths"` + // TLSConfigs are pairs of CA/Cert/Key which then are used when creating the transport + // that communicates with the registry. + TLSConfigs map[string]TLSConfig `toml:"tls_configs" json:"tlsConfigs"` } // PluginConfig contains toml config related to CRI plugin, diff --git a/pkg/server/image_pull.go b/pkg/server/image_pull.go index dd3c1564c..cb71b6778 100644 --- a/pkg/server/image_pull.go +++ b/pkg/server/image_pull.go @@ -17,7 +17,10 @@ limitations under the License. package server import ( + "crypto/tls" + "crypto/x509" "encoding/base64" + "io/ioutil" "net/http" "net/url" "strings" @@ -28,6 +31,7 @@ import ( "github.com/containerd/containerd/reference" "github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes/docker" + "github.com/containerd/cri/pkg/config" distribution "github.com/docker/distribution/reference" imagespec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -246,6 +250,33 @@ func (c *criService) credentials(auth *runtime.AuthConfig) func(string) (string, } } +// getRegistryTLSTransport returns a http.Transport configured with a CA/Cert/Key specified by registryTLSConfig +func (c *criService) getRegistryTLSTransport(ctx context.Context, registryTLSConfig config.TLSConfig) (*http.Transport, error) { + cert, err := tls.LoadX509KeyPair(registryTLSConfig.CertFile, registryTLSConfig.KeyFile) + if err != nil { + return nil, errors.Wrap(err, "failed to load cert file") + } + + caCertPool, err := x509.SystemCertPool() + if err != nil { + return nil, errors.Wrap(err, "failed to get system cert pool") + } + caCert, err := ioutil.ReadFile(registryTLSConfig.CAFile) + if err != nil { + return nil, errors.Wrap(err, "failed to load CA file") + } + caCertPool.AppendCertsFromPEM(caCert) + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + } + tlsConfig.BuildNameToCertificate() + transport := &http.Transport{TLSClientConfig: tlsConfig} + + return transport, nil +} + // getResolver tries registry mirrors and the default registry, and returns the resolver and descriptor // from the first working registry. func (c *criService) getResolver(ctx context.Context, ref string, cred func(string) (string, string, error)) (remotes.Resolver, imagespec.Descriptor, error) { @@ -253,15 +284,27 @@ func (c *criService) getResolver(ctx context.Context, ref string, cred func(stri if err != nil { return nil, imagespec.Descriptor{}, errors.Wrap(err, "parse image reference") } + + httpClient := http.DefaultClient + // Try mirrors in order first, and then try default host name. for _, e := range c.config.Registry.Mirrors[refspec.Hostname()].Endpoints { u, err := url.Parse(e) if err != nil { return nil, imagespec.Descriptor{}, errors.Wrapf(err, "parse registry endpoint %q", e) } + + if registryTLSConfig, ok := c.config.Registry.TLSConfigs[u.Host]; ok { + tlsTransport, err := c.getRegistryTLSTransport(ctx, registryTLSConfig) + if err != nil { + return nil, imagespec.Descriptor{}, errors.Wrapf(err, "get tlsTransport for registry %q", refspec.Hostname()) + } + httpClient = &http.Client{Transport: tlsTransport} + } + resolver := docker.NewResolver(docker.ResolverOptions{ Authorizer: docker.NewAuthorizer(http.DefaultClient, cred), - Client: http.DefaultClient, + Client: httpClient, Host: func(string) (string, error) { return u.Host, nil }, // By default use "https". PlainHTTP: u.Scheme == "http", @@ -273,9 +316,22 @@ func (c *criService) getResolver(ctx context.Context, ref string, cred func(stri logrus.WithError(err).Debugf("Tried registry mirror %q but failed", e) // Continue to try next endpoint } + + hostname, err := docker.DefaultHost(refspec.Hostname()) + if err != nil { + return nil, imagespec.Descriptor{}, errors.Wrapf(err, "get host for refspec %q", refspec.Hostname()) + } + if registryTLSConfig, ok := c.config.Registry.TLSConfigs[hostname]; ok { + tlsTransport, err := c.getRegistryTLSTransport(ctx, registryTLSConfig) + if err != nil { + return nil, imagespec.Descriptor{}, errors.Wrapf(err, "get tlsTransport for registry %q", refspec.Hostname()) + } + httpClient = &http.Client{Transport: tlsTransport} + } + resolver := docker.NewResolver(docker.ResolverOptions{ Credentials: cred, - Client: http.DefaultClient, + Client: httpClient, }) _, desc, err := resolver.Resolve(ctx, ref) if err != nil {