Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support AWS tags with spaces and valid special characters #1374

Merged
merged 4 commits into from
Sep 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion manager/cluster_config_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
def export(base_key, value):
if base_key.lower().startswith("cortex_tags"):
inlined_tags = ",".join([f"{k}={v}" for k, v in value.items()])
print(f"export CORTEX_TAGS={inlined_tags}")
print(f"export CORTEX_TAGS='{inlined_tags}'")
print(f"export CORTEX_TAGS_JSON='{json.dumps(value)}'")
return

Expand Down
2 changes: 1 addition & 1 deletion manager/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ function main() {
fi

# create VPC Link
create_vpc_link_output=$(aws apigatewayv2 create-vpc-link --region $CORTEX_REGION --tags $CORTEX_TAGS --name $CORTEX_CLUSTER_NAME --subnet-ids $private_subnets --security-group-ids $default_security_group)
create_vpc_link_output=$(aws apigatewayv2 create-vpc-link --region $CORTEX_REGION --tags "$CORTEX_TAGS_JSON" --name $CORTEX_CLUSTER_NAME --subnet-ids $private_subnets --security-group-ids $default_security_group)
vpc_link_id=$(echo $create_vpc_link_output | jq .VpcLinkId | tr -d '"')
if [ "$vpc_link_id" = "" ] || [ "$vpc_link_id" = "null" ]; then
echo -e "unable to extract vpc link ID from create-vpc-link output:\n$create_vpc_link_output"
Expand Down
2 changes: 1 addition & 1 deletion manager/manifests/fluentd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ data:
region "#{ENV['AWS_REGION']}"
log_group_name_key group_name
log_stream_name_key stream_name
log_group_aws_tags ${CORTEX_TAGS_JSON}
log_group_aws_tags "${CORTEX_TAGS_JSON}"
remove_log_stream_name_key true
remove_log_group_name_key true
auto_create_stream true
Expand Down
4 changes: 2 additions & 2 deletions manager/manifests/istio-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ gateways:
serviceAnnotations:
${CORTEX_OPERATOR_LOAD_BALANCER_ANNOTATION}
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: ${CORTEX_TAGS}
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: "${CORTEX_TAGS}"
type: LoadBalancer
externalTrafficPolicy: Local # https://medium.com/pablo-perez/k8s-externaltrafficpolicy-local-or-cluster-40b259a19404, https://www.asykim.com/blog/deep-dive-into-kubernetes-external-traffic-policies
ports:
Expand Down Expand Up @@ -84,7 +84,7 @@ gateways:
serviceAnnotations:
${CORTEX_API_LOAD_BALANCER_ANNOTATION}
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: ${CORTEX_TAGS}
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: "${CORTEX_TAGS}"
${CORTEX_SSL_CERTIFICATE_ANNOTATION}
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
type: LoadBalancer
Expand Down
18 changes: 17 additions & 1 deletion pkg/lib/configreader/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const (
ErrInvalidYAML = "configreader.invalid_yaml"
ErrTooLong = "configreader.too_long"
ErrTooShort = "configreader.too_short"
ErrLeadingWhitespace = "configreader.leading_whitespace"
ErrTrailingWhitespace = "configreader.trailing_whitespace"
ErrAlphaNumericDashUnderscore = "configreader.alpha_numeric_dash_underscore"
ErrAlphaNumericDashDotUnderscore = "configreader.alpha_numeric_dash_dot_underscore"
ErrInvalidAWSTag = "configreader.invalid_aws_tag"
Expand Down Expand Up @@ -114,6 +116,20 @@ func ErrorTooShort(provided string, minLen int) error {
})
}

func ErrorLeadingWhitespace(provided string) error {
return errors.WithStack(&errors.Error{
Kind: ErrLeadingWhitespace,
Message: fmt.Sprintf("%s cannot start with whitespace", s.UserStr(provided)),
})
}

func ErrorTrailingWhitespace(provided string) error {
return errors.WithStack(&errors.Error{
Kind: ErrTrailingWhitespace,
Message: fmt.Sprintf("%s cannot end with whitespace", s.UserStr(provided)),
})
}

