Merge branch 'main' of github.com:letsencrypt/boulder into second-breakfast

This commit is contained in:
James Renken 2025-07-17 14:30:43 -07:00
commit 0aa2a78740
No known key found for this signature in database
22 changed files with 319 additions and 70 deletions

2
go.mod
View File

@ -5,7 +5,7 @@ go 1.24.0
require (
github.com/aws/aws-sdk-go-v2 v1.36.5
github.com/aws/aws-sdk-go-v2/config v1.29.17
github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0
github.com/aws/smithy-go v1.22.4
github.com/eggsampler/acme/v3 v3.6.2-0.20250208073118-0466a0230941
github.com/go-jose/go-jose/v4 v4.1.0

4
go.sum
View File

@ -33,8 +33,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzRE
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0 h1:JubM8CGDDFaAOmBrd8CRYNr49ZNgEAiLwGwgNMdS0nw=
github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0 h1:5Y75q0RPQoAbieyOuGLhjV9P3txvYgXv2lg0UwJOfmE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=

View File

@ -54,28 +54,20 @@
"issuance": {
"certProfiles": {
"legacy": {
"omitCommonName": false,
"omitKeyEncipherment": false,
"omitClientAuth": false,
"omitSKID": false,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "7776000s",
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_subject_common_name_included",
"e_dnsname_not_valid_tld",
"w_ext_subject_key_identifier_not_recommended_subscriber"
]
},
"modern": {
"omitCommonName": true,
"omitKeyEncipherment": true,
"omitClientAuth": true,
"omitSKID": true,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "583200s",
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert"
]
},
"shortlived": {
"omitCommonName": true,
"omitKeyEncipherment": true,
@ -86,7 +78,22 @@
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert"
"w_ext_subject_key_identifier_missing_sub_cert",
"e_dnsname_not_valid_tld"
]
},
"modern": {
"omitCommonName": true,
"omitKeyEncipherment": true,
"omitClientAuth": true,
"omitSKID": true,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "583200s",
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert",
"e_dnsname_not_valid_tld"
]
}
},

View File

@ -10,12 +10,14 @@
"badResultsOnly": true,
"checkPeriod": "72h",
"acceptableValidityDurations": [
"7776000s"
"7776000s",
"160h"
],
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_subject_common_name_included",
"w_ext_subject_key_identifier_missing_sub_cert",
"w_subject_common_name_included",
"e_dnsname_not_valid_tld",
"w_ext_subject_key_identifier_not_recommended_subscriber"
],
"ctLogListFile": "test/ct-test-srv/log_list.json",

View File

@ -55,7 +55,11 @@
"issuance": {
"certProfiles": {
"legacy": {
"allowMustStaple": true,
"allowMustStaple": false,
"omitCommonName": false,
"omitKeyEncipherment": false,
"omitClientAuth": false,
"omitSKID": false,
"omitOCSP": true,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "7776000s",
@ -63,26 +67,12 @@
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_subject_common_name_included",
"e_dnsname_not_valid_tld",
"w_ext_subject_key_identifier_not_recommended_subscriber"
]
},
"modern": {
"allowMustStaple": true,
"omitCommonName": true,
"omitKeyEncipherment": true,
"omitClientAuth": true,
"omitSKID": true,
"omitOCSP": true,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "583200s",
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert"
]
},
"shortlived": {
"allowMustStaple": true,
"allowMustStaple": false,
"omitCommonName": true,
"omitKeyEncipherment": true,
"omitClientAuth": true,
@ -93,7 +83,24 @@
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert"
"w_ext_subject_key_identifier_missing_sub_cert",
"e_dnsname_not_valid_tld"
]
},
"modern": {
"allowMustStaple": false,
"omitCommonName": true,
"omitKeyEncipherment": true,
"omitClientAuth": true,
"omitSKID": true,
"omitOCSP": true,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "583200s",
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert",
"e_dnsname_not_valid_tld"
]
}
},

View File

@ -10,11 +10,13 @@
"badResultsOnly": true,
"checkPeriod": "72h",
"acceptableValidityDurations": [
"7776000s"
"7776000s",
"160h"
],
"ignoredLints": [
"w_subject_common_name_included",
"w_ext_subject_key_identifier_missing_sub_cert",
"w_subject_common_name_included",
"e_dnsname_not_valid_tld",
"w_ext_subject_key_identifier_not_recommended_subscriber"
],
"ctLogListFile": "test/ct-test-srv/log_list.json"

