github.com/minio/console@v1.4.1/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/v3/policy/condition" 31 32 minioIAMPolicy "github.com/minio/pkg/v3/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 // Allow Create Access Key when admin:CreateServiceAccount is provided with a condition 143 for _, statement := range policy.Statements { 144 if statement.Effect == "Deny" && len(statement.Conditions) > 0 && 145 statement.Actions.Contains(minioIAMPolicy.CreateServiceAccountAdminAction) { 146 defaultActions.Add(minioIAMPolicy.Action(minioIAMPolicy.CreateServiceAccountAdminAction)) 147 } 148 } 149 150 permissions := map[string]minioIAMPolicy.ActionSet{ 151 ConsoleResourceName: defaultActions, 152 } 153 deniedActions := map[string]minioIAMPolicy.ActionSet{} 154 155 var allowResources []*models.PermissionResource 156 157 for _, statement := range policy.Statements { 158 for _, resource := range statement.Resources.ToSlice() { 159 resourceName := resource.String() 160 statementActions := statement.Actions.ToSlice() 161 var prefixes []string 162 163 if statement.Effect == "Allow" { 164 // check if val are denied before adding them to the map 165 var allowedActions []minioIAMPolicy.Action 166 if dActions, ok := deniedActions[resourceName]; ok { 167 for _, action := range statementActions { 168 if len(dActions.Intersection(minioIAMPolicy.NewActionSet(action))) == 0 { 169 // It's ok to allow this action 170 allowedActions = append(allowedActions, action) 171 } 172 } 173 } else { 174 allowedActions = statementActions 175 } 176 177 // Add validated actions 178 if resourceActions, ok := permissions[resourceName]; ok { 179 mergedActions := append(resourceActions.ToSlice(), allowedActions...) 180 permissions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...) 181 } else { 182 mergedActions := append(defaultActions.ToSlice(), allowedActions...) 183 permissions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...) 184 } 185 186 // Allow Permissions request 187 conditions, err := statement.Conditions.MarshalJSON() 188 if err != nil { 189 return nil, ErrorWithContext(ctx, err) 190 } 191 192 var wrapper map[string]Conditions 193 194 if err := json.Unmarshal(conditions, &wrapper); err != nil { 195 return nil, ErrorWithContext(ctx, err) 196 } 197 198 for condition, elements := range wrapper { 199 prefixes = elements.S3Prefix 200 201 resourceElement := models.PermissionResource{ 202 Resource: resourceName, 203 Prefixes: prefixes, 204 ConditionOperator: condition, 205 } 206 207 allowResources = append(allowResources, &resourceElement) 208 } 209 } else { 210 // Add new banned actions to the map 211 if resourceActions, ok := deniedActions[resourceName]; ok { 212 mergedActions := append(resourceActions.ToSlice(), statementActions...) 213 deniedActions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...) 214 } else { 215 deniedActions[resourceName] = statement.Actions 216 } 217 // Remove existing val from key if necessary 218 if currentResourceActions, ok := permissions[resourceName]; ok { 219 var newAllowedActions []minioIAMPolicy.Action 220 for _, action := range currentResourceActions.ToSlice() { 221 if len(deniedActions[resourceName].Intersection(minioIAMPolicy.NewActionSet(action))) == 0 { 222 // It's ok to allow this action 223 newAllowedActions = append(newAllowedActions, action) 224 } 225 } 226 permissions[resourceName] = minioIAMPolicy.NewActionSet(newAllowedActions...) 227 } 228 } 229 } 230 } 231 resourcePermissions := map[string][]string{} 232 for key, val := range permissions { 233 var resourceActions []string 234 for _, action := range val.ToSlice() { 235 resourceActions = append(resourceActions, string(action)) 236 } 237 resourcePermissions[key] = resourceActions 238 239 } 240 serializedPolicy, err := json.Marshal(policy) 241 if err != nil { 242 return nil, ErrorWithContext(ctx, err, ErrInvalidSession) 243 } 244 var sessionPolicy *models.IamPolicy 245 err = json.Unmarshal(serializedPolicy, &sessionPolicy) 246 if err != nil { 247 return nil, ErrorWithContext(ctx, err) 248 } 249 250 // environment constants 251 var envConstants models.EnvironmentConstants 252 253 envConstants.MaxConcurrentUploads = getMaxConcurrentUploadsLimit() 254 envConstants.MaxConcurrentDownloads = getMaxConcurrentDownloadsLimit() 255 256 sessionResp := &models.SessionResponse{ 257 Features: getListOfEnabledFeatures(ctx, userAdminClient, session), 258 Status: models.SessionResponseStatusOk, 259 Operator: false, 260 DistributedMode: erasure, 261 Permissions: resourcePermissions, 262 AllowResources: allowResources, 263 CustomStyles: customStyles, 264 EnvConstants: &envConstants, 265 ServerEndPoint: getMinIOServer(), 266 } 267 return sessionResp, nil 268 } 269 270 // getListOfEnabledFeatures returns a list of features 271 func getListOfEnabledFeatures(ctx context.Context, minioClient MinioAdmin, session *models.Principal) []string { 272 features := []string{} 273 logSearchURL := getLogSearchURL() 274 oidcEnabled := oauth2.IsIDPEnabled() 275 ldapEnabled := ldap.GetLDAPEnabled() 276 277 if logSearchURL != "" { 278 features = append(features, "log-search") 279 } 280 if oidcEnabled { 281 features = append(features, "oidc-idp", "external-idp") 282 } 283 if ldapEnabled { 284 features = append(features, "ldap-idp", "external-idp") 285 } 286 287 if session.Hm { 288 features = append(features, "hide-menu") 289 } 290 if session.Ob { 291 features = append(features, "object-browser-only") 292 } 293 if minioClient != nil { 294 _, err := minioClient.kmsStatus(ctx) 295 if err == nil { 296 features = append(features, "kms") 297 } 298 } 299 300 return features 301 }