Skip to content

Commit 03c7f81

Browse files
committed
feat: calculate default no_proxy values based on cluster
1 parent 7b7fde3 commit 03c7f81

File tree

8 files changed

+252
-33
lines changed

8 files changed

+252
-33
lines changed

charts/capi-runtime-extensions/templates/role.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,11 @@ rules:
3030
- patch
3131
- update
3232
- watch
33+
- apiGroups:
34+
- cluster.x-k8s.io
35+
resources:
36+
- clusters
37+
verbs:
38+
- get
39+
- list
40+
- watch

cmd/capi-runtime-extensions/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func main() {
7878
servicelbgc.New(client),
7979
calico.New(client, calicoCNIConfig),
8080
httpproxy.NewVariable(),
81-
httpproxy.NewPatch(),
81+
httpproxy.NewPatch(client),
8282
)
8383

8484
// Initialize and parse command line flags.

docs/content/http-proxy.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,17 @@ spec:
3535
values:
3636
http: http://example.com
3737
https: http://example.com
38-
no:
38+
additionalNo:
3939
- http://no-proxy-1.example.com
4040
- http://no-proxy-2.example.com
4141
```
4242
43+
The `additionalNo` list will be added to default pre-calculated values that apply on k8s networking.
44+
45+
```
46+
localhost,127.0.0.1,<POD CIDRS>,<SERVICE CIDRS>,kubernetes,.svc,.svc.cluster,.svc.cluster.local
47+
```
48+
4349
Applying this configuration will result in new bootstrap files on the `KubeadmControlPlaneTemplate`
4450
and `KubeadmConfigTemplate`.
4551

pkg/handlers/httpproxy/inject.go

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
// Copyright 2023 D2iQ, Inc. All rights reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters,verbs=watch;list;get
5+
46
package httpproxy
57

68
import (
79
"context"
10+
"errors"
11+
"fmt"
12+
"strings"
813

914
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1015
"k8s.io/apimachinery/pkg/runtime"
1116
"k8s.io/apimachinery/pkg/runtime/serializer"
1217
"k8s.io/apimachinery/pkg/types"
18+
capiv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1319
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
1420
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
1521
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
1622
"sigs.k8s.io/cluster-api/exp/runtime/topologymutation"
1723
ctrl "sigs.k8s.io/controller-runtime"
24+
ctrclient "sigs.k8s.io/controller-runtime/pkg/client"
1825

1926
"github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/patches"
2027
"github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/patches/selectors"
@@ -29,14 +36,15 @@ const (
2936

3037
type httpProxyPatchHandler struct {
3138
decoder runtime.Decoder
39+
client ctrclient.Reader
3240
}
3341

3442
var (
3543
_ handlers.NamedHandler = &httpProxyPatchHandler{}
3644
_ handlers.GeneratePatchesMutationHandler = &httpProxyPatchHandler{}
3745
)
3846

39-
func NewPatch() *httpProxyPatchHandler {
47+
func NewPatch(cl ctrclient.Reader) *httpProxyPatchHandler {
4048
scheme := runtime.NewScheme()
4149
_ = bootstrapv1.AddToScheme(scheme)
4250
_ = controlplanev1.AddToScheme(scheme)
@@ -45,6 +53,7 @@ func NewPatch() *httpProxyPatchHandler {
4553
controlplanev1.GroupVersion,
4654
bootstrapv1.GroupVersion,
4755
),
56+
client: cl,
4857
}
4958
}
5059

@@ -57,6 +66,12 @@ func (h *httpProxyPatchHandler) GeneratePatches(
5766
req *runtimehooksv1.GeneratePatchesRequest,
5867
resp *runtimehooksv1.GeneratePatchesResponse,
5968
) {
69+
log := ctrl.LoggerFrom(ctx)
70+
noProxy, err := h.detectNoProxy(ctx, req)
71+
if err != nil {
72+
log.Error(err, "failed to resolve no proxy value")
73+
}
74+
6075
topologymutation.WalkTemplates(
6176
ctx,
6277
h.decoder,
@@ -68,7 +83,7 @@ func (h *httpProxyPatchHandler) GeneratePatches(
6883
vars map[string]apiextensionsv1.JSON,
6984
holderRef runtimehooksv1.HolderReference,
7085
) error {
71-
log := ctrl.LoggerFrom(ctx).WithValues(
86+
log = log.WithValues(
7287
"holderRef", holderRef,
7388
)
7489

@@ -95,7 +110,7 @@ func (h *httpProxyPatchHandler) GeneratePatches(
95110
}).Info("adding files to kubeadm config spec")
96111
obj.Spec.Template.Spec.KubeadmConfigSpec.Files = append(
97112
obj.Spec.Template.Spec.KubeadmConfigSpec.Files,
98-
generateSystemdFiles(httpProxyVariable)...,
113+
generateSystemdFiles(httpProxyVariable, noProxy)...,
99114
)
100115
return nil
101116
}); err != nil {
@@ -111,7 +126,7 @@ func (h *httpProxyPatchHandler) GeneratePatches(
111126
}).Info("adding files to worker node kubeadm config template")
112127
obj.Spec.Template.Spec.Files = append(
113128
obj.Spec.Template.Spec.Files,
114-
generateSystemdFiles(httpProxyVariable)...,
129+
generateSystemdFiles(httpProxyVariable, noProxy)...,
115130
)
116131
return nil
117132
}); err != nil {
@@ -122,3 +137,77 @@ func (h *httpProxyPatchHandler) GeneratePatches(
122137
},
123138
)
124139
}
140+
141+
func (h *httpProxyPatchHandler) detectNoProxy(
142+
ctx context.Context,
143+
req *runtimehooksv1.GeneratePatchesRequest,
144+
) ([]string, error) {
145+
clusterKey := types.NamespacedName{}
146+
147+
for _, item := range req.Items {
148+
if item.HolderReference.Kind == "Cluster" && item.HolderReference.APIVersion == capiv1.GroupVersion.String() {
149+
clusterKey.Name = item.HolderReference.Name
150+
clusterKey.Namespace = item.HolderReference.Namespace
151+
}
152+
}
153+
154+
if clusterKey.Name == "" {
155+
return nil, errors.New("failed to detect cluster name from GeneratePatch request")
156+
}
157+
158+
cluster := &capiv1.Cluster{}
159+
if err := h.client.Get(ctx, clusterKey, cluster); err != nil {
160+
return nil, err
161+
}
162+
163+
return generateNoProxy(cluster), nil
164+
}
165+
166+
// generateNoProxy creates default NO_PROXY values that should be applied on cluster
167+
// in any environment and are preventing the use of proxy for cluster internal
168+
// networking.
169+
func generateNoProxy(cluster *capiv1.Cluster) []string {
170+
noProxy := []string{
171+
"localhost",
172+
"127.0.0.1",
173+
}
174+
175+
if cluster.Spec.ClusterNetwork != nil &&
176+
cluster.Spec.ClusterNetwork.Pods != nil {
177+
noProxy = append(noProxy, cluster.Spec.ClusterNetwork.Pods.CIDRBlocks...)
178+
}
179+
180+
if cluster.Spec.ClusterNetwork != nil &&
181+
cluster.Spec.ClusterNetwork.Services != nil {
182+
noProxy = append(noProxy, cluster.Spec.ClusterNetwork.Services.CIDRBlocks...)
183+
}
184+
185+
serviceDomain := "cluster.local"
186+
if cluster.Spec.ClusterNetwork != nil &&
187+
cluster.Spec.ClusterNetwork.ServiceDomain != "" {
188+
serviceDomain = cluster.Spec.ClusterNetwork.ServiceDomain
189+
}
190+
191+
noProxy = append(noProxy, []string{
192+
"kubernetes",
193+
"kubernetes.default",
194+
".svc",
195+
}...)
196+
197+
noProxy = append(noProxy, mapServiceDomain(serviceDomain, ".svc")...)
198+
199+
return noProxy
200+
}
201+
202+
// mapServiceDomain generates NO_PROXY values for service domain to allow use
203+
// of short service name in cluster.
204+
// Example: my.custom => [.svc, .svc.my, .svc.my.custom]
205+
func mapServiceDomain(serviceDomain, svcName string) []string {
206+
mapped := []string{svcName}
207+
progress := svcName
208+
for _, part := range strings.Split(serviceDomain, ".") {
209+
progress = fmt.Sprintf("%s.%s", progress, part)
210+
mapped = append(mapped, progress)
211+
}
212+
return mapped
213+
}

pkg/handlers/httpproxy/inject_test.go

Lines changed: 109 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2023 D2iQ, Inc. All rights reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
package httpproxy_test
4+
package httpproxy
55

66
import (
77
"bytes"
@@ -16,16 +16,17 @@ import (
1616
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1717
"k8s.io/apimachinery/pkg/runtime"
1818
"k8s.io/apimachinery/pkg/types"
19+
capiv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1920
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
2021
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
2122
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
22-
23-
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/httpproxy"
23+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
2424
)
2525

2626
func TestGeneratePatches(t *testing.T) {
2727
g := NewWithT(t)
28-
h := httpproxy.NewPatch()
28+
fakeClient := fake.NewClientBuilder().Build()
29+
h := NewPatch(fakeClient)
2930
req := &runtimehooksv1.GeneratePatchesRequest{}
3031
resp := &runtimehooksv1.GeneratePatchesResponse{}
3132
h.GeneratePatches(context.Background(), req, resp)
@@ -34,13 +35,14 @@ func TestGeneratePatches(t *testing.T) {
3435

3536
func TestGeneratePatches_KubeadmControlPlaneTemplate(t *testing.T) {
3637
g := NewWithT(t)
37-
h := httpproxy.NewPatch()
38+
fakeClient := fake.NewClientBuilder().Build()
39+
h := NewPatch(fakeClient)
3840
req := &runtimehooksv1.GeneratePatchesRequest{
3941
Variables: []runtimehooksv1.Variable{
40-
newVariable(httpproxy.VariableName, httpproxy.HTTPProxyVariables{
41-
HTTP: "http://example.com",
42-
HTTPS: "https://example.com",
43-
NO: []string{"https://no-proxy.example.com"},
42+
newVariable(VariableName, HTTPProxyVariables{
43+
HTTP: "http://example.com",
44+
HTTPS: "https://example.com",
45+
AdditionalNO: []string{"https://no-proxy.example.com"},
4446
}),
4547
},
4648
Items: []runtimehooksv1.GeneratePatchesRequestItem{
@@ -84,13 +86,14 @@ func TestGeneratePatches_KubeadmControlPlaneTemplate(t *testing.T) {
8486

8587
func TestGeneratePatches_KubeadmConfigTemplate(t *testing.T) {
8688
g := NewWithT(t)
87-
h := httpproxy.NewPatch()
89+
fakeClient := fake.NewClientBuilder().Build()
90+
h := NewPatch(fakeClient)
8891
req := &runtimehooksv1.GeneratePatchesRequest{
8992
Variables: []runtimehooksv1.Variable{
90-
newVariable(httpproxy.VariableName, httpproxy.HTTPProxyVariables{
91-
HTTP: "http://example.com",
92-
HTTPS: "https://example.com",
93-
NO: []string{"https://no-proxy.example.com"},
93+
newVariable(VariableName, HTTPProxyVariables{
94+
HTTP: "http://example.com",
95+
HTTPS: "https://example.com",
96+
AdditionalNO: []string{"https://no-proxy.example.com"},
9497
}),
9598
newVariable("builtin", map[string]any{
9699
"machineDeployment": map[string]any{
@@ -137,6 +140,98 @@ func TestGeneratePatches_KubeadmConfigTemplate(t *testing.T) {
137140
})))
138141
}
139142

143+
func TestGenerateNoProxy(t *testing.T) {
144+
t.Parallel()
145+
g := NewWithT(t)
146+
147+
testCases := []struct {
148+
name string
149+
cluster *capiv1.Cluster
150+
expectedNoProxy []string
151+
}{
152+
{
153+
name: "no networking config",
154+
cluster: &capiv1.Cluster{},
155+
expectedNoProxy: []string{
156+
"localhost", "127.0.0.1", "kubernetes", "kubernetes.default",
157+
".svc", ".svc", ".svc.cluster", ".svc.cluster.local",
158+
},
159+
},
160+
{
161+
name: "custom pod network",
162+
cluster: &capiv1.Cluster{
163+
Spec: capiv1.ClusterSpec{
164+
ClusterNetwork: &capiv1.ClusterNetwork{
165+
Pods: &capiv1.NetworkRanges{
166+
CIDRBlocks: []string{"10.0.0.0/24", "10.0.1.0/24"},
167+
},
168+
},
169+
},
170+
},
171+
expectedNoProxy: []string{
172+
"localhost", "127.0.0.1", "10.0.0.0/24", "10.0.1.0/24", "kubernetes", "kubernetes.default",
173+
".svc", ".svc", ".svc.cluster", ".svc.cluster.local",
174+
},
175+
},
176+
{
177+
name: "custom service network",
178+
cluster: &capiv1.Cluster{
179+
Spec: capiv1.ClusterSpec{
180+
ClusterNetwork: &capiv1.ClusterNetwork{
181+
Services: &capiv1.NetworkRanges{
182+
CIDRBlocks: []string{"172.16.0.0/24", "172.16.1.0/24"},
183+
},
184+
},
185+
},
186+
},
187+
expectedNoProxy: []string{
188+
"localhost", "127.0.0.1", "172.16.0.0/24", "172.16.1.0/24", "kubernetes", "kubernetes.default",
189+
".svc", ".svc", ".svc.cluster", ".svc.cluster.local",
190+
},
191+
},
192+
{
193+
name: "custom servicedomain",
194+
cluster: &capiv1.Cluster{
195+
Spec: capiv1.ClusterSpec{
196+
ClusterNetwork: &capiv1.ClusterNetwork{
197+
ServiceDomain: "foo.bar",
198+
},
199+
},
200+
},
201+
expectedNoProxy: []string{
202+
"localhost", "127.0.0.1", "kubernetes", "kubernetes.default",
203+
".svc", ".svc", ".svc.foo", ".svc.foo.bar",
204+
},
205+
},
206+
{
207+
name: "all options",
208+
cluster: &capiv1.Cluster{
209+
Spec: capiv1.ClusterSpec{
210+
ClusterNetwork: &capiv1.ClusterNetwork{
211+
Pods: &capiv1.NetworkRanges{
212+
CIDRBlocks: []string{"10.10.0.0/16"},
213+
},
214+
Services: &capiv1.NetworkRanges{
215+
CIDRBlocks: []string{"172.16.0.0/16"},
216+
},
217+
ServiceDomain: "foo.bar",
218+
},
219+
},
220+
},
221+
expectedNoProxy: []string{
222+
"localhost", "127.0.0.1", "10.10.0.0/16", "172.16.0.0/16", "kubernetes", "kubernetes.default",
223+
".svc", ".svc", ".svc.foo", ".svc.foo.bar",
224+
},
225+
},
226+
}
227+
228+
for _, tc := range testCases {
229+
t.Run(tc.name, func(tt *testing.T) {
230+
g.Expect(generateNoProxy(tc.cluster)).To(Equal(tc.expectedNoProxy))
231+
})
232+
}
233+
}
234+
140235
func toJSON(v any) []byte {
141236
data, err := json.Marshal(v)
142237
if err != nil {

0 commit comments

Comments
 (0)