// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package util import ( "context" "crypto/tls" "fmt" "net/http" "net/url" "strconv" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-hclog" "google.golang.org/grpc" ) func DialExposedGRPCConn( ctx context.Context, logger hclog.Logger, exposedServerGRPCPort int, token string, tlsConfig *tls.Config, ) (*grpc.ClientConn, func(), error) { if exposedServerGRPCPort <= 0 { return nil, nil, fmt.Errorf("cannot dial server grpc on port %d", exposedServerGRPCPort) } cfg := discovery.Config{ Addresses: "127.0.0.1", GRPCPort: exposedServerGRPCPort, // Disable server watch because we only need to get server IPs once. ServerWatchDisabled: true, TLS: tlsConfig, Credentials: discovery.Credentials{ Type: discovery.CredentialsTypeStatic, Static: discovery.StaticTokenCredential{ Token: token, }, }, } watcher, err := discovery.NewWatcher(ctx, cfg, logger.Named("consul-server-connection-manager")) if err != nil { return nil, nil, err } go watcher.Run() // We recycle the GRPC connection from the discovery client because it // should have all the necessary dial options, including the resolver that // continuously updates Consul server addresses. Otherwise, a lot of code from consul-server-connection-manager // would need to be duplicated state, err := watcher.State() if err != nil { watcher.Stop() return nil, nil, fmt.Errorf("unable to get connection manager state: %w", err) } return state.GRPCConn, func() { watcher.Stop() }, nil } func ProxyNotPooledAPIClient(proxyPort int, containerIP string, containerPort int, token string) (*api.Client, error) { return proxyAPIClient(cleanhttp.DefaultTransport(), proxyPort, containerIP, containerPort, token) } func ProxyAPIClient(proxyPort int, containerIP string, containerPort int, token string) (*api.Client, error) { return proxyAPIClient(cleanhttp.DefaultPooledTransport(), proxyPort, containerIP, containerPort, token) } func proxyAPIClient(baseTransport *http.Transport, proxyPort int, containerIP string, containerPort int, token string) (*api.Client, error) { if proxyPort <= 0 { return nil, fmt.Errorf("cannot use an http proxy on port %d", proxyPort) } if containerIP == "" { return nil, fmt.Errorf("container IP is required") } if containerPort <= 0 { return nil, fmt.Errorf("cannot dial api client on port %d", containerPort) } proxyURL, err := url.Parse("http://127.0.0.1:" + strconv.Itoa(proxyPort)) if err != nil { return nil, err } cfg := api.DefaultConfig() cfg.Transport = baseTransport cfg.Transport.Proxy = http.ProxyURL(proxyURL) cfg.Address = fmt.Sprintf("http://%s:%d", containerIP, containerPort) cfg.Token = token return api.NewClient(cfg) } func ProxyNotPooledHTTPTransport(proxyPort int) (*http.Transport, error) { return proxyHTTPTransport(cleanhttp.DefaultTransport(), proxyPort) } func ProxyHTTPTransport(proxyPort int) (*http.Transport, error) { return proxyHTTPTransport(cleanhttp.DefaultPooledTransport(), proxyPort) } func proxyHTTPTransport(baseTransport *http.Transport, proxyPort int) (*http.Transport, error) { if proxyPort <= 0 { return nil, fmt.Errorf("cannot use an http proxy on port %d", proxyPort) } proxyURL, err := url.Parse("http://127.0.0.1:" + strconv.Itoa(proxyPort)) if err != nil { return nil, err } baseTransport.Proxy = http.ProxyURL(proxyURL) return baseTransport, nil }