View File

@ -7,7 +7,9 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"net"
"strings"
"testing"
@ -224,3 +226,76 @@ func TestIPShortLived(t *testing.T) {
t.Errorf("got cert with first IP SAN '%s', wanted '%s'", cert.IPAddresses[0], ip)
}
}
// TestIPCNRejected verifies that we will reject IP address identifiers when
// they occur in the Subject CommonName.
func TestIPCNRejected(t *testing.T) {
t.Parallel()
// Create an account.
client, err := makeClient("mailto:example@letsencrypt.org")
if err != nil {
t.Fatalf("creating acme client: %s", err)
}
// Create an IP address identifier to request.
ip := "64.112.117.122"
ipParsed := net.ParseIP(ip)
idents := []acme.Identifier{
{Type: "ip", Value: ip},
}
order, err := client.Client.NewOrderExtension(client.Account, idents, acme.OrderExtension{Profile: "shortlived"})
if err != nil {
t.Fatalf("creating order: %s", err)
}
if len(order.Authorizations) != 1 {
t.Fatalf("Got %d authorizations, expected 1", len(order.Authorizations))
}
auth, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0])
chal, ok := auth.ChallengeMap[acme.ChallengeTypeHTTP01]
if !ok {
t.Fatalf("no HTTP challenge at %s", order.Authorizations[0])
}
_, err = testSrvClient.AddHTTP01Response(chal.Token, chal.KeyAuthorization)
if err != nil {
t.Fatalf("adding HTTP challenge response: %s", err)
}
defer testSrvClient.RemoveHTTP01Response(chal.Token)
chal, err = client.Client.UpdateChallenge(client.Account, chal)
if err != nil {
t.Fatalf("updating challenge: %s", err)
}
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("creating random cert key: %s", err)
}
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{CommonName: ip},
SignatureAlgorithm: x509.ECDSAWithSHA256,
PublicKeyAlgorithm: x509.ECDSA,
PublicKey: key.Public(),
IPAddresses: []net.IP{ipParsed},
}
csrDer, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, key)
if err != nil {
t.Fatalf("making csr: %s", err)
}
csr, err := x509.ParseCertificateRequest(csrDer)
if err != nil {
t.Fatalf("parsing csr: %s", err)
}
_, err = client.Client.FinalizeOrder(client.Account, order, csr)
if err == nil {
t.Errorf("Finalizing order with IP in CN: got nil error, want badCSR error")
}
if !strings.Contains(err.Error(), "CSR contains IP address in Common Name") {
t.Errorf("issuing with IP in CN failed for the wrong reason: %s", err)
}
}

View File

