Skip to content

Commit 2f7f77a

Browse files
committed
feat: calculate default no_proxy values based on cluster
1 parent 2ba39a7 commit 2f7f77a

File tree

9 files changed

+296
-78
lines changed

9 files changed

+296
-78
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
@@ -79,7 +79,7 @@ func main() {
7979
servicelbgc.New(client),
8080
calico.New(client, calicoCNIConfig),
8181
httpproxy.NewVariable(),
82-
httpproxy.NewPatch(),
82+
httpproxy.NewPatch(client),
8383
extraapiservercertsans.NewVariable(),
8484
extraapiservercertsans.NewPatch(),
8585
)

docs/content/http-proxy.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,17 @@ spec:
3232
topology:
3333
variables:
3434
- name: proxy
35-
value:
35+
values:
3636
http: http://example.com
37-
https: https://example.com
38-
no:
37+
https: http://example.com
38+
additionalNo:
3939
- no-proxy-1.example.com
4040
- 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+
`localhost,127.0.0.1,<POD CIDRS>,<SERVICE CIDRS>,kubernetes,kubernetes.default,.svc,.svc.cluster.local`.
45+
4346
Applying this configuration will result in new bootstrap files on the `KubeadmControlPlaneTemplate`
4447
and `KubeadmConfigTemplate`.
4548

pkg/handlers/httpproxy/inject.go

Lines changed: 89 additions & 7 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"
17+
"k8s.io/apimachinery/pkg/types"
18+
capiv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1219
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
1320
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
1421
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
1522
"sigs.k8s.io/cluster-api/exp/runtime/topologymutation"
1623
ctrl "sigs.k8s.io/controller-runtime"
17-
"sigs.k8s.io/controller-runtime/pkg/client"
24+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
1825

