STS tokens can be obtained by using local APIs once the remote JWT token is presented, current code was not validating the incoming token in the first place and was incorrectly making a network operation using that token. For the most part this always works without issues, but under adversarial scenarios it exposes client to hand-craft a request that can reach internal services without authentication. This kind of proxying should be avoided before validating the incoming token.
Unified · Split
| @@ -2125,6 +2125,12 @@ func toAPIError(ctx context.Context, err error) APIError { · 2124 unmodified lines | ||
| 2125 | 2125 | HTTPStatusCode: e.Response().StatusCode, |
| 2126 | 2126 | } |
| 2127 | 2127 | // Add more Gateway SDKs here if any in future. |
| 2128 | + default: | |
| 2129 | + apiErr = APIError{ | |
| 2130 | + Code: apiErr.Code, | |
| 2131 | + Description: fmt.Sprintf("%s: cause(%v)", apiErr.Description, err), | |
| 2132 | + HTTPStatusCode: apiErr.HTTPStatusCode, | |
| 2133 | + } | |
| 2128 | 2134 | } |
| 2129 | 2135 | } |
| 2130 | 2136 | |
| @@ -20,7 +20,6 @@ import ( · 19 unmodified lines | ||
| 20 | 20 | "context" |
| 21 | 21 | "crypto/subtle" |
| 22 | 22 | "encoding/json" |
| 23 | − "encoding/xml" | |
| 24 | 23 | "errors" |
| 25 | 24 | "fmt" |
| 26 | 25 | "io" |
| @@ -2202,54 +2201,61 @@ type LoginSTSArgs struct { · 2175 unmodified lines | ||
| 2202 | 2201 | Token string `json:"token" form:"token"` |
| 2203 | 2202 | } |
| 2204 | 2203 | |
| 2204 | +var errSTSNotInitialized = errors.New("STS API not initialized, please configure STS support") | |
| 2205 | + | |
| 2205 | 2206 | // LoginSTS - STS user login handler. |
| 2206 | 2207 | func (web *webAPIHandlers) LoginSTS(r *http.Request, args *LoginSTSArgs, reply *LoginRep) error { |
| 2207 | 2208 | ctx := newWebContext(r, args, "WebLoginSTS") |
| 2208 | 2209 | |
| 2209 | − v := url.Values{} | |
| 2210 | − v.Set("Action", webIdentity) | |
| 2211 | − v.Set("WebIdentityToken", args.Token) | |
| 2212 | − v.Set("Version", stsAPIVersion) | |
| 2213 | − | |
| 2214 | − scheme := "http" | |
| 2215 | − if sourceScheme := handlers.GetSourceScheme(r); sourceScheme != "" { | |
| 2216 | − scheme = sourceScheme | |
| 2217 | − } | |
| 2218 | − if globalIsTLS { | |
| 2219 | − scheme = "https" | |
| 2210 | + if globalOpenIDValidators == nil { | |
| 2211 | + return toJSONError(ctx, errSTSNotInitialized) | |
| 2220 | 2212 | } |
| 2221 | 2213 | |
| 2222 | − u := &url.URL{ | |
| 2223 | − Scheme: scheme, | |
| 2224 | − Host: r.Host, | |
| 2214 | + v, err := globalOpenIDValidators.Get("jwt") | |
| 2215 | + if err != nil { | |
| 2216 | + logger.LogIf(ctx, err) | |
| 2217 | + return toJSONError(ctx, errSTSNotInitialized) | |
| 2225 | 2218 | } |
| 2226 | 2219 | |
| 2227 | − u.RawQuery = v.Encode() | |
| 2228 | − | |
| 2229 | − req, err := http.NewRequest(http.MethodPost, u.String(), nil) | |
| 2220 | + m, err := v.Validate(args.Token, "") | |
| 2230 | 2221 | if err != nil { |
| 2231 | 2222 | return toJSONError(ctx, err) |
| 2232 | 2223 | } |
| 2233 | 2224 | |
| 2234 | − clnt := &http.Client{ | |
| 2235 | − Transport: NewGatewayHTTPTransport(), | |
| 2225 | + // JWT has requested a custom claim with policy value set. | |
| 2226 | + // This is a MinIO STS API specific value, this value should | |
| 2227 | + // be set and configured on your identity provider as part of | |
| 2228 | + // JWT custom claims. | |
| 2229 | + var policyName string | |
| 2230 | + policySet, ok := iampolicy.GetPoliciesFromClaims(m, iamPolicyClaimNameOpenID()) | |
| 2231 | + if ok { | |
| 2232 | + policyName = globalIAMSys.CurrentPolicies(strings.Join(policySet.ToSlice(), ",")) | |
| 2236 | 2233 | } |
| 2237 | − resp, err := clnt.Do(req) | |
| 2234 | + if policyName == "" && globalPolicyOPA == nil { | |
| 2235 | + return toJSONError(ctx, fmt.Errorf("%s claim missing from the JWT token, credentials will not be generated", iamPolicyClaimNameOpenID())) | |
| 2236 | + } | |
| 2237 | + m[iamPolicyClaimNameOpenID()] = policyName | |
| 2238 | + | |
| 2239 | + secret := globalActiveCred.SecretKey | |
| 2240 | + cred, err := auth.GetNewCredentialsWithMetadata(m, secret) | |
| 2238 | 2241 | if err != nil { |
| 2239 | 2242 | return toJSONError(ctx, err) |
| 2240 | 2243 | } |
| 2241 | − defer xhttp.DrainBody(resp.Body) | |
| 2242 | 2244 | |
| 2243 | − if resp.StatusCode != http.StatusOK { | |
| 2244 | − return toJSONError(ctx, errors.New(resp.Status)) | |
| 2245 | + // Set the newly generated credentials. | |
| 2246 | + if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil { | |
| 2247 | + return toJSONError(ctx, err) | |
| 2245 | 2248 | } |
| 2246 | 2249 | |
| 2247 | − a := AssumeRoleWithWebIdentityResponse{} | |
| 2248 | − if err = xml.NewDecoder(resp.Body).Decode(&a); err != nil { | |
| 2249 | − return toJSONError(ctx, err) | |
| 2250 | + // Notify all other MinIO peers to reload temp users | |
| 2251 | + for _, nerr := range globalNotificationSys.LoadUser(cred.AccessKey, true) { | |
| 2252 | + if nerr.Err != nil { | |
| 2253 | + logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) | |
| 2254 | + logger.LogIf(ctx, nerr.Err) | |
| 2255 | + } | |
| 2250 | 2256 | } |
| 2251 | 2257 | |
| 2252 | − reply.Token = a.Result.Credentials.SessionToken | |
| 2258 | + reply.Token = cred.SessionToken | |
| 2253 | 2259 | reply.UIVersion = browser.UIVersion |
| 2254 | 2260 | return nil |
| 2255 | 2261 | } |
| @@ -2304,6 +2310,8 @@ func toWebAPIError(ctx context.Context, err error) APIError { · 48 unmodified lines | ||
| 2304 | 2310 | HTTPStatusCode: http.StatusBadRequest, |
| 2305 | 2311 | Description: err.Error(), |
| 2306 | 2312 | } |
| 2313 | + case errSTSNotInitialized: | |
| 2314 | + return APIError(stsErrCodes.ToSTSErr(ErrSTSNotInitialized)) | |
| 2307 | 2315 | case errServerNotInitialized: |
| 2308 | 2316 | return APIError{ |
| 2309 | 2317 | Code: "XMinioServerNotInitialized", |