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,61 @@
package query
import (
"fmt"
"net/url"
)
// Array represents the encoding of Query lists and sets. A Query array is a
// representation of a list of values of a fixed type. A serialized array might
// look like the following:
//
// ListName.member.1=foo
// &ListName.member.2=bar
// &Listname.member.3=baz
type Array struct {
// The query values to add the array to.
values url.Values
// The array's prefix, which includes the names of all parent structures
// and ends with the name of the list. For example, the prefix might be
// "ParentStructure.ListName". This prefix will be used to form the full
// keys for each element in the list. For example, an entry might have the
// key "ParentStructure.ListName.member.MemberName.1".
//
// While this is currently represented as a string that gets added to, it
// could also be represented as a stack that only gets condensed into a
// string when a finalized key is created. This could potentially reduce
// allocations.
prefix string
// Whether the list is flat or not. A list that is not flat will produce the
// following entry to the url.Values for a given entry:
// ListName.MemberName.1=value
// A list that is flat will produce the following:
// ListName.1=value
flat bool
// The location name of the member. In most cases this should be "member".
memberName string
// Elements are stored in values, so we keep track of the list size here.
size int32
}
func newArray(values url.Values, prefix string, flat bool, memberName string) *Array {
return &Array{
values: values,
prefix: prefix,
flat: flat,
memberName: memberName,
}
}
// Value adds a new element to the Query Array. Returns a Value type used to
// encode the array element.
func (a *Array) Value() Value {
// Query lists start a 1, so adjust the size first
a.size++
prefix := a.prefix
if !a.flat {
prefix = fmt.Sprintf("%s.%s", prefix, a.memberName)
}
// Lists can't have flat members
return newValue(a.values, fmt.Sprintf("%s.%d", prefix, a.size), false)
}

View File

@ -0,0 +1,80 @@
package query
import (
"io"
"net/url"
"sort"
)
// Encoder is a Query encoder that supports construction of Query body
// values using methods.
type Encoder struct {
// The query values that will be built up to manage encoding.
values url.Values
// The writer that the encoded body will be written to.
writer io.Writer
Value
}
// NewEncoder returns a new Query body encoder
func NewEncoder(writer io.Writer) *Encoder {
values := url.Values{}
return &Encoder{
values: values,
writer: writer,
Value: newBaseValue(values),
}
}
// Encode returns the []byte slice representing the current
// state of the Query encoder.
func (e Encoder) Encode() error {
ws, ok := e.writer.(interface{ WriteString(string) (int, error) })
if !ok {
// Fall back to less optimal byte slice casting if WriteString isn't available.
ws = &wrapWriteString{writer: e.writer}
}
// Get the keys and sort them to have a stable output
keys := make([]string, 0, len(e.values))
for k := range e.values {
keys = append(keys, k)
}
sort.Strings(keys)
isFirstEntry := true
for _, key := range keys {
queryValues := e.values[key]
escapedKey := url.QueryEscape(key)
for _, value := range queryValues {
if !isFirstEntry {
if _, err := ws.WriteString(`&`); err != nil {
return err
}
} else {
isFirstEntry = false
}
if _, err := ws.WriteString(escapedKey); err != nil {
return err
}
if _, err := ws.WriteString(`=`); err != nil {
return err
}
if _, err := ws.WriteString(url.QueryEscape(value)); err != nil {
return err
}
}
}
return nil
}
// wrapWriteString wraps an io.Writer to provide a WriteString method
// where one is not available.
type wrapWriteString struct {
writer io.Writer
}
// WriteString writes a string to the wrapped writer by casting it to
// a byte array first.
func (w wrapWriteString) WriteString(v string) (int, error) {
return w.writer.Write([]byte(v))
}

View File

