Skip to content
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

feat: Skip upstream certificate validation #205

Open
wants to merge 1 commit into
base: master
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ Usage of _output/kube-rbac-proxy:
--tls-private-key-file string File containing the default x509 private key matching --tls-cert-file.
--tls-reload-interval duration The interval at which to watch for TLS certificate changes, by default set to 1 minute. (default 1m0s)
--upstream string The upstream URL to proxy to once requests have successfully been authenticated and authorized.
--upstream-ca-file string The CA the upstream uses for TLS connection. This is required when the upstream uses TLS and its own CA certificate
--upstream-force-h2c Force h2c to communiate with the upstream. This is required when the upstream speaks h2c(http/2 cleartext - insecure variant of http/2) only. For example, go-grpc server in the insecure mode, such as helm's tiller w/o TLS, speaks h2c only
--upstream-ca-file string The CA the upstream uses for TLS connection. This is required when the upstream uses TLS and its own CA certificate.
--upstream-force-h2c Force h2c to communicate with the upstream. This is required when the upstream speaks h2c(http/2 cleartext - insecure variant of http/2) only. For example, go-grpc server in the insecure mode, such as helm's tiller w/o TLS, speaks h2c only.
--upstream-insecure-skip-verify Skip upstream certificate validation. Useful if an upstream uses TLS encryption with self-signed certificates.
-v, --v Level number for the log level verbosity
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```
Expand Down
51 changes: 23 additions & 28 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ import (
type config struct {
insecureListenAddress string
secureListenAddress string
upstream string
upstreamForceH2C bool
upstreamCAFile string
auth proxy.Config
tls tlsConfig
kubeconfigLocation string
allowPaths []string
ignorePaths []string

auth proxy.Config
tls tlsConfig
upstream upstreamConfig

kubeconfigLocation string
allowPaths []string
ignorePaths []string
}

type tlsConfig struct {
Expand All @@ -73,6 +73,13 @@ type tlsConfig struct {
reloadInterval time.Duration
}

type upstreamConfig struct {
url string
forceH2C bool
caFile string
insecureSkipVerify bool
}

type configfile struct {
AuthorizationConfig *authz.Config `json:"authorization,omitempty"`
}
Expand Down Expand Up @@ -101,13 +108,16 @@ func main() {
// kube-rbac-proxy flags
flagset.StringVar(&cfg.insecureListenAddress, "insecure-listen-address", "", "The address the kube-rbac-proxy HTTP server should listen on.")
flagset.StringVar(&cfg.secureListenAddress, "secure-listen-address", "", "The address the kube-rbac-proxy HTTPs server should listen on.")
flagset.StringVar(&cfg.upstream, "upstream", "", "The upstream URL to proxy to once requests have successfully been authenticated and authorized.")
flagset.BoolVar(&cfg.upstreamForceH2C, "upstream-force-h2c", false, "Force h2c to communiate with the upstream. This is required when the upstream speaks h2c(http/2 cleartext - insecure variant of http/2) only. For example, go-grpc server in the insecure mode, such as helm's tiller w/o TLS, speaks h2c only")
flagset.StringVar(&cfg.upstreamCAFile, "upstream-ca-file", "", "The CA the upstream uses for TLS connection. This is required when the upstream uses TLS and its own CA certificate")
flagset.StringVar(&configFileName, "config-file", "", "Configuration file to configure kube-rbac-proxy.")
flagset.StringSliceVar(&cfg.allowPaths, "allow-paths", nil, "Comma-separated list of paths against which kube-rbac-proxy pattern-matches the incoming request. If the request doesn't match, kube-rbac-proxy responds with a 404 status code. If omitted, the incoming request path isn't checked. Cannot be used with --ignore-paths.")
flagset.StringSliceVar(&cfg.ignorePaths, "ignore-paths", nil, "Comma-separated list of paths against which kube-rbac-proxy pattern-matches the incoming request. If the requst matches, it will proxy the request without performing an authentication or authorization check. Cannot be used with --allow-paths.")

// Upstream flags
flagset.StringVar(&cfg.upstream.url, "upstream", "", "The upstream URL to proxy to once requests have successfully been authenticated and authorized.")
flagset.BoolVar(&cfg.upstream.forceH2C, "upstream-force-h2c", false, "Force h2c to communicate with the upstream. This is required when the upstream speaks h2c(http/2 cleartext - insecure variant of http/2) only. For example, go-grpc server in the insecure mode, such as helm's tiller w/o TLS, speaks h2c only.")
flagset.StringVar(&cfg.upstream.caFile, "upstream-ca-file", "", "The CA the upstream uses for TLS connection. This is required when the upstream uses TLS and its own CA certificate.")
flagset.BoolVar(&cfg.upstream.insecureSkipVerify, "upstream-insecure-skip-verify", false, "Skip upstream certificate validation. Useful if an upstream uses TLS encryption with self-signed certificates.")

// TLS flags
flagset.StringVar(&cfg.tls.certFile, "tls-cert-file", "", "File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert)")
flagset.StringVar(&cfg.tls.keyFile, "tls-private-key-file", "", "File containing the default x509 private key matching --tls-cert-file.")
Expand Down Expand Up @@ -141,7 +151,7 @@ func main() {
}
kcfg := initKubeConfig(cfg.kubeconfigLocation)

upstreamURL, err := url.Parse(cfg.upstream)
upstreamURL, err := url.Parse(cfg.upstream.url)
if err != nil {
klog.Fatalf("Failed to parse upstream URL: %v", err)
}
Expand Down Expand Up @@ -245,7 +255,7 @@ For more information, please go to https://github.com/brancz/kube-rbac-proxy/iss
sarAuthorizer,
)

upstreamTransport, err := initTransport(cfg.upstreamCAFile)
upstreamTransport, err := initTransport(cfg.upstream)
if err != nil {
klog.Fatalf("Failed to set up upstream TLS connection: %v", err)
}
Expand All @@ -271,21 +281,6 @@ For more information, please go to https://github.com/brancz/kube-rbac-proxy/iss
proxy := httputil.NewSingleHostReverseProxy(upstreamURL)
proxy.Transport = upstreamTransport

if cfg.upstreamForceH2C {
// Force http/2 for connections to the upstream i.e. do not start with HTTP1.1 UPGRADE req to
// initialize http/2 session.
// See https://github.com/golang/go/issues/14141#issuecomment-219212895 for more context
proxy.Transport = &http2.Transport{
// Allow http schema. This doesn't automatically disable TLS
AllowHTTP: true,
// Do disable TLS.
// In combination with the schema check above. We could enforce h2c against the upstream server
DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(netw, addr)
},
}
}

handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ignorePathFound := false
for _, pathIgnored := range cfg.ignorePaths {
Expand Down
51 changes: 34 additions & 17 deletions transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package main

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
Expand All @@ -25,37 +26,53 @@ import (
"net/http"
"os"
"time"
)

func initTransport(upstreamCAFile string) (http.RoundTripper, error) {
if upstreamCAFile == "" {
return http.DefaultTransport, nil
}
"golang.org/x/net/http2"
)

rootPEM, err := os.ReadFile(upstreamCAFile)
if err != nil {
return nil, fmt.Errorf("error reading upstream CA file: %v", err)
func initTransport(upstream upstreamConfig) (http.RoundTripper, error) {
// Force http/2 for connections to the upstream i.e. do not start with HTTP1.1 UPGRADE req to
// initialize http/2 session.
// See https://github.com/golang/go/issues/14141#issuecomment-219212895 for more context
if upstream.forceH2C {
return &http2.Transport{
// Allow http schema. This doesn't automatically disable TLS
AllowHTTP: true,
// Do disable TLS.
// In combination with the schema check above. We could enforce h2c against the upstream server
DialTLSContext: func(ctx context.Context, netw, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(netw, addr)
},
}, nil
}

roots := x509.NewCertPool()
if ok := roots.AppendCertsFromPEM([]byte(rootPEM)); !ok {
return nil, errors.New("error parsing upstream CA certificate")

if upstream.caFile != "" {
rootPEM, err := os.ReadFile(upstream.caFile)
if err != nil {
return nil, fmt.Errorf("error reading upstream CA file: %v", err)
}

if ok := roots.AppendCertsFromPEM(rootPEM); !ok {
return nil, errors.New("error parsing upstream CA certificate")
}
}

// http.Transport sourced from go 1.10.7
transport := &http.Transport{
// http.Transport sourced from go 1.19
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{RootCAs: roots},
}

return transport, nil
TLSClientConfig: &tls.Config{
RootCAs: roots,
InsecureSkipVerify: upstream.insecureSkipVerify,
},
}, nil
}
29 changes: 27 additions & 2 deletions transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ package main
import (
"net/http"
"testing"

"golang.org/x/net/http2"
)

func TestInitTransportWithDefault(t *testing.T) {
roundTripper, err := initTransport("")
roundTripper, err := initTransport(upstreamConfig{})
if err != nil {
t.Errorf("want err to be nil, but got %v", err)
return
Expand All @@ -32,7 +34,7 @@ func TestInitTransportWithDefault(t *testing.T) {
}

func TestInitTransportWithCustomCA(t *testing.T) {
roundTripper, err := initTransport("test/ca.pem")
roundTripper, err := initTransport(upstreamConfig{caFile: "test/ca.pem"})
if err != nil {
t.Errorf("want err to be nil, but got %v", err)
return
Expand All @@ -42,3 +44,26 @@ func TestInitTransportWithCustomCA(t *testing.T) {
t.Error("expected root CA to be set, got nil")
}
}

func TestInitTransportInsecure(t *testing.T) {
roundTripper, err := initTransport(upstreamConfig{insecureSkipVerify: true})
if err != nil {
t.Errorf("want err to be nil, but got %v", err)
return
}
transport := roundTripper.(*http.Transport)
if transport.TLSClientConfig.InsecureSkipVerify == false {
t.Error("expected insecure transport")
}
}

func TestInitH2CTransport(t *testing.T) {
roundTripper, err := initTransport(upstreamConfig{forceH2C: true})
if err != nil {
t.Errorf("want err to be nil, but got %v", err)
return
}
if _, ok := roundTripper.(*http2.Transport); ok == false {
t.Error("expected http2 transport")
}
}