rbd: add aws-sts-metdata encryption type

With Amazon STS and kubernetes cluster is configured with
OIDC identity provider, credentials to access Amazon KMS
can be fetched using oidc-token(serviceaccount token).
Each tenant/namespace needs to create a secret with aws region,
role and CMK ARN.
Ceph-CSI will assume the given role with oidc token and access
aws KMS, with given CMK to encrypt/decrypt DEK which will stored
in the image metdata.

Refer: https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html
Resolves: #2879

Signed-off-by: Rakshith R <rar@redhat.com>
This commit is contained in:
Rakshith R
2022-03-02 16:00:48 +05:30
committed by mergify[bot]
parent 13dcc89ac8
commit 4f0bb2315b
217 changed files with 24757 additions and 72 deletions

View File

@ -0,0 +1,65 @@
package http
import (
"context"
"fmt"
"github.com/aws/smithy-go/middleware"
)
const contentMD5Header = "Content-Md5"
// contentMD5Checksum provides a middleware to compute and set
// content-md5 checksum for a http request
type contentMD5Checksum struct {
}
// AddContentChecksumMiddleware adds checksum middleware to middleware's
// build step.
func AddContentChecksumMiddleware(stack *middleware.Stack) error {
// This middleware must be executed before request body is set.
return stack.Build.Add(&contentMD5Checksum{}, middleware.Before)
}
// ID returns the identifier for the checksum middleware
func (m *contentMD5Checksum) ID() string { return "ContentChecksum" }
// HandleBuild adds behavior to compute md5 checksum and add content-md5 header
// on http request
func (m *contentMD5Checksum) HandleBuild(
ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler,
) (
out middleware.BuildOutput, metadata middleware.Metadata, err error,
) {
req, ok := in.Request.(*Request)
if !ok {
return out, metadata, fmt.Errorf("unknown request type %T", req)
}
// if Content-MD5 header is already present, return
if v := req.Header.Get(contentMD5Header); len(v) != 0 {
return next.HandleBuild(ctx, in)
}
// fetch the request stream.
stream := req.GetStream()
// compute checksum if payload is explicit
if stream != nil {
v, err := computeMD5Checksum(stream)
if err != nil {
return out, metadata, fmt.Errorf("error computing md5 checksum, %w", err)
}
// reset the request stream
if err := req.RewindStream(); err != nil {
return out, metadata, fmt.Errorf(
"error rewinding request stream after computing md5 checksum, %w", err)
}
// set the 'Content-MD5' header
req.Header.Set(contentMD5Header, string(v))
}
// set md5 header value
return next.HandleBuild(ctx, in)
}

View File

@ -0,0 +1,120 @@
package http
import (
"context"
"fmt"
"net/http"
smithy "github.com/aws/smithy-go"
"github.com/aws/smithy-go/middleware"
)
// ClientDo provides the interface for custom HTTP client implementations.
type ClientDo interface {
Do(*http.Request) (*http.Response, error)
}
// ClientDoFunc provides a helper to wrap a function as an HTTP client for
// round tripping requests.
type ClientDoFunc func(*http.Request) (*http.Response, error)
// Do will invoke the underlying func, returning the result.
func (fn ClientDoFunc) Do(r *http.Request) (*http.Response, error) {
return fn(r)
}
// ClientHandler wraps a client that implements the HTTP Do method. Standard
// implementation is http.Client.
type ClientHandler struct {
client ClientDo
}
// NewClientHandler returns an initialized middleware handler for the client.
func NewClientHandler(client ClientDo) ClientHandler {
return ClientHandler{
client: client,
}
}
// Handle implements the middleware Handler interface, that will invoke the
// underlying HTTP client. Requires the input to be a Smithy *Request. Returns
// a smithy *Response, or error if the request failed.
func (c ClientHandler) Handle(ctx context.Context, input interface{}) (
out interface{}, metadata middleware.Metadata, err error,
) {
req, ok := input.(*Request)
if !ok {
return nil, metadata, fmt.Errorf("expect Smithy http.Request value as input, got unsupported type %T", input)
}
builtRequest := req.Build(ctx)
if err := ValidateEndpointHost(builtRequest.Host); err != nil {
return nil, metadata, err
}
resp, err := c.client.Do(builtRequest)
if resp == nil {
// Ensure a http response value is always present to prevent unexpected
// panics.
resp = &http.Response{
Header: http.Header{},
Body: http.NoBody,
}
}
if err != nil {
err = &RequestSendError{Err: err}
// Override the error with a context canceled error, if that was canceled.
select {
case <-ctx.Done():
err = &smithy.CanceledError{Err: ctx.Err()}
default:
}
}
// HTTP RoundTripper *should* close the request body. But this may not happen in a timely manner.
// So instead Smithy *Request Build wraps the body to be sent in a safe closer that will clear the
// stream reference so that it can be safely reused.
if builtRequest.Body != nil {
_ = builtRequest.Body.Close()
}
return &Response{Response: resp}, metadata, err
}
// RequestSendError provides a generic request transport error. This error
// should wrap errors making HTTP client requests.
//
// The ClientHandler will wrap the HTTP client's error if the client request
// fails, and did not fail because of context canceled.
type RequestSendError struct {
Err error
}
// ConnectionError returns that the error is related to not being able to send
// the request, or receive a response from the service.
func (e *RequestSendError) ConnectionError() bool {
return true
}
// Unwrap returns the underlying error, if there was one.
func (e *RequestSendError) Unwrap() error {
return e.Err
}
func (e *RequestSendError) Error() string {
return fmt.Sprintf("request send failed, %v", e.Err)
}
// NopClient provides a client that ignores the request, and returns an empty
// successful HTTP response value.
type NopClient struct{}
// Do ignores the request and returns a 200 status empty response.
func (NopClient) Do(r *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: 200,
Header: http.Header{},
Body: http.NoBody,
}, nil
}

