/* * 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 validator import ( "crypto" "crypto/tls" "encoding/json" "errors" "fmt" "net" "net/http" "os" "strconv" "time" jwtgo "github.com/dgrijalva/jwt-go" xnet "github.com/minio/minio/pkg/net" ) // JWKSArgs - RSA authentication target arguments type JWKSArgs struct { URL *xnet.URL `json:"url"` publicKeys map[string]crypto.PublicKey } // Validate JWT authentication target arguments func (r *JWKSArgs) Validate() error { return nil } // PopulatePublicKey - populates a new publickey from the JWKS URL. func (r *JWKSArgs) PopulatePublicKey() error { insecureClient := &http.Client{Transport: newCustomHTTPTransport(true)} client := &http.Client{Transport: newCustomHTTPTransport(false)} resp, err := client.Get(r.URL.String()) if err != nil { resp, err = insecureClient.Get(r.URL.String()) if err != nil { return err } } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return errors.New(resp.Status) } var jwk JWKS if err = json.NewDecoder(resp.Body).Decode(&jwk); err != nil { return err } r.publicKeys = make(map[string]crypto.PublicKey) for _, key := range jwk.Keys { var publicKey crypto.PublicKey publicKey, err = key.DecodePublicKey() if err != nil { return err } r.publicKeys[key.Kid] = publicKey } return nil } // UnmarshalJSON - decodes JSON data. func (r *JWKSArgs) UnmarshalJSON(data []byte) error { // subtype to avoid recursive call to UnmarshalJSON() type subJWKSArgs JWKSArgs var sr subJWKSArgs // IAM related envs. if jwksURL, ok := os.LookupEnv("MINIO_IAM_JWKS_URL"); ok { u, err := xnet.ParseURL(jwksURL) if err != nil { return err } sr.URL = u } else { if err := json.Unmarshal(data, &sr); err != nil { return err } } ar := JWKSArgs(sr) if ar.URL == nil || ar.URL.String() == "" { *r = ar return nil } if err := ar.Validate(); err != nil { return err } if err := ar.PopulatePublicKey(); err != nil { return err } *r = ar return nil } // JWT - rs client grants provider details. type JWT struct { args JWKSArgs } func expToInt64(expI interface{}) (expAt int64, err error) { switch exp := expI.(type) { case float64: expAt = int64(exp) case int64: expAt = exp case json.Number: expAt, err = exp.Int64() if err != nil { return 0, err } default: return 0, ErrInvalidDuration } return expAt, nil } // GetDefaultExpiration - returns the expiration seconds expected. func GetDefaultExpiration(dsecs string) (time.Duration, error) { defaultExpiryDuration := time.Duration(60) * time.Minute // Defaults to 1hr. if dsecs != "" { expirySecs, err := strconv.ParseInt(dsecs, 10, 64) if err != nil { return 0, ErrInvalidDuration } // The duration, in seconds, of the role session. // The value can range from 900 seconds (15 minutes) // to 12 hours. if expirySecs < 900 || expirySecs > 43200 { return 0, ErrInvalidDuration } defaultExpiryDuration = time.Duration(expirySecs) * time.Second } return defaultExpiryDuration, nil } // newCustomHTTPTransport returns a new http configuration // used while communicating with the cloud backends. // This sets the value for MaxIdleConnsPerHost from 2 (go default) // to 100. func newCustomHTTPTransport(insecure bool) *http.Transport { return &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, MaxIdleConns: 1024, MaxIdleConnsPerHost: 1024, IdleConnTimeout: 30 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, DisableCompression: true, } } // Validate - validates the access token. func (p *JWT) Validate(token, dsecs string) (map[string]interface{}, error) { jp := new(jwtgo.Parser) jp.ValidMethods = []string{"RS256", "RS384", "RS512", "ES256", "ES384", "ES512"} keyFuncCallback := func(jwtToken *jwtgo.Token) (interface{}, error) { kid, ok := jwtToken.Header["kid"].(string) if !ok { return nil, fmt.Errorf("Invalid kid value %v", jwtToken.Header["kid"]) } return p.args.publicKeys[kid], nil } var claims jwtgo.MapClaims jwtToken, err := jp.ParseWithClaims(token, &claims, keyFuncCallback) if err != nil { if err = p.args.PopulatePublicKey(); err != nil { return nil, err } jwtToken, err = jwtgo.ParseWithClaims(token, &claims, keyFuncCallback) if err != nil { return nil, err } } if !jwtToken.Valid { return nil, ErrTokenExpired } expAt, err := expToInt64(claims["exp"]) if err != nil { return nil, err } defaultExpiryDuration, err := GetDefaultExpiration(dsecs) if err != nil { return nil, err } if time.Unix(expAt, 0).UTC().Sub(time.Now().UTC()) < defaultExpiryDuration { defaultExpiryDuration = time.Unix(expAt, 0).UTC().Sub(time.Now().UTC()) } expiry := time.Now().UTC().Add(defaultExpiryDuration).Unix() if expAt < expiry { claims["exp"] = strconv.FormatInt(expAt, 64) } return claims, nil } // ID returns the provider name and authentication type. func (p *JWT) ID() ID { return "jwt" } // NewJWT - initialize new jwt authenticator. func NewJWT(args JWKSArgs) *JWT { return &JWT{ args: args, } }