1
0
mirror of https://github.com/lxc/incus.git synced 2026-02-05 09:46:19 +01:00
Files
incus/client/util.go
Stéphane Graber 54e7c21734 client: Make golangci-lint clean
Signed-off-by: Stéphane Graber <stgraber@stgraber.org>
2025-04-02 19:43:11 -04:00

253 lines
6.8 KiB
Go

package incus
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/lxc/incus/v6/shared/proxy"
localtls "github.com/lxc/incus/v6/shared/tls"
)
// tlsHTTPClient creates an HTTP client with a specified Transport Layer Security (TLS) configuration.
// It takes in parameters for client certificates, keys, Certificate Authority, server certificates,
// a boolean for skipping verification, a proxy function, and a transport wrapper function.
// It returns the HTTP client with the provided configurations and handles any errors that might occur during the setup process.
func tlsHTTPClient(client *http.Client, tlsClientCert string, tlsClientKey string, tlsCA string, tlsServerCert string, insecureSkipVerify bool, proxyFunc func(req *http.Request) (*url.URL, error), transportWrapper func(t *http.Transport) HTTPTransporter) (*http.Client, error) {
// Get the TLS configuration
tlsConfig, err := localtls.GetTLSConfigMem(tlsClientCert, tlsClientKey, tlsCA, tlsServerCert, insecureSkipVerify)
if err != nil {
return nil, err
}
// Define the http transport
transport := &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: proxy.FromEnvironment,
DisableKeepAlives: true,
ExpectContinueTimeout: time.Second * 30,
ResponseHeaderTimeout: time.Second * 3600,
TLSHandshakeTimeout: time.Second * 5,
}
// Allow overriding the proxy
if proxyFunc != nil {
transport.Proxy = proxyFunc
}
// Special TLS handling
transport.DialTLSContext = func(ctx context.Context, network string, addr string) (net.Conn, error) {
tlsDial := func(network string, addr string, config *tls.Config, resetName bool) (net.Conn, error) {
conn, err := localtls.RFC3493Dialer(ctx, network, addr)
if err != nil {
return nil, err
}
// Setup TLS
if resetName {
hostName, _, err := net.SplitHostPort(addr)
if err != nil {
hostName = addr
}
config = config.Clone()
config.ServerName = hostName
}
tlsConn := tls.Client(conn, config)
// Validate the connection
err = tlsConn.Handshake()
if err != nil {
_ = conn.Close()
return nil, err
}
if !config.InsecureSkipVerify {
err := tlsConn.VerifyHostname(config.ServerName)
if err != nil {
_ = conn.Close()
return nil, err
}
}
return tlsConn, nil
}
conn, err := tlsDial(network, addr, transport.TLSClientConfig, false)
if err != nil {
// We may have gotten redirected to a non-Incus machine
return tlsDial(network, addr, transport.TLSClientConfig, true)
}
return conn, nil
}
// Define the http client
if client == nil {
client = &http.Client{}
}
if transportWrapper != nil {
client.Transport = transportWrapper(transport)
} else {
client.Transport = transport
}
// Setup redirect policy
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
// Replicate the headers
req.Header = via[len(via)-1].Header
return nil
}
return client, nil
}
// unixHTTPClient creates an HTTP client that communicates over a Unix socket.
// It takes in the connection arguments and the Unix socket path as parameters.
// The function sets up a Unix socket dialer, configures the HTTP transport, and returns the HTTP client with the specified configurations.
// Any errors encountered during the setup process are also handled by the function.
func unixHTTPClient(args *ConnectionArgs, path string) (*http.Client, error) {
// Setup a Unix socket dialer
unixDial := func(_ context.Context, _ string, _ string) (net.Conn, error) {
raddr, err := net.ResolveUnixAddr("unix", path)
if err != nil {
return nil, err
}
return net.DialUnix("unix", nil, raddr)
}
if args == nil {
args = &ConnectionArgs{}
}
// Define the http transport
transport := &http.Transport{
DialContext: unixDial,
DisableKeepAlives: true,
Proxy: args.Proxy,
ExpectContinueTimeout: time.Second * 30,
ResponseHeaderTimeout: time.Second * 3600,
TLSHandshakeTimeout: time.Second * 5,
}
// Define the http client
client := args.HTTPClient
if client == nil {
client = &http.Client{}
}
client.Transport = transport
// Setup redirect policy
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
// Replicate the headers
req.Header = via[len(via)-1].Header
return nil
}
return client, nil
}
// remoteOperationResult used for storing the error that occurred for a particular remote URL.
type remoteOperationResult struct {
URL string
Error error
}
func remoteOperationError(msg string, errors []remoteOperationResult) error {
// Check if empty
if len(errors) == 0 {
return nil
}
// Check if all identical
var err error
for _, entry := range errors {
if err != nil && entry.Error.Error() != err.Error() {
errorStrs := make([]string, 0, len(errors))
for _, error := range errors {
errorStrs = append(errorStrs, fmt.Sprintf("%s: %v", error.URL, error.Error))
}
return fmt.Errorf("%s:\n - %s", msg, strings.Join(errorStrs, "\n - "))
}
err = entry.Error
}
// Check if successful
if err != nil {
return fmt.Errorf("%s: %w", msg, err)
}
return nil
}
// Set the value of a query parameter in the given URI.
func setQueryParam(uri, param, value string) (string, error) {
fields, err := url.Parse(uri)
if err != nil {
return "", err
}
values := fields.Query()
values.Set(param, url.QueryEscape(value))
fields.RawQuery = values.Encode()
return fields.String(), nil
}
// urlsToResourceNames returns a list of resource names extracted from one or more URLs of the same resource type.
// The resource type path prefix to match is provided by the matchPathPrefix argument.
func urlsToResourceNames(matchPathPrefix string, urls ...string) ([]string, error) {
resourceNames := make([]string, 0, len(urls))
for _, urlRaw := range urls {
u, err := url.Parse(urlRaw)
if err != nil {
return nil, fmt.Errorf("Failed parsing URL %q: %w", urlRaw, err)
}
_, after, found := strings.Cut(u.Path, fmt.Sprintf("%s/", matchPathPrefix))
if !found {
return nil, fmt.Errorf("Unexpected URL path %q", u)
}
resourceNames = append(resourceNames, after)
}
return resourceNames, nil
}
// parseFilters translates filters passed at client side to form acceptable by server-side API.
func parseFilters(filters []string) string {
var result []string
for _, filter := range filters {
if strings.Contains(filter, "=") {
membs := strings.SplitN(filter, "=", 2)
result = append(result, fmt.Sprintf("%s eq %s", membs[0], membs[1]))
}
}
return strings.Join(result, " and ")
}
// HTTPTransporter represents a wrapper around *http.Transport.
// It is used to add some pre and postprocessing logic to http requests / responses.
type HTTPTransporter interface {
http.RoundTripper
// Transport what this struct wraps
Transport() *http.Transport
}