mirror of
https://github.com/minio/minio.git
synced 2025-11-07 12:52:58 -05:00
Introduce STS client grants API and OPA policy integration (#6168)
This PR introduces two new features - AWS STS compatible STS API named AssumeRoleWithClientGrants ``` POST /?Action=AssumeRoleWithClientGrants&Token=<jwt> ``` This API endpoint returns temporary access credentials, access tokens signature types supported by this API - RSA keys - ECDSA keys Fetches the required public key from the JWKS endpoints, provides them as rsa or ecdsa public keys. - External policy engine support, in this case OPA policy engine - Credentials are stored on disks
This commit is contained in:
committed by
kannappanr
parent
16a100b597
commit
54ae364def
@@ -27,6 +27,8 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
)
|
||||
|
||||
// Signature and API related constants.
|
||||
@@ -68,7 +70,14 @@ func doesPolicySignatureV2Match(formValues http.Header) APIErrorCode {
|
||||
cred := globalServerConfig.GetCredential()
|
||||
accessKey := formValues.Get("AWSAccessKeyId")
|
||||
if accessKey != cred.AccessKey {
|
||||
return ErrInvalidAccessKeyID
|
||||
if globalIAMSys == nil {
|
||||
return ErrInvalidAccessKeyID
|
||||
}
|
||||
var ok bool
|
||||
cred, ok = globalIAMSys.GetUser(accessKey)
|
||||
if !ok {
|
||||
return ErrInvalidAccessKeyID
|
||||
}
|
||||
}
|
||||
policy := formValues.Get("Policy")
|
||||
signature := formValues.Get("Signature")
|
||||
@@ -146,7 +155,14 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode {
|
||||
|
||||
// Validate if access key id same.
|
||||
if accessKey != cred.AccessKey {
|
||||
return ErrInvalidAccessKeyID
|
||||
if globalIAMSys == nil {
|
||||
return ErrInvalidAccessKeyID
|
||||
}
|
||||
var ok bool
|
||||
cred, ok = globalIAMSys.GetUser(accessKey)
|
||||
if !ok {
|
||||
return ErrInvalidAccessKeyID
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the request has not expired.
|
||||
@@ -165,7 +181,7 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode {
|
||||
return ErrInvalidRequest
|
||||
}
|
||||
|
||||
expectedSignature := preSignatureV2(r.Method, encodedResource, strings.Join(filteredQueries, "&"), r.Header, expires)
|
||||
expectedSignature := preSignatureV2(cred, r.Method, encodedResource, strings.Join(filteredQueries, "&"), r.Header, expires)
|
||||
if !compareSignatureV2(gotSignature, expectedSignature) {
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
@@ -173,6 +189,29 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode {
|
||||
return ErrNone
|
||||
}
|
||||
|
||||
func getReqAccessKeyV2(r *http.Request) (string, bool, APIErrorCode) {
|
||||
if accessKey := r.URL.Query().Get("AWSAccessKeyId"); accessKey != "" {
|
||||
owner, s3Err := checkKeyValid(accessKey)
|
||||
return accessKey, owner, s3Err
|
||||
}
|
||||
|
||||
// below is V2 Signed Auth header format, splitting on `space` (after the `AWS` string).
|
||||
// Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature
|
||||
authFields := strings.Split(r.Header.Get("Authorization"), " ")
|
||||
if len(authFields) != 2 {
|
||||
return "", false, ErrMissingFields
|
||||
}
|
||||
|
||||
// Then will be splitting on ":", this will seprate `AWSAccessKeyId` and `Signature` string.
|
||||
keySignFields := strings.Split(strings.TrimSpace(authFields[1]), ":")
|
||||
if len(keySignFields) != 2 {
|
||||
return "", false, ErrMissingFields
|
||||
}
|
||||
|
||||
owner, s3Err := checkKeyValid(keySignFields[0])
|
||||
return keySignFields[0], owner, s3Err
|
||||
}
|
||||
|
||||
// Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;
|
||||
// Signature = Base64( HMAC-SHA1( YourSecretKey, UTF-8-Encoding-Of( StringToSign ) ) );
|
||||
//
|
||||
@@ -193,41 +232,43 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode {
|
||||
// - http://docs.aws.amazon.com/AmazonS3/latest/dev/auth-request-sig-v2.html
|
||||
// returns true if matches, false otherwise. if error is not nil then it is always false
|
||||
|
||||
func validateV2AuthHeader(v2Auth string) APIErrorCode {
|
||||
func validateV2AuthHeader(r *http.Request) (auth.Credentials, APIErrorCode) {
|
||||
var cred auth.Credentials
|
||||
v2Auth := r.Header.Get("Authorization")
|
||||
if v2Auth == "" {
|
||||
return ErrAuthHeaderEmpty
|
||||
return cred, ErrAuthHeaderEmpty
|
||||
}
|
||||
|
||||
// Verify if the header algorithm is supported or not.
|
||||
if !strings.HasPrefix(v2Auth, signV2Algorithm) {
|
||||
return ErrSignatureVersionNotSupported
|
||||
return cred, ErrSignatureVersionNotSupported
|
||||
}
|
||||
|
||||
// below is V2 Signed Auth header format, splitting on `space` (after the `AWS` string).
|
||||
// Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature
|
||||
authFields := strings.Split(v2Auth, " ")
|
||||
if len(authFields) != 2 {
|
||||
return ErrMissingFields
|
||||
}
|
||||
|
||||
// Then will be splitting on ":", this will seprate `AWSAccessKeyId` and `Signature` string.
|
||||
keySignFields := strings.Split(strings.TrimSpace(authFields[1]), ":")
|
||||
if len(keySignFields) != 2 {
|
||||
return ErrMissingFields
|
||||
accessKey, owner, apiErr := getReqAccessKeyV2(r)
|
||||
if apiErr != ErrNone {
|
||||
return cred, apiErr
|
||||
}
|
||||
|
||||
cred = globalServerConfig.GetCredential()
|
||||
// Access credentials.
|
||||
cred := globalServerConfig.GetCredential()
|
||||
if keySignFields[0] != cred.AccessKey {
|
||||
return ErrInvalidAccessKeyID
|
||||
if !owner {
|
||||
if globalIAMSys == nil {
|
||||
return cred, ErrInvalidAccessKeyID
|
||||
}
|
||||
var ok bool
|
||||
cred, ok = globalIAMSys.GetUser(accessKey)
|
||||
if !ok {
|
||||
return cred, ErrInvalidAccessKeyID
|
||||
}
|
||||
}
|
||||
|
||||
return ErrNone
|
||||
return cred, ErrNone
|
||||
}
|
||||
|
||||
func doesSignV2Match(r *http.Request) APIErrorCode {
|
||||
v2Auth := r.Header.Get("Authorization")
|
||||
|
||||
if apiError := validateV2AuthHeader(v2Auth); apiError != ErrNone {
|
||||
cred, apiError := validateV2AuthHeader(r)
|
||||
if apiError != ErrNone {
|
||||
return apiError
|
||||
}
|
||||
|
||||
@@ -249,12 +290,12 @@ func doesSignV2Match(r *http.Request) APIErrorCode {
|
||||
return ErrInvalidRequest
|
||||
}
|
||||
|
||||
prefix := fmt.Sprintf("%s %s:", signV2Algorithm, globalServerConfig.GetCredential().AccessKey)
|
||||
prefix := fmt.Sprintf("%s %s:", signV2Algorithm, cred.AccessKey)
|
||||
if !strings.HasPrefix(v2Auth, prefix) {
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
v2Auth = v2Auth[len(prefix):]
|
||||
expectedAuth := signatureV2(r.Method, encodedResource, strings.Join(unescapedQueries, "&"), r.Header)
|
||||
expectedAuth := signatureV2(cred, r.Method, encodedResource, strings.Join(unescapedQueries, "&"), r.Header)
|
||||
if !compareSignatureV2(v2Auth, expectedAuth) {
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
@@ -268,15 +309,13 @@ func calculateSignatureV2(stringToSign string, secret string) string {
|
||||
}
|
||||
|
||||
// Return signature-v2 for the presigned request.
|
||||
func preSignatureV2(method string, encodedResource string, encodedQuery string, headers http.Header, expires string) string {
|
||||
cred := globalServerConfig.GetCredential()
|
||||
func preSignatureV2(cred auth.Credentials, method string, encodedResource string, encodedQuery string, headers http.Header, expires string) string {
|
||||
stringToSign := getStringToSignV2(method, encodedResource, encodedQuery, headers, expires)
|
||||
return calculateSignatureV2(stringToSign, cred.SecretKey)
|
||||
}
|
||||
|
||||
// Return the signature v2 of a given request.
|
||||
func signatureV2(method string, encodedResource string, encodedQuery string, headers http.Header) string {
|
||||
cred := globalServerConfig.GetCredential()
|
||||
func signatureV2(cred auth.Credentials, method string, encodedResource string, encodedQuery string, headers http.Header) string {
|
||||
stringToSign := getStringToSignV2(method, encodedResource, encodedQuery, headers, "")
|
||||
signature := calculateSignatureV2(stringToSign, cred.SecretKey)
|
||||
return signature
|
||||
|
||||
Reference in New Issue
Block a user