Skip to content
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
36 changes: 20 additions & 16 deletions api/utils/aws/identifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ package aws

import (
"regexp"
"strings"
"unicode"

"github.com/gravitational/trace"
)
Expand All @@ -40,9 +38,6 @@ func IsValidAccountID(accountID string) error {
return nil
}

// matchRoleName is a regex that matches against AWS IAM Role Names.
var matchRoleName = regexp.MustCompile(`^[\w+=,.@-]+$`).MatchString

// IsValidIAMRoleName checks whether the role name is a valid AWS IAM Role identifier.
//
// > Length Constraints: Minimum length of 1. Maximum length of 64.
Expand All @@ -60,17 +55,26 @@ func IsValidIAMRoleName(roleName string) error {
// It does not do a full validation, because AWS doesn't provide documentation for that.
// However, they usually only have the following chars: [a-z0-9\-]
func IsValidRegion(region string) error {
indexNotFound := -1

if len(region) == 0 {
return trace.BadParameter("region is invalid")
}

if strings.IndexFunc(region, func(r rune) bool {
return !(unicode.IsDigit(r) || unicode.IsLetter(r) || r == '-')
}) == indexNotFound {
if matchRegion.MatchString(region) {
return nil
}

return trace.BadParameter("region is invalid")
return trace.BadParameter("region %q is invalid", region)
}

var (
// matchRoleName is a regex that matches against AWS IAM Role Names.
matchRoleName = regexp.MustCompile(`^[\w+=,.@-]+$`).MatchString

// matchRegion is a regex that defines the format of AWS regions.
//
// The regex matches the following from left to right:
// - starts with 2 lower case letters that represents a geo region like a
// country code
// - optional -gov, -iso, -isob for corresponding partitions
// - a word that should be a direction like "east", "west", etc.
// - a number counter
//
// Reference:
// https://github.com/aws/aws-sdk-go-v2/blob/main/codegen/smithy-aws-go-codegen/src/main/resources/software/amazon/smithy/aws/go/codegen/endpoints.json
matchRegion = regexp.MustCompile(`^[a-z]{2}(-gov|-iso|-isob)?-\w+-\d+$`)
)
10 changes: 10 additions & 0 deletions api/utils/aws/identifiers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ func TestIsValidRegion(t *testing.T) {
region: "us-gov-east-1",
errCheck: require.NoError,
},
{
name: "valid format",
region: "xx-iso-somewhere-100",
errCheck: require.NoError,
},
{
name: "empty",
region: "",
Expand All @@ -163,6 +168,11 @@ func TestIsValidRegion(t *testing.T) {
region: "us@east-1",
errCheck: isBadParamErrFn,
},
{
name: "invalid country code",
region: "xxx-east-1",
errCheck: isBadParamErrFn,
},
} {
t.Run(tt.name, func(t *testing.T) {
tt.errCheck(t, IsValidRegion(tt.region))
Expand Down
12 changes: 12 additions & 0 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
awsutils "github.com/gravitational/teleport/lib/utils/aws"
)

// CommandLineFlags stores command line flag values, it's a much simplified subset
Expand Down Expand Up @@ -1309,6 +1310,17 @@ func applyDiscoveryConfig(fc *FileConfig, cfg *servicecfg.Config) error {
return trace.Wrap(err)
}

for _, region := range matcher.Regions {
if !awsutils.IsKnownRegion(region) {
log.Warnf("AWS matcher uses unknown region %q. "+
"There could be a typo in %q. "+
"Ignore this message if this is a new AWS region that is unknown to the AWS SDK used to compile this binary. "+
"Known regions are: %v.",
region, region, awsutils.GetKnownRegions(),
)
}
}

cfg.Discovery.AWSMatchers = append(cfg.Discovery.AWSMatchers,
types.AWSMatcher{
Types: matcher.Types,
Expand Down
21 changes: 4 additions & 17 deletions lib/config/fileconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,11 @@ import (
"strings"
"time"

"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/coreos/go-oidc/oauth2"
"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/ssh"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v2"

Expand All @@ -45,6 +43,7 @@ import (
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/types/installers"
apiutils "github.com/gravitational/teleport/api/utils"
awsapiutils "github.com/gravitational/teleport/api/utils/aws"
"github.com/gravitational/teleport/api/utils/tlsutils"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/client"
Expand Down Expand Up @@ -494,21 +493,9 @@ kubernetes matchers are present`)
return nil
}

// awsRegions returns the list of all regions available on every aws partition.
// It is used to validate the AWSMatcher.Regions values.
func awsRegions() []string {
var regions []string
partitions := endpoints.DefaultPartitions()
for _, partition := range partitions {
regions = append(regions, maps.Keys(partition.Regions())...)
}
return regions
}

// checkAndSetDefaultsForAWSMatchers sets the default values for discovery AWS matchers
// and validates the provided types.
func checkAndSetDefaultsForAWSMatchers(matcherInput []AWSMatcher) error {
regions := awsRegions()
for i := range matcherInput {
matcher := &matcherInput[i]
for _, matcherType := range matcher.Types {
Expand All @@ -520,13 +507,13 @@ func checkAndSetDefaultsForAWSMatchers(matcherInput []AWSMatcher) error {

if len(matcher.Regions) == 0 {
return trace.BadParameter("discovery service requires at least one region; supported regions are: %v",
regions)
awsutils.GetKnownRegions())
}

for _, region := range matcher.Regions {
if !slices.Contains(regions, region) {
if err := awsapiutils.IsValidRegion(region); err != nil {
return trace.BadParameter("discovery service does not support region %q; supported regions are: %v",
region, regions)
region, awsutils.GetKnownRegions())
}
}

Expand Down
50 changes: 50 additions & 0 deletions lib/utils/aws/region.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
Copyright 2023 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package aws

import (
"sync"

"github.com/aws/aws-sdk-go/aws/endpoints"
Comment thread
greedy52 marked this conversation as resolved.
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)

// IsKnownRegion returns true if provided region is one of the "well-known"
// AWS regions.
func IsKnownRegion(region string) bool {
return slices.Contains(GetKnownRegions(), region)
}

// GetKnownRegions returns a list of "well-known" AWS regions generated from
// AWS SDK.
func GetKnownRegions() []string {
knownRegionsOnce.Do(func() {
var regions []string
partitions := endpoints.DefaultPartitions()
for _, partition := range partitions {
regions = append(regions, maps.Keys(partition.Regions())...)
}
knownRegions = regions
})
return knownRegions
}

var (
knownRegions []string
knownRegionsOnce sync.Once
)
62 changes: 62 additions & 0 deletions lib/utils/aws/region_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
Copyright 2023 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package aws

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/gravitational/teleport/api/utils/aws"
)

func TestGetKnownRegions(t *testing.T) {
// Picked a few regions just to make sure GetKnownRegions is returning
// something that includes these.
t.Run("hand picked", func(t *testing.T) {
for _, region := range []string{
"us-east-1",
"il-central-1",
"cn-north-1",
"us-gov-west-1",
"us-isob-east-1",
} {
require.Contains(t, GetKnownRegions(), region)
}
})

// Ideally this should be tested in api/utils/aws but api has no access
// to AWS SDK. If this fails aws.IsValidRegion should be updated.
t.Run("IsValidRegion", func(t *testing.T) {
for _, region := range GetKnownRegions() {
require.NoError(t, aws.IsValidRegion(region))
}
})

}
func TestIsKnownRegion(t *testing.T) {
for _, region := range GetKnownRegions() {
require.True(t, IsKnownRegion(region))
}

for _, region := range []string{
"us-east-100",
"cn-north",
} {
require.False(t, IsKnownRegion(region))
}
}