@@ -17,10 +17,17 @@ limitations under the License.
1717package v1beta2
1818
1919import (
20+ "encoding/base64"
21+ "fmt"
22+ "net"
23+ "net/url"
24+ "strings"
25+
2026 "github.com/google/go-cmp/cmp"
2127 "github.com/pkg/errors"
2228 apierrors "k8s.io/apimachinery/pkg/api/errors"
2329 "k8s.io/apimachinery/pkg/runtime"
30+ "k8s.io/apimachinery/pkg/util/validation"
2431 "k8s.io/apimachinery/pkg/util/validation/field"
2532 ctrl "sigs.k8s.io/controller-runtime"
2633 "sigs.k8s.io/controller-runtime/pkg/webhook"
@@ -171,17 +178,132 @@ func (r *AWSMachine) ignitionEnabled() bool {
171178
172179func (r * AWSMachine ) validateIgnitionAndCloudInit () field.ErrorList {
173180 var allErrs field.ErrorList
181+ if ! r .ignitionEnabled () {
182+ return allErrs
183+ }
174184
175185 // Feature gate is not enabled but ignition is enabled then send a forbidden error.
176- if ! feature .Gates .Enabled (feature .BootstrapFormatIgnition ) && r . ignitionEnabled () {
186+ if ! feature .Gates .Enabled (feature .BootstrapFormatIgnition ) {
177187 allErrs = append (allErrs , field .Forbidden (field .NewPath ("spec" , "ignition" ),
178188 "can be set only if the BootstrapFormatIgnition feature gate is enabled" ))
179189 }
180190
181- if r .ignitionEnabled () && r .cloudInitConfigured () {
191+ // If ignition is enabled, cloudInit should not be configured.
192+ if r .cloudInitConfigured () {
182193 allErrs = append (allErrs , field .Forbidden (field .NewPath ("spec" , "cloudInit" ), "cannot be set if spec.ignition is set" ))
183194 }
184195
196+ // Proxy and TLS are only valid for Ignition versions >= 3.1.
197+ if r .Spec .Ignition .Version == "2.3" || r .Spec .Ignition .Version == "3.0" {
198+ if r .Spec .Ignition .Proxy != nil {
199+ allErrs = append (allErrs , field .Forbidden (field .NewPath ("spec" , "ignition" , "proxy" ), "cannot be set if spec.ignition.version is 2.3 or 3.0" ))
200+ }
201+ if r .Spec .Ignition .TLS != nil {
202+ allErrs = append (allErrs , field .Forbidden (field .NewPath ("spec" , "ignition" , "tls" ), "cannot be set if spec.ignition.version is 2.3 or 3.0" ))
203+ }
204+ }
205+
206+ allErrs = append (allErrs , r .validateIgnitionProxy ()... )
207+ allErrs = append (allErrs , r .validateIgnitionTLS ()... )
208+
209+ return allErrs
210+ }
211+
212+ func (r * AWSMachine ) validateIgnitionProxy () field.ErrorList {
213+ var allErrs field.ErrorList
214+
215+ if r .Spec .Ignition .Proxy == nil {
216+ return allErrs
217+ }
218+
219+ // Validate HTTPProxy.
220+ if r .Spec .Ignition .Proxy .HTTPProxy != nil {
221+ // Parse the url to check if it is valid.
222+ _ , err := url .Parse (* r .Spec .Ignition .Proxy .HTTPProxy )
223+ if err != nil {
224+ allErrs = append (allErrs , field .Invalid (field .NewPath ("spec" , "ignition" , "proxy" , "httpProxy" ), * r .Spec .Ignition .Proxy .HTTPProxy , "invalid URL" ))
225+ }
226+ }
227+
228+ // Validate HTTPSProxy.
229+ if r .Spec .Ignition .Proxy .HTTPSProxy != nil {
230+ // Parse the url to check if it is valid.
231+ _ , err := url .Parse (* r .Spec .Ignition .Proxy .HTTPSProxy )
232+ if err != nil {
233+ allErrs = append (allErrs , field .Invalid (field .NewPath ("spec" , "ignition" , "proxy" , "httpsProxy" ), * r .Spec .Ignition .Proxy .HTTPSProxy , "invalid URL" ))
234+ }
235+ }
236+
237+ // Validate NoProxy.
238+ for _ , noProxy := range r .Spec .Ignition .Proxy .NoProxy {
239+ noProxy := string (noProxy )
240+ // Validate here that the value `noProxy` is:
241+ // - A domain name
242+ // - A domain name matches that name and all subdomains
243+ // - A domain name with a leading . matches subdomains only
244+
245+ // A special DNS label (*).
246+ if noProxy == "*" {
247+ continue
248+ }
249+ // An IP address prefix (1.2.3.4).
250+ if ip := net .ParseIP (noProxy ); ip != nil {
251+ continue
252+ }
253+ // An IP address prefix in CIDR notation (1.2.3.4/8).
254+ if _ , _ , err := net .ParseCIDR (noProxy ); err == nil {
255+ continue
256+ }
257+ // An IP or domain name with a port.
258+ if _ , _ , err := net .SplitHostPort (noProxy ); err == nil {
259+ continue
260+ }
261+ // A domain name.
262+ if noProxy [0 ] == '.' {
263+ // If it starts with a dot, it should be a domain name.
264+ noProxy = noProxy [1 :]
265+ }
266+ // Validate that the value matches DNS 1123.
267+ if errs := validation .IsDNS1123Subdomain (noProxy ); len (errs ) > 0 {
268+ allErrs = append (allErrs , field .Invalid (field .NewPath ("spec" , "ignition" , "proxy" , "noProxy" ), noProxy , fmt .Sprintf ("invalid noProxy value, please refer to the field documentation: %s" , strings .Join (errs , "; " ))))
269+ }
270+ }
271+
272+ return allErrs
273+ }
274+
275+ func (r * AWSMachine ) validateIgnitionTLS () field.ErrorList {
276+ var allErrs field.ErrorList
277+
278+ if r .Spec .Ignition .TLS == nil {
279+ return allErrs
280+ }
281+
282+ for _ , source := range r .Spec .Ignition .TLS .CASources {
283+ // Validate that source is RFC 2397 data URL.
284+ u , err := url .Parse (string (source ))
285+ if err != nil {
286+ allErrs = append (allErrs , field .Invalid (field .NewPath ("spec" , "ignition" , "tls" , "caSources" ), source , "invalid URL" ))
287+ }
288+
289+ switch u .Scheme {
290+ case "http" , "https" , "tftp" , "s3" , "arn" , "gs" :
291+ // Valid schemes.
292+ case "data" :
293+ // Validate that the data URL is base64 encoded.
294+ i := strings .Index (u .Opaque , "," )
295+ if i < 0 {
296+ allErrs = append (allErrs , field .Invalid (field .NewPath ("spec" , "ignition" , "tls" , "caSources" ), source , "invalid data URL" ))
297+ }
298+ // Validate that the data URL is base64 encoded.
299+ if _ , err := base64 .StdEncoding .DecodeString (u .Opaque [i + 1 :]); err != nil {
300+ allErrs = append (allErrs , field .Invalid (field .NewPath ("spec" , "ignition" , "tls" , "caSources" ), source , "invalid base64 encoding for data url" ))
301+ }
302+ default :
303+ allErrs = append (allErrs , field .Invalid (field .NewPath ("spec" , "ignition" , "tls" , "caSources" ), source , "unsupported URL scheme" ))
304+ }
305+ }
306+
185307 return allErrs
186308}
187309
0 commit comments