@ -0,0 +1,78 @@
package query
import (
"fmt"
"net/url"
)
// Map represents the encoding of Query maps. A Query map is a representation
// of a mapping of arbitrary string keys to arbitrary values of a fixed type.
// A Map differs from an Object in that the set of keys is not fixed, in that
// the values must all be of the same type, and that map entries are ordered.
// A serialized map might look like the following:
//
// MapName.entry.1.key=Foo
// &MapName.entry.1.value=spam
// &MapName.entry.2.key=Bar
// &MapName.entry.2.value=eggs
type Map struct {
// The query values to add the map to.
values url.Values
// The map's prefix, which includes the names of all parent structures
// and ends with the name of the object. For example, the prefix might be
// "ParentStructure.MapName". This prefix will be used to form the full
// keys for each key-value pair of the map. For example, a value might have
// the key "ParentStructure.MapName.1.value".
//
// While this is currently represented as a string that gets added to, it
// could also be represented as a stack that only gets condensed into a
// string when a finalized key is created. This could potentially reduce
// allocations.
prefix string
// Whether the map is flat or not. A map that is not flat will produce the
// following entries to the url.Values for a given key-value pair:
// MapName.entry.1.KeyLocationName=mykey
// MapName.entry.1.ValueLocationName=myvalue
// A map that is flat will produce the following:
// MapName.1.KeyLocationName=mykey
// MapName.1.ValueLocationName=myvalue
flat bool
// The location name of the key. In most cases this should be "key".
keyLocationName string
// The location name of the value. In most cases this should be "value".
valueLocationName string
// Elements are stored in values, so we keep track of the list size here.
size int32
}
func newMap(values url.Values, prefix string, flat bool, keyLocationName string, valueLocationName string) *Map {
return &Map{
values: values,
prefix: prefix,
flat: flat,
keyLocationName: keyLocationName,
valueLocationName: valueLocationName,
}
}
// Key adds the given named key to the Query map.
// Returns a Value encoder that should be used to encode a Query value type.
func (m *Map) Key(name string) Value {
// Query lists start a 1, so adjust the size first
m.size++
var key string
var value string
if m.flat {
key = fmt.Sprintf("%s.%d.%s", m.prefix, m.size, m.keyLocationName)
value = fmt.Sprintf("%s.%d.%s", m.prefix, m.size, m.valueLocationName)
} else {
key = fmt.Sprintf("%s.entry.%d.%s", m.prefix, m.size, m.keyLocationName)
value = fmt.Sprintf("%s.entry.%d.%s", m.prefix, m.size, m.valueLocationName)
}
// The key can only be a string, so we just go ahead and set it here
newValue(m.values, key, false).String(name)
// Maps can't have flat members
return newValue(m.values, value, false)
}

View File

@ -0,0 +1,62 @@
package query
import (
"context"
"fmt"
"io/ioutil"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
// AddAsGetRequestMiddleware adds a middleware to the Serialize stack after the
// operation serializer that will convert the query request body to a GET
// operation with the query message in the HTTP request querystring.
func AddAsGetRequestMiddleware(stack *middleware.Stack) error {
return stack.Serialize.Insert(&asGetRequest{}, "OperationSerializer", middleware.After)
}
type asGetRequest struct{}
func (*asGetRequest) ID() string { return "Query:AsGetRequest" }
func (m *asGetRequest) HandleSerialize(
ctx context.Context, input middleware.SerializeInput, next middleware.SerializeHandler,
) (
out middleware.SerializeOutput, metadata middleware.Metadata, err error,
) {
req, ok := input.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, fmt.Errorf("expect smithy HTTP Request, got %T", input.Request)
}
req.Method = "GET"
// If the stream is not set, nothing else to do.
stream := req.GetStream()
if stream == nil {
return next.HandleSerialize(ctx, input)
}
// Clear the stream since there will not be any body.
req.Header.Del("Content-Type")
req, err = req.SetStream(nil)
if err != nil {
return out, metadata, fmt.Errorf("unable update request body %w", err)
}
input.Request = req
// Update request query with the body's query string value.
delim := ""
if len(req.URL.RawQuery) != 0 {
delim = "&"
}
b, err := ioutil.ReadAll(stream)
if err != nil {
return out, metadata, fmt.Errorf("unable to get request body %w", err)
}
req.URL.RawQuery += delim + string(b)
return next.HandleSerialize(ctx, input)
}

View File

@ -0,0 +1,56 @@
package query
import (
"fmt"
"net/url"
)
// Object represents the encoding of Query structures and unions. A Query
// object is a representation of a mapping of string keys to arbitrary
// values where there is a fixed set of keys whose values each have their
// own known type. A serialized object might look like the following:
//
// ObjectName.Foo=value
// &ObjectName.Bar=5
type Object struct {
// The query values to add the object to.
values url.Values
// The object's prefix, which includes the names of all parent structures
// and ends with the name of the object. For example, the prefix might be
// "ParentStructure.ObjectName". This prefix will be used to form the full
// keys for each member of the object. For example, a member might have the
// key "ParentStructure.ObjectName.MemberName".
//
// While this is currently represented as a string that gets added to, it
// could also be represented as a stack that only gets condensed into a
// string when a finalized key is created. This could potentially reduce
// allocations.
prefix string
}
func newObject(values url.Values, prefix string) *Object {
return &Object{
values: values,
prefix: prefix,
}
}
// Key adds the given named key to the Query object.
// Returns a Value encoder that should be used to encode a Query value type.
func (o *Object) Key(name string) Value {
return o.key(name, false)
}
// FlatKey adds the given named key to the Query object.
// Returns a Value encoder that should be used to encode a Query value type. The
// value will be flattened if it is a map or array.
func (o *Object) FlatKey(name string) Value {
return o.key(name, true)
}
func (o *Object) key(name string, flatValue bool) Value {
if o.prefix != "" {
return newValue(o.values, fmt.Sprintf("%s.%s", o.prefix, name), flatValue)
}
return newValue(o.values, name, flatValue)
}

