github.com/minio/console@v1.3.0/api/user_session.go (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "strconv" 24 "time" 25 26 policies "github.com/minio/console/api/policy" 27 "github.com/minio/madmin-go/v3" 28 29 jwtgo "github.com/golang-jwt/jwt/v4" 30 "github.com/minio/pkg/v2/policy/condition" 31 32 minioIAMPolicy "github.com/minio/pkg/v2/policy" 33 34 "github.com/go-openapi/runtime/middleware" 35 "github.com/minio/console/api/operations" 36 authApi "github.com/minio/console/api/operations/auth" 37 "github.com/minio/console/models" 38 "github.com/minio/console/pkg/auth/idp/oauth2" 39 "github.com/minio/console/pkg/auth/ldap" 40 ) 41 42 type Conditions struct { 43 S3Prefix []string `json:"s3:prefix"` 44 } 45 46 func registerSessionHandlers(api *operations.ConsoleAPI) { 47 // session check 48 api.AuthSessionCheckHandler = authApi.SessionCheckHandlerFunc(func(params authApi.SessionCheckParams, session *models.Principal) middleware.Responder { 49 sessionResp, err := getSessionResponse(params.HTTPRequest.Context(), session) 50 if err != nil { 51 return authApi.NewSessionCheckDefault(err.Code).WithPayload(err.APIError) 52 } 53 return authApi.NewSessionCheckOK().WithPayload(sessionResp) 54 }) 55 } 56 57 func getClaimsFromToken(sessionToken string) (map[string]interface{}, error) { 58 jp := new(jwtgo.Parser) 59 // nolint:staticcheck // ignore SA1019 60 jp.ValidMethods = []string{ 61 "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", 62 "RS3256", "RS3384", "RS3512", "ES3256", "ES3384", "ES3512", 63 } 64 var claims jwtgo.MapClaims 65 _, _, err := jp.ParseUnverified(sessionToken, &claims) 66 if err != nil { 67 return nil, err 68 } 69 return claims, nil 70 } 71 72 // getSessionResponse parse the token of the current session and returns a list of allowed actions to render in the UI 73 func getSessionResponse(ctx context.Context, session *models.Principal) (*models.SessionResponse, *CodedAPIError) { 74 ctx, cancel := context.WithCancel(ctx) 75 defer cancel() 76 77 // serialize output 78 if session == nil { 79 return nil, ErrorWithContext(ctx, ErrInvalidSession) 80 } 81 tokenClaims, _ := getClaimsFromToken(session.STSSessionToken) 82 // initialize admin client 83 mAdminClient, err := NewMinioAdminClient(ctx, &models.Principal{ 84 STSAccessKeyID: session.STSAccessKeyID, 85 STSSecretAccessKey: session.STSSecretAccessKey, 86 STSSessionToken: session.STSSessionToken, 87 }) 88 if err != nil { 89 return nil, ErrorWithContext(ctx, err, ErrInvalidSession) 90 } 91 userAdminClient := AdminClient{Client: mAdminClient} 92 // Obtain the current policy assigned to this user 93 // necessary for generating the list of allowed endpoints 94 accountInfo, err := getAccountInfo(ctx, userAdminClient) 95 if err != nil { 96 return nil, ErrorWithContext(ctx, err, ErrInvalidSession) 97 } 98 erasure := accountInfo.Server.Type == madmin.Erasure 99 rawPolicy := policies.ReplacePolicyVariables(tokenClaims, accountInfo) 100 policy, err := minioIAMPolicy.ParseConfig(bytes.NewReader(rawPolicy)) 101 if err != nil { 102 return nil, ErrorWithContext(ctx, err, ErrInvalidSession) 103 } 104 currTime := time.Now().UTC() 105 106 customStyles := session.CustomStyleOb 107 // This actions will be global, meaning has to be attached to all resources 108 conditionValues := map[string][]string{ 109 condition.AWSUsername.Name(): {session.AccountAccessKey}, 110 // All calls to MinIO from console use temporary credentials. 111 condition.AWSPrincipalType.Name(): {"AssumeRole"}, 112 condition.AWSSecureTransport.Name(): {strconv.FormatBool(getMinIOEndpointIsSecure())}, 113 condition.AWSCurrentTime.Name(): {currTime.Format(time.RFC3339)}, 114 condition.AWSEpochTime.Name(): {strconv.FormatInt(currTime.Unix(), 10)}, 115 116 // All calls from console are signature v4. 117 condition.S3SignatureVersion.Name(): {"AWS4-HMAC-SHA256"}, 118 // All calls from console are signature v4. 119 condition.S3AuthType.Name(): {"REST-HEADER"}, 120 // This is usually empty, may be set some times (rare). 121 condition.S3LocationConstraint.Name(): {GetMinIORegion()}, 122 } 123 124 claims, err := getClaimsFromToken(session.STSSessionToken) 125 if err != nil { 126 return nil, ErrorWithContext(ctx, err, ErrInvalidSession) 127 } 128 129 // Support all LDAP, JWT variables 130 for k, v := range claims { 131 vstr, ok := v.(string) 132 if !ok { 133 // skip all non-strings 134 continue 135 } 136 // store all claims from sessionToken 137 conditionValues[k] = []string{vstr} 138 } 139 140 defaultActions := policy.IsAllowedActions("", "", conditionValues) 141 142 permissions := map[string]minioIAMPolicy.ActionSet{ 143 ConsoleResourceName: defaultActions, 144 } 145 deniedActions := map[string]minioIAMPolicy.ActionSet{} 146 147 var allowResources []*models.PermissionResource 148 149 for _, statement := range policy.Statements { 150 for _, resource := range statement.Resources.ToSlice() { 151 resourceName := resource.String() 152 statementActions := statement.Actions.ToSlice() 153 var prefixes []string 154 155 if statement.Effect == "Allow" { 156 // check if val are denied before adding them to the map 157 var allowedActions []minioIAMPolicy.Action 158 if dActions, ok := deniedActions[resourceName]; ok { 159 for _, action := range statementActions { 160 if len(dActions.Intersection(minioIAMPolicy.NewActionSet(action))) == 0 { 161 // It's ok to allow this action 162 allowedActions = append(allowedActions, action) 163 } 164 } 165 } else { 166 allowedActions = statementActions 167 } 168 169 // Add validated actions 170 if resourceActions, ok := permissions[resourceName]; ok { 171 mergedActions := append(resourceActions.ToSlice(), allowedActions...) 172 permissions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...) 173 } else { 174 mergedActions := append(defaultActions.ToSlice(), allowedActions...) 175 permissions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...) 176 } 177 178 // Allow Permissions request 179 conditions, err := statement.Conditions.MarshalJSON() 180 if err != nil { 181 return nil, ErrorWithContext(ctx, err) 182 } 183 184 var wrapper map[string]Conditions 185 186 if err := json.Unmarshal(conditions, &wrapper); err != nil { 187 return nil, ErrorWithContext(ctx, err) 188 } 189 190 for condition, elements := range wrapper { 191 prefixes = elements.S3Prefix 192 193 resourceElement := models.PermissionResource{ 194 Resource: resourceName, 195 Prefixes: prefixes, 196 ConditionOperator: condition, 197 } 198 199 allowResources = append(allowResources, &resourceElement) 200 } 201 } else { 202 // Add new banned actions to the map 203 if resourceActions, ok := deniedActions[resourceName]; ok { 204 mergedActions := append(resourceActions.ToSlice(), statementActions...) 205 deniedActions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...) 206 } else { 207 deniedActions[resourceName] = statement.Actions 208 } 209 // Remove existing val from key if necessary 210 if currentResourceActions, ok := permissions[resourceName]; ok { 211 var newAllowedActions []minioIAMPolicy.Action 212 for _, action := range currentResourceActions.ToSlice() { 213 if len(deniedActions[resourceName].Intersection(minioIAMPolicy.NewActionSet(action))) == 0 { 214 // It's ok to allow this action 215 newAllowedActions = append(newAllowedActions, action) 216 } 217 } 218 permissions[resourceName] = minioIAMPolicy.NewActionSet(newAllowedActions...) 219 } 220 } 221 } 222 } 223 resourcePermissions := map[string][]string{} 224 for key, val := range permissions { 225 var resourceActions []string 226 for _, action := range val.ToSlice() { 227 resourceActions = append(resourceActions, string(action)) 228 } 229 resourcePermissions[key] = resourceActions 230 231 } 232 serializedPolicy, err := json.Marshal(policy) 233 if err != nil { 234 return nil, ErrorWithContext(ctx, err, ErrInvalidSession) 235 } 236 var sessionPolicy *models.IamPolicy 237 err = json.Unmarshal(serializedPolicy, &sessionPolicy) 238 if err != nil { 239 return nil, ErrorWithContext(ctx, err) 240 } 241 242 // environment constants 243 var envConstants models.EnvironmentConstants 244 245 envConstants.MaxConcurrentUploads = getMaxConcurrentUploadsLimit() 246 envConstants.MaxConcurrentDownloads = getMaxConcurrentDownloadsLimit() 247 248 sessionResp := &models.SessionResponse{ 249 Features: getListOfEnabledFeatures(ctx, userAdminClient, session), 250 Status: models.SessionResponseStatusOk, 251 Operator: false, 252 DistributedMode: erasure, 253 Permissions: resourcePermissions, 254 AllowResources: allowResources, 255 CustomStyles: customStyles, 256 EnvConstants: &envConstants, 257 ServerEndPoint: getMinIOServer(), 258 } 259 return sessionResp, nil 260 } 261 262 // getListOfEnabledFeatures returns a list of features 263 func getListOfEnabledFeatures(ctx context.Context, minioClient MinioAdmin, session *models.Principal) []string { 264 features := []string{} 265 logSearchURL := getLogSearchURL() 266 oidcEnabled := oauth2.IsIDPEnabled() 267 ldapEnabled := ldap.GetLDAPEnabled() 268 269 if logSearchURL != "" { 270 features = append(features, "log-search") 271 } 272 if oidcEnabled { 273 features = append(features, "oidc-idp", "external-idp") 274 } 275 if ldapEnabled { 276 features = append(features, "ldap-idp", "external-idp") 277 } 278 279 if session.Hm { 280 features = append(features, "hide-menu") 281 } 282 if session.Ob { 283 features = append(features, "object-browser-only") 284 } 285 if minioClient != nil { 286 _, err := minioClient.kmsStatus(ctx) 287 if err == nil { 288 features = append(features, "kms") 289 } 290 } 291 292 return features 293 }