View File

@ -0,0 +1,5 @@
/*
Package http provides the HTTP transport client and request/response types
needed to round trip API operation calls with an service.
*/
package http

View File

@ -0,0 +1,163 @@
package http
import (
"fmt"
"strconv"
"strings"
"unicode"
)
func splitHeaderListValues(vs []string, splitFn func(string) ([]string, error)) ([]string, error) {
values := make([]string, 0, len(vs))
for i := 0; i < len(vs); i++ {
parts, err := splitFn(vs[i])
if err != nil {
return nil, err
}
values = append(values, parts...)
}
return values, nil
}
// SplitHeaderListValues attempts to split the elements of the slice by commas,
// and return a list of all values separated. Returns error if unable to
// separate the values.
func SplitHeaderListValues(vs []string) ([]string, error) {
return splitHeaderListValues(vs, quotedCommaSplit)
}
func quotedCommaSplit(v string) (parts []string, err error) {
v = strings.TrimSpace(v)
expectMore := true
for i := 0; i < len(v); i++ {
if unicode.IsSpace(rune(v[i])) {
continue
}
expectMore = false
// leading space in part is ignored.
// Start of value must be non-space, or quote.
//
// - If quote, enter quoted mode, find next non-escaped quote to
// terminate the value.
// - Otherwise, find next comma to terminate value.
remaining := v[i:]
var value string
var valueLen int
if remaining[0] == '"' {
//------------------------------
// Quoted value
//------------------------------
var j int
var skipQuote bool
for j += 1; j < len(remaining); j++ {
if remaining[j] == '\\' || (remaining[j] != '\\' && skipQuote) {
skipQuote = !skipQuote
continue
}
if remaining[j] == '"' {
break
}
}
if j == len(remaining) || j == 1 {
return nil, fmt.Errorf("value %v missing closing double quote",
remaining)
}
valueLen = j + 1
tail := remaining[valueLen:]
var k int
for ; k < len(tail); k++ {
if !unicode.IsSpace(rune(tail[k])) && tail[k] != ',' {
return nil, fmt.Errorf("value %v has non-space trailing characters",
remaining)
}
if tail[k] == ',' {
expectMore = true
break
}
}
value = remaining[:valueLen]
value, err = strconv.Unquote(value)
if err != nil {
return nil, fmt.Errorf("failed to unquote value %v, %w", value, err)
}
// Pad valueLen to include trailing space(s) so `i` is updated correctly.
valueLen += k
} else {
//------------------------------
// Unquoted value
//------------------------------
// Index of the next comma is the length of the value, or end of string.
valueLen = strings.Index(remaining, ",")
if valueLen != -1 {
expectMore = true
} else {
valueLen = len(remaining)
}
value = strings.TrimSpace(remaining[:valueLen])
}
i += valueLen
parts = append(parts, value)
}
if expectMore {
parts = append(parts, "")
}
return parts, nil
}
// SplitHTTPDateTimestampHeaderListValues attempts to split the HTTP-Date
// timestamp values in the slice by commas, and return a list of all values
// separated. The split is aware of the HTTP-Date timestamp format, and will skip
// comma within the timestamp value. Returns an error if unable to split the
// timestamp values.
func SplitHTTPDateTimestampHeaderListValues(vs []string) ([]string, error) {
return splitHeaderListValues(vs, splitHTTPDateHeaderValue)
}
func splitHTTPDateHeaderValue(v string) ([]string, error) {
if n := strings.Count(v, ","); n <= 1 {
// Nothing to do if only contains a no, or single HTTPDate value
return []string{v}, nil
} else if n%2 == 0 {
return nil, fmt.Errorf("invalid timestamp HTTPDate header comma separations, %q", v)
}
var parts []string
var i, j int
var doSplit bool
for ; i < len(v); i++ {
if v[i] == ',' {
if doSplit {
doSplit = false
parts = append(parts, strings.TrimSpace(v[j:i]))
j = i + 1
} else {
// Skip the first comma in the timestamp value since that
// separates the day from the rest of the timestamp.
//
// Tue, 17 Dec 2019 23:48:18 GMT
doSplit = true
}
}
}
// Add final part
if j < len(v) {
parts = append(parts, strings.TrimSpace(v[j:]))
}
return parts, nil
}

