diff --git a/app/upstream/forwarder/ses.go b/app/upstream/forwarder/ses.go index 0fdee82..b9af409 100644 --- a/app/upstream/forwarder/ses.go +++ b/app/upstream/forwarder/ses.go @@ -5,13 +5,15 @@ import ( "encoding/json" "log/slog" gohttp "net/http" + "net/url" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws/transport/http" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" - "github.com/aws/aws-sdk-go-v2/service/ses" + awsses "github.com/aws/aws-sdk-go-v2/service/ses" "github.com/aws/aws-sdk-go-v2/service/ses/types" + endpoints "github.com/aws/smithy-go/endpoints" "github.com/leonardinius/smtpd-proxy/app/upstream" ) @@ -30,7 +32,7 @@ type sesUpstreamSettings struct { type sesUpstream struct { settings sesUpstreamSettings - ses *ses.Client + client *awsses.Client logger *slog.Logger } @@ -62,23 +64,20 @@ func (u *sesUpstream) Configure(ctx context.Context, settings map[string]any) (u tr.MaxIdleConnsPerHost = maxConnections tr.MaxConnsPerHost = 0 }) - endpointResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) { - if c.Region != "" { - region = c.Region - } - if c.Endpoint != "" { - return aws.Endpoint{ - PartitionID: "aws", - URL: c.Endpoint, - SigningRegion: region, - }, nil + + opts := make([]func(*awsses.Options), 0) + if c.Endpoint != "" { + u, err := url.Parse(c.Endpoint) + if err != nil { + return nil, err } + opts = append(opts, awsses.WithEndpointResolverV2(&v2EndpointResolver{ + Endpoint: u, + Headers: gohttp.Header{}, + })) + } - // returning EndpointNotFoundError will allow the service to fallback to its default resolution - return aws.Endpoint{}, &aws.EndpointNotFoundError{} - }) cfg, err := config.LoadDefaultConfig(ctx, - config.WithEndpointResolverWithOptions(endpointResolver), config.WithRegion(c.Region), config.WithCredentialsProvider(credentialsProvider), config.WithHTTPClient(httpClient), @@ -87,7 +86,7 @@ func (u *sesUpstream) Configure(ctx context.Context, settings map[string]any) (u return nil, err } - u.ses = ses.NewFromConfig(cfg) + u.client = awsses.NewFromConfig(cfg, opts...) return u, nil } @@ -110,19 +109,19 @@ func (u *sesUpstream) sesForwardRaw(ctx context.Context, mail *upstream.Email) e destinations = append(destinations, mail.Bcc...) destinations = append(destinations, mail.Cc...) - inputRaw := &ses.SendRawEmailInput{ + inputRaw := &awsses.SendRawEmailInput{ Source: aws.String(mail.From), Destinations: destinations, RawMessage: &types.RawMessage{Data: bytes}, } // Attempt to send the email. - _, err = u.ses.SendRawEmail(ctx, inputRaw) + _, err = u.client.SendRawEmail(ctx, inputRaw) return err } func (u *sesUpstream) sesForwardSimple(ctx context.Context, mail *upstream.Email) error { - input := &ses.SendEmailInput{ + input := &awsses.SendEmailInput{ Source: aws.String(mail.From), Destination: &types.Destination{ ToAddresses: mail.To, @@ -149,6 +148,21 @@ func (u *sesUpstream) sesForwardSimple(ctx context.Context, mail *upstream.Email } // Attempt to send the email. - _, err := u.ses.SendEmail(ctx, input) + _, err := u.client.SendEmail(ctx, input) return err } + +type v2EndpointResolver struct { + Endpoint *url.URL + Headers gohttp.Header +} + +// ResolveEndpoint implements ses.EndpointResolverV2. +func (r *v2EndpointResolver) ResolveEndpoint(ctx context.Context, params awsses.EndpointParameters) (endpoints.Endpoint, error) { + return endpoints.Endpoint{ + URI: *r.Endpoint, + Headers: r.Headers, + }, nil +} + +var _ awsses.EndpointResolverV2 = (*v2EndpointResolver)(nil) diff --git a/go.mod b/go.mod index 503d13b..511cdd7 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/leonardinius/smtpd-proxy go 1.22 require ( - github.com/aws/aws-sdk-go-v2 v1.27.0 - github.com/aws/aws-sdk-go-v2/config v1.27.16 - github.com/aws/aws-sdk-go-v2/credentials v1.17.16 - github.com/aws/aws-sdk-go-v2/service/ses v1.22.9 + github.com/aws/aws-sdk-go-v2 v1.27.2 + github.com/aws/aws-sdk-go-v2/config v1.27.18 + github.com/aws/aws-sdk-go-v2/credentials v1.17.18 + github.com/aws/aws-sdk-go-v2/service/ses v1.22.11 github.com/creasty/defaults v1.7.0 github.com/docker/docker v26.1.4+incompatible github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead @@ -24,15 +24,15 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.9 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.28.10 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect github.com/aws/smithy-go v1.20.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/containerd/containerd v1.7.15 // indirect diff --git a/go.sum b/go.sum index ca71e7b..eba756a 100644 --- a/go.sum +++ b/go.sum @@ -8,32 +8,32 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= -github.com/aws/aws-sdk-go-v2 v1.27.0 h1:7bZWKoXhzI+mMR/HjdMx8ZCC5+6fY0lS5tr0bbgiLlo= -github.com/aws/aws-sdk-go-v2 v1.27.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= -github.com/aws/aws-sdk-go-v2/config v1.27.16 h1:knpCuH7laFVGYTNd99Ns5t+8PuRjDn4HnnZK48csipM= -github.com/aws/aws-sdk-go-v2/config v1.27.16/go.mod h1:vutqgRhDUktwSge3hrC3nkuirzkJ4E/mLj5GvI0BQas= -github.com/aws/aws-sdk-go-v2/credentials v1.17.16 h1:7d2QxY83uYl0l58ceyiSpxg9bSbStqBC6BeEeHEchwo= -github.com/aws/aws-sdk-go-v2/credentials v1.17.16/go.mod h1:Ae6li/6Yc6eMzysRL2BXlPYvnrLLBg3D11/AmOjw50k= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 h1:dQLK4TjtnlRGb0czOht2CevZ5l6RSyRWAnKeGd7VAFE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3/go.mod h1:TL79f2P6+8Q7dTsILpiVST+AL9lkF6PPGI167Ny0Cjw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 h1:lf/8VTF2cM+N4SLzaYJERKEWAXq8MOMpZfU6wEPWsPk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7/go.mod h1:4SjkU7QiqK2M9oozyMzfZ/23LmUY+h3oFqhdeP5OMiI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 h1:4OYVp0705xu8yjdyoWix0r9wPIRXnIzzOoUpQVHIJ/g= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7/go.mod h1:vd7ESTEvI76T2Na050gODNmNU7+OyKrIKroYTu4ABiI= +github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8= +github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk= +github.com/aws/aws-sdk-go-v2/config v1.27.18/go.mod h1:0xz6cgdX55+kmppvPm2IaKzIXOheGJhAufacPJaXZ7c= +github.com/aws/aws-sdk-go-v2/credentials v1.17.18 h1:D/ALDWqK4JdY3OFgA2thcPO1c9aYTT5STS/CvnkqY1c= +github.com/aws/aws-sdk-go-v2/credentials v1.17.18/go.mod h1:JuitCWq+F5QGUrmMPsk945rop6bB57jdscu+Glozdnc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzSClHZje/6JkPW5aZyEvrQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5/go.mod h1:gjvE2KBUgUQhcv89jqxrIxH9GaKs1JbZzWejj/DaHGA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 h1:cy8ahBJuhtM8GTTSyOkfy6WVPV1IE+SS5/wfXUYuulw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9/go.mod h1:CZBXGLaJnEZI6EVNcPd7a6B5IC5cA/GkRWtu9fp3S6Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 h1:A4SYk07ef04+vxZToz9LWvAXl9LW0NClpPpMsi31cz0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9/go.mod h1:5jJcHuwDagxN+ErjQ3PU3ocf6Ylc/p9x+BLO/+X4iXw= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJR7JwlSZcHnEa7CNjrSIyVxMFWGAaXy4fJY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y= -github.com/aws/aws-sdk-go-v2/service/ses v1.22.9 h1:PCKzjZiusiIDUkbc3vXq1KsHleIYxfUEtSqO+TpWQU4= -github.com/aws/aws-sdk-go-v2/service/ses v1.22.9/go.mod h1:qPKQ6sRnlNj8HrPbMdEVMNNX9nxyNGQMII63JAUtiuw= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.9 h1:aD7AGQhvPuAxlSUfo0CWU7s6FpkbyykMhGYMvlqTjVs= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.9/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3 h1:Pav5q3cA260Zqez42T9UhIlsd9QeypszRPwC9LdSSsQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3/go.mod h1:9lmoVDVLz/yUZwLaQ676TK02fhCu4+PgRSmMaKR1ozk= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.10 h1:69tpbPED7jKPyzMcrwSvhWcJ9bPnZsZs18NT40JwM0g= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.10/go.mod h1:0Aqn1MnEuitqfsCNyKsdKLhDUOr4txD/g19EfiUqgws= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 h1:o4T+fKxA3gTMcluBNZZXE9DNaMkJuUL1O3mffCUjoJo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11/go.mod h1:84oZdJ+VjuJKs9v1UTC9NaodRZRseOXCTgku+vQJWR8= +github.com/aws/aws-sdk-go-v2/service/ses v1.22.11 h1:919biBiauEw5KUwQaMDvZyMSsSM7dGweMjtkl7ab5zo= +github.com/aws/aws-sdk-go-v2/service/ses v1.22.11/go.mod h1:EQMYqZwFi9mZMm819jKEtAilNXyKbKbHE7oRdizmxJY= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 h1:gEYM2GSpr4YNWc6hCd5nod4+d4kd9vWIAWrmGuLdlMw= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.11/go.mod h1:gVvwPdPNYehHSP9Rs7q27U1EU+3Or2ZpXvzAYJNh63w= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 h1:iXjh3uaH3vsVcnyZX7MqCoCfcyxIrVE9iOQruRaWPrQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5/go.mod h1:5ZXesEuy/QcO0WUnt+4sDkxhdXRHTu2yG0uCSH8B6os= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF5e2mfxHCg7ZVMYmk= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk= github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= diff --git a/tests/ses_forwarder_test.go b/tests/ses_forwarder_test.go index cd0cd39..a7d5a9d 100644 --- a/tests/ses_forwarder_test.go +++ b/tests/ses_forwarder_test.go @@ -6,7 +6,9 @@ import ( "fmt" "io" "net" + "net/http" "net/smtp" + "net/url" "os" "path/filepath" "strings" @@ -17,6 +19,7 @@ import ( awsconfig "github.com/aws/aws-sdk-go-v2/config" awscreds "github.com/aws/aws-sdk-go-v2/credentials" awsses "github.com/aws/aws-sdk-go-v2/service/ses" + endpoints "github.com/aws/smithy-go/endpoints" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" "github.com/jordan-wright/email" @@ -204,22 +207,17 @@ func iniFakeSesSMTPContainer(ctx context.Context) (tc.Container, error) { } func newSesClient(ctx context.Context, t *testing.T, endpoint string) *awsses.Client { - endpointResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) { - return aws.Endpoint{ - PartitionID: "aws", - URL: endpoint, - SigningRegion: "us-east-1", - }, nil - }) - credentialsProvider := awscreds. - NewStaticCredentialsProvider("amz-key-1", "amz-**-secret", "") + u, err := url.Parse(endpoint) + require.NoError(t, err) + v2Resolver := &v2EndpointResolver{Endpoint: u, Headers: http.Header{}} + + credentialsProvider := awscreds.NewStaticCredentialsProvider("amz-key-1", "amz-**-secret", "") cfg, err := awsconfig.LoadDefaultConfig(ctx, - awsconfig.WithEndpointResolverWithOptions(endpointResolver), awsconfig.WithRegion("us-east-1"), awsconfig.WithCredentialsProvider(credentialsProvider), ) require.NoError(t, err) - return awsses.NewFromConfig(cfg) + return awsses.NewFromConfig(cfg, awsses.WithEndpointResolverV2(v2Resolver)) } func requireSesFileWithContains(t *testing.T, needle string) *os.File { @@ -261,3 +259,18 @@ func requireSesFileWithContains(t *testing.T, needle string) *os.File { require.NoError(t, err) return sesFile } + +type v2EndpointResolver struct { + Endpoint *url.URL + Headers http.Header +} + +// ResolveEndpoint implements ses.EndpointResolverV2. +func (r *v2EndpointResolver) ResolveEndpoint(ctx context.Context, params awsses.EndpointParameters) (endpoints.Endpoint, error) { + return endpoints.Endpoint{ + URI: *r.Endpoint, + Headers: r.Headers, + }, nil +} + +var _ awsses.EndpointResolverV2 = (*v2EndpointResolver)(nil)