mirror of
https://github.com/minio/minio.git
synced 2025-11-08 21:24:55 -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
352
cmd/iam.go
Normal file
352
cmd/iam.go
Normal file
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/iam/policy"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
const (
|
||||
// IAM configuration directory.
|
||||
iamConfigPrefix = minioConfigPrefix + "/iam"
|
||||
|
||||
// IAM users directory.
|
||||
iamConfigUsersPrefix = iamConfigPrefix + "/users/"
|
||||
|
||||
// IAM sts directory.
|
||||
iamConfigSTSPrefix = iamConfigPrefix + "/sts/"
|
||||
|
||||
// IAM identity file which captures identity credentials.
|
||||
iamIdentityFile = "identity.json"
|
||||
|
||||
// IAM policy file which provides policies for each users.
|
||||
iamPolicyFile = "policy.json"
|
||||
)
|
||||
|
||||
// IAMSys - config system.
|
||||
type IAMSys struct {
|
||||
sync.RWMutex
|
||||
iamUsersMap map[string]auth.Credentials
|
||||
iamPolicyMap map[string]iampolicy.Policy
|
||||
}
|
||||
|
||||
// Load - load iam.json
|
||||
func (sys *IAMSys) Load(objAPI ObjectLayer) error {
|
||||
return sys.Init(objAPI)
|
||||
}
|
||||
|
||||
// Init - initializes config system from iam.json
|
||||
func (sys *IAMSys) Init(objAPI ObjectLayer) error {
|
||||
if objAPI == nil {
|
||||
return errInvalidArgument
|
||||
}
|
||||
|
||||
if err := sys.refresh(objAPI); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Refresh IAMSys in background.
|
||||
go func() {
|
||||
ticker := time.NewTicker(globalRefreshIAMInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-globalServiceDoneCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
logger.LogIf(context.Background(), sys.refresh(objAPI))
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// SetPolicy - sets policy to given user name. If policy is empty,
|
||||
// existing policy is removed.
|
||||
func (sys *IAMSys) SetPolicy(accessKey string, p iampolicy.Policy) error {
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
configFile := pathJoin(iamConfigUsersPrefix, accessKey, iamPolicyFile)
|
||||
data, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sys.Lock()
|
||||
defer sys.Unlock()
|
||||
|
||||
if err = saveConfig(context.Background(), objectAPI, configFile, data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.IsEmpty() {
|
||||
delete(sys.iamPolicyMap, accessKey)
|
||||
} else {
|
||||
sys.iamPolicyMap[accessKey] = p
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveTempPolicy - this is used for temporary credentials only.
|
||||
func (sys *IAMSys) SaveTempPolicy(accessKey string, p iampolicy.Policy) error {
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
configFile := pathJoin(iamConfigSTSPrefix, accessKey, iamPolicyFile)
|
||||
data, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sys.Lock()
|
||||
defer sys.Unlock()
|
||||
|
||||
if err = saveConfig(context.Background(), objectAPI, configFile, data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.IsEmpty() {
|
||||
delete(sys.iamPolicyMap, accessKey)
|
||||
} else {
|
||||
sys.iamPolicyMap[accessKey] = p
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeletePolicy - sets policy to given user name. If policy is empty,
|
||||
// existing policy is removed.
|
||||
func (sys *IAMSys) DeletePolicy(accessKey string) error {
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
configFile := pathJoin(iamConfigUsersPrefix, accessKey, iamPolicyFile)
|
||||
|
||||
sys.Lock()
|
||||
defer sys.Unlock()
|
||||
|
||||
err := objectAPI.DeleteObject(context.Background(), minioMetaBucket, configFile)
|
||||
|
||||
delete(sys.iamPolicyMap, accessKey)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteUser - set user credentials.
|
||||
func (sys *IAMSys) DeleteUser(accessKey string) error {
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
sys.Lock()
|
||||
defer sys.Unlock()
|
||||
|
||||
configFile := pathJoin(iamConfigUsersPrefix, accessKey, iamIdentityFile)
|
||||
err := objectAPI.DeleteObject(context.Background(), minioMetaBucket, configFile)
|
||||
|
||||
delete(sys.iamUsersMap, accessKey)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetTempUser - set temporary user credentials, these credentials have an expiry.
|
||||
func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials) error {
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
sys.Lock()
|
||||
defer sys.Unlock()
|
||||
|
||||
configFile := pathJoin(iamConfigSTSPrefix, accessKey, iamIdentityFile)
|
||||
data, err := json.Marshal(cred)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = saveConfig(context.Background(), objectAPI, configFile, data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sys.iamUsersMap[accessKey] = cred
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUser - set user credentials.
|
||||
func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
configFile := pathJoin(iamConfigUsersPrefix, accessKey, iamIdentityFile)
|
||||
data, err := json.Marshal(uinfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sys.Lock()
|
||||
defer sys.Unlock()
|
||||
|
||||
if err = saveConfig(context.Background(), objectAPI, configFile, data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sys.iamUsersMap[accessKey] = auth.Credentials{
|
||||
AccessKey: accessKey,
|
||||
SecretKey: uinfo.SecretKey,
|
||||
Status: string(uinfo.Status),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUser - get user credentials
|
||||
func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
|
||||
sys.RLock()
|
||||
defer sys.RUnlock()
|
||||
|
||||
cred, ok = sys.iamUsersMap[accessKey]
|
||||
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 {
|
||||
sys.RLock()
|
||||
defer sys.RUnlock()
|
||||
|
||||
// If policy is available for given user, check the policy.
|
||||
if p, found := sys.iamPolicyMap[args.AccountName]; found {
|
||||
// If opa is configured, use OPA in conjunction with IAM policies.
|
||||
if globalPolicyOPA != nil {
|
||||
return p.IsAllowed(args) && globalPolicyOPA.IsAllowed(args)
|
||||
}
|
||||
return p.IsAllowed(args)
|
||||
}
|
||||
|
||||
// If no policies are set, let the policy arrive from OPA if any.
|
||||
if globalPolicyOPA != nil {
|
||||
return globalPolicyOPA.IsAllowed(args)
|
||||
}
|
||||
|
||||
// As policy is not available and OPA is not configured, return the owner value.
|
||||
return args.IsOwner
|
||||
}
|
||||
|
||||
// reloadUsers reads an updates users, policies from object layer into user and policy maps.
|
||||
func reloadUsers(objectAPI ObjectLayer, prefix string, usersMap map[string]auth.Credentials, policyMap map[string]iampolicy.Policy) error {
|
||||
marker := ""
|
||||
for {
|
||||
var lo ListObjectsInfo
|
||||
var err error
|
||||
lo, err = objectAPI.ListObjects(context.Background(), minioMetaBucket, prefix, marker, "/", 1000)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
marker = lo.NextMarker
|
||||
for _, prefix := range lo.Prefixes {
|
||||
idFile := pathJoin(prefix, iamIdentityFile)
|
||||
pFile := pathJoin(prefix, iamPolicyFile)
|
||||
cdata, cerr := readConfig(context.Background(), objectAPI, idFile)
|
||||
pdata, perr := readConfig(context.Background(), objectAPI, pFile)
|
||||
if cerr != nil && cerr != errConfigNotFound {
|
||||
return cerr
|
||||
}
|
||||
if perr != nil && perr != errConfigNotFound {
|
||||
return perr
|
||||
}
|
||||
if cerr == errConfigNotFound && perr == errConfigNotFound {
|
||||
continue
|
||||
}
|
||||
if cerr == nil {
|
||||
var cred auth.Credentials
|
||||
if err = json.Unmarshal(cdata, &cred); err != nil {
|
||||
return err
|
||||
}
|
||||
cred.AccessKey = path.Base(prefix)
|
||||
if cred.IsExpired() {
|
||||
// Delete expired identity.
|
||||
objectAPI.DeleteObject(context.Background(), minioMetaBucket, idFile)
|
||||
// Delete expired identity policy.
|
||||
objectAPI.DeleteObject(context.Background(), minioMetaBucket, pFile)
|
||||
continue
|
||||
}
|
||||
usersMap[cred.AccessKey] = cred
|
||||
}
|
||||
if perr == nil {
|
||||
var p iampolicy.Policy
|
||||
if err = json.Unmarshal(pdata, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
policyMap[path.Base(prefix)] = p
|
||||
}
|
||||
}
|
||||
if !lo.IsTruncated {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Refresh IAMSys.
|
||||
func (sys *IAMSys) refresh(objAPI ObjectLayer) error {
|
||||
iamUsersMap := make(map[string]auth.Credentials)
|
||||
iamPolicyMap := make(map[string]iampolicy.Policy)
|
||||
|
||||
if err := reloadUsers(objAPI, iamConfigUsersPrefix, iamUsersMap, iamPolicyMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := reloadUsers(objAPI, iamConfigSTSPrefix, iamUsersMap, iamPolicyMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sys.Lock()
|
||||
defer sys.Unlock()
|
||||
|
||||
sys.iamUsersMap = iamUsersMap
|
||||
sys.iamPolicyMap = iamPolicyMap
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewIAMSys - creates new config system object.
|
||||
func NewIAMSys() *IAMSys {
|
||||
return &IAMSys{
|
||||
iamUsersMap: make(map[string]auth.Credentials),
|
||||
iamPolicyMap: make(map[string]iampolicy.Policy),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user