TUN-3375: Upgrade coredns and prometheus dependencies

This commit is contained in:
Igor Postelnik 2020-09-09 13:09:42 -05:00
parent 7acea1ac99
commit 741cd66c9e
757 changed files with 86868 additions and 32428 deletions

71
go.mod
View File

@ -1,22 +1,19 @@
module github.com/cloudflare/cloudflared module github.com/cloudflare/cloudflared
go 1.12 go 1.14
require ( require (
github.com/BurntSushi/go-sumtype v0.0.0-20190304192233-fcb4a6205bdc // indirect
github.com/DATA-DOG/go-sqlmock v1.3.3 github.com/DATA-DOG/go-sqlmock v1.3.3
github.com/acmacalister/skittles v0.0.0-20160609003031-7423546701e1 github.com/acmacalister/skittles v0.0.0-20160609003031-7423546701e1
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
github.com/aws/aws-sdk-go v1.25.8 github.com/aws/aws-sdk-go v1.34.19
github.com/beorn7/perks v1.0.1 // indirect
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93 github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93
github.com/cloudflare/cfssl v0.0.0-20141119014638-2f7f44e802e2 // indirect
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc
github.com/coredns/coredns v1.2.0 github.com/coredns/coredns v1.7.0
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73 github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73
github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0 github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0
github.com/equinox-io/equinox v1.2.0 github.com/equinox-io/equinox v1.2.0
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
@ -24,57 +21,45 @@ require (
github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434 github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect github.com/frankban/quicktest v1.10.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 github.com/fsnotify/fsnotify v1.4.9
github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10 github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10
github.com/gliderlabs/ssh v0.0.0-20191009160644-63518b5243e0 github.com/gliderlabs/ssh v0.0.0-20191009160644-63518b5243e0
github.com/go-sql-driver/mysql v1.4.1 github.com/go-sql-driver/mysql v1.5.0
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/google/uuid v1.1.1 github.com/google/go-cmp v0.5.2 // indirect
github.com/google/uuid v1.1.2
github.com/gorilla/mux v1.7.3 github.com/gorilla/mux v1.7.3
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
github.com/jmoiron/sqlx v1.2.0 github.com/jmoiron/sqlx v1.2.0
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/kr/text v0.2.0 // indirect
github.com/kshvakov/clickhouse v1.3.11 github.com/kshvakov/clickhouse v1.3.11
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lib/pq v1.2.0 github.com/lib/pq v1.2.0
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect
github.com/mattn/go-sqlite3 v1.11.0 github.com/mattn/go-sqlite3 v1.11.0
github.com/mholt/caddy v0.0.0-20180807230124-d3b731e9255b // indirect github.com/miekg/dns v1.1.31
github.com/miekg/dns v1.1.27
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/opentracing/opentracing-go v1.1.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/philhofer/fwd v1.0.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pkg/errors v0.8.1 github.com/pierrec/lz4 v2.5.2+incompatible // indirect
github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/prometheus/client_golang v1.0.0 github.com/prometheus/client_golang v1.7.1
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect github.com/prometheus/common v0.13.0 // indirect
github.com/prometheus/common v0.7.0 // indirect github.com/stretchr/testify v1.6.0
github.com/prometheus/procfs v0.0.5 // indirect
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
github.com/stretchr/testify v1.3.0
github.com/tinylib/msgp v1.1.0 // indirect
github.com/urfave/cli/v2 v2.2.0 github.com/urfave/cli/v2 v2.2.0
github.com/xo/dburl v0.0.0-20191005012637-293c3298d6c0 github.com/xo/dburl v0.0.0-20191005012637-293c3298d6c0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/net v0.0.0-20200625001655-4c5254603344 golang.org/x/net v0.0.0-20200904194848-62affa334b73
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae golang.org/x/sys v0.0.0-20200909081042-eff7692f9009
golang.org/x/text v0.3.3 // indirect google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
google.golang.org/appengine v1.5.0 // indirect google.golang.org/grpc v1.32.0 // indirect
google.golang.org/genproto v0.0.0-20191007204434-a023cd5227bd // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
google.golang.org/grpc v1.24.0 // indirect
gopkg.in/coreos/go-oidc.v2 v2.1.0 gopkg.in/coreos/go-oidc.v2 v2.1.0
gopkg.in/square/go-jose.v2 v2.4.0 // indirect gopkg.in/square/go-jose.v2 v2.4.0 // indirect
gopkg.in/urfave/cli.v2 v2.0.0-20180128181224-d604b6ffeee8 // indirect gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v2 v2.2.4 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
zombiezen.com/go/capnproto2 v2.18.0+incompatible zombiezen.com/go/capnproto2 v2.18.0+incompatible
) )
// ../../go/pkg/mod/github.com/coredns/coredns@v1.2.0/plugin/metrics/metrics.go:40:49: too many arguments in call to prometheus.NewProcessCollector
// have (int, string)
// want (prometheus.ProcessCollectorOpts)
replace github.com/prometheus/client_golang => github.com/prometheus/client_golang v0.9.0-pre1

985
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ import (
"sync" "sync"
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics"
"github.com/coredns/coredns/plugin/metrics/vars" "github.com/coredns/coredns/plugin/metrics/vars"
"github.com/coredns/coredns/plugin/pkg/dnstest" "github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/rcode" "github.com/coredns/coredns/plugin/pkg/rcode"
@ -27,7 +28,6 @@ func NewMetricsPlugin(next plugin.Handler) *MetricsPlugin {
prometheus.MustRegister(vars.RequestDuration) prometheus.MustRegister(vars.RequestDuration)
prometheus.MustRegister(vars.RequestSize) prometheus.MustRegister(vars.RequestSize)
prometheus.MustRegister(vars.RequestDo) prometheus.MustRegister(vars.RequestDo)
prometheus.MustRegister(vars.RequestType)
prometheus.MustRegister(vars.ResponseSize) prometheus.MustRegister(vars.ResponseSize)
prometheus.MustRegister(vars.ResponseRcode) prometheus.MustRegister(vars.ResponseRcode)
}) })
@ -42,7 +42,8 @@ func (p MetricsPlugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dn
status, err := plugin.NextOrFailure(p.Name(), p.Next, ctx, rw, r) status, err := plugin.NextOrFailure(p.Name(), p.Next, ctx, rw, r)
// Update built-in metrics // Update built-in metrics
vars.Report(ctx, state, ".", rcode.ToString(rw.Rcode), rw.Len, rw.Start) server := metrics.WithServer(ctx)
vars.Report(server, state, ".", rcode.ToString(rw.Rcode), rw.Len, rw.Start)
return status, err return status, err
} }

View File

