Skip to content

Commit de03816

Browse files
authored
Add region validation to AWS dns secret validation (#1652)
* Add region validation to AWS secret validation * remove unexpected keys in validateNoUnexpectedKeys for infra secrets. * Change region regex to be more flexible.
1 parent 64f7a07 commit de03816

3 files changed

Lines changed: 175 additions & 8 deletions

File tree

pkg/apis/aws/validation/filter.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ var (
4141
// see https://aws.amazon.com/blogs/security/a-safer-way-to-distribute-aws-credentials-to-ec2/
4242
// #nosec G101 -- This is a validation regex pattern, not a hardcoded credential
4343
SecretAccessKeyRegex = `^[A-Za-z0-9/+=]+$`
44+
// RegionRegex matches AWS region names, e.g. us-east-1, eu-west-2, eusc-de-east-1
45+
// see https://docs.aws.amazon.com/general/latest/gr/rande.html
46+
// and https://aws.amazon.com/de/blogs/aws/opening-the-aws-european-sovereign-cloud/ (section "Some technical details")
47+
RegionRegex = `^[a-z-]+-\d+$`
4448

4549
validateK8sResourceName = combineValidationFuncs(regex(k8sResourceNameRegex), notEmpty, maxLength(253))
4650
validateVpcID = combineValidationFuncs(regex(VpcIDRegex), notEmpty, maxLength(255))
@@ -54,6 +58,7 @@ var (
5458
validateCapacityReservationGroup = combineValidationFuncs(regex(CapacityReservationGroupRegex), notEmpty, maxLength(255))
5559
validateAccessKeyID = hideSensitiveValue(combineValidationFuncs(regex(AccessKeyIDRegex), minLength(20), maxLength(20)))
5660
validateSecretAccessKey = hideSensitiveValue(combineValidationFuncs(regex(SecretAccessKeyRegex), minLength(40), maxLength(40)))
61+
validateRegion = combineValidationFuncs(regex(RegionRegex), maxLength(32))
5762
)
5863

5964
type validateFunc[T any] func(T, *field.Path) field.ErrorList

pkg/apis/aws/validation/secrets.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package validation
77
import (
88
"fmt"
99

10-
securityv1alpha1constants "github.com/gardener/gardener/pkg/apis/security/v1alpha1/constants"
1110
corev1 "k8s.io/api/core/v1"
1211
"k8s.io/apimachinery/pkg/util/sets"
1312
"k8s.io/apimachinery/pkg/util/validation/field"
@@ -36,9 +35,9 @@ func ValidateCloudProviderSecret(secret *corev1.Secret, fldPath *field.Path, kin
3635
dataPath := fldPath.Child("data")
3736
secretRef := fmt.Sprintf("%s/%s", secret.Namespace, secret.Name)
3837

39-
var accessKeyIDKey, secretAccessKeyKey string
40-
var accessKeyID, secretAccessKey []byte
41-
var accessKeyIDExists, secretAccessKeyExists bool
38+
var accessKeyIDKey, secretAccessKeyKey, regionKey string
39+
var accessKeyID, secretAccessKey, region []byte
40+
var accessKeyIDExists, secretAccessKeyExists, regionExists bool
4241

4342
switch kind {
4443
case SecretKindInfrastructure:
@@ -49,9 +48,7 @@ func ValidateCloudProviderSecret(secret *corev1.Secret, fldPath *field.Path, kin
4948

5049
// Validate no unexpected keys exist
5150
allErrs = append(allErrs, validateNoUnexpectedKeys(secret.Data, dataPath, secretRef,
52-
aws.AccessKeyID, aws.SecretAccessKey, aws.Region,
53-
aws.SharedCredentialsFile, securityv1alpha1constants.DataKeyConfig,
54-
aws.RoleARN, aws.WorkloadIdentityTokenFileKey)...)
51+
aws.AccessKeyID, aws.SecretAccessKey)...)
5552

5653
case SecretKindDns:
5754
// For DNS secrets, check for DNS-specific key aliases first, then fall back to
@@ -72,13 +69,22 @@ func ValidateCloudProviderSecret(secret *corev1.Secret, fldPath *field.Path, kin
7269
secretAccessKeyKey = aws.SecretAccessKey
7370
}
7471

72+
region, regionExists = secret.Data[aws.DNSRegion]
73+
if regionExists {
74+
regionKey = aws.DNSRegion
75+
} else {
76+
region, regionExists = secret.Data[aws.Region]
77+
regionKey = aws.Region
78+
}
79+
7580
// Validate no unexpected keys exist
7681
// For DNS, we allow either the standard infrastructure keys or the DNS-specific alias keys, but not a mix
7782
// Prefer standard keys if any are present
7883
_, hasStandardAccessKey := secret.Data[aws.AccessKeyID]
7984
_, hasStandardSecretKey := secret.Data[aws.SecretAccessKey]
85+
_, hasStandardRegionKey := secret.Data[aws.Region]
8086

81-
if hasStandardAccessKey || hasStandardSecretKey {
87+
if hasStandardAccessKey || hasStandardSecretKey || hasStandardRegionKey {
8288
allErrs = append(allErrs, validateNoUnexpectedKeys(secret.Data, dataPath, secretRef,
8389
aws.AccessKeyID, aws.SecretAccessKey, aws.Region)...)
8490
} else {
@@ -114,6 +120,11 @@ func ValidateCloudProviderSecret(secret *corev1.Secret, fldPath *field.Path, kin
114120
allErrs = append(allErrs, validateSecretAccessKey(string(secretAccessKey), dataPath.Key(secretAccessKeyKey))...)
115121
}
116122

123+
// Validate region
124+
if regionExists && len(region) > 0 {
125+
allErrs = append(allErrs, validateRegion(string(region), dataPath.Key(regionKey))...)
126+
}
127+
117128
return allErrs
118129
}
119130

pkg/apis/aws/validation/secrets_test.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,157 @@ var _ = Describe("Secret validation", func() {
532532
}))))
533533
})
534534
})
535+
Context("with region field", func() {
536+
It("should pass with valid AWS_REGION", func() {
537+
secret.Data = map[string][]byte{
538+
aws.DNSAccessKeyID: []byte(validAccessKeyID),
539+
aws.DNSSecretAccessKey: []byte(validSecretAccessKey),
540+
aws.DNSRegion: []byte("us-east-1"),
541+
}
542+
543+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
544+
Expect(errs).To(BeEmpty())
545+
})
546+
547+
It("should pass with valid region using camelCase key", func() {
548+
secret.Data = map[string][]byte{
549+
aws.AccessKeyID: []byte(validAccessKeyID),
550+
aws.SecretAccessKey: []byte(validSecretAccessKey),
551+
aws.Region: []byte("eu-west-2"),
552+
}
553+
554+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
555+
Expect(errs).To(BeEmpty())
556+
})
557+
558+
It("should pass with valid GovCloud region", func() {
559+
secret.Data = map[string][]byte{
560+
aws.DNSAccessKeyID: []byte(validAccessKeyID),
561+
aws.DNSSecretAccessKey: []byte(validSecretAccessKey),
562+
aws.DNSRegion: []byte("us-gov-west-1"),
563+
}
564+
565+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
566+
Expect(errs).To(BeEmpty())
567+
})
568+
569+
It("should pass with valid multi-zone region", func() {
570+
secret.Data = map[string][]byte{
571+
aws.DNSAccessKeyID: []byte(validAccessKeyID),
572+
aws.DNSSecretAccessKey: []byte(validSecretAccessKey),
573+
aws.DNSRegion: []byte("ap-southeast-3"),
574+
}
575+
576+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
577+
Expect(errs).To(BeEmpty())
578+
})
579+
580+
It("should pass when region field is empty", func() {
581+
secret.Data = map[string][]byte{
582+
aws.DNSAccessKeyID: []byte(validAccessKeyID),
583+
aws.DNSSecretAccessKey: []byte(validSecretAccessKey),
584+
aws.DNSRegion: []byte(""),
585+
}
586+
587+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
588+
Expect(errs).To(BeEmpty())
589+
})
590+
591+
It("should pass when region field is missing", func() {
592+
secret.Data = map[string][]byte{
593+
aws.DNSAccessKeyID: []byte(validAccessKeyID),
594+
aws.DNSSecretAccessKey: []byte(validSecretAccessKey),
595+
}
596+
597+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
598+
Expect(errs).To(BeEmpty())
599+
})
600+
601+
It("should fail when region format is invalid", func() {
602+
secret.Data = map[string][]byte{
603+
aws.DNSAccessKeyID: []byte(validAccessKeyID),
604+
aws.DNSSecretAccessKey: []byte(validSecretAccessKey),
605+
aws.DNSRegion: []byte("invalid-region"),
606+
}
607+
608+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
609+
Expect(errs).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{
610+
"Type": Equal(field.ErrorTypeInvalid),
611+
"Field": Equal("secret.data[AWS_REGION]"),
612+
}))))
613+
})
614+
615+
It("should fail when region has uppercase characters", func() {
616+
secret.Data = map[string][]byte{
617+
aws.DNSAccessKeyID: []byte(validAccessKeyID),
618+
aws.DNSSecretAccessKey: []byte(validSecretAccessKey),
619+
aws.DNSRegion: []byte("US-EAST-1"),
620+
}
621+
622+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
623+
Expect(errs).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{
624+
"Type": Equal(field.ErrorTypeInvalid),
625+
"Field": Equal("secret.data[AWS_REGION]"),
626+
}))))
627+
})
628+
629+
It("should fail when region is too long", func() {
630+
secret.Data = map[string][]byte{
631+
aws.DNSAccessKeyID: []byte(validAccessKeyID),
632+
aws.DNSSecretAccessKey: []byte(validSecretAccessKey),
633+
aws.DNSRegion: []byte("us-extremely-long-region-name-that-exceeds-limit-1"),
634+
}
635+
636+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
637+
Expect(errs).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{
638+
"Type": Equal(field.ErrorTypeInvalid),
639+
"Field": Equal("secret.data[AWS_REGION]"),
640+
}))))
641+
})
642+
643+
It("should fail when region contains invalid characters", func() {
644+
secret.Data = map[string][]byte{
645+
aws.DNSAccessKeyID: []byte(validAccessKeyID),
646+
aws.DNSSecretAccessKey: []byte(validSecretAccessKey),
647+
aws.DNSRegion: []byte("us_east_1"),
648+
}
649+
650+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
651+
Expect(errs).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{
652+
"Type": Equal(field.ErrorTypeInvalid),
653+
"Field": Equal("secret.data[AWS_REGION]"),
654+
}))))
655+
})
656+
657+
It("should fail when region using camelCase key is invalid", func() {
658+
secret.Data = map[string][]byte{
659+
aws.AccessKeyID: []byte(validAccessKeyID),
660+
aws.SecretAccessKey: []byte(validSecretAccessKey),
661+
aws.Region: []byte("invalid_region"),
662+
}
663+
664+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
665+
Expect(errs).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{
666+
"Type": Equal(field.ErrorTypeInvalid),
667+
"Field": Equal("secret.data[region]"),
668+
}))))
669+
})
670+
671+
It("should fail when both region keys are present", func() {
672+
secret.Data = map[string][]byte{
673+
aws.AccessKeyID: []byte(validAccessKeyID),
674+
aws.SecretAccessKey: []byte(validSecretAccessKey),
675+
aws.Region: []byte("us-east-1"),
676+
aws.DNSRegion: []byte("us-west-2"),
677+
}
678+
679+
errs := ValidateCloudProviderSecret(secret, fldPath, SecretKindDns)
680+
Expect(errs).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{
681+
"Type": Equal(field.ErrorTypeForbidden),
682+
"Field": Equal("secret.data[AWS_REGION]"),
683+
}))))
684+
})
685+
})
535686
})
536687

537688
Context("Invalid Secret Kind", func() {

0 commit comments

Comments
 (0)