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
27 changes: 27 additions & 0 deletions api/utils/aws/identifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package aws

import (
"regexp"

"github.com/gravitational/trace"
)

Expand All @@ -35,3 +37,28 @@ func IsValidAccountID(accountID string) error {

return nil
}

// IsValidRegion ensures the region looks to be valid.
// 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 {
if matchRegion.MatchString(region) {
return nil
}
return trace.BadParameter("region %q is invalid", region)
}

var (
// 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+$`)
)
52 changes: 52 additions & 0 deletions api/utils/aws/identifiers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,55 @@ func TestIsValidAccountID(t *testing.T) {
})
}
}

func TestIsValidRegion(t *testing.T) {
isBadParamErrFn := func(tt require.TestingT, err error, i ...any) {
require.True(tt, trace.IsBadParameter(err), "expected bad parameter, got %v", err)
}

for _, tt := range []struct {
name string
region string
errCheck require.ErrorAssertionFunc
}{
{
name: "us region",
region: "us-east-1",
errCheck: require.NoError,
},
{
name: "eu region",
region: "eu-west-1",
errCheck: require.NoError,
},
{
name: "us gov",
region: "us-gov-east-1",
errCheck: require.NoError,
},
{
name: "valid format",
region: "xx-iso-somewhere-100",
errCheck: require.NoError,
},
{
name: "empty",
region: "",
errCheck: isBadParamErrFn,
},
{
name: "symbols",
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))
})
}
}
12 changes: 12 additions & 0 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,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 @@ -1234,6 +1235,17 @@ func applyDiscoveryConfig(fc *FileConfig, cfg *service.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,
services.AWSMatcher{
Types: matcher.Types,
Expand Down
22 changes: 5 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/bpf"
Expand All @@ -55,6 +54,7 @@ import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/sshutils/x11"
"github.com/gravitational/teleport/lib/utils"
awsutils "github.com/gravitational/teleport/lib/utils/aws"
)

// FileConfig structure represents the teleport configuration stored in a config file
Expand Down Expand Up @@ -477,21 +477,9 @@ func (conf *FileConfig) CheckAndSetDefaults() error {
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 @@ -503,13 +491,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"
"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
)
61 changes: 61 additions & 0 deletions lib/utils/aws/region_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
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",
"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))
}
}