mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
417f45fc61
#15556 We will need to pay attention when releasing fleet (the github actions were modified to use the local file now). Should be reviewed by commits (first commit is the actual adding of the `version.go` file) - [X] Manual QA for all new/changed functionality Manually tested the following: - `Settings -> My account` on the UI and checked the `/version` endpoint response. (Or also visiting https://localhost:8080/version on a browser). - Ran `make fleetctl fleet`, `./build/fleetctl --version` and `./build/fleet version`.
208 lines
5.8 KiB
Go
208 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"runtime"
|
|
|
|
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/service"
|
|
"github.com/fleetdm/fleet/v4/server/version"
|
|
kithttp "github.com/go-kit/kit/transport/http"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
func unauthenticatedClientFromCLI(c *cli.Context) (*service.Client, error) {
|
|
cc, err := clientConfigFromCLI(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return unauthenticatedClientFromConfig(cc, getDebug(c), c.App.Writer, c.App.ErrWriter)
|
|
}
|
|
|
|
func clientFromCLI(c *cli.Context) (*service.Client, error) {
|
|
fleetClient, err := unauthenticatedClientFromCLI(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
configPath, context := c.String("config"), c.String("context")
|
|
|
|
// if a config file is explicitly provided, do not set an invalid arbitrary token
|
|
if !c.IsSet("config") && flag.Lookup("test.v") != nil {
|
|
fleetClient.SetToken("AAAA")
|
|
return fleetClient, nil
|
|
}
|
|
|
|
// Add authentication token
|
|
t, err := getConfigValue(configPath, context, "token")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting token from the config: %w", err)
|
|
}
|
|
|
|
token, ok := t.(string)
|
|
if !ok {
|
|
fmt.Fprintln(os.Stderr, "Token invalid. Please log in with: fleetctl login")
|
|
return nil, fmt.Errorf("token config value expected type %T, got %T: %+v", "", t, t)
|
|
}
|
|
if token == "" {
|
|
fmt.Fprintln(os.Stderr, "Token missing. Please log in with: fleetctl login")
|
|
return nil, errors.New("token config value missing")
|
|
}
|
|
fleetClient.SetToken(token)
|
|
|
|
// Check if version matches fleet server. Also ensures that the token is valid.
|
|
clientInfo := version.Version()
|
|
|
|
serverInfo, err := fleetClient.Version()
|
|
if err != nil {
|
|
if errors.Is(err, service.ErrUnauthenticated) {
|
|
fmt.Fprintln(os.Stderr, "Token invalid or session expired. Please log in with: fleetctl login")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
if clientInfo.Version != serverInfo.Version {
|
|
fmt.Fprintf(
|
|
os.Stderr,
|
|
"Warning: Version mismatch.\nClient Version: %s\nServer Version: %s\n",
|
|
clientInfo.Version, serverInfo.Version,
|
|
)
|
|
// This is just a warning, continue ...
|
|
}
|
|
|
|
// check that AppConfig's Apple BM terms are not expired.
|
|
var sce kithttp.StatusCoder
|
|
switch appCfg, err := fleetClient.GetAppConfig(); {
|
|
case err == nil:
|
|
if appCfg.MDM.AppleBMTermsExpired {
|
|
fleet.WriteAppleBMTermsExpiredBanner(os.Stderr)
|
|
// This is just a warning, continue ...
|
|
}
|
|
case errors.As(err, &sce) && sce.StatusCode() == http.StatusForbidden:
|
|
// OK, could be a user without permissions to read app config (e.g. gitops).
|
|
default:
|
|
return nil, err
|
|
}
|
|
|
|
return fleetClient, nil
|
|
}
|
|
|
|
func unauthenticatedClientFromConfig(cc Context, debug bool, outputWriter io.Writer, errWriter io.Writer) (*service.Client, error) {
|
|
options := []service.ClientOption{
|
|
service.SetClientOutputWriter(outputWriter),
|
|
service.SetClientErrorWriter(errWriter),
|
|
}
|
|
|
|
if len(cc.CustomHeaders) > 0 {
|
|
options = append(options, service.WithCustomHeaders(cc.CustomHeaders))
|
|
}
|
|
|
|
if flag.Lookup("test.v") != nil {
|
|
return service.NewClient(
|
|
os.Getenv("FLEET_SERVER_ADDRESS"), true, "", "", options...)
|
|
}
|
|
|
|
if cc.Address == "" {
|
|
return nil, errors.New("set the Fleet API address with: fleetctl config set --address https://localhost:8080")
|
|
}
|
|
|
|
if runtime.GOOS == "windows" && cc.RootCA == "" && !cc.TLSSkipVerify {
|
|
return nil, errors.New("Windows clients must configure rootca (secure) or tls-skip-verify (insecure)")
|
|
}
|
|
|
|
if debug {
|
|
options = append(options, service.EnableClientDebug())
|
|
}
|
|
|
|
fleet, err := service.NewClient(
|
|
cc.Address,
|
|
cc.TLSSkipVerify,
|
|
cc.RootCA,
|
|
cc.URLPrefix,
|
|
options...,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating Fleet API client handler: %w", err)
|
|
}
|
|
|
|
return fleet, nil
|
|
}
|
|
|
|
// returns an HTTP client and the parsed URL for the configured server's
|
|
// address. The reason why this exists instead of using
|
|
// unauthenticatedClientFromConfig is because this doesn't apply the same rules
|
|
// around TLS config - in particular, it only sets a root CA if one is
|
|
// explicitly configured.
|
|
func rawHTTPClientFromConfig(cc Context) (*http.Client, *url.URL, error) {
|
|
if flag.Lookup("test.v") != nil {
|
|
cc.Address = os.Getenv("FLEET_SERVER_ADDRESS")
|
|
}
|
|
baseURL, err := url.Parse(cc.Address)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("parse address: %w", err)
|
|
}
|
|
|
|
var rootCA *x509.CertPool
|
|
if cc.RootCA != "" {
|
|
rootCA = x509.NewCertPool()
|
|
// read in the root cert file specified in the context
|
|
certs, err := os.ReadFile(cc.RootCA)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("reading root CA: %w", err)
|
|
}
|
|
|
|
// add certs to pool
|
|
if ok := rootCA.AppendCertsFromPEM(certs); !ok {
|
|
return nil, nil, errors.New("failed to add certificates to root CA pool")
|
|
}
|
|
}
|
|
|
|
cli := fleethttp.NewClient(fleethttp.WithTLSClientConfig(&tls.Config{
|
|
InsecureSkipVerify: cc.TLSSkipVerify,
|
|
RootCAs: rootCA,
|
|
}))
|
|
return cli, baseURL, nil
|
|
}
|
|
|
|
func clientConfigFromCLI(c *cli.Context) (Context, error) {
|
|
// if a config file is explicitly provided, do not return a default context,
|
|
// just override the address and skip verify before returning.
|
|
if !c.IsSet("config") && flag.Lookup("test.v") != nil {
|
|
return Context{
|
|
Address: os.Getenv("FLEET_SERVER_ADDRESS"),
|
|
TLSSkipVerify: true,
|
|
}, nil
|
|
}
|
|
|
|
var zeroCtx Context
|
|
|
|
if err := makeConfigIfNotExists(c.String("config")); err != nil {
|
|
return zeroCtx, fmt.Errorf("error verifying that config exists at %s: %w", c.String("config"), err)
|
|
}
|
|
|
|
config, err := readConfig(c.String("config"))
|
|
if err != nil {
|
|
return zeroCtx, err
|
|
}
|
|
|
|
cc, ok := config.Contexts[c.String("context")]
|
|
if !ok {
|
|
return zeroCtx, fmt.Errorf("context %q is not found", c.String("context"))
|
|
}
|
|
if flag.Lookup("test.v") != nil {
|
|
cc.Address = os.Getenv("FLEET_SERVER_ADDRESS")
|
|
cc.TLSSkipVerify = true
|
|
}
|
|
return cc, nil
|
|
}
|