1
0
mirror of https://github.com/juanfont/headscale.git synced 2024-12-20 19:09:07 +01:00
juanfont.headscale/cmd/headscale/cli/utils.go

225 lines
5.2 KiB
Go
Raw Normal View History

package cli
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"os"
"reflect"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol"
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
2021-08-05 19:26:49 +02:00
"github.com/rs/zerolog/log"
"google.golang.org/grpc"
2022-02-12 20:08:33 +01:00
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"gopkg.in/yaml.v3"
)
const (
2022-01-25 23:11:15 +01:00
HeadscaleDateTimeFormat = "2006-01-02 15:04:05"
SocketWritePermissions = 0o666
)
func getHeadscaleApp() (*hscontrol.Headscale, error) {
cfg, err := types.GetHeadscaleConfig()
if err != nil {
2022-08-04 10:47:00 +02:00
return nil, fmt.Errorf(
"failed to load configuration while creating headscale instance: %w",
err,
)
}
app, err := hscontrol.NewHeadscale(cfg)
if err != nil {
return nil, err
}
2021-07-04 13:24:05 +02:00
// We are doing this here, as in the future could be cool to have it also hot-reload
2021-07-11 15:10:11 +02:00
if cfg.ACL.PolicyPath != "" {
aclPath := util.AbsolutePathFromConfigPath(cfg.ACL.PolicyPath)
pol, err := policy.LoadACLPolicyFromPath(aclPath)
2021-07-11 15:10:11 +02:00
if err != nil {
log.Fatal().
2021-08-05 21:57:47 +02:00
Str("path", aclPath).
2021-08-05 19:26:49 +02:00
Err(err).
Msg("Could not load the ACL policy")
2021-07-11 15:10:11 +02:00
}
app.ACLPolicy = pol
2021-07-04 13:24:05 +02:00
}
return app, nil
}
func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.ClientConn, context.CancelFunc) {
cfg, err := types.GetHeadscaleConfig()
if err != nil {
log.Fatal().
Err(err).
Caller().
Msgf("Failed to load configuration")
2022-06-26 09:52:04 +02:00
os.Exit(-1) // we get here if logging is suppressed (i.e., json output)
}
log.Debug().
Dur("timeout", cfg.CLI.Timeout).
Msgf("Setting timeout")
ctx, cancel := context.WithTimeout(context.Background(), cfg.CLI.Timeout)
grpcOptions := []grpc.DialOption{
grpc.WithBlock(),
}
address := cfg.CLI.Address
// If the address is not set, we assume that we are on the server hosting hscontrol.
if address == "" {
log.Debug().
Str("socket", cfg.UnixSocket).
Msgf("HEADSCALE_CLI_ADDRESS environment is not set, connecting to unix socket.")
address = cfg.UnixSocket
// Try to give the user better feedback if we cannot write to the headscale
// socket.
socket, err := os.OpenFile(cfg.UnixSocket, os.O_WRONLY, SocketWritePermissions) //nolint
if err != nil {
if os.IsPermission(err) {
log.Fatal().
Err(err).
Str("socket", cfg.UnixSocket).
Msgf("Unable to read/write to headscale socket, do you have the correct permissions?")
}
}
socket.Close()
grpcOptions = append(
grpcOptions,
2022-02-12 20:08:33 +01:00
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(util.GrpcSocketDialer),
)
} else {
// If we are not connecting to a local server, require an API key for authentication
apiKey := cfg.CLI.APIKey
if apiKey == "" {
2022-01-25 23:11:15 +01:00
log.Fatal().Caller().Msgf("HEADSCALE_CLI_API_KEY environment variable needs to be set.")
}
grpcOptions = append(grpcOptions,
grpc.WithPerRPCCredentials(tokenAuth{
token: apiKey,
}),
)
if cfg.CLI.Insecure {
tlsConfig := &tls.Config{
2022-02-13 09:46:35 +01:00
// turn of gosec as we are intentionally setting
// insecure.
//nolint:gosec
InsecureSkipVerify: true,
}
grpcOptions = append(grpcOptions,
grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
)
} else {
grpcOptions = append(grpcOptions,
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
)
}
}
log.Trace().Caller().Str("address", address).Msg("Connecting via gRPC")
2021-10-29 19:36:11 +02:00
conn, err := grpc.DialContext(ctx, address, grpcOptions...)
if err != nil {
2022-01-25 23:11:15 +01:00
log.Fatal().Caller().Err(err).Msgf("Could not connect: %v", err)
2022-06-26 09:52:11 +02:00
os.Exit(-1) // we get here if logging is suppressed (i.e., json output)
}
client := v1.NewHeadscaleServiceClient(conn)
return ctx, client, conn, cancel
}
func SuccessOutput(result interface{}, override string, outputFormat string) {
2022-02-12 20:08:41 +01:00
var jsonBytes []byte
var err error
switch outputFormat {
case "json":
2022-02-12 20:08:41 +01:00
jsonBytes, err = json.MarshalIndent(result, "", "\t")
if err != nil {
log.Fatal().Err(err).Msg("failed to unmarshal output")
}
case "json-line":
2022-02-12 20:08:41 +01:00
jsonBytes, err = json.Marshal(result)
if err != nil {
log.Fatal().Err(err).Msg("failed to unmarshal output")
}
case "yaml":
2022-02-12 20:08:41 +01:00
jsonBytes, err = yaml.Marshal(result)
if err != nil {
log.Fatal().Err(err).Msg("failed to unmarshal output")
}
default:
2021-11-15 19:36:02 +01:00
//nolint
fmt.Println(override)
2021-11-14 16:46:09 +01:00
return
}
2021-11-15 19:36:02 +01:00
//nolint
2022-02-12 20:08:41 +01:00
fmt.Println(string(jsonBytes))
}
func ErrorOutput(errResult error, override string, outputFormat string) {
type errOutput struct {
Error string `json:"error"`
}
SuccessOutput(errOutput{errResult.Error()}, override, outputFormat)
}
func HasMachineOutputFlag() bool {
for _, arg := range os.Args {
if arg == "json" || arg == "json-line" || arg == "yaml" {
return true
}
}
2021-11-14 16:46:09 +01:00
return false
}
2021-10-29 19:08:21 +02:00
type tokenAuth struct {
token string
}
// Return value is mapped to request headers.
2021-11-13 09:36:45 +01:00
func (t tokenAuth) GetRequestMetadata(
ctx context.Context,
in ...string,
) (map[string]string, error) {
2021-10-29 19:08:21 +02:00
return map[string]string{
"authorization": "Bearer " + t.token,
}, nil
}
func (tokenAuth) RequireTransportSecurity() bool {
return true
2021-10-29 19:08:21 +02:00
}
2021-10-31 10:40:43 +01:00
2022-04-25 22:33:53 +02:00
func contains[T string](ts []T, t T) bool {
for _, v := range ts {
2022-04-25 22:27:44 +02:00
if reflect.DeepEqual(v, t) {
return true
}
}
return false
}