89
vendor/github.com/aws/smithy-go/transport/http/host.go generated vendored Normal file
View File

@ -0,0 +1,89 @@
package http
import (
"fmt"
"net"
"strconv"
"strings"
)
// ValidateEndpointHost validates that the host string passed in is a valid RFC
// 3986 host. Returns error if the host is not valid.
func ValidateEndpointHost(host string) error {
var errors strings.Builder
var hostname string
var port string
var err error
if strings.Contains(host, ":") {
hostname, port, err = net.SplitHostPort(host)
if err != nil {
errors.WriteString(fmt.Sprintf("\n endpoint %v, failed to parse, got ", host))
errors.WriteString(err.Error())
}
if !ValidPortNumber(port) {
errors.WriteString(fmt.Sprintf("port number should be in range [0-65535], got %v", port))
}
} else {
hostname = host
}
labels := strings.Split(hostname, ".")
for i, label := range labels {
if i == len(labels)-1 && len(label) == 0 {
// Allow trailing dot for FQDN hosts.
continue
}
if !ValidHostLabel(label) {
errors.WriteString("\nendpoint host domain labels must match \"[a-zA-Z0-9-]{1,63}\", but found: ")
errors.WriteString(label)
}
}
if len(hostname) == 0 && len(port) != 0 {
errors.WriteString("\nendpoint host with port must not be empty")
}
if len(hostname) > 255 {
errors.WriteString(fmt.Sprintf("\nendpoint host must be less than 255 characters, but was %d", len(hostname)))
}
if len(errors.String()) > 0 {
return fmt.Errorf("invalid endpoint host%s", errors.String())
}
return nil
}
// ValidPortNumber returns whether the port is valid RFC 3986 port.
func ValidPortNumber(port string) bool {
i, err := strconv.Atoi(port)
if err != nil {
return false
}
if i < 0 || i > 65535 {
return false
}
return true
}
// ValidHostLabel returns whether the label is a valid RFC 3986 host abel.
func ValidHostLabel(label string) bool {
if l := len(label); l == 0 || l > 63 {
return false
}
for _, r := range label {
switch {
case r >= '0' && r <= '9':
case r >= 'A' && r <= 'Z':
case r >= 'a' && r <= 'z':
case r == '-':
default:
return false
}
}
return true
}

View File

@ -0,0 +1,75 @@
package io
import (
"io"
"sync"
)
// NewSafeReadCloser returns a new safeReadCloser that wraps readCloser.
func NewSafeReadCloser(readCloser io.ReadCloser) io.ReadCloser {
sr := &safeReadCloser{
readCloser: readCloser,
}
if _, ok := readCloser.(io.WriterTo); ok {
return &safeWriteToReadCloser{safeReadCloser: sr}
}
return sr
}
// safeWriteToReadCloser wraps a safeReadCloser but exposes a WriteTo interface implementation. This will panic
// if the underlying io.ReadClose does not support WriteTo. Use NewSafeReadCloser to ensure the proper handling of this
// type.
type safeWriteToReadCloser struct {
*safeReadCloser
}
// WriteTo implements the io.WriteTo interface.
func (r *safeWriteToReadCloser) WriteTo(w io.Writer) (int64, error) {
r.safeReadCloser.mtx.Lock()
defer r.safeReadCloser.mtx.Unlock()
if r.safeReadCloser.closed {
return 0, io.EOF
}
return r.safeReadCloser.readCloser.(io.WriterTo).WriteTo(w)
}
// safeReadCloser wraps a io.ReadCloser and presents an io.ReadCloser interface. When Close is called on safeReadCloser
// the underlying Close method will be executed, and then the reference to the reader will be dropped. This type
// is meant to be used with the net/http library which will retain a reference to the request body for the lifetime
// of a goroutine connection. Wrapping in this manner will ensure that no data race conditions are falsely reported.
// This type is thread-safe.
type safeReadCloser struct {
readCloser io.ReadCloser
closed bool
mtx sync.Mutex
}
// Read reads up to len(p) bytes into p from the underlying read. If the reader is closed io.EOF will be returned.
func (r *safeReadCloser) Read(p []byte) (n int, err error) {
r.mtx.Lock()
defer r.mtx.Unlock()
if r.closed {
return 0, io.EOF
}
return r.readCloser.Read(p)
}
// Close calls the underlying io.ReadCloser's Close method, removes the reference to the reader, and returns any error
// reported from Close. Subsequent calls to Close will always return a nil error.
func (r *safeReadCloser) Close() error {
r.mtx.Lock()
defer r.mtx.Unlock()
if r.closed {
return nil
}
r.closed = true
rc := r.readCloser
r.readCloser = nil
return rc.Close()
}

