Skip to content

Commit 67a7da5

Browse files
JGAntunesajp-io
andauthored
feat(host-preflights): add cidr preflight checks (#1355)
* feat(host-preflights): add cidr preflight checks * chore: adding unit tests * chore: fully test template rendering * chore: test flag logic * fix: address flag feedback * chore: change preflight exclude behaviour * Update failure wording * fix: preflight tests and template * chore: add getCIDRs CIDR only flag test --------- Co-authored-by: Alex Parker <[email protected]>
1 parent ea49d7d commit 67a7da5

File tree

6 files changed

+627
-4
lines changed

6 files changed

+627
-4
lines changed

cmd/embedded-cluster/install.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func RunHostPreflights(c *cli.Context, provider *defaults.Provider, applier *add
152152
return fmt.Errorf("unable to read host preflights: %w", err)
153153
}
154154

155-
data := preflights.TemplateData{
155+
data, err := preflights.TemplateData{
156156
ReplicatedAPIURL: replicatedAPIURL,
157157
ProxyRegistryURL: proxyRegistryURL,
158158
IsAirgap: isAirgap,
@@ -162,6 +162,10 @@ func RunHostPreflights(c *cli.Context, provider *defaults.Provider, applier *add
162162
K0sDataDir: provider.EmbeddedClusterK0sSubDir(),
163163
OpenEBSDataDir: provider.EmbeddedClusterOpenEBSLocalSubDir(),
164164
SystemArchitecture: runtime.GOARCH,
165+
}.WithCIDRData(getCIDRs(c))
166+
167+
if err != nil {
168+
return fmt.Errorf("unable to get host preflights data: %w", err)
165169
}
166170
chpfs, err := preflights.GetClusterHostPreflights(c.Context, data)
167171
if err != nil {

cmd/embedded-cluster/network.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,16 @@ func withSubnetCIDRFlags(flags []cli.Flag) []cli.Flag {
4545
// --pod-cidr and --service-cidr have been set, they are used. Otherwise,
4646
// the cidr flag is split into pod and service CIDRs.
4747
func DeterminePodAndServiceCIDRs(c *cli.Context) (string, string, error) {
48-
if c.IsSet("pod-cidr") && c.IsSet("service-cidr") {
48+
if c.IsSet("pod-cidr") || c.IsSet("service-cidr") {
4949
return c.String("pod-cidr"), c.String("service-cidr"), nil
5050
}
5151
return netutils.SplitNetworkCIDR(c.String("cidr"))
5252
}
53+
54+
// getCIDRs returns the CIDRs in use based on the provided cli flags.
55+
func getCIDRs(c *cli.Context) (string, string, string) {
56+
if c.IsSet("pod-cidr") || c.IsSet("service-cidr") {
57+
return c.String("pod-cidr"), c.String("service-cidr"), ""
58+
}
59+
return "", "", c.String("cidr")
60+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"testing"
6+
7+
"github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1"
8+
"github.com/stretchr/testify/require"
9+
"github.com/urfave/cli/v2"
10+
)
11+
12+
func Test_getCIDRs(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
buildCliContext func(*flag.FlagSet) *cli.Context
16+
expected []string
17+
}{
18+
{
19+
name: "with pod and service flags",
20+
expected: []string{
21+
"10.0.0.0/24",
22+
"10.1.0.0/24",
23+
"",
24+
},
25+
buildCliContext: func(flagSet *flag.FlagSet) *cli.Context {
26+
c := cli.NewContext(cli.NewApp(), flagSet, nil)
27+
c.Set("pod-cidr", "10.0.0.0/24")
28+
c.Set("service-cidr", "10.1.0.0/24")
29+
return c
30+
},
31+
},
32+
{
33+
name: "with pod flag",
34+
expected: []string{
35+
"10.0.0.0/24",
36+
v1beta1.DefaultNetwork().ServiceCIDR,
37+
"",
38+
},
39+
buildCliContext: func(flagSet *flag.FlagSet) *cli.Context {
40+
c := cli.NewContext(cli.NewApp(), flagSet, nil)
41+
c.Set("pod-cidr", "10.0.0.0/24")
42+
return c
43+
},
44+
},
45+
{
46+
name: "with pod, service and cidr flags",
47+
expected: []string{
48+
"10.0.0.0/24",
49+
"10.1.0.0/24",
50+
"",
51+
},
52+
buildCliContext: func(flagSet *flag.FlagSet) *cli.Context {
53+
c := cli.NewContext(cli.NewApp(), flagSet, nil)
54+
c.Set("pod-cidr", "10.0.0.0/24")
55+
c.Set("service-cidr", "10.1.0.0/24")
56+
c.Set("cidr", "10.2.0.0/24")
57+
return c
58+
},
59+
},
60+
{
61+
name: "with pod and cidr flags",
62+
expected: []string{
63+
"10.0.0.0/24",
64+
v1beta1.DefaultNetwork().ServiceCIDR,
65+
"",
66+
},
67+
buildCliContext: func(flagSet *flag.FlagSet) *cli.Context {
68+
c := cli.NewContext(cli.NewApp(), flagSet, nil)
69+
c.Set("pod-cidr", "10.0.0.0/24")
70+
c.Set("cidr", "10.2.0.0/24")
71+
return c
72+
},
73+
},
74+
{
75+
name: "with cidr flag",
76+
expected: []string{
77+
"",
78+
"",
79+
"10.2.0.0/24",
80+
},
81+
buildCliContext: func(flagSet *flag.FlagSet) *cli.Context {
82+
c := cli.NewContext(cli.NewApp(), flagSet, nil)
83+
c.Set("cidr", "10.2.0.0/24")
84+
return c
85+
},
86+
},
87+
}
88+
89+
for _, test := range tests {
90+
t.Run(test.name, func(t *testing.T) {
91+
req := require.New(t)
92+
93+
flagSet := flag.NewFlagSet(t.Name(), 0)
94+
flags := withSubnetCIDRFlags([]cli.Flag{})
95+
for _, f := range flags {
96+
err := f.Apply(flagSet)
97+
req.NoError(err)
98+
}
99+
100+
cc := test.buildCliContext(flagSet)
101+
podCIDR, serviceCIDR, CIDR := getCIDRs(cc)
102+
req.Equal(test.expected[0], podCIDR)
103+
req.Equal(test.expected[1], serviceCIDR)
104+
req.Equal(test.expected[2], CIDR)
105+
})
106+
}
107+
}
108+
109+
func Test_DeterminePodAndServiceCIDRs(t *testing.T) {
110+
111+
tests := []struct {
112+
name string
113+
buildCliContext func(*flag.FlagSet) *cli.Context
114+
expected []string
115+
}{
116+
{
117+
name: "with pod flag",
118+
expected: []string{
119+
"10.0.0.0/16",
120+
v1beta1.DefaultNetwork().ServiceCIDR,
121+
},
122+
buildCliContext: func(flagSet *flag.FlagSet) *cli.Context {
123+
c := cli.NewContext(cli.NewApp(), flagSet, nil)
124+
c.Set("pod-cidr", "10.0.0.0/16")
125+
return c
126+
},
127+
},
128+
{
129+
name: "with service flag",
130+
expected: []string{
131+
v1beta1.DefaultNetwork().PodCIDR,
132+
"10.1.0.0/16",
133+
},
134+
buildCliContext: func(flagSet *flag.FlagSet) *cli.Context {
135+
c := cli.NewContext(cli.NewApp(), flagSet, nil)
136+
c.Set("service-cidr", "10.1.0.0/16")
137+
return c
138+
},
139+
},
140+
{
141+
name: "with cidr flag",
142+
expected: []string{
143+
"10.0.0.0/16",
144+
"10.1.0.0/16",
145+
},
146+
buildCliContext: func(flagSet *flag.FlagSet) *cli.Context {
147+
c := cli.NewContext(cli.NewApp(), flagSet, nil)
148+
c.Set("cidr", "10.0.0.0/15")
149+
return c
150+
},
151+
},
152+
}
153+
154+
for _, test := range tests {
155+
t.Run(test.name, func(t *testing.T) {
156+
req := require.New(t)
157+
158+
flagSet := flag.NewFlagSet(t.Name(), 0)
159+
flags := withSubnetCIDRFlags([]cli.Flag{})
160+
for _, f := range flags {
161+
err := f.Apply(flagSet)
162+
req.NoError(err)
163+
}
164+
165+
cc := test.buildCliContext(flagSet)
166+
podCIDR, serviceCIDR, err := DeterminePodAndServiceCIDRs(cc)
167+
req.NoError(err)
168+
req.Equal(test.expected[0], podCIDR)
169+
req.Equal(test.expected[1], serviceCIDR)
170+
})
171+
}
172+
}

pkg/preflights/host-preflight.yaml

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,21 @@ spec:
127127
collectorName: 'wildcard-check'
128128
hostnames:
129129
- '*'
130+
- subnetAvailable:
131+
collectorName: Pod CIDR
132+
exclude: '{{ eq .PodCIDR.CIDR "" }}'
133+
CIDRRangeAlloc: '{{ .PodCIDR.CIDR }}'
134+
desiredCIDR: {{.PodCIDR.Size}}
135+
- subnetAvailable:
136+
collectorName: Service CIDR
137+
exclude: '{{ eq .ServiceCIDR.CIDR "" }}'
138+
CIDRRangeAlloc: '{{ .ServiceCIDR.CIDR }}'
139+
desiredCIDR: {{.ServiceCIDR.Size}}
140+
- subnetAvailable:
141+
collectorName: CIDR
142+
exclude: '{{ eq .GlobalCIDR.CIDR "" }}'
143+
CIDRRangeAlloc: '{{ .GlobalCIDR.CIDR }}'
144+
desiredCIDR: {{.GlobalCIDR.Size}}
130145
analyzers:
131146
- cpu:
132147
checkName: CPU
@@ -763,10 +778,10 @@ spec:
763778
outcomes:
764779
- fail:
765780
when: 'true'
766-
message: {{ .DataDir }} cannot be symlinked. Remove the symlink, or use the --data-dir flag to provide an alternate data directory.
781+
message: "{{ .DataDir }} cannot be symlinked. Remove the symlink, or use the --data-dir flag to provide an alternate data directory."
767782
- pass:
768783
when: 'false'
769-
message: {{ .DataDir }} is not a symlink.
784+
message: "{{ .DataDir }} is not a symlink."
770785
- jsonCompare:
771786
checkName: Wildcard DNS
772787
fileName: host-collectors/dns/wildcard-check/result.json
@@ -780,3 +795,36 @@ spec:
780795
- pass:
781796
when: 'true'
782797
message: No wildcard DNS entry detected.
798+
- subnetAvailable:
799+
checkName: Pod CIDR Availability
800+
collectorName: Pod CIDR
801+
exclude: '{{ eq .PodCIDR.CIDR "" }}'
802+
outcomes:
803+
- fail:
804+
when: "no-subnet-available"
805+
message: "{{ .PodCIDR.CIDR }} is not available. Use --pod-cidr to specify an available CIDR block."
806+
- pass:
807+
when: "a-subnet-is-available"
808+
message: Specified Pod CIDR is available.
809+
- subnetAvailable:
810+
checkName: Service CIDR Availability
811+
collectorName: Service CIDR
812+
exclude: '{{ eq .ServiceCIDR.CIDR "" }}'
813+
outcomes:
814+
- fail:
815+
when: "no-subnet-available"
816+
message: "{{ .ServiceCIDR.CIDR }} is not available. Use --service-cidr to specify an available CIDR block."
817+
- pass:
818+
when: "a-subnet-is-available"
819+
message: Specified Service CIDR is available.
820+
- subnetAvailable:
821+
checkName: CIDR Availability
822+
collectorName: CIDR
823+
exclude: '{{ eq .GlobalCIDR.CIDR "" }}'
824+
outcomes:
825+
- fail:
826+
when: "no-subnet-available"
827+
message: "{{ .GlobalCIDR.CIDR }} is not available. Use --cidr to specify a CIDR block of available private IP addresses (/16 or larger)."
828+
- pass:
829+
when: "a-subnet-is-available"
830+
message: Specified CIDR is available.

pkg/preflights/template.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@ package preflights
44
import (
55
"bytes"
66
"fmt"
7+
"net"
78
"text/template"
89
)
910

11+
type CIDRData struct {
12+
// Should specify the IP Address and size of the network e.g. "10.0.0.0/8"
13+
CIDR string
14+
// The size of the CIDR network. Should match the CIDR size above.
15+
Size int
16+
}
17+
1018
type TemplateData struct {
1119
IsAirgap bool
1220
ReplicatedAPIURL string
@@ -17,6 +25,51 @@ type TemplateData struct {
1725
K0sDataDir string
1826
OpenEBSDataDir string
1927
SystemArchitecture string
28+
ServiceCIDR CIDRData
29+
PodCIDR CIDRData
30+
GlobalCIDR CIDRData
31+
}
32+
33+
// WithCIDRData sets the respective CIDR properties in the TemplateData struct based on the provided CIDR strings
34+
func (t TemplateData) WithCIDRData(podCIDR, serviceCIDR, globalCIDR string) (TemplateData, error) {
35+
if globalCIDR != "" {
36+
_, cidr, err := net.ParseCIDR(globalCIDR)
37+
if err != nil {
38+
return t, fmt.Errorf("invalid cidr: %w", err)
39+
}
40+
size, _ := cidr.Mask.Size()
41+
t.GlobalCIDR = CIDRData{
42+
CIDR: cidr.String(),
43+
Size: size,
44+
}
45+
return t, nil
46+
}
47+
48+
s := CIDRData{}
49+
p := CIDRData{}
50+
if serviceCIDR != "" {
51+
_, cidr, err := net.ParseCIDR(serviceCIDR)
52+
if err != nil {
53+
return t, fmt.Errorf("invalid service cidr: %w", err)
54+
}
55+
size, _ := cidr.Mask.Size()
56+
s.CIDR = cidr.String()
57+
s.Size = size
58+
}
59+
60+
if podCIDR != "" {
61+
_, cidr, err := net.ParseCIDR(podCIDR)
62+
if err != nil {
63+
return t, fmt.Errorf("invalid pod cidr: %w", err)
64+
}
65+
size, _ := cidr.Mask.Size()
66+
p.CIDR = cidr.String()
67+
p.Size = size
68+
}
69+
70+
t.ServiceCIDR = s
71+
t.PodCIDR = p
72+
return t, nil
2073
}
2174

2275
func renderTemplate(spec string, data TemplateData) (string, error) {

0 commit comments

Comments
 (0)