Skip to content

Commit

Permalink
c/k/a/opts: check for secure connection on h2c,rh
Browse files Browse the repository at this point in the history
h2c is unencrypted http/2 and we don't want to allow it outside of
localhost.
request headers functionality is setting confidential in the headers, we
want this to happen only through mTLS. We can't guarantee that upstream
verifies client certificates.
  • Loading branch information
ibihim committed Jan 26, 2024
1 parent f5d9a22 commit 0d9070d
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 4 deletions.
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@ test-unit:
test-e2e:
go test -timeout 55m -v ./test/e2e/ $(TEST_RUN_ARGS) --kubeconfig=$(KUBECONFIG)

test-local:
@echo 'run: VERSION=local make clean container kind-create-cluster test'
test-local-setup: VERSION = local
test-local-setup: VERSION_SEMVER = $(shell cat VERSION)
test-local-setup: clean container kind-create-cluster
test-local: test-local-setup test-e2e

kind-delete-cluster:
kind delete cluster
Expand Down
77 changes: 75 additions & 2 deletions cmd/kube-rbac-proxy/app/options/proxyoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ limitations under the License.
package options

import (
"context"
"crypto/tls"
"fmt"
"net"
"net/url"
"os"
"path"
"time"

"github.com/ghodss/yaml"
"github.com/spf13/pflag"
Expand All @@ -36,8 +40,9 @@ import (

// ProxyOptions are options specific to the kube-rbac-proxy
type ProxyOptions struct {
Upstream string
UpstreamForceH2C bool
Upstream string
UpstreamForceH2C bool
UpstreamDNSTimeout int

UpstreamCAFile string
UpstreamClientCertFile string
Expand All @@ -57,6 +62,7 @@ type ProxyOptions struct {
func (o *ProxyOptions) AddFlags(flagset *pflag.FlagSet) {
flagset.StringVar(&o.Upstream, "upstream", "", "The upstream URL to proxy to once requests have successfully been authenticated and authorized.")
flagset.BoolVar(&o.UpstreamForceH2C, "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.IntVar(&o.UpstreamDNSTimeout, "upstream-dns-timeout", 5, "The timeout in seconds for DNS lookups of the upstream. If set to 0, no timeout is set.")

// upstream tls options
flagset.StringVar(&o.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")
Expand Down Expand Up @@ -103,6 +109,14 @@ func (o *ProxyOptions) Validate() []error {
}
}

// Verify secure connection settings.
hasIdentityHeader := len(o.UpstreamHeader.GroupsFieldName) > 0 || len(o.UpstreamHeader.UserFieldName) > 0
if hasIdentityHeader || o.UpstreamForceH2C {
if err := hasSecureConnection(o); err != nil {
errs = append(errs, err)
}
}

return errs
}

Expand Down Expand Up @@ -132,6 +146,65 @@ func (o *ProxyOptions) ApplyTo(c *server.KubeRBACProxyInfo, a *serverconfig.Auth
return nil
}

func hasSecureConnection(o *ProxyOptions) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(o.UpstreamDNSTimeout)*time.Second)
defer cancel()

isLoopback, loopbackErr := isLoopbackAddress(ctx, o.Upstream)
if loopbackErr == nil && isLoopback {
return nil
}

if o.UpstreamForceH2C {
return fmt.Errorf("loopback address is required for h2c")
}

couldBeMTLS, mTLSErr := isMTLSMaybe(o.UpstreamClientCertFile, o.UpstreamClientKeyFile)
if !couldBeMTLS || mTLSErr != nil {
return fmt.Errorf("secure connection is required, neither mTLS nor loopback addresss is configured: %w", mTLSErr)
}

return nil
}

func isMTLSMaybe(upstreamClientCertPath, upstreamClientKeyPath string) (bool, error) {
if len(upstreamClientCertPath) > 0 {
_, err := tls.LoadX509KeyPair(upstreamClientCertPath, upstreamClientKeyPath)
if err != nil {
return false, fmt.Errorf("failed to read upstream client cert/key: %w", err)
}

return true, nil
}

return false, nil
}

func isLoopbackAddress(ctx context.Context, address string) (bool, error) {
u, err := url.Parse(address)
if err != nil {
return false, fmt.Errorf("failed to parse upstream URL: %w", err)
}

ip := net.ParseIP(u.Hostname())
if ip != nil {
return ip.IsLoopback(), nil
}

ips, err := (&net.Resolver{}).LookupIPAddr(ctx, u.Hostname())
if err != nil {
return false, fmt.Errorf("failed to lookup ip: %w", err)
}

for _, ip := range ips {
if !ip.IP.IsLoopback() {
return false, nil
}
}

return true, nil
}

type configfile struct {
AuthorizationConfig *authz.AuthzConfig `json:"authorization,omitempty"`
}
Expand Down

0 comments on commit 0d9070d

Please sign in to comment.