View File

@ -0,0 +1,25 @@
package http
import (
"crypto/md5"
"encoding/base64"
"fmt"
"io"
)
// computeMD5Checksum computes base64 md5 checksum of an io.Reader's contents.
// Returns the byte slice of md5 checksum and an error.
func computeMD5Checksum(r io.Reader) ([]byte, error) {
h := md5.New()
// copy errors may be assumed to be from the body.
_, err := io.Copy(h, r)
if err != nil {
return nil, fmt.Errorf("failed to read body: %w", err)
}
// encode the md5 checksum in base64.
sum := h.Sum(nil)
sum64 := make([]byte, base64.StdEncoding.EncodedLen(len(sum)))
base64.StdEncoding.Encode(sum64, sum)
return sum64, nil
}

View File

@ -0,0 +1,79 @@
package http
import (
"context"
"github.com/aws/smithy-go/logging"
"github.com/aws/smithy-go/middleware"
"io"
"io/ioutil"
)
// AddErrorCloseResponseBodyMiddleware adds the middleware to automatically
// close the response body of an operation request if the request response
// failed.
func AddErrorCloseResponseBodyMiddleware(stack *middleware.Stack) error {
return stack.Deserialize.Insert(&errorCloseResponseBodyMiddleware{}, "OperationDeserializer", middleware.Before)
}
type errorCloseResponseBodyMiddleware struct{}
func (*errorCloseResponseBodyMiddleware) ID() string {
return "ErrorCloseResponseBody"
}
func (m *errorCloseResponseBodyMiddleware) HandleDeserialize(
ctx context.Context, input middleware.DeserializeInput, next middleware.DeserializeHandler,
) (
output middleware.DeserializeOutput, metadata middleware.Metadata, err error,
) {
out, metadata, err := next.HandleDeserialize(ctx, input)
if err != nil {
if resp, ok := out.RawResponse.(*Response); ok && resp != nil && resp.Body != nil {
// Consume the full body to prevent TCP connection resets on some platforms
_, _ = io.Copy(ioutil.Discard, resp.Body)
// Do not validate that the response closes successfully.
resp.Body.Close()
}
}
return out, metadata, err
}
// AddCloseResponseBodyMiddleware adds the middleware to automatically close
// the response body of an operation request, after the response had been
// deserialized.
func AddCloseResponseBodyMiddleware(stack *middleware.Stack) error {
return stack.Deserialize.Insert(&closeResponseBody{}, "OperationDeserializer", middleware.Before)
}
type closeResponseBody struct{}
func (*closeResponseBody) ID() string {
return "CloseResponseBody"
}
func (m *closeResponseBody) HandleDeserialize(
ctx context.Context, input middleware.DeserializeInput, next middleware.DeserializeHandler,
) (
output middleware.DeserializeOutput, metadata middleware.Metadata, err error,
) {
out, metadata, err := next.HandleDeserialize(ctx, input)
if err != nil {
return out, metadata, err
}
if resp, ok := out.RawResponse.(*Response); ok {
// Consume the full body to prevent TCP connection resets on some platforms
_, copyErr := io.Copy(ioutil.Discard, resp.Body)
if copyErr != nil {
middleware.GetLogger(ctx).Logf(logging.Warn, "failed to discard remaining HTTP response body, this may affect connection reuse")
}
closeErr := resp.Body.Close()
if closeErr != nil {
middleware.GetLogger(ctx).Logf(logging.Warn, "failed to close HTTP response body, this may affect connection reuse")
}
}
return out, metadata, err
}

View File

