<commit_before>/*
Copyright 2020-2021 Gravitational, 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 webclient provides a client for the Teleport Proxy API endpoints.
package webclient

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"

	"github.com/gravitational/teleport/api/constants"
	"github.com/gravitational/teleport/api/defaults"
	"github.com/gravitational/trace"
)

// newWebClient creates a new client to the HTTPS web proxy.
func newWebClient(insecure bool, pool *x509.CertPool) *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				RootCAs:            pool,
				InsecureSkipVerify: insecure,
			},
		},
	}
}

// Find fetches discovery data by connecting to the given web proxy address.
// It is designed to fetch proxy public addresses without any inefficiencies.
func Find(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (*PingResponse, error) {
	clt := newWebClient(insecure, pool)
	defer clt.CloseIdleConnections()

	endpoint := fmt.Sprintf("https://%s/webapi/find", proxyAddr)
	resp, err := clt.Get(endpoint)
	if err != nil {
		return nil, trace.Wrap(err)
	}

	defer resp.Body.Close()
	pr := &PingResponse{}
	if err := json.NewDecoder(resp.Body).Decode(pr); err != nil {
		return nil, trace.Wrap(err)
	}

	return pr, nil
}

// Ping serves two purposes. The first is to validate the HTTP endpoint of a
// Teleport proxy. This leads to better user experience: users get connection
// errors before being asked for passwords. The second is to return the form
// of authentication that the server supports. This also leads to better user
// experience: users only get prompted for the type of authentication the server supports.
func Ping(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool, connectorName string) (*PingResponse, error) {
	clt := newWebClient(insecure, pool)
	defer clt.CloseIdleConnections()

	endpoint := fmt.Sprintf("https://%s/webapi/ping", proxyAddr)
	if connectorName != "" {
		endpoint = fmt.Sprintf("%s/%s", endpoint, connectorName)
	}

	resp, err := clt.Get(endpoint)
	if err != nil {
		return nil, trace.Wrap(err)
	}

	defer resp.Body.Close()
	pr := &PingResponse{}
	if err := json.NewDecoder(resp.Body).Decode(pr); err != nil {
		return nil, trace.Wrap(err)
	}

	return pr, nil
}

// GetTunnelAddr returns the tunnel address either set in an environment variable or retrieved from the web proxy.
func GetTunnelAddr(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (string, error) {
	// If TELEPORT_TUNNEL_PUBLIC_ADDR is set, nothing else has to be done, return it.
	if tunnelAddr := os.Getenv(defaults.TunnelPublicAddrEnvar); tunnelAddr != "" {
		return extractHostPort(tunnelAddr)
	}

	// Ping web proxy to retrieve tunnel proxy address.
	pr, err := Find(ctx, proxyAddr, insecure, nil)
	if err != nil {
		return "", trace.Wrap(err)
	}
	return tunnelAddr(proxyAddr, pr.Proxy.SSH)
}

func GetMOTD(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (*MotD, error) {
	clt := newWebClient(insecure, pool)
	defer clt.CloseIdleConnections()

	endpoint := fmt.Sprintf("https://%s/webapi/motd", proxyAddr)

	resp, err := clt.Get(endpoint)
	if err != nil {
		return nil, trace.Wrap(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, trace.BadParameter("failed to fetch message of the day: %d", resp.StatusCode)
	}

	motd := &MotD{}
	if err := json.NewDecoder(resp.Body).Decode(motd); err != nil {
		return nil, trace.Wrap(err)
	}

	return motd, nil
}

// MotD holds data about the current message of the day.
type MotD struct {
	Text string
}
<commit_msg>webclient: use the provided context (#7801)<commit_after>/*
Copyright 2020-2021 Gravitational, 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 webclient provides a client for the Teleport Proxy API endpoints.
package webclient

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"

	"github.com/gravitational/teleport/api/constants"
	"github.com/gravitational/teleport/api/defaults"
	"github.com/gravitational/trace"
)

// newWebClient creates a new client to the HTTPS web proxy.
func newWebClient(insecure bool, pool *x509.CertPool) *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				RootCAs:            pool,
				InsecureSkipVerify: insecure,
			},
		},
	}
}

// Find fetches discovery data by connecting to the given web proxy address.
// It is designed to fetch proxy public addresses without any inefficiencies.
func Find(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (*PingResponse, error) {
	clt := newWebClient(insecure, pool)
	defer clt.CloseIdleConnections()

	endpoint := fmt.Sprintf("https://%s/webapi/find", proxyAddr)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return nil, trace.Wrap(err)
	}

	resp, err := clt.Do(req)
	if err != nil {
		return nil, trace.Wrap(err)
	}

	defer resp.Body.Close()
	pr := &PingResponse{}
	if err := json.NewDecoder(resp.Body).Decode(pr); err != nil {
		return nil, trace.Wrap(err)
	}

	return pr, nil
}

// Ping serves two purposes. The first is to validate the HTTP endpoint of a
// Teleport proxy. This leads to better user experience: users get connection
// errors before being asked for passwords. The second is to return the form
// of authentication that the server supports. This also leads to better user
// experience: users only get prompted for the type of authentication the server supports.
func Ping(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool, connectorName string) (*PingResponse, error) {
	clt := newWebClient(insecure, pool)
	defer clt.CloseIdleConnections()

	endpoint := fmt.Sprintf("https://%s/webapi/ping", proxyAddr)
	if connectorName != "" {
		endpoint = fmt.Sprintf("%s/%s", endpoint, connectorName)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return nil, trace.Wrap(err)
	}

	resp, err := clt.Do(req)
	if err != nil {
		return nil, trace.Wrap(err)
	}

	defer resp.Body.Close()
	pr := &PingResponse{}
	if err := json.NewDecoder(resp.Body).Decode(pr); err != nil {
		return nil, trace.Wrap(err)
	}

	return pr, nil
}

// GetTunnelAddr returns the tunnel address either set in an environment variable or retrieved from the web proxy.
func GetTunnelAddr(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (string, error) {
	// If TELEPORT_TUNNEL_PUBLIC_ADDR is set, nothing else has to be done, return it.
	if tunnelAddr := os.Getenv(defaults.TunnelPublicAddrEnvar); tunnelAddr != "" {
		return extractHostPort(tunnelAddr)
	}

	// Ping web proxy to retrieve tunnel proxy address.
	pr, err := Find(ctx, proxyAddr, insecure, nil)
	if err != nil {
		return "", trace.Wrap(err)
	}
	return tunnelAddr(proxyAddr, pr.Proxy.SSH)
}

func GetMOTD(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (*MotD, error) {
	clt := newWebClient(insecure, pool)
	defer clt.CloseIdleConnections()

	endpoint := fmt.Sprintf("https://%s/webapi/motd", proxyAddr)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return nil, trace.Wrap(err)
	}

	resp, err := clt.Do(req)
	if err != nil {
		return nil, trace.Wrap(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, trace.BadParameter("failed to fetch message of the day: %d", resp.StatusCode)
	}

	motd := &MotD{}
	if err := json.NewDecoder(resp.Body).Decode(motd); err != nil {
		return nil, trace.Wrap(err)
	}

	return motd, nil
}

// MotD holds data about the current message of the day.
type MotD struct {
	Text string
}

{'fraction_non_alphanumeric': 0.11336032388663968, 'fraction_numerical': 0.007197480881691408, 'mean_word_length': 4.176367869615833, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1189915', 'n_tokens_mistral': 2956, 'n_tokens_neox': 2808, 'n_words': 955}