@ -27,6 +27,7 @@ var (
// ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB // ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB
// and KiB are both 1024. // and KiB are both 1024.
// However "kB", which is the correct SI spelling of 1000 Bytes, is rejected.
func ParseBase2Bytes(s string) (Base2Bytes, error) { func ParseBase2Bytes(s string) (Base2Bytes, error) {
n, err := ParseUnit(s, bytesUnitMap) n, err := ParseUnit(s, bytesUnitMap)
if err != nil { if err != nil {
@ -68,12 +69,13 @@ func ParseMetricBytes(s string) (MetricBytes, error) {
return MetricBytes(n), err return MetricBytes(n), err
} }
// TODO: represents 1000B as uppercase "KB", while SI standard requires "kB".
func (m MetricBytes) String() string { func (m MetricBytes) String() string {
return ToString(int64(m), 1000, "B", "B") return ToString(int64(m), 1000, "B", "B")
} }
// ParseStrictBytes supports both iB and B suffixes for base 2 and metric, // ParseStrictBytes supports both iB and B suffixes for base 2 and metric,
// respectively. That is, KiB represents 1024 and KB represents 1000. // respectively. That is, KiB represents 1024 and kB, KB represent 1000.
func ParseStrictBytes(s string) (int64, error) { func ParseStrictBytes(s string) (int64, error) {
n, err := ParseUnit(s, bytesUnitMap) n, err := ParseUnit(s, bytesUnitMap)
if err != nil { if err != nil {

View File

@ -1 +1,3 @@
module github.com/alecthomas/units module github.com/alecthomas/units
require github.com/stretchr/testify v1.4.0

11
vendor/github.com/alecthomas/units/go.sum generated vendored Normal file
View File

@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -14,13 +14,37 @@ const (
) )
func MakeUnitMap(suffix, shortSuffix string, scale int64) map[string]float64 { func MakeUnitMap(suffix, shortSuffix string, scale int64) map[string]float64 {
return map[string]float64{ res := map[string]float64{
shortSuffix: 1, shortSuffix: 1,
"K" + suffix: float64(scale), // see below for "k" / "K"
"M" + suffix: float64(scale * scale), "M" + suffix: float64(scale * scale),
"G" + suffix: float64(scale * scale * scale), "G" + suffix: float64(scale * scale * scale),
"T" + suffix: float64(scale * scale * scale * scale), "T" + suffix: float64(scale * scale * scale * scale),
"P" + suffix: float64(scale * scale * scale * scale * scale), "P" + suffix: float64(scale * scale * scale * scale * scale),
"E" + suffix: float64(scale * scale * scale * scale * scale * scale), "E" + suffix: float64(scale * scale * scale * scale * scale * scale),
} }
// Standard SI prefixes use lowercase "k" for kilo = 1000.
// For compatibility, and to be fool-proof, we accept both "k" and "K" in metric mode.
//
// However, official binary prefixes are always capitalized - "KiB" -
// and we specifically never parse "kB" as 1024B because:
//
// (1) people pedantic enough to use lowercase according to SI unlikely to abuse "k" to mean 1024 :-)
//
// (2) Use of capital K for 1024 was an informal tradition predating IEC prefixes:
// "The binary meaning of the kilobyte for 1024 bytes typically uses the symbol KB, with an
// uppercase letter K."
// -- https://en.wikipedia.org/wiki/Kilobyte#Base_2_(1024_bytes)
// "Capitalization of the letter K became the de facto standard for binary notation, although this
// could not be extended to higher powers, and use of the lowercase k did persist.[13][14][15]"
// -- https://en.wikipedia.org/wiki/Binary_prefix#History
// See also the extensive https://en.wikipedia.org/wiki/Timeline_of_binary_prefixes.
if scale == 1024 {
res["K"+suffix] = float64(scale)
} else {
res["k"+suffix] = float64(scale)
res["K"+suffix] = float64(scale)
}
return res
} }

93
vendor/github.com/aws/aws-sdk-go/aws/arn/arn.go generated vendored Normal file
View File

@ -0,0 +1,93 @@
// Package arn provides a parser for interacting with Amazon Resource Names.
package arn
import (
"errors"
"strings"
)
const (
arnDelimiter = ":"
arnSections = 6
arnPrefix = "arn:"
// zero-indexed
sectionPartition = 1
sectionService = 2
sectionRegion = 3
sectionAccountID = 4
sectionResource = 5
// errors
invalidPrefix = "arn: invalid prefix"
invalidSections = "arn: not enough sections"
)
// ARN captures the individual fields of an Amazon Resource Name.
// See http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html for more information.
type ARN struct {
// The partition that the resource is in. For standard AWS regions, the partition is "aws". If you have resources in
// other partitions, the partition is "aws-partitionname". For example, the partition for resources in the China
// (Beijing) region is "aws-cn".
Partition string
// The service namespace that identifies the AWS product (for example, Amazon S3, IAM, or Amazon RDS). For a list of
// namespaces, see
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces.
Service string
// The region the resource resides in. Note that the ARNs for some resources do not require a region, so this
// component might be omitted.
Region string
// The ID of the AWS account that owns the resource, without the hyphens. For example, 123456789012. Note that the
// ARNs for some resources don't require an account number, so this component might be omitted.
AccountID string
// The content of this part of the ARN varies by service. It often includes an indicator of the type of resource —
// for example, an IAM user or Amazon RDS database - followed by a slash (/) or a colon (:), followed by the
// resource name itself. Some services allows paths for resource names, as described in
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#arns-paths.
Resource string
}
// Parse parses an ARN into its constituent parts.
//
// Some example ARNs:
// arn:aws:elasticbeanstalk:us-east-1:123456789012:environment/My App/MyEnvironment
// arn:aws:iam::123456789012:user/David
// arn:aws:rds:eu-west-1:123456789012:db:mysql-db
// arn:aws:s3:::my_corporate_bucket/exampleobject.png
func Parse(arn string) (ARN, error) {
if !strings.HasPrefix(arn, arnPrefix) {
return ARN{}, errors.New(invalidPrefix)
}
sections := strings.SplitN(arn, arnDelimiter, arnSections)
if len(sections) != arnSections {
return ARN{}, errors.New(invalidSections)
}
return ARN{
Partition: sections[sectionPartition],
Service: sections[sectionService],
Region: sections[sectionRegion],
AccountID: sections[sectionAccountID],
Resource: sections[sectionResource],
}, nil
}
// IsARN returns whether the given string is an ARN by looking for
// whether the string starts with "arn:" and contains the correct number
// of sections delimited by colons(:).
func IsARN(arn string) bool {
return strings.HasPrefix(arn, arnPrefix) && strings.Count(arn, ":") >= arnSections-1
}
// String returns the canonical representation of the ARN
func (arn ARN) String() string {
return arnPrefix +
arn.Partition + arnDelimiter +
arn.Service + arnDelimiter +
arn.Region + arnDelimiter +
arn.AccountID + arnDelimiter +
arn.Resource
}

View File

@ -70,7 +70,7 @@ func rValuesAtPath(v interface{}, path string, createPath, caseSensitive, nilTer
value = value.FieldByNameFunc(func(name string) bool { value = value.FieldByNameFunc(func(name string) bool {
if c == name { if c == name {
return true return true
} else if !caseSensitive && strings.ToLower(name) == strings.ToLower(c) { } else if !caseSensitive && strings.EqualFold(name, c) {
return true return true
} }
return false return false

View File

@ -12,6 +12,7 @@ import (
type Config struct { type Config struct {
Config *aws.Config Config *aws.Config
Handlers request.Handlers Handlers request.Handlers
PartitionID string
Endpoint string Endpoint string
SigningRegion string SigningRegion string
SigningName string SigningName string

View File

@ -5,6 +5,7 @@ type ClientInfo struct {
ServiceName string ServiceName string
ServiceID string ServiceID string
APIVersion string APIVersion string
PartitionID string
Endpoint string Endpoint string
SigningName string SigningName string
SigningRegion string SigningRegion string

View File

@ -43,7 +43,7 @@ type Config struct {
// An optional endpoint URL (hostname only or fully qualified URI) // An optional endpoint URL (hostname only or fully qualified URI)
// that overrides the default generated endpoint for a client. Set this // that overrides the default generated endpoint for a client. Set this
// to `""` to use the default generated endpoint. // to `nil` or the value to `""` to use the default generated endpoint.
// //
// Note: You must still provide a `Region` value when specifying an // Note: You must still provide a `Region` value when specifying an
// endpoint for a client. // endpoint for a client.
@ -138,7 +138,7 @@ type Config struct {
// `ExpectContinueTimeout` for information on adjusting the continue wait // `ExpectContinueTimeout` for information on adjusting the continue wait
// timeout. https://golang.org/pkg/net/http/#Transport // timeout. https://golang.org/pkg/net/http/#Transport
// //
// You should use this flag to disble 100-Continue if you experience issues // You should use this flag to disable 100-Continue if you experience issues
// with proxies or third party S3 compatible services. // with proxies or third party S3 compatible services.
S3Disable100Continue *bool S3Disable100Continue *bool
@ -161,6 +161,17 @@ type Config struct {
// on GetObject API calls. // on GetObject API calls.
S3DisableContentMD5Validation *bool S3DisableContentMD5Validation *bool
// Set this to `true` to have the S3 service client to use the region specified
// in the ARN, when an ARN is provided as an argument to a bucket parameter.
S3UseARNRegion *bool
// Set this to `true` to enable the SDK to unmarshal API response header maps to
// normalized lower case map keys.
//
// For example S3's X-Amz-Meta prefixed header will be unmarshaled to lower case
// Metadata member's map keys. The value of the header in the map is unaffected.
LowerCaseHeaderMaps *bool
// Set this to `true` to disable the EC2Metadata client from overriding the // Set this to `true` to disable the EC2Metadata client from overriding the
// default http.Client's Timeout. This is helpful if you do not want the // default http.Client's Timeout. This is helpful if you do not want the
// EC2Metadata client to create a new http.Client. This options is only // EC2Metadata client to create a new http.Client. This options is only
@ -172,7 +183,7 @@ type Config struct {
// //
// Example: // Example:
// sess := session.Must(session.NewSession(aws.NewConfig() // sess := session.Must(session.NewSession(aws.NewConfig()
// .WithEC2MetadataDiableTimeoutOverride(true))) // .WithEC2MetadataDisableTimeoutOverride(true)))
// //
// svc := s3.New(sess) // svc := s3.New(sess)
// //
@ -183,7 +194,7 @@ type Config struct {
// both IPv4 and IPv6 addressing. // both IPv4 and IPv6 addressing.
// //
// Setting this for a service which does not support dual stack will fail // Setting this for a service which does not support dual stack will fail
// to make requets. It is not recommended to set this value on the session // to make requests. It is not recommended to set this value on the session
// as it will apply to all service clients created with the session. Even // as it will apply to all service clients created with the session. Even
// services which don't support dual stack endpoints. // services which don't support dual stack endpoints.
// //
@ -227,6 +238,7 @@ type Config struct {
// EnableEndpointDiscovery will allow for endpoint discovery on operations that // EnableEndpointDiscovery will allow for endpoint discovery on operations that
// have the definition in its model. By default, endpoint discovery is off. // have the definition in its model. By default, endpoint discovery is off.
// To use EndpointDiscovery, Endpoint should be unset or set to an empty string.
// //
// Example: // Example:
// sess := session.Must(session.NewSession(&aws.Config{ // sess := session.Must(session.NewSession(&aws.Config{
@ -246,6 +258,12 @@ type Config struct {
// Disabling this feature is useful when you want to use local endpoints // Disabling this feature is useful when you want to use local endpoints
// for testing that do not support the modeled host prefix pattern. // for testing that do not support the modeled host prefix pattern.
DisableEndpointHostPrefix *bool DisableEndpointHostPrefix *bool
// STSRegionalEndpoint will enable regional or legacy endpoint resolving
STSRegionalEndpoint endpoints.STSRegionalEndpoint
// S3UsEast1RegionalEndpoint will enable regional or legacy endpoint resolving
S3UsEast1RegionalEndpoint endpoints.S3UsEast1RegionalEndpoint
} }
// NewConfig returns a new Config pointer that can be chained with builder // NewConfig returns a new Config pointer that can be chained with builder
@ -379,6 +397,13 @@ func (c *Config) WithS3DisableContentMD5Validation(enable bool) *Config {
} }
// WithS3UseARNRegion sets a config S3UseARNRegion value and
// returning a Config pointer for chaining
func (c *Config) WithS3UseARNRegion(enable bool) *Config {
c.S3UseARNRegion = &enable
return c
}
// WithUseDualStack sets a config UseDualStack value returning a Config // WithUseDualStack sets a config UseDualStack value returning a Config
// pointer for chaining. // pointer for chaining.
func (c *Config) WithUseDualStack(enable bool) *Config { func (c *Config) WithUseDualStack(enable bool) *Config {
@ -420,6 +445,20 @@ func (c *Config) MergeIn(cfgs ...*Config) {
} }
} }
// WithSTSRegionalEndpoint will set whether or not to use regional endpoint flag
// when resolving the endpoint for a service
func (c *Config) WithSTSRegionalEndpoint(sre endpoints.STSRegionalEndpoint) *Config {
c.STSRegionalEndpoint = sre
return c
}
// WithS3UsEast1RegionalEndpoint will set whether or not to use regional endpoint flag
// when resolving the endpoint for a service
func (c *Config) WithS3UsEast1RegionalEndpoint(sre endpoints.S3UsEast1RegionalEndpoint) *Config {
c.S3UsEast1RegionalEndpoint = sre
return c
}
func mergeInConfig(dst *Config, other *Config) { func mergeInConfig(dst *Config, other *Config) {
if other == nil { if other == nil {
return return
@ -493,6 +532,10 @@ func mergeInConfig(dst *Config, other *Config) {
dst.S3DisableContentMD5Validation = other.S3DisableContentMD5Validation dst.S3DisableContentMD5Validation = other.S3DisableContentMD5Validation
} }
if other.S3UseARNRegion != nil {
dst.S3UseARNRegion = other.S3UseARNRegion
}
if other.UseDualStack != nil { if other.UseDualStack != nil {
dst.UseDualStack = other.UseDualStack dst.UseDualStack = other.UseDualStack
} }
@ -520,6 +563,14 @@ func mergeInConfig(dst *Config, other *Config) {
if other.DisableEndpointHostPrefix != nil { if other.DisableEndpointHostPrefix != nil {
dst.DisableEndpointHostPrefix = other.DisableEndpointHostPrefix dst.DisableEndpointHostPrefix = other.DisableEndpointHostPrefix
} }
if other.STSRegionalEndpoint != endpoints.UnsetSTSEndpoint {
dst.STSRegionalEndpoint = other.STSRegionalEndpoint
}
if other.S3UsEast1RegionalEndpoint != endpoints.UnsetS3UsEast1Endpoint {
dst.S3UsEast1RegionalEndpoint = other.S3UsEast1RegionalEndpoint
}
} }
// Copy will return a shallow copy of the Config object. If any additional // Copy will return a shallow copy of the Config object. If any additional

View File

@ -2,42 +2,8 @@
package aws package aws
import "time" import (
"github.com/aws/aws-sdk-go/internal/context"
// An emptyCtx is a copy of the Go 1.7 context.emptyCtx type. This is copied to
// provide a 1.6 and 1.5 safe version of context that is compatible with Go
// 1.7's Context.
//
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}
func (e *emptyCtx) String() string {
switch e {
case backgroundCtx:
return "aws.BackgroundContext"
}
return "unknown empty Context"
}
var (
backgroundCtx = new(emptyCtx)
) )
// BackgroundContext returns a context that will never be canceled, has no // BackgroundContext returns a context that will never be canceled, has no
@ -52,5 +18,5 @@ var (
// //
// See https://golang.org/pkg/context for more information on Contexts. // See https://golang.org/pkg/context for more information on Contexts.
func BackgroundContext() Context { func BackgroundContext() Context {
return backgroundCtx return context.BackgroundCtx
} }

View File

@ -161,7 +161,7 @@ func handleSendError(r *request.Request, err error) {
} }
// Catch all request errors, and let the default retrier determine // Catch all request errors, and let the default retrier determine
// if the error is retryable. // if the error is retryable.
r.Error = awserr.New("RequestError", "send request failed", err) r.Error = awserr.New(request.ErrCodeRequestError, "send request failed", err)
// Override the error with a context canceled error, if that was canceled. // Override the error with a context canceled error, if that was canceled.
ctx := r.Context() ctx := r.Context()
@ -225,6 +225,8 @@ var ValidateEndpointHandler = request.NamedHandler{Name: "core.ValidateEndpointH
if r.ClientInfo.SigningRegion == "" && aws.StringValue(r.Config.Region) == "" { if r.ClientInfo.SigningRegion == "" && aws.StringValue(r.Config.Region) == "" {
r.Error = aws.ErrMissingRegion r.Error = aws.ErrMissingRegion
} else if r.ClientInfo.Endpoint == "" { } else if r.ClientInfo.Endpoint == "" {
// Was any endpoint provided by the user, or one was derived by the
// SDK's endpoint resolver?
r.Error = aws.ErrMissingEndpoint r.Error = aws.ErrMissingEndpoint
} }
}} }}

View File

@ -0,0 +1,22 @@
// +build !go1.7
package credentials
import (
"github.com/aws/aws-sdk-go/internal/context"
)
// backgroundContext returns a context that will never be canceled, has no
// values, and no deadline. This context is used by the SDK to provide
// backwards compatibility with non-context API operations and functionality.
//
// Go 1.6 and before:
// This context function is equivalent to context.Background in the Go stdlib.
//
// Go 1.7 and later:
// The context returned will be the value returned by context.Background()
//
// See https://golang.org/pkg/context for more information on Contexts.
func backgroundContext() Context {
return context.BackgroundCtx
}

View File

@ -0,0 +1,20 @@
// +build go1.7
package credentials
import "context"
// backgroundContext returns a context that will never be canceled, has no
// values, and no deadline. This context is used by the SDK to provide
// backwards compatibility with non-context API operations and functionality.
//
// Go 1.6 and before:
// This context function is equivalent to context.Background in the Go stdlib.
//
// Go 1.7 and later:
// The context returned will be the value returned by context.Background()
//
// See https://golang.org/pkg/context for more information on Contexts.
func backgroundContext() Context {
return context.Background()
}

View File

@ -0,0 +1,39 @@
// +build !go1.9
package credentials
import "time"
// Context is an copy of the Go v1.7 stdlib's context.Context interface.
// It is represented as a SDK interface to enable you to use the "WithContext"
// API methods with Go v1.6 and a Context type such as golang.org/x/net/context.
//
// This type, aws.Context, and context.Context are equivalent.
//
// See https://golang.org/pkg/context on how to use contexts.
type Context interface {
// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
Deadline() (deadline time.Time, ok bool)
// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
Done() <-chan struct{}
// Err returns a non-nil error value after Done is closed. Err returns
// Canceled if the context was canceled or DeadlineExceeded if the
// context's deadline passed. No other values for Err are defined.
// After Done is closed, successive calls to Err return the same value.
Err() error
// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
//
// Use context values only for request-scoped data that transits
// processes and API boundaries, not for passing optional parameters to
// functions.
Value(key interface{}) interface{}
}

View File

@ -0,0 +1,13 @@
// +build go1.9
package credentials
import "context"
// Context is an alias of the Go stdlib's context.Context interface.
// It can be used within the SDK's API operation "WithContext" methods.
//
// This type, aws.Context, and context.Context are equivalent.
//
// See https://golang.org/pkg/context on how to use contexts.
type Context = context.Context

View File

@ -50,10 +50,11 @@ package credentials
import ( import (
"fmt" "fmt"
"sync" "sync/atomic"
"time" "time"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/internal/sync/singleflight"
) )
// AnonymousCredentials is an empty Credential object that can be used as // AnonymousCredentials is an empty Credential object that can be used as
@ -106,6 +107,13 @@ type Provider interface {
IsExpired() bool IsExpired() bool
} }
// ProviderWithContext is a Provider that can retrieve credentials with a Context
type ProviderWithContext interface {
Provider
RetrieveWithContext(Context) (Value, error)
}
// An Expirer is an interface that Providers can implement to expose the expiration // An Expirer is an interface that Providers can implement to expose the expiration
// time, if known. If the Provider cannot accurately provide this info, // time, if known. If the Provider cannot accurately provide this info,
// it should not implement this interface. // it should not implement this interface.
@ -197,20 +205,68 @@ func (e *Expiry) ExpiresAt() time.Time {
// first instance of the credentials Value. All calls to Get() after that // first instance of the credentials Value. All calls to Get() after that
// will return the cached credentials Value until IsExpired() returns true. // will return the cached credentials Value until IsExpired() returns true.
type Credentials struct { type Credentials struct {
creds Value creds atomic.Value
forceRefresh bool sf singleflight.Group
m sync.RWMutex
provider Provider provider Provider
} }
// NewCredentials returns a pointer to a new Credentials with the provider set. // NewCredentials returns a pointer to a new Credentials with the provider set.
func NewCredentials(provider Provider) *Credentials { func NewCredentials(provider Provider) *Credentials {
return &Credentials{ c := &Credentials{
provider: provider, provider: provider,
forceRefresh: true,
} }
c.creds.Store(Value{})
return c
}
// GetWithContext returns the credentials value, or error if the credentials
// Value failed to be retrieved. Will return early if the passed in context is
// canceled.
//
// Will return the cached credentials Value if it has not expired. If the
// credentials Value has expired the Provider's Retrieve() will be called
// to refresh the credentials.
//
// If Credentials.Expire() was called the credentials Value will be force
// expired, and the next call to Get() will cause them to be refreshed.
//
// Passed in Context is equivalent to aws.Context, and context.Context.
func (c *Credentials) GetWithContext(ctx Context) (Value, error) {
if curCreds := c.creds.Load(); !c.isExpired(curCreds) {
return curCreds.(Value), nil
}
// Cannot pass context down to the actual retrieve, because the first
// context would cancel the whole group when there is not direct
// association of items in the group.
resCh := c.sf.DoChan("", func() (interface{}, error) {
return c.singleRetrieve(&suppressedContext{ctx})
})
select {
case res := <-resCh:
return res.Val.(Value), res.Err
case <-ctx.Done():
return Value{}, awserr.New("RequestCanceled",
"request context canceled", ctx.Err())
}
}
func (c *Credentials) singleRetrieve(ctx Context) (creds interface{}, err error) {
if curCreds := c.creds.Load(); !c.isExpired(curCreds) {
return curCreds.(Value), nil
}
if p, ok := c.provider.(ProviderWithContext); ok {
creds, err = p.RetrieveWithContext(ctx)
} else {
creds, err = c.provider.Retrieve()
}
if err == nil {
c.creds.Store(creds)
}
return creds, err
} }
// Get returns the credentials value, or error if the credentials Value failed // Get returns the credentials value, or error if the credentials Value failed
@ -223,30 +279,7 @@ func NewCredentials(provider Provider) *Credentials {
// If Credentials.Expire() was called the credentials Value will be force // If Credentials.Expire() was called the credentials Value will be force
// expired, and the next call to Get() will cause them to be refreshed. // expired, and the next call to Get() will cause them to be refreshed.
func (c *Credentials) Get() (Value, error) { func (c *Credentials) Get() (Value, error) {
// Check the cached credentials first with just the read lock. return c.GetWithContext(backgroundContext())
c.m.RLock()
if !c.isExpired() {
creds := c.creds
c.m.RUnlock()
return creds, nil
}
c.m.RUnlock()
// Credentials are expired need to retrieve the credentials taking the full
// lock.
c.m.Lock()
defer c.m.Unlock()
if c.isExpired() {
creds, err := c.provider.Retrieve()
if err != nil {
return Value{}, err
}
c.creds = creds
c.forceRefresh = false
}
return c.creds, nil
} }
// Expire expires the credentials and forces them to be retrieved on the // Expire expires the credentials and forces them to be retrieved on the
@ -255,10 +288,7 @@ func (c *Credentials) Get() (Value, error) {
// This will override the Provider's expired state, and force Credentials // This will override the Provider's expired state, and force Credentials
// to call the Provider's Retrieve(). // to call the Provider's Retrieve().
func (c *Credentials) Expire() { func (c *Credentials) Expire() {
c.m.Lock() c.creds.Store(Value{})
defer c.m.Unlock()
c.forceRefresh = true
} }
// IsExpired returns if the credentials are no longer valid, and need // IsExpired returns if the credentials are no longer valid, and need
@ -267,33 +297,43 @@ func (c *Credentials) Expire() {
// If the Credentials were forced to be expired with Expire() this will // If the Credentials were forced to be expired with Expire() this will
// reflect that override. // reflect that override.
func (c *Credentials) IsExpired() bool { func (c *Credentials) IsExpired() bool {
c.m.RLock() return c.isExpired(c.creds.Load())
defer c.m.RUnlock()
return c.isExpired()
} }
// isExpired helper method wrapping the definition of expired credentials. // isExpired helper method wrapping the definition of expired credentials.
func (c *Credentials) isExpired() bool { func (c *Credentials) isExpired(creds interface{}) bool {
return c.forceRefresh || c.provider.IsExpired() return creds == nil || creds.(Value) == Value{} || c.provider.IsExpired()
} }
// ExpiresAt provides access to the functionality of the Expirer interface of // ExpiresAt provides access to the functionality of the Expirer interface of
// the underlying Provider, if it supports that interface. Otherwise, it returns // the underlying Provider, if it supports that interface. Otherwise, it returns
// an error. // an error.
func (c *Credentials) ExpiresAt() (time.Time, error) { func (c *Credentials) ExpiresAt() (time.Time, error) {
c.m.RLock()
defer c.m.RUnlock()
expirer, ok := c.provider.(Expirer) expirer, ok := c.provider.(Expirer)
if !ok { if !ok {
return time.Time{}, awserr.New("ProviderNotExpirer", return time.Time{}, awserr.New("ProviderNotExpirer",
fmt.Sprintf("provider %s does not support ExpiresAt()", c.creds.ProviderName), fmt.Sprintf("provider %s does not support ExpiresAt()", c.creds.Load().(Value).ProviderName),
nil) nil)
} }
if c.forceRefresh { if c.creds.Load().(Value) == (Value{}) {
// set expiration time to the distant past // set expiration time to the distant past
return time.Time{}, nil return time.Time{}, nil
} }
return expirer.ExpiresAt(), nil return expirer.ExpiresAt(), nil
} }
type suppressedContext struct {
Context
}
func (s *suppressedContext) Deadline() (deadline time.Time, ok bool) {
return time.Time{}, false
}
func (s *suppressedContext) Done() <-chan struct{} {
return nil
}
func (s *suppressedContext) Err() error {
return nil
}

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/client" "github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
@ -87,7 +88,14 @@ func NewCredentialsWithClient(client *ec2metadata.EC2Metadata, options ...func(*
// Error will be returned if the request fails, or unable to extract // Error will be returned if the request fails, or unable to extract
// the desired credentials. // the desired credentials.
func (m *EC2RoleProvider) Retrieve() (credentials.Value, error) { func (m *EC2RoleProvider) Retrieve() (credentials.Value, error) {
credsList, err := requestCredList(m.Client) return m.RetrieveWithContext(aws.BackgroundContext())
}
// RetrieveWithContext retrieves credentials from the EC2 service.
// Error will be returned if the request fails, or unable to extract
// the desired credentials.
func (m *EC2RoleProvider) RetrieveWithContext(ctx credentials.Context) (credentials.Value, error) {
credsList, err := requestCredList(ctx, m.Client)
if err != nil { if err != nil {
return credentials.Value{ProviderName: ProviderName}, err return credentials.Value{ProviderName: ProviderName}, err
} }
@ -97,7 +105,7 @@ func (m *EC2RoleProvider) Retrieve() (credentials.Value, error) {
} }
credsName := credsList[0] credsName := credsList[0]
roleCreds, err := requestCred(m.Client, credsName) roleCreds, err := requestCred(ctx, m.Client, credsName)
if err != nil { if err != nil {
return credentials.Value{ProviderName: ProviderName}, err return credentials.Value{ProviderName: ProviderName}, err
} }
@ -130,8 +138,8 @@ const iamSecurityCredsPath = "iam/security-credentials/"
// requestCredList requests a list of credentials from the EC2 service. // requestCredList requests a list of credentials from the EC2 service.
// If there are no credentials, or there is an error making or receiving the request // If there are no credentials, or there is an error making or receiving the request
func requestCredList(client *ec2metadata.EC2Metadata) ([]string, error) { func requestCredList(ctx aws.Context, client *ec2metadata.EC2Metadata) ([]string, error) {
resp, err := client.GetMetadata(iamSecurityCredsPath) resp, err := client.GetMetadataWithContext(ctx, iamSecurityCredsPath)
if err != nil { if err != nil {
return nil, awserr.New("EC2RoleRequestError", "no EC2 instance role found", err) return nil, awserr.New("EC2RoleRequestError", "no EC2 instance role found", err)
} }
@ -154,8 +162,8 @@ func requestCredList(client *ec2metadata.EC2Metadata) ([]string, error) {
// //
// If the credentials cannot be found, or there is an error reading the response // If the credentials cannot be found, or there is an error reading the response
// and error will be returned. // and error will be returned.
func requestCred(client *ec2metadata.EC2Metadata, credsName string) (ec2RoleCredRespBody, error) { func requestCred(ctx aws.Context, client *ec2metadata.EC2Metadata, credsName string) (ec2RoleCredRespBody, error) {
resp, err := client.GetMetadata(sdkuri.PathJoin(iamSecurityCredsPath, credsName)) resp, err := client.GetMetadataWithContext(ctx, sdkuri.PathJoin(iamSecurityCredsPath, credsName))
if err != nil { if err != nil {
return ec2RoleCredRespBody{}, return ec2RoleCredRespBody{},
awserr.New("EC2RoleRequestError", awserr.New("EC2RoleRequestError",

View File

@ -116,7 +116,13 @@ func (p *Provider) IsExpired() bool {
// Retrieve will attempt to request the credentials from the endpoint the Provider // Retrieve will attempt to request the credentials from the endpoint the Provider
// was configured for. And error will be returned if the retrieval fails. // was configured for. And error will be returned if the retrieval fails.
func (p *Provider) Retrieve() (credentials.Value, error) { func (p *Provider) Retrieve() (credentials.Value, error) {
resp, err := p.getCredentials() return p.RetrieveWithContext(aws.BackgroundContext())
}
// RetrieveWithContext will attempt to request the credentials from the endpoint the Provider
// was configured for. And error will be returned if the retrieval fails.
func (p *Provider) RetrieveWithContext(ctx credentials.Context) (credentials.Value, error) {
resp, err := p.getCredentials(ctx)
if err != nil { if err != nil {
return credentials.Value{ProviderName: ProviderName}, return credentials.Value{ProviderName: ProviderName},
awserr.New("CredentialsEndpointError", "failed to load credentials", err) awserr.New("CredentialsEndpointError", "failed to load credentials", err)
@ -148,7 +154,7 @@ type errorOutput struct {
Message string `json:"message"` Message string `json:"message"`
} }
func (p *Provider) getCredentials() (*getCredentialsOutput, error) { func (p *Provider) getCredentials(ctx aws.Context) (*getCredentialsOutput, error) {
op := &request.Operation{ op := &request.Operation{
Name: "GetCredentials", Name: "GetCredentials",
HTTPMethod: "GET", HTTPMethod: "GET",
@ -156,6 +162,7 @@ func (p *Provider) getCredentials() (*getCredentialsOutput, error) {
out := &getCredentialsOutput{} out := &getCredentialsOutput{}
req := p.Client.NewRequest(op, nil, out) req := p.Client.NewRequest(op, nil, out)
req.SetContext(ctx)
req.HTTPRequest.Header.Set("Accept", "application/json") req.HTTPRequest.Header.Set("Accept", "application/json")
if authToken := p.AuthorizationToken; len(authToken) != 0 { if authToken := p.AuthorizationToken; len(authToken) != 0 {
req.HTTPRequest.Header.Set("Authorization", authToken) req.HTTPRequest.Header.Set("Authorization", authToken)

View File

@ -90,6 +90,7 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/internal/sdkio"
) )
const ( const (
@ -142,7 +143,7 @@ const (
// DefaultBufSize limits buffer size from growing to an enormous // DefaultBufSize limits buffer size from growing to an enormous
// amount due to a faulty process. // amount due to a faulty process.
DefaultBufSize = 1024 DefaultBufSize = int(8 * sdkio.KibiByte)
// DefaultTimeout default limit on time a process can run. // DefaultTimeout default limit on time a process can run.
DefaultTimeout = time.Duration(1) * time.Minute DefaultTimeout = time.Duration(1) * time.Minute

View File

@ -17,8 +17,9 @@ var (
ErrSharedCredentialsHomeNotFound = awserr.New("UserHomeNotFound", "user home directory not found.", nil) ErrSharedCredentialsHomeNotFound = awserr.New("UserHomeNotFound", "user home directory not found.", nil)
) )
// A SharedCredentialsProvider retrieves credentials from the current user's home // A SharedCredentialsProvider retrieves access key pair (access key ID,
// directory, and keeps track if those credentials are expired. // secret access key, and session token if present) credentials from the current
// user's home directory, and keeps track if those credentials are expired.
// //
// Profile ini file example: $HOME/.aws/credentials // Profile ini file example: $HOME/.aws/credentials
type SharedCredentialsProvider struct { type SharedCredentialsProvider struct {

View File

@ -19,7 +19,9 @@ type StaticProvider struct {
} }
// NewStaticCredentials returns a pointer to a new Credentials object // NewStaticCredentials returns a pointer to a new Credentials object
// wrapping a static credentials value provider. // wrapping a static credentials value provider. Token is only required
// for temporary security credentials retrieved via STS, otherwise an empty
// string can be passed for this parameter.
func NewStaticCredentials(id, secret, token string) *Credentials { func NewStaticCredentials(id, secret, token string) *Credentials {
return NewCredentials(&StaticProvider{Value: Value{ return NewCredentials(&StaticProvider{Value: Value{
AccessKeyID: id, AccessKeyID: id,

View File

@ -87,6 +87,7 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/client" "github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/internal/sdkrand" "github.com/aws/aws-sdk-go/internal/sdkrand"
"github.com/aws/aws-sdk-go/service/sts" "github.com/aws/aws-sdk-go/service/sts"
) )
@ -118,6 +119,10 @@ type AssumeRoler interface {
AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error)
} }
type assumeRolerWithContext interface {
AssumeRoleWithContext(aws.Context, *sts.AssumeRoleInput, ...request.Option) (*sts.AssumeRoleOutput, error)
}
// DefaultDuration is the default amount of time in minutes that the credentials // DefaultDuration is the default amount of time in minutes that the credentials
// will be valid for. // will be valid for.
var DefaultDuration = time.Duration(15) * time.Minute var DefaultDuration = time.Duration(15) * time.Minute
@ -144,6 +149,13 @@ type AssumeRoleProvider struct {
// Session name, if you wish to reuse the credentials elsewhere. // Session name, if you wish to reuse the credentials elsewhere.
RoleSessionName string RoleSessionName string
// Optional, you can pass tag key-value pairs to your session. These tags are called session tags.
Tags []*sts.Tag
// A list of keys for session tags that you want to set as transitive.
// If you set a tag key as transitive, the corresponding key and value passes to subsequent sessions in a role chain.
TransitiveTagKeys []*string
// Expiry duration of the STS credentials. Defaults to 15 minutes if not set. // Expiry duration of the STS credentials. Defaults to 15 minutes if not set.
Duration time.Duration Duration time.Duration
@ -157,6 +169,29 @@ type AssumeRoleProvider struct {
// size. // size.
Policy *string Policy *string
// The ARNs of IAM managed policies you want to use as managed session policies.
// The policies must exist in the same account as the role.
//
// This parameter is optional. You can provide up to 10 managed policy ARNs.
// However, the plain text that you use for both inline and managed session
// policies can't exceed 2,048 characters.
//
// An AWS conversion compresses the passed session policies and session tags
// into a packed binary format that has a separate limit. Your request can fail
// for this limit even if your plain text meets the other requirements. The
// PackedPolicySize response element indicates by percentage how close the policies
// and tags for your request are to the upper size limit.
//
// Passing policies to this operation returns new temporary credentials. The
// resulting session's permissions are the intersection of the role's identity-based
// policy and the session policies. You can use the role's temporary credentials
// in subsequent AWS API calls to access resources in the account that owns
// the role. You cannot use session policies to grant more permissions than
// those allowed by the identity-based policy of the role that is being assumed.
// For more information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// in the IAM User Guide.
PolicyArns []*sts.PolicyDescriptorType
// The identification number of the MFA device that is associated with the user // The identification number of the MFA device that is associated with the user
// who is making the AssumeRole call. Specify this value if the trust policy // who is making the AssumeRole call. Specify this value if the trust policy
// of the role being assumed includes a condition that requires MFA authentication. // of the role being assumed includes a condition that requires MFA authentication.
@ -258,6 +293,11 @@ func NewCredentialsWithClient(svc AssumeRoler, roleARN string, options ...func(*
// Retrieve generates a new set of temporary credentials using STS. // Retrieve generates a new set of temporary credentials using STS.
func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) { func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) {
return p.RetrieveWithContext(aws.BackgroundContext())
}
// RetrieveWithContext generates a new set of temporary credentials using STS.
func (p *AssumeRoleProvider) RetrieveWithContext(ctx credentials.Context) (credentials.Value, error) {
// Apply defaults where parameters are not set. // Apply defaults where parameters are not set.
if p.RoleSessionName == "" { if p.RoleSessionName == "" {
// Try to work out a role name that will hopefully end up unique. // Try to work out a role name that will hopefully end up unique.
@ -273,6 +313,9 @@ func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) {
RoleArn: aws.String(p.RoleARN), RoleArn: aws.String(p.RoleARN),
RoleSessionName: aws.String(p.RoleSessionName), RoleSessionName: aws.String(p.RoleSessionName),
ExternalId: p.ExternalID, ExternalId: p.ExternalID,
Tags: p.Tags,
PolicyArns: p.PolicyArns,
TransitiveTagKeys: p.TransitiveTagKeys,
} }
if p.Policy != nil { if p.Policy != nil {
input.Policy = p.Policy input.Policy = p.Policy
@ -295,7 +338,15 @@ func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) {
} }
} }
roleOutput, err := p.Client.AssumeRole(input) var roleOutput *sts.AssumeRoleOutput
var err error
if c, ok := p.Client.(assumeRolerWithContext); ok {
roleOutput, err = c.AssumeRoleWithContext(ctx, input)
} else {
roleOutput, err = p.Client.AssumeRole(input)
}
if err != nil { if err != nil {
return credentials.Value{ProviderName: ProviderName}, err return credentials.Value{ProviderName: ProviderName}, err
} }

View File

@ -28,15 +28,46 @@ const (
// compare test values. // compare test values.
var now = time.Now var now = time.Now
// TokenFetcher shuold return WebIdentity token bytes or an error
type TokenFetcher interface {
FetchToken(credentials.Context) ([]byte, error)
}
// FetchTokenPath is a path to a WebIdentity token file
type FetchTokenPath string
// FetchToken returns a token by reading from the filesystem
func (f FetchTokenPath) FetchToken(ctx credentials.Context) ([]byte, error) {
data, err := ioutil.ReadFile(string(f))
if err != nil {
errMsg := fmt.Sprintf("unable to read file at %s", f)
return nil, awserr.New(ErrCodeWebIdentity, errMsg, err)
}
return data, nil
}
// WebIdentityRoleProvider is used to retrieve credentials using // WebIdentityRoleProvider is used to retrieve credentials using
// an OIDC token. // an OIDC token.
type WebIdentityRoleProvider struct { type WebIdentityRoleProvider struct {
credentials.Expiry credentials.Expiry
PolicyArns []*sts.PolicyDescriptorType
client stsiface.STSAPI // Duration the STS credentials will be valid for. Truncated to seconds.
// If unset, the assumed role will use AssumeRoleWithWebIdentity's default
// expiry duration. See
// https://docs.aws.amazon.com/sdk-for-go/api/service/sts/#STS.AssumeRoleWithWebIdentity
// for more information.
Duration time.Duration
// The amount of time the credentials will be refreshed before they expire.
// This is useful refresh credentials before they expire to reduce risk of
// using credentials as they expire. If unset, will default to no expiry
// window.
ExpiryWindow time.Duration ExpiryWindow time.Duration
tokenFilePath string client stsiface.STSAPI
tokenFetcher TokenFetcher
roleARN string roleARN string
roleSessionName string roleSessionName string
} }
@ -52,9 +83,15 @@ func NewWebIdentityCredentials(c client.ConfigProvider, roleARN, roleSessionName
// NewWebIdentityRoleProvider will return a new WebIdentityRoleProvider with the // NewWebIdentityRoleProvider will return a new WebIdentityRoleProvider with the
// provided stsiface.STSAPI // provided stsiface.STSAPI
func NewWebIdentityRoleProvider(svc stsiface.STSAPI, roleARN, roleSessionName, path string) *WebIdentityRoleProvider { func NewWebIdentityRoleProvider(svc stsiface.STSAPI, roleARN, roleSessionName, path string) *WebIdentityRoleProvider {
return NewWebIdentityRoleProviderWithToken(svc, roleARN, roleSessionName, FetchTokenPath(path))
}
// NewWebIdentityRoleProviderWithToken will return a new WebIdentityRoleProvider with the
// provided stsiface.STSAPI and a TokenFetcher
func NewWebIdentityRoleProviderWithToken(svc stsiface.STSAPI, roleARN, roleSessionName string, tokenFetcher TokenFetcher) *WebIdentityRoleProvider {
return &WebIdentityRoleProvider{ return &WebIdentityRoleProvider{
client: svc, client: svc,
tokenFilePath: path, tokenFetcher: tokenFetcher,
roleARN: roleARN, roleARN: roleARN,
roleSessionName: roleSessionName, roleSessionName: roleSessionName,
} }
@ -64,10 +101,16 @@ func NewWebIdentityRoleProvider(svc stsiface.STSAPI, roleARN, roleSessionName, p
// 'WebIdentityTokenFilePath' specified destination and if that is empty an // 'WebIdentityTokenFilePath' specified destination and if that is empty an
// error will be returned. // error will be returned.
func (p *WebIdentityRoleProvider) Retrieve() (credentials.Value, error) { func (p *WebIdentityRoleProvider) Retrieve() (credentials.Value, error) {
b, err := ioutil.ReadFile(p.tokenFilePath) return p.RetrieveWithContext(aws.BackgroundContext())
}
// RetrieveWithContext will attempt to assume a role from a token which is located at
// 'WebIdentityTokenFilePath' specified destination and if that is empty an
// error will be returned.
func (p *WebIdentityRoleProvider) RetrieveWithContext(ctx credentials.Context) (credentials.Value, error) {
b, err := p.tokenFetcher.FetchToken(ctx)
if err != nil { if err != nil {
errMsg := fmt.Sprintf("unable to read file at %s", p.tokenFilePath) return credentials.Value{}, awserr.New(ErrCodeWebIdentity, "failed fetching WebIdentity token: ", err)
return credentials.Value{}, awserr.New(ErrCodeWebIdentity, errMsg, err)
} }
sessionName := p.roleSessionName sessionName := p.roleSessionName
@ -76,11 +119,22 @@ func (p *WebIdentityRoleProvider) Retrieve() (credentials.Value, error) {
// uses unix time in nanoseconds to uniquely identify sessions. // uses unix time in nanoseconds to uniquely identify sessions.
sessionName = strconv.FormatInt(now().UnixNano(), 10) sessionName = strconv.FormatInt(now().UnixNano(), 10)
} }
var duration *int64
if p.Duration != 0 {
duration = aws.Int64(int64(p.Duration / time.Second))
}
req, resp := p.client.AssumeRoleWithWebIdentityRequest(&sts.AssumeRoleWithWebIdentityInput{ req, resp := p.client.AssumeRoleWithWebIdentityRequest(&sts.AssumeRoleWithWebIdentityInput{
PolicyArns: p.PolicyArns,
RoleArn: &p.roleARN, RoleArn: &p.roleARN,
RoleSessionName: &sessionName, RoleSessionName: &sessionName,
WebIdentityToken: aws.String(string(b)), WebIdentityToken: aws.String(string(b)),
DurationSeconds: duration,
}) })
req.SetContext(ctx)
// InvalidIdentityToken error is a temporary error that can occur // InvalidIdentityToken error is a temporary error that can occur
// when assuming an Role with a JWT web identity token. // when assuming an Role with a JWT web identity token.
req.RetryErrorCodes = append(req.RetryErrorCodes, sts.ErrCodeInvalidIdentityTokenException) req.RetryErrorCodes = append(req.RetryErrorCodes, sts.ErrCodeInvalidIdentityTokenException)

View File

@ -66,7 +66,6 @@ func (rep *Reporter) sendAPICallAttemptMetric(r *request.Request) {
XAmzRequestID: aws.String(r.RequestID), XAmzRequestID: aws.String(r.RequestID),
AttemptCount: aws.Int(r.RetryCount + 1),
AttemptLatency: aws.Int(int(now.Sub(r.AttemptTime).Nanoseconds() / int64(time.Millisecond))), AttemptLatency: aws.Int(int(now.Sub(r.AttemptTime).Nanoseconds() / int64(time.Millisecond))),
AccessKey: aws.String(creds.AccessKeyID), AccessKey: aws.String(creds.AccessKeyID),
} }
@ -90,7 +89,7 @@ func getMetricException(err awserr.Error) metricException {
code := err.Code() code := err.Code()
switch code { switch code {
case "RequestError", case request.ErrCodeRequestError,
request.ErrCodeSerialization, request.ErrCodeSerialization,
request.CanceledErrorCode: request.CanceledErrorCode:
return sdkException{ return sdkException{

View File

@ -4,28 +4,73 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/internal/sdkuri" "github.com/aws/aws-sdk-go/internal/sdkuri"
) )
// getToken uses the duration to return a token for EC2 metadata service,
// or an error if the request failed.
func (c *EC2Metadata) getToken(ctx aws.Context, duration time.Duration) (tokenOutput, error) {
op := &request.Operation{
Name: "GetToken",
HTTPMethod: "PUT",
HTTPPath: "/latest/api/token",
}
var output tokenOutput
req := c.NewRequest(op, nil, &output)
req.SetContext(ctx)
// remove the fetch token handler from the request handlers to avoid infinite recursion
req.Handlers.Sign.RemoveByName(fetchTokenHandlerName)
// Swap the unmarshalMetadataHandler with unmarshalTokenHandler on this request.
req.Handlers.Unmarshal.Swap(unmarshalMetadataHandlerName, unmarshalTokenHandler)
ttl := strconv.FormatInt(int64(duration/time.Second), 10)
req.HTTPRequest.Header.Set(ttlHeader, ttl)
err := req.Send()
// Errors with bad request status should be returned.
if err != nil {
err = awserr.NewRequestFailure(
awserr.New(req.HTTPResponse.Status, http.StatusText(req.HTTPResponse.StatusCode), err),
req.HTTPResponse.StatusCode, req.RequestID)
}
return output, err
}
// GetMetadata uses the path provided to request information from the EC2 // GetMetadata uses the path provided to request information from the EC2
// instance metdata service. The content will be returned as a string, or // instance metadata service. The content will be returned as a string, or
// error if the request failed. // error if the request failed.
func (c *EC2Metadata) GetMetadata(p string) (string, error) { func (c *EC2Metadata) GetMetadata(p string) (string, error) {
return c.GetMetadataWithContext(aws.BackgroundContext(), p)
}
// GetMetadataWithContext uses the path provided to request information from the EC2
// instance metadata service. The content will be returned as a string, or
// error if the request failed.
func (c *EC2Metadata) GetMetadataWithContext(ctx aws.Context, p string) (string, error) {
op := &request.Operation{ op := &request.Operation{
Name: "GetMetadata", Name: "GetMetadata",
HTTPMethod: "GET", HTTPMethod: "GET",
HTTPPath: sdkuri.PathJoin("/meta-data", p), HTTPPath: sdkuri.PathJoin("/latest/meta-data", p),
} }
output := &metadataOutput{} output := &metadataOutput{}
req := c.NewRequest(op, nil, output)
err := req.Send()
req := c.NewRequest(op, nil, output)
req.SetContext(ctx)
err := req.Send()
return output.Content, err return output.Content, err
} }
@ -33,21 +78,24 @@ func (c *EC2Metadata) GetMetadata(p string) (string, error) {
// there is no user-data setup for the EC2 instance a "NotFoundError" error // there is no user-data setup for the EC2 instance a "NotFoundError" error
// code will be returned. // code will be returned.
func (c *EC2Metadata) GetUserData() (string, error) { func (c *EC2Metadata) GetUserData() (string, error) {
return c.GetUserDataWithContext(aws.BackgroundContext())
}
// GetUserDataWithContext returns the userdata that was configured for the service. If
// there is no user-data setup for the EC2 instance a "NotFoundError" error
// code will be returned.
func (c *EC2Metadata) GetUserDataWithContext(ctx aws.Context) (string, error) {
op := &request.Operation{ op := &request.Operation{
Name: "GetUserData", Name: "GetUserData",
HTTPMethod: "GET", HTTPMethod: "GET",
HTTPPath: "/user-data", HTTPPath: "/latest/user-data",
} }
output := &metadataOutput{} output := &metadataOutput{}
req := c.NewRequest(op, nil, output) req := c.NewRequest(op, nil, output)
req.Handlers.UnmarshalError.PushBack(func(r *request.Request) { req.SetContext(ctx)
if r.HTTPResponse.StatusCode == http.StatusNotFound {
r.Error = awserr.New("NotFoundError", "user-data not found", r.Error)
}
})
err := req.Send()
err := req.Send()
return output.Content, err return output.Content, err
} }
@ -55,16 +103,24 @@ func (c *EC2Metadata) GetUserData() (string, error) {
// instance metadata service for dynamic data. The content will be returned // instance metadata service for dynamic data. The content will be returned
// as a string, or error if the request failed. // as a string, or error if the request failed.
func (c *EC2Metadata) GetDynamicData(p string) (string, error) { func (c *EC2Metadata) GetDynamicData(p string) (string, error) {
return c.GetDynamicDataWithContext(aws.BackgroundContext(), p)
}
// GetDynamicDataWithContext uses the path provided to request information from the EC2
// instance metadata service for dynamic data. The content will be returned
// as a string, or error if the request failed.
func (c *EC2Metadata) GetDynamicDataWithContext(ctx aws.Context, p string) (string, error) {
op := &request.Operation{ op := &request.Operation{
Name: "GetDynamicData", Name: "GetDynamicData",
HTTPMethod: "GET", HTTPMethod: "GET",
HTTPPath: sdkuri.PathJoin("/dynamic", p), HTTPPath: sdkuri.PathJoin("/latest/dynamic", p),
} }
output := &metadataOutput{} output := &metadataOutput{}
req := c.NewRequest(op, nil, output) req := c.NewRequest(op, nil, output)
err := req.Send() req.SetContext(ctx)
err := req.Send()
return output.Content, err return output.Content, err
} }
@ -72,7 +128,14 @@ func (c *EC2Metadata) GetDynamicData(p string) (string, error) {
// instance. Error is returned if the request fails or is unable to parse // instance. Error is returned if the request fails or is unable to parse
// the response. // the response.
func (c *EC2Metadata) GetInstanceIdentityDocument() (EC2InstanceIdentityDocument, error) { func (c *EC2Metadata) GetInstanceIdentityDocument() (EC2InstanceIdentityDocument, error) {
resp, err := c.GetDynamicData("instance-identity/document") return c.GetInstanceIdentityDocumentWithContext(aws.BackgroundContext())
}
// GetInstanceIdentityDocumentWithContext retrieves an identity document describing an
// instance. Error is returned if the request fails or is unable to parse
// the response.
func (c *EC2Metadata) GetInstanceIdentityDocumentWithContext(ctx aws.Context) (EC2InstanceIdentityDocument, error) {
resp, err := c.GetDynamicDataWithContext(ctx, "instance-identity/document")
if err != nil { if err != nil {
return EC2InstanceIdentityDocument{}, return EC2InstanceIdentityDocument{},
awserr.New("EC2MetadataRequestError", awserr.New("EC2MetadataRequestError",
@ -91,7 +154,12 @@ func (c *EC2Metadata) GetInstanceIdentityDocument() (EC2InstanceIdentityDocument
// IAMInfo retrieves IAM info from the metadata API // IAMInfo retrieves IAM info from the metadata API
func (c *EC2Metadata) IAMInfo() (EC2IAMInfo, error) { func (c *EC2Metadata) IAMInfo() (EC2IAMInfo, error) {
resp, err := c.GetMetadata("iam/info") return c.IAMInfoWithContext(aws.BackgroundContext())
}
// IAMInfoWithContext retrieves IAM info from the metadata API
func (c *EC2Metadata) IAMInfoWithContext(ctx aws.Context) (EC2IAMInfo, error) {
resp, err := c.GetMetadataWithContext(ctx, "iam/info")
if err != nil { if err != nil {
return EC2IAMInfo{}, return EC2IAMInfo{},
awserr.New("EC2MetadataRequestError", awserr.New("EC2MetadataRequestError",
@ -116,24 +184,36 @@ func (c *EC2Metadata) IAMInfo() (EC2IAMInfo, error) {
// Region returns the region the instance is running in. // Region returns the region the instance is running in.
func (c *EC2Metadata) Region() (string, error) { func (c *EC2Metadata) Region() (string, error) {
resp, err := c.GetMetadata("placement/availability-zone") return c.RegionWithContext(aws.BackgroundContext())
}
// RegionWithContext returns the region the instance is running in.
func (c *EC2Metadata) RegionWithContext(ctx aws.Context) (string, error) {
ec2InstanceIdentityDocument, err := c.GetInstanceIdentityDocumentWithContext(ctx)
if err != nil { if err != nil {
return "", err return "", err
} }
// extract region from the ec2InstanceIdentityDocument
if len(resp) == 0 { region := ec2InstanceIdentityDocument.Region
return "", awserr.New("EC2MetadataError", "invalid Region response", nil) if len(region) == 0 {
return "", awserr.New("EC2MetadataError", "invalid region received for ec2metadata instance", nil)
} }
// returns region
// returns region without the suffix. Eg: us-west-2a becomes us-west-2 return region, nil
return resp[:len(resp)-1], nil
} }
// Available returns if the application has access to the EC2 Metadata service. // Available returns if the application has access to the EC2 Metadata service.
// Can be used to determine if application is running within an EC2 Instance and // Can be used to determine if application is running within an EC2 Instance and
// the metadata service is available. // the metadata service is available.
func (c *EC2Metadata) Available() bool { func (c *EC2Metadata) Available() bool {
if _, err := c.GetMetadata("instance-id"); err != nil { return c.AvailableWithContext(aws.BackgroundContext())
}
// AvailableWithContext returns if the application has access to the EC2 Metadata service.
// Can be used to determine if application is running within an EC2 Instance and
// the metadata service is available.
func (c *EC2Metadata) AvailableWithContext(ctx aws.Context) bool {
if _, err := c.GetMetadataWithContext(ctx, "instance-id"); err != nil {
return false return false
} }

View File

@ -5,6 +5,10 @@
// variable "AWS_EC2_METADATA_DISABLED=true". This environment variable set to // variable "AWS_EC2_METADATA_DISABLED=true". This environment variable set to
// true instructs the SDK to disable the EC2 Metadata client. The client cannot // true instructs the SDK to disable the EC2 Metadata client. The client cannot
// be used while the environment variable is set to true, (case insensitive). // be used while the environment variable is set to true, (case insensitive).
//
// The endpoint of the EC2 IMDS client can be configured via the environment
// variable, AWS_EC2_METADATA_SERVICE_ENDPOINT when creating the client with a
// Session. See aws/session#Options.EC2IMDSEndpoint for more details.
package ec2metadata package ec2metadata
import ( import (
@ -12,7 +16,9 @@ import (
"errors" "errors"
"io" "io"
"net/http" "net/http"
"net/url"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
@ -24,9 +30,25 @@ import (
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
) )
const (
// ServiceName is the name of the service. // ServiceName is the name of the service.
const ServiceName = "ec2metadata" ServiceName = "ec2metadata"
const disableServiceEnvVar = "AWS_EC2_METADATA_DISABLED" disableServiceEnvVar = "AWS_EC2_METADATA_DISABLED"
// Headers for Token and TTL
ttlHeader = "x-aws-ec2-metadata-token-ttl-seconds"
tokenHeader = "x-aws-ec2-metadata-token"
// Named Handler constants
fetchTokenHandlerName = "FetchTokenHandler"
unmarshalMetadataHandlerName = "unmarshalMetadataHandler"
unmarshalTokenHandlerName = "unmarshalTokenHandler"
enableTokenProviderHandlerName = "enableTokenProviderHandler"
// TTL constants
defaultTTL = 21600 * time.Second
ttlExpirationWindow = 30 * time.Second
)
// A EC2Metadata is an EC2 Metadata service Client. // A EC2Metadata is an EC2 Metadata service Client.
type EC2Metadata struct { type EC2Metadata struct {
@ -52,6 +74,9 @@ func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2Metadata {
// a client when not using a session. Generally using just New with a session // a client when not using a session. Generally using just New with a session
// is preferred. // is preferred.
// //
// Will remove the URL path from the endpoint provided to ensure the EC2 IMDS
// client is able to communicate with the EC2 IMDS API.
//
// If an unmodified HTTP client is provided from the stdlib default, or no client // If an unmodified HTTP client is provided from the stdlib default, or no client
// the EC2RoleProvider's EC2Metadata HTTP client's timeout will be shortened. // the EC2RoleProvider's EC2Metadata HTTP client's timeout will be shortened.
// To disable this set Config.EC2MetadataDisableTimeoutOverride to false. Enabled by default. // To disable this set Config.EC2MetadataDisableTimeoutOverride to false. Enabled by default.
@ -63,8 +88,19 @@ func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegio
// use a shorter timeout than default because the metadata // use a shorter timeout than default because the metadata
// service is local if it is running, and to fail faster // service is local if it is running, and to fail faster
// if not running on an ec2 instance. // if not running on an ec2 instance.
Timeout: 5 * time.Second, Timeout: 1 * time.Second,
} }
// max number of retries on the client operation
cfg.MaxRetries = aws.Int(2)
}
if u, err := url.Parse(endpoint); err == nil {
// Remove path from the endpoint since it will be added by requests.
// This is an artifact of the SDK adding `/latest` to the endpoint for
// EC2 IMDS, but this is now moved to the operation definition.
u.Path = ""
u.RawPath = ""
endpoint = u.String()
} }
svc := &EC2Metadata{ svc := &EC2Metadata{
@ -80,13 +116,27 @@ func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegio
), ),
} }
svc.Handlers.Unmarshal.PushBack(unmarshalHandler) // token provider instance
tp := newTokenProvider(svc, defaultTTL)
// NamedHandler for fetching token
svc.Handlers.Sign.PushBackNamed(request.NamedHandler{
Name: fetchTokenHandlerName,
Fn: tp.fetchTokenHandler,
})
// NamedHandler for enabling token provider
svc.Handlers.Complete.PushBackNamed(request.NamedHandler{
Name: enableTokenProviderHandlerName,
Fn: tp.enableTokenProviderHandler,
})
svc.Handlers.Unmarshal.PushBackNamed(unmarshalHandler)
svc.Handlers.UnmarshalError.PushBack(unmarshalError) svc.Handlers.UnmarshalError.PushBack(unmarshalError)
svc.Handlers.Validate.Clear() svc.Handlers.Validate.Clear()
svc.Handlers.Validate.PushBack(validateEndpointHandler) svc.Handlers.Validate.PushBack(validateEndpointHandler)
// Disable the EC2 Metadata service if the environment variable is set. // Disable the EC2 Metadata service if the environment variable is set.
// This shortcirctes the service's functionality to always fail to send // This short-circuits the service's functionality to always fail to send
// requests. // requests.
if strings.ToLower(os.Getenv(disableServiceEnvVar)) == "true" { if strings.ToLower(os.Getenv(disableServiceEnvVar)) == "true" {
svc.Handlers.Send.SwapNamed(request.NamedHandler{ svc.Handlers.Send.SwapNamed(request.NamedHandler{
@ -107,7 +157,6 @@ func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegio
for _, option := range opts { for _, option := range opts {
option(svc.Client) option(svc.Client)
} }
return svc return svc
} }
@ -119,30 +168,74 @@ type metadataOutput struct {
Content string Content string
} }
func unmarshalHandler(r *request.Request) { type tokenOutput struct {
Token string
TTL time.Duration
}
// unmarshal token handler is used to parse the response of a getToken operation
var unmarshalTokenHandler = request.NamedHandler{
Name: unmarshalTokenHandlerName,
Fn: func(r *request.Request) {
defer r.HTTPResponse.Body.Close() defer r.HTTPResponse.Body.Close()
b := &bytes.Buffer{} var b bytes.Buffer
if _, err := io.Copy(b, r.HTTPResponse.Body); err != nil { if _, err := io.Copy(&b, r.HTTPResponse.Body); err != nil {
r.Error = awserr.New(request.ErrCodeSerialization, "unable to unmarshal EC2 metadata response", err) r.Error = awserr.NewRequestFailure(awserr.New(request.ErrCodeSerialization,
"unable to unmarshal EC2 metadata response", err), r.HTTPResponse.StatusCode, r.RequestID)
return
}
v := r.HTTPResponse.Header.Get(ttlHeader)
data, ok := r.Data.(*tokenOutput)
if !ok {
return
}
data.Token = b.String()
// TTL is in seconds
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
r.Error = awserr.NewRequestFailure(awserr.New(request.ParamFormatErrCode,
"unable to parse EC2 token TTL response", err), r.HTTPResponse.StatusCode, r.RequestID)
return
}
t := time.Duration(i) * time.Second
data.TTL = t
},
}
var unmarshalHandler = request.NamedHandler{
Name: unmarshalMetadataHandlerName,
Fn: func(r *request.Request) {
defer r.HTTPResponse.Body.Close()
var b bytes.Buffer
if _, err := io.Copy(&b, r.HTTPResponse.Body); err != nil {
r.Error = awserr.NewRequestFailure(awserr.New(request.ErrCodeSerialization,
"unable to unmarshal EC2 metadata response", err), r.HTTPResponse.StatusCode, r.RequestID)
return return
} }
if data, ok := r.Data.(*metadataOutput); ok { if data, ok := r.Data.(*metadataOutput); ok {
data.Content = b.String() data.Content = b.String()
} }
},
} }
func unmarshalError(r *request.Request) { func unmarshalError(r *request.Request) {
defer r.HTTPResponse.Body.Close() defer r.HTTPResponse.Body.Close()
b := &bytes.Buffer{} var b bytes.Buffer
if _, err := io.Copy(b, r.HTTPResponse.Body); err != nil {
r.Error = awserr.New(request.ErrCodeSerialization, "unable to unmarshal EC2 metadata error response", err) if _, err := io.Copy(&b, r.HTTPResponse.Body); err != nil {
r.Error = awserr.NewRequestFailure(
awserr.New(request.ErrCodeSerialization, "unable to unmarshal EC2 metadata error response", err),
r.HTTPResponse.StatusCode, r.RequestID)
return return
} }
// Response body format is not consistent between metadata endpoints. // Response body format is not consistent between metadata endpoints.
// Grab the error message as a string and include that as the source error // Grab the error message as a string and include that as the source error
r.Error = awserr.New("EC2MetadataError", "failed to make EC2Metadata request", errors.New(b.String())) r.Error = awserr.NewRequestFailure(awserr.New("EC2MetadataError", "failed to make EC2Metadata request", errors.New(b.String())),
r.HTTPResponse.StatusCode, r.RequestID)
} }
func validateEndpointHandler(r *request.Request) { func validateEndpointHandler(r *request.Request) {

View File

@ -0,0 +1,92 @@
package ec2metadata
import (
"net/http"
"sync/atomic"
"time"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
)
// A tokenProvider struct provides access to EC2Metadata client
// and atomic instance of a token, along with configuredTTL for it.
// tokenProvider also provides an atomic flag to disable the
// fetch token operation.
// The disabled member will use 0 as false, and 1 as true.
type tokenProvider struct {
client *EC2Metadata
token atomic.Value
configuredTTL time.Duration
disabled uint32
}
// A ec2Token struct helps use of token in EC2 Metadata service ops
type ec2Token struct {
token string
credentials.Expiry
}
// newTokenProvider provides a pointer to a tokenProvider instance
func newTokenProvider(c *EC2Metadata, duration time.Duration) *tokenProvider {
return &tokenProvider{client: c, configuredTTL: duration}
}
// fetchTokenHandler fetches token for EC2Metadata service client by default.
func (t *tokenProvider) fetchTokenHandler(r *request.Request) {
// short-circuits to insecure data flow if tokenProvider is disabled.
if v := atomic.LoadUint32(&t.disabled); v == 1 {
return
}
if ec2Token, ok := t.token.Load().(ec2Token); ok && !ec2Token.IsExpired() {
r.HTTPRequest.Header.Set(tokenHeader, ec2Token.token)
return
}
output, err := t.client.getToken(r.Context(), t.configuredTTL)
if err != nil {
// change the disabled flag on token provider to true,
// when error is request timeout error.
if requestFailureError, ok := err.(awserr.RequestFailure); ok {
switch requestFailureError.StatusCode() {
case http.StatusForbidden, http.StatusNotFound, http.StatusMethodNotAllowed:
atomic.StoreUint32(&t.disabled, 1)
case http.StatusBadRequest:
r.Error = requestFailureError
}
// Check if request timed out while waiting for response
if e, ok := requestFailureError.OrigErr().(awserr.Error); ok {
if e.Code() == request.ErrCodeRequestError {
atomic.StoreUint32(&t.disabled, 1)
}
}
}
return
}
newToken := ec2Token{
token: output.Token,
}
newToken.SetExpiration(time.Now().Add(output.TTL), ttlExpirationWindow)
t.token.Store(newToken)
// Inject token header to the request.
if ec2Token, ok := t.token.Load().(ec2Token); ok {
r.HTTPRequest.Header.Set(tokenHeader, ec2Token.token)
}
}
// enableTokenProviderHandler enables the token provider
func (t *tokenProvider) enableTokenProviderHandler(r *request.Request) {
// If the error code status is 401, we enable the token provider
if e, ok := r.Error.(awserr.RequestFailure); ok && e != nil &&
e.StatusCode() == http.StatusUnauthorized {
atomic.StoreUint32(&t.disabled, 0)
}
}

View File

@ -83,6 +83,7 @@ func decodeV3Endpoints(modelDef modelDefinition, opts DecodeModelOptions) (Resol
p := &ps[i] p := &ps[i]
custAddEC2Metadata(p) custAddEC2Metadata(p)
custAddS3DualStack(p) custAddS3DualStack(p)
custRegionalS3(p)
custRmIotDataService(p) custRmIotDataService(p)
custFixAppAutoscalingChina(p) custFixAppAutoscalingChina(p)
custFixAppAutoscalingUsGov(p) custFixAppAutoscalingUsGov(p)
@ -92,7 +93,7 @@ func decodeV3Endpoints(modelDef modelDefinition, opts DecodeModelOptions) (Resol
} }
func custAddS3DualStack(p *partition) { func custAddS3DualStack(p *partition) {
if p.ID != "aws" { if !(p.ID == "aws" || p.ID == "aws-cn" || p.ID == "aws-us-gov") {
return return
} }
@ -100,6 +101,33 @@ func custAddS3DualStack(p *partition) {
custAddDualstack(p, "s3-control") custAddDualstack(p, "s3-control")
} }
func custRegionalS3(p *partition) {
if p.ID != "aws" {
return
}
service, ok := p.Services["s3"]
if !ok {
return
}
// If global endpoint already exists no customization needed.
if _, ok := service.Endpoints["aws-global"]; ok {
return
}
service.PartitionEndpoint = "aws-global"
service.Endpoints["us-east-1"] = endpoint{}
service.Endpoints["aws-global"] = endpoint{
Hostname: "s3.amazonaws.com",
CredentialScope: credentialScope{
Region: "us-east-1",
},
}
p.Services["s3"] = service
}
func custAddDualstack(p *partition, svcName string) { func custAddDualstack(p *partition, svcName string) {
s, ok := p.Services[svcName] s, ok := p.Services[svcName]
if !ok { if !ok {

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ package endpoints
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strings"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
) )
@ -46,6 +47,108 @@ type Options struct {
// //
// This option is ignored if StrictMatching is enabled. // This option is ignored if StrictMatching is enabled.
ResolveUnknownService bool ResolveUnknownService bool
// STS Regional Endpoint flag helps with resolving the STS endpoint
STSRegionalEndpoint STSRegionalEndpoint
// S3 Regional Endpoint flag helps with resolving the S3 endpoint
S3UsEast1RegionalEndpoint S3UsEast1RegionalEndpoint
}
// STSRegionalEndpoint is an enum for the states of the STS Regional Endpoint
// options.
type STSRegionalEndpoint int
func (e STSRegionalEndpoint) String() string {
switch e {
case LegacySTSEndpoint:
return "legacy"
case RegionalSTSEndpoint:
return "regional"
case UnsetSTSEndpoint:
return ""
default:
return "unknown"
}
}
const (
// UnsetSTSEndpoint represents that STS Regional Endpoint flag is not specified.
UnsetSTSEndpoint STSRegionalEndpoint = iota
// LegacySTSEndpoint represents when STS Regional Endpoint flag is specified
// to use legacy endpoints.
LegacySTSEndpoint
// RegionalSTSEndpoint represents when STS Regional Endpoint flag is specified
// to use regional endpoints.
RegionalSTSEndpoint
)
// GetSTSRegionalEndpoint function returns the STSRegionalEndpointFlag based
// on the input string provided in env config or shared config by the user.
//
// `legacy`, `regional` are the only case-insensitive valid strings for
// resolving the STS regional Endpoint flag.
func GetSTSRegionalEndpoint(s string) (STSRegionalEndpoint, error) {
switch {
case strings.EqualFold(s, "legacy"):
return LegacySTSEndpoint, nil
case strings.EqualFold(s, "regional"):
return RegionalSTSEndpoint, nil
default:
return UnsetSTSEndpoint, fmt.Errorf("unable to resolve the value of STSRegionalEndpoint for %v", s)
}
}
// S3UsEast1RegionalEndpoint is an enum for the states of the S3 us-east-1
// Regional Endpoint options.
type S3UsEast1RegionalEndpoint int
func (e S3UsEast1RegionalEndpoint) String() string {
switch e {
case LegacyS3UsEast1Endpoint:
return "legacy"
case RegionalS3UsEast1Endpoint:
return "regional"
case UnsetS3UsEast1Endpoint:
return ""
default:
return "unknown"
}
}
const (
// UnsetS3UsEast1Endpoint represents that S3 Regional Endpoint flag is not
// specified.
UnsetS3UsEast1Endpoint S3UsEast1RegionalEndpoint = iota
// LegacyS3UsEast1Endpoint represents when S3 Regional Endpoint flag is
// specified to use legacy endpoints.
LegacyS3UsEast1Endpoint
// RegionalS3UsEast1Endpoint represents when S3 Regional Endpoint flag is
// specified to use regional endpoints.
RegionalS3UsEast1Endpoint
)
// GetS3UsEast1RegionalEndpoint function returns the S3UsEast1RegionalEndpointFlag based
// on the input string provided in env config or shared config by the user.
//
// `legacy`, `regional` are the only case-insensitive valid strings for
// resolving the S3 regional Endpoint flag.
func GetS3UsEast1RegionalEndpoint(s string) (S3UsEast1RegionalEndpoint, error) {
switch {
case strings.EqualFold(s, "legacy"):
return LegacyS3UsEast1Endpoint, nil
case strings.EqualFold(s, "regional"):
return RegionalS3UsEast1Endpoint, nil
default:
return UnsetS3UsEast1Endpoint,
fmt.Errorf("unable to resolve the value of S3UsEast1RegionalEndpoint for %v", s)
}
} }
// Set combines all of the option functions together. // Set combines all of the option functions together.
@ -79,6 +182,12 @@ func ResolveUnknownServiceOption(o *Options) {
o.ResolveUnknownService = true o.ResolveUnknownService = true
} }
// STSRegionalEndpointOption enables the STS endpoint resolver behavior to resolve
// STS endpoint to their regional endpoint, instead of the global endpoint.
func STSRegionalEndpointOption(o *Options) {
o.STSRegionalEndpoint = RegionalSTSEndpoint
}
// A Resolver provides the interface for functionality to resolve endpoints. // A Resolver provides the interface for functionality to resolve endpoints.
// The build in Partition and DefaultResolver return value satisfy this interface. // The build in Partition and DefaultResolver return value satisfy this interface.
type Resolver interface { type Resolver interface {
@ -194,7 +303,7 @@ func (p Partition) ID() string { return p.id }
// require the provided service and region to be known by the partition. // require the provided service and region to be known by the partition.
// If the endpoint cannot be strictly resolved an error will be returned. This // If the endpoint cannot be strictly resolved an error will be returned. This
// mode is useful to ensure the endpoint resolved is valid. Without // mode is useful to ensure the endpoint resolved is valid. Without
// StrictMatching enabled the endpoint returned my look valid but may not work. // StrictMatching enabled the endpoint returned may look valid but may not work.
// StrictMatching requires the SDK to be updated if you want to take advantage // StrictMatching requires the SDK to be updated if you want to take advantage
// of new regions and services expansions. // of new regions and services expansions.
// //
@ -208,7 +317,7 @@ func (p Partition) EndpointFor(service, region string, opts ...func(*Options)) (
// Regions returns a map of Regions indexed by their ID. This is useful for // Regions returns a map of Regions indexed by their ID. This is useful for
// enumerating over the regions in a partition. // enumerating over the regions in a partition.
func (p Partition) Regions() map[string]Region { func (p Partition) Regions() map[string]Region {
rs := map[string]Region{} rs := make(map[string]Region, len(p.p.Regions))
for id, r := range p.p.Regions { for id, r := range p.p.Regions {
rs[id] = Region{ rs[id] = Region{
id: id, id: id,
@ -223,7 +332,7 @@ func (p Partition) Regions() map[string]Region {
// Services returns a map of Service indexed by their ID. This is useful for // Services returns a map of Service indexed by their ID. This is useful for
// enumerating over the services in a partition. // enumerating over the services in a partition.
func (p Partition) Services() map[string]Service { func (p Partition) Services() map[string]Service {
ss := map[string]Service{} ss := make(map[string]Service, len(p.p.Services))
for id := range p.p.Services { for id := range p.p.Services {
ss[id] = Service{ ss[id] = Service{
id: id, id: id,
@ -310,7 +419,7 @@ func (s Service) Regions() map[string]Region {
// A region is the AWS region the service exists in. Whereas a Endpoint is // A region is the AWS region the service exists in. Whereas a Endpoint is
// an URL that can be resolved to a instance of a service. // an URL that can be resolved to a instance of a service.
func (s Service) Endpoints() map[string]Endpoint { func (s Service) Endpoints() map[string]Endpoint {
es := map[string]Endpoint{} es := make(map[string]Endpoint, len(s.p.Services[s.id].Endpoints))
for id := range s.p.Services[s.id].Endpoints { for id := range s.p.Services[s.id].Endpoints {
es[id] = Endpoint{ es[id] = Endpoint{
id: id, id: id,
@ -350,6 +459,9 @@ type ResolvedEndpoint struct {
// The endpoint URL // The endpoint URL
URL string URL string
// The endpoint partition
PartitionID string
// The region that should be used for signing requests. // The region that should be used for signing requests.
SigningRegion string SigningRegion string

View File

@ -0,0 +1,24 @@
package endpoints
var legacyGlobalRegions = map[string]map[string]struct{}{
"sts": {
"ap-northeast-1": {},
"ap-south-1": {},
"ap-southeast-1": {},
"ap-southeast-2": {},
"ca-central-1": {},
"eu-central-1": {},
"eu-north-1": {},
"eu-west-1": {},
"eu-west-2": {},
"eu-west-3": {},
"sa-east-1": {},
"us-east-1": {},
"us-east-2": {},
"us-west-1": {},
"us-west-2": {},
},
"s3": {
"us-east-1": {},
},
}

View File

@ -7,6 +7,8 @@ import (
"strings" "strings"
) )
var regionValidationRegex = regexp.MustCompile(`^[[:alnum:]]([[:alnum:]\-]*[[:alnum:]])?$`)
type partitions []partition type partitions []partition
func (ps partitions) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) { func (ps partitions) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
@ -75,24 +77,56 @@ func (p partition) canResolveEndpoint(service, region string, strictMatch bool)
return p.RegionRegex.MatchString(region) return p.RegionRegex.MatchString(region)
} }
func allowLegacyEmptyRegion(service string) bool {
legacy := map[string]struct{}{
"budgets": {},
"ce": {},
"chime": {},
"cloudfront": {},
"ec2metadata": {},
"iam": {},
"importexport": {},
"organizations": {},
"route53": {},
"sts": {},
"support": {},
"waf": {},
}
_, allowed := legacy[service]
return allowed
}
func (p partition) EndpointFor(service, region string, opts ...func(*Options)) (resolved ResolvedEndpoint, err error) { func (p partition) EndpointFor(service, region string, opts ...func(*Options)) (resolved ResolvedEndpoint, err error) {
var opt Options var opt Options
opt.Set(opts...) opt.Set(opts...)
s, hasService := p.Services[service] s, hasService := p.Services[service]
if !(hasService || opt.ResolveUnknownService) { if len(service) == 0 || !(hasService || opt.ResolveUnknownService) {
// Only return error if the resolver will not fallback to creating // Only return error if the resolver will not fallback to creating
// endpoint based on service endpoint ID passed in. // endpoint based on service endpoint ID passed in.
return resolved, NewUnknownServiceError(p.ID, service, serviceList(p.Services)) return resolved, NewUnknownServiceError(p.ID, service, serviceList(p.Services))
} }
if len(region) == 0 && allowLegacyEmptyRegion(service) && len(s.PartitionEndpoint) != 0 {
region = s.PartitionEndpoint
}
if (service == "sts" && opt.STSRegionalEndpoint != RegionalSTSEndpoint) ||
(service == "s3" && opt.S3UsEast1RegionalEndpoint != RegionalS3UsEast1Endpoint) {
if _, ok := legacyGlobalRegions[service][region]; ok {
region = "aws-global"
}
}
e, hasEndpoint := s.endpointForRegion(region) e, hasEndpoint := s.endpointForRegion(region)
if !hasEndpoint && opt.StrictMatching { if len(region) == 0 || (!hasEndpoint && opt.StrictMatching) {
return resolved, NewUnknownEndpointError(p.ID, service, region, endpointList(s.Endpoints)) return resolved, NewUnknownEndpointError(p.ID, service, region, endpointList(s.Endpoints))
} }
defs := []endpoint{p.Defaults, s.Defaults} defs := []endpoint{p.Defaults, s.Defaults}
return e.resolve(service, region, p.DNSSuffix, defs, opt), nil
return e.resolve(service, p.ID, region, p.DNSSuffix, defs, opt)
} }
func serviceList(ss services) []string { func serviceList(ss services) []string {
@ -201,7 +235,7 @@ func getByPriority(s []string, p []string, def string) string {
return s[0] return s[0]
} }
func (e endpoint) resolve(service, region, dnsSuffix string, defs []endpoint, opts Options) ResolvedEndpoint { func (e endpoint) resolve(service, partitionID, region, dnsSuffix string, defs []endpoint, opts Options) (ResolvedEndpoint, error) {
var merged endpoint var merged endpoint
for _, def := range defs { for _, def := range defs {
merged.mergeIn(def) merged.mergeIn(def)
@ -209,20 +243,6 @@ func (e endpoint) resolve(service, region, dnsSuffix string, defs []endpoint, op
merged.mergeIn(e) merged.mergeIn(e)
e = merged e = merged
hostname := e.Hostname
// Offset the hostname for dualstack if enabled
if opts.UseDualStack && e.HasDualStack == boxedTrue {
hostname = e.DualStackHostname
}
u := strings.Replace(hostname, "{service}", service, 1)
u = strings.Replace(u, "{region}", region, 1)
u = strings.Replace(u, "{dnsSuffix}", dnsSuffix, 1)
scheme := getEndpointScheme(e.Protocols, opts.DisableSSL)
u = fmt.Sprintf("%s://%s", scheme, u)
signingRegion := e.CredentialScope.Region signingRegion := e.CredentialScope.Region
if len(signingRegion) == 0 { if len(signingRegion) == 0 {
signingRegion = region signingRegion = region
@ -235,13 +255,32 @@ func (e endpoint) resolve(service, region, dnsSuffix string, defs []endpoint, op
signingNameDerived = true signingNameDerived = true
} }
hostname := e.Hostname
// Offset the hostname for dualstack if enabled
if opts.UseDualStack && e.HasDualStack == boxedTrue {
hostname = e.DualStackHostname
region = signingRegion
}
if !validateInputRegion(region) {
return ResolvedEndpoint{}, fmt.Errorf("invalid region identifier format provided")
}
u := strings.Replace(hostname, "{service}", service, 1)
u = strings.Replace(u, "{region}", region, 1)
u = strings.Replace(u, "{dnsSuffix}", dnsSuffix, 1)
scheme := getEndpointScheme(e.Protocols, opts.DisableSSL)
u = fmt.Sprintf("%s://%s", scheme, u)
return ResolvedEndpoint{ return ResolvedEndpoint{
URL: u, URL: u,
PartitionID: partitionID,
SigningRegion: signingRegion, SigningRegion: signingRegion,
SigningName: signingName, SigningName: signingName,
SigningNameDerived: signingNameDerived, SigningNameDerived: signingNameDerived,
SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner), SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner),
} }, nil
} }
func getEndpointScheme(protocols []string, disableSSL bool) string { func getEndpointScheme(protocols []string, disableSSL bool) string {
@ -306,3 +345,7 @@ const (
boxedFalse boxedFalse
boxedTrue boxedTrue
) )
func validateInputRegion(region string) bool {
return regionValidationRegex.MatchString(region)
}

View File

@ -9,7 +9,8 @@ func isErrConnectionReset(err error) bool {
return false return false
} }
if strings.Contains(err.Error(), "connection reset") || if strings.Contains(err.Error(), "use of closed network connection") ||
strings.Contains(err.Error(), "connection reset") ||
strings.Contains(err.Error(), "broken pipe") { strings.Contains(err.Error(), "broken pipe") {
return true return true
} }

View File

@ -10,6 +10,7 @@ import (
type Handlers struct { type Handlers struct {
Validate HandlerList Validate HandlerList
Build HandlerList Build HandlerList
BuildStream HandlerList
Sign HandlerList Sign HandlerList
Send HandlerList Send HandlerList
ValidateResponse HandlerList ValidateResponse HandlerList
@ -28,6 +29,7 @@ func (h *Handlers) Copy() Handlers {
return Handlers{ return Handlers{
Validate: h.Validate.copy(), Validate: h.Validate.copy(),
Build: h.Build.copy(), Build: h.Build.copy(),
BuildStream: h.BuildStream.copy(),
Sign: h.Sign.copy(), Sign: h.Sign.copy(),
Send: h.Send.copy(), Send: h.Send.copy(),
ValidateResponse: h.ValidateResponse.copy(), ValidateResponse: h.ValidateResponse.copy(),
@ -46,6 +48,7 @@ func (h *Handlers) Copy() Handlers {
func (h *Handlers) Clear() { func (h *Handlers) Clear() {
h.Validate.Clear() h.Validate.Clear()
h.Build.Clear() h.Build.Clear()
h.BuildStream.Clear()
h.Send.Clear() h.Send.Clear()
h.Sign.Clear() h.Sign.Clear()
h.Unmarshal.Clear() h.Unmarshal.Clear()
@ -67,6 +70,9 @@ func (h *Handlers) IsEmpty() bool {
if h.Build.Len() != 0 { if h.Build.Len() != 0 {
return false return false
} }
if h.BuildStream.Len() != 0 {
return false
}
if h.Send.Len() != 0 { if h.Send.Len() != 0 {
return false return false
} }
@ -320,3 +326,18 @@ func MakeAddToUserAgentFreeFormHandler(s string) func(*Request) {
AddToUserAgent(r, s) AddToUserAgent(r, s)
} }
} }
// WithSetRequestHeaders updates the operation request's HTTP header to contain
// the header key value pairs provided. If the header key already exists in the
// request's HTTP header set, the existing value(s) will be replaced.
func WithSetRequestHeaders(h map[string]string) Option {
return withRequestHeader(h).SetRequestHeaders
}
type withRequestHeader map[string]string
func (h withRequestHeader) SetRequestHeaders(r *Request) {
for k, v := range h {
r.HTTPRequest.Header[k] = []string{v}
}
}

View File

@ -36,6 +36,10 @@ const (
// API request that was canceled. Requests given a aws.Context may // API request that was canceled. Requests given a aws.Context may
// return this error when canceled. // return this error when canceled.
CanceledErrorCode = "RequestCanceled" CanceledErrorCode = "RequestCanceled"
// ErrCodeRequestError is an error preventing the SDK from continuing to
// process the request.
ErrCodeRequestError = "RequestError"
) )
// A Request is the service request to be made. // A Request is the service request to be made.
@ -51,6 +55,7 @@ type Request struct {
HTTPRequest *http.Request HTTPRequest *http.Request
HTTPResponse *http.Response HTTPResponse *http.Response
Body io.ReadSeeker Body io.ReadSeeker
streamingBody io.ReadCloser
BodyStart int64 // offset from beginning of Body that the request body starts BodyStart int64 // offset from beginning of Body that the request body starts
Params interface{} Params interface{}
Error error Error error
@ -99,8 +104,12 @@ type Operation struct {
BeforePresignFn func(r *Request) error BeforePresignFn func(r *Request) error
} }
// New returns a new Request pointer for the service API // New returns a new Request pointer for the service API operation and
// operation and parameters. // parameters.
//
// A Retryer should be provided to direct how the request is retried. If
// Retryer is nil, a default no retry value will be used. You can use
// NoOpRetryer in the Client package to disable retry behavior directly.
// //
// Params is any value of input parameters to be the request payload. // Params is any value of input parameters to be the request payload.
// Data is pointer value to an object which the request's response // Data is pointer value to an object which the request's response
@ -108,6 +117,10 @@ type Operation struct {
func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers, func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request { retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request {
if retryer == nil {
retryer = noOpRetryer{}
}
method := operation.HTTPMethod method := operation.HTTPMethod
if method == "" { if method == "" {
method = "POST" method = "POST"
@ -122,8 +135,6 @@ func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
err = awserr.New("InvalidEndpointURL", "invalid endpoint uri", err) err = awserr.New("InvalidEndpointURL", "invalid endpoint uri", err)
} }
SanitizeHostForHeader(httpReq)
r := &Request{ r := &Request{
Config: cfg, Config: cfg,
ClientInfo: clientInfo, ClientInfo: clientInfo,
@ -287,6 +298,13 @@ func (r *Request) SetReaderBody(reader io.ReadSeeker) {
r.ResetBody() r.ResetBody()
} }
// SetStreamingBody set the reader to be used for the request that will stream
// bytes to the server. Request's Body must not be set to any reader.
func (r *Request) SetStreamingBody(reader io.ReadCloser) {
r.streamingBody = reader
r.SetReaderBody(aws.ReadSeekCloser(reader))
}
// Presign returns the request's signed URL. Error will be returned // Presign returns the request's signed URL. Error will be returned
// if the signing fails. The expire parameter is only used for presigned Amazon // if the signing fails. The expire parameter is only used for presigned Amazon
// S3 API requests. All other AWS services will use a fixed expiration // S3 API requests. All other AWS services will use a fixed expiration
@ -406,11 +424,17 @@ func (r *Request) Sign() error {
return r.Error return r.Error
} }
SanitizeHostForHeader(r.HTTPRequest)
r.Handlers.Sign.Run(r) r.Handlers.Sign.Run(r)
return r.Error return r.Error
} }
func (r *Request) getNextRequestBody() (body io.ReadCloser, err error) { func (r *Request) getNextRequestBody() (body io.ReadCloser, err error) {
if r.streamingBody != nil {
return r.streamingBody, nil
}
if r.safeBody != nil { if r.safeBody != nil {
r.safeBody.Close() r.safeBody.Close()
} }
@ -615,6 +639,10 @@ func getHost(r *http.Request) string {
return r.Host return r.Host
} }
if r.URL == nil {
return ""
}
return r.URL.Host return r.URL.Host
} }

View File

@ -17,11 +17,13 @@ import (
// does the pagination between API operations, and Paginator defines the // does the pagination between API operations, and Paginator defines the
// configuration that will be used per page request. // configuration that will be used per page request.
// //
// cont := true // for p.Next() {
// for p.Next() && cont {
// data := p.Page().(*s3.ListObjectsOutput) // data := p.Page().(*s3.ListObjectsOutput)
// // process the page's data // // process the page's data
// // ...
// // break out of loop to stop fetching additional pages
// } // }
//
// return p.Err() // return p.Err()
// //
// See service client API operation Pages methods for examples how the SDK will // See service client API operation Pages methods for examples how the SDK will

View File

@ -35,16 +35,47 @@ type Retryer interface {
} }
// WithRetryer sets a Retryer value to the given Config returning the Config // WithRetryer sets a Retryer value to the given Config returning the Config
// value for chaining. // value for chaining. The value must not be nil.
func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config { func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config {
if retryer == nil {
if cfg.Logger != nil {
cfg.Logger.Log("ERROR: Request.WithRetryer called with nil retryer. Replacing with retry disabled Retryer.")
}
retryer = noOpRetryer{}
}
cfg.Retryer = retryer cfg.Retryer = retryer
return cfg return cfg
}
// noOpRetryer is a internal no op retryer used when a request is created
// without a retryer.
//
// Provides a retryer that performs no retries.
// It should be used when we do not want retries to be performed.
type noOpRetryer struct{}
// MaxRetries returns the number of maximum returns the service will use to make
// an individual API; For NoOpRetryer the MaxRetries will always be zero.
func (d noOpRetryer) MaxRetries() int {
return 0
}
// ShouldRetry will always return false for NoOpRetryer, as it should never retry.
func (d noOpRetryer) ShouldRetry(_ *Request) bool {
return false
}
// RetryRules returns the delay duration before retrying this request again;
// since NoOpRetryer does not retry, RetryRules always returns 0.
func (d noOpRetryer) RetryRules(_ *Request) time.Duration {
return 0
} }
// retryableCodes is a collection of service response codes which are retry-able // retryableCodes is a collection of service response codes which are retry-able
// without any further action. // without any further action.
var retryableCodes = map[string]struct{}{ var retryableCodes = map[string]struct{}{
"RequestError": {}, ErrCodeRequestError: {},
"RequestTimeout": {}, "RequestTimeout": {},
ErrCodeResponseTimeout: {}, ErrCodeResponseTimeout: {},
"RequestTimeoutException": {}, // Glacier's flavor of RequestTimeout "RequestTimeoutException": {}, // Glacier's flavor of RequestTimeout
@ -52,6 +83,7 @@ var retryableCodes = map[string]struct{}{
var throttleCodes = map[string]struct{}{ var throttleCodes = map[string]struct{}{
"ProvisionedThroughputExceededException": {}, "ProvisionedThroughputExceededException": {},
"ThrottledException": {}, // SNS, XRay, ResourceGroupsTagging API
"Throttling": {}, "Throttling": {},
"ThrottlingException": {}, "ThrottlingException": {},
"RequestLimitExceeded": {}, "RequestLimitExceeded": {},
@ -60,6 +92,7 @@ var throttleCodes = map[string]struct{}{
"TooManyRequestsException": {}, // Lambda functions "TooManyRequestsException": {}, // Lambda functions
"PriorRequestNotComplete": {}, // Route53 "PriorRequestNotComplete": {}, // Route53
"TransactionInProgressException": {}, "TransactionInProgressException": {},
"EC2ThrottledException": {}, // EC2
} }
// credsExpiredCodes is a collection of error codes which signify the credentials // credsExpiredCodes is a collection of error codes which signify the credentials
@ -145,8 +178,8 @@ func shouldRetryError(origErr error) bool {
origErr := err.OrigErr() origErr := err.OrigErr()
var shouldRetry bool var shouldRetry bool
if origErr != nil { if origErr != nil {
shouldRetry := shouldRetryError(origErr) shouldRetry = shouldRetryError(origErr)
if err.Code() == "RequestError" && !shouldRetry { if err.Code() == ErrCodeRequestError && !shouldRetry {
return false return false
} }
} }

View File

@ -3,6 +3,7 @@ package session
import ( import (
"fmt" "fmt"
"os" "os"
"time"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
@ -47,10 +48,10 @@ func resolveCredentials(cfg *aws.Config,
} }
// WebIdentityEmptyRoleARNErr will occur if 'AWS_WEB_IDENTITY_TOKEN_FILE' was set but // WebIdentityEmptyRoleARNErr will occur if 'AWS_WEB_IDENTITY_TOKEN_FILE' was set but
// 'AWS_IAM_ROLE_ARN' was not set. // 'AWS_ROLE_ARN' was not set.
var WebIdentityEmptyRoleARNErr = awserr.New(stscreds.ErrCodeWebIdentity, "role ARN is not set", nil) var WebIdentityEmptyRoleARNErr = awserr.New(stscreds.ErrCodeWebIdentity, "role ARN is not set", nil)
// WebIdentityEmptyTokenFilePathErr will occur if 'AWS_IAM_ROLE_ARN' was set but // WebIdentityEmptyTokenFilePathErr will occur if 'AWS_ROLE_ARN' was set but
// 'AWS_WEB_IDENTITY_TOKEN_FILE' was not set. // 'AWS_WEB_IDENTITY_TOKEN_FILE' was not set.
var WebIdentityEmptyTokenFilePathErr = awserr.New(stscreds.ErrCodeWebIdentity, "token file path is not set", nil) var WebIdentityEmptyTokenFilePathErr = awserr.New(stscreds.ErrCodeWebIdentity, "token file path is not set", nil)
@ -206,7 +207,14 @@ func credsFromAssumeRole(cfg aws.Config,
sharedCfg.RoleARN, sharedCfg.RoleARN,
func(opt *stscreds.AssumeRoleProvider) { func(opt *stscreds.AssumeRoleProvider) {
opt.RoleSessionName = sharedCfg.RoleSessionName opt.RoleSessionName = sharedCfg.RoleSessionName
if sessOpts.AssumeRoleDuration == 0 &&
sharedCfg.AssumeRoleDuration != nil &&
*sharedCfg.AssumeRoleDuration/time.Minute > 15 {
opt.Duration = *sharedCfg.AssumeRoleDuration
} else if sessOpts.AssumeRoleDuration != 0 {
opt.Duration = sessOpts.AssumeRoleDuration opt.Duration = sessOpts.AssumeRoleDuration
}
// Assume role with external ID // Assume role with external ID
if len(sharedCfg.ExternalID) > 0 { if len(sharedCfg.ExternalID) > 0 {

View File

@ -241,5 +241,22 @@ over the AWS_CA_BUNDLE environment variable, and will be used if both are set.
Setting a custom HTTPClient in the aws.Config options will override this setting. Setting a custom HTTPClient in the aws.Config options will override this setting.
To use this option and custom HTTP client, the HTTP client needs to be provided To use this option and custom HTTP client, the HTTP client needs to be provided
when creating the session. Not the service client. when creating the session. Not the service client.
The endpoint of the EC2 IMDS client can be configured via the environment
variable, AWS_EC2_METADATA_SERVICE_ENDPOINT when creating the client with a
Session. See Options.EC2IMDSEndpoint for more details.
AWS_EC2_METADATA_SERVICE_ENDPOINT=http://169.254.169.254
If using an URL with an IPv6 address literal, the IPv6 address
component must be enclosed in square brackets.
AWS_EC2_METADATA_SERVICE_ENDPOINT=http://[::1]
The custom EC2 IMDS endpoint can also be specified via the Session options.
sess, err := session.NewSessionWithOptions(session.Options{
EC2IMDSEndpoint: "http://[::1]",
})
*/ */
package session package session

View File

@ -1,12 +1,15 @@
package session package session
import ( import (
"fmt"
"os" "os"
"strconv" "strconv"
"strings"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/defaults" "github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/endpoints"
) )
// EnvProviderName provides a name of the provider when config is loaded from environment. // EnvProviderName provides a name of the provider when config is loaded from environment.
@ -125,6 +128,31 @@ type envConfig struct {
// //
// AWS_ROLE_SESSION_NAME=session_name // AWS_ROLE_SESSION_NAME=session_name
RoleSessionName string RoleSessionName string
// Specifies the STS Regional Endpoint flag for the SDK to resolve the endpoint
// for a service.
//
// AWS_STS_REGIONAL_ENDPOINTS=regional
// This can take value as `regional` or `legacy`
STSRegionalEndpoint endpoints.STSRegionalEndpoint
// Specifies the S3 Regional Endpoint flag for the SDK to resolve the
// endpoint for a service.
//
// AWS_S3_US_EAST_1_REGIONAL_ENDPOINT=regional
// This can take value as `regional` or `legacy`
S3UsEast1RegionalEndpoint endpoints.S3UsEast1RegionalEndpoint
// Specifies if the S3 service should allow ARNs to direct the region
// the client's requests are sent to.
//
// AWS_S3_USE_ARN_REGION=true
S3UseARNRegion bool
// Specifies the alternative endpoint to use for EC2 IMDS.
//
// AWS_EC2_METADATA_SERVICE_ENDPOINT=http://[::1]
EC2IMDSEndpoint string
} }
var ( var (
@ -179,6 +207,18 @@ var (
roleSessionNameEnvKey = []string{ roleSessionNameEnvKey = []string{
"AWS_ROLE_SESSION_NAME", "AWS_ROLE_SESSION_NAME",
} }
stsRegionalEndpointKey = []string{
"AWS_STS_REGIONAL_ENDPOINTS",
}
s3UsEast1RegionalEndpoint = []string{
"AWS_S3_US_EAST_1_REGIONAL_ENDPOINT",
}
s3UseARNRegionEnvKey = []string{
"AWS_S3_USE_ARN_REGION",
}
ec2IMDSEndpointEnvKey = []string{
"AWS_EC2_METADATA_SERVICE_ENDPOINT",
}
) )
// loadEnvConfig retrieves the SDK's environment configuration. // loadEnvConfig retrieves the SDK's environment configuration.
@ -187,7 +227,7 @@ var (
// If the environment variable `AWS_SDK_LOAD_CONFIG` is set to a truthy value // If the environment variable `AWS_SDK_LOAD_CONFIG` is set to a truthy value
// the shared SDK config will be loaded in addition to the SDK's specific // the shared SDK config will be loaded in addition to the SDK's specific
// configuration values. // configuration values.
func loadEnvConfig() envConfig { func loadEnvConfig() (envConfig, error) {
enableSharedConfig, _ := strconv.ParseBool(os.Getenv("AWS_SDK_LOAD_CONFIG")) enableSharedConfig, _ := strconv.ParseBool(os.Getenv("AWS_SDK_LOAD_CONFIG"))
return envConfigLoad(enableSharedConfig) return envConfigLoad(enableSharedConfig)
} }
@ -198,11 +238,11 @@ func loadEnvConfig() envConfig {
// Loads the shared configuration in addition to the SDK's specific configuration. // Loads the shared configuration in addition to the SDK's specific configuration.
// This will load the same values as `loadEnvConfig` if the `AWS_SDK_LOAD_CONFIG` // This will load the same values as `loadEnvConfig` if the `AWS_SDK_LOAD_CONFIG`
// environment variable is set. // environment variable is set.
func loadSharedEnvConfig() envConfig { func loadSharedEnvConfig() (envConfig, error) {
return envConfigLoad(true) return envConfigLoad(true)
} }
func envConfigLoad(enableSharedConfig bool) envConfig { func envConfigLoad(enableSharedConfig bool) (envConfig, error) {
cfg := envConfig{} cfg := envConfig{}
cfg.EnableSharedConfig = enableSharedConfig cfg.EnableSharedConfig = enableSharedConfig
@ -264,12 +304,50 @@ func envConfigLoad(enableSharedConfig bool) envConfig {
cfg.CustomCABundle = os.Getenv("AWS_CA_BUNDLE") cfg.CustomCABundle = os.Getenv("AWS_CA_BUNDLE")
return cfg var err error
// STS Regional Endpoint variable
for _, k := range stsRegionalEndpointKey {
if v := os.Getenv(k); len(v) != 0 {
cfg.STSRegionalEndpoint, err = endpoints.GetSTSRegionalEndpoint(v)
if err != nil {
return cfg, fmt.Errorf("failed to load, %v from env config, %v", k, err)
}
}
}
// S3 Regional Endpoint variable
for _, k := range s3UsEast1RegionalEndpoint {
if v := os.Getenv(k); len(v) != 0 {
cfg.S3UsEast1RegionalEndpoint, err = endpoints.GetS3UsEast1RegionalEndpoint(v)
if err != nil {
return cfg, fmt.Errorf("failed to load, %v from env config, %v", k, err)
}
}
}
var s3UseARNRegion string
setFromEnvVal(&s3UseARNRegion, s3UseARNRegionEnvKey)
if len(s3UseARNRegion) != 0 {
switch {
case strings.EqualFold(s3UseARNRegion, "false"):
cfg.S3UseARNRegion = false
case strings.EqualFold(s3UseARNRegion, "true"):
cfg.S3UseARNRegion = true
default:
return envConfig{}, fmt.Errorf(
"invalid value for environment variable, %s=%s, need true or false",
s3UseARNRegionEnvKey[0], s3UseARNRegion)
}
}
setFromEnvVal(&cfg.EC2IMDSEndpoint, ec2IMDSEndpointEnvKey)
return cfg, nil
} }
func setFromEnvVal(dst *string, keys []string) { func setFromEnvVal(dst *string, keys []string) {
for _, k := range keys { for _, k := range keys {
if v := os.Getenv(k); len(v) > 0 { if v := os.Getenv(k); len(v) != 0 {
*dst = v *dst = v
break break
} }

View File

@ -48,6 +48,8 @@ var ErrSharedConfigInvalidCredSource = awserr.New(ErrCodeSharedConfig, "credenti
type Session struct { type Session struct {
Config *aws.Config Config *aws.Config
Handlers request.Handlers Handlers request.Handlers
options Options
} }
// New creates a new instance of the handlers merging in the provided configs // New creates a new instance of the handlers merging in the provided configs
@ -73,7 +75,7 @@ type Session struct {
// func is called instead of waiting to receive an error until a request is made. // func is called instead of waiting to receive an error until a request is made.
func New(cfgs ...*aws.Config) *Session { func New(cfgs ...*aws.Config) *Session {
// load initial config from environment // load initial config from environment
envCfg := loadEnvConfig() envCfg, envErr := loadEnvConfig()
if envCfg.EnableSharedConfig { if envCfg.EnableSharedConfig {
var cfg aws.Config var cfg aws.Config
@ -93,17 +95,17 @@ func New(cfgs ...*aws.Config) *Session {
// Session creation failed, need to report the error and prevent // Session creation failed, need to report the error and prevent
// any requests from succeeding. // any requests from succeeding.
s = &Session{Config: defaults.Config()} s = &Session{Config: defaults.Config()}
s.Config.MergeIn(cfgs...) s.logDeprecatedNewSessionError(msg, err, cfgs)
s.Config.Logger.Log("ERROR:", msg, "Error:", err)
s.Handlers.Validate.PushBack(func(r *request.Request) {
r.Error = err
})
} }
return s return s
} }
s := deprecatedNewSession(cfgs...) s := deprecatedNewSession(envCfg, cfgs...)
if envErr != nil {
msg := "failed to load env config"
s.logDeprecatedNewSessionError(msg, envErr, cfgs)
}
if csmCfg, err := loadCSMConfig(envCfg, []string{}); err != nil { if csmCfg, err := loadCSMConfig(envCfg, []string{}); err != nil {
if l := s.Config.Logger; l != nil { if l := s.Config.Logger; l != nil {
@ -112,11 +114,8 @@ func New(cfgs ...*aws.Config) *Session {
} else if csmCfg.Enabled { } else if csmCfg.Enabled {
err := enableCSM(&s.Handlers, csmCfg, s.Config.Logger) err := enableCSM(&s.Handlers, csmCfg, s.Config.Logger)
if err != nil { if err != nil {
err = fmt.Errorf("failed to enable CSM, %v", err) msg := "failed to enable CSM"
s.Config.Logger.Log("ERROR:", err.Error()) s.logDeprecatedNewSessionError(msg, err, cfgs)
s.Handlers.Validate.PushBack(func(r *request.Request) {
r.Error = err
})
} }
} }
@ -246,6 +245,23 @@ type Options struct {
// function to initialize this value before changing the handlers to be // function to initialize this value before changing the handlers to be
// used by the SDK. // used by the SDK.
Handlers request.Handlers Handlers request.Handlers
// Allows specifying a custom endpoint to be used by the EC2 IMDS client
// when making requests to the EC2 IMDS API. The must endpoint value must
// include protocol prefix.
//
// If unset, will the EC2 IMDS client will use its default endpoint.
//
// Can also be specified via the environment variable,
// AWS_EC2_METADATA_SERVICE_ENDPOINT.
//
// AWS_EC2_METADATA_SERVICE_ENDPOINT=http://169.254.169.254
//
// If using an URL with an IPv6 address literal, the IPv6 address
// component must be enclosed in square brackets.
//
// AWS_EC2_METADATA_SERVICE_ENDPOINT=http://[::1]
EC2IMDSEndpoint string
} }
// NewSessionWithOptions returns a new Session created from SDK defaults, config files, // NewSessionWithOptions returns a new Session created from SDK defaults, config files,
@ -279,10 +295,17 @@ type Options struct {
// })) // }))
func NewSessionWithOptions(opts Options) (*Session, error) { func NewSessionWithOptions(opts Options) (*Session, error) {
var envCfg envConfig var envCfg envConfig
var err error
if opts.SharedConfigState == SharedConfigEnable { if opts.SharedConfigState == SharedConfigEnable {
envCfg = loadSharedEnvConfig() envCfg, err = loadSharedEnvConfig()
if err != nil {
return nil, fmt.Errorf("failed to load shared config, %v", err)
}
} else { } else {
envCfg = loadEnvConfig() envCfg, err = loadEnvConfig()
if err != nil {
return nil, fmt.Errorf("failed to load environment config, %v", err)
}
} }
if len(opts.Profile) != 0 { if len(opts.Profile) != 0 {
@ -325,7 +348,25 @@ func Must(sess *Session, err error) *Session {
return sess return sess
} }
func deprecatedNewSession(cfgs ...*aws.Config) *Session { // Wraps the endpoint resolver with a resolver that will return a custom
// endpoint for EC2 IMDS.
func wrapEC2IMDSEndpoint(resolver endpoints.Resolver, endpoint string) endpoints.Resolver {
return endpoints.ResolverFunc(
func(service, region string, opts ...func(*endpoints.Options)) (
endpoints.ResolvedEndpoint, error,
) {
if service == ec2MetadataServiceID {
return endpoints.ResolvedEndpoint{
URL: endpoint,
SigningName: ec2MetadataServiceID,
SigningRegion: region,
}, nil
}
return resolver.EndpointFor(service, region)
})
}
func deprecatedNewSession(envCfg envConfig, cfgs ...*aws.Config) *Session {
cfg := defaults.Config() cfg := defaults.Config()
handlers := defaults.Handlers() handlers := defaults.Handlers()
@ -337,6 +378,11 @@ func deprecatedNewSession(cfgs ...*aws.Config) *Session {
// endpoints for service client configurations. // endpoints for service client configurations.
cfg.EndpointResolver = endpoints.DefaultResolver() cfg.EndpointResolver = endpoints.DefaultResolver()
} }
if len(envCfg.EC2IMDSEndpoint) != 0 {
cfg.EndpointResolver = wrapEC2IMDSEndpoint(cfg.EndpointResolver, envCfg.EC2IMDSEndpoint)
}
cfg.Credentials = defaults.CredChain(cfg, handlers) cfg.Credentials = defaults.CredChain(cfg, handlers)
// Reapply any passed in configs to override credentials if set // Reapply any passed in configs to override credentials if set
@ -345,6 +391,9 @@ func deprecatedNewSession(cfgs ...*aws.Config) *Session {
s := &Session{ s := &Session{
Config: cfg, Config: cfg,
Handlers: handlers, Handlers: handlers,
options: Options{
EC2IMDSEndpoint: envCfg.EC2IMDSEndpoint,
},
} }
initHandlers(s) initHandlers(s)
@ -414,6 +463,7 @@ func newSession(opts Options, envCfg envConfig, cfgs ...*aws.Config) (*Session,
s := &Session{ s := &Session{
Config: cfg, Config: cfg,
Handlers: handlers, Handlers: handlers,
options: opts,
} }
initHandlers(s) initHandlers(s)
@ -550,6 +600,30 @@ func mergeConfigSrcs(cfg, userCfg *aws.Config,
} }
} }
// Regional Endpoint flag for STS endpoint resolving
mergeSTSRegionalEndpointConfig(cfg, []endpoints.STSRegionalEndpoint{
userCfg.STSRegionalEndpoint,
envCfg.STSRegionalEndpoint,
sharedCfg.STSRegionalEndpoint,
endpoints.LegacySTSEndpoint,
})
// Regional Endpoint flag for S3 endpoint resolving
mergeS3UsEast1RegionalEndpointConfig(cfg, []endpoints.S3UsEast1RegionalEndpoint{
userCfg.S3UsEast1RegionalEndpoint,
envCfg.S3UsEast1RegionalEndpoint,
sharedCfg.S3UsEast1RegionalEndpoint,
endpoints.LegacyS3UsEast1Endpoint,
})
ec2IMDSEndpoint := sessOpts.EC2IMDSEndpoint
if len(ec2IMDSEndpoint) == 0 {
ec2IMDSEndpoint = envCfg.EC2IMDSEndpoint
}
if len(ec2IMDSEndpoint) != 0 {
cfg.EndpointResolver = wrapEC2IMDSEndpoint(cfg.EndpointResolver, ec2IMDSEndpoint)
}
// Configure credentials if not already set by the user when creating the // Configure credentials if not already set by the user when creating the
// Session. // Session.
if cfg.Credentials == credentials.AnonymousCredentials && userCfg.Credentials == nil { if cfg.Credentials == credentials.AnonymousCredentials && userCfg.Credentials == nil {
@ -560,9 +634,35 @@ func mergeConfigSrcs(cfg, userCfg *aws.Config,
cfg.Credentials = creds cfg.Credentials = creds
} }
cfg.S3UseARNRegion = userCfg.S3UseARNRegion
if cfg.S3UseARNRegion == nil {
cfg.S3UseARNRegion = &envCfg.S3UseARNRegion
}
if cfg.S3UseARNRegion == nil {
cfg.S3UseARNRegion = &sharedCfg.S3UseARNRegion
}
return nil return nil
} }
func mergeSTSRegionalEndpointConfig(cfg *aws.Config, values []endpoints.STSRegionalEndpoint) {
for _, v := range values {
if v != endpoints.UnsetSTSEndpoint {
cfg.STSRegionalEndpoint = v
break
}
}
}
func mergeS3UsEast1RegionalEndpointConfig(cfg *aws.Config, values []endpoints.S3UsEast1RegionalEndpoint) {
for _, v := range values {
if v != endpoints.UnsetS3UsEast1Endpoint {
cfg.S3UsEast1RegionalEndpoint = v
break
}
}
}
func initHandlers(s *Session) { func initHandlers(s *Session) {
// Add the Validate parameter handler if it is not disabled. // Add the Validate parameter handler if it is not disabled.
s.Handlers.Validate.Remove(corehandlers.ValidateParametersHandler) s.Handlers.Validate.Remove(corehandlers.ValidateParametersHandler)
@ -581,6 +681,7 @@ func (s *Session) Copy(cfgs ...*aws.Config) *Session {
newSession := &Session{ newSession := &Session{
Config: s.Config.Copy(cfgs...), Config: s.Config.Copy(cfgs...),
Handlers: s.Handlers.Copy(), Handlers: s.Handlers.Copy(),
options: s.options,
} }
initHandlers(newSession) initHandlers(newSession)
@ -591,47 +692,69 @@ func (s *Session) Copy(cfgs ...*aws.Config) *Session {
// ClientConfig satisfies the client.ConfigProvider interface and is used to // ClientConfig satisfies the client.ConfigProvider interface and is used to
// configure the service client instances. Passing the Session to the service // configure the service client instances. Passing the Session to the service
// client's constructor (New) will use this method to configure the client. // client's constructor (New) will use this method to configure the client.
func (s *Session) ClientConfig(serviceName string, cfgs ...*aws.Config) client.Config { func (s *Session) ClientConfig(service string, cfgs ...*aws.Config) client.Config {
// Backwards compatibility, the error will be eaten if user calls ClientConfig
// directly. All SDK services will use ClientconfigWithError.
cfg, _ := s.clientConfigWithErr(serviceName, cfgs...)
return cfg
}
func (s *Session) clientConfigWithErr(serviceName string, cfgs ...*aws.Config) (client.Config, error) {
s = s.Copy(cfgs...) s = s.Copy(cfgs...)
var resolved endpoints.ResolvedEndpoint
var err error
region := aws.StringValue(s.Config.Region) region := aws.StringValue(s.Config.Region)
resolved, err := s.resolveEndpoint(service, region, s.Config)
if err != nil {
s.Handlers.Validate.PushBack(func(r *request.Request) {
if len(r.ClientInfo.Endpoint) != 0 {
// Error occurred while resolving endpoint, but the request
// being invoked has had an endpoint specified after the client
// was created.
return
}
r.Error = err
})
}
if endpoint := aws.StringValue(s.Config.Endpoint); len(endpoint) != 0 { return client.Config{
resolved.URL = endpoints.AddScheme(endpoint, aws.BoolValue(s.Config.DisableSSL)) Config: s.Config,
resolved.SigningRegion = region Handlers: s.Handlers,
} else { PartitionID: resolved.PartitionID,
resolved, err = s.Config.EndpointResolver.EndpointFor( Endpoint: resolved.URL,
serviceName, region, SigningRegion: resolved.SigningRegion,
SigningNameDerived: resolved.SigningNameDerived,
SigningName: resolved.SigningName,
}
}
const ec2MetadataServiceID = "ec2metadata"
func (s *Session) resolveEndpoint(service, region string, cfg *aws.Config) (endpoints.ResolvedEndpoint, error) {
if ep := aws.StringValue(cfg.Endpoint); len(ep) != 0 {
return endpoints.ResolvedEndpoint{
URL: endpoints.AddScheme(ep, aws.BoolValue(cfg.DisableSSL)),
SigningRegion: region,
}, nil
}
resolved, err := cfg.EndpointResolver.EndpointFor(service, region,
func(opt *endpoints.Options) { func(opt *endpoints.Options) {
opt.DisableSSL = aws.BoolValue(s.Config.DisableSSL) opt.DisableSSL = aws.BoolValue(cfg.DisableSSL)
opt.UseDualStack = aws.BoolValue(s.Config.UseDualStack) opt.UseDualStack = aws.BoolValue(cfg.UseDualStack)
// Support for STSRegionalEndpoint where the STSRegionalEndpoint is
// provided in envConfig or sharedConfig with envConfig getting
// precedence.
opt.STSRegionalEndpoint = cfg.STSRegionalEndpoint
// Support for S3UsEast1RegionalEndpoint where the S3UsEast1RegionalEndpoint is
// provided in envConfig or sharedConfig with envConfig getting
// precedence.
opt.S3UsEast1RegionalEndpoint = cfg.S3UsEast1RegionalEndpoint
// Support the condition where the service is modeled but its // Support the condition where the service is modeled but its
// endpoint metadata is not available. // endpoint metadata is not available.
opt.ResolveUnknownService = true opt.ResolveUnknownService = true
}, },
) )
if err != nil {
return endpoints.ResolvedEndpoint{}, err
} }
return client.Config{ return resolved, nil
Config: s.Config,
Handlers: s.Handlers,
Endpoint: resolved.URL,
SigningRegion: resolved.SigningRegion,
SigningNameDerived: resolved.SigningNameDerived,
SigningName: resolved.SigningName,
}, err
} }
// ClientConfigNoResolveEndpoint is the same as ClientConfig with the exception // ClientConfigNoResolveEndpoint is the same as ClientConfig with the exception
@ -641,12 +764,9 @@ func (s *Session) ClientConfigNoResolveEndpoint(cfgs ...*aws.Config) client.Conf
s = s.Copy(cfgs...) s = s.Copy(cfgs...)
var resolved endpoints.ResolvedEndpoint var resolved endpoints.ResolvedEndpoint
region := aws.StringValue(s.Config.Region)
if ep := aws.StringValue(s.Config.Endpoint); len(ep) > 0 { if ep := aws.StringValue(s.Config.Endpoint); len(ep) > 0 {
resolved.URL = endpoints.AddScheme(ep, aws.BoolValue(s.Config.DisableSSL)) resolved.URL = endpoints.AddScheme(ep, aws.BoolValue(s.Config.DisableSSL))
resolved.SigningRegion = region resolved.SigningRegion = aws.StringValue(s.Config.Region)
} }
return client.Config{ return client.Config{
@ -658,3 +778,14 @@ func (s *Session) ClientConfigNoResolveEndpoint(cfgs ...*aws.Config) client.Conf
SigningName: resolved.SigningName, SigningName: resolved.SigningName,
} }
} }
// logDeprecatedNewSessionError function enables error handling for session
func (s *Session) logDeprecatedNewSessionError(msg string, err error, cfgs []*aws.Config) {
// Session creation failed, need to report the error and prevent
// any requests from succeeding.
s.Config.MergeIn(cfgs...)
s.Config.Logger.Log("ERROR:", msg, "Error:", err)
s.Handlers.Validate.PushBack(func(r *request.Request) {
r.Error = err
})
}

View File

@ -2,9 +2,11 @@ package session
import ( import (
"fmt" "fmt"
"time"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/internal/ini" "github.com/aws/aws-sdk-go/internal/ini"
) )
@ -21,6 +23,7 @@ const (
externalIDKey = `external_id` // optional externalIDKey = `external_id` // optional
mfaSerialKey = `mfa_serial` // optional mfaSerialKey = `mfa_serial` // optional
roleSessionNameKey = `role_session_name` // optional roleSessionNameKey = `role_session_name` // optional
roleDurationSecondsKey = "duration_seconds" // optional
// CSM options // CSM options
csmEnabledKey = `csm_enabled` csmEnabledKey = `csm_enabled`
@ -40,10 +43,19 @@ const (
// Web Identity Token File // Web Identity Token File
webIdentityTokenFileKey = `web_identity_token_file` // optional webIdentityTokenFileKey = `web_identity_token_file` // optional
// Additional config fields for regional or legacy endpoints
stsRegionalEndpointSharedKey = `sts_regional_endpoints`
// Additional config fields for regional or legacy endpoints
s3UsEast1RegionalSharedKey = `s3_us_east_1_regional_endpoint`
// DefaultSharedConfigProfile is the default profile to be used when // DefaultSharedConfigProfile is the default profile to be used when
// loading configuration from the config files if another profile name // loading configuration from the config files if another profile name
// is not provided. // is not provided.
DefaultSharedConfigProfile = `default` DefaultSharedConfigProfile = `default`
// S3 ARN Region Usage
s3UseARNRegionKey = "s3_use_arn_region"
) )
// sharedConfig represents the configuration fields of the SDK config files. // sharedConfig represents the configuration fields of the SDK config files.
@ -67,6 +79,7 @@ type sharedConfig struct {
RoleSessionName string RoleSessionName string
ExternalID string ExternalID string
MFASerial string MFASerial string
AssumeRoleDuration *time.Duration
SourceProfileName string SourceProfileName string
SourceProfile *sharedConfig SourceProfile *sharedConfig
@ -88,6 +101,24 @@ type sharedConfig struct {
CSMHost string CSMHost string
CSMPort string CSMPort string
CSMClientID string CSMClientID string
// Specifies the Regional Endpoint flag for the SDK to resolve the endpoint for a service
//
// sts_regional_endpoints = regional
// This can take value as `LegacySTSEndpoint` or `RegionalSTSEndpoint`
STSRegionalEndpoint endpoints.STSRegionalEndpoint
// Specifies the Regional Endpoint flag for the SDK to resolve the endpoint for a service
//
// s3_us_east_1_regional_endpoint = regional
// This can take value as `LegacyS3UsEast1Endpoint` or `RegionalS3UsEast1Endpoint`
S3UsEast1RegionalEndpoint endpoints.S3UsEast1RegionalEndpoint
// Specifies if the S3 service should allow ARNs to direct the region
// the client's requests are sent to.
//
// s3_use_arn_region=true
S3UseARNRegion bool
} }
type sharedConfigFile struct { type sharedConfigFile struct {
@ -244,8 +275,30 @@ func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile, e
updateString(&cfg.RoleSessionName, section, roleSessionNameKey) updateString(&cfg.RoleSessionName, section, roleSessionNameKey)
updateString(&cfg.SourceProfileName, section, sourceProfileKey) updateString(&cfg.SourceProfileName, section, sourceProfileKey)
updateString(&cfg.CredentialSource, section, credentialSourceKey) updateString(&cfg.CredentialSource, section, credentialSourceKey)
updateString(&cfg.Region, section, regionKey) updateString(&cfg.Region, section, regionKey)
if section.Has(roleDurationSecondsKey) {
d := time.Duration(section.Int(roleDurationSecondsKey)) * time.Second
cfg.AssumeRoleDuration = &d
}
if v := section.String(stsRegionalEndpointSharedKey); len(v) != 0 {
sre, err := endpoints.GetSTSRegionalEndpoint(v)
if err != nil {
return fmt.Errorf("failed to load %s from shared config, %s, %v",
stsRegionalEndpointSharedKey, file.Filename, err)
}
cfg.STSRegionalEndpoint = sre
}
if v := section.String(s3UsEast1RegionalSharedKey); len(v) != 0 {
sre, err := endpoints.GetS3UsEast1RegionalEndpoint(v)
if err != nil {
return fmt.Errorf("failed to load %s from shared config, %s, %v",
s3UsEast1RegionalSharedKey, file.Filename, err)
}
cfg.S3UsEast1RegionalEndpoint = sre
}
} }
updateString(&cfg.CredentialProcess, section, credentialProcessKey) updateString(&cfg.CredentialProcess, section, credentialProcessKey)
@ -271,6 +324,8 @@ func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile, e
updateString(&cfg.CSMPort, section, csmPortKey) updateString(&cfg.CSMPort, section, csmPortKey)
updateString(&cfg.CSMClientID, section, csmClientIDKey) updateString(&cfg.CSMClientID, section, csmClientIDKey)
updateBool(&cfg.S3UseARNRegion, section, s3UseARNRegionKey)
return nil return nil
} }
@ -363,6 +418,15 @@ func updateString(dst *string, section ini.Section, key string) {
*dst = section.String(key) *dst = section.String(key)
} }
// updateBool will only update the dst with the value in the section key, key
// is present in the section.
func updateBool(dst *bool, section ini.Section, key string) {
if !section.Has(key) {
return
}
*dst = section.Bool(key)
}
// updateBoolPtr will only update the dst with the value in the section key, // updateBoolPtr will only update the dst with the value in the section key,
// key is present in the section. // key is present in the section.
func updateBoolPtr(dst **bool, section ini.Section, key string) { func updateBoolPtr(dst **bool, section ini.Section, key string) {

View File

@ -1,8 +1,7 @@
package v4 package v4
import ( import (
"net/http" "github.com/aws/aws-sdk-go/internal/strings"
"strings"
) )
// validator houses a set of rule needed for validation of a // validator houses a set of rule needed for validation of a
@ -61,7 +60,7 @@ type patterns []string
// been found // been found
func (p patterns) IsValid(value string) bool { func (p patterns) IsValid(value string) bool {
for _, pattern := range p { for _, pattern := range p {
if strings.HasPrefix(http.CanonicalHeaderKey(value), pattern) { if strings.HasPrefixFold(value, pattern) {
return true return true
} }
} }

View File

@ -0,0 +1,13 @@
// +build !go1.7
package v4
import (
"net/http"
"github.com/aws/aws-sdk-go/aws"
)
func requestContext(r *http.Request) aws.Context {
return aws.BackgroundContext()
}

View File

@ -0,0 +1,13 @@
// +build go1.7
package v4
import (
"net/http"
"github.com/aws/aws-sdk-go/aws"
)
func requestContext(r *http.Request) aws.Context {
return r.Context()
}

View File

@ -0,0 +1,63 @@
package v4
import (
"encoding/hex"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws/credentials"
)
type credentialValueProvider interface {
Get() (credentials.Value, error)
}
// StreamSigner implements signing of event stream encoded payloads
type StreamSigner struct {
region string
service string
credentials credentialValueProvider
prevSig []byte
}
// NewStreamSigner creates a SigV4 signer used to sign Event Stream encoded messages
func NewStreamSigner(region, service string, seedSignature []byte, credentials *credentials.Credentials) *StreamSigner {
return &StreamSigner{
region: region,
service: service,
credentials: credentials,
prevSig: seedSignature,
}
}
// GetSignature takes an event stream encoded headers and payload and returns a signature
func (s *StreamSigner) GetSignature(headers, payload []byte, date time.Time) ([]byte, error) {
credValue, err := s.credentials.Get()
if err != nil {
return nil, err
}
sigKey := deriveSigningKey(s.region, s.service, credValue.SecretAccessKey, date)
keyPath := buildSigningScope(s.region, s.service, date)
stringToSign := buildEventStreamStringToSign(headers, payload, s.prevSig, keyPath, date)
signature := hmacSHA256(sigKey, []byte(stringToSign))
s.prevSig = signature
return signature, nil
}
func buildEventStreamStringToSign(headers, payload, prevSig []byte, scope string, date time.Time) string {
return strings.Join([]string{
"AWS4-HMAC-SHA256-PAYLOAD",
formatTime(date),
scope,
hex.EncodeToString(prevSig),
hex.EncodeToString(hashSHA256(headers)),
hex.EncodeToString(hashSHA256(payload)),
}, "\n")
}

View File

@ -76,9 +76,14 @@ import (
) )
const ( const (
authorizationHeader = "Authorization"
authHeaderSignatureElem = "Signature="
signatureQueryKey = "X-Amz-Signature"
authHeaderPrefix = "AWS4-HMAC-SHA256" authHeaderPrefix = "AWS4-HMAC-SHA256"
timeFormat = "20060102T150405Z" timeFormat = "20060102T150405Z"
shortTimeFormat = "20060102" shortTimeFormat = "20060102"
awsV4Request = "aws4_request"
// emptyStringSHA256 is a SHA256 of an empty string // emptyStringSHA256 is a SHA256 of an empty string
emptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` emptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
@ -87,7 +92,7 @@ const (
var ignoredHeaders = rules{ var ignoredHeaders = rules{
blacklist{ blacklist{
mapRule{ mapRule{
"Authorization": struct{}{}, authorizationHeader: struct{}{},
"User-Agent": struct{}{}, "User-Agent": struct{}{},
"X-Amzn-Trace-Id": struct{}{}, "X-Amzn-Trace-Id": struct{}{},
}, },
@ -231,8 +236,6 @@ type signingCtx struct {
credValues credentials.Value credValues credentials.Value
isPresign bool isPresign bool
formattedTime string
formattedShortTime string
unsignedPayload bool unsignedPayload bool
bodyDigest string bodyDigest string
@ -337,7 +340,7 @@ func (v4 Signer) signWithBody(r *http.Request, body io.ReadSeeker, service, regi
} }
var err error var err error
ctx.credValues, err = v4.Credentials.Get() ctx.credValues, err = v4.Credentials.GetWithContext(requestContext(r))
if err != nil { if err != nil {
return http.Header{}, err return http.Header{}, err
} }
@ -532,39 +535,56 @@ func (ctx *signingCtx) build(disableHeaderHoisting bool) error {
ctx.buildSignature() // depends on string to sign ctx.buildSignature() // depends on string to sign
if ctx.isPresign { if ctx.isPresign {
ctx.Request.URL.RawQuery += "&X-Amz-Signature=" + ctx.signature ctx.Request.URL.RawQuery += "&" + signatureQueryKey + "=" + ctx.signature
} else { } else {
parts := []string{ parts := []string{
authHeaderPrefix + " Credential=" + ctx.credValues.AccessKeyID + "/" + ctx.credentialString, authHeaderPrefix + " Credential=" + ctx.credValues.AccessKeyID + "/" + ctx.credentialString,
"SignedHeaders=" + ctx.signedHeaders, "SignedHeaders=" + ctx.signedHeaders,
"Signature=" + ctx.signature, authHeaderSignatureElem + ctx.signature,
} }
ctx.Request.Header.Set("Authorization", strings.Join(parts, ", ")) ctx.Request.Header.Set(authorizationHeader, strings.Join(parts, ", "))
} }
return nil return nil
} }
func (ctx *signingCtx) buildTime() { // GetSignedRequestSignature attempts to extract the signature of the request.
ctx.formattedTime = ctx.Time.UTC().Format(timeFormat) // Returning an error if the request is unsigned, or unable to extract the
ctx.formattedShortTime = ctx.Time.UTC().Format(shortTimeFormat) // signature.
func GetSignedRequestSignature(r *http.Request) ([]byte, error) {
if auth := r.Header.Get(authorizationHeader); len(auth) != 0 {
ps := strings.Split(auth, ", ")
for _, p := range ps {
if idx := strings.Index(p, authHeaderSignatureElem); idx >= 0 {
sig := p[len(authHeaderSignatureElem):]
if len(sig) == 0 {
return nil, fmt.Errorf("invalid request signature authorization header")
}
return hex.DecodeString(sig)
}
}
}
if sig := r.URL.Query().Get("X-Amz-Signature"); len(sig) != 0 {
return hex.DecodeString(sig)
}
return nil, fmt.Errorf("request not signed")
}
func (ctx *signingCtx) buildTime() {
if ctx.isPresign { if ctx.isPresign {
duration := int64(ctx.ExpireTime / time.Second) duration := int64(ctx.ExpireTime / time.Second)
ctx.Query.Set("X-Amz-Date", ctx.formattedTime) ctx.Query.Set("X-Amz-Date", formatTime(ctx.Time))
ctx.Query.Set("X-Amz-Expires", strconv.FormatInt(duration, 10)) ctx.Query.Set("X-Amz-Expires", strconv.FormatInt(duration, 10))
} else { } else {
ctx.Request.Header.Set("X-Amz-Date", ctx.formattedTime) ctx.Request.Header.Set("X-Amz-Date", formatTime(ctx.Time))
} }
} }
func (ctx *signingCtx) buildCredentialString() { func (ctx *signingCtx) buildCredentialString() {
ctx.credentialString = strings.Join([]string{ ctx.credentialString = buildSigningScope(ctx.Region, ctx.ServiceName, ctx.Time)
ctx.formattedShortTime,
ctx.Region,
ctx.ServiceName,
"aws4_request",
}, "/")
if ctx.isPresign { if ctx.isPresign {
ctx.Query.Set("X-Amz-Credential", ctx.credValues.AccessKeyID+"/"+ctx.credentialString) ctx.Query.Set("X-Amz-Credential", ctx.credValues.AccessKeyID+"/"+ctx.credentialString)
@ -588,8 +608,7 @@ func (ctx *signingCtx) buildCanonicalHeaders(r rule, header http.Header) {
var headers []string var headers []string
headers = append(headers, "host") headers = append(headers, "host")
for k, v := range header { for k, v := range header {
canonicalKey := http.CanonicalHeaderKey(k) if !r.IsValid(k) {
if !r.IsValid(canonicalKey) {
continue // ignored header continue // ignored header
} }
if ctx.SignedHeaderVals == nil { if ctx.SignedHeaderVals == nil {
@ -653,19 +672,15 @@ func (ctx *signingCtx) buildCanonicalString() {
func (ctx *signingCtx) buildStringToSign() { func (ctx *signingCtx) buildStringToSign() {
ctx.stringToSign = strings.Join([]string{ ctx.stringToSign = strings.Join([]string{
authHeaderPrefix, authHeaderPrefix,
ctx.formattedTime, formatTime(ctx.Time),
ctx.credentialString, ctx.credentialString,
hex.EncodeToString(makeSha256([]byte(ctx.canonicalString))), hex.EncodeToString(hashSHA256([]byte(ctx.canonicalString))),
}, "\n") }, "\n")
} }
func (ctx *signingCtx) buildSignature() { func (ctx *signingCtx) buildSignature() {
secret := ctx.credValues.SecretAccessKey creds := deriveSigningKey(ctx.Region, ctx.ServiceName, ctx.credValues.SecretAccessKey, ctx.Time)
date := makeHmac([]byte("AWS4"+secret), []byte(ctx.formattedShortTime)) signature := hmacSHA256(creds, []byte(ctx.stringToSign))
region := makeHmac(date, []byte(ctx.Region))
service := makeHmac(region, []byte(ctx.ServiceName))
credentials := makeHmac(service, []byte("aws4_request"))
signature := makeHmac(credentials, []byte(ctx.stringToSign))
ctx.signature = hex.EncodeToString(signature) ctx.signature = hex.EncodeToString(signature)
} }
@ -726,13 +741,13 @@ func (ctx *signingCtx) removePresign() {
ctx.Query.Del("X-Amz-SignedHeaders") ctx.Query.Del("X-Amz-SignedHeaders")
} }
func makeHmac(key []byte, data []byte) []byte { func hmacSHA256(key []byte, data []byte) []byte {
hash := hmac.New(sha256.New, key) hash := hmac.New(sha256.New, key)
hash.Write(data) hash.Write(data)
return hash.Sum(nil) return hash.Sum(nil)
} }
func makeSha256(data []byte) []byte { func hashSHA256(data []byte) []byte {
hash := sha256.New() hash := sha256.New()
hash.Write(data) hash.Write(data)
return hash.Sum(nil) return hash.Sum(nil)
@ -804,3 +819,28 @@ func stripExcessSpaces(vals []string) {
vals[i] = string(buf[:m]) vals[i] = string(buf[:m])
} }
} }
func buildSigningScope(region, service string, dt time.Time) string {
return strings.Join([]string{
formatShortTime(dt),
region,
service,
awsV4Request,
}, "/")
}
func deriveSigningKey(region, service, secretKey string, dt time.Time) []byte {
kDate := hmacSHA256([]byte("AWS4"+secretKey), []byte(formatShortTime(dt)))
kRegion := hmacSHA256(kDate, []byte(region))
kService := hmacSHA256(kRegion, []byte(service))
signingKey := hmacSHA256(kService, []byte(awsV4Request))
return signingKey
}
func formatShortTime(dt time.Time) string {
return dt.UTC().Format(shortTimeFormat)
}
func formatTime(dt time.Time) string {
return dt.UTC().Format(timeFormat)
}

View File

@ -2,6 +2,7 @@ package aws
import ( import (
"io" "io"
"strings"
"sync" "sync"
"github.com/aws/aws-sdk-go/internal/sdkio" "github.com/aws/aws-sdk-go/internal/sdkio"
@ -205,3 +206,59 @@ func (b *WriteAtBuffer) Bytes() []byte {
defer b.m.Unlock() defer b.m.Unlock()
return b.buf return b.buf
} }
// MultiCloser is a utility to close multiple io.Closers within a single
// statement.
type MultiCloser []io.Closer
// Close closes all of the io.Closers making up the MultiClosers. Any
// errors that occur while closing will be returned in the order they
// occur.
func (m MultiCloser) Close() error {
var errs errors
for _, c := range m {
err := c.Close()
if err != nil {
errs = append(errs, err)
}
}
if len(errs) != 0 {
return errs
}
return nil
}
type errors []error
func (es errors) Error() string {
var parts []string
for _, e := range es {
parts = append(parts, e.Error())
}
return strings.Join(parts, "\n")
}
// CopySeekableBody copies the seekable body to an io.Writer
func CopySeekableBody(dst io.Writer, src io.ReadSeeker) (int64, error) {
curPos, err := src.Seek(0, sdkio.SeekCurrent)
if err != nil {
return 0, err
}
// copy errors may be assumed to be from the body.
n, err := io.Copy(dst, src)
if err != nil {
return n, err
}
// seek back to the first position after reading to reset
// the body for transmission.
_, err = src.Seek(curPos, sdkio.SeekStart)
if err != nil {
return n, err
}
return n, nil
}

View File

@ -5,4 +5,4 @@ package aws
const SDKName = "aws-sdk-go" const SDKName = "aws-sdk-go"
// SDKVersion is the version of this SDK // SDKVersion is the version of this SDK
const SDKVersion = "1.25.8" const SDKVersion = "1.34.19"

View File

@ -0,0 +1,40 @@
// +build !go1.7
package context
import "time"
// An emptyCtx is a copy of the Go 1.7 context.emptyCtx type. This is copied to
// provide a 1.6 and 1.5 safe version of context that is compatible with Go
// 1.7's Context.
//
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}
func (e *emptyCtx) String() string {
switch e {
case BackgroundCtx:
return "aws.BackgroundContext"
}
return "unknown empty Context"
}
// BackgroundCtx is the common base context.
var BackgroundCtx = new(emptyCtx)

View File

@ -0,0 +1,11 @@
package strings
import (
"strings"
)
// HasPrefixFold tests whether the string s begins with prefix, interpreted as UTF-8 strings,
// under Unicode case-folding.
func HasPrefixFold(s, prefix string) bool {
return len(s) >= len(prefix) && strings.EqualFold(s[0:len(prefix)], prefix)
}

View File

@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,120 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package singleflight provides a duplicate function call suppression
// mechanism.
package singleflight
import "sync"
// call is an in-flight or completed singleflight.Do call
type call struct {
wg sync.WaitGroup
// These fields are written once before the WaitGroup is done
// and are only read after the WaitGroup is done.
val interface{}
err error
// forgotten indicates whether Forget was called with this call's key
// while the call was still in flight.
forgotten bool
// These fields are read and written with the singleflight
// mutex held before the WaitGroup is done, and are read but
// not written after the WaitGroup is done.
dups int
chans []chan<- Result
}
// Group represents a class of work and forms a namespace in
// which units of work can be executed with duplicate suppression.
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
}
// Result holds the results of Do, so they can be passed
// on a channel.
type Result struct {
Val interface{}
Err error
Shared bool
}
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
// The return value shared indicates whether v was given to multiple callers.
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
if c, ok := g.m[key]; ok {
c.dups++
g.mu.Unlock()
c.wg.Wait()
return c.val, c.err, true
}
c := new(call)
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
g.doCall(c, key, fn)
return c.val, c.err, c.dups > 0
}
// DoChan is like Do but returns a channel that will receive the
// results when they are ready.
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {
ch := make(chan Result, 1)
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
if c, ok := g.m[key]; ok {
c.dups++
c.chans = append(c.chans, ch)
g.mu.Unlock()
return ch
}
c := &call{chans: []chan<- Result{ch}}
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
go g.doCall(c, key, fn)
return ch
}
// doCall handles the single call for a key.
func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
c.val, c.err = fn()
c.wg.Done()
g.mu.Lock()
if !c.forgotten {
delete(g.m, key)
}
for _, ch := range c.chans {
ch <- Result{c.val, c.err, c.dups > 0}
}
g.mu.Unlock()
}
// Forget tells the singleflight to forget about a key. Future calls
// to Do for this key will call the function rather than waiting for
// an earlier call to complete.
func (g *Group) Forget(key string) {
g.mu.Lock()
if c, ok := g.m[key]; ok {
c.forgotten = true
}
delete(g.m, key)
g.mu.Unlock()
}

View File

@ -0,0 +1,53 @@
package checksum
import (
"crypto/md5"
"encoding/base64"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
)
const contentMD5Header = "Content-Md5"
// AddBodyContentMD5Handler computes and sets the HTTP Content-MD5 header for requests that
// require it.
func AddBodyContentMD5Handler(r *request.Request) {
// if Content-MD5 header is already present, return
if v := r.HTTPRequest.Header.Get(contentMD5Header); len(v) != 0 {
return
}
// if S3DisableContentMD5Validation flag is set, return
if aws.BoolValue(r.Config.S3DisableContentMD5Validation) {
return
}
// if request is presigned, return
if r.IsPresigned() {
return
}
// if body is not seekable, return
if !aws.IsReaderSeekable(r.Body) {
if r.Config.Logger != nil {
r.Config.Logger.Log(fmt.Sprintf(
"Unable to compute Content-MD5 for unseekable body, S3.%s",
r.Operation.Name))
}
return
}
h := md5.New()
if _, err := aws.CopySeekableBody(h, r.Body); err != nil {
r.Error = awserr.New("ContentMD5", "failed to compute body MD5", err)
return
}
// encode the md5 checksum in base64 and set the request header.
v := base64.StdEncoding.EncodeToString(h.Sum(nil))
r.HTTPRequest.Header.Set(contentMD5Header, v)
}

View File

@ -101,7 +101,7 @@ func (hs *decodedHeaders) UnmarshalJSON(b []byte) error {
} }
headers.Set(h.Name, value) headers.Set(h.Name, value)
} }
(*hs) = decodedHeaders(headers) *hs = decodedHeaders(headers)
return nil return nil
} }

View File

@ -21,10 +21,24 @@ type Decoder struct {
// NewDecoder initializes and returns a Decoder for decoding event // NewDecoder initializes and returns a Decoder for decoding event
// stream messages from the reader provided. // stream messages from the reader provided.
func NewDecoder(r io.Reader) *Decoder { func NewDecoder(r io.Reader, opts ...func(*Decoder)) *Decoder {
return &Decoder{ d := &Decoder{
r: r, r: r,
} }
for _, opt := range opts {
opt(d)
}
return d
}
// DecodeWithLogger adds a logger to be used by the decoder when decoding
// stream events.
func DecodeWithLogger(logger aws.Logger) func(*Decoder) {
return func(d *Decoder) {
d.logger = logger
}
} }
// Decode attempts to decode a single message from the event stream reader. // Decode attempts to decode a single message from the event stream reader.
@ -40,6 +54,15 @@ func (d *Decoder) Decode(payloadBuf []byte) (m Message, err error) {
}() }()
} }
m, err = Decode(reader, payloadBuf)
return m, err
}
// Decode attempts to decode a single message from the event stream reader.
// Will return the event stream message, or error if Decode fails to read
// the message from the reader.
func Decode(reader io.Reader, payloadBuf []byte) (m Message, err error) {
crc := crc32.New(crc32IEEETable) crc := crc32.New(crc32IEEETable)
hashReader := io.TeeReader(reader, crc) hashReader := io.TeeReader(reader, crc)
@ -72,12 +95,6 @@ func (d *Decoder) Decode(payloadBuf []byte) (m Message, err error) {
return m, nil return m, nil
} }
// UseLogger specifies the Logger that that the decoder should use to log the
// message decode to.
func (d *Decoder) UseLogger(logger aws.Logger) {
d.logger = logger
}
func logMessageDecode(logger aws.Logger, msgBuf *bytes.Buffer, msg Message, decodeErr error) { func logMessageDecode(logger aws.Logger, msgBuf *bytes.Buffer, msg Message, decodeErr error) {
w := bytes.NewBuffer(nil) w := bytes.NewBuffer(nil)
defer func() { logger.Log(w.String()) }() defer func() { logger.Log(w.String()) }()

View File

@ -3,61 +3,107 @@ package eventstream
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"hash" "hash"
"hash/crc32" "hash/crc32"
"io" "io"
"github.com/aws/aws-sdk-go/aws"
) )
// Encoder provides EventStream message encoding. // Encoder provides EventStream message encoding.
type Encoder struct { type Encoder struct {
w io.Writer w io.Writer
logger aws.Logger
headersBuf *bytes.Buffer headersBuf *bytes.Buffer
} }
// NewEncoder initializes and returns an Encoder to encode Event Stream // NewEncoder initializes and returns an Encoder to encode Event Stream
// messages to an io.Writer. // messages to an io.Writer.
func NewEncoder(w io.Writer) *Encoder { func NewEncoder(w io.Writer, opts ...func(*Encoder)) *Encoder {
return &Encoder{ e := &Encoder{
w: w, w: w,
headersBuf: bytes.NewBuffer(nil), headersBuf: bytes.NewBuffer(nil),
} }
for _, opt := range opts {
opt(e)
}
return e
}
// EncodeWithLogger adds a logger to be used by the encode when decoding
// stream events.
func EncodeWithLogger(logger aws.Logger) func(*Encoder) {
return func(d *Encoder) {
d.logger = logger
}
} }
// Encode encodes a single EventStream message to the io.Writer the Encoder // Encode encodes a single EventStream message to the io.Writer the Encoder
// was created with. An error is returned if writing the message fails. // was created with. An error is returned if writing the message fails.
func (e *Encoder) Encode(msg Message) error { func (e *Encoder) Encode(msg Message) (err error) {
e.headersBuf.Reset() e.headersBuf.Reset()
err := encodeHeaders(e.headersBuf, msg.Headers) writer := e.w
if err != nil { if e.logger != nil {
encodeMsgBuf := bytes.NewBuffer(nil)
writer = io.MultiWriter(writer, encodeMsgBuf)
defer func() {
logMessageEncode(e.logger, encodeMsgBuf, msg, err)
}()
}
if err = EncodeHeaders(e.headersBuf, msg.Headers); err != nil {
return err return err
} }
crc := crc32.New(crc32IEEETable) crc := crc32.New(crc32IEEETable)
hashWriter := io.MultiWriter(e.w, crc) hashWriter := io.MultiWriter(writer, crc)
headersLen := uint32(e.headersBuf.Len()) headersLen := uint32(e.headersBuf.Len())
payloadLen := uint32(len(msg.Payload)) payloadLen := uint32(len(msg.Payload))
if err := encodePrelude(hashWriter, crc, headersLen, payloadLen); err != nil { if err = encodePrelude(hashWriter, crc, headersLen, payloadLen); err != nil {
return err return err
} }
if headersLen > 0 { if headersLen > 0 {
if _, err := io.Copy(hashWriter, e.headersBuf); err != nil { if _, err = io.Copy(hashWriter, e.headersBuf); err != nil {
return err return err
} }
} }
if payloadLen > 0 { if payloadLen > 0 {
if _, err := hashWriter.Write(msg.Payload); err != nil { if _, err = hashWriter.Write(msg.Payload); err != nil {
return err return err
} }
} }
msgCRC := crc.Sum32() msgCRC := crc.Sum32()
return binary.Write(e.w, binary.BigEndian, msgCRC) return binary.Write(writer, binary.BigEndian, msgCRC)
}
func logMessageEncode(logger aws.Logger, msgBuf *bytes.Buffer, msg Message, encodeErr error) {
w := bytes.NewBuffer(nil)
defer func() { logger.Log(w.String()) }()
fmt.Fprintf(w, "Message to encode:\n")
encoder := json.NewEncoder(w)
if err := encoder.Encode(msg); err != nil {
fmt.Fprintf(w, "Failed to get encoded message, %v\n", err)
}
if encodeErr != nil {
fmt.Fprintf(w, "Encode error: %v\n", encodeErr)
return
}
fmt.Fprintf(w, "Raw message:\n%s\n", hex.Dump(msgBuf.Bytes()))
} }
func encodePrelude(w io.Writer, crc hash.Hash32, headersLen, payloadLen uint32) error { func encodePrelude(w io.Writer, crc hash.Hash32, headersLen, payloadLen uint32) error {
@ -86,7 +132,9 @@ func encodePrelude(w io.Writer, crc hash.Hash32, headersLen, payloadLen uint32)
return nil return nil
} }
func encodeHeaders(w io.Writer, headers Headers) error { // EncodeHeaders writes the header values to the writer encoded in the event
// stream format. Returns an error if a header fails to encode.
func EncodeHeaders(w io.Writer, headers Headers) error {
for _, h := range headers { for _, h := range headers {
hn := headerName{ hn := headerName{
Len: uint8(len(h.Name)), Len: uint8(len(h.Name)),

View File

@ -1,6 +1,9 @@
package eventstreamapi package eventstreamapi
import "fmt" import (
"fmt"
"sync"
)
type messageError struct { type messageError struct {
code string code string
@ -22,3 +25,53 @@ func (e messageError) Error() string {
func (e messageError) OrigErr() error { func (e messageError) OrigErr() error {
return nil return nil
} }
// OnceError wraps the behavior of recording an error
// once and signal on a channel when this has occurred.
// Signaling is done by closing of the channel.
//
// Type is safe for concurrent usage.
type OnceError struct {
mu sync.RWMutex
err error
ch chan struct{}
}
// NewOnceError return a new OnceError
func NewOnceError() *OnceError {
return &OnceError{
ch: make(chan struct{}, 1),
}
}
// Err acquires a read-lock and returns an
// error if one has been set.
func (e *OnceError) Err() error {
e.mu.RLock()
err := e.err
e.mu.RUnlock()
return err
}
// SetError acquires a write-lock and will set
// the underlying error value if one has not been set.
func (e *OnceError) SetError(err error) {
if err == nil {
return
}
e.mu.Lock()
if e.err == nil {
e.err = err
close(e.ch)
}
e.mu.Unlock()
}
// ErrorSet returns a channel that will be used to signal
// that an error has been set. This channel will be closed
// when the error value has been set for OnceError.
func (e *OnceError) ErrorSet() <-chan struct{} {
return e.ch
}

View File

@ -2,9 +2,7 @@ package eventstreamapi
import ( import (
"fmt" "fmt"
"io"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/private/protocol" "github.com/aws/aws-sdk-go/private/protocol"
"github.com/aws/aws-sdk-go/private/protocol/eventstream" "github.com/aws/aws-sdk-go/private/protocol/eventstream"
) )
@ -15,27 +13,8 @@ type Unmarshaler interface {
UnmarshalEvent(protocol.PayloadUnmarshaler, eventstream.Message) error UnmarshalEvent(protocol.PayloadUnmarshaler, eventstream.Message) error
} }
// EventStream headers with specific meaning to async API functionality.
const (
MessageTypeHeader = `:message-type` // Identifies type of message.
EventMessageType = `event`
ErrorMessageType = `error`
ExceptionMessageType = `exception`
// Message Events
EventTypeHeader = `:event-type` // Identifies message event type e.g. "Stats".
// Message Error
ErrorCodeHeader = `:error-code`
ErrorMessageHeader = `:error-message`
// Message Exception
ExceptionTypeHeader = `:exception-type`
)
// EventReader provides reading from the EventStream of an reader. // EventReader provides reading from the EventStream of an reader.
type EventReader struct { type EventReader struct {
reader io.ReadCloser
decoder *eventstream.Decoder decoder *eventstream.Decoder
unmarshalerForEventType func(string) (Unmarshaler, error) unmarshalerForEventType func(string) (Unmarshaler, error)
@ -47,27 +26,18 @@ type EventReader struct {
// NewEventReader returns a EventReader built from the reader and unmarshaler // NewEventReader returns a EventReader built from the reader and unmarshaler
// provided. Use ReadStream method to start reading from the EventStream. // provided. Use ReadStream method to start reading from the EventStream.
func NewEventReader( func NewEventReader(
reader io.ReadCloser, decoder *eventstream.Decoder,
payloadUnmarshaler protocol.PayloadUnmarshaler, payloadUnmarshaler protocol.PayloadUnmarshaler,
unmarshalerForEventType func(string) (Unmarshaler, error), unmarshalerForEventType func(string) (Unmarshaler, error),
) *EventReader { ) *EventReader {
return &EventReader{ return &EventReader{
reader: reader, decoder: decoder,
decoder: eventstream.NewDecoder(reader),
payloadUnmarshaler: payloadUnmarshaler, payloadUnmarshaler: payloadUnmarshaler,
unmarshalerForEventType: unmarshalerForEventType, unmarshalerForEventType: unmarshalerForEventType,
payloadBuf: make([]byte, 10*1024), payloadBuf: make([]byte, 10*1024),
} }
} }
// UseLogger instructs the EventReader to use the logger and log level
// specified.
func (r *EventReader) UseLogger(logger aws.Logger, logLevel aws.LogLevelType) {
if logger != nil && logLevel.Matches(aws.LogDebugWithEventStreamBody) {
r.decoder.UseLogger(logger)
}
}
// ReadEvent attempts to read a message from the EventStream and return the // ReadEvent attempts to read a message from the EventStream and return the
// unmarshaled event value that the message is for. // unmarshaled event value that the message is for.
// //
@ -95,14 +65,26 @@ func (r *EventReader) ReadEvent() (event interface{}, err error) {
case EventMessageType: case EventMessageType:
return r.unmarshalEventMessage(msg) return r.unmarshalEventMessage(msg)
case ExceptionMessageType: case ExceptionMessageType:
err = r.unmarshalEventException(msg) return nil, r.unmarshalEventException(msg)
return nil, err
case ErrorMessageType: case ErrorMessageType:
return nil, r.unmarshalErrorMessage(msg) return nil, r.unmarshalErrorMessage(msg)
default: default:
return nil, fmt.Errorf("unknown eventstream message type, %v", typ) return nil, &UnknownMessageTypeError{
Type: typ, Message: msg.Clone(),
} }
} }
}
// UnknownMessageTypeError provides an error when a message is received from
// the stream, but the reader is unable to determine what kind of message it is.
type UnknownMessageTypeError struct {
Type string
Message eventstream.Message
}
func (e *UnknownMessageTypeError) Error() string {
return "unknown eventstream message type, " + e.Type
}
func (r *EventReader) unmarshalEventMessage( func (r *EventReader) unmarshalEventMessage(
msg eventstream.Message, msg eventstream.Message,
@ -174,11 +156,6 @@ func (r *EventReader) unmarshalErrorMessage(msg eventstream.Message) (err error)
return msgErr return msgErr
} }
// Close closes the EventReader's EventStream reader.
func (r *EventReader) Close() error {
return r.reader.Close()
}
// GetHeaderString returns the value of the header as a string. If the header // GetHeaderString returns the value of the header as a string. If the header
// is not set or the value is not a string an error will be returned. // is not set or the value is not a string an error will be returned.
func GetHeaderString(msg eventstream.Message, headerName string) (string, error) { func GetHeaderString(msg eventstream.Message, headerName string) (string, error) {

View File

@ -0,0 +1,23 @@
package eventstreamapi
// EventStream headers with specific meaning to async API functionality.
const (
ChunkSignatureHeader = `:chunk-signature` // chunk signature for message
DateHeader = `:date` // Date header for signature
// Message header and values
MessageTypeHeader = `:message-type` // Identifies type of message.
EventMessageType = `event`
ErrorMessageType = `error`
ExceptionMessageType = `exception`
// Message Events
EventTypeHeader = `:event-type` // Identifies message event type e.g. "Stats".
// Message Error
ErrorCodeHeader = `:error-code`
ErrorMessageHeader = `:error-message`
// Message Exception
ExceptionTypeHeader = `:exception-type`
)

View File

@ -0,0 +1,123 @@
package eventstreamapi
import (
"bytes"
"strings"
"time"
"github.com/aws/aws-sdk-go/private/protocol/eventstream"
)
var timeNow = time.Now
// StreamSigner defines an interface for the implementation of signing of event stream payloads
type StreamSigner interface {
GetSignature(headers, payload []byte, date time.Time) ([]byte, error)
}
// SignEncoder envelopes event stream messages
// into an event stream message payload with included
// signature headers using the provided signer and encoder.
type SignEncoder struct {
signer StreamSigner
encoder Encoder
bufEncoder *BufferEncoder
closeErr error
closed bool
}
// NewSignEncoder returns a new SignEncoder using the provided stream signer and
// event stream encoder.
func NewSignEncoder(signer StreamSigner, encoder Encoder) *SignEncoder {
// TODO: Need to pass down logging
return &SignEncoder{
signer: signer,
encoder: encoder,
bufEncoder: NewBufferEncoder(),
}
}
// Close encodes a final event stream signing envelope with an empty event stream
// payload. This final end-frame is used to mark the conclusion of the stream.
func (s *SignEncoder) Close() error {
if s.closed {
return s.closeErr
}
if err := s.encode([]byte{}); err != nil {
if strings.Contains(err.Error(), "on closed pipe") {
return nil
}
s.closeErr = err
s.closed = true
return s.closeErr
}
return nil
}
// Encode takes the provided message and add envelopes the message
// with the required signature.
func (s *SignEncoder) Encode(msg eventstream.Message) error {
payload, err := s.bufEncoder.Encode(msg)
if err != nil {
return err
}
return s.encode(payload)
}
func (s SignEncoder) encode(payload []byte) error {
date := timeNow()
var msg eventstream.Message
msg.Headers.Set(DateHeader, eventstream.TimestampValue(date))
msg.Payload = payload
var headers bytes.Buffer
if err := eventstream.EncodeHeaders(&headers, msg.Headers); err != nil {
return err
}
sig, err := s.signer.GetSignature(headers.Bytes(), msg.Payload, date)
if err != nil {
return err
}
msg.Headers.Set(ChunkSignatureHeader, eventstream.BytesValue(sig))
return s.encoder.Encode(msg)
}
// BufferEncoder is a utility that provides a buffered
// event stream encoder
type BufferEncoder struct {
encoder Encoder
buffer *bytes.Buffer
}
// NewBufferEncoder returns a new BufferEncoder initialized
// with a 1024 byte buffer.
func NewBufferEncoder() *BufferEncoder {
buf := bytes.NewBuffer(make([]byte, 1024))
return &BufferEncoder{
encoder: eventstream.NewEncoder(buf),
buffer: buf,
}
}
// Encode returns the encoded message as a byte slice.
// The returned byte slice will be modified on the next encode call
// and should not be held onto.
func (e *BufferEncoder) Encode(msg eventstream.Message) ([]byte, error) {
e.buffer.Reset()
if err := e.encoder.Encode(msg); err != nil {
return nil, err
}
return e.buffer.Bytes(), nil
}

View File

@ -0,0 +1,129 @@
package eventstreamapi
import (
"fmt"
"io"
"sync"
"github.com/aws/aws-sdk-go/aws"
)
// StreamWriter provides concurrent safe writing to an event stream.
type StreamWriter struct {
eventWriter *EventWriter
stream chan eventWriteAsyncReport
done chan struct{}
closeOnce sync.Once
err *OnceError
streamCloser io.Closer
}
// NewStreamWriter returns a StreamWriter for the event writer, and stream
// closer provided.
func NewStreamWriter(eventWriter *EventWriter, streamCloser io.Closer) *StreamWriter {
w := &StreamWriter{
eventWriter: eventWriter,
streamCloser: streamCloser,
stream: make(chan eventWriteAsyncReport),
done: make(chan struct{}),
err: NewOnceError(),
}
go w.writeStream()
return w
}
// Close terminates the writers ability to write new events to the stream. Any
// future call to Send will fail with an error.
func (w *StreamWriter) Close() error {
w.closeOnce.Do(w.safeClose)
return w.Err()
}
func (w *StreamWriter) safeClose() {
close(w.done)
}
// ErrorSet returns a channel which will be closed
// if an error occurs.
func (w *StreamWriter) ErrorSet() <-chan struct{} {
return w.err.ErrorSet()
}
// Err returns any error that occurred while attempting to write an event to the
// stream.
func (w *StreamWriter) Err() error {
return w.err.Err()
}
// Send writes a single event to the stream returning an error if the write
// failed.
//
// Send may be called concurrently. Events will be written to the stream
// safely.
func (w *StreamWriter) Send(ctx aws.Context, event Marshaler) error {
if err := w.Err(); err != nil {
return err
}
resultCh := make(chan error)
wrapped := eventWriteAsyncReport{
Event: event,
Result: resultCh,
}
select {
case w.stream <- wrapped:
case <-ctx.Done():
return ctx.Err()
case <-w.done:
return fmt.Errorf("stream closed, unable to send event")
}
select {
case err := <-resultCh:
return err
case <-ctx.Done():
return ctx.Err()
case <-w.done:
return fmt.Errorf("stream closed, unable to send event")
}
}
func (w *StreamWriter) writeStream() {
defer w.Close()
for {
select {
case wrapper := <-w.stream:
err := w.eventWriter.WriteEvent(wrapper.Event)
wrapper.ReportResult(w.done, err)
if err != nil {
w.err.SetError(err)
return
}
case <-w.done:
if err := w.streamCloser.Close(); err != nil {
w.err.SetError(err)
}
return
}
}
}
type eventWriteAsyncReport struct {
Event Marshaler
Result chan<- error
}
func (e eventWriteAsyncReport) ReportResult(cancel <-chan struct{}, err error) bool {
select {
case e.Result <- err:
return true
case <-cancel:
return false
}
}

View File

@ -0,0 +1,109 @@
package eventstreamapi
import (
"github.com/aws/aws-sdk-go/private/protocol"
"github.com/aws/aws-sdk-go/private/protocol/eventstream"
)
// Marshaler provides a marshaling interface for event types to event stream
// messages.
type Marshaler interface {
MarshalEvent(protocol.PayloadMarshaler) (eventstream.Message, error)
}
// Encoder is an stream encoder that will encode an event stream message for
// the transport.
type Encoder interface {
Encode(eventstream.Message) error
}
// EventWriter provides a wrapper around the underlying event stream encoder
// for an io.WriteCloser.
type EventWriter struct {
encoder Encoder
payloadMarshaler protocol.PayloadMarshaler
eventTypeFor func(Marshaler) (string, error)
}
// NewEventWriter returns a new event stream writer, that will write to the
// writer provided. Use the WriteEvent method to write an event to the stream.
func NewEventWriter(encoder Encoder, pm protocol.PayloadMarshaler, eventTypeFor func(Marshaler) (string, error),
) *EventWriter {
return &EventWriter{
encoder: encoder,
payloadMarshaler: pm,
eventTypeFor: eventTypeFor,
}
}
// WriteEvent writes an event to the stream. Returns an error if the event
// fails to marshal into a message, or writing to the underlying writer fails.
func (w *EventWriter) WriteEvent(event Marshaler) error {
msg, err := w.marshal(event)
if err != nil {
return err
}
return w.encoder.Encode(msg)
}
func (w *EventWriter) marshal(event Marshaler) (eventstream.Message, error) {
eventType, err := w.eventTypeFor(event)
if err != nil {
return eventstream.Message{}, err
}
msg, err := event.MarshalEvent(w.payloadMarshaler)
if err != nil {
return eventstream.Message{}, err
}
msg.Headers.Set(EventTypeHeader, eventstream.StringValue(eventType))
return msg, nil
}
//type EventEncoder struct {
// encoder Encoder
// ppayloadMarshaler protocol.PayloadMarshaler
// eventTypeFor func(Marshaler) (string, error)
//}
//
//func (e EventEncoder) Encode(event Marshaler) error {
// msg, err := e.marshal(event)
// if err != nil {
// return err
// }
//
// return w.encoder.Encode(msg)
//}
//
//func (e EventEncoder) marshal(event Marshaler) (eventstream.Message, error) {
// eventType, err := w.eventTypeFor(event)
// if err != nil {
// return eventstream.Message{}, err
// }
//
// msg, err := event.MarshalEvent(w.payloadMarshaler)
// if err != nil {
// return eventstream.Message{}, err
// }
//
// msg.Headers.Set(EventTypeHeader, eventstream.StringValue(eventType))
// return msg, nil
//}
//
//func (w *EventWriter) marshal(event Marshaler) (eventstream.Message, error) {
// eventType, err := w.eventTypeFor(event)
// if err != nil {
// return eventstream.Message{}, err
// }
//
// msg, err := event.MarshalEvent(w.payloadMarshaler)
// if err != nil {
// return eventstream.Message{}, err
// }
//
// msg.Headers.Set(EventTypeHeader, eventstream.StringValue(eventType))
// return msg, nil
//}
//

View File

@ -52,6 +52,15 @@ func (hs *Headers) Del(name string) {
} }
} }
// Clone returns a deep copy of the headers
func (hs Headers) Clone() Headers {
o := make(Headers, 0, len(hs))
for _, h := range hs {
o.Set(h.Name, h.Value)
}
return o
}
func decodeHeaders(r io.Reader) (Headers, error) { func decodeHeaders(r io.Reader) (Headers, error) {
hs := Headers{} hs := Headers{}

View File

@ -461,6 +461,11 @@ func (v *TimestampValue) decode(r io.Reader) error {
return nil return nil
} }
// MarshalJSON implements the json.Marshaler interface
func (v TimestampValue) MarshalJSON() ([]byte, error) {
return []byte(v.String()), nil
}
func timeFromEpochMilli(t int64) time.Time { func timeFromEpochMilli(t int64) time.Time {
secs := t / 1e3 secs := t / 1e3
msec := t % 1e3 msec := t % 1e3

View File

@ -27,7 +27,7 @@ func (m *Message) rawMessage() (rawMessage, error) {
if len(m.Headers) > 0 { if len(m.Headers) > 0 {
var headers bytes.Buffer var headers bytes.Buffer
if err := encodeHeaders(&headers, m.Headers); err != nil { if err := EncodeHeaders(&headers, m.Headers); err != nil {
return rawMessage{}, err return rawMessage{}, err
} }
raw.Headers = headers.Bytes() raw.Headers = headers.Bytes()
@ -57,6 +57,20 @@ func (m *Message) rawMessage() (rawMessage, error) {
return raw, nil return raw, nil
} }
// Clone returns a deep copy of the message.
func (m Message) Clone() Message {
var payload []byte
if m.Payload != nil {
payload = make([]byte, len(m.Payload))
copy(payload, m.Payload)
}
return Message{
Headers: m.Headers.Clone(),
Payload: payload,
}
}
type messagePrelude struct { type messagePrelude struct {
Length uint32 Length uint32
HeadersLen uint32 HeadersLen uint32

View File

@ -6,7 +6,9 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"math/big"
"reflect" "reflect"
"strings"
"time" "time"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
@ -14,6 +16,8 @@ import (
"github.com/aws/aws-sdk-go/private/protocol" "github.com/aws/aws-sdk-go/private/protocol"
) )
var millisecondsFloat = new(big.Float).SetInt64(1e3)
// UnmarshalJSONError unmarshal's the reader's JSON document into the passed in // UnmarshalJSONError unmarshal's the reader's JSON document into the passed in
// type. The value to unmarshal the json document into must be a pointer to the // type. The value to unmarshal the json document into must be a pointer to the
// type. // type.
@ -38,17 +42,42 @@ func UnmarshalJSONError(v interface{}, stream io.Reader) error {
func UnmarshalJSON(v interface{}, stream io.Reader) error { func UnmarshalJSON(v interface{}, stream io.Reader) error {
var out interface{} var out interface{}
err := json.NewDecoder(stream).Decode(&out) decoder := json.NewDecoder(stream)
decoder.UseNumber()
err := decoder.Decode(&out)
if err == io.EOF { if err == io.EOF {
return nil return nil
} else if err != nil { } else if err != nil {
return err return err
} }
return unmarshalAny(reflect.ValueOf(v), out, "") return unmarshaler{}.unmarshalAny(reflect.ValueOf(v), out, "")
} }
func unmarshalAny(value reflect.Value, data interface{}, tag reflect.StructTag) error { // UnmarshalJSONCaseInsensitive reads a stream and unmarshals the result into the
// object v. Ignores casing for structure members.
func UnmarshalJSONCaseInsensitive(v interface{}, stream io.Reader) error {
var out interface{}
decoder := json.NewDecoder(stream)
decoder.UseNumber()
err := decoder.Decode(&out)
if err == io.EOF {
return nil
} else if err != nil {
return err
}
return unmarshaler{
caseInsensitive: true,
}.unmarshalAny(reflect.ValueOf(v), out, "")
}
type unmarshaler struct {
caseInsensitive bool
}
func (u unmarshaler) unmarshalAny(value reflect.Value, data interface{}, tag reflect.StructTag) error {
vtype := value.Type() vtype := value.Type()
if vtype.Kind() == reflect.Ptr { if vtype.Kind() == reflect.Ptr {
vtype = vtype.Elem() // check kind of actual element type vtype = vtype.Elem() // check kind of actual element type
@ -80,17 +109,17 @@ func unmarshalAny(value reflect.Value, data interface{}, tag reflect.StructTag)
if field, ok := vtype.FieldByName("_"); ok { if field, ok := vtype.FieldByName("_"); ok {
tag = field.Tag tag = field.Tag
} }
return unmarshalStruct(value, data, tag) return u.unmarshalStruct(value, data, tag)
case "list": case "list":
return unmarshalList(value, data, tag) return u.unmarshalList(value, data, tag)
case "map": case "map":
return unmarshalMap(value, data, tag) return u.unmarshalMap(value, data, tag)
default: default:
return unmarshalScalar(value, data, tag) return u.unmarshalScalar(value, data, tag)
} }
} }
func unmarshalStruct(value reflect.Value, data interface{}, tag reflect.StructTag) error { func (u unmarshaler) unmarshalStruct(value reflect.Value, data interface{}, tag reflect.StructTag) error {
if data == nil { if data == nil {
return nil return nil
} }
@ -114,7 +143,7 @@ func unmarshalStruct(value reflect.Value, data interface{}, tag reflect.StructTa
// unwrap any payloads // unwrap any payloads
if payload := tag.Get("payload"); payload != "" { if payload := tag.Get("payload"); payload != "" {
field, _ := t.FieldByName(payload) field, _ := t.FieldByName(payload)
return unmarshalAny(value.FieldByName(payload), data, field.Tag) return u.unmarshalAny(value.FieldByName(payload), data, field.Tag)
} }
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
@ -128,9 +157,19 @@ func unmarshalStruct(value reflect.Value, data interface{}, tag reflect.StructTa
if locName := field.Tag.Get("locationName"); locName != "" { if locName := field.Tag.Get("locationName"); locName != "" {
name = locName name = locName
} }
if u.caseInsensitive {
if _, ok := mapData[name]; !ok {
// Fallback to uncased name search if the exact name didn't match.
for kn, v := range mapData {
if strings.EqualFold(kn, name) {
mapData[name] = v
}
}
}
}
member := value.FieldByIndex(field.Index) member := value.FieldByIndex(field.Index)
err := unmarshalAny(member, mapData[name], field.Tag) err := u.unmarshalAny(member, mapData[name], field.Tag)
if err != nil { if err != nil {
return err return err
} }
@ -138,7 +177,7 @@ func unmarshalStruct(value reflect.Value, data interface{}, tag reflect.StructTa
return nil return nil
} }
func unmarshalList(value reflect.Value, data interface{}, tag reflect.StructTag) error { func (u unmarshaler) unmarshalList(value reflect.Value, data interface{}, tag reflect.StructTag) error {
if data == nil { if data == nil {
return nil return nil
} }
@ -153,7 +192,7 @@ func unmarshalList(value reflect.Value, data interface{}, tag reflect.StructTag)
} }
for i, c := range listData { for i, c := range listData {
err := unmarshalAny(value.Index(i), c, "") err := u.unmarshalAny(value.Index(i), c, "")
if err != nil { if err != nil {
return err return err
} }
@ -162,7 +201,7 @@ func unmarshalList(value reflect.Value, data interface{}, tag reflect.StructTag)
return nil return nil
} }
func unmarshalMap(value reflect.Value, data interface{}, tag reflect.StructTag) error { func (u unmarshaler) unmarshalMap(value reflect.Value, data interface{}, tag reflect.StructTag) error {
if data == nil { if data == nil {
return nil return nil
} }
@ -179,14 +218,14 @@ func unmarshalMap(value reflect.Value, data interface{}, tag reflect.StructTag)
kvalue := reflect.ValueOf(k) kvalue := reflect.ValueOf(k)
vvalue := reflect.New(value.Type().Elem()).Elem() vvalue := reflect.New(value.Type().Elem()).Elem()
unmarshalAny(vvalue, v, "") u.unmarshalAny(vvalue, v, "")
value.SetMapIndex(kvalue, vvalue) value.SetMapIndex(kvalue, vvalue)
} }
return nil return nil
} }
func unmarshalScalar(value reflect.Value, data interface{}, tag reflect.StructTag) error { func (u unmarshaler) unmarshalScalar(value reflect.Value, data interface{}, tag reflect.StructTag) error {
switch d := data.(type) { switch d := data.(type) {
case nil: case nil:
@ -222,16 +261,31 @@ func unmarshalScalar(value reflect.Value, data interface{}, tag reflect.StructTa
default: default:
return fmt.Errorf("unsupported value: %v (%s)", value.Interface(), value.Type()) return fmt.Errorf("unsupported value: %v (%s)", value.Interface(), value.Type())
} }
case float64: case json.Number:
switch value.Interface().(type) { switch value.Interface().(type) {
case *int64: case *int64:
di := int64(d) // Retain the old behavior where we would just truncate the float64
// calling d.Int64() here could cause an invalid syntax error due to the usage of strconv.ParseInt
f, err := d.Float64()
if err != nil {
return err
}
di := int64(f)
value.Set(reflect.ValueOf(&di)) value.Set(reflect.ValueOf(&di))
case *float64: case *float64:
value.Set(reflect.ValueOf(&d)) f, err := d.Float64()
if err != nil {
return err
}
value.Set(reflect.ValueOf(&f))
case *time.Time: case *time.Time:
// Time unmarshaled from a float64 can only be epoch seconds float, ok := new(big.Float).SetString(d.String())
t := time.Unix(int64(d), 0).UTC() if !ok {
return fmt.Errorf("unsupported float time representation: %v", d.String())
}
float = float.Mul(float, millisecondsFloat)
ms, _ := float.Int64()
t := time.Unix(0, ms*1e6).UTC()
value.Set(reflect.ValueOf(&t)) value.Set(reflect.ValueOf(&t))
default: default:
return fmt.Errorf("unsupported value: %v (%s)", value.Interface(), value.Type()) return fmt.Errorf("unsupported value: %v (%s)", value.Interface(), value.Type())

View File

@ -64,7 +64,7 @@ func (h HandlerPayloadMarshal) MarshalPayload(w io.Writer, v interface{}) error
metadata.ClientInfo{}, metadata.ClientInfo{},
request.Handlers{}, request.Handlers{},
nil, nil,
&request.Operation{HTTPMethod: "GET"}, &request.Operation{HTTPMethod: "PUT"},
v, v,
nil, nil,
) )

View File

@ -0,0 +1,49 @@
package protocol
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
)
// RequireHTTPMinProtocol request handler is used to enforce that
// the target endpoint supports the given major and minor HTTP protocol version.
type RequireHTTPMinProtocol struct {
Major, Minor int
}
// Handler will mark the request.Request with an error if the
// target endpoint did not connect with the required HTTP protocol
// major and minor version.
func (p RequireHTTPMinProtocol) Handler(r *request.Request) {
if r.Error != nil || r.HTTPResponse == nil {
return
}
if !strings.HasPrefix(r.HTTPResponse.Proto, "HTTP") {
r.Error = newMinHTTPProtoError(p.Major, p.Minor, r)
}
if r.HTTPResponse.ProtoMajor < p.Major || r.HTTPResponse.ProtoMinor < p.Minor {
r.Error = newMinHTTPProtoError(p.Major, p.Minor, r)
}
}
// ErrCodeMinimumHTTPProtocolError error code is returned when the target endpoint
// did not match the required HTTP major and minor protocol version.
const ErrCodeMinimumHTTPProtocolError = "MinimumHTTPProtocolError"
func newMinHTTPProtoError(major, minor int, r *request.Request) error {
return awserr.NewRequestFailure(
awserr.New("MinimumHTTPProtocolError",
fmt.Sprintf(
"operation requires minimum HTTP protocol of HTTP/%d.%d, but was %s",
major, minor, r.HTTPResponse.Proto,
),
nil,
),
r.HTTPResponse.StatusCode, r.RequestID,
)
}

View File

@ -1,7 +1,7 @@
// Package query provides serialization of AWS query requests, and responses. // Package query provides serialization of AWS query requests, and responses.
package query package query
//go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/query.json build_test.go //go:generate go run -tags codegen ../../../private/model/cli/gen-protocol-tests ../../../models/protocol_tests/input/query.json build_test.go
import ( import (
"net/url" "net/url"

View File

@ -1,6 +1,6 @@
package query package query
//go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/query.json unmarshal_test.go //go:generate go run -tags codegen ../../../private/model/cli/gen-protocol-tests ../../../models/protocol_tests/output/query.json unmarshal_test.go
import ( import (
"encoding/xml" "encoding/xml"

View File

@ -15,6 +15,7 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
awsStrings "github.com/aws/aws-sdk-go/internal/strings"
"github.com/aws/aws-sdk-go/private/protocol" "github.com/aws/aws-sdk-go/private/protocol"
) )
@ -28,7 +29,9 @@ var UnmarshalMetaHandler = request.NamedHandler{Name: "awssdk.rest.UnmarshalMeta
func Unmarshal(r *request.Request) { func Unmarshal(r *request.Request) {
if r.DataFilled() { if r.DataFilled() {
v := reflect.Indirect(reflect.ValueOf(r.Data)) v := reflect.Indirect(reflect.ValueOf(r.Data))
unmarshalBody(r, v) if err := unmarshalBody(r, v); err != nil {
r.Error = err
}
} }
} }
@ -40,12 +43,21 @@ func UnmarshalMeta(r *request.Request) {
r.RequestID = r.HTTPResponse.Header.Get("X-Amz-Request-Id") r.RequestID = r.HTTPResponse.Header.Get("X-Amz-Request-Id")
} }
if r.DataFilled() { if r.DataFilled() {
v := reflect.Indirect(reflect.ValueOf(r.Data)) if err := UnmarshalResponse(r.HTTPResponse, r.Data, aws.BoolValue(r.Config.LowerCaseHeaderMaps)); err != nil {
unmarshalLocationElements(r, v) r.Error = err
}
} }
} }
func unmarshalBody(r *request.Request, v reflect.Value) { // UnmarshalResponse attempts to unmarshal the REST response headers to
// the data type passed in. The type must be a pointer. An error is returned
// with any error unmarshaling the response into the target datatype.
func UnmarshalResponse(resp *http.Response, data interface{}, lowerCaseHeaderMaps bool) error {
v := reflect.Indirect(reflect.ValueOf(data))
return unmarshalLocationElements(resp, v, lowerCaseHeaderMaps)
}
func unmarshalBody(r *request.Request, v reflect.Value) error {
if field, ok := v.Type().FieldByName("_"); ok { if field, ok := v.Type().FieldByName("_"); ok {
if payloadName := field.Tag.Get("payload"); payloadName != "" { if payloadName := field.Tag.Get("payload"); payloadName != "" {
pfield, _ := v.Type().FieldByName(payloadName) pfield, _ := v.Type().FieldByName(payloadName)
@ -57,35 +69,38 @@ func unmarshalBody(r *request.Request, v reflect.Value) {
defer r.HTTPResponse.Body.Close() defer r.HTTPResponse.Body.Close()
b, err := ioutil.ReadAll(r.HTTPResponse.Body) b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil { if err != nil {
r.Error = awserr.New(request.ErrCodeSerialization, "failed to decode REST response", err) return awserr.New(request.ErrCodeSerialization, "failed to decode REST response", err)
} else {
payload.Set(reflect.ValueOf(b))
} }
payload.Set(reflect.ValueOf(b))
case *string: case *string:
defer r.HTTPResponse.Body.Close() defer r.HTTPResponse.Body.Close()
b, err := ioutil.ReadAll(r.HTTPResponse.Body) b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil { if err != nil {
r.Error = awserr.New(request.ErrCodeSerialization, "failed to decode REST response", err) return awserr.New(request.ErrCodeSerialization, "failed to decode REST response", err)
} else { }
str := string(b) str := string(b)
payload.Set(reflect.ValueOf(&str)) payload.Set(reflect.ValueOf(&str))
}
default: default:
switch payload.Type().String() { switch payload.Type().String() {
case "io.ReadCloser": case "io.ReadCloser":
payload.Set(reflect.ValueOf(r.HTTPResponse.Body)) payload.Set(reflect.ValueOf(r.HTTPResponse.Body))
case "io.ReadSeeker": case "io.ReadSeeker":
b, err := ioutil.ReadAll(r.HTTPResponse.Body) b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil { if err != nil {
r.Error = awserr.New(request.ErrCodeSerialization, return awserr.New(request.ErrCodeSerialization,
"failed to read response body", err) "failed to read response body", err)
return
} }
payload.Set(reflect.ValueOf(ioutil.NopCloser(bytes.NewReader(b)))) payload.Set(reflect.ValueOf(ioutil.NopCloser(bytes.NewReader(b))))
default: default:
io.Copy(ioutil.Discard, r.HTTPResponse.Body) io.Copy(ioutil.Discard, r.HTTPResponse.Body)
defer r.HTTPResponse.Body.Close() r.HTTPResponse.Body.Close()
r.Error = awserr.New(request.ErrCodeSerialization, return awserr.New(request.ErrCodeSerialization,
"failed to decode REST response", "failed to decode REST response",
fmt.Errorf("unknown payload type %s", payload.Type())) fmt.Errorf("unknown payload type %s", payload.Type()))
} }
@ -94,9 +109,11 @@ func unmarshalBody(r *request.Request, v reflect.Value) {
} }
} }
} }
return nil
} }
func unmarshalLocationElements(r *request.Request, v reflect.Value) { func unmarshalLocationElements(resp *http.Response, v reflect.Value, lowerCaseHeaderMaps bool) error {
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
m, field := v.Field(i), v.Type().Field(i) m, field := v.Field(i), v.Type().Field(i)
if n := field.Name; n[0:1] == strings.ToLower(n[0:1]) { if n := field.Name; n[0:1] == strings.ToLower(n[0:1]) {
@ -111,26 +128,25 @@ func unmarshalLocationElements(r *request.Request, v reflect.Value) {
switch field.Tag.Get("location") { switch field.Tag.Get("location") {
case "statusCode": case "statusCode":
unmarshalStatusCode(m, r.HTTPResponse.StatusCode) unmarshalStatusCode(m, resp.StatusCode)
case "header": case "header":
err := unmarshalHeader(m, r.HTTPResponse.Header.Get(name), field.Tag) err := unmarshalHeader(m, resp.Header.Get(name), field.Tag)
if err != nil { if err != nil {
r.Error = awserr.New(request.ErrCodeSerialization, "failed to decode REST response", err) return awserr.New(request.ErrCodeSerialization, "failed to decode REST response", err)
break
} }
case "headers": case "headers":
prefix := field.Tag.Get("locationName") prefix := field.Tag.Get("locationName")
err := unmarshalHeaderMap(m, r.HTTPResponse.Header, prefix) err := unmarshalHeaderMap(m, resp.Header, prefix, lowerCaseHeaderMaps)
if err != nil { if err != nil {
r.Error = awserr.New(request.ErrCodeSerialization, "failed to decode REST response", err) awserr.New(request.ErrCodeSerialization, "failed to decode REST response", err)
break
} }
} }
} }
if r.Error != nil {
return
}
} }
return nil
} }
func unmarshalStatusCode(v reflect.Value, statusCode int) { func unmarshalStatusCode(v reflect.Value, statusCode int) {
@ -145,7 +161,7 @@ func unmarshalStatusCode(v reflect.Value, statusCode int) {
} }
} }
func unmarshalHeaderMap(r reflect.Value, headers http.Header, prefix string) error { func unmarshalHeaderMap(r reflect.Value, headers http.Header, prefix string, normalize bool) error {
if len(headers) == 0 { if len(headers) == 0 {
return nil return nil
} }
@ -153,8 +169,12 @@ func unmarshalHeaderMap(r reflect.Value, headers http.Header, prefix string) err
case map[string]*string: // we only support string map value types case map[string]*string: // we only support string map value types
out := map[string]*string{} out := map[string]*string{}
for k, v := range headers { for k, v := range headers {
if awsStrings.HasPrefixFold(k, prefix) {
if normalize == true {
k = strings.ToLower(k)
} else {
k = http.CanonicalHeaderKey(k) k = http.CanonicalHeaderKey(k)
if strings.HasPrefix(strings.ToLower(k), strings.ToLower(prefix)) { }
out[k[len(prefix):]] = &v[0] out[k[len(prefix):]] = &v[0]
} }
} }

View File

@ -2,8 +2,8 @@
// requests and responses. // requests and responses.
package restxml package restxml
//go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/rest-xml.json build_test.go //go:generate go run -tags codegen ../../../private/model/cli/gen-protocol-tests ../../../models/protocol_tests/input/rest-xml.json build_test.go
//go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/rest-xml.json unmarshal_test.go //go:generate go run -tags codegen ../../../private/model/cli/gen-protocol-tests ../../../models/protocol_tests/output/rest-xml.json unmarshal_test.go
import ( import (
"bytes" "bytes"

View File

@ -27,8 +27,8 @@ const (
// RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z // RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z
ISO8601TimeFormat = "2006-01-02T15:04:05.999999999Z" ISO8601TimeFormat = "2006-01-02T15:04:05.999999999Z"
// This format is used for output time without seconds precision // This format is used for output time with fractional second precision up to milliseconds
ISO8601OutputTimeFormat = "2006-01-02T15:04:05Z" ISO8601OutputTimeFormat = "2006-01-02T15:04:05.999999999Z"
) )
// IsKnownTimestampFormat returns if the timestamp format name // IsKnownTimestampFormat returns if the timestamp format name
@ -48,7 +48,7 @@ func IsKnownTimestampFormat(name string) bool {
// FormatTime returns a string value of the time. // FormatTime returns a string value of the time.
func FormatTime(name string, t time.Time) string { func FormatTime(name string, t time.Time) string {
t = t.UTC() t = t.UTC().Truncate(time.Millisecond)
switch name { switch name {
case RFC822TimeFormatName: case RFC822TimeFormatName:
@ -56,7 +56,8 @@ func FormatTime(name string, t time.Time) string {
case ISO8601TimeFormatName: case ISO8601TimeFormatName:
return t.Format(ISO8601OutputTimeFormat) return t.Format(ISO8601OutputTimeFormat)
case UnixTimeFormatName: case UnixTimeFormatName:
return strconv.FormatInt(t.Unix(), 10) ms := t.UnixNano() / int64(time.Millisecond)
return strconv.FormatFloat(float64(ms)/1e3, 'f', -1, 64)
default: default:
panic("unknown timestamp format name, " + name) panic("unknown timestamp format name, " + name)
} }

View File

@ -19,3 +19,9 @@ func UnmarshalDiscardBody(r *request.Request) {
io.Copy(ioutil.Discard, r.HTTPResponse.Body) io.Copy(ioutil.Discard, r.HTTPResponse.Body)
r.HTTPResponse.Body.Close() r.HTTPResponse.Body.Close()
} }
// ResponseMetadata provides the SDK response metadata attributes.
type ResponseMetadata struct {
StatusCode int
RequestID string
}

View File

@ -0,0 +1,65 @@
package protocol
import (
"net/http"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
)
// UnmarshalErrorHandler provides unmarshaling errors API response errors for
// both typed and untyped errors.
type UnmarshalErrorHandler struct {
unmarshaler ErrorUnmarshaler
}
// ErrorUnmarshaler is an abstract interface for concrete implementations to
// unmarshal protocol specific response errors.
type ErrorUnmarshaler interface {
UnmarshalError(*http.Response, ResponseMetadata) (error, error)
}
// NewUnmarshalErrorHandler returns an UnmarshalErrorHandler
// initialized for the set of exception names to the error unmarshalers
func NewUnmarshalErrorHandler(unmarshaler ErrorUnmarshaler) *UnmarshalErrorHandler {
return &UnmarshalErrorHandler{
unmarshaler: unmarshaler,
}
}
// UnmarshalErrorHandlerName is the name of the named handler.
const UnmarshalErrorHandlerName = "awssdk.protocol.UnmarshalError"
// NamedHandler returns a NamedHandler for the unmarshaler using the set of
// errors the unmarshaler was initialized for.
func (u *UnmarshalErrorHandler) NamedHandler() request.NamedHandler {
return request.NamedHandler{
Name: UnmarshalErrorHandlerName,
Fn: u.UnmarshalError,
}
}
// UnmarshalError will attempt to unmarshal the API response's error message
// into either a generic SDK error type, or a typed error corresponding to the
// errors exception name.
func (u *UnmarshalErrorHandler) UnmarshalError(r *request.Request) {
defer r.HTTPResponse.Body.Close()
respMeta := ResponseMetadata{
StatusCode: r.HTTPResponse.StatusCode,
RequestID: r.RequestID,
}
v, err := u.unmarshaler.UnmarshalError(r.HTTPResponse, respMeta)
if err != nil {
r.Error = awserr.NewRequestFailure(
awserr.New(request.ErrCodeSerialization,
"failed to unmarshal response error", err),
respMeta.StatusCode,
respMeta.RequestID,
)
return
}
r.Error = v
}

View File

@ -8,6 +8,7 @@ import (
"reflect" "reflect"
"sort" "sort"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/aws/aws-sdk-go/private/protocol" "github.com/aws/aws-sdk-go/private/protocol"
@ -60,6 +61,14 @@ func (b *xmlBuilder) buildValue(value reflect.Value, current *XMLNode, tag refle
return nil return nil
} }
xml := tag.Get("xml")
if len(xml) != 0 {
name := strings.SplitAfterN(xml, ",", 2)[0]
if name == "-" {
return nil
}
}
t := tag.Get("type") t := tag.Get("type")
if t == "" { if t == "" {
switch value.Kind() { switch value.Kind() {

View File

@ -64,6 +64,14 @@ func UnmarshalXML(v interface{}, d *xml.Decoder, wrapper string) error {
// parse deserializes any value from the XMLNode. The type tag is used to infer the type, or reflect // parse deserializes any value from the XMLNode. The type tag is used to infer the type, or reflect
// will be used to determine the type from r. // will be used to determine the type from r.
func parse(r reflect.Value, node *XMLNode, tag reflect.StructTag) error { func parse(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
xml := tag.Get("xml")
if len(xml) != 0 {
name := strings.SplitAfterN(xml, ",", 2)[0]
if name == "-" {
return nil
}
}
rtype := r.Type() rtype := r.Type()
if rtype.Kind() == reflect.Ptr { if rtype.Kind() == reflect.Ptr {
rtype = rtype.Elem() // check kind of actual element type rtype = rtype.Elem() // check kind of actual element type

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,6 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/internal/sdkio"
) )
const ( const (
@ -25,30 +24,6 @@ const (
appendMD5TxEncoding = "append-md5" appendMD5TxEncoding = "append-md5"
) )
// contentMD5 computes and sets the HTTP Content-MD5 header for requests that
// require it.
func contentMD5(r *request.Request) {
h := md5.New()
if !aws.IsReaderSeekable(r.Body) {
if r.Config.Logger != nil {
r.Config.Logger.Log(fmt.Sprintf(
"Unable to compute Content-MD5 for unseekable body, S3.%s",
r.Operation.Name))
}
return
}
if _, err := copySeekableBody(h, r.Body); err != nil {
r.Error = awserr.New("ContentMD5", "failed to compute body MD5", err)
return
}
// encode the md5 checksum in base64 and set the request header.
v := base64.StdEncoding.EncodeToString(h.Sum(nil))
r.HTTPRequest.Header.Set(contentMD5Header, v)
}
// computeBodyHashes will add Content MD5 and Content Sha256 hashes to the // computeBodyHashes will add Content MD5 and Content Sha256 hashes to the
// request. If the body is not seekable or S3DisableContentMD5Validation set // request. If the body is not seekable or S3DisableContentMD5Validation set
// this handler will be ignored. // this handler will be ignored.
@ -90,7 +65,7 @@ func computeBodyHashes(r *request.Request) {
dst = io.MultiWriter(hashers...) dst = io.MultiWriter(hashers...)
} }
if _, err := copySeekableBody(dst, r.Body); err != nil { if _, err := aws.CopySeekableBody(dst, r.Body); err != nil {
r.Error = awserr.New("BodyHashError", "failed to compute body hashes", err) r.Error = awserr.New("BodyHashError", "failed to compute body hashes", err)
return return
} }
@ -119,28 +94,6 @@ const (
sha256HexEncLen = sha256.Size * 2 // hex.EncodedLen sha256HexEncLen = sha256.Size * 2 // hex.EncodedLen
) )
func copySeekableBody(dst io.Writer, src io.ReadSeeker) (int64, error) {
curPos, err := src.Seek(0, sdkio.SeekCurrent)
if err != nil {
return 0, err
}
// hash the body. seek back to the first position after reading to reset
// the body for transmission. copy errors may be assumed to be from the
// body.
n, err := io.Copy(dst, src)
if err != nil {
return n, err
}
_, err = src.Seek(curPos, sdkio.SeekStart)
if err != nil {
return n, err
}
return n, nil
}
// Adds the x-amz-te: append_md5 header to the request. This requests the service // Adds the x-amz-te: append_md5 header to the request. This requests the service
// responds with a trailing MD5 checksum. // responds with a trailing MD5 checksum.
// //

View File

@ -4,6 +4,7 @@ import (
"github.com/aws/aws-sdk-go/aws/client" "github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/internal/s3err" "github.com/aws/aws-sdk-go/internal/s3err"
"github.com/aws/aws-sdk-go/service/s3/internal/arn"
) )
func init() { func init() {
@ -13,7 +14,7 @@ func init() {
func defaultInitClientFn(c *client.Client) { func defaultInitClientFn(c *client.Client) {
// Support building custom endpoints based on config // Support building custom endpoints based on config
c.Handlers.Build.PushFront(updateEndpointForS3Config) c.Handlers.Build.PushFront(endpointHandler)
// Require SSL when using SSE keys // Require SSL when using SSE keys
c.Handlers.Validate.PushBack(validateSSERequiresSSL) c.Handlers.Validate.PushBack(validateSSERequiresSSL)
@ -27,17 +28,11 @@ func defaultInitClientFn(c *client.Client) {
} }
func defaultInitRequestFn(r *request.Request) { func defaultInitRequestFn(r *request.Request) {
// Add reuest handlers for specific platforms. // Add request handlers for specific platforms.
// e.g. 100-continue support for PUT requests using Go 1.6 // e.g. 100-continue support for PUT requests using Go 1.6
platformRequestHandlers(r) platformRequestHandlers(r)
switch r.Operation.Name { switch r.Operation.Name {
case opPutBucketCors, opPutBucketLifecycle, opPutBucketPolicy,
opPutBucketTagging, opDeleteObjects, opPutBucketLifecycleConfiguration,
opPutObjectLegalHold, opPutObjectRetention, opPutObjectLockConfiguration,
opPutBucketReplication:
// These S3 operations require Content-MD5 to be set
r.Handlers.Build.PushBack(contentMD5)
case opGetBucketLocation: case opGetBucketLocation:
// GetBucketLocation has custom parsing logic // GetBucketLocation has custom parsing logic
r.Handlers.Unmarshal.PushFront(buildGetBucketLocation) r.Handlers.Unmarshal.PushFront(buildGetBucketLocation)
@ -73,3 +68,8 @@ type sseCustomerKeyGetter interface {
type copySourceSSECustomerKeyGetter interface { type copySourceSSECustomerKeyGetter interface {
getCopySourceSSECustomerKey() string getCopySourceSSECustomerKey() string
} }
type endpointARNGetter interface {
getEndpointARN() (arn.Resource, error)
hasEndpointARN() bool
}

View File

@ -104,19 +104,6 @@
// content from S3. The Encryption and Decryption clients can be used concurrently // content from S3. The Encryption and Decryption clients can be used concurrently
// once the client is created. // once the client is created.
// //
// sess := session.Must(session.NewSession())
//
// // Create the decryption client.
// svc := s3crypto.NewDecryptionClient(sess)
//
// // The object will be downloaded from S3 and decrypted locally. By metadata
// // about the object's encryption will instruct the decryption client how
// // decrypt the content of the object. By default KMS is used for keys.
// result, err := svc.GetObject(&s3.GetObjectInput {
// Bucket: aws.String(myBucket),
// Key: aws.String(myKey),
// })
//
// See the s3crypto package documentation for more information. // See the s3crypto package documentation for more information.
// https://docs.aws.amazon.com/sdk-for-go/api/service/s3/s3crypto/ // https://docs.aws.amazon.com/sdk-for-go/api/service/s3/s3crypto/
// //

233
vendor/github.com/aws/aws-sdk-go/service/s3/endpoint.go generated vendored Normal file
View File

@ -0,0 +1,233 @@
package s3
import (
"net/url"
"strings"
"github.com/aws/aws-sdk-go/aws"
awsarn "github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol"
"github.com/aws/aws-sdk-go/service/s3/internal/arn"
)
// Used by shapes with members decorated as endpoint ARN.
func parseEndpointARN(v string) (arn.Resource, error) {
return arn.ParseResource(v, accessPointResourceParser)
}
func accessPointResourceParser(a awsarn.ARN) (arn.Resource, error) {
resParts := arn.SplitResource(a.Resource)
switch resParts[0] {
case "accesspoint":
return arn.ParseAccessPointResource(a, resParts[1:])
default:
return nil, arn.InvalidARNError{ARN: a, Reason: "unknown resource type"}
}
}
func endpointHandler(req *request.Request) {
endpoint, ok := req.Params.(endpointARNGetter)
if !ok || !endpoint.hasEndpointARN() {
updateBucketEndpointFromParams(req)
return
}
resource, err := endpoint.getEndpointARN()
if err != nil {
req.Error = newInvalidARNError(nil, err)
return
}
resReq := resourceRequest{
Resource: resource,
Request: req,
}
if resReq.IsCrossPartition() {
req.Error = newClientPartitionMismatchError(resource,
req.ClientInfo.PartitionID, aws.StringValue(req.Config.Region), nil)
return
}
if !resReq.AllowCrossRegion() && resReq.IsCrossRegion() {
req.Error = newClientRegionMismatchError(resource,
req.ClientInfo.PartitionID, aws.StringValue(req.Config.Region), nil)
return
}
if resReq.HasCustomEndpoint() {
req.Error = newInvalidARNWithCustomEndpointError(resource, nil)
return
}
switch tv := resource.(type) {
case arn.AccessPointARN:
err = updateRequestAccessPointEndpoint(req, tv)
if err != nil {
req.Error = err
}
default:
req.Error = newInvalidARNError(resource, nil)
}
}
type resourceRequest struct {
Resource arn.Resource
Request *request.Request
}
func (r resourceRequest) ARN() awsarn.ARN {
return r.Resource.GetARN()
}
func (r resourceRequest) AllowCrossRegion() bool {
return aws.BoolValue(r.Request.Config.S3UseARNRegion)
}
func (r resourceRequest) UseFIPS() bool {
return isFIPS(aws.StringValue(r.Request.Config.Region))
}
func (r resourceRequest) IsCrossPartition() bool {
return r.Request.ClientInfo.PartitionID != r.Resource.GetARN().Partition
}
func (r resourceRequest) IsCrossRegion() bool {
return isCrossRegion(r.Request, r.Resource.GetARN().Region)
}
func (r resourceRequest) HasCustomEndpoint() bool {
return len(aws.StringValue(r.Request.Config.Endpoint)) > 0
}
func isFIPS(clientRegion string) bool {
return strings.HasPrefix(clientRegion, "fips-") || strings.HasSuffix(clientRegion, "-fips")
}
func isCrossRegion(req *request.Request, otherRegion string) bool {
return req.ClientInfo.SigningRegion != otherRegion
}
func updateBucketEndpointFromParams(r *request.Request) {
bucket, ok := bucketNameFromReqParams(r.Params)
if !ok {
// Ignore operation requests if the bucket name was not provided
// if this is an input validation error the validation handler
// will report it.
return
}
updateEndpointForS3Config(r, bucket)
}
func updateRequestAccessPointEndpoint(req *request.Request, accessPoint arn.AccessPointARN) error {
// Accelerate not supported
if aws.BoolValue(req.Config.S3UseAccelerate) {
return newClientConfiguredForAccelerateError(accessPoint,
req.ClientInfo.PartitionID, aws.StringValue(req.Config.Region), nil)
}
// Ignore the disable host prefix for access points since custom endpoints
// are not supported.
req.Config.DisableEndpointHostPrefix = aws.Bool(false)
if err := accessPointEndpointBuilder(accessPoint).Build(req); err != nil {
return err
}
removeBucketFromPath(req.HTTPRequest.URL)
return nil
}
func removeBucketFromPath(u *url.URL) {
u.Path = strings.Replace(u.Path, "/{Bucket}", "", -1)
if u.Path == "" {
u.Path = "/"
}
}
type accessPointEndpointBuilder arn.AccessPointARN
const (
accessPointPrefixLabel = "accesspoint"
accountIDPrefixLabel = "accountID"
accesPointPrefixTemplate = "{" + accessPointPrefixLabel + "}-{" + accountIDPrefixLabel + "}."
)
func (a accessPointEndpointBuilder) Build(req *request.Request) error {
resolveRegion := arn.AccessPointARN(a).Region
cfgRegion := aws.StringValue(req.Config.Region)
if isFIPS(cfgRegion) {
if aws.BoolValue(req.Config.S3UseARNRegion) && isCrossRegion(req, resolveRegion) {
// FIPS with cross region is not supported, the SDK must fail
// because there is no well defined method for SDK to construct a
// correct FIPS endpoint.
return newClientConfiguredForCrossRegionFIPSError(arn.AccessPointARN(a),
req.ClientInfo.PartitionID, cfgRegion, nil)
}
resolveRegion = cfgRegion
}
endpoint, err := resolveRegionalEndpoint(req, resolveRegion)
if err != nil {
return newFailedToResolveEndpointError(arn.AccessPointARN(a),
req.ClientInfo.PartitionID, cfgRegion, err)
}
if err = updateRequestEndpoint(req, endpoint.URL); err != nil {
return err
}
const serviceEndpointLabel = "s3-accesspoint"
// dualstack provided by endpoint resolver
cfgHost := req.HTTPRequest.URL.Host
if strings.HasPrefix(cfgHost, "s3") {
req.HTTPRequest.URL.Host = serviceEndpointLabel + cfgHost[2:]
}
protocol.HostPrefixBuilder{
Prefix: accesPointPrefixTemplate,
LabelsFn: a.hostPrefixLabelValues,
}.Build(req)
req.ClientInfo.SigningName = endpoint.SigningName
req.ClientInfo.SigningRegion = endpoint.SigningRegion
err = protocol.ValidateEndpointHost(req.Operation.Name, req.HTTPRequest.URL.Host)
if err != nil {
return newInvalidARNError(arn.AccessPointARN(a), err)
}
return nil
}
func (a accessPointEndpointBuilder) hostPrefixLabelValues() map[string]string {
return map[string]string{
accessPointPrefixLabel: arn.AccessPointARN(a).AccessPointName,
accountIDPrefixLabel: arn.AccessPointARN(a).AccountID,
}
}
func resolveRegionalEndpoint(r *request.Request, region string) (endpoints.ResolvedEndpoint, error) {
return r.Config.EndpointResolver.EndpointFor(EndpointsID, region, func(opts *endpoints.Options) {
opts.DisableSSL = aws.BoolValue(r.Config.DisableSSL)
opts.UseDualStack = aws.BoolValue(r.Config.UseDualStack)
opts.S3UsEast1RegionalEndpoint = endpoints.RegionalS3UsEast1Endpoint
})
}
func updateRequestEndpoint(r *request.Request, endpoint string) (err error) {
endpoint = endpoints.AddScheme(endpoint, aws.BoolValue(r.Config.DisableSSL))
r.HTTPRequest.URL, err = url.Parse(endpoint + r.Operation.HTTPPath)
if err != nil {
return awserr.New(request.ErrCodeSerialization,
"failed to parse endpoint URL", err)
}
return nil
}

View File

@ -0,0 +1,151 @@
package s3
import (
"fmt"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3/internal/arn"
)
const (
invalidARNErrorErrCode = "InvalidARNError"
configurationErrorErrCode = "ConfigurationError"
)
type invalidARNError struct {
message string
resource arn.Resource
origErr error
}
func (e invalidARNError) Error() string {
var extra string
if e.resource != nil {
extra = "ARN: " + e.resource.String()
}
return awserr.SprintError(e.Code(), e.Message(), extra, e.origErr)
}
func (e invalidARNError) Code() string {
return invalidARNErrorErrCode
}
func (e invalidARNError) Message() string {
return e.message
}
func (e invalidARNError) OrigErr() error {
return e.origErr
}
func newInvalidARNError(resource arn.Resource, err error) invalidARNError {
return invalidARNError{
message: "invalid ARN",
origErr: err,
resource: resource,
}
}
func newInvalidARNWithCustomEndpointError(resource arn.Resource, err error) invalidARNError {
return invalidARNError{
message: "resource ARN not supported with custom client endpoints",
origErr: err,
resource: resource,
}
}
// ARN not supported for the target partition
func newInvalidARNWithUnsupportedPartitionError(resource arn.Resource, err error) invalidARNError {
return invalidARNError{
message: "resource ARN not supported for the target ARN partition",
origErr: err,
resource: resource,
}
}
type configurationError struct {
message string
resource arn.Resource
clientPartitionID string
clientRegion string
origErr error
}
func (e configurationError) Error() string {
extra := fmt.Sprintf("ARN: %s, client partition: %s, client region: %s",
e.resource, e.clientPartitionID, e.clientRegion)
return awserr.SprintError(e.Code(), e.Message(), extra, e.origErr)
}
func (e configurationError) Code() string {
return configurationErrorErrCode
}
func (e configurationError) Message() string {
return e.message
}
func (e configurationError) OrigErr() error {
return e.origErr
}
func newClientPartitionMismatchError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
return configurationError{
message: "client partition does not match provided ARN partition",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
func newClientRegionMismatchError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
return configurationError{
message: "client region does not match provided ARN region",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
func newFailedToResolveEndpointError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
return configurationError{
message: "endpoint resolver failed to find an endpoint for the provided ARN region",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
func newClientConfiguredForFIPSError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
return configurationError{
message: "client configured for fips but cross-region resource ARN provided",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
func newClientConfiguredForAccelerateError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
return configurationError{
message: "client configured for S3 Accelerate but is supported with resource ARN",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
func newClientConfiguredForCrossRegionFIPSError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
return configurationError{
message: "client configured for FIPS with cross-region enabled but is supported with cross-region resource ARN",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}

View File

@ -13,6 +13,12 @@ const (
// ErrCodeBucketAlreadyOwnedByYou for service response error code // ErrCodeBucketAlreadyOwnedByYou for service response error code
// "BucketAlreadyOwnedByYou". // "BucketAlreadyOwnedByYou".
//
// The bucket you tried to create already exists, and you own it. Amazon S3
// returns this error in all AWS Regions except in the North Virginia Region.
// For legacy compatibility, if you re-create an existing bucket that you already
// own in the North Virginia Region, Amazon S3 returns 200 OK and resets the
// bucket access control lists (ACLs).
ErrCodeBucketAlreadyOwnedByYou = "BucketAlreadyOwnedByYou" ErrCodeBucketAlreadyOwnedByYou = "BucketAlreadyOwnedByYou"
// ErrCodeNoSuchBucket for service response error code // ErrCodeNoSuchBucket for service response error code
@ -36,13 +42,13 @@ const (
// ErrCodeObjectAlreadyInActiveTierError for service response error code // ErrCodeObjectAlreadyInActiveTierError for service response error code
// "ObjectAlreadyInActiveTierError". // "ObjectAlreadyInActiveTierError".
// //
// This operation is not allowed against this storage tier // This operation is not allowed against this storage tier.
ErrCodeObjectAlreadyInActiveTierError = "ObjectAlreadyInActiveTierError" ErrCodeObjectAlreadyInActiveTierError = "ObjectAlreadyInActiveTierError"
// ErrCodeObjectNotInActiveTierError for service response error code // ErrCodeObjectNotInActiveTierError for service response error code
// "ObjectNotInActiveTierError". // "ObjectNotInActiveTierError".
// //
// The source object of the COPY operation is not in the active tier and is // The source object of the COPY operation is not in the active tier and is
// only stored in Amazon Glacier. // only stored in Amazon S3 Glacier.
ErrCodeObjectNotInActiveTierError = "ObjectNotInActiveTierError" ErrCodeObjectNotInActiveTierError = "ObjectNotInActiveTierError"
) )

View File

@ -30,10 +30,10 @@ var accelerateOpBlacklist = operationBlacklist{
opListBuckets, opCreateBucket, opDeleteBucket, opListBuckets, opCreateBucket, opDeleteBucket,
} }
// Request handler to automatically add the bucket name to the endpoint domain // Automatically add the bucket name to the endpoint domain
// if possible. This style of bucket is valid for all bucket names which are // if possible. This style of bucket is valid for all bucket names which are
// DNS compatible and do not contain "." // DNS compatible and do not contain "."
func updateEndpointForS3Config(r *request.Request) { func updateEndpointForS3Config(r *request.Request, bucketName string) {
forceHostStyle := aws.BoolValue(r.Config.S3ForcePathStyle) forceHostStyle := aws.BoolValue(r.Config.S3ForcePathStyle)
accelerate := aws.BoolValue(r.Config.S3UseAccelerate) accelerate := aws.BoolValue(r.Config.S3UseAccelerate)
@ -43,45 +43,29 @@ func updateEndpointForS3Config(r *request.Request) {
r.Config.Logger.Log("ERROR: aws.Config.S3UseAccelerate is not compatible with aws.Config.S3ForcePathStyle, ignoring S3ForcePathStyle.") r.Config.Logger.Log("ERROR: aws.Config.S3UseAccelerate is not compatible with aws.Config.S3ForcePathStyle, ignoring S3ForcePathStyle.")
} }
} }
updateEndpointForAccelerate(r) updateEndpointForAccelerate(r, bucketName)
} else if !forceHostStyle && r.Operation.Name != opGetBucketLocation { } else if !forceHostStyle && r.Operation.Name != opGetBucketLocation {
updateEndpointForHostStyle(r) updateEndpointForHostStyle(r, bucketName)
} }
} }
func updateEndpointForHostStyle(r *request.Request) { func updateEndpointForHostStyle(r *request.Request, bucketName string) {
bucket, ok := bucketNameFromReqParams(r.Params) if !hostCompatibleBucketName(r.HTTPRequest.URL, bucketName) {
if !ok {
// Ignore operation requests if the bucketname was not provided
// if this is an input validation error the validation handler
// will report it.
return
}
if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
// bucket name must be valid to put into the host // bucket name must be valid to put into the host
return return
} }
moveBucketToHost(r.HTTPRequest.URL, bucket) moveBucketToHost(r.HTTPRequest.URL, bucketName)
} }
var ( var (
accelElem = []byte("s3-accelerate.dualstack.") accelElem = []byte("s3-accelerate.dualstack.")
) )
func updateEndpointForAccelerate(r *request.Request) { func updateEndpointForAccelerate(r *request.Request, bucketName string) {
bucket, ok := bucketNameFromReqParams(r.Params) if !hostCompatibleBucketName(r.HTTPRequest.URL, bucketName) {
if !ok {
// Ignore operation requests if the bucketname was not provided
// if this is an input validation error the validation handler
// will report it.
return
}
if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
r.Error = awserr.New("InvalidParameterException", r.Error = awserr.New("InvalidParameterException",
fmt.Sprintf("bucket name %s is not compatible with S3 Accelerate", bucket), fmt.Sprintf("bucket name %s is not compatible with S3 Accelerate", bucketName),
nil) nil)
return return
} }
@ -106,7 +90,7 @@ func updateEndpointForAccelerate(r *request.Request) {
r.HTTPRequest.URL.Host = strings.Join(parts, ".") r.HTTPRequest.URL.Host = strings.Join(parts, ".")
moveBucketToHost(r.HTTPRequest.URL, bucket) moveBucketToHost(r.HTTPRequest.URL, bucketName)
} }
// Attempts to retrieve the bucket name from the request input parameters. // Attempts to retrieve the bucket name from the request input parameters.
@ -148,8 +132,5 @@ func dnsCompatibleBucketName(bucket string) bool {
// moveBucketToHost moves the bucket name from the URI path to URL host. // moveBucketToHost moves the bucket name from the URI path to URL host.
func moveBucketToHost(u *url.URL, bucket string) { func moveBucketToHost(u *url.URL, bucket string) {
u.Host = bucket + "." + u.Host u.Host = bucket + "." + u.Host
u.Path = strings.Replace(u.Path, "/{Bucket}", "", -1) removeBucketFromPath(u)
if u.Path == "" {
u.Path = "/"
}
} }

View File

@ -0,0 +1,45 @@
package arn
import (
"strings"
"github.com/aws/aws-sdk-go/aws/arn"
)
// AccessPointARN provides representation
type AccessPointARN struct {
arn.ARN
AccessPointName string
}
// GetARN returns the base ARN for the Access Point resource
func (a AccessPointARN) GetARN() arn.ARN {
return a.ARN
}
// ParseAccessPointResource attempts to parse the ARN's resource as an
// AccessPoint resource.
func ParseAccessPointResource(a arn.ARN, resParts []string) (AccessPointARN, error) {
if len(a.Region) == 0 {
return AccessPointARN{}, InvalidARNError{a, "region not set"}
}
if len(a.AccountID) == 0 {
return AccessPointARN{}, InvalidARNError{a, "account-id not set"}
}
if len(resParts) == 0 {
return AccessPointARN{}, InvalidARNError{a, "resource-id not set"}
}
if len(resParts) > 1 {
return AccessPointARN{}, InvalidARNError{a, "sub resource not supported"}
}
resID := resParts[0]
if len(strings.TrimSpace(resID)) == 0 {
return AccessPointARN{}, InvalidARNError{a, "resource-id not set"}
}
return AccessPointARN{
ARN: a,
AccessPointName: resID,
}, nil
}

View File

@ -0,0 +1,71 @@
package arn
import (
"strings"
"github.com/aws/aws-sdk-go/aws/arn"
)
// Resource provides the interfaces abstracting ARNs of specific resource
// types.
type Resource interface {
GetARN() arn.ARN
String() string
}
// ResourceParser provides the function for parsing an ARN's resource
// component into a typed resource.
type ResourceParser func(arn.ARN) (Resource, error)
// ParseResource parses an AWS ARN into a typed resource for the S3 API.
func ParseResource(s string, resParser ResourceParser) (resARN Resource, err error) {
a, err := arn.Parse(s)
if err != nil {
return nil, err
}
if len(a.Partition) == 0 {
return nil, InvalidARNError{a, "partition not set"}
}
if a.Service != "s3" {
return nil, InvalidARNError{a, "service is not S3"}
}
if len(a.Resource) == 0 {
return nil, InvalidARNError{a, "resource not set"}
}
return resParser(a)
}
// SplitResource splits the resource components by the ARN resource delimiters.
func SplitResource(v string) []string {
var parts []string
var offset int
for offset <= len(v) {
idx := strings.IndexAny(v[offset:], "/:")
if idx < 0 {
parts = append(parts, v[offset:])
break
}
parts = append(parts, v[offset:idx+offset])
offset += idx + 1
}
return parts
}
// IsARN returns whether the given string is an ARN
func IsARN(s string) bool {
return arn.IsARN(s)
}
// InvalidARNError provides the error for an invalid ARN error.
type InvalidARNError struct {
ARN arn.ARN
Reason string
}
func (e InvalidARNError) Error() string {
return "invalid Amazon S3 ARN, " + e.Reason + ", " + e.ARN.String()
}

View File

@ -31,7 +31,7 @@ var initRequest func(*request.Request)
const ( const (
ServiceName = "s3" // Name of service. ServiceName = "s3" // Name of service.
EndpointsID = ServiceName // ID to lookup a service endpoint with. EndpointsID = ServiceName // ID to lookup a service endpoint with.
ServiceID = "S3" // ServiceID is a unique identifer of a specific service. ServiceID = "S3" // ServiceID is a unique identifier of a specific service.
) )
// New creates a new instance of the S3 client with a session. // New creates a new instance of the S3 client with a session.
@ -39,6 +39,8 @@ const (
// aws.Config parameter to add your extra config. // aws.Config parameter to add your extra config.
// //
// Example: // Example:
// mySession := session.Must(session.NewSession())
//
// // Create a S3 client from just a session. // // Create a S3 client from just a session.
// svc := s3.New(mySession) // svc := s3.New(mySession)
// //
@ -46,11 +48,11 @@ const (
// svc := s3.New(mySession, aws.NewConfig().WithRegion("us-west-2")) // svc := s3.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *S3 { func New(p client.ConfigProvider, cfgs ...*aws.Config) *S3 {
c := p.ClientConfig(EndpointsID, cfgs...) c := p.ClientConfig(EndpointsID, cfgs...)
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion, c.SigningName) return newClient(*c.Config, c.Handlers, c.PartitionID, c.Endpoint, c.SigningRegion, c.SigningName)
} }
// newClient creates, initializes and returns a new service client instance. // newClient creates, initializes and returns a new service client instance.
func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion, signingName string) *S3 { func newClient(cfg aws.Config, handlers request.Handlers, partitionID, endpoint, signingRegion, signingName string) *S3 {
svc := &S3{ svc := &S3{
Client: client.New( Client: client.New(
cfg, cfg,
@ -59,6 +61,7 @@ func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegio
ServiceID: ServiceID, ServiceID: ServiceID,
SigningName: signingName, SigningName: signingName,
SigningRegion: signingRegion, SigningRegion: signingRegion,
PartitionID: partitionID,
Endpoint: endpoint, Endpoint: endpoint,
APIVersion: "2006-03-01", APIVersion: "2006-03-01",
}, },
@ -75,6 +78,7 @@ func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegio
svc.Handlers.UnmarshalMeta.PushBackNamed(restxml.UnmarshalMetaHandler) svc.Handlers.UnmarshalMeta.PushBackNamed(restxml.UnmarshalMetaHandler)
svc.Handlers.UnmarshalError.PushBackNamed(restxml.UnmarshalErrorHandler) svc.Handlers.UnmarshalError.PushBackNamed(restxml.UnmarshalErrorHandler)
svc.Handlers.BuildStream.PushBackNamed(restxml.BuildHandler)
svc.Handlers.UnmarshalStream.PushBackNamed(restxml.UnmarshalHandler) svc.Handlers.UnmarshalStream.PushBackNamed(restxml.UnmarshalHandler)
// Run custom client initialization if present // Run custom client initialization if present

View File

@ -69,7 +69,7 @@ func computeKeyMD5(keyHeader, keyMD5Header, key string, r *http.Request) {
return return
} }
// In backwards compatiable, the header's value is not base64 encoded, // In backwards compatible, the header's value is not base64 encoded,
// and needs to be encoded and updated by the SDK's customizations. // and needs to be encoded and updated by the SDK's customizations.
b64Key := base64.StdEncoding.EncodeToString([]byte(key)) b64Key := base64.StdEncoding.EncodeToString([]byte(key))
r.Header.Set(keyHeader, b64Key) r.Header.Set(keyHeader, b64Key)

View File

@ -2,6 +2,7 @@ package s3
import ( import (
"bytes" "bytes"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -24,17 +25,18 @@ func copyMultipartStatusOKUnmarhsalError(r *request.Request) {
r.HTTPResponse.Body = ioutil.NopCloser(body) r.HTTPResponse.Body = ioutil.NopCloser(body)
defer body.Seek(0, sdkio.SeekStart) defer body.Seek(0, sdkio.SeekStart)
if body.Len() == 0 {
// If there is no body don't attempt to parse the body.
return
}
unmarshalError(r) unmarshalError(r)
if err, ok := r.Error.(awserr.Error); ok && err != nil { if err, ok := r.Error.(awserr.Error); ok && err != nil {
if err.Code() == request.ErrCodeSerialization { if err.Code() == request.ErrCodeSerialization &&
err.OrigErr() != io.EOF {
r.Error = nil r.Error = nil
return return
} }
// if empty payload
if err.OrigErr() == io.EOF {
r.HTTPResponse.StatusCode = http.StatusInternalServerError
} else {
r.HTTPResponse.StatusCode = http.StatusServiceUnavailable r.HTTPResponse.StatusCode = http.StatusServiceUnavailable
} }
} }
}

View File

@ -1,6 +1,7 @@
package s3 package s3
import ( import (
"bytes"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io" "io"
@ -45,17 +46,24 @@ func unmarshalError(r *request.Request) {
// Attempt to parse error from body if it is known // Attempt to parse error from body if it is known
var errResp xmlErrorResponse var errResp xmlErrorResponse
err := xmlutil.UnmarshalXMLError(&errResp, r.HTTPResponse.Body) var err error
if err == io.EOF { if r.HTTPResponse.StatusCode >= 200 && r.HTTPResponse.StatusCode < 300 {
// Only capture the error if an unmarshal error occurs that is not EOF, err = s3unmarshalXMLError(&errResp, r.HTTPResponse.Body)
// because S3 might send an error without a error message which causes } else {
// the XML unmarshal to fail with EOF. err = xmlutil.UnmarshalXMLError(&errResp, r.HTTPResponse.Body)
err = nil
} }
if err != nil { if err != nil {
var errorMsg string
if err == io.EOF {
errorMsg = "empty response payload"
} else {
errorMsg = "failed to unmarshal error message"
}
r.Error = awserr.NewRequestFailure( r.Error = awserr.NewRequestFailure(
awserr.New(request.ErrCodeSerialization, awserr.New(request.ErrCodeSerialization,
"failed to unmarshal error message", err), errorMsg, err),
r.HTTPResponse.StatusCode, r.HTTPResponse.StatusCode,
r.RequestID, r.RequestID,
) )
@ -86,3 +94,21 @@ type RequestFailure interface {
// Host ID is the S3 Host ID needed for debug, and contacting support // Host ID is the S3 Host ID needed for debug, and contacting support
HostID() string HostID() string
} }
// s3unmarshalXMLError is s3 specific xml error unmarshaler
// for 200 OK errors and response payloads.
// This function differs from the xmlUtil.UnmarshalXMLError
// func. It does not ignore the EOF error and passes it up.
// Related to bug fix for `s3 200 OK response with empty payload`
func s3unmarshalXMLError(v interface{}, stream io.Reader) error {
var errBuf bytes.Buffer
body := io.TeeReader(stream, &errBuf)
err := xml.NewDecoder(body).Decode(v)
if err != nil && err != io.EOF {
return awserr.NewUnmarshalError(err,
"failed to unmarshal error message", errBuf.Bytes())
}
return err
}

View File

@ -78,6 +78,8 @@ func (c *STS) AssumeRoleRequest(input *AssumeRoleInput) (req *request.Request, o
// IAM Roles (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html) // IAM Roles (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Session Duration
//
// By default, the temporary security credentials created by AssumeRole last // By default, the temporary security credentials created by AssumeRole last
// for one hour. However, you can use the optional DurationSeconds parameter // for one hour. However, you can use the optional DurationSeconds parameter
// to specify the duration of your session. You can provide a value from 900 // to specify the duration of your session. You can provide a value from 900
@ -91,6 +93,8 @@ func (c *STS) AssumeRoleRequest(input *AssumeRoleInput) (req *request.Request, o
// URL. For more information, see Using IAM Roles (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html) // URL. For more information, see Using IAM Roles (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Permissions
//
// The temporary security credentials created by AssumeRole can be used to make // The temporary security credentials created by AssumeRole can be used to make
// API calls to any AWS service with the following exception: You cannot call // API calls to any AWS service with the following exception: You cannot call
// the AWS STS GetFederationToken or GetSessionToken API operations. // the AWS STS GetFederationToken or GetSessionToken API operations.
@ -99,7 +103,7 @@ func (c *STS) AssumeRoleRequest(input *AssumeRoleInput) (req *request.Request, o
// to this operation. You can pass a single JSON policy document to use as an // to this operation. You can pass a single JSON policy document to use as an
// inline session policy. You can also specify up to 10 managed policies to // inline session policy. You can also specify up to 10 managed policies to
// use as managed session policies. The plain text that you use for both inline // use as managed session policies. The plain text that you use for both inline
// and managed session policies shouldn't exceed 2048 characters. Passing policies // and managed session policies can't exceed 2,048 characters. Passing policies
// to this operation returns new temporary credentials. The resulting session's // to this operation returns new temporary credentials. The resulting session's
// permissions are the intersection of the role's identity-based policy and // permissions are the intersection of the role's identity-based policy and
// the session policies. You can use the role's temporary credentials in subsequent // the session policies. You can use the role's temporary credentials in subsequent
@ -131,6 +135,24 @@ func (c *STS) AssumeRoleRequest(input *AssumeRoleInput) (req *request.Request, o
// see IAM Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html) // see IAM Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Tags
//
// (Optional) You can pass tag key-value pairs to your session. These tags are
// called session tags. For more information about session tags, see Passing
// Session Tags in STS (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
//
// An administrator must grant you the permissions necessary to pass session
// tags. The administrator can also create granular permissions to allow you
// to pass only specific session tags. For more information, see Tutorial: Using
// Tags for Attribute-Based Access Control (https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_attribute-based-access-control.html)
// in the IAM User Guide.
//
// You can set the session tags as transitive. Transitive tags persist during
// role chaining. For more information, see Chaining Roles with Session Tags
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining)
// in the IAM User Guide.
//
// Using MFA with AssumeRole // Using MFA with AssumeRole
// //
// (Optional) You can include multi-factor authentication (MFA) information // (Optional) You can include multi-factor authentication (MFA) information
@ -165,9 +187,18 @@ func (c *STS) AssumeRoleRequest(input *AssumeRoleInput) (req *request.Request, o
// message describes the specific error. // message describes the specific error.
// //
// * ErrCodePackedPolicyTooLargeException "PackedPolicyTooLarge" // * ErrCodePackedPolicyTooLargeException "PackedPolicyTooLarge"
// The request was rejected because the policy document was too large. The error // The request was rejected because the total packed size of the session policies
// message describes how big the policy document is, in packed form, as a percentage // and session tags combined was too large. An AWS conversion compresses the
// of what the API allows. // session policy document, session policy ARNs, and session tags into a packed
// binary format that has a separate limit. The error message indicates by percentage
// how close the policies and tags are to the upper size limit. For more information,
// see Passing Session Tags in STS (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
//
// You could receive this error even though you meet other defined session policy
// and session tag limits. For more information, see IAM and STS Entity Character
// Limits (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html)
// in the IAM User Guide.
// //
// * ErrCodeRegionDisabledException "RegionDisabledException" // * ErrCodeRegionDisabledException "RegionDisabledException"
// STS is not activated in the requested region for the account that is being // STS is not activated in the requested region for the account that is being
@ -256,6 +287,8 @@ func (c *STS) AssumeRoleWithSAMLRequest(input *AssumeRoleWithSAMLInput) (req *re
// an access key ID, a secret access key, and a security token. Applications // an access key ID, a secret access key, and a security token. Applications
// can use these temporary security credentials to sign calls to AWS services. // can use these temporary security credentials to sign calls to AWS services.
// //
// Session Duration
//
// By default, the temporary security credentials created by AssumeRoleWithSAML // By default, the temporary security credentials created by AssumeRoleWithSAML
// last for one hour. However, you can use the optional DurationSeconds parameter // last for one hour. However, you can use the optional DurationSeconds parameter
// to specify the duration of your session. Your role session lasts for the // to specify the duration of your session. Your role session lasts for the
@ -271,6 +304,8 @@ func (c *STS) AssumeRoleWithSAMLRequest(input *AssumeRoleWithSAMLInput) (req *re
// URL. For more information, see Using IAM Roles (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html) // URL. For more information, see Using IAM Roles (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Permissions
//
// The temporary security credentials created by AssumeRoleWithSAML can be used // The temporary security credentials created by AssumeRoleWithSAML can be used
// to make API calls to any AWS service with the following exception: you cannot // to make API calls to any AWS service with the following exception: you cannot
// call the STS GetFederationToken or GetSessionToken API operations. // call the STS GetFederationToken or GetSessionToken API operations.
@ -279,7 +314,7 @@ func (c *STS) AssumeRoleWithSAMLRequest(input *AssumeRoleWithSAMLInput) (req *re
// to this operation. You can pass a single JSON policy document to use as an // to this operation. You can pass a single JSON policy document to use as an
// inline session policy. You can also specify up to 10 managed policies to // inline session policy. You can also specify up to 10 managed policies to
// use as managed session policies. The plain text that you use for both inline // use as managed session policies. The plain text that you use for both inline
// and managed session policies shouldn't exceed 2048 characters. Passing policies // and managed session policies can't exceed 2,048 characters. Passing policies
// to this operation returns new temporary credentials. The resulting session's // to this operation returns new temporary credentials. The resulting session's
// permissions are the intersection of the role's identity-based policy and // permissions are the intersection of the role's identity-based policy and
// the session policies. You can use the role's temporary credentials in subsequent // the session policies. You can use the role's temporary credentials in subsequent
@ -289,12 +324,6 @@ func (c *STS) AssumeRoleWithSAMLRequest(input *AssumeRoleWithSAMLInput) (req *re
// information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session) // information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Before your application can call AssumeRoleWithSAML, you must configure your
// SAML identity provider (IdP) to issue the claims required by AWS. Additionally,
// you must use AWS Identity and Access Management (IAM) to create a SAML provider
// entity in your AWS account that represents your identity provider. You must
// also create an IAM role that specifies this SAML provider in its trust policy.
//
// Calling AssumeRoleWithSAML does not require the use of AWS security credentials. // Calling AssumeRoleWithSAML does not require the use of AWS security credentials.
// The identity of the caller is validated by using keys in the metadata document // The identity of the caller is validated by using keys in the metadata document
// that is uploaded for the SAML provider entity for your identity provider. // that is uploaded for the SAML provider entity for your identity provider.
@ -302,8 +331,50 @@ func (c *STS) AssumeRoleWithSAMLRequest(input *AssumeRoleWithSAMLInput) (req *re
// Calling AssumeRoleWithSAML can result in an entry in your AWS CloudTrail // Calling AssumeRoleWithSAML can result in an entry in your AWS CloudTrail
// logs. The entry includes the value in the NameID element of the SAML assertion. // logs. The entry includes the value in the NameID element of the SAML assertion.
// We recommend that you use a NameIDType that is not associated with any personally // We recommend that you use a NameIDType that is not associated with any personally
// identifiable information (PII). For example, you could instead use the Persistent // identifiable information (PII). For example, you could instead use the persistent
// Identifier (urn:oasis:names:tc:SAML:2.0:nameid-format:persistent). // identifier (urn:oasis:names:tc:SAML:2.0:nameid-format:persistent).
//
// Tags
//
// (Optional) You can configure your IdP to pass attributes into your SAML assertion
// as session tags. Each session tag consists of a key name and an associated
// value. For more information about session tags, see Passing Session Tags
// in STS (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
//
// You can pass up to 50 session tags. The plain text session tag keys cant
// exceed 128 characters and the values cant exceed 256 characters. For these
// and additional limits, see IAM and STS Character Limits (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-limits.html#reference_iam-limits-entity-length)
// in the IAM User Guide.
//
// An AWS conversion compresses the passed session policies and session tags
// into a packed binary format that has a separate limit. Your request can fail
// for this limit even if your plain text meets the other requirements. The
// PackedPolicySize response element indicates by percentage how close the policies
// and tags for your request are to the upper size limit.
//
// You can pass a session tag with the same key as a tag that is attached to
// the role. When you do, session tags override the role's tags with the same
// key.
//
// An administrator must grant you the permissions necessary to pass session
// tags. The administrator can also create granular permissions to allow you
// to pass only specific session tags. For more information, see Tutorial: Using
// Tags for Attribute-Based Access Control (https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_attribute-based-access-control.html)
// in the IAM User Guide.
//
// You can set the session tags as transitive. Transitive tags persist during
// role chaining. For more information, see Chaining Roles with Session Tags
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining)
// in the IAM User Guide.
//
// SAML Configuration
//
// Before your application can call AssumeRoleWithSAML, you must configure your
// SAML identity provider (IdP) to issue the claims required by AWS. Additionally,
// you must use AWS Identity and Access Management (IAM) to create a SAML provider
// entity in your AWS account that represents your identity provider. You must
// also create an IAM role that specifies this SAML provider in its trust policy.
// //
// For more information, see the following resources: // For more information, see the following resources:
// //
@ -332,9 +403,18 @@ func (c *STS) AssumeRoleWithSAMLRequest(input *AssumeRoleWithSAMLInput) (req *re
// message describes the specific error. // message describes the specific error.
// //
// * ErrCodePackedPolicyTooLargeException "PackedPolicyTooLarge" // * ErrCodePackedPolicyTooLargeException "PackedPolicyTooLarge"
// The request was rejected because the policy document was too large. The error // The request was rejected because the total packed size of the session policies
// message describes how big the policy document is, in packed form, as a percentage // and session tags combined was too large. An AWS conversion compresses the
// of what the API allows. // session policy document, session policy ARNs, and session tags into a packed
// binary format that has a separate limit. The error message indicates by percentage
// how close the policies and tags are to the upper size limit. For more information,
// see Passing Session Tags in STS (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
//
// You could receive this error even though you meet other defined session policy
// and session tag limits. For more information, see IAM and STS Entity Character
// Limits (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html)
// in the IAM User Guide.
// //
// * ErrCodeIDPRejectedClaimException "IDPRejectedClaim" // * ErrCodeIDPRejectedClaimException "IDPRejectedClaim"
// The identity provider (IdP) reported that authentication failed. This might // The identity provider (IdP) reported that authentication failed. This might
@ -456,6 +536,8 @@ func (c *STS) AssumeRoleWithWebIdentityRequest(input *AssumeRoleWithWebIdentityI
// key ID, a secret access key, and a security token. Applications can use these // key ID, a secret access key, and a security token. Applications can use these
// temporary security credentials to sign calls to AWS service API operations. // temporary security credentials to sign calls to AWS service API operations.
// //
// Session Duration
//
// By default, the temporary security credentials created by AssumeRoleWithWebIdentity // By default, the temporary security credentials created by AssumeRoleWithWebIdentity
// last for one hour. However, you can use the optional DurationSeconds parameter // last for one hour. However, you can use the optional DurationSeconds parameter
// to specify the duration of your session. You can provide a value from 900 // to specify the duration of your session. You can provide a value from 900
@ -469,6 +551,8 @@ func (c *STS) AssumeRoleWithWebIdentityRequest(input *AssumeRoleWithWebIdentityI
// URL. For more information, see Using IAM Roles (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html) // URL. For more information, see Using IAM Roles (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Permissions
//
// The temporary security credentials created by AssumeRoleWithWebIdentity can // The temporary security credentials created by AssumeRoleWithWebIdentity can
// be used to make API calls to any AWS service with the following exception: // be used to make API calls to any AWS service with the following exception:
// you cannot call the STS GetFederationToken or GetSessionToken API operations. // you cannot call the STS GetFederationToken or GetSessionToken API operations.
@ -477,7 +561,7 @@ func (c *STS) AssumeRoleWithWebIdentityRequest(input *AssumeRoleWithWebIdentityI
// to this operation. You can pass a single JSON policy document to use as an // to this operation. You can pass a single JSON policy document to use as an
// inline session policy. You can also specify up to 10 managed policies to // inline session policy. You can also specify up to 10 managed policies to
// use as managed session policies. The plain text that you use for both inline // use as managed session policies. The plain text that you use for both inline
// and managed session policies shouldn't exceed 2048 characters. Passing policies // and managed session policies can't exceed 2,048 characters. Passing policies
// to this operation returns new temporary credentials. The resulting session's // to this operation returns new temporary credentials. The resulting session's
// permissions are the intersection of the role's identity-based policy and // permissions are the intersection of the role's identity-based policy and
// the session policies. You can use the role's temporary credentials in subsequent // the session policies. You can use the role's temporary credentials in subsequent
@ -487,6 +571,42 @@ func (c *STS) AssumeRoleWithWebIdentityRequest(input *AssumeRoleWithWebIdentityI
// information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session) // information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Tags
//
// (Optional) You can configure your IdP to pass attributes into your web identity
// token as session tags. Each session tag consists of a key name and an associated
// value. For more information about session tags, see Passing Session Tags
// in STS (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
//
// You can pass up to 50 session tags. The plain text session tag keys cant
// exceed 128 characters and the values cant exceed 256 characters. For these
// and additional limits, see IAM and STS Character Limits (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-limits.html#reference_iam-limits-entity-length)
// in the IAM User Guide.
//
// An AWS conversion compresses the passed session policies and session tags
// into a packed binary format that has a separate limit. Your request can fail
// for this limit even if your plain text meets the other requirements. The
// PackedPolicySize response element indicates by percentage how close the policies
// and tags for your request are to the upper size limit.
//
// You can pass a session tag with the same key as a tag that is attached to
// the role. When you do, the session tag overrides the role tag with the same
// key.
//
// An administrator must grant you the permissions necessary to pass session
// tags. The administrator can also create granular permissions to allow you
// to pass only specific session tags. For more information, see Tutorial: Using
// Tags for Attribute-Based Access Control (https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_attribute-based-access-control.html)
// in the IAM User Guide.
//
// You can set the session tags as transitive. Transitive tags persist during
// role chaining. For more information, see Chaining Roles with Session Tags
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining)
// in the IAM User Guide.
//
// Identities
//
// Before your application can call AssumeRoleWithWebIdentity, you must have // Before your application can call AssumeRoleWithWebIdentity, you must have
// an identity token from a supported identity provider and create a role that // an identity token from a supported identity provider and create a role that
// the application can assume. The role that your application assumes must trust // the application can assume. The role that your application assumes must trust
@ -514,8 +634,8 @@ func (c *STS) AssumeRoleWithWebIdentityRequest(input *AssumeRoleWithWebIdentityI
// * AWS SDK for iOS Developer Guide (http://aws.amazon.com/sdkforios/) and // * AWS SDK for iOS Developer Guide (http://aws.amazon.com/sdkforios/) and
// AWS SDK for Android Developer Guide (http://aws.amazon.com/sdkforandroid/). // AWS SDK for Android Developer Guide (http://aws.amazon.com/sdkforandroid/).
// These toolkits contain sample apps that show how to invoke the identity // These toolkits contain sample apps that show how to invoke the identity
// providers, and then how to use the information from these providers to // providers. The toolkits then show how to use the information from these
// get and use temporary security credentials. // providers to get and use temporary security credentials.
// //
// * Web Identity Federation with Mobile Applications (http://aws.amazon.com/articles/web-identity-federation-with-mobile-applications). // * Web Identity Federation with Mobile Applications (http://aws.amazon.com/articles/web-identity-federation-with-mobile-applications).
// This article discusses web identity federation and shows an example of // This article discusses web identity federation and shows an example of
@ -535,9 +655,18 @@ func (c *STS) AssumeRoleWithWebIdentityRequest(input *AssumeRoleWithWebIdentityI
// message describes the specific error. // message describes the specific error.
// //
// * ErrCodePackedPolicyTooLargeException "PackedPolicyTooLarge" // * ErrCodePackedPolicyTooLargeException "PackedPolicyTooLarge"
// The request was rejected because the policy document was too large. The error // The request was rejected because the total packed size of the session policies
// message describes how big the policy document is, in packed form, as a percentage // and session tags combined was too large. An AWS conversion compresses the
// of what the API allows. // session policy document, session policy ARNs, and session tags into a packed
// binary format that has a separate limit. The error message indicates by percentage
// how close the policies and tags are to the upper size limit. For more information,
// see Passing Session Tags in STS (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
//
// You could receive this error even though you meet other defined session policy
// and session tag limits. For more information, see IAM and STS Entity Character
// Limits (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html)
// in the IAM User Guide.
// //
// * ErrCodeIDPRejectedClaimException "IDPRejectedClaim" // * ErrCodeIDPRejectedClaimException "IDPRejectedClaim"
// The identity provider (IdP) reported that authentication failed. This might // The identity provider (IdP) reported that authentication failed. This might
@ -547,11 +676,11 @@ func (c *STS) AssumeRoleWithWebIdentityRequest(input *AssumeRoleWithWebIdentityI
// can also mean that the claim has expired or has been explicitly revoked. // can also mean that the claim has expired or has been explicitly revoked.
// //
// * ErrCodeIDPCommunicationErrorException "IDPCommunicationError" // * ErrCodeIDPCommunicationErrorException "IDPCommunicationError"
// The request could not be fulfilled because the non-AWS identity provider // The request could not be fulfilled because the identity provider (IDP) that
// (IDP) that was asked to verify the incoming identity token could not be reached. // was asked to verify the incoming identity token could not be reached. This
// This is often a transient error caused by network conditions. Retry the request // is often a transient error caused by network conditions. Retry the request
// a limited number of times so that you don't exceed the request rate. If the // a limited number of times so that you don't exceed the request rate. If the
// error persists, the non-AWS identity provider might be down or not responding. // error persists, the identity provider might be down or not responding.
// //
// * ErrCodeInvalidIdentityTokenException "InvalidIdentityToken" // * ErrCodeInvalidIdentityTokenException "InvalidIdentityToken"
// The web identity token that was passed could not be validated by AWS. Get // The web identity token that was passed could not be validated by AWS. Get
@ -763,7 +892,8 @@ func (c *STS) GetAccessKeyInfoRequest(input *GetAccessKeyInfoInput) (req *reques
// pull a credentials report (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html) // pull a credentials report (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html)
// to learn which IAM user owns the keys. To learn who requested the temporary // to learn which IAM user owns the keys. To learn who requested the temporary
// credentials for an ASIA access key, view the STS events in your CloudTrail // credentials for an ASIA access key, view the STS events in your CloudTrail
// logs (https://docs.aws.amazon.com/IAM/latest/UserGuide/cloudtrail-integration.html). // logs (https://docs.aws.amazon.com/IAM/latest/UserGuide/cloudtrail-integration.html)
// in the IAM User Guide.
// //
// This operation does not indicate the state of the access key. The key might // This operation does not indicate the state of the access key. The key might
// be active, inactive, or deleted. Active keys might not have permissions to // be active, inactive, or deleted. Active keys might not have permissions to
@ -850,7 +980,8 @@ func (c *STS) GetCallerIdentityRequest(input *GetCallerIdentityInput) (req *requ
// sts:GetCallerIdentity action, you can still perform this operation. Permissions // sts:GetCallerIdentity action, you can still perform this operation. Permissions
// are not required because the same information is returned when an IAM user // are not required because the same information is returned when an IAM user
// or role is denied access. To view an example response, see I Am Not Authorized // or role is denied access. To view an example response, see I Am Not Authorized
// to Perform: iam:DeleteVirtualMFADevice (https://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_general.html#troubleshoot_general_access-denied-delete-mfa). // to Perform: iam:DeleteVirtualMFADevice (https://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_general.html#troubleshoot_general_access-denied-delete-mfa)
// in the IAM User Guide.
// //
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
// with awserr.Error's Code and Message methods to get detailed information about // with awserr.Error's Code and Message methods to get detailed information about
@ -942,7 +1073,8 @@ func (c *STS) GetFederationTokenRequest(input *GetFederationTokenInput) (req *re
// or an OpenID Connect-compatible identity provider. In this case, we recommend // or an OpenID Connect-compatible identity provider. In this case, we recommend
// that you use Amazon Cognito (http://aws.amazon.com/cognito/) or AssumeRoleWithWebIdentity. // that you use Amazon Cognito (http://aws.amazon.com/cognito/) or AssumeRoleWithWebIdentity.
// For more information, see Federation Through a Web-based Identity Provider // For more information, see Federation Through a Web-based Identity Provider
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#api_assumerolewithwebidentity). // (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#api_assumerolewithwebidentity)
// in the IAM User Guide.
// //
// You can also call GetFederationToken using the security credentials of an // You can also call GetFederationToken using the security credentials of an
// AWS account root user, but we do not recommend it. Instead, we recommend // AWS account root user, but we do not recommend it. Instead, we recommend
@ -952,41 +1084,67 @@ func (c *STS) GetFederationTokenRequest(input *GetFederationTokenInput) (req *re
// Practices (https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) // Practices (https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Session duration
//
// The temporary credentials are valid for the specified duration, from 900 // The temporary credentials are valid for the specified duration, from 900
// seconds (15 minutes) up to a maximum of 129,600 seconds (36 hours). The default // seconds (15 minutes) up to a maximum of 129,600 seconds (36 hours). The default
// is 43,200 seconds (12 hours). Temporary credentials that are obtained by // session duration is 43,200 seconds (12 hours). Temporary credentials that
// using AWS account root user credentials have a maximum duration of 3,600 // are obtained by using AWS account root user credentials have a maximum duration
// seconds (1 hour). // of 3,600 seconds (1 hour).
//
// The temporary security credentials created by GetFederationToken can be used
// to make API calls to any AWS service with the following exceptions:
//
// * You cannot use these credentials to call any IAM API operations.
//
// * You cannot call any STS API operations except GetCallerIdentity.
// //
// Permissions // Permissions
// //
// You can use the temporary credentials created by GetFederationToken in any
// AWS service except the following:
//
// * You cannot call any IAM operations using the AWS CLI or the AWS API.
//
// * You cannot call any STS operations except GetCallerIdentity.
//
// You must pass an inline or managed session policy (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session) // You must pass an inline or managed session policy (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// to this operation. You can pass a single JSON policy document to use as an // to this operation. You can pass a single JSON policy document to use as an
// inline session policy. You can also specify up to 10 managed policies to // inline session policy. You can also specify up to 10 managed policies to
// use as managed session policies. The plain text that you use for both inline // use as managed session policies. The plain text that you use for both inline
// and managed session policies shouldn't exceed 2048 characters. // and managed session policies can't exceed 2,048 characters.
// //
// Though the session policy parameters are optional, if you do not pass a policy, // Though the session policy parameters are optional, if you do not pass a policy,
// then the resulting federated user session has no permissions. The only exception // then the resulting federated user session has no permissions. When you pass
// is when the credentials are used to access a resource that has a resource-based // session policies, the session permissions are the intersection of the IAM
// policy that specifically references the federated user session in the Principal // user policies and the session policies that you pass. This gives you a way
// element of the policy. When you pass session policies, the session permissions // to further restrict the permissions for a federated user. You cannot use
// are the intersection of the IAM user policies and the session policies that // session policies to grant more permissions than those that are defined in
// you pass. This gives you a way to further restrict the permissions for a // the permissions policy of the IAM user. For more information, see Session
// federated user. You cannot use session policies to grant more permissions // Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// than those that are defined in the permissions policy of the IAM user. For
// more information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// in the IAM User Guide. For information about using GetFederationToken to // in the IAM User Guide. For information about using GetFederationToken to
// create temporary security credentials, see GetFederationToken—Federation // create temporary security credentials, see GetFederationToken—Federation
// Through a Custom Identity Broker (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#api_getfederationtoken). // Through a Custom Identity Broker (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#api_getfederationtoken).
// //
// You can use the credentials to access a resource that has a resource-based
// policy. If that policy specifically references the federated user session
// in the Principal element of the policy, the session has the permissions allowed
// by the policy. These permissions are granted in addition to the permissions
// granted by the session policies.
//
// Tags
//
// (Optional) You can pass tag key-value pairs to your session. These are called
// session tags. For more information about session tags, see Passing Session
// Tags in STS (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
//
// An administrator must grant you the permissions necessary to pass session
// tags. The administrator can also create granular permissions to allow you
// to pass only specific session tags. For more information, see Tutorial: Using
// Tags for Attribute-Based Access Control (https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_attribute-based-access-control.html)
// in the IAM User Guide.
//
// Tag keyvalue pairs are not case sensitive, but case is preserved. This
// means that you cannot have separate Department and department tag keys. Assume
// that the user that you are federating has the Department=Marketing tag and
// you pass the department=engineering session tag. Department and department
// are not saved as separate tags, and the session tag passed in the request
// takes precedence over the user tag.
//
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
// with awserr.Error's Code and Message methods to get detailed information about // with awserr.Error's Code and Message methods to get detailed information about
// the error. // the error.
@ -1000,9 +1158,18 @@ func (c *STS) GetFederationTokenRequest(input *GetFederationTokenInput) (req *re
// message describes the specific error. // message describes the specific error.
// //
// * ErrCodePackedPolicyTooLargeException "PackedPolicyTooLarge" // * ErrCodePackedPolicyTooLargeException "PackedPolicyTooLarge"
// The request was rejected because the policy document was too large. The error // The request was rejected because the total packed size of the session policies
// message describes how big the policy document is, in packed form, as a percentage // and session tags combined was too large. An AWS conversion compresses the
// of what the API allows. // session policy document, session policy ARNs, and session tags into a packed
// binary format that has a separate limit. The error message indicates by percentage
// how close the policies and tags are to the upper size limit. For more information,
// see Passing Session Tags in STS (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
//
// You could receive this error even though you meet other defined session policy
// and session tag limits. For more information, see IAM and STS Entity Character
// Limits (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html)
// in the IAM User Guide.
// //
// * ErrCodeRegionDisabledException "RegionDisabledException" // * ErrCodeRegionDisabledException "RegionDisabledException"
// STS is not activated in the requested region for the account that is being // STS is not activated in the requested region for the account that is being
@ -1091,6 +1258,8 @@ func (c *STS) GetSessionTokenRequest(input *GetSessionTokenInput) (req *request.
// and Comparing the AWS STS API operations (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#stsapi_comparison) // and Comparing the AWS STS API operations (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#stsapi_comparison)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Session Duration
//
// The GetSessionToken operation must be called by using the long-term AWS security // The GetSessionToken operation must be called by using the long-term AWS security
// credentials of the AWS account root user or an IAM user. Credentials that // credentials of the AWS account root user or an IAM user. Credentials that
// are created by IAM users are valid for the duration that you specify. This // are created by IAM users are valid for the duration that you specify. This
@ -1099,6 +1268,8 @@ func (c *STS) GetSessionTokenRequest(input *GetSessionTokenInput) (req *request.
// based on account credentials can range from 900 seconds (15 minutes) up to // based on account credentials can range from 900 seconds (15 minutes) up to
// 3,600 seconds (1 hour), with a default of 1 hour. // 3,600 seconds (1 hour), with a default of 1 hour.
// //
// Permissions
//
// The temporary security credentials created by GetSessionToken can be used // The temporary security credentials created by GetSessionToken can be used
// to make API calls to any AWS service with the following exceptions: // to make API calls to any AWS service with the following exceptions:
// //
@ -1213,16 +1384,16 @@ type AssumeRoleInput struct {
// in the IAM User Guide. // in the IAM User Guide.
// //
// The plain text that you use for both inline and managed session policies // The plain text that you use for both inline and managed session policies
// shouldn't exceed 2048 characters. The JSON policy characters can be any ASCII // can't exceed 2,048 characters. The JSON policy characters can be any ASCII
// character from the space character to the end of the valid character list // character from the space character to the end of the valid character list
// (\u0020 through \u00FF). It can also include the tab (\u0009), linefeed (\u000A), // (\u0020 through \u00FF). It can also include the tab (\u0009), linefeed (\u000A),
// and carriage return (\u000D) characters. // and carriage return (\u000D) characters.
// //
// The characters in this parameter count towards the 2048 character session // An AWS conversion compresses the passed session policies and session tags
// policy guideline. However, an AWS conversion compresses the session policies // into a packed binary format that has a separate limit. Your request can fail
// into a packed binary format that has a separate limit. This is the enforced // for this limit even if your plain text meets the other requirements. The
// limit. The PackedPolicySize response element indicates by percentage how // PackedPolicySize response element indicates by percentage how close the policies
// close the policy is to the upper size limit. // and tags for your request are to the upper size limit.
Policy *string `min:"1" type:"string"` Policy *string `min:"1" type:"string"`
// The Amazon Resource Names (ARNs) of the IAM managed policies that you want // The Amazon Resource Names (ARNs) of the IAM managed policies that you want
@ -1231,15 +1402,15 @@ type AssumeRoleInput struct {
// //
// This parameter is optional. You can provide up to 10 managed policy ARNs. // This parameter is optional. You can provide up to 10 managed policy ARNs.
// However, the plain text that you use for both inline and managed session // However, the plain text that you use for both inline and managed session
// policies shouldn't exceed 2048 characters. For more information about ARNs, // policies can't exceed 2,048 characters. For more information about ARNs,
// see Amazon Resource Names (ARNs) and AWS Service Namespaces (https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) // see Amazon Resource Names (ARNs) and AWS Service Namespaces (https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html)
// in the AWS General Reference. // in the AWS General Reference.
// //
// The characters in this parameter count towards the 2048 character session // An AWS conversion compresses the passed session policies and session tags
// policy guideline. However, an AWS conversion compresses the session policies // into a packed binary format that has a separate limit. Your request can fail
// into a packed binary format that has a separate limit. This is the enforced // for this limit even if your plain text meets the other requirements. The
// limit. The PackedPolicySize response element indicates by percentage how // PackedPolicySize response element indicates by percentage how close the policies
// close the policy is to the upper size limit. // and tags for your request are to the upper size limit.
// //
// Passing policies to this operation returns new temporary credentials. The // Passing policies to this operation returns new temporary credentials. The
// resulting session's permissions are the intersection of the role's identity-based // resulting session's permissions are the intersection of the role's identity-based
@ -1284,6 +1455,41 @@ type AssumeRoleInput struct {
// also include underscores or any of the following characters: =,.@- // also include underscores or any of the following characters: =,.@-
SerialNumber *string `min:"9" type:"string"` SerialNumber *string `min:"9" type:"string"`
// A list of session tags that you want to pass. Each session tag consists of
// a key name and an associated value. For more information about session tags,
// see Tagging AWS STS Sessions (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
//
// This parameter is optional. You can pass up to 50 session tags. The plain
// text session tag keys cant exceed 128 characters, and the values cant
// exceed 256 characters. For these and additional limits, see IAM and STS Character
// Limits (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-limits.html#reference_iam-limits-entity-length)
// in the IAM User Guide.
//
// An AWS conversion compresses the passed session policies and session tags
// into a packed binary format that has a separate limit. Your request can fail
// for this limit even if your plain text meets the other requirements. The
// PackedPolicySize response element indicates by percentage how close the policies
// and tags for your request are to the upper size limit.
//
// You can pass a session tag with the same key as a tag that is already attached
// to the role. When you do, session tags override a role tag with the same
// key.
//
// Tag keyvalue pairs are not case sensitive, but case is preserved. This
// means that you cannot have separate Department and department tag keys. Assume
// that the role has the Department=Marketing tag and you pass the department=engineering
// session tag. Department and department are not saved as separate tags, and
// the session tag passed in the request takes precedence over the role tag.
//
// Additionally, if you used temporary credentials to perform this operation,
// the new session inherits any transitive session tags from the calling session.
// If you pass a session tag with the same key as an inherited tag, the operation
// fails. To view the inherited tags for a session, see the AWS CloudTrail logs.
// For more information, see Viewing Session Tags in CloudTrail (https://docs.aws.amazon.com/IAM/latest/UserGuide/session-tags.html#id_session-tags_ctlogs)
// in the IAM User Guide.
Tags []*Tag `type:"list"`
// The value provided by the MFA device, if the trust policy of the role being // The value provided by the MFA device, if the trust policy of the role being
// assumed requires MFA (that is, if the policy includes a condition that tests // assumed requires MFA (that is, if the policy includes a condition that tests
// for MFA). If the role being assumed requires MFA and if the TokenCode value // for MFA). If the role being assumed requires MFA and if the TokenCode value
@ -1292,6 +1498,19 @@ type AssumeRoleInput struct {
// The format for this parameter, as described by its regex pattern, is a sequence // The format for this parameter, as described by its regex pattern, is a sequence
// of six numeric digits. // of six numeric digits.
TokenCode *string `min:"6" type:"string"` TokenCode *string `min:"6" type:"string"`
// A list of keys for session tags that you want to set as transitive. If you
// set a tag key as transitive, the corresponding key and value passes to subsequent
// sessions in a role chain. For more information, see Chaining Roles with Session
// Tags (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining)
// in the IAM User Guide.
//
// This parameter is optional. When you set session tags as transitive, the
// session policy and session tags packed binary limit is not affected.
//
// If you choose not to specify a transitive tag key, then no tags are passed
// from this session to any subsequent sessions.
TransitiveTagKeys []*string `type:"list"`
} }
// String returns the string representation // String returns the string representation
@ -1344,6 +1563,16 @@ func (s *AssumeRoleInput) Validate() error {
} }
} }
} }
if s.Tags != nil {
for i, v := range s.Tags {
if v == nil {
continue
}
if err := v.Validate(); err != nil {
invalidParams.AddNested(fmt.Sprintf("%s[%v]", "Tags", i), err.(request.ErrInvalidParams))
}
}
}
if invalidParams.Len() > 0 { if invalidParams.Len() > 0 {
return invalidParams return invalidParams
@ -1393,12 +1622,24 @@ func (s *AssumeRoleInput) SetSerialNumber(v string) *AssumeRoleInput {
return s return s
} }
// SetTags sets the Tags field's value.
func (s *AssumeRoleInput) SetTags(v []*Tag) *AssumeRoleInput {
s.Tags = v
return s
}
// SetTokenCode sets the TokenCode field's value. // SetTokenCode sets the TokenCode field's value.
func (s *AssumeRoleInput) SetTokenCode(v string) *AssumeRoleInput { func (s *AssumeRoleInput) SetTokenCode(v string) *AssumeRoleInput {
s.TokenCode = &v s.TokenCode = &v
return s return s
} }
// SetTransitiveTagKeys sets the TransitiveTagKeys field's value.
func (s *AssumeRoleInput) SetTransitiveTagKeys(v []*string) *AssumeRoleInput {
s.TransitiveTagKeys = v
return s
}
// Contains the response to a successful AssumeRole request, including temporary // Contains the response to a successful AssumeRole request, including temporary
// AWS credentials that can be used to make AWS requests. // AWS credentials that can be used to make AWS requests.
type AssumeRoleOutput struct { type AssumeRoleOutput struct {
@ -1418,9 +1659,10 @@ type AssumeRoleOutput struct {
// We strongly recommend that you make no assumptions about the maximum size. // We strongly recommend that you make no assumptions about the maximum size.
Credentials *Credentials `type:"structure"` Credentials *Credentials `type:"structure"`
// A percentage value that indicates the size of the policy in packed form. // A percentage value that indicates the packed size of the session policies
// The service rejects any policy with a packed size greater than 100 percent, // and session tags combined passed in the request. The request fails if the
// which means the policy exceeded the allowed space. // packed size is greater than 100 percent, which means the policies and tags
// exceeded the allowed space.
PackedPolicySize *int64 `type:"integer"` PackedPolicySize *int64 `type:"integer"`
} }
@ -1491,16 +1733,16 @@ type AssumeRoleWithSAMLInput struct {
// in the IAM User Guide. // in the IAM User Guide.
// //
// The plain text that you use for both inline and managed session policies // The plain text that you use for both inline and managed session policies
// shouldn't exceed 2048 characters. The JSON policy characters can be any ASCII // can't exceed 2,048 characters. The JSON policy characters can be any ASCII
// character from the space character to the end of the valid character list // character from the space character to the end of the valid character list
// (\u0020 through \u00FF). It can also include the tab (\u0009), linefeed (\u000A), // (\u0020 through \u00FF). It can also include the tab (\u0009), linefeed (\u000A),
// and carriage return (\u000D) characters. // and carriage return (\u000D) characters.
// //
// The characters in this parameter count towards the 2048 character session // An AWS conversion compresses the passed session policies and session tags
// policy guideline. However, an AWS conversion compresses the session policies // into a packed binary format that has a separate limit. Your request can fail
// into a packed binary format that has a separate limit. This is the enforced // for this limit even if your plain text meets the other requirements. The
// limit. The PackedPolicySize response element indicates by percentage how // PackedPolicySize response element indicates by percentage how close the policies
// close the policy is to the upper size limit. // and tags for your request are to the upper size limit.
Policy *string `min:"1" type:"string"` Policy *string `min:"1" type:"string"`
// The Amazon Resource Names (ARNs) of the IAM managed policies that you want // The Amazon Resource Names (ARNs) of the IAM managed policies that you want
@ -1509,15 +1751,15 @@ type AssumeRoleWithSAMLInput struct {
// //
// This parameter is optional. You can provide up to 10 managed policy ARNs. // This parameter is optional. You can provide up to 10 managed policy ARNs.
// However, the plain text that you use for both inline and managed session // However, the plain text that you use for both inline and managed session
// policies shouldn't exceed 2048 characters. For more information about ARNs, // policies can't exceed 2,048 characters. For more information about ARNs,
// see Amazon Resource Names (ARNs) and AWS Service Namespaces (https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) // see Amazon Resource Names (ARNs) and AWS Service Namespaces (https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html)
// in the AWS General Reference. // in the AWS General Reference.
// //
// The characters in this parameter count towards the 2048 character session // An AWS conversion compresses the passed session policies and session tags
// policy guideline. However, an AWS conversion compresses the session policies // into a packed binary format that has a separate limit. Your request can fail
// into a packed binary format that has a separate limit. This is the enforced // for this limit even if your plain text meets the other requirements. The
// limit. The PackedPolicySize response element indicates by percentage how // PackedPolicySize response element indicates by percentage how close the policies
// close the policy is to the upper size limit. // and tags for your request are to the upper size limit.
// //
// Passing policies to this operation returns new temporary credentials. The // Passing policies to this operation returns new temporary credentials. The
// resulting session's permissions are the intersection of the role's identity-based // resulting session's permissions are the intersection of the role's identity-based
@ -1546,7 +1788,7 @@ type AssumeRoleWithSAMLInput struct {
// in the IAM User Guide. // in the IAM User Guide.
// //
// SAMLAssertion is a required field // SAMLAssertion is a required field
SAMLAssertion *string `min:"4" type:"string" required:"true"` SAMLAssertion *string `min:"4" type:"string" required:"true" sensitive:"true"`
} }
// String returns the string representation // String returns the string representation
@ -1673,9 +1915,10 @@ type AssumeRoleWithSAMLOutput struct {
// ) ) // ) )
NameQualifier *string `type:"string"` NameQualifier *string `type:"string"`
// A percentage value that indicates the size of the policy in packed form. // A percentage value that indicates the packed size of the session policies
// The service rejects any policy with a packed size greater than 100 percent, // and session tags combined passed in the request. The request fails if the
// which means the policy exceeded the allowed space. // packed size is greater than 100 percent, which means the policies and tags
// exceeded the allowed space.
PackedPolicySize *int64 `type:"integer"` PackedPolicySize *int64 `type:"integer"`
// The value of the NameID element in the Subject element of the SAML assertion. // The value of the NameID element in the Subject element of the SAML assertion.
@ -1786,16 +2029,16 @@ type AssumeRoleWithWebIdentityInput struct {
// in the IAM User Guide. // in the IAM User Guide.
// //
// The plain text that you use for both inline and managed session policies // The plain text that you use for both inline and managed session policies
// shouldn't exceed 2048 characters. The JSON policy characters can be any ASCII // can't exceed 2,048 characters. The JSON policy characters can be any ASCII
// character from the space character to the end of the valid character list // character from the space character to the end of the valid character list
// (\u0020 through \u00FF). It can also include the tab (\u0009), linefeed (\u000A), // (\u0020 through \u00FF). It can also include the tab (\u0009), linefeed (\u000A),
// and carriage return (\u000D) characters. // and carriage return (\u000D) characters.
// //
// The characters in this parameter count towards the 2048 character session // An AWS conversion compresses the passed session policies and session tags
// policy guideline. However, an AWS conversion compresses the session policies // into a packed binary format that has a separate limit. Your request can fail
// into a packed binary format that has a separate limit. This is the enforced // for this limit even if your plain text meets the other requirements. The
// limit. The PackedPolicySize response element indicates by percentage how // PackedPolicySize response element indicates by percentage how close the policies
// close the policy is to the upper size limit. // and tags for your request are to the upper size limit.
Policy *string `min:"1" type:"string"` Policy *string `min:"1" type:"string"`
// The Amazon Resource Names (ARNs) of the IAM managed policies that you want // The Amazon Resource Names (ARNs) of the IAM managed policies that you want
@ -1804,15 +2047,15 @@ type AssumeRoleWithWebIdentityInput struct {
// //
// This parameter is optional. You can provide up to 10 managed policy ARNs. // This parameter is optional. You can provide up to 10 managed policy ARNs.
// However, the plain text that you use for both inline and managed session // However, the plain text that you use for both inline and managed session
// policies shouldn't exceed 2048 characters. For more information about ARNs, // policies can't exceed 2,048 characters. For more information about ARNs,
// see Amazon Resource Names (ARNs) and AWS Service Namespaces (https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) // see Amazon Resource Names (ARNs) and AWS Service Namespaces (https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html)
// in the AWS General Reference. // in the AWS General Reference.
// //
// The characters in this parameter count towards the 2048 character session // An AWS conversion compresses the passed session policies and session tags
// policy guideline. However, an AWS conversion compresses the session policies // into a packed binary format that has a separate limit. Your request can fail
// into a packed binary format that has a separate limit. This is the enforced // for this limit even if your plain text meets the other requirements. The
// limit. The PackedPolicySize response element indicates by percentage how // PackedPolicySize response element indicates by percentage how close the policies
// close the policy is to the upper size limit. // and tags for your request are to the upper size limit.
// //
// Passing policies to this operation returns new temporary credentials. The // Passing policies to this operation returns new temporary credentials. The
// resulting session's permissions are the intersection of the role's identity-based // resulting session's permissions are the intersection of the role's identity-based
@ -1857,7 +2100,7 @@ type AssumeRoleWithWebIdentityInput struct {
// the application makes an AssumeRoleWithWebIdentity call. // the application makes an AssumeRoleWithWebIdentity call.
// //
// WebIdentityToken is a required field // WebIdentityToken is a required field
WebIdentityToken *string `min:"4" type:"string" required:"true"` WebIdentityToken *string `min:"4" type:"string" required:"true" sensitive:"true"`
} }
// String returns the string representation // String returns the string representation
@ -1983,9 +2226,10 @@ type AssumeRoleWithWebIdentityOutput struct {
// We strongly recommend that you make no assumptions about the maximum size. // We strongly recommend that you make no assumptions about the maximum size.
Credentials *Credentials `type:"structure"` Credentials *Credentials `type:"structure"`
// A percentage value that indicates the size of the policy in packed form. // A percentage value that indicates the packed size of the session policies
// The service rejects any policy with a packed size greater than 100 percent, // and session tags combined passed in the request. The request fails if the
// which means the policy exceeded the allowed space. // packed size is greater than 100 percent, which means the policies and tags
// exceeded the allowed space.
PackedPolicySize *int64 `type:"integer"` PackedPolicySize *int64 `type:"integer"`
// The issuing authority of the web identity token presented. For OpenID Connect // The issuing authority of the web identity token presented. For OpenID Connect
@ -2057,7 +2301,7 @@ type AssumedRoleUser struct {
// The ARN of the temporary security credentials that are returned from the // The ARN of the temporary security credentials that are returned from the
// AssumeRole action. For more information about ARNs and how to use them in // AssumeRole action. For more information about ARNs and how to use them in
// policies, see IAM Identifiers (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html) // policies, see IAM Identifiers (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html)
// in Using IAM. // in the IAM User Guide.
// //
// Arn is a required field // Arn is a required field
Arn *string `min:"20" type:"string" required:"true"` Arn *string `min:"20" type:"string" required:"true"`
@ -2225,7 +2469,7 @@ type FederatedUser struct {
// The ARN that specifies the federated user that is associated with the credentials. // The ARN that specifies the federated user that is associated with the credentials.
// For more information about ARNs and how to use them in policies, see IAM // For more information about ARNs and how to use them in policies, see IAM
// Identifiers (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html) // Identifiers (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html)
// in Using IAM. // in the IAM User Guide.
// //
// Arn is a required field // Arn is a required field
Arn *string `min:"20" type:"string" required:"true"` Arn *string `min:"20" type:"string" required:"true"`
@ -2265,7 +2509,7 @@ type GetAccessKeyInfoInput struct {
// The identifier of an access key. // The identifier of an access key.
// //
// This parameter allows (through its regex pattern) a string of characters // This parameter allows (through its regex pattern) a string of characters
// that can consist of any upper- or lowercased letter or digit. // that can consist of any upper- or lowercase letter or digit.
// //
// AccessKeyId is a required field // AccessKeyId is a required field
AccessKeyId *string `min:"16" type:"string" required:"true"` AccessKeyId *string `min:"16" type:"string" required:"true"`
@ -2418,10 +2662,7 @@ type GetFederationTokenInput struct {
// use as managed session policies. // use as managed session policies.
// //
// This parameter is optional. However, if you do not pass any session policies, // This parameter is optional. However, if you do not pass any session policies,
// then the resulting federated user session has no permissions. The only exception // then the resulting federated user session has no permissions.
// is when the credentials are used to access a resource that has a resource-based
// policy that specifically references the federated user session in the Principal
// element of the policy.
// //
// When you pass session policies, the session permissions are the intersection // When you pass session policies, the session permissions are the intersection
// of the IAM user policies and the session policies that you pass. This gives // of the IAM user policies and the session policies that you pass. This gives
@ -2431,17 +2672,23 @@ type GetFederationTokenInput struct {
// Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session) // Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// in the IAM User Guide. // in the IAM User Guide.
// //
// The resulting credentials can be used to access a resource that has a resource-based
// policy. If that policy specifically references the federated user session
// in the Principal element of the policy, the session has the permissions allowed
// by the policy. These permissions are granted in addition to the permissions
// that are granted by the session policies.
//
// The plain text that you use for both inline and managed session policies // The plain text that you use for both inline and managed session policies
// shouldn't exceed 2048 characters. The JSON policy characters can be any ASCII // can't exceed 2,048 characters. The JSON policy characters can be any ASCII
// character from the space character to the end of the valid character list // character from the space character to the end of the valid character list
// (\u0020 through \u00FF). It can also include the tab (\u0009), linefeed (\u000A), // (\u0020 through \u00FF). It can also include the tab (\u0009), linefeed (\u000A),
// and carriage return (\u000D) characters. // and carriage return (\u000D) characters.
// //
// The characters in this parameter count towards the 2048 character session // An AWS conversion compresses the passed session policies and session tags
// policy guideline. However, an AWS conversion compresses the session policies // into a packed binary format that has a separate limit. Your request can fail
// into a packed binary format that has a separate limit. This is the enforced // for this limit even if your plain text meets the other requirements. The
// limit. The PackedPolicySize response element indicates by percentage how // PackedPolicySize response element indicates by percentage how close the policies
// close the policy is to the upper size limit. // and tags for your request are to the upper size limit.
Policy *string `min:"1" type:"string"` Policy *string `min:"1" type:"string"`
// The Amazon Resource Names (ARNs) of the IAM managed policies that you want // The Amazon Resource Names (ARNs) of the IAM managed policies that you want
@ -2452,16 +2699,13 @@ type GetFederationTokenInput struct {
// to this operation. You can pass a single JSON policy document to use as an // to this operation. You can pass a single JSON policy document to use as an
// inline session policy. You can also specify up to 10 managed policies to // inline session policy. You can also specify up to 10 managed policies to
// use as managed session policies. The plain text that you use for both inline // use as managed session policies. The plain text that you use for both inline
// and managed session policies shouldn't exceed 2048 characters. You can provide // and managed session policies can't exceed 2,048 characters. You can provide
// up to 10 managed policy ARNs. For more information about ARNs, see Amazon // up to 10 managed policy ARNs. For more information about ARNs, see Amazon
// Resource Names (ARNs) and AWS Service Namespaces (https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) // Resource Names (ARNs) and AWS Service Namespaces (https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html)
// in the AWS General Reference. // in the AWS General Reference.
// //
// This parameter is optional. However, if you do not pass any session policies, // This parameter is optional. However, if you do not pass any session policies,
// then the resulting federated user session has no permissions. The only exception // then the resulting federated user session has no permissions.
// is when the credentials are used to access a resource that has a resource-based
// policy that specifically references the federated user session in the Principal
// element of the policy.
// //
// When you pass session policies, the session permissions are the intersection // When you pass session policies, the session permissions are the intersection
// of the IAM user policies and the session policies that you pass. This gives // of the IAM user policies and the session policies that you pass. This gives
@ -2471,12 +2715,46 @@ type GetFederationTokenInput struct {
// Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session) // Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// in the IAM User Guide. // in the IAM User Guide.
// //
// The characters in this parameter count towards the 2048 character session // The resulting credentials can be used to access a resource that has a resource-based
// policy guideline. However, an AWS conversion compresses the session policies // policy. If that policy specifically references the federated user session
// into a packed binary format that has a separate limit. This is the enforced // in the Principal element of the policy, the session has the permissions allowed
// limit. The PackedPolicySize response element indicates by percentage how // by the policy. These permissions are granted in addition to the permissions
// close the policy is to the upper size limit. // that are granted by the session policies.
//
// An AWS conversion compresses the passed session policies and session tags
// into a packed binary format that has a separate limit. Your request can fail
// for this limit even if your plain text meets the other requirements. The
// PackedPolicySize response element indicates by percentage how close the policies
// and tags for your request are to the upper size limit.
PolicyArns []*PolicyDescriptorType `type:"list"` PolicyArns []*PolicyDescriptorType `type:"list"`
// A list of session tags. Each session tag consists of a key name and an associated
// value. For more information about session tags, see Passing Session Tags
// in STS (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
//
// This parameter is optional. You can pass up to 50 session tags. The plain
// text session tag keys cant exceed 128 characters and the values cant
// exceed 256 characters. For these and additional limits, see IAM and STS Character
// Limits (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-limits.html#reference_iam-limits-entity-length)
// in the IAM User Guide.
//
// An AWS conversion compresses the passed session policies and session tags
// into a packed binary format that has a separate limit. Your request can fail
// for this limit even if your plain text meets the other requirements. The
// PackedPolicySize response element indicates by percentage how close the policies
// and tags for your request are to the upper size limit.
//
// You can pass a session tag with the same key as a tag that is already attached
// to the user you are federating. When you do, session tags override a user
// tag with the same key.
//
// Tag keyvalue pairs are not case sensitive, but case is preserved. This
// means that you cannot have separate Department and department tag keys. Assume
// that the role has the Department=Marketing tag and you pass the department=engineering
// session tag. Department and department are not saved as separate tags, and
// the session tag passed in the request takes precedence over the role tag.
Tags []*Tag `type:"list"`
} }
// String returns the string representation // String returns the string representation
@ -2514,6 +2792,16 @@ func (s *GetFederationTokenInput) Validate() error {
} }
} }
} }
if s.Tags != nil {
for i, v := range s.Tags {
if v == nil {
continue
}
if err := v.Validate(); err != nil {
invalidParams.AddNested(fmt.Sprintf("%s[%v]", "Tags", i), err.(request.ErrInvalidParams))
}
}
}
if invalidParams.Len() > 0 { if invalidParams.Len() > 0 {
return invalidParams return invalidParams
@ -2545,6 +2833,12 @@ func (s *GetFederationTokenInput) SetPolicyArns(v []*PolicyDescriptorType) *GetF
return s return s
} }
// SetTags sets the Tags field's value.
func (s *GetFederationTokenInput) SetTags(v []*Tag) *GetFederationTokenInput {
s.Tags = v
return s
}
// Contains the response to a successful GetFederationToken request, including // Contains the response to a successful GetFederationToken request, including
// temporary AWS credentials that can be used to make AWS requests. // temporary AWS credentials that can be used to make AWS requests.
type GetFederationTokenOutput struct { type GetFederationTokenOutput struct {
@ -2563,9 +2857,10 @@ type GetFederationTokenOutput struct {
// an Amazon S3 bucket policy. // an Amazon S3 bucket policy.
FederatedUser *FederatedUser `type:"structure"` FederatedUser *FederatedUser `type:"structure"`
// A percentage value indicating the size of the policy in packed form. The // A percentage value that indicates the packed size of the session policies
// service rejects policies for which the packed size is greater than 100 percent // and session tags combined passed in the request. The request fails if the
// of the allowed value. // packed size is greater than 100 percent, which means the policies and tags
// exceeded the allowed space.
PackedPolicySize *int64 `type:"integer"` PackedPolicySize *int64 `type:"integer"`
} }
@ -2748,3 +3043,73 @@ func (s *PolicyDescriptorType) SetArn(v string) *PolicyDescriptorType {
s.Arn = &v s.Arn = &v
return s return s
} }
// You can pass custom key-value pair attributes when you assume a role or federate
// a user. These are called session tags. You can then use the session tags
// to control access to resources. For more information, see Tagging AWS STS
// Sessions (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
type Tag struct {
_ struct{} `type:"structure"`
// The key for a session tag.
//
// You can pass up to 50 session tags. The plain text session tag keys cant
// exceed 128 characters. For these and additional limits, see IAM and STS Character
// Limits (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-limits.html#reference_iam-limits-entity-length)
// in the IAM User Guide.
//
// Key is a required field
Key *string `min:"1" type:"string" required:"true"`
// The value for a session tag.
//
// You can pass up to 50 session tags. The plain text session tag values cant
// exceed 256 characters. For these and additional limits, see IAM and STS Character
// Limits (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-limits.html#reference_iam-limits-entity-length)
// in the IAM User Guide.
//
// Value is a required field
Value *string `type:"string" required:"true"`
}
// String returns the string representation
func (s Tag) String() string {
return awsutil.Prettify(s)
}
// GoString returns the string representation
func (s Tag) GoString() string {
return s.String()
}
// Validate inspects the fields of the type to determine if they are valid.
func (s *Tag) Validate() error {
invalidParams := request.ErrInvalidParams{Context: "Tag"}
if s.Key == nil {
invalidParams.Add(request.NewErrParamRequired("Key"))
}
if s.Key != nil && len(*s.Key) < 1 {
invalidParams.Add(request.NewErrParamMinLen("Key", 1))
}
if s.Value == nil {
invalidParams.Add(request.NewErrParamRequired("Value"))
}
if invalidParams.Len() > 0 {
return invalidParams
}
return nil
}
// SetKey sets the Key field's value.
func (s *Tag) SetKey(v string) *Tag {
s.Key = &v
return s
}
// SetValue sets the Value field's value.
func (s *Tag) SetValue(v string) *Tag {
s.Value = &v
return s
}

View File

@ -14,11 +14,11 @@ const (
// ErrCodeIDPCommunicationErrorException for service response error code // ErrCodeIDPCommunicationErrorException for service response error code
// "IDPCommunicationError". // "IDPCommunicationError".
// //
// The request could not be fulfilled because the non-AWS identity provider // The request could not be fulfilled because the identity provider (IDP) that
// (IDP) that was asked to verify the incoming identity token could not be reached. // was asked to verify the incoming identity token could not be reached. This
// This is often a transient error caused by network conditions. Retry the request // is often a transient error caused by network conditions. Retry the request
// a limited number of times so that you don't exceed the request rate. If the // a limited number of times so that you don't exceed the request rate. If the
// error persists, the non-AWS identity provider might be down or not responding. // error persists, the identity provider might be down or not responding.
ErrCodeIDPCommunicationErrorException = "IDPCommunicationError" ErrCodeIDPCommunicationErrorException = "IDPCommunicationError"
// ErrCodeIDPRejectedClaimException for service response error code // ErrCodeIDPRejectedClaimException for service response error code
@ -56,9 +56,18 @@ const (
// ErrCodePackedPolicyTooLargeException for service response error code // ErrCodePackedPolicyTooLargeException for service response error code
// "PackedPolicyTooLarge". // "PackedPolicyTooLarge".
// //
// The request was rejected because the policy document was too large. The error // The request was rejected because the total packed size of the session policies
// message describes how big the policy document is, in packed form, as a percentage // and session tags combined was too large. An AWS conversion compresses the
// of what the API allows. // session policy document, session policy ARNs, and session tags into a packed
// binary format that has a separate limit. The error message indicates by percentage
// how close the policies and tags are to the upper size limit. For more information,
// see Passing Session Tags in STS (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)
// in the IAM User Guide.
//
// You could receive this error even though you meet other defined session policy
// and session tag limits. For more information, see IAM and STS Entity Character
// Limits (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html)
// in the IAM User Guide.
ErrCodePackedPolicyTooLargeException = "PackedPolicyTooLarge" ErrCodePackedPolicyTooLargeException = "PackedPolicyTooLarge"
// ErrCodeRegionDisabledException for service response error code // ErrCodeRegionDisabledException for service response error code

View File

@ -31,7 +31,7 @@ var initRequest func(*request.Request)
const ( const (
ServiceName = "sts" // Name of service. ServiceName = "sts" // Name of service.
EndpointsID = ServiceName // ID to lookup a service endpoint with. EndpointsID = ServiceName // ID to lookup a service endpoint with.
ServiceID = "STS" // ServiceID is a unique identifer of a specific service. ServiceID = "STS" // ServiceID is a unique identifier of a specific service.
) )
// New creates a new instance of the STS client with a session. // New creates a new instance of the STS client with a session.
@ -39,6 +39,8 @@ const (
// aws.Config parameter to add your extra config. // aws.Config parameter to add your extra config.
// //
// Example: // Example:
// mySession := session.Must(session.NewSession())
//
// // Create a STS client from just a session. // // Create a STS client from just a session.
// svc := sts.New(mySession) // svc := sts.New(mySession)
// //
@ -46,11 +48,11 @@ const (
// svc := sts.New(mySession, aws.NewConfig().WithRegion("us-west-2")) // svc := sts.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *STS { func New(p client.ConfigProvider, cfgs ...*aws.Config) *STS {
c := p.ClientConfig(EndpointsID, cfgs...) c := p.ClientConfig(EndpointsID, cfgs...)
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion, c.SigningName) return newClient(*c.Config, c.Handlers, c.PartitionID, c.Endpoint, c.SigningRegion, c.SigningName)
} }
// newClient creates, initializes and returns a new service client instance. // newClient creates, initializes and returns a new service client instance.
func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion, signingName string) *STS { func newClient(cfg aws.Config, handlers request.Handlers, partitionID, endpoint, signingRegion, signingName string) *STS {
svc := &STS{ svc := &STS{
Client: client.New( Client: client.New(
cfg, cfg,
@ -59,6 +61,7 @@ func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegio
ServiceID: ServiceID, ServiceID: ServiceID,
SigningName: signingName, SigningName: signingName,
SigningRegion: signingRegion, SigningRegion: signingRegion,
PartitionID: partitionID,
Endpoint: endpoint, Endpoint: endpoint,
APIVersion: "2011-06-15", APIVersion: "2011-06-15",
}, },

View File

@ -9,6 +9,13 @@
# go fmt will enforce this, but in case a user has not called "go fmt" allow GIT to catch this: # go fmt will enforce this, but in case a user has not called "go fmt" allow GIT to catch this:
*.go text eol=lf core.whitespace whitespace=indent-with-non-tab,trailing-space,tabwidth=4 *.go text eol=lf core.whitespace whitespace=indent-with-non-tab,trailing-space,tabwidth=4
go.mod text eol=lf
go.sum text eol=lf
*.txt text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.tpl text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.htm text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.html text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.md text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 *.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
.git* text eol=auto core.whitespace whitespace=trailing-space .git* text eol=auto core.whitespace whitespace=trailing-space

Some files were not shown because too many files have changed in this diff Show More