@ -0,0 +1,90 @@
package http
import (
"context"
"fmt"
"github.com/aws/smithy-go/middleware"
)
// ComputeContentLength provides a middleware to set the content-length
// header for the length of a serialize request body.
type ComputeContentLength struct {
}
// AddComputeContentLengthMiddleware adds ComputeContentLength to the middleware
// stack's Build step.
func AddComputeContentLengthMiddleware(stack *middleware.Stack) error {
return stack.Build.Add(&ComputeContentLength{}, middleware.After)
}
// ID returns the identifier for the ComputeContentLength.
func (m *ComputeContentLength) ID() string { return "ComputeContentLength" }
// HandleBuild adds the length of the serialized request to the HTTP header
// if the length can be determined.
func (m *ComputeContentLength) HandleBuild(
ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler,
) (
out middleware.BuildOutput, metadata middleware.Metadata, err error,
) {
req, ok := in.Request.(*Request)
if !ok {
return out, metadata, fmt.Errorf("unknown request type %T", req)
}
// do nothing if request content-length was set to 0 or above.
if req.ContentLength >= 0 {
return next.HandleBuild(ctx, in)
}
// attempt to compute stream length
if n, ok, err := req.StreamLength(); err != nil {
return out, metadata, fmt.Errorf(
"failed getting length of request stream, %w", err)
} else if ok {
req.ContentLength = n
if n == 0 {
// If the content length could be determined, and the body is empty
// the stream must be cleared to prevent unexpected chunk encoding.
req, _ = req.SetStream(nil)
in.Request = req
}
}
return next.HandleBuild(ctx, in)
}
// validateContentLength provides a middleware to validate the content-length
// is valid (greater than zero), for the serialized request payload.
type validateContentLength struct{}
// ValidateContentLengthHeader adds middleware that validates request content-length
// is set to value greater than zero.
func ValidateContentLengthHeader(stack *middleware.Stack) error {
return stack.Build.Add(&validateContentLength{}, middleware.After)
}
// ID returns the identifier for the ComputeContentLength.
func (m *validateContentLength) ID() string { return "ValidateContentLength" }
// HandleBuild adds the length of the serialized request to the HTTP header
// if the length can be determined.
func (m *validateContentLength) HandleBuild(
ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler,
) (
out middleware.BuildOutput, metadata middleware.Metadata, err error,
) {
req, ok := in.Request.(*Request)
if !ok {
return out, metadata, fmt.Errorf("unknown request type %T", req)
}
// if request content-length was set to less than 0, return an error
if req.ContentLength < 0 {
return out, metadata, fmt.Errorf(
"content length for payload is required and must be at least 0")
}
return next.HandleBuild(ctx, in)
}

View File

@ -0,0 +1,88 @@
package http
import (
"context"
"fmt"
"github.com/aws/smithy-go/middleware"
)
type headerValue struct {
header string
value string
append bool
}
type headerValueHelper struct {
headerValues []headerValue
}
func (h *headerValueHelper) addHeaderValue(value headerValue) {
h.headerValues = append(h.headerValues, value)
}
func (h *headerValueHelper) ID() string {
return "HTTPHeaderHelper"
}
func (h *headerValueHelper) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (out middleware.BuildOutput, metadata middleware.Metadata, err error) {
req, ok := in.Request.(*Request)
if !ok {
return out, metadata, fmt.Errorf("unknown transport type %T", in.Request)
}
for _, value := range h.headerValues {
if value.append {
req.Header.Add(value.header, value.value)
} else {
req.Header.Set(value.header, value.value)
}
}
return next.HandleBuild(ctx, in)
}
func getOrAddHeaderValueHelper(stack *middleware.Stack) (*headerValueHelper, error) {
id := (*headerValueHelper)(nil).ID()
m, ok := stack.Build.Get(id)
if !ok {
m = &headerValueHelper{}
err := stack.Build.Add(m, middleware.After)
if err != nil {
return nil, err
}
}
requestUserAgent, ok := m.(*headerValueHelper)
if !ok {
return nil, fmt.Errorf("%T for %s middleware did not match expected type", m, id)
}
return requestUserAgent, nil
}
// AddHeaderValue returns a stack mutator that adds the header value pair to header.
// Appends to any existing values if present.
func AddHeaderValue(header string, value string) func(stack *middleware.Stack) error {
return func(stack *middleware.Stack) error {
helper, err := getOrAddHeaderValueHelper(stack)
if err != nil {
return err
}
helper.addHeaderValue(headerValue{header: header, value: value, append: true})
return nil
}
}
// SetHeaderValue returns a stack mutator that adds the header value pair to header.
// Replaces any existing values if present.
func SetHeaderValue(header string, value string) func(stack *middleware.Stack) error {
return func(stack *middleware.Stack) error {
helper, err := getOrAddHeaderValueHelper(stack)
if err != nil {
return err
}
helper.addHeaderValue(headerValue{header: header, value: value, append: false})
return nil
}
}