func ErrorAlphaNumericDashUnderscore(provided string) error {
return errors.WithStack(&errors.Error{
Kind: ErrAlphaNumericDashUnderscore,
Expand All @@ -131,7 +147,7 @@ func ErrorAlphaNumericDashDotUnderscore(provided string) error {
func ErrorInvalidAWSTag(provided string) error {
return errors.WithStack(&errors.Error{
Kind: ErrInvalidAWSTag,
Message: fmt.Sprintf("%s must contain only letters, numbers, spaces, and the following characters: _ . : / = + - @", s.UserStr(provided)),
Message: fmt.Sprintf("%s must contain only letters, numbers, spaces, and the following characters: _ . : / + - @", s.UserStr(provided)),
})
}

Expand Down
14 changes: 14 additions & 0 deletions pkg/lib/configreader/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ type StringValidation struct {
InvalidPrefixes []string
MaxLength int
MinLength int
DisallowLeadingWhitespace bool
DisallowTrailingWhitespace bool
AlphaNumericDashDotUnderscoreOrEmpty bool
AlphaNumericDashDotUnderscore bool
AlphaNumericDashUnderscore bool
Expand Down Expand Up @@ -257,6 +259,18 @@ func ValidateStringVal(val string, v *StringValidation) error {
}
}

if v.DisallowLeadingWhitespace {
if regex.HasLeadingWhitespace(val) {
return ErrorLeadingWhitespace(val)
}
}

if v.DisallowTrailingWhitespace {
if regex.HasTrailingWhitespace(val) {
return ErrorTrailingWhitespace(val)
}
}

if v.AlphaNumericDashDotUnderscore {
if !regex.IsAlphaNumericDashDotUnderscore(val) {
return ErrorAlphaNumericDashDotUnderscore(val)
Expand Down
4 changes: 4 additions & 0 deletions pkg/lib/configreader/string_ptr.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type StringPtrValidation struct {
InvalidPrefixes []string
MaxLength int
MinLength int
DisallowLeadingWhitespace bool
DisallowTrailingWhitespace bool
AlphaNumericDashDotUnderscoreOrEmpty bool
AlphaNumericDashDotUnderscore bool
AlphaNumericDashUnderscore bool
Expand All @@ -59,6 +61,8 @@ func makeStringValValidation(v *StringPtrValidation) *StringValidation {
InvalidPrefixes: v.InvalidPrefixes,
MaxLength: v.MaxLength,
MinLength: v.MinLength,
DisallowLeadingWhitespace: v.DisallowLeadingWhitespace,
DisallowTrailingWhitespace: v.DisallowTrailingWhitespace,
AlphaNumericDashDotUnderscoreOrEmpty: v.AlphaNumericDashDotUnderscoreOrEmpty,
AlphaNumericDashDotUnderscore: v.AlphaNumericDashDotUnderscore,
AlphaNumericDashUnderscore: v.AlphaNumericDashUnderscore,
Expand Down
17 changes: 15 additions & 2 deletions pkg/lib/regex/regex.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,21 @@ func MatchAnyRegex(s string, regexes []*regexp.Regexp) bool {
return false
}

// letters, numbers, spaces representable in UTF-8, and the following characters: _ . : / = + - @
var _awsTagRegex = regexp.MustCompile(`^[\sa-zA-Z0-9_\-\.:/=+@]+$`)
var _leadingWhitespaceRegex = regexp.MustCompile(`^\s+`)

func HasLeadingWhitespace(s string) bool {
return _leadingWhitespaceRegex.MatchString(s)
}

var _trailingWhitespaceRegex = regexp.MustCompile(`\s+$`)

func HasTrailingWhitespace(s string) bool {
return _trailingWhitespaceRegex.MatchString(s)
}

// letters, numbers, spaces representable in UTF-8, and the following characters: _ . : / + - @
// = is not supported because it doesn't propagate to the NLB correctly (via the k8s service annotation)
var _awsTagRegex = regexp.MustCompile(`^[\sa-zA-Z0-9_\-\.:/+@]+$`)

func IsValidAWSTag(s string) bool {
return _awsTagRegex.MatchString(s)
Expand Down
166 changes: 166 additions & 0 deletions pkg/lib/regex/regex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package regex
import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

type regexpMatch struct {
Expand All @@ -27,6 +29,170 @@ type regexpMatch struct {
subs []string
}

func TestHasLeadingWhitespace(t *testing.T) {
testcases := []regexpMatch{
{
input: " test",
match: true,
},
{
input: " test",
match: true,
},
{
input: "\ntest",
match: true,
},
{
input: " test ",
match: true,
},
{
input: " test ",
match: true,
},
{
input: "\ntest\n",
match: true,
},
{
input: "test",
match: false,
},
{
input: "te st",
match: false,
},
{
input: "te st",
match: false,
},
{
input: "te\nst",
match: false,
},
{
input: "_",
match: false,
},
{
input: "test ",
match: false,
},
{
input: "test ",
match: false,
},
{
input: "test\n",
match: false,
},
{
input: " ",
match: true,
},
{
input: " ",
match: true,
},
{
input: "\n",
match: true,
},
{
input: "",
match: false,
},
}

for i := range testcases {
match := HasLeadingWhitespace(testcases[i].input)
assert.Equal(t, testcases[i].match, match, "input: "+testcases[i].input)
}
}

func TestHasTrailingWhitespace(t *testing.T) {
testcases := []regexpMatch{
{
input: " test",
match: false,
},
{
input: " test",
match: false,
},
{
input: "\ntest",
match: false,
},
{
input: " test ",
match: true,
},
{
input: " test ",
match: true,
},
{
input: "\ntest\n",
match: true,
},
{
input: "test",
match: false,
},
{
input: "te st",
match: false,
},
{
input: "te st",
match: false,
},
{
input: "te\nst",
match: false,
},
{
input: "_",
match: false,
},
{
input: "test ",
match: true,
},
{
input: "test ",
match: true,
},
{
input: "test\n",
match: true,
},
{
input: " ",
match: true,
},
{
input: " ",
match: true,
},
{
input: "\n",
match: true,
},
{
input: "",
match: false,
},
}

for i := range testcases {
match := HasTrailingWhitespace(testcases[i].input)
assert.Equal(t, testcases[i].match, match, "input: "+testcases[i].input)
}
}

func TestAlphaNumericDashDotUnderscoreRegex(t *testing.T) {
testcases := []regexpMatch{
{
Expand Down
28 changes: 18 additions & 10 deletions pkg/types/clusterconfig/clusterconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ var (
_cachedCNISupportedInstances *string
// This regex is stricter than the actual S3 rules
_strictS3BucketRegex = regexp.MustCompile(`^([a-z0-9])+(-[a-z0-9]+)*$`)
_invalidTagPrefixes = []string{"kubernetes.io/", "k8s.io/", "eksctl.", "alpha.eksctl.", "beta.eksctl.", "aws:", "Aws:", "aWs:", "awS:", "aWS:", "AwS:", "aWS:", "AWS:"}
)

type Config struct {
Expand Down Expand Up @@ -165,16 +166,20 @@ var UserValidation = &cr.StructValidation{
AllowEmpty: true,
ConvertNullToEmpty: true,
KeyStringValidator: &cr.StringValidation{
MinLength: 1,
MaxLength: 127,
InvalidPrefixes: []string{"aws:", "Aws:", "aWs:", "awS:", "aWS:", "AwS:", "aWS:", "AWS:"},
AWSTag: true,
MinLength: 1,
MaxLength: 127,
DisallowLeadingWhitespace: true,
DisallowTrailingWhitespace: true,
InvalidPrefixes: _invalidTagPrefixes,
AWSTag: true,
},
ValueStringValidator: &cr.StringValidation{
MinLength: 1,
MaxLength: 255,
InvalidPrefixes: []string{"aws:", "Aws:", "aWs:", "awS:", "aWS:", "AwS:", "aWS:", "AWS:"},
AWSTag: true,
MinLength: 1,
MaxLength: 255,
DisallowLeadingWhitespace: true,
DisallowTrailingWhitespace: true,
InvalidPrefixes: _invalidTagPrefixes,
AWSTag: true,
},
},
},
Expand Down Expand Up @@ -547,6 +552,7 @@ func (cc *Config) SQSNamePrefix() string {
return SQSNamePrefix(cc.ClusterName)
}

// this validates the user-provided cluster config
func (cc *Config) Validate(awsClient *aws.Client) error {
fmt.Print("verifying your configuration ...\n\n")

Expand Down Expand Up @@ -620,8 +626,10 @@ func (cc *Config) Validate(awsClient *aws.Client) error {
}
}

if cc.Tags[ClusterNameTag] != "" && cc.Tags[ClusterNameTag] != cc.ClusterName {
return ErrorCantOverrideDefaultTag()
for tagName := range cc.Tags {
if strings.HasPrefix(tagName, "cortex.dev/") {
return errors.Wrap(cr.ErrorCantHavePrefix(tagName, "cortex.dev/"), TagsKey)
}
}
cc.Tags[ClusterNameTag] = cc.ClusterName

Expand Down
Loading