mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
Add support for session policies in STS APIs (#7747)
This PR adds support for adding session policies for further restrictions on STS credentials, useful in situations when applications want to generate creds for multiple interested parties with different set of policy restrictions. This session policy is not mandatory, but optional. Fixes #7732
This commit is contained in:
parent
98d3913a1e
commit
1af6e8cb72
@ -200,6 +200,39 @@ func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) {
|
||||
if _, ok = v.(string); !ok {
|
||||
return nil, errInvalidAccessKeyID
|
||||
}
|
||||
|
||||
if globalPolicyOPA == nil {
|
||||
// If OPA is not set, session token should
|
||||
// have a policy and its mandatory, reject
|
||||
// requests without policy claim.
|
||||
p, pok := claims[iampolicy.PolicyName]
|
||||
if !pok {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
if _, pok = p.(string); !pok {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
sp, spok := claims[iampolicy.SessionPolicyName]
|
||||
// Sub policy is optional, if not set return success.
|
||||
if !spok {
|
||||
return claims, nil
|
||||
}
|
||||
// Sub policy is set but its not a string, reject such requests
|
||||
spStr, spok := sp.(string)
|
||||
if !spok {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
// Looks like subpolicy is set and is a string, if set then its
|
||||
// base64 encoded, decode it. Decoding fails reject such requests.
|
||||
spBytes, err := base64.StdEncoding.DecodeString(spStr)
|
||||
if err != nil {
|
||||
// Base64 decoding fails, we should log to indicate
|
||||
// something is malforming the request sent by client.
|
||||
logger.LogIf(context.Background(), err)
|
||||
return nil, errAuthentication
|
||||
}
|
||||
claims[iampolicy.SessionPolicyName] = string(spBytes)
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
|
76
cmd/iam.go
76
cmd/iam.go
@ -17,6 +17,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"path"
|
||||
@ -649,16 +650,87 @@ func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
|
||||
return cred, ok && cred.IsValid()
|
||||
}
|
||||
|
||||
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
||||
func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool {
|
||||
// IsAllowedSTS is meant for STS based temporary credentials,
|
||||
// which implements claims validation and verification other than
|
||||
// applying policies.
|
||||
func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool {
|
||||
pname, ok := args.Claims[iampolicy.PolicyName]
|
||||
if !ok {
|
||||
// When claims are set, it should have a "policy" field.
|
||||
return false
|
||||
}
|
||||
pnameStr, ok := pname.(string)
|
||||
if !ok {
|
||||
// When claims has "policy" field, it should be string.
|
||||
return false
|
||||
}
|
||||
|
||||
sys.RLock()
|
||||
defer sys.RUnlock()
|
||||
|
||||
// If policy is available for given user, check the policy.
|
||||
name, ok := sys.iamPolicyMap[args.AccountName]
|
||||
if !ok {
|
||||
// No policy available reject.
|
||||
return false
|
||||
}
|
||||
|
||||
if pnameStr != name {
|
||||
// When claims has a policy, it should match the
|
||||
// policy of args.AccountName which server remembers.
|
||||
// if not reject such requests.
|
||||
return false
|
||||
}
|
||||
|
||||
// Now check if we have a sessionPolicy.
|
||||
spolicy, ok := args.Claims[iampolicy.SessionPolicyName]
|
||||
if !ok {
|
||||
// Sub policy not set, this is most common since subPolicy
|
||||
// is optional, use the top level policy only.
|
||||
p, ok := sys.iamCannedPolicyMap[pnameStr]
|
||||
return ok && p.IsAllowed(args)
|
||||
}
|
||||
|
||||
spolicyStr, ok := spolicy.(string)
|
||||
if !ok {
|
||||
// Sub policy if set, should be a string reject
|
||||
// malformed/malicious requests.
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if policy is parseable.
|
||||
subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
|
||||
if err != nil {
|
||||
// Log any error in input session policy config.
|
||||
logger.LogIf(context.Background(), err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Policy without Version string value reject it.
|
||||
if subPolicy.Version == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Sub policy is set and valid.
|
||||
p, ok := sys.iamCannedPolicyMap[pnameStr]
|
||||
return ok && p.IsAllowed(args) && subPolicy.IsAllowed(args)
|
||||
}
|
||||
|
||||
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
||||
func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool {
|
||||
// If opa is configured, use OPA always.
|
||||
if globalPolicyOPA != nil {
|
||||
return globalPolicyOPA.IsAllowed(args)
|
||||
}
|
||||
|
||||
// With claims set, we should do STS related checks and validation.
|
||||
if len(args.Claims) > 0 {
|
||||
return sys.IsAllowedSTS(args)
|
||||
}
|
||||
|
||||
sys.RLock()
|
||||
defer sys.RUnlock()
|
||||
|
||||
// If policy is available for given user, check the policy.
|
||||
if name, found := sys.iamPolicyMap[args.AccountName]; found {
|
||||
p, ok := sys.iamCannedPolicyMap[name]
|
||||
|
@ -17,13 +17,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
"github.com/minio/minio/pkg/iam/validator"
|
||||
"github.com/minio/minio/pkg/wildcard"
|
||||
)
|
||||
@ -124,11 +127,6 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.Form.Get("Policy") != "" {
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
|
||||
if r.Form.Get("Version") != stsAPIVersion {
|
||||
logger.LogIf(ctx, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSMissingParameter))
|
||||
@ -147,6 +145,29 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
|
||||
ctx = newContext(r, w, action)
|
||||
defer logger.AuditLog(w, r, action, nil)
|
||||
|
||||
sessionPolicyStr := r.Form.Get("Policy")
|
||||
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
|
||||
// The plain text that you use for both inline and managed session
|
||||
// policies shouldn't exceed 2048 characters.
|
||||
if len(sessionPolicyStr) > 2048 {
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
|
||||
if len(sessionPolicyStr) > 0 {
|
||||
sessionPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr)))
|
||||
if err != nil {
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
|
||||
// Version in policy must not be empty
|
||||
if sessionPolicy.Version == "" {
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
m := make(map[string]interface{})
|
||||
m["exp"], err = validator.GetDefaultExpiration(r.Form.Get("DurationSeconds"))
|
||||
@ -171,7 +192,11 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
|
||||
// This policy is the policy associated with the user
|
||||
// requesting for temporary credentials. The temporary
|
||||
// credentials will inherit the same policy requirements.
|
||||
m["policy"] = policyName
|
||||
m[iampolicy.PolicyName] = policyName
|
||||
|
||||
if len(sessionPolicyStr) > 0 {
|
||||
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr))
|
||||
}
|
||||
|
||||
secret := globalServerConfig.GetCredential().SecretKey
|
||||
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
|
||||
@ -216,11 +241,6 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
if r.Form.Get("Policy") != "" {
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
|
||||
if r.Form.Get("Version") != stsAPIVersion {
|
||||
logger.LogIf(ctx, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSMissingParameter))
|
||||
@ -276,6 +296,29 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
sessionPolicyStr := r.Form.Get("Policy")
|
||||
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
|
||||
// The plain text that you use for both inline and managed session
|
||||
// policies shouldn't exceed 2048 characters.
|
||||
if len(sessionPolicyStr) > 2048 {
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
|
||||
if len(sessionPolicyStr) > 0 {
|
||||
sessionPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr)))
|
||||
if err != nil {
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
|
||||
// Version in policy must not be empty
|
||||
if sessionPolicy.Version == "" {
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
secret := globalServerConfig.GetCredential().SecretKey
|
||||
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
|
||||
if err != nil {
|
||||
@ -289,7 +332,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
|
||||
// be set and configured on your identity provider as part of
|
||||
// JWT custom claims.
|
||||
var policyName string
|
||||
if v, ok := m["policy"]; ok {
|
||||
if v, ok := m[iampolicy.PolicyName]; ok {
|
||||
policyName, _ = v.(string)
|
||||
}
|
||||
|
||||
@ -298,6 +341,10 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
|
||||
subFromToken, _ = v.(string)
|
||||
}
|
||||
|
||||
if len(sessionPolicyStr) > 0 {
|
||||
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr))
|
||||
}
|
||||
|
||||
// Set the newly generated credentials.
|
||||
if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
|
@ -10,19 +10,28 @@ The temporary security credentials returned by this API consists of an access ke
|
||||
#### DurationSeconds
|
||||
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 12 hours. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds.
|
||||
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *Integer* |
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *Integer* |
|
||||
| *Valid Range* | *Minimum value of 900. Maximum value of 43200.* |
|
||||
| *Required* | *No* |
|
||||
| *Required* | *No* |
|
||||
|
||||
#### Policy
|
||||
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
|
||||
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| *Valid Range* | *Minimum length of 1. Maximum length of 2048.* |
|
||||
| *Required* | *No* |
|
||||
|
||||
#### Version
|
||||
Indicates STS API version information, the only supported value is '2011-06-15'. This value is borrowed from AWS STS API documentation for compatibility reasons.
|
||||
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| *Required* | *Yes* |
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| *Required* | *Yes* |
|
||||
|
||||
#### AUTHPARAMS
|
||||
Indicates STS API Authorization information. If you are familiar with AWS Signature V4 Authorization header, this STS API supports signature V4 authorization as mentioned [here](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
|
||||
@ -35,7 +44,7 @@ XML error response for this API is similar to [AWS STS AssumeRole](https://docs.
|
||||
|
||||
#### Sample Request
|
||||
```
|
||||
http://minio:9000/?Action=AssumeRole&DurationSeconds=3600&Version=2011-06-15&AUTHPARAMS
|
||||
http://minio:9000/?Action=AssumeRole&DurationSeconds=3600&Version=2011-06-15&Policy={"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}]}&AUTHPARAMS
|
||||
```
|
||||
|
||||
#### Sample Response
|
||||
@ -82,7 +91,7 @@ aws_secret_access_key = foo12345
|
||||
> NOTE: In the following commands `--role-arn` and `--role-session-name` are not meaningful for MinIO and can be set to any value satisfying the command line requirements.
|
||||
|
||||
```
|
||||
$ aws --profile foobar --endpoint-url http://localhost:9000 sts assume-role --role-arn arn:xxx:xxx:xxx:xxx --role-session-name anything
|
||||
$ aws --profile foobar --endpoint-url http://localhost:9000 sts assume-role --policy '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}]}' --role-arn arn:xxx:xxx:xxx:xxxx --role-session-name anything
|
||||
{
|
||||
"AssumedRoleUser": {
|
||||
"Arn": ""
|
||||
|
@ -9,29 +9,37 @@ By default, the temporary security credentials created by AssumeRoleWithClientGr
|
||||
#### DurationSeconds
|
||||
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 12 hours. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds.
|
||||
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *Integer* |
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *Integer* |
|
||||
| *Valid Range* | *Minimum value of 900. Maximum value of 43200.* |
|
||||
| *Required* | *No* |
|
||||
| *Required* | *No* |
|
||||
|
||||
#### Policy
|
||||
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
|
||||
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| *Valid Range* | *Minimum length of 1. Maximum length of 2048.* |
|
||||
| *Required* | *No* |
|
||||
|
||||
#### Token
|
||||
The OAuth 2.0 access token that is provided by the identity provider. Application must get this token by authenticating the application using client credential grants before the application makes an AssumeRoleWithClientGrants call.
|
||||
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| *Length Constraints* | *Minimum length of 4. Maximum length of 2048.* |
|
||||
| *Required* | *Yes* |
|
||||
| *Required* | *Yes* |
|
||||
|
||||
#### Version
|
||||
Indicates STS API version information, the only supported value is '2011-06-15'. This value is borrowed from AWS STS API documentation for compatibility reasons.
|
||||
|
||||
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| *Required* | *Yes* |
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| *Required* | *Yes* |
|
||||
|
||||
#### Response Elements
|
||||
XML response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_ResponseElements)
|
||||
|
@ -7,28 +7,37 @@ By default, the temporary security credentials created by AssumeRoleWithWebIdent
|
||||
#### DurationSeconds
|
||||
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 12 hours. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds.
|
||||
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *Integer* |
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *Integer* |
|
||||
| *Valid Range* | *Minimum value of 900. Maximum value of 43200.* |
|
||||
| *Required* | *No* |
|
||||
| *Required* | *No* |
|
||||
|
||||
#### Policy
|
||||
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
|
||||
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| *Valid Range* | *Minimum length of 1. Maximum length of 2048.* |
|
||||
| *Required* | *No* |
|
||||
|
||||
#### WebIdentityToken
|
||||
The OAuth 2.0 access token that is provided by the web identity provider. Application must get this token by authenticating the user who is using your application with a web identity provider before the application makes an AssumeRoleWithWebIdentity call.
|
||||
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| *Length Constraints* | *Minimum length of 4. Maximum length of 2048.* |
|
||||
| *Required* | *Yes* |
|
||||
| *Required* | *Yes* |
|
||||
|
||||
#### Version
|
||||
Indicates STS API version information, the only supported value is '2011-06-15'. This value is borrowed from AWS STS API documentation for compatibility reasons.
|
||||
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| *Required* | *Yes* |
|
||||
| Params | Value |
|
||||
| :-- | :-- |
|
||||
| *Type* | *String* |
|
||||
| *Required* | *Yes* |
|
||||
|
||||
#### Response Elements
|
||||
XML response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_ResponseElements)
|
||||
|
@ -20,6 +20,12 @@ import (
|
||||
"github.com/minio/minio/pkg/policy"
|
||||
)
|
||||
|
||||
// Policy claim constants
|
||||
const (
|
||||
PolicyName = "policy"
|
||||
SessionPolicyName = "sessionPolicy"
|
||||
)
|
||||
|
||||
// ReadWrite - provides full access to all buckets and all objects
|
||||
var ReadWrite = Policy{
|
||||
Version: DefaultVersion,
|
||||
|
Loading…
Reference in New Issue
Block a user