View File

@ -0,0 +1,75 @@
package http
import (
"context"
"fmt"
"net/http/httputil"
"github.com/aws/smithy-go/logging"
"github.com/aws/smithy-go/middleware"
)
// RequestResponseLogger is a deserialize middleware that will log the request and response HTTP messages and optionally
// their respective bodies. Will not perform any logging if none of the options are set.
type RequestResponseLogger struct {
LogRequest bool
LogRequestWithBody bool
LogResponse bool
LogResponseWithBody bool
}
// ID is the middleware identifier.
func (r *RequestResponseLogger) ID() string {
return "RequestResponseLogger"
}
// HandleDeserialize will log the request and response HTTP messages if configured accordingly.
func (r *RequestResponseLogger) HandleDeserialize(
ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler,
) (
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
) {
logger := middleware.GetLogger(ctx)
if r.LogRequest || r.LogRequestWithBody {
smithyRequest, ok := in.Request.(*Request)
if !ok {
return out, metadata, fmt.Errorf("unknown transport type %T", in)
}
rc := smithyRequest.Build(ctx)
reqBytes, err := httputil.DumpRequestOut(rc, r.LogRequestWithBody)
if err != nil {
return out, metadata, err
}
logger.Logf(logging.Debug, "Request\n%v", string(reqBytes))
if r.LogRequestWithBody {
smithyRequest, err = smithyRequest.SetStream(rc.Body)
if err != nil {
return out, metadata, err
}
in.Request = smithyRequest
}
}
out, metadata, err = next.HandleDeserialize(ctx, in)
if (err == nil) && (r.LogResponse || r.LogResponseWithBody) {
smithyResponse, ok := out.RawResponse.(*Response)
if !ok {
return out, metadata, fmt.Errorf("unknown transport type %T", out.RawResponse)
}
respBytes, err := httputil.DumpResponse(smithyResponse.Response, r.LogResponseWithBody)
if err != nil {
return out, metadata, fmt.Errorf("failed to dump response %w", err)
}
logger.Logf(logging.Debug, "Response\n%v", string(respBytes))
}
return out, metadata, err
}

View File

@ -0,0 +1,51 @@
package http
import (
"context"
"github.com/aws/smithy-go/middleware"
)
type (
hostnameImmutableKey struct{}
hostPrefixDisableKey struct{}
)
// GetHostnameImmutable retrieves whether the endpoint hostname should be considered
// immutable or not.
//
// Scoped to stack values. Use middleware#ClearStackValues to clear all stack
// values.
func GetHostnameImmutable(ctx context.Context) (v bool) {
v, _ = middleware.GetStackValue(ctx, hostnameImmutableKey{}).(bool)
return v
}
// SetHostnameImmutable sets or modifies whether the request's endpoint hostname
// should be considered immutable or not.
//
// Scoped to stack values. Use middleware#ClearStackValues to clear all stack
// values.
func SetHostnameImmutable(ctx context.Context, value bool) context.Context {
return middleware.WithStackValue(ctx, hostnameImmutableKey{}, value)
}
// IsEndpointHostPrefixDisabled retrieves whether the hostname prefixing is
// disabled.
//
// Scoped to stack values. Use middleware#ClearStackValues to clear all stack
// values.
func IsEndpointHostPrefixDisabled(ctx context.Context) (v bool) {
v, _ = middleware.GetStackValue(ctx, hostPrefixDisableKey{}).(bool)
return v
}
// DisableEndpointHostPrefix sets or modifies whether the request's endpoint host
// prefixing should be disabled. If value is true, endpoint host prefixing
// will be disabled.
//
// Scoped to stack values. Use middleware#ClearStackValues to clear all stack
// values.
func DisableEndpointHostPrefix(ctx context.Context, value bool) context.Context {
return middleware.WithStackValue(ctx, hostPrefixDisableKey{}, value)
}

View File

