@@ -212,18 +212,133 @@ func newAWSProvider(spec *api.ProviderConfig, configurationLoader AWSConfigurati
212212 return provider , nil
213213}
214214
215+ // resolveYAMLAnchors processes YAML anchors and aliases, returning clean YAML
216+ // that can be safely parsed by strict unmarshaling. It removes the "aliases"
217+ // field commonly used for anchor definitions as it's not part of ClusterConfig schema.
218+ func resolveYAMLAnchors (data []byte ) ([]byte , error ) {
219+ // Security: Limit input size to prevent memory exhaustion attacks
220+ const maxInputSize = 1024 * 1024 // 1MB limit
221+ if len (data ) > maxInputSize {
222+ return nil , fmt .Errorf ("YAML input too large: %d bytes exceeds limit of %d bytes" , len (data ), maxInputSize )
223+ }
224+
225+ // Security: Check for excessive nesting depth to prevent stack overflow
226+ const maxNestingDepth = 10
227+ if nestingDepth := countNestingDepth (data ); nestingDepth > maxNestingDepth {
228+ return nil , fmt .Errorf ("YAML nesting too deep: %d levels exceeds limit of %d" , nestingDepth , maxNestingDepth )
229+ }
230+
231+ // Resolve YAML anchors and aliases by unmarshaling to interface{} first.
232+ // This step processes any YAML anchors (&anchor) and aliases (*alias) in the input,
233+ // expanding them to their full values.
234+ var resolved interface {}
235+ if err := yaml .Unmarshal (data , & resolved ); err != nil {
236+ return nil , err
237+ }
238+
239+ // Marshal back to get resolved YAML without anchors/aliases
240+ resolvedData , err := yaml .Marshal (resolved )
241+ if err != nil {
242+ return nil , err
243+ }
244+
245+ // Security: Check for excessive expansion (YAML bomb protection)
246+ const maxExpansionRatio = 10
247+ if len (resolvedData ) > len (data )* maxExpansionRatio {
248+ return nil , fmt .Errorf ("YAML expansion too large: %d bytes expanded from %d bytes (ratio: %d, limit: %d)" ,
249+ len (resolvedData ), len (data ), len (resolvedData )/ len (data ), maxExpansionRatio )
250+ }
251+
252+ // Remove any top-level fields that are not part of ClusterConfig schema.
253+ // The "aliases" field is commonly used to define YAML anchors but is not
254+ // a valid ClusterConfig field, so we filter it out after anchor resolution.
255+ var temp map [string ]interface {}
256+ if err := yaml .Unmarshal (resolvedData , & temp ); err != nil {
257+ return nil , err
258+ }
259+
260+ // Security: Remove any non-standard top-level fields that could bypass validation
261+ allowedTopLevelFields := map [string ]bool {
262+ "accessConfig" : true ,
263+ "addons" : true ,
264+ "addonsConfig" : true ,
265+ "apiVersion" : true ,
266+ "autoModeConfig" : true ,
267+ "availabilityZones" : true ,
268+ "cloudWatch" : true ,
269+ "fargateProfiles" : true ,
270+ "gitops" : true ,
271+ "iam" : true ,
272+ "iamIdentityMappings" : true ,
273+ "identityProviders" : true ,
274+ "karpenter" : true ,
275+ "kind" : true ,
276+ "kubernetesNetworkConfig" : true ,
277+ "localZones" : true ,
278+ "managedNodeGroups" : true ,
279+ "metadata" : true ,
280+ "nodeGroups" : true ,
281+ "outpost" : true ,
282+ "privateCluster" : true ,
283+ "remoteNetworkConfig" : true ,
284+ "secretsEncryption" : true ,
285+ "vpc" : true ,
286+ "zonalShiftConfig" : true ,
287+ }
288+
289+ for field := range temp {
290+ if ! allowedTopLevelFields [field ] {
291+ delete (temp , field )
292+ }
293+ }
294+
295+ // Marshal back to clean YAML
296+ return yaml .Marshal (temp )
297+ }
298+
299+ // countNestingDepth estimates YAML nesting depth by counting indentation
300+ func countNestingDepth (data []byte ) int {
301+ lines := strings .Split (string (data ), "\n " )
302+ maxDepth := 0
303+ for _ , line := range lines {
304+ if strings .TrimSpace (line ) == "" || strings .HasPrefix (strings .TrimSpace (line ), "#" ) {
305+ continue
306+ }
307+ depth := 0
308+ for _ , char := range line {
309+ if char == ' ' {
310+ depth ++
311+ } else if char == '\t' {
312+ depth += 2 // Count tabs as 2 spaces
313+ } else {
314+ break
315+ }
316+ }
317+ if depth / 2 > maxDepth { // Assuming 2-space indentation
318+ maxDepth = depth / 2
319+ }
320+ }
321+ return maxDepth
322+ }
323+
215324// ParseConfig parses data into a ClusterConfig
216325func ParseConfig (data []byte ) (* api.ClusterConfig , error ) {
326+ // Resolve YAML anchors and aliases before parsing
327+ cleanData , err := resolveYAMLAnchors (data )
328+ if err != nil {
329+ return nil , err
330+ }
331+
217332 // strict mode is not available in runtime.Decode, so we use the parser
218333 // directly; we don't store the resulting object, this is just the means
219334 // of detecting any unknown keys
220335 // NOTE: we must use sigs.k8s.io/yaml, as it behaves differently from
221336 // github.com/ghodss/yaml, which didn't handle nested structs well
222- if err := yaml .UnmarshalStrict (data , & api.ClusterConfig {}); err != nil {
337+ if err := yaml .UnmarshalStrict (cleanData , & api.ClusterConfig {}); err != nil {
223338 return nil , err
224339 }
225340
226- obj , err := runtime .Decode (scheme .Codecs .UniversalDeserializer (), data )
341+ obj , err := runtime .Decode (scheme .Codecs .UniversalDeserializer (), cleanData )
227342 if err != nil {
228343 return nil , err
229344 }
0 commit comments