8000 Allow specifying TLS client certificates with proxy by glennpratt · Pull Request #4660 · distribution/distribution · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Allow specifying TLS client certificates with proxy #4660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
18 changes: 18 additions & 0 deletions configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,9 @@ type Proxy struct {
// if not set, defaults to 7 * 24 hours
// If set to zero, will never expire cache
TTL *time.Duration `yaml:"ttl,omitempty"`

// TLS configures client TLS for proxying to the remote registry
TLS *ProxyTLS `yaml:"tls,omitempty"`
}

// ExecConfig defines the configuration for executing a command as a credential helper.
Expand All @@ -711,6 +714,21 @@ type ExecConfig struct {
Lifetime *time.Duration `yaml:"lifetime,omitempty"`
}

// ProxyTLS configures TLS for registries requiring client certificates.
type ProxyTLS struct {
// Certificate specifies the path to the certificate file for TLS authentication.
// This certificate is used to establish a secure connection with the registry.
Certificate string `yaml:"certificate,omitempty"`

// Key specifies the path to the private key file associated with the certificate.
// This key is used to authenticate the client during the TLS handshake.
Key string `yaml:"key,omitempty"`

// ClientCAs specifies a list of certificates to be used to verify the server's
// certificate during the TLS handshake. This can be used for mutual TLS authentication.
ClientCAs []string `yaml:"clientcas,omitempty"`
}

// Validation configures validation options for the registry.
type Validation struct {
// Enabled enables the other options in this section. This field is
Expand Down
48 changes: 45 additions & 3 deletions registry/proxy/proxyregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package proxy

import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"net/url"
"os"
"sync"
"time"

Expand Down Expand Up @@ -32,6 +35,7 @@ type proxyingRegistry struct {
remoteURL url.URL
authChallenger authChallenger
basicAuth auth.CredentialStore
baseTransport http.RoundTripper
}

// NewRegistryPullThroughCache creates a registry acting as a pull through cache
Expand Down Expand Up @@ -127,6 +131,11 @@ func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Name
return nil, err
}

baseTr, err := getHttpTransport(config.TLS, remoteURL)
if err != nil {
return nil, fmt.Errorf("get proxy http transport: %w", err)
}

return &proxyingRegistry{
embedded: registry,
scheduler: s,
Expand All @@ -137,7 +146,8 @@ func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Name
cm: challenge.NewSimpleManager(),
cs: cs,
},
basicAuth: b,
basicAuth: b,
baseTransport: baseTr,
}, nil
}

Expand All @@ -153,7 +163,7 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named
c := pr.authChallenger

tkopts := auth.TokenHandlerOptions{
Transport: http.DefaultTransport,
Transport: pr.baseTransport,
Credentials: c.credentialStore(),
Scopes: []auth.Scope{
auth.RepositoryScope{
Expand All @@ -164,7 +174,7 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named
Logger: dcontext.GetLogger(ctx),
}

tr := transport.NewTransport(http.DefaultTransport,
tr := transport.NewTransport(pr.baseTransport,
auth.NewAuthorizer(c.challengeManager(),
auth.NewTokenHandlerWithOptions(tkopts),
auth.NewBasicHandler(pr.basicAuth)))
Expand Down Expand Up @@ -215,6 +225,38 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named
}, nil
}

// getHttpTransport builds http.RoundTripper adding TLS configuration if provided
func getHttpTransport(cfg *configuration.ProxyTLS, remoteURL *url.URL) (http.RoundTripper, error) {
if cfg == nil || (cfg.Certificate == "" && cfg.Key == "") {
return http.DefaultTransport, nil
}
if remoteURL.Scheme != "https" {
return nil, fmt.Errorf("TLS configuration set but URL is not HTTPS: %s", remoteURL)
}

tlsConfig := &tls.Config{}
cert, err := tls.LoadX509KeyPair(cfg.Certificate, cfg.Key)
if err != nil {
return nil, fmt.Errorf("load TLS key pair from %s and %s: %w", cfg.Certificate, cfg.Key, err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
if len(cfg.ClientCAs) > 0 {
pool := x509.NewCertPool()
for _, ca := range cfg.ClientCAs {
caPem, err := os.ReadFile(ca)
if err != nil {
return nil, fmt.Errorf("reading client CA %s: %w", ca, err)
}

if ok := pool.AppendCertsFromPEM(caPem); !ok {
return nil, fmt.Errorf("could not add CA to pool from %s", ca)
}
}
}

return &http.Transport{TLSClientConfig: tlsConfig}, nil
}

func (pr *proxyingRegistry) Blobs() distribution.BlobEnumerator {
return pr.embedded.Blobs()
}
Expand Down
0