@@ -212,18 +212,122 @@ 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 = 5
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 = 5
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+ "apiVersion" : true ,
263+ "kind" : true ,
264+ "metadata" : true ,
265+ "managedNodeGroups" : true ,
266+ "nodeGroups" : true ,
267+ "fargateProfiles" : true ,
268+ "vpc" : true ,
269+ "iam" : true ,
270+ "addons" : true ,
271+ "privateCluster" : true ,
272+ "cloudWatch" : true ,
273+ "secretsEncryption" : true ,
274+ "kubernetesNetworkConfig" : true ,
275+ "accessConfig" : true ,
276+ }
277+
278+ for field := range temp {
279+ if ! allowedTopLevelFields [field ] {
280+ delete (temp , field )
281+ }
282+ }
283+
284+ // Marshal back to clean YAML
285+ return yaml .Marshal (temp )
286+ }
287+
288+ // countNestingDepth estimates YAML nesting depth by counting indentation
289+ func countNestingDepth (data []byte ) int {
290+ lines := strings .Split (string (data ), "\n " )
291+ maxDepth := 0
292+ for _ , line := range lines {
293+ if strings .TrimSpace (line ) == "" || strings .HasPrefix (strings .TrimSpace (line ), "#" ) {
294+ continue
295+ }
296+ depth := 0
297+ for _ , char := range line {
298+ if char == ' ' {
299+ depth ++
300+ } else if char == '\t' {
301+ depth += 2 // Count tabs as 2 spaces
302+ } else {
303+ break
304+ }
305+ }
306+ if depth / 2 > maxDepth { // Assuming 2-space indentation
307+ maxDepth = depth / 2
308+ }
309+ }
310+ return maxDepth
311+ }
312+
215313// ParseConfig parses data into a ClusterConfig
216314func ParseConfig (data []byte ) (* api.ClusterConfig , error ) {
315+ // Resolve YAML anchors and aliases before parsing
316+ cleanData , err := resolveYAMLAnchors (data )
317+ if err != nil {
318+ return nil , err
319+ }
320+
217321 // strict mode is not available in runtime.Decode, so we use the parser
218322 // directly; we don't store the resulting object, this is just the means
219323 // of detecting any unknown keys
220324 // NOTE: we must use sigs.k8s.io/yaml, as it behaves differently from
221325 // github.com/ghodss/yaml, which didn't handle nested structs well
222- if err := yaml .UnmarshalStrict (data , & api.ClusterConfig {}); err != nil {
326+ if err := yaml .UnmarshalStrict (cleanData , & api.ClusterConfig {}); err != nil {
223327 return nil , err
224328 }
225329
226- obj , err := runtime .Decode (scheme .Codecs .UniversalDeserializer (), data )
330+ obj , err := runtime .Decode (scheme .Codecs .UniversalDeserializer (), cleanData )
227331 if err != nil {
228332 return nil , err
229333 }
0 commit comments