Skip to content

Commit

Permalink
Merge pull request #38057 from hashicorp/b-invalid-fips-endpoints
Browse files Browse the repository at this point in the history
Add fallback when AWS SDK returns invalid FIPS endpoint when global `UseFIPSEndpoint` is set
  • Loading branch information
ewbankkit authored Jun 20, 2024
2 parents 72576b8 + e1a5b36 commit 393a4d1
Show file tree
Hide file tree
Showing 737 changed files with 29,692 additions and 6,201 deletions.
3 changes: 3 additions & 0 deletions .changelog/38057.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
provider: Now falls back to non-FIPS endpoint if `use_fips_endpoint` is set and no FIPS endpoint is available
```
46 changes: 35 additions & 11 deletions docs/add-a-new-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,22 @@ If an AWS service must be created in a non-standard way, for example, the servic
func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*costoptimizationhub.Client, error) {
cfg := *(config["aws_sdkv2_config"].(*aws.Config))

return costoptimizationhub.NewFromConfig(cfg, func(o *costoptimizationhub.Options) {
if endpoint := config["endpoint"].(string); endpoint != "" {
o.BaseEndpoint = aws.String(endpoint)
} else if config["partition"].(string) == names.StandardPartitionID {
// Cost Optimization Hub endpoint is available only in us-east-1 Region.
o.Region = names.USEast1RegionID
}
}), nil
return costoptimizationhub.NewFromConfig(cfg,
costoptimizationhub.WithEndpointResolverV2(newEndpointResolverSDKv2()),
withBaseEndpoint(config[names.AttrEndpoint].(string)),
func(o *costoptimizationhub.Options) {
if config["partition"].(string) == names.StandardPartitionID {
// Cost Optimization Hub endpoint is available only in us-east-1 Region.
if cfg.Region != names.USEast1RegionID {
tflog.Info(ctx, "overriding region", map[string]any{
"original_region": cfg.Region,
"override_region": names.USEast1RegionID,
})
o.Region = names.USEast1RegionID
}
}
},
), nil
}
```

