Skip to content

Commit

Permalink
Add auto discovery of trusted proxies in Kubernetes:
Browse files Browse the repository at this point in the history
They will eliminate issues from users having to
determine the trusted proxies. This functionality can
be disabled if desired. By default we run auto discovery.

This will help simplify the deployment of Smee in the Helm
Chart.

Signed-off-by: Jacob Weinstock <[email protected]>
  • Loading branch information
jacobweinstock committed Dec 13, 2023
1 parent 6a15608 commit a693b3f
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 16 deletions.
96 changes: 94 additions & 2 deletions cmd/smee/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package main

import (
"context"
"strings"

"github.com/go-logr/logr"
"github.com/tinkerbell/dhcp/backend/file"
"github.com/tinkerbell/dhcp/backend/kube"
"github.com/tinkerbell/dhcp/handler"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
Expand All @@ -27,7 +31,7 @@ type File struct {
Enabled bool
}

func (k *Kube) Backend(ctx context.Context) (handler.BackendReader, error) {
func (k *Kube) getClient() (*rest.Config, error) {

Check warning on line 34 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L34

Added line #L34 was not covered by tests
ccfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{
ExplicitPath: k.ConfigFilePath,
Expand All @@ -47,6 +51,15 @@ func (k *Kube) Backend(ctx context.Context) (handler.BackendReader, error) {
return nil, err
}

return config, nil

Check warning on line 54 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L54

Added line #L54 was not covered by tests
}

func (k *Kube) backend(ctx context.Context) (handler.BackendReader, error) {
config, err := k.getClient()
if err != nil {
return nil, err

Check warning on line 60 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L57-L60

Added lines #L57 - L60 were not covered by tests
}

kb, err := kube.NewBackend(config)
if err != nil {
return nil, err
Expand All @@ -62,7 +75,7 @@ func (k *Kube) Backend(ctx context.Context) (handler.BackendReader, error) {
return kb, nil
}

func (s *File) Backend(ctx context.Context, logger logr.Logger) (handler.BackendReader, error) {
func (s *File) backend(ctx context.Context, logger logr.Logger) (handler.BackendReader, error) {

Check warning on line 78 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L78

Added line #L78 was not covered by tests
f, err := file.NewWatcher(logger, s.FilePath)
if err != nil {
return nil, err
Expand All @@ -72,3 +85,82 @@ func (s *File) Backend(ctx context.Context, logger logr.Logger) (handler.Backend

return f, nil
}

// discoverTrustedProxies will use the Kubernetes client to discover the CIDR Ranges for Pods in cluster.
func (k *Kube) discoverTrustedProxies(ctx context.Context, l logr.Logger, trustedProxies []string) []string {
config, err := k.getClient()
if err != nil {
l.Error(err, "failed to get Kubernetes client config")
return nil

Check warning on line 94 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L90-L94

Added lines #L90 - L94 were not covered by tests
}
c, err := corev1client.NewForConfig(config)
if err != nil {
l.Error(err, "failed to create Kubernetes client")
return nil

Check warning on line 99 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L96-L99

Added lines #L96 - L99 were not covered by tests
}

return combinedCIDRs(ctx, l, c, trustedProxies)

Check warning on line 102 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L102

Added line #L102 was not covered by tests
}

// combinedCIDRs returns the CIDR Ranges for Pods in cluster. Not all Kubernetes distributions provide a way to discover the entire podCIDR.
// Some distributions just provide the podCIDRs assigned to each node. combinedCIDRs tries all known locations where pod CIDRs might exist.
// For example, if a cluster has 3 nodes, each with a /24 podCIDR, and the cluster has a /16 podCIDR, combinedCIDRs will return 4 CIDR ranges.
func combinedCIDRs(ctx context.Context, l logr.Logger, c *corev1client.CoreV1Client, trustedProxies []string) []string {
if podCIDRS, err := perNodePodCIDRs(ctx, c); err == nil {
trustedProxies = append(trustedProxies, podCIDRS...)
} else {
l.V(1).Info("failed to get per node podCIDRs", "err", err)

Check warning on line 112 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L108-L112

Added lines #L108 - L112 were not covered by tests
}

if clusterCIDR, err := clusterPodCIDR(ctx, c); err == nil {
trustedProxies = append(trustedProxies, clusterCIDR...)
} else {
l.V(1).Info("failed to get cluster wide podCIDR", "err", err)

Check warning on line 118 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L115-L118

Added lines #L115 - L118 were not covered by tests
}

return trustedProxies

Check warning on line 121 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L121

Added line #L121 was not covered by tests
}

// perNodePodCIDRs returns the CIDR Range for Pods on each node. This is the per node podCIDR as compared to the total podCIDR.
// This will get the podCIDR from each node in the cluster, not the entire cluster podCIDR. If a cluster grows after this is run,
// the new nodes will not be included until this func is run again.
// This should be used in conjunction with ClusterPodCIDR to be as complete and cross distribution compatible as possible.
func perNodePodCIDRs(ctx context.Context, c *corev1client.CoreV1Client) ([]string, error) {
ns, err := c.Nodes().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err

Check warning on line 131 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L128-L131

Added lines #L128 - L131 were not covered by tests
}

var trustedProxies []string
for _, n := range ns.Items {
trustedProxies = append(trustedProxies, n.Spec.PodCIDRs...)

Check warning on line 136 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L134-L136

Added lines #L134 - L136 were not covered by tests
}

return trustedProxies, nil

Check warning on line 139 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L139

Added line #L139 was not covered by tests
}

// clusterPodCIDR returns the CIDR Range for Pods in cluster. This is the total podCIDR as compared to the per node podCIDR.
// Some Kubernetes distributions do not run a kube-controller-manager pod, so this func should be used in conjunction with PerNodePodCIDRs
// to be as complete and cross distribution compatible as possible.
func clusterPodCIDR(ctx context.Context, c *corev1client.CoreV1Client) ([]string, error) {

Check warning on line 145 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L145

Added line #L145 was not covered by tests
// https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/
pods, err := c.Pods("kube-system").List(ctx, metav1.ListOptions{
LabelSelector: "component=kube-controller-manager",
})
if err != nil {
return nil, err

Check warning on line 151 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L147-L151

Added lines #L147 - L151 were not covered by tests
}

var trustedProxies []string
for _, p := range pods.Items {
for _, c := range p.Spec.Containers {
for _, e := range c.Command {
if strings.HasPrefix(e, "--cluster-cidr") {
trustedProxies = append(trustedProxies, strings.Split(e, "=")[1])

Check warning on line 159 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L154-L159

Added lines #L154 - L159 were not covered by tests
}
}
}
}

return trustedProxies, nil

Check warning on line 165 in cmd/smee/backend.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/backend.go#L165

Added line #L165 was not covered by tests
}
1 change: 1 addition & 0 deletions cmd/smee/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func ipxeHTTPScriptFlags(c *config, fs *flag.FlagSet) {
fs.StringVar(&c.ipxeHTTPScript.bindAddr, "http-addr", detectPublicIPv4(":80"), "[http] local IP:Port to listen on for iPXE HTTP script requests")
fs.StringVar(&c.ipxeHTTPScript.extraKernelArgs, "extra-kernel-args", "", "[http] extra set of kernel args (k=v k=v) that are appended to the kernel cmdline iPXE script")
fs.StringVar(&c.ipxeHTTPScript.trustedProxies, "trusted-proxies", "", "[http] comma separated list of trusted proxies in CIDR notation")
fs.BoolVar(&c.ipxeHTTPScript.disableDiscoverTrustedProxies, "disable-discover-trusted-proxies", false, "[http] disable discovery of trusted proxies from Kubernetes, only available for the Kubernetes backend")
fs.StringVar(&c.ipxeHTTPScript.hookURL, "osie-url", "", "[http] URL where OSIE (HookOS) images are located")
fs.StringVar(&c.ipxeHTTPScript.tinkServer, "tink-server", "", "[http] IP:Port for the Tink server")
fs.BoolVar(&c.ipxeHTTPScript.tinkServerUseTLS, "tink-server-tls", false, "[http] use TLS for Tink server")
Expand Down
1 change: 1 addition & 0 deletions cmd/smee/flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ FLAGS
-dhcp-ip-for-packet [dhcp] IP address to use in DHCP packets (opt 54, etc) (default "%[1]v")
-dhcp-syslog-ip [dhcp] Syslog server IP address to use in DHCP packets (opt 7) (default "%[1]v")
-dhcp-tftp-ip [dhcp] TFTP server IP address to use in DHCP packets (opt 66, etc) (default "%[1]v:69")
-disable-discover-trusted-proxies [http] disable discovery of trusted proxies from Kubernetes, only available for the Kubernetes backend (default "false")
-extra-kernel-args [http] extra set of kernel args (k=v k=v) that are appended to the kernel cmdline iPXE script
-http-addr [http] local IP:Port to listen on for iPXE HTTP script requests (default "%[1]v:80")
-http-ipxe-binary-enabled [http] enable iPXE HTTP binary server (default "true")
Expand Down
31 changes: 18 additions & 13 deletions cmd/smee/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ type ipxeHTTPBinary struct {
}

type ipxeHTTPScript struct {
enabled bool
bindAddr string
extraKernelArgs string
hookURL string
tinkServer string
tinkServerUseTLS bool
trustedProxies string
enabled bool
bindAddr string
extraKernelArgs string
hookURL string
tinkServer string
tinkServerUseTLS bool
trustedProxies string
disableDiscoverTrustedProxies bool
}

type dhcpConfig struct {
Expand Down Expand Up @@ -179,13 +180,13 @@ func main() {
case cfg.backends.file.Enabled && cfg.backends.kubernetes.Enabled:
panic("only one backend can be enabled at a time")
case cfg.backends.file.Enabled:
b, err := cfg.backends.file.Backend(ctx, log)
b, err := cfg.backends.file.backend(ctx, log)

Check warning on line 183 in cmd/smee/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/main.go#L183

Added line #L183 was not covered by tests
if err != nil {
panic(fmt.Errorf("failed to run file backend: %w", err))
}
br = b
default: // default backend is kubernetes
b, err := cfg.backends.kubernetes.Backend(ctx)
b, err := cfg.backends.kubernetes.backend(ctx)

Check warning on line 189 in cmd/smee/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/main.go#L189

Added line #L189 was not covered by tests
if err != nil {
panic(fmt.Errorf("failed to run kubernetes backend: %w", err))
}
Expand All @@ -207,13 +208,17 @@ func main() {

if len(handlers) > 0 {
// start the http server for ipxe binaries and scripts
tp := parseTrustedProxies(cfg.ipxeHTTPScript.trustedProxies)
if cfg.backends.kubernetes.Enabled {
tp = cfg.backends.kubernetes.discoverTrustedProxies(ctx, log, tp)

Check warning on line 213 in cmd/smee/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/main.go#L211-L213

Added lines #L211 - L213 were not covered by tests
}
httpServer := &http.Config{
GitRev: GitRev,
StartTime: startTime,
Logger: log,
TrustedProxies: parseTrustedProxies(cfg.ipxeHTTPScript.trustedProxies),
TrustedProxies: tp,

Check warning on line 219 in cmd/smee/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/main.go#L219

Added line #L219 was not covered by tests
}
log.Info("serving http", "addr", cfg.ipxeHTTPScript.bindAddr)
log.Info("serving http", "addr", cfg.ipxeHTTPScript.bindAddr, "trusted_proxies", tp)

Check warning on line 221 in cmd/smee/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/main.go#L221

Added line #L221 was not covered by tests
g.Go(func() error {
return httpServer.ServeHTTP(ctx, cfg.ipxeHTTPScript.bindAddr, handlers)
})
Expand Down Expand Up @@ -302,13 +307,13 @@ func (c *config) dhcpHandler(ctx context.Context, log logr.Logger) (*reservation
case c.backends.file.Enabled && c.backends.kubernetes.Enabled:
panic("only one backend can be enabled at a time")
case c.backends.file.Enabled:
b, err := c.backends.file.Backend(ctx, log)
b, err := c.backends.file.backend(ctx, log)

Check warning on line 310 in cmd/smee/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/main.go#L310

Added line #L310 was not covered by tests
if err != nil {
return nil, fmt.Errorf("failed to create file backend: %w", err)

Check warning on line 312 in cmd/smee/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/main.go#L312

Added line #L312 was not covered by tests
}
dh.Backend = b
default: // default backend is kubernetes
b, err := c.backends.kubernetes.Backend(ctx)
b, err := c.backends.kubernetes.backend(ctx)

Check warning on line 316 in cmd/smee/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/main.go#L316

Added line #L316 was not covered by tests
if err != nil {
return nil, fmt.Errorf("failed to create kubernetes backend: %w", err)

Check warning on line 318 in cmd/smee/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/smee/main.go#L318

Added line #L318 was not covered by tests
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
go.opentelemetry.io/otel/trace v1.19.0
go.uber.org/zap v1.26.0
golang.org/x/sync v0.5.0
k8s.io/apimachinery v0.28.4
k8s.io/client-go v0.28.4
)

Expand Down Expand Up @@ -93,7 +94,6 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.28.4 // indirect
k8s.io/apimachinery v0.28.4 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230928205116-a78145627833 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
Expand Down

0 comments on commit a693b3f

Please sign in to comment.