diff --git a/cmd/aws-iam-authenticator/verify.go b/cmd/aws-iam-authenticator/verify.go index c01f0e048..d3feeb542 100644 --- a/cmd/aws-iam-authenticator/verify.go +++ b/cmd/aws-iam-authenticator/verify.go @@ -57,7 +57,7 @@ var verifyCmd = &cobra.Command{ instanceRegion := getInstanceRegion(context.Background()) - id, err := token.NewVerifier(clusterID, partition, instanceRegion).Verify(tok) + id, err := token.NewVerifier(clusterID, partition, instanceRegion, nil).Verify(tok) if err != nil { fmt.Fprintf(os.Stderr, "could not verify token: %v\n", err) os.Exit(1) diff --git a/go.mod b/go.mod index e8829e2ad..bbecdab30 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,18 @@ module sigs.k8s.io/aws-iam-authenticator go 1.24.4 require ( - github.com/aws/aws-sdk-go-v2 v1.38.0 + github.com/aws/aws-sdk-go-v2 v1.38.2 github.com/aws/aws-sdk-go-v2/config v1.31.0 github.com/aws/aws-sdk-go-v2/credentials v1.18.4 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 github.com/aws/aws-sdk-go-v2/service/ec2 v1.244.0 github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 - github.com/aws/smithy-go v1.22.5 + github.com/aws/smithy-go v1.23.0 github.com/fsnotify/fsnotify v1.9.0 github.com/gofrs/flock v0.12.1 github.com/google/go-cmp v0.7.0 github.com/manifoldco/promptui v0.9.0 - github.com/prometheus/client_golang v1.22.0 + github.com/prometheus/client_golang v1.23.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/afero v1.14.0 github.com/spf13/cobra v1.9.1 @@ -31,9 +31,10 @@ require ( ) require ( - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.5 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/service/account v1.28.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 // indirect @@ -56,6 +57,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -63,7 +65,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.64.0 // indirect + github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/sagikazarmark/locafero v0.9.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect diff --git a/go.sum b/go.sum index 0290d3327..b6f6a8cc9 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/aws/aws-sdk-go-v2 v1.38.0 h1:UCRQ5mlqcFk9HJDIqENSLR3wiG1VTWlyUfLDEvY7RxU= github.com/aws/aws-sdk-go-v2 v1.38.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2 v1.38.2 h1:QUkLO1aTW0yqW95pVzZS0LGFanL71hJ0a49w4TJLMyM= +github.com/aws/aws-sdk-go-v2 v1.38.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= github.com/aws/aws-sdk-go-v2/config v1.31.0 h1:9yH0xiY5fUnVNLRWO0AtayqwU1ndriZdN78LlhruJR4= github.com/aws/aws-sdk-go-v2/config v1.31.0/go.mod h1:VeV3K72nXnhbe4EuxxhzsDc/ByrCSlZwUnWH52Nde/I= github.com/aws/aws-sdk-go-v2/credentials v1.18.4 h1:IPd0Algf1b+Qy9BcDp0sCUcIWdCQPSzDoMK3a8pcbUM= @@ -8,10 +10,16 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 h1:GicIdnekoJsjq9wqnvyi2el github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3/go.mod h1:R7BIi6WNC5mc1kfRM7XM/VHC3uRWkjc396sfabq4iOo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 h1:o9RnO+YZ4X+kt5Z7Nvcishlz0nksIt2PIzDglLMP0vA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3/go.mod h1:+6aLJzOG1fvMOyzIySYjOFjcguGvVRL68R+uoRencN4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.5 h1:d45S2DqHZOkHu0uLUW92VdBoT5v0hh3EyR+DzMEh3ag= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.5/go.mod h1:G6e/dR2c2huh6JmIo9SXysjuLuDDGWMeYGibfW2ZrXg= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 h1:joyyUFhiTQQmVK6ImzNU9TQSNRNeD9kOklqTzyk5v6s= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3/go.mod h1:+vNIyZQP3b3B1tSLI0lxvrU9cfM7gpdRXMFfm67ZcPc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.5 h1:ENhnQOV3SxWHplOqNN1f+uuCNf9n4Y/PKpl6b1WRP0Q= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.5/go.mod h1:csQLMI+odbC0/J+UecSTztG70Dc4aTCOu4GyPNDNpVo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/service/account v1.28.1 h1:GJqHyB+4c8U0p+S7w/C1CGNb3gqgs1Sw6lTqMSpGcHA= +github.com/aws/aws-sdk-go-v2/service/account v1.28.1/go.mod h1:UCcTaFy22BpCwjdiXGTCiVjtBZgtJb56eos4OI43B9g= github.com/aws/aws-sdk-go-v2/service/ec2 v1.244.0 h1:KfETrpt7yv2nkSrjOltgmKyAl8scbzYc4TFtZeoV6uc= github.com/aws/aws-sdk-go-v2/service/ec2 v1.244.0/go.mod h1:EeWmteKqZjaMj45MUmPET1SisFI+HkqWIRQoyjMivcc= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= @@ -26,6 +34,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 h1:MG9VFW43M4A8BYeAfaJJZWrroinx github.com/aws/aws-sdk-go-v2/service/sts v1.37.0/go.mod h1:JdeBDPgpJfuS6rU/hNglmOigKhyEZtBmbraLE4GK1J8= github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= +github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -119,10 +129,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index ea409d14b..83e216a72 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -40,6 +40,7 @@ type Metrics struct { EC2DescribeInstanceCallCount prometheus.Counter StsConnectionFailure *prometheus.CounterVec StsResponses *prometheus.CounterVec + StsDisableRegionRequests prometheus.Counter DynamicFileFailures prometheus.Counter StsThrottling *prometheus.CounterVec E2ELatency *prometheus.HistogramVec @@ -86,6 +87,13 @@ func createMetrics(reg prometheus.Registerer) Metrics { Help: "Sts responses with error code label", }, []string{"ResponseCode", "StsRegion"}, ), + StsDisableRegionRequests: factory.NewCounter( + prometheus.CounterOpts{ + Name: "sts_disabled_region_call", + Namespace: Namespace, + Help: "Number of STS calls made to regions that are disabled / disabling", + }, + ), Latency: factory.NewHistogramVec( prometheus.HistogramOpts{ Namespace: Namespace, diff --git a/pkg/server/server.go b/pkg/server/server.go index f5d8a29cc..139156edc 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -30,6 +30,8 @@ import ( awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" + "github.com/aws/aws-sdk-go-v2/service/account" + "github.com/aws/aws-sdk-go-v2/service/account/types" "sigs.k8s.io/aws-iam-authenticator/pkg/config" "sigs.k8s.io/aws-iam-authenticator/pkg/ec2provider" "sigs.k8s.io/aws-iam-authenticator/pkg/errutil" @@ -78,6 +80,7 @@ type handler struct { backendModeConfigInitDone bool scrubbedAccounts []string cfg config.Config + disabledRegions map[string]bool } // New authentication webhook server. @@ -216,14 +219,21 @@ func (c *Server) getHandler(ctx context.Context, backendMapper BackendMapper, ec instanceRegion = instanceRegionOutput.Region } + acctClient := account.NewFromConfig(cfg) + disabledRegions, err := listDisabledRegions(ctx, acctClient) + if err != nil { + logrus.Errorf("failed to list region status for account : %v", err) + } + h := &handler{ - verifier: token.NewVerifier(c.ClusterID, c.PartitionID, instanceRegion), + verifier: token.NewVerifier(c.ClusterID, c.PartitionID, instanceRegion, disabledRegions), ec2Provider: ec2provider.New(ctx, c.ServerEC2DescribeInstancesRoleARN, c.SourceARN, instanceRegion, ec2DescribeQps, ec2DescribeBurst), clusterID: c.ClusterID, backendMapper: backendMapper, scrubbedAccounts: c.Config.ScrubbedAWSAccounts, cfg: c.Config, backendModeConfigInitDone: false, + disabledRegions: disabledRegions, } h.HandleFunc("/authenticate", func(w http.ResponseWriter, r *http.Request) { @@ -242,6 +252,28 @@ func (c *Server) getHandler(ctx context.Context, backendMapper BackendMapper, ec return h } +func listDisabledRegions(ctx context.Context, client *account.Client) (map[string]bool, error) { + disabledRegions := make(map[string]bool) + listInput := &account.ListRegionsInput{ + RegionOptStatusContains: []types.RegionOptStatus{types.RegionOptStatusDisabled, types.RegionOptStatusDisabling}, + } + + for { + listResult, err := client.ListRegions(ctx, listInput) + if err != nil { + return nil, fmt.Errorf("Error listing region status for account %v", err) + } + for _, res := range listResult.Regions { + disabledRegions[*res.RegionName] = true + } + if listResult.NextToken == nil { + break + } + } + return disabledRegions, nil + +} + func BuildMapperChain(cfg config.Config, modes []string) (BackendMapper, error) { backendMapper := BackendMapper{ mappers: []mapper.Mapper{}, diff --git a/pkg/token/token.go b/pkg/token/token.go index b5203b46b..380b6a735 100644 --- a/pkg/token/token.go +++ b/pkg/token/token.go @@ -424,6 +424,7 @@ type tokenVerifier struct { partition string region string mutex sync.RWMutex + disabledRegions map[string]bool } // Returns the hostnames (regular and dualstack) for a service given a certain region and partition. @@ -458,7 +459,7 @@ func validateInputRegion(region string) bool { } // NewVerifier creates a Verifier that is bound to the clusterID and uses the default http client. -func NewVerifier(clusterID, partitionID, region string) Verifier { +func NewVerifier(clusterID, partitionID, region string, disabledRegions map[string]bool) Verifier { // Initialize metrics if they haven't already been initialized to avoid a // nil pointer panic when setting metric values. if !metrics.Initialized() { @@ -476,6 +477,7 @@ func NewVerifier(clusterID, partitionID, region string) Verifier { validSTShostnames: make(map[string]bool), partition: partitionID, region: region, + disabledRegions: disabledRegions, } } @@ -623,6 +625,12 @@ func (v *tokenVerifier) Verify(token string) (*Identity, error) { return nil, err } + if v.disabledRegions != nil { + if _, ok := v.disabledRegions[stsRegion]; ok { + metrics.Get().StsDisableRegionRequests.Inc() + } + } + if parsedURL.Path != "/" { return nil, FormatError{"unexpected path in pre-signed URL"} } diff --git a/pkg/token/token_test.go b/pkg/token/token_test.go index 9e94457c1..8c331d036 100644 --- a/pkg/token/token_test.go +++ b/pkg/token/token_test.go @@ -24,6 +24,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/pkg/apis/clientauthentication" clientauthv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" @@ -40,7 +41,7 @@ func TestMain(m *testing.M) { func validationErrorTest(t *testing.T, partition string, token string, expectedErr string) { t.Helper() - _, err := NewVerifier("", partition, "").(*tokenVerifier).Verify(token) + _, err := NewVerifier("", partition, "", nil).(*tokenVerifier).Verify(token) errorContains(t, err, expectedErr) } @@ -55,6 +56,45 @@ func validationSuccessTest(t *testing.T, partition, token string) { } } +func validationDisabledRegionMetricsTest(t *testing.T, partition, token string) { + t.Helper() + arn := "arn:aws:iam::123456789012:user/Alice" + account := "123456789012" + userID := "Alice" + + body := jsonResponse(arn, account, userID) + statusCode := 200 + + var rc io.ReadCloser + if body != "" { + rc = io.NopCloser(bytes.NewReader([]byte(body))) + } + verifier := &tokenVerifier{ + client: &http.Client{ + Transport: &roundTripper{ + err: nil, + resp: &http.Response{ + StatusCode: statusCode, + Body: rc, + }, + }, + }, + validSTShostnames: make(map[string]bool), + partition: partition, + disabledRegions: map[string]bool{"sa-east-1": true}, + } + + _, err := verifier.Verify(token) + if err != nil { + t.Errorf("received unexpected error: %s", err) + } + + if value := testutil.ToFloat64(metrics.Get().StsDisableRegionRequests); value != 1.0 { + t.Errorf("disable region requests metrics value must be 1") + } + +} + func errorContains(t *testing.T, err error, expectedErr string) { t.Helper() if err == nil || !strings.Contains(err.Error(), expectedErr) { @@ -219,7 +259,7 @@ func TestSTSEndpoints(t *testing.T) { } for _, c := range cases { - verifier := NewVerifier("", c.partition, c.region).(*tokenVerifier) + verifier := NewVerifier("", c.partition, c.region, nil).(*tokenVerifier) err := verifier.verifyHost(c.domain) if err != nil && c.valid { t.Errorf("%s is not valid endpoint for partition %s, %v", c.domain, c.partition, err) @@ -262,7 +302,7 @@ func TestSTSEndpointResolution(t *testing.T) { } for _, c := range cases { - verifier := NewVerifier("", c.partition, c.region).(*tokenVerifier) + verifier := NewVerifier("", c.partition, c.region, nil).(*tokenVerifier) err := verifier.verifyHost(c.domain) if err != nil && c.valid { t.Errorf("%s is not valid endpoint for partition %s, %v", c.domain, c.partition, err) @@ -297,6 +337,7 @@ func TestVerifyTokenPreSTSValidations(t *testing.T) { validationSuccessTest(t, "aws", toToken(fmt.Sprintf("https://sts.ca-central-1.amazonaws.com/?action=GetCallerIdentity&x-amz-signedheaders=x-k8s-aws-id&x-amz-date=%s&x-amz-expires=60", timeStr))) validationSuccessTest(t, "aws", toToken(fmt.Sprintf("https://sts.eu-west-1.amazonaws.com/?action=GetCallerIdentity&x-amz-signedheaders=x-k8s-aws-id&x-amz-date=%s&x-amz-expires=60", timeStr))) validationSuccessTest(t, "aws", toToken(fmt.Sprintf("https://sts.sa-east-1.amazonaws.com/?action=GetCallerIdentity&x-amz-signedheaders=x-k8s-aws-id&x-amz-date=%s&x-amz-expires=60", timeStr))) + validationDisabledRegionMetricsTest(t, "aws", toToken(fmt.Sprintf("https://sts.sa-east-1.amazonaws.com/?action=GetCallerIdentity&x-amz-signedheaders=x-k8s-aws-id&x-amz-date=%s&x-amz-expires=60", timeStr))) validationErrorTest(t, "aws", toToken(fmt.Sprintf("https://sts.us-west-2.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAAAAAAAAAAAAAAAAA%%2F20220601%%2Fus-west-2%%2Fsts%%2Faws4_request&X-Amz-Date=%s&X-Amz-Expires=900&X-Amz-Security-Token=XXXXXXXXXXXXX&X-Amz-SignedHeaders=host%%3Bx-k8s-aws-id&x-amz-credential=eve&X-Amz-Signature=999999999999999999", timeStr)), "input token was not properly formatted: duplicate query parameter found:") } @@ -330,7 +371,7 @@ func TestVerifyNoRedirectsFollowed(t *testing.T) { })) defer ts.Close() - tokVerifier := NewVerifier("", "aws", "").(*tokenVerifier) + tokVerifier := NewVerifier("", "aws", "", nil).(*tokenVerifier) resp, err := tokVerifier.client.Get(ts.URL) if err != nil {