1926
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/handlers"
2027
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/handlers/mutation"
@@ -30,14 +37,15 @@ const (
3037

3138
type httpProxyPatchHandler struct {
3239
decoder runtime.Decoder
40+
client ctrlclient.Reader
3341
}
3442

3543
var (
3644
_ handlers.Named = &httpProxyPatchHandler{}
3745
_ mutation.GeneratePatches = &httpProxyPatchHandler{}
3846
)
3947

40-
func NewPatch() *httpProxyPatchHandler {
48+
func NewPatch(cl ctrlclient.Reader) *httpProxyPatchHandler {
4149
scheme := runtime.NewScheme()
4250
_ = bootstrapv1.AddToScheme(scheme)
4351
_ = controlplanev1.AddToScheme(scheme)
@@ -46,6 +54,7 @@ func NewPatch() *httpProxyPatchHandler {
4654
controlplanev1.GroupVersion,
4755
bootstrapv1.GroupVersion,
4856
),
57+
client: cl,
4958
}
5059
}
5160

@@ -58,6 +67,12 @@ func (h *httpProxyPatchHandler) GeneratePatches(
5867
req *runtimehooksv1.GeneratePatchesRequest,
5968
resp *runtimehooksv1.GeneratePatchesResponse,
6069
) {
70+
log := ctrl.LoggerFrom(ctx)
71+
noProxy, err := h.detectNoProxy(ctx, req)
72+
if err != nil {
73+
log.Error(err, "failed to resolve no proxy value")
74+
}
75+
6176
topologymutation.WalkTemplates(
6277
ctx,
6378
h.decoder,
@@ -69,7 +84,7 @@ func (h *httpProxyPatchHandler) GeneratePatches(
6984
vars map[string]apiextensionsv1.JSON,
7085
holderRef runtimehooksv1.HolderReference,
7186
) error {
72-
log := ctrl.LoggerFrom(ctx).WithValues(
87+
log = log.WithValues(
7388
"holderRef", holderRef,
7489
)
7590

@@ -92,11 +107,11 @@ func (h *httpProxyPatchHandler) GeneratePatches(
92107
func(obj *controlplanev1.KubeadmControlPlaneTemplate) error {
93108
log.WithValues(
94109
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
95-
"patchedObjectName", client.ObjectKeyFromObject(obj),
110+
"patchedObjectName", ctrlclient.ObjectKeyFromObject(obj),
96111
).Info("adding files to control plane kubeadm config spec")
97112
obj.Spec.Template.Spec.KubeadmConfigSpec.Files = append(
98113
obj.Spec.Template.Spec.KubeadmConfigSpec.Files,
99-
generateSystemdFiles(httpProxyVariable)...,
114+
generateSystemdFiles(httpProxyVariable, noProxy)...,
100115
)
101116
return nil
102117
}); err != nil {
@@ -108,11 +123,11 @@ func (h *httpProxyPatchHandler) GeneratePatches(
108123
func(obj *bootstrapv1.KubeadmConfigTemplate) error {
109124
log.WithValues(
110125
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
111-
"patchedObjectName", client.ObjectKeyFromObject(obj),
126+
"patchedObjectName", ctrlclient.ObjectKeyFromObject(obj),
112127
).Info("adding files to worker node kubeadm config template")
113128
obj.Spec.Template.Spec.Files = append(
114129
obj.Spec.Template.Spec.Files,
115-
generateSystemdFiles(httpProxyVariable)...,
130+
generateSystemdFiles(httpProxyVariable, noProxy)...,
116131
)
117132
return nil
118133
}); err != nil {
@@ -123,3 +138,70 @@ func (h *httpProxyPatchHandler) GeneratePatches(
123138
},
124139
)
125140
}
141+
142+
func (h *httpProxyPatchHandler) detectNoProxy(
143+
ctx context.Context,
144+
req *runtimehooksv1.GeneratePatchesRequest,
145+
) ([]string, error) {
146+
clusterKey := types.NamespacedName{}
147+
148+
for i := range req.Items {
149+
item := req.Items[i]
150+
if item.HolderReference.Kind == "Cluster" &&
151+
item.HolderReference.APIVersion == capiv1.GroupVersion.String() {
152+
clusterKey.Name = item.HolderReference.Name
153+
clusterKey.Namespace = item.HolderReference.Namespace
154+
}
155+
}
156+
157+
if clusterKey.Name == "" {
158+
return nil, errors.New("failed to detect cluster name from GeneratePatch request")
159+
}
160+
161+
cluster := &capiv1.Cluster{}
162+
if err := h.client.Get(ctx, clusterKey, cluster); err != nil {
163+
return nil, err
164+
}
165+
166+
return generateNoProxy(cluster), nil
167+
}
168+
169+
// generateNoProxy creates default NO_PROXY values that should be applied on cluster
170+
// in any environment and are preventing the use of proxy for cluster internal
171+
// networking.
172+
func generateNoProxy(cluster *capiv1.Cluster) []string {
173+
noProxy := []string{
174+
"localhost",
175+
"127.0.0.1",
176+
}
177+
178+
if cluster.Spec.ClusterNetwork != nil &&
179+
cluster.Spec.ClusterNetwork.Pods != nil {
180+
noProxy = append(noProxy, cluster.Spec.ClusterNetwork.Pods.CIDRBlocks...)
181+
}
182+
183+
if cluster.Spec.ClusterNetwork != nil &&
184+
cluster.Spec.ClusterNetwork.Services != nil {
185+
noProxy = append(noProxy, cluster.Spec.ClusterNetwork.Services.CIDRBlocks...)
186+
}
187+
188+
serviceDomain := "cluster.local"
189+
if cluster.Spec.ClusterNetwork != nil &&
190+
cluster.Spec.ClusterNetwork.ServiceDomain != "" {
191+
serviceDomain = cluster.Spec.ClusterNetwork.ServiceDomain
192+
}
193+
194+
noProxy = append(noProxy, []string{
195+
"kubernetes",
196+
"kubernetes.default",
197+
".svc",
198+
}...)
199+
200+
// append .svc.<SERVICE_DOMAIN>
201+
noProxy = append(
202+
noProxy,
203+
fmt.Sprintf(".svc.%s", strings.TrimLeft(serviceDomain, ".")),
204+
)
205+
206+
return noProxy
207+
}

pkg/handlers/httpproxy/inject_test.go

Lines changed: 109 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@ import (
88

99
. "github.com/onsi/gomega"
1010
"k8s.io/apiserver/pkg/storage/names"
11+
capiv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1112
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
13+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1214

1315
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/testutils/capitest"
1416
)
1517

1618
func TestGeneratePatches(t *testing.T) {
1719
capitest.ValidateGeneratePatches(
1820
t,
19-
NewPatch,
21+
func() *httpProxyPatchHandler {
22+
fakeClient := fake.NewClientBuilder().Build()
23+
return NewPatch(fakeClient)
24+
},
2025
capitest.PatchTestDef{
2126
Name: "unset variable",
2227
},
@@ -26,9 +31,9 @@ func TestGeneratePatches(t *testing.T) {
2631
capitest.VariableWithValue(
2732
VariableName,
2833
HTTPProxyVariables{
29-
HTTP: "http://example.com",
30-
HTTPS: "https://example.com",
31-
No: []string{"no-proxy.example.com"},
34+
HTTP: "http://example.com",
35+
HTTPS: "https://example.com",
36+
AdditionalNo: []string{"no-proxy.example.com"},
3237
},
3338
),
3439
capitest.VariableWithValue(
@@ -53,9 +58,9 @@ func TestGeneratePatches(t *testing.T) {
5358
capitest.VariableWithValue(
5459
VariableName,
5560
HTTPProxyVariables{
56-
HTTP: "http://example.com",
57-
HTTPS: "https://example.com",
58-
No: []string{"no-proxy.example.com"},
61+
HTTP: "http://example.com",
62+
HTTPS: "https://example.com",
63+
AdditionalNo: []string{"no-proxy.example.com"},
5964
},
6065
),
6166
capitest.VariableWithValue(
@@ -80,9 +85,9 @@ func TestGeneratePatches(t *testing.T) {
8085
capitest.VariableWithValue(
8186
VariableName,
8287
HTTPProxyVariables{
83-
HTTP: "http://example.com",
84-
HTTPS: "https://example.com",
85-
No: []string{"no-proxy.example.com"},
88+
HTTP: "http://example.com",
89+
HTTPS: "https://example.com",
90+
AdditionalNo: []string{"no-proxy.example.com"},
8691
},
8792
),
8893
},
@@ -95,3 +100,97 @@ func TestGeneratePatches(t *testing.T) {
95100
},
96101
)
97102
}
103+
104+
func TestGenerateNoProxy(t *testing.T) {
105+
t.Parallel()
106+
g := NewWithT(t)
107+
108+
testCases := []struct {
109+
name string
110+
cluster *capiv1.Cluster
111+
expectedNoProxy []string
112+
}{
113+
{
114+
name: "no networking config",
115+
cluster: &capiv1.Cluster{},
116+
expectedNoProxy: []string{
117+
"localhost", "127.0.0.1", "kubernetes", "kubernetes.default",
118+
".svc", ".svc.cluster.local",
119+
},
120+
},
121+
{
122+
name: "custom pod network",
123+
cluster: &capiv1.Cluster{
124+
Spec: capiv1.ClusterSpec{
125+
ClusterNetwork: &capiv1.ClusterNetwork{
126+
Pods: &capiv1.NetworkRanges{
127+
CIDRBlocks: []string{"10.0.0.0/24", "10.0.1.0/24"},
128+
},
129+
},
130+
},
131+
},
132+
expectedNoProxy: []string{
133+
"localhost", "127.0.0.1", "10.0.0.0/24", "10.0.1.0/24", "kubernetes",
134+
"kubernetes.default", ".svc", ".svc.cluster.local",
135+
},
136+
},
137+
{
138+
name: "custom service network",
139+
cluster: &capiv1.Cluster{
140+
Spec: capiv1.ClusterSpec{
141+
ClusterNetwork: &capiv1.ClusterNetwork{
142+
Services: &capiv1.NetworkRanges{
143+
CIDRBlocks: []string{"172.16.0.0/24", "172.16.1.0/24"},
144+
},
145+
},
146+
},
147+
},
148+
expectedNoProxy: []string{
149+
"localhost", "127.0.0.1", "172.16.0.0/24", "172.16.1.0/24", "kubernetes",
150+
"kubernetes.default", ".svc", ".svc.cluster.local",
151+
},
152+
},
153+
{
154+
name: "custom servicedomain",
155+
cluster: &capiv1.Cluster{
156+
Spec: capiv1.ClusterSpec{
157+
ClusterNetwork: &capiv1.ClusterNetwork{
158+
ServiceDomain: "foo.bar",
159+
},
160+
},
161+
},
162+
expectedNoProxy: []string{
163+
"localhost", "127.0.0.1", "kubernetes", "kubernetes.default",
164+
".svc", ".svc.foo.bar",
165+
},
166+
},
167+
{
168+
name: "all options",
169+
cluster: &capiv1.Cluster{
170+
Spec: capiv1.ClusterSpec{
171+
ClusterNetwork: &capiv1.ClusterNetwork{
172+
Pods: &capiv1.NetworkRanges{
173+
CIDRBlocks: []string{"10.10.0.0/16"},
174+
},
175+
Services: &capiv1.NetworkRanges{
176+
CIDRBlocks: []string{"172.16.0.0/16"},
177+
},
178+
ServiceDomain: "foo.bar",
179+
},
180+
},
181+
},
182+
expectedNoProxy: []string{
183+
"localhost", "127.0.0.1", "10.10.0.0/16", "172.16.0.0/16", "kubernetes",
184+
"kubernetes.default", ".svc", ".svc.foo.bar",
185+
},
186+
},
187+
}
188+
189+
for _, tc := range testCases {
190+
tc := tc
191+
t.Run(tc.name, func(tt *testing.T) {
192+
tt.Parallel()
193+
g.Expect(generateNoProxy(tc.cluster)).To(Equal(tc.expectedNoProxy))
194+
})
195+
}
196+
}

0 commit comments

Comments
 (0)