Expand All @@ -121,11 +129,27 @@ If an AWS service must be created in a non-standard way, for example, the servic
// NewConn returns a new AWS SDK for Go v1 client for this service package's AWS API.
func (p *servicePackage) NewConn(ctx context.Context) (*globalaccelerator_sdkv1.GlobalAccelerator, error) {
sess := p.config["session"].(*session_sdkv1.Session)
config := &aws_sdkv1.Config{Endpoint: aws_sdkv1.String(p.config["endpoint"].(string))}

cfg := aws.Config{}

if endpoint := config[names.AttrEndpoint].(string); endpoint != "" {
tflog.Debug(ctx, "setting endpoint", map[string]any{
"tf_aws.endpoint": endpoint,
})
cfg.Endpoint = aws.String(endpoint)
} else {
cfg.EndpointResolver = newEndpointResolverSDKv1(ctx)
}

// Force "global" services to correct Regions.
if p.config["partition"].(string) == endpoints_sdkv1.AwsPartitionID {
config.Region = aws_sdkv1.String(endpoints_sdkv1.UsWest2RegionID)
if config["partition"].(string) == endpoints.AwsPartitionID {
if aws.StringValue(cfg.Region) != endpoints.UsWest2RegionID {
tflog.Info(ctx, "overriding region", map[string]any{
"original_region": aws.StringValue(cfg.Region),
"override_region": endpoints.UsWest2RegionID,
})
cfg.Region = aws.String(endpoints.UsWest2RegionID)
}
}

return globalaccelerator_sdkv1.New(sess.Copy(config)), nil
Expand Down
24 changes: 14 additions & 10 deletions docs/retries-and-waiters.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,20 @@ When custom service client configurations are applied, these will be defined in
func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*s3_sdkv2.Client, error) {
cfg := *(config["aws_sdkv2_config"].(*aws_sdkv2.Config))

return s3_sdkv2.NewFromConfig(cfg, func(o *s3_sdkv2.Options) {
// ..other configuration..

o.Retryer = conns.AddIsErrorRetryables(cfg.Retryer().(aws_sdkv2.RetryerV2), retry_sdkv2.IsErrorRetryableFunc(func(err error) aws_sdkv2.Ternary {
if tfawserr_sdkv2.ErrMessageContains(err, errCodeOperationAborted, "A conflicting conditional operation is currently in progress against this resource. Please try again.") {
return aws_sdkv2.TrueTernary
}
return aws_sdkv2.UnknownTernary // Delegate to configured Retryer.
}))
}), nil
return s3_sdkv2.NewFromConfig(cfg,
s3.WithEndpointResolverV2(newEndpointResolverSDKv2()),
withBaseEndpoint(config[names.AttrEndpoint].(string)),
func(o *s3_sdkv2.Options) {
// ..other configuration..
o.Retryer = conns.AddIsErrorRetryables(cfg.Retryer().(aws_sdkv2.RetryerV2), retry_sdkv2.IsErrorRetryableFunc(func(err error) aws_sdkv2.Ternary {
if tfawserr_sdkv2.ErrMessageContains(err, errCodeOperationAborted, "A conflicting conditional operation is currently in progress against this resource. Please try again.") {
return aws_sdkv2.TrueTernary
}
return aws_sdkv2.UnknownTernary // Delegate to configured Retryer.
}))
},
), nil
}
```

Expand Down
59 changes: 40 additions & 19 deletions internal/generate/serviceendpointtests/file.gtpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import (
{{- end }}
"fmt"
"maps"
{{- if and (ne .GoV1Package "") (eq .GoV2Package "") }}
"net"
"net/url"
{{- end }}
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -133,7 +132,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S
testcases := map[string]endpointTestCase{
"no config": {
with: []setupFunc{withNoConfig},
expected: expectDefaultEndpoint(expectedEndpointRegion),
expected: expectDefaultEndpoint(t, expectedEndpointRegion),
},

// Package name endpoint on Config
Expand Down Expand Up @@ -474,7 +473,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S
with: []setupFunc{
withUseFIPSInConfig,
},
expected: expectDefaultFIPSEndpoint(expectedEndpointRegion),
expected: expectDefaultFIPSEndpoint(t, expectedEndpointRegion),
},

"use fips config with package name endpoint config": {
Expand Down Expand Up @@ -517,22 +516,22 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S
{{ end -}}
}

func defaultEndpoint(region string) string {
func defaultEndpoint(region string) (url.URL, error) {
{{- if ne .GoV2Package "" }}
r := {{ .GoV2Package }}_sdkv2.NewDefaultEndpointResolverV2()

ep, err := r.ResolveEndpoint(context.Background(), {{ .GoV2Package }}_sdkv2.EndpointParameters{
Region: aws_sdkv2.String(region),
})
if err != nil {
return err.Error()
return url.URL{}, err
}

if ep.URI.Path == "" {
ep.URI.Path = "/"
}

return ep.URI.String()
return ep.URI, nil
{{ else }}
r := endpoints.DefaultResolver()

Expand All @@ -543,7 +542,7 @@ func defaultEndpoint(region string) string {
{{- end -}}
)
if err != nil {
return err.Error()
return url.URL{}, err
}

url, _ := url.Parse(ep.URL)
Expand All @@ -552,11 +551,11 @@ func defaultEndpoint(region string) string {
url.Path = "/"
}

return url.String()
return *url, nil
{{ end -}}
}

func defaultFIPSEndpoint(region string) string {
func defaultFIPSEndpoint(region string) (url.URL, error) {
{{- if ne .GoV2Package "" }}
r := {{ .GoV2Package }}_sdkv2.NewDefaultEndpointResolverV2()

Expand All @@ -565,14 +564,14 @@ func defaultFIPSEndpoint(region string) string {
UseFIPS: aws_sdkv2.Bool(true),
})
if err != nil {
return err.Error()
return url.URL{}, err
}

if ep.URI.Path == "" {
ep.URI.Path = "/"
}

return ep.URI.String()
return ep.URI, nil
{{ else }}
r := endpoints.DefaultResolver()

Expand All @@ -581,7 +580,7 @@ func defaultFIPSEndpoint(region string) string {
opt.UseFIPSEndpoint = endpoints.FIPSEndpointStateEnabled
})
if err != nil {
return err.Error()
return url.URL{}, err
}

url, _ := url.Parse(ep.URL)
Expand All @@ -590,7 +589,7 @@ func defaultFIPSEndpoint(region string) string {
url.Path = "/"
}

return url.String()
return *url, nil
{{ end -}}
}

Expand Down Expand Up @@ -716,16 +715,38 @@ func withUseFIPSInConfig(setup *caseSetup) {
setup.config["use_fips_endpoint"] = true
}

func expectDefaultEndpoint(region string) caseExpectations {
func expectDefaultEndpoint(t *testing.T, region string) caseExpectations {
t.Helper()

endpoint, err := defaultEndpoint(region)
if err != nil {
t.Fatalf("resolving accessanalyzer default endpoint: %s", err)
}

return caseExpectations{
endpoint: defaultEndpoint(region),
endpoint: endpoint.String(),
region: expectedCallRegion,
}
}

func expectDefaultFIPSEndpoint(region string) caseExpectations {
return caseExpectations{
endpoint: defaultFIPSEndpoint(region),
func expectDefaultFIPSEndpoint(t *testing.T, region string) caseExpectations {
t.Helper()

endpoint, err := defaultFIPSEndpoint(region)
if err != nil {
t.Fatalf("resolving accessanalyzer FIPS endpoint: %s", err)
}

hostname := endpoint.Hostname()
_, err = net.LookupHost(hostname)
if dnsErr, ok := errs.As[*net.DNSError](err); ok && dnsErr.IsNotFound {
return expectDefaultEndpoint(t, region)
} else if err != nil {
t.Fatalf("looking up accessanalyzer endpoint %q: %s", hostname, err)
}

return caseExpectations{
endpoint: endpoint.String(),
region: expectedCallRegion,
}
}
Expand Down
Loading

0 comments on commit 393a4d1

Please sign in to comment.