@ -0,0 +1,79 @@
package http
import (
"context"
"fmt"
"github.com/aws/smithy-go/middleware"
"strings"
)
// MinimumProtocolError is an error type indicating that the established connection did not meet the expected minimum
// HTTP protocol version.
type MinimumProtocolError struct {
proto string
expectedProtoMajor int
expectedProtoMinor int
}
// Error returns the error message.
func (m *MinimumProtocolError) Error() string {
return fmt.Sprintf("operation requires minimum HTTP protocol of HTTP/%d.%d, but was %s",
m.expectedProtoMajor, m.expectedProtoMinor, m.proto)
}
// RequireMinimumProtocol is a deserialization middleware that asserts that the established HTTP connection
// meets the minimum major ad minor version.
type RequireMinimumProtocol struct {
ProtoMajor int
ProtoMinor int
}
// AddRequireMinimumProtocol adds the RequireMinimumProtocol middleware to the stack using the provided minimum
// protocol major and minor version.
func AddRequireMinimumProtocol(stack *middleware.Stack, major, minor int) error {
return stack.Deserialize.Insert(&RequireMinimumProtocol{
ProtoMajor: major,
ProtoMinor: minor,
}, "OperationDeserializer", middleware.Before)
}
// ID returns the middleware identifier string.
func (r *RequireMinimumProtocol) ID() string {
return "RequireMinimumProtocol"
}
// HandleDeserialize asserts that the established connection is a HTTP connection with the minimum major and minor
// protocol version.
func (r *RequireMinimumProtocol) HandleDeserialize(
ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler,
) (
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
) {
out, metadata, err = next.HandleDeserialize(ctx, in)
if err != nil {
return out, metadata, err
}
response, ok := out.RawResponse.(*Response)
if !ok {
return out, metadata, fmt.Errorf("unknown transport type: %T", out.RawResponse)
}
if !strings.HasPrefix(response.Proto, "HTTP") {
return out, metadata, &MinimumProtocolError{
proto: response.Proto,
expectedProtoMajor: r.ProtoMajor,
expectedProtoMinor: r.ProtoMinor,
}
}
if response.ProtoMajor < r.ProtoMajor || response.ProtoMinor < r.ProtoMinor {
return out, metadata, &MinimumProtocolError{
proto: response.Proto,
expectedProtoMajor: r.ProtoMajor,
expectedProtoMinor: r.ProtoMinor,
}
}
return out, metadata, err
}

View File

@ -0,0 +1,154 @@
package http
import (
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
iointernal "github.com/aws/smithy-go/transport/http/internal/io"
)
// Request provides the HTTP specific request structure for HTTP specific
// middleware steps to use to serialize input, and send an operation's request.
type Request struct {
*http.Request
stream io.Reader
isStreamSeekable bool
streamStartPos int64
}
// NewStackRequest returns an initialized request ready to be populated with the
// HTTP request details. Returns empty interface so the function can be used as
// a parameter to the Smithy middleware Stack constructor.
func NewStackRequest() interface{} {
return &Request{
Request: &http.Request{
URL: &url.URL{},
Header: http.Header{},
ContentLength: -1, // default to unknown length
},
}
}
// Clone returns a deep copy of the Request for the new context. A reference to
// the Stream is copied, but the underlying stream is not copied.
func (r *Request) Clone() *Request {
rc := *r
rc.Request = rc.Request.Clone(context.TODO())
return &rc
}
// StreamLength returns the number of bytes of the serialized stream attached
// to the request and ok set. If the length cannot be determined, an error will
// be returned.
func (r *Request) StreamLength() (size int64, ok bool, err error) {
if r.stream == nil {
return 0, true, nil
}
if l, ok := r.stream.(interface{ Len() int }); ok {
return int64(l.Len()), true, nil
}
if !r.isStreamSeekable {
return 0, false, nil
}
s := r.stream.(io.Seeker)
endOffset, err := s.Seek(0, io.SeekEnd)
if err != nil {
return 0, false, err
}
// The reason to seek to streamStartPos instead of 0 is to ensure that the
// SDK only sends the stream from the starting position the user's
// application provided it to the SDK at. For example application opens a
// file, and wants to skip the first N bytes uploading the rest. The
// application would move the file's offset N bytes, then hand it off to
// the SDK to send the remaining. The SDK should respect that initial offset.
_, err = s.Seek(r.streamStartPos, io.SeekStart)
if err != nil {
return 0, false, err
}
return endOffset - r.streamStartPos, true, nil
}
// RewindStream will rewind the io.Reader to the relative start position if it
// is an io.Seeker.
func (r *Request) RewindStream() error {
// If there is no stream there is nothing to rewind.
if r.stream == nil {
return nil
}
if !r.isStreamSeekable {
return fmt.Errorf("request stream is not seekable")
}
_, err := r.stream.(io.Seeker).Seek(r.streamStartPos, io.SeekStart)
return err
}
// GetStream returns the request stream io.Reader if a stream is set. If no
// stream is present nil will be returned.
func (r *Request) GetStream() io.Reader {
return r.stream
}
// IsStreamSeekable returns whether the stream is seekable.
func (r *Request) IsStreamSeekable() bool {
return r.isStreamSeekable
}
// SetStream returns a clone of the request with the stream set to the provided reader.
// May return an error if the provided reader is seekable but returns an error.
func (r *Request) SetStream(reader io.Reader) (rc *Request, err error) {
rc = r.Clone()
switch v := reader.(type) {
case io.Seeker:
n, err := v.Seek(0, io.SeekCurrent)
if err != nil {
return r, err
}
rc.isStreamSeekable = true
rc.streamStartPos = n
default:
rc.isStreamSeekable = false
}
rc.stream = reader
return rc, err
}
// Build returns a build standard HTTP request value from the Smithy request.
// The request's stream is wrapped in a safe container that allows it to be
// reused for subsequent attempts.
func (r *Request) Build(ctx context.Context) *http.Request {
req := r.Request.Clone(ctx)
if r.stream == nil && req.ContentLength == -1 {
req.ContentLength = 0
}
switch stream := r.stream.(type) {
case *io.PipeReader:
req.Body = ioutil.NopCloser(stream)
req.ContentLength = -1
default:
if r.stream != nil {
req.Body = iointernal.NewSafeReadCloser(ioutil.NopCloser(stream))
}
}
return req
}
// RequestCloner is a function that can take an input request type and clone the request
// for use in a subsequent retry attempt.
func RequestCloner(v interface{}) interface{} {
return v.(*Request).Clone()
}

