internal/fleetserver/client.go (95 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package fleetserver
import (
"context"
"crypto/tls"
"fmt"
"io"
"net/http"
"net/url"
"github.com/elastic/elastic-package/internal/certs"
"github.com/elastic/elastic-package/internal/logger"
)
// Client is a client for Fleet Server API. This API only supports authentication with API
// keys, though some endpoints are also available without any authentication.
type Client struct {
address string
apiKey string
certificateAuthority string
tlSkipVerify bool
http *http.Client
httpClientSetup func(*http.Client) *http.Client
}
type ClientOption func(*Client)
func NewClient(address string, opts ...ClientOption) (*Client, error) {
client := Client{
address: address,
}
for _, opt := range opts {
opt(&client)
}
httpClient, err := client.httpClient()
if err != nil {
return nil, fmt.Errorf("cannot create HTTP client: %w", err)
}
client.http = httpClient
return &client, nil
}
// APIKey option sets the API key to be used by the client for authentication.
func APIKey(apiKey string) ClientOption {
return func(c *Client) {
c.apiKey = apiKey
}
}
// TLSSkipVerify option disables TLS verification.
func TLSSkipVerify() ClientOption {
return func(c *Client) {
c.tlSkipVerify = true
}
}
// CertificateAuthority sets the certificate authority to be used by the client.
func CertificateAuthority(certificateAuthority string) ClientOption {
return func(c *Client) {
c.certificateAuthority = certificateAuthority
}
}
// HTTPClientSetup adds an initializing function for the http client.
func HTTPClientSetup(setup func(*http.Client) *http.Client) ClientOption {
return func(c *Client) {
c.httpClientSetup = setup
}
}
func (c *Client) httpClient() (*http.Client, error) {
client := &http.Client{}
if c.tlSkipVerify {
client.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
} else if c.certificateAuthority != "" {
rootCAs, err := certs.SystemPoolWithCACertificate(c.certificateAuthority)
if err != nil {
return nil, fmt.Errorf("reading CA certificate: %w", err)
}
client.Transport = &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: rootCAs},
}
}
if c.httpClientSetup != nil {
client = c.httpClientSetup(client)
}
return client, nil
}
func (c *Client) httpRequest(ctx context.Context, method, resourcePath string, reqBody io.Reader) (*http.Request, error) {
base, err := url.Parse(c.address)
if err != nil {
return nil, fmt.Errorf("could not create base URL from host: %v: %w", c.address, err)
}
rel, err := url.Parse(resourcePath)
if err != nil {
return nil, fmt.Errorf("could not create relative URL from resource path: %v: %w", resourcePath, err)
}
u := base.JoinPath(rel.EscapedPath())
u.RawQuery = rel.RawQuery
logger.Debugf("%s %s", method, u)
req, err := http.NewRequestWithContext(ctx, method, u.String(), reqBody)
if err != nil {
return nil, fmt.Errorf("could not create %v request to Fleet Server API resource: %s: %w", method, resourcePath, err)
}
if c.apiKey != "" {
req.Header.Set("Authorization", "ApiKey "+c.apiKey)
}
return req, nil
}