View File

@ -0,0 +1,106 @@
package query
import (
"math/big"
"net/url"
"github.com/aws/smithy-go/encoding/httpbinding"
)
// Value represents a Query Value type.
type Value struct {
// The query values to add the value to.
values url.Values
// The value's key, which will form the prefix for complex types.
key string
// Whether the value should be flattened or not if it's a flattenable type.
flat bool
queryValue httpbinding.QueryValue
}
func newValue(values url.Values, key string, flat bool) Value {
return Value{
values: values,
key: key,
flat: flat,
queryValue: httpbinding.NewQueryValue(values, key, false),
}
}
func newBaseValue(values url.Values) Value {
return Value{
values: values,
queryValue: httpbinding.NewQueryValue(nil, "", false),
}
}
// Array returns a new Array encoder.
func (qv Value) Array(locationName string) *Array {
return newArray(qv.values, qv.key, qv.flat, locationName)
}
// Object returns a new Object encoder.
func (qv Value) Object() *Object {
return newObject(qv.values, qv.key)
}
// Map returns a new Map encoder.
func (qv Value) Map(keyLocationName string, valueLocationName string) *Map {
return newMap(qv.values, qv.key, qv.flat, keyLocationName, valueLocationName)
}
// Base64EncodeBytes encodes v as a base64 query string value.
// This is intended to enable compatibility with the JSON encoder.
func (qv Value) Base64EncodeBytes(v []byte) {
qv.queryValue.Blob(v)
}
// Boolean encodes v as a query string value
func (qv Value) Boolean(v bool) {
qv.queryValue.Boolean(v)
}
// String encodes v as a query string value
func (qv Value) String(v string) {
qv.queryValue.String(v)
}
// Byte encodes v as a query string value
func (qv Value) Byte(v int8) {
qv.queryValue.Byte(v)
}
// Short encodes v as a query string value
func (qv Value) Short(v int16) {
qv.queryValue.Short(v)
}
// Integer encodes v as a query string value
func (qv Value) Integer(v int32) {
qv.queryValue.Integer(v)
}
// Long encodes v as a query string value
func (qv Value) Long(v int64) {
qv.queryValue.Long(v)
}
// Float encodes v as a query string value
func (qv Value) Float(v float32) {
qv.queryValue.Float(v)
}
// Double encodes v as a query string value
func (qv Value) Double(v float64) {
qv.queryValue.Double(v)
}
// BigInteger encodes v as a query string value
func (qv Value) BigInteger(v *big.Int) {
qv.queryValue.BigInteger(v)
}
// BigDecimal encodes v as a query string value
func (qv Value) BigDecimal(v *big.Float) {
qv.queryValue.BigDecimal(v)
}

View File

@ -0,0 +1,56 @@
package xml
import (
"encoding/xml"
"fmt"
"io"
)
// ErrorComponents represents the error response fields
// that will be deserialized from an xml error response body
type ErrorComponents struct {
Code string
Message string
RequestID string
}
// GetErrorResponseComponents returns the error fields from an xml error response body
func GetErrorResponseComponents(r io.Reader, noErrorWrapping bool) (ErrorComponents, error) {
if noErrorWrapping {
var errResponse noWrappedErrorResponse
if err := xml.NewDecoder(r).Decode(&errResponse); err != nil && err != io.EOF {
return ErrorComponents{}, fmt.Errorf("error while deserializing xml error response: %w", err)
}
return ErrorComponents{
Code: errResponse.Code,
Message: errResponse.Message,
RequestID: errResponse.RequestID,
}, nil
}
var errResponse wrappedErrorResponse
if err := xml.NewDecoder(r).Decode(&errResponse); err != nil && err != io.EOF {
return ErrorComponents{}, fmt.Errorf("error while deserializing xml error response: %w", err)
}
return ErrorComponents{
Code: errResponse.Code,
Message: errResponse.Message,
RequestID: errResponse.RequestID,
}, nil
}
// noWrappedErrorResponse represents the error response body with
// no internal <Error></Error wrapping
type noWrappedErrorResponse struct {
Code string `xml:"Code"`
Message string `xml:"Message"`
RequestID string `xml:"RequestId"`
}
// wrappedErrorResponse represents the error response body
// wrapped within <Error>...</Error>
type wrappedErrorResponse struct {
Code string `xml:"Error>Code"`
Message string `xml:"Error>Message"`
RequestID string `xml:"RequestId"`
}