View File

@ -0,0 +1,34 @@
package http
import (
"fmt"
"net/http"
)
// Response provides the HTTP specific response structure for HTTP specific
// middleware steps to use to deserialize the response from an operation call.
type Response struct {
*http.Response
}
// ResponseError provides the HTTP centric error type wrapping the underlying
// error with the HTTP response value.
type ResponseError struct {
Response *Response
Err error
}
// HTTPStatusCode returns the HTTP response status code received from the service.
func (e *ResponseError) HTTPStatusCode() int { return e.Response.StatusCode }
// HTTPResponse returns the HTTP response received from the service.
func (e *ResponseError) HTTPResponse() *Response { return e.Response }
// Unwrap returns the nested error if any, or nil.
func (e *ResponseError) Unwrap() error { return e.Err }
func (e *ResponseError) Error() string {
return fmt.Sprintf(
"http response error StatusCode: %d, %v",
e.Response.StatusCode, e.Err)
}

13
vendor/github.com/aws/smithy-go/transport/http/time.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
package http
import (
"time"
smithytime "github.com/aws/smithy-go/time"
)
// ParseTime parses a time string like the HTTP Date header. This uses a more
// relaxed rule set for date parsing compared to the standard library.
func ParseTime(text string) (t time.Time, err error) {
return smithytime.ParseHTTPDate(text)
}

44
vendor/github.com/aws/smithy-go/transport/http/url.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
package http
import "strings"
// JoinPath returns an absolute URL path composed of the two paths provided.
// Enforces that the returned path begins with '/'. If added path is empty the
// returned path suffix will match the first parameter suffix.
func JoinPath(a, b string) string {
if len(a) == 0 {
a = "/"
} else if a[0] != '/' {
a = "/" + a
}
if len(b) != 0 && b[0] == '/' {
b = b[1:]
}
if len(b) != 0 && len(a) > 1 && a[len(a)-1] != '/' {
a = a + "/"
}
return a + b
}
// JoinRawQuery returns an absolute raw query expression. Any duplicate '&'
// will be collapsed to single separator between values.
func JoinRawQuery(a, b string) string {
a = strings.TrimFunc(a, isAmpersand)
b = strings.TrimFunc(b, isAmpersand)
if len(a) == 0 {
return b
}
if len(b) == 0 {
return a
}
return a + "&" + b
}
func isAmpersand(v rune) bool {
return v == '&'
}

View File

@ -0,0 +1,37 @@
package http
import (
"strings"
)
// UserAgentBuilder is a builder for a HTTP User-Agent string.
type UserAgentBuilder struct {
sb strings.Builder
}
// NewUserAgentBuilder returns a new UserAgentBuilder.
func NewUserAgentBuilder() *UserAgentBuilder {
return &UserAgentBuilder{sb: strings.Builder{}}
}
// AddKey adds the named component/product to the agent string
func (u *UserAgentBuilder) AddKey(key string) {
u.appendTo(key)
}
// AddKeyValue adds the named key to the agent string with the given value.
func (u *UserAgentBuilder) AddKeyValue(key, value string) {
u.appendTo(key + "/" + value)
}
// Build returns the constructed User-Agent string. May be called multiple times.
func (u *UserAgentBuilder) Build() string {
return u.sb.String()
}
func (u *UserAgentBuilder) appendTo(value string) {
if u.sb.Len() > 0 {
u.sb.WriteRune(' ')
}
u.sb.WriteString(value)
}