@ -90,7 +90,7 @@ func branch(args []string) error {
// components.
parts := strings.SplitN(tag, ".", 3)
if len(parts) != 3 {
return fmt.Errorf("failed to parse patch version from release tag %q", tag)
return fmt.Errorf("failed to parse release tag %q as semver", tag)
}
major := parts[0]
@ -101,7 +101,7 @@ func branch(args []string) error {
minor := parts[1]
t, err := time.Parse("20060102", minor)
if err != nil {
return fmt.Errorf("expected minor portion of release tag to be a ")
return fmt.Errorf("expected minor portion of release tag to be a date: %w", err)
}
if t.Year() < 2015 {
return fmt.Errorf("minor portion of release tag appears to be an unrealistic date: %q", t.String())

View File

@ -32,6 +32,7 @@ import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
@ -85,63 +86,105 @@ func tag(args []string) error {
return fmt.Errorf("invalid flags: %w", err)
}
if len(fs.Args()) > 1 {
var branch string
switch len(fs.Args()) {
case 0:
branch = "main"
case 1:
branch = fs.Arg(0)
if !strings.HasPrefix(branch, "release-branch-") {
return fmt.Errorf("branch must be 'main' or 'release-branch-...', got %q", branch)
}
default:
return fmt.Errorf("too many args: %#v", fs.Args())
}
branch := "main"
if len(fs.Args()) == 1 {
branch = fs.Arg(0)
}
switch {
case branch == "main":
break
case strings.HasPrefix(branch, "release-branch-"):
return fmt.Errorf("sorry, tagging hotfix release branches is not yet supported")
default:
return fmt.Errorf("branch must be 'main' or 'release-branch-...', got %q", branch)
}
// Fetch all of the latest commits on this ref from origin, so that we can
// ensure we're tagging the tip of the upstream branch.
// ensure we're tagging the tip of the upstream branch, and that we have all
// of the extant tags along this branch if its a release branch.
_, err = git("fetch", "origin", branch)
if err != nil {
return err
}
// We use semver's vMajor.Minor.Patch format, where the Major version is
// always 0 (no backwards compatibility guarantees), the Minor version is
// the date of the release, and the Patch number is zero for normal releases
// and only non-zero for hotfix releases.
minor := time.Now().Format("20060102")
version := fmt.Sprintf("v0.%s.0", minor)
message := fmt.Sprintf("Release %s", version)
var tag string
switch branch {
case "main":
tag = fmt.Sprintf("v0.%s.0", time.Now().Format("20060102"))
default:
tag, err = nextTagOnBranch(branch)
if err != nil {
return fmt.Errorf("failed to compute next hotfix tag: %w", err)
}
}
// Produce the tag, using -s to PGP sign it. This will fail if a tag with
// that name already exists.
_, err = git("tag", "-s", "-m", message, version, "origin/"+branch)
message := fmt.Sprintf("Release %s", tag)
_, err = git("tag", "-s", "-m", message, tag, "origin/"+branch)
if err != nil {
return err
}
// Show the result of the tagging operation, including the tag message and
// signature, and the commit hash and message, but not the diff.
out, err := git("show", "-s", version)
out, err := git("show", "-s", tag)
if err != nil {
return err
}
show(out)
if push {
_, err = git("push", "origin", version)
_, err = git("push", "origin", tag)
if err != nil {
return err
}
} else {
fmt.Println()
fmt.Println("Please inspect the tag above, then run:")
fmt.Printf(" git push origin %s\n", version)
fmt.Printf(" git push origin %s\n", tag)
}
return nil
}
func nextTagOnBranch(branch string) (string, error) {
baseVersion := strings.TrimPrefix(branch, "release-branch-")
out, err := git("tag", "--list", "--no-column", baseVersion+".*")
if err != nil {
return "", fmt.Errorf("failed to list extant tags on branch %q: %w", branch, err)
}
maxPatch := 0
for tag := range strings.SplitSeq(strings.TrimSpace(out), "\n") {
parts := strings.SplitN(tag, ".", 3)
if len(parts) != 3 {
return "", fmt.Errorf("failed to parse release tag %q as semver", tag)
}
major := parts[0]
if major != "v0" {
return "", fmt.Errorf("expected major portion of prior release tag %q to be 'v0'", tag)
}
minor := parts[1]
t, err := time.Parse("20060102", minor)
if err != nil {
return "", fmt.Errorf("expected minor portion of prior release tag %q to be a date: %w", tag, err)
}
if t.Year() < 2015 {
return "", fmt.Errorf("minor portion of prior release tag %q appears to be an unrealistic date: %q", tag, t.String())
}
patch := parts[2]
patchInt, err := strconv.Atoi(patch)
if err != nil {
return "", fmt.Errorf("patch portion of prior release tag %q is not an integer: %w", tag, err)
}
if patchInt > maxPatch {
maxPatch = patchInt
}
}
return fmt.Sprintf("%s.%d", baseVersion, maxPatch+1), nil
}

View File

@ -1,3 +1,7 @@
# v1.83.0 (2025-07-02)
* **Feature**: Added support for directory bucket creation with tags and bucket ARN retrieval in CreateBucket, ListDirectoryBuckets, and HeadBucket operations
# v1.82.0 (2025-06-25)
* **Feature**: Adds support for additional server-side encryption mode and storage class values for accessing Amazon FSx data from Amazon S3 using S3 Access Points

View File

@ -268,6 +268,15 @@ func (in *CreateBucketInput) bindEndpointParams(p *EndpointParameters) {
type CreateBucketOutput struct {
// The Amazon Resource Name (ARN) of the S3 bucket. ARNs uniquely identify Amazon
// Web Services resources across all of Amazon Web Services.
//
// This parameter is only supported for S3 directory buckets. For more
// information, see [Using tags with directory buckets].
//
// [Using tags with directory buckets]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-buckets-tagging.html
BucketArn *string
// A forward slash followed by the name of the bucket.
Location *string

View File

@ -155,6 +155,15 @@ type HeadBucketOutput struct {
// For directory buckets, the value of this field is false .
AccessPointAlias *bool
// The Amazon Resource Name (ARN) of the S3 bucket. ARNs uniquely identify Amazon
// Web Services resources across all of Amazon Web Services.
//
// This parameter is only supported for S3 directory buckets. For more
// information, see [Using tags with directory buckets].
//
// [Using tags with directory buckets]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-buckets-tagging.html
BucketArn *string
// The name of the location where the bucket will be created.
//
// For directory buckets, the Zone ID of the Availability Zone or the Local Zone

View File

@ -183,6 +183,9 @@ type ListMultipartUploadsInput struct {
// the substring starts at the beginning of the key. The keys that are grouped
// under CommonPrefixes result element are not returned elsewhere in the response.
//
// CommonPrefixes is filtered out from results if it is not lexicographically
// greater than the key-marker.
//
// Directory buckets - For directory buckets, / is the only supported delimiter.
Delimiter *string

View File

@ -79,6 +79,9 @@ type ListObjectVersionsInput struct {
// delimiter are grouped under a single result element in CommonPrefixes . These
// groups are counted as one result against the max-keys limitation. These keys
// are not returned elsewhere in the response.
//
// CommonPrefixes is filtered out from results if it is not lexicographically
// greater than the key-marker.
Delimiter *string
// Encoding type used by Amazon S3 to encode the [object keys] in the response. Responses are

View File

@ -109,6 +109,9 @@ type ListObjectsInput struct {
Bucket *string
// A delimiter is a character that you use to group keys.
//
// CommonPrefixes is filtered out from results if it is not lexicographically
// greater than the key-marker.
Delimiter *string
// Encoding type used by Amazon S3 to encode the [object keys] in the response. Responses are

View File

@ -149,6 +149,9 @@ type ListObjectsV2Input struct {
// A delimiter is a character that you use to group keys.
//
// CommonPrefixes is filtered out from results if it is not lexicographically
// greater than the StartAfter value.
//
// - Directory buckets - For directory buckets, / is the only supported delimiter.
//
// - Directory buckets - When you query ListObjectsV2 with a delimiter during

View File

@ -749,6 +749,11 @@ func awsRestxml_deserializeOpHttpBindingsCreateBucketOutput(v *CreateBucketOutpu
return fmt.Errorf("unsupported deserialization for nil %T", v)
}
if headerValues := response.Header.Values("x-amz-bucket-arn"); len(headerValues) != 0 {
headerValues[0] = strings.TrimSpace(headerValues[0])
v.BucketArn = ptr.String(headerValues[0])
}
if headerValues := response.Header.Values("Location"); len(headerValues) != 0 {
headerValues[0] = strings.TrimSpace(headerValues[0])
v.Location = ptr.String(headerValues[0])
@ -7613,6 +7618,11 @@ func awsRestxml_deserializeOpHttpBindingsHeadBucketOutput(v *HeadBucketOutput, r
v.AccessPointAlias = ptr.Bool(vv)
}
if headerValues := response.Header.Values("x-amz-bucket-arn"); len(headerValues) != 0 {
headerValues[0] = strings.TrimSpace(headerValues[0])
v.BucketArn = ptr.String(headerValues[0])
}
if headerValues := response.Header.Values("x-amz-bucket-location-name"); len(headerValues) != 0 {
headerValues[0] = strings.TrimSpace(headerValues[0])
v.BucketLocationName = ptr.String(headerValues[0])
@ -14783,6 +14793,19 @@ func awsRestxml_deserializeDocumentBucket(v **types.Bucket, decoder smithyxml.No
originalDecoder := decoder
decoder = smithyxml.WrapNodeDecoder(originalDecoder.Decoder, t)
switch {
case strings.EqualFold("BucketArn", t.Name.Local):
val, err := decoder.Value()
if err != nil {
return err
}
if val == nil {
break
}
{
xtv := string(val)
sv.BucketArn = ptr.String(xtv)
}
case strings.EqualFold("BucketRegion", t.Name.Local):
val, err := decoder.Value()
if err != nil {

View File

@ -3,4 +3,4 @@
package s3
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.82.0"
const goModuleVersion = "1.83.0"

View File

@ -10351,6 +10351,19 @@ func awsRestxml_serializeDocumentCreateBucketConfiguration(v *types.CreateBucket
el := value.MemberElement(root)
el.String(string(v.LocationConstraint))
}
if v.Tags != nil {
rootAttr := []smithyxml.Attr{}
root := smithyxml.StartElement{
Name: smithyxml.Name{
Local: "Tags",
},
Attr: rootAttr,
}
el := value.MemberElement(root)
if err := awsRestxml_serializeDocumentTagSet(v.Tags, el); err != nil {
return err
}
}
return nil
}

View File

@ -179,6 +179,15 @@ type AnalyticsS3BucketDestination struct {
// In terms of implementation, a Bucket is a resource.
type Bucket struct {
// The Amazon Resource Name (ARN) of the S3 bucket. ARNs uniquely identify Amazon
// Web Services resources across all of Amazon Web Services.
//
// This parameter is only supported for S3 directory buckets. For more
// information, see [Using tags with directory buckets].
//
// [Using tags with directory buckets]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-buckets-tagging.html
BucketArn *string
// BucketRegion indicates the Amazon Web Services region where the bucket is
// located. If the request contains at least one valid parameter, it is included in
// the response.
@ -615,6 +624,16 @@ type CreateBucketConfiguration struct {
// [Regions and Endpoints]: https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
LocationConstraint BucketLocationConstraint
// An array of tags that you can apply to the bucket that you're creating. Tags
// are key-value pairs of metadata used to categorize and organize your buckets,
// track costs, and control access.
//
// This parameter is only supported for S3 directory buckets. For more
// information, see [Using tags with directory buckets].
//
// [Using tags with directory buckets]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-buckets-tagging.html
Tags []Tag
noSmithyDocumentSerde
}
@ -871,6 +890,8 @@ type Destination struct {
// For valid values, see the StorageClass element of the [PUT Bucket replication] action in the Amazon S3
// API Reference.
//
// FSX_OPENZFS is not an accepted value when replicating objects.
//
// [PUT Bucket replication]: https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTreplication.html
StorageClass StorageClass

View File

@ -2565,6 +2565,23 @@ func validateCORSRules(v []types.CORSRule) error {
}
}
func validateCreateBucketConfiguration(v *types.CreateBucketConfiguration) error {
if v == nil {
return nil
}
invalidParams := smithy.InvalidParamsError{Context: "CreateBucketConfiguration"}
if v.Tags != nil {
if err := validateTagSet(v.Tags); err != nil {
invalidParams.AddNested("Tags", err.(smithy.InvalidParamsError))
}
}
if invalidParams.Len() > 0 {
return invalidParams
} else {
return nil
}
}
func validateDelete(v *types.Delete) error {
if v == nil {
return nil
@ -4059,6 +4076,11 @@ func validateOpCreateBucketInput(v *CreateBucketInput) error {
if v.Bucket == nil {
invalidParams.Add(smithy.NewErrParamRequired("Bucket"))
}
if v.CreateBucketConfiguration != nil {
if err := validateCreateBucketConfiguration(v.CreateBucketConfiguration); err != nil {
invalidParams.AddNested("CreateBucketConfiguration", err.(smithy.InvalidParamsError))
}
}
if invalidParams.Len() > 0 {
return invalidParams
} else {

2
vendor/modules.txt vendored
View File

@ -77,7 +77,7 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url
github.com/aws/aws-sdk-go-v2/service/internal/s3shared
github.com/aws/aws-sdk-go-v2/service/internal/s3shared/arn
github.com/aws/aws-sdk-go-v2/service/internal/s3shared/config
# github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0
# github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0
## explicit; go 1.22
github.com/aws/aws-sdk-go-v2/service/s3
github.com/aws/aws-sdk-go-v2/service/s3/internal/arn