/* * MinIO Cloud Storage, (C) 2020 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 env import ( "context" "crypto/tls" "crypto/x509" "errors" "fmt" "io/ioutil" "net" "net/http" "net/url" "os" "regexp" "time" "github.com/dgrijalva/jwt-go" ) const ( webEnvScheme = "env" webEnvSchemeSecure = "env+tls" ) var ( globalRootCAs *x509.CertPool ) // RegisterGlobalCAs register the global root CAs func RegisterGlobalCAs(CAs *x509.CertPool) { globalRootCAs = CAs } func isValidEnvScheme(scheme string) bool { switch scheme { case webEnvScheme: fallthrough case webEnvSchemeSecure: return true } return false } var ( hostKeys = regexp.MustCompile("^(https?://)(.*?):(.*?)@(.*?)$") ) func fetchEnvHTTP(envKey string, u *url.URL) (string, error) { switch u.Scheme { case webEnvScheme: u.Scheme = "http" case webEnvSchemeSecure: u.Scheme = "https" } ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var ( username, password string ) envURL := u.String() if hostKeys.MatchString(envURL) { parts := hostKeys.FindStringSubmatch(envURL) if len(parts) != 5 { return "", errors.New("invalid arguments") } username = parts[2] password = parts[3] envURL = fmt.Sprintf("%s%s", parts[1], parts[4]) } if username == "" && password == "" && u.User != nil { username = u.User.Username() password, _ = u.User.Password() } req, err := http.NewRequestWithContext(ctx, http.MethodGet, envURL+"?key="+envKey, nil) if err != nil { return "", err } claims := &jwt.StandardClaims{ ExpiresAt: int64(15 * time.Minute), Issuer: username, Subject: envKey, } token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims) ss, err := token.SignedString([]byte(password)) if err != nil { return "", err } req.Header.Set("Authorization", "Bearer "+ss) clnt := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 3 * time.Second, KeepAlive: 5 * time.Second, }).DialContext, ResponseHeaderTimeout: 3 * time.Second, TLSHandshakeTimeout: 3 * time.Second, ExpectContinueTimeout: 3 * time.Second, TLSClientConfig: &tls.Config{ RootCAs: globalRootCAs, }, // Go net/http automatically unzip if content-type is // gzip disable this feature, as we are always interested // in raw stream. DisableCompression: true, }, } resp, err := clnt.Do(req) if err != nil { return "", err } envValueBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return "", err } return string(envValueBytes), nil } // Environ returns a copy of strings representing the // environment, in the form "key=value". func Environ() []string { return os.Environ() } // LookupEnv retrieves the value of the environment variable // named by the key. If the variable is present in the // environment the value (which may be empty) is returned // and the boolean is true. Otherwise the returned value // will be empty and the boolean will be false. // // Additionally if the input is env://username:password@remote:port/ // to fetch ENV values for the env value from a remote server. func LookupEnv(key string) (string, bool) { v, ok := os.LookupEnv(key) if ok { u, err := url.Parse(v) if err != nil { return v, true } if !isValidEnvScheme(u.Scheme) { return v, true } v, err = fetchEnvHTTP(key, u) if err != nil { return "", false } return v, true } return "", false }