mirror of
https://github.com/juanfont/headscale.git
synced 2026-01-23 02:24:10 +00:00
cmd: add option to get and set policy directly from database (#2765)
Some checks are pending
Build / build-nix (push) Waiting to run
Build / build-cross (GOARCH=amd64 GOOS=darwin) (push) Waiting to run
Build / build-cross (GOARCH=amd64 GOOS=linux) (push) Waiting to run
Build / build-cross (GOARCH=arm64 GOOS=darwin) (push) Waiting to run
Build / build-cross (GOARCH=arm64 GOOS=linux) (push) Waiting to run
Check Generated Files / check-generated (push) Waiting to run
Tests / test (push) Waiting to run
Some checks are pending
Build / build-nix (push) Waiting to run
Build / build-cross (GOARCH=amd64 GOOS=darwin) (push) Waiting to run
Build / build-cross (GOARCH=amd64 GOOS=linux) (push) Waiting to run
Build / build-cross (GOARCH=arm64 GOOS=darwin) (push) Waiting to run
Build / build-cross (GOARCH=arm64 GOOS=linux) (push) Waiting to run
Check Generated Files / check-generated (push) Waiting to run
Tests / test (push) Waiting to run
This commit is contained in:
parent
2938d03878
commit
2b30a15a68
10 changed files with 365 additions and 126 deletions
|
|
@ -9,7 +9,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
survey "github.com/AlecAivazis/survey/v2"
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/pterm/pterm"
|
||||
|
|
@ -222,8 +221,6 @@ var listNodeRoutesCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error converting ID to integer: %s", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
||||
|
|
@ -290,8 +287,6 @@ var expireNodeCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error converting ID to integer: %s", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
||||
|
|
@ -312,8 +307,6 @@ var expireNodeCmd = &cobra.Command{
|
|||
),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
SuccessOutput(response.GetNode(), "Node expired", output)
|
||||
|
|
@ -333,8 +326,6 @@ var renameNodeCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error converting ID to integer: %s", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
||||
|
|
@ -360,8 +351,6 @@ var renameNodeCmd = &cobra.Command{
|
|||
),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
SuccessOutput(response.GetNode(), "Node renamed", output)
|
||||
|
|
@ -382,8 +371,6 @@ var deleteNodeCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error converting ID to integer: %s", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
||||
|
|
@ -401,8 +388,6 @@ var deleteNodeCmd = &cobra.Command{
|
|||
"Error getting node node: "+status.Convert(err).Message(),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
deleteRequest := &v1.DeleteNodeRequest{
|
||||
|
|
@ -412,16 +397,10 @@ var deleteNodeCmd = &cobra.Command{
|
|||
confirm := false
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
if !force {
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf(
|
||||
"Do you want to remove the node %s?",
|
||||
getResponse.GetNode().GetName(),
|
||||
),
|
||||
}
|
||||
err = survey.AskOne(prompt, &confirm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
confirm = util.YesNo(fmt.Sprintf(
|
||||
"Do you want to remove the node %s?",
|
||||
getResponse.GetNode().GetName(),
|
||||
))
|
||||
}
|
||||
|
||||
if confirm || force {
|
||||
|
|
@ -437,8 +416,6 @@ var deleteNodeCmd = &cobra.Command{
|
|||
"Error deleting node: "+status.Convert(err).Message(),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
SuccessOutput(
|
||||
map[string]string{"Result": "Node deleted"},
|
||||
|
|
@ -465,8 +442,6 @@ var moveNodeCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error converting ID to integer: %s", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
user, err := cmd.Flags().GetUint64("user")
|
||||
|
|
@ -476,8 +451,6 @@ var moveNodeCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error getting user: %s", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
||||
|
|
@ -495,8 +468,6 @@ var moveNodeCmd = &cobra.Command{
|
|||
"Error getting node: "+status.Convert(err).Message(),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
moveRequest := &v1.MoveNodeRequest{
|
||||
|
|
@ -511,8 +482,6 @@ var moveNodeCmd = &cobra.Command{
|
|||
"Error moving node: "+status.Convert(err).Message(),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
SuccessOutput(moveResponse.GetNode(), "Node moved to another user", output)
|
||||
|
|
@ -535,20 +504,13 @@ If you remove IPv4 or IPv6 prefixes from the config,
|
|||
it can be run to remove the IPs that should no longer
|
||||
be assigned to nodes.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var err error
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
|
||||
confirm := false
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
if !force {
|
||||
prompt := &survey.Confirm{
|
||||
Message: "Are you sure that you want to assign/remove IPs to/from nodes?",
|
||||
}
|
||||
err = survey.AskOne(prompt, &confirm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
confirm = util.YesNo("Are you sure that you want to assign/remove IPs to/from nodes?")
|
||||
}
|
||||
|
||||
if confirm || force {
|
||||
|
|
@ -563,8 +525,6 @@ be assigned to nodes.`,
|
|||
"Error backfilling IPs: "+status.Convert(err).Message(),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
SuccessOutput(changes, "Node IPs backfilled successfully", output)
|
||||
|
|
@ -763,8 +723,6 @@ var tagCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error converting ID to integer: %s", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
tagsToSet, err := cmd.Flags().GetStringSlice("tags")
|
||||
if err != nil {
|
||||
|
|
@ -773,8 +731,6 @@ var tagCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error retrieving list of tags to add to node, %v", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Sending tags to node
|
||||
|
|
@ -789,8 +745,6 @@ var tagCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error while sending tags to headscale: %s", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
|
|
@ -820,8 +774,6 @@ var approveRoutesCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error converting ID to integer: %s", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
routes, err := cmd.Flags().GetStringSlice("routes")
|
||||
if err != nil {
|
||||
|
|
@ -830,8 +782,6 @@ var approveRoutesCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error retrieving list of routes to add to node, %v", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Sending routes to node
|
||||
|
|
@ -846,8 +796,6 @@ var approveRoutesCmd = &cobra.Command{
|
|||
fmt.Sprintf("Error while sending routes to headscale: %s", err),
|
||||
output,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
|
|
|
|||
|
|
@ -6,21 +6,30 @@ import (
|
|||
"os"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/db"
|
||||
"github.com/juanfont/headscale/hscontrol/policy"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
const (
|
||||
bypassFlag = "bypass-grpc-and-access-database-directly"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(policyCmd)
|
||||
|
||||
getPolicy.Flags().BoolP(bypassFlag, "", false, "Uses the headscale config to directly access the database, bypassing gRPC and does not require the server to be running")
|
||||
policyCmd.AddCommand(getPolicy)
|
||||
|
||||
setPolicy.Flags().StringP("file", "f", "", "Path to a policy file in HuJSON format")
|
||||
if err := setPolicy.MarkFlagRequired("file"); err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
setPolicy.Flags().BoolP(bypassFlag, "", false, "Uses the headscale config to directly access the database, bypassing gRPC and does not require the server to be running")
|
||||
policyCmd.AddCommand(setPolicy)
|
||||
|
||||
checkPolicy.Flags().StringP("file", "f", "", "Path to a policy file in HuJSON format")
|
||||
|
|
@ -41,21 +50,58 @@ var getPolicy = &cobra.Command{
|
|||
Aliases: []string{"show", "view", "fetch"},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
||||
defer cancel()
|
||||
defer conn.Close()
|
||||
var policy string
|
||||
if bypass, _ := cmd.Flags().GetBool(bypassFlag); bypass {
|
||||
confirm := false
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
if !force {
|
||||
confirm = util.YesNo("DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?")
|
||||
}
|
||||
|
||||
request := &v1.GetPolicyRequest{}
|
||||
if !confirm && !force {
|
||||
ErrorOutput(nil, "Aborting command", output)
|
||||
return
|
||||
}
|
||||
|
||||
response, err := client.GetPolicy(ctx, request)
|
||||
if err != nil {
|
||||
ErrorOutput(err, fmt.Sprintf("Failed loading ACL Policy: %s", err), output)
|
||||
cfg, err := types.LoadServerConfig()
|
||||
if err != nil {
|
||||
ErrorOutput(err, fmt.Sprintf("Failed loading config: %s", err), output)
|
||||
}
|
||||
|
||||
d, err := db.NewHeadscaleDatabase(
|
||||
cfg.Database,
|
||||
cfg.BaseDomain,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
ErrorOutput(err, fmt.Sprintf("Failed to open database: %s", err), output)
|
||||
}
|
||||
|
||||
pol, err := d.GetPolicy()
|
||||
if err != nil {
|
||||
ErrorOutput(err, fmt.Sprintf("Failed loading Policy from database: %s", err), output)
|
||||
}
|
||||
|
||||
policy = pol.Data
|
||||
} else {
|
||||
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
||||
defer cancel()
|
||||
defer conn.Close()
|
||||
|
||||
request := &v1.GetPolicyRequest{}
|
||||
|
||||
response, err := client.GetPolicy(ctx, request)
|
||||
if err != nil {
|
||||
ErrorOutput(err, fmt.Sprintf("Failed loading ACL Policy: %s", err), output)
|
||||
}
|
||||
|
||||
policy = response.GetPolicy()
|
||||
}
|
||||
|
||||
// TODO(pallabpain): Maybe print this better?
|
||||
// This does not pass output as we dont support yaml, json or json-line
|
||||
// output for this command. It is HuJSON already.
|
||||
SuccessOutput("", response.GetPolicy(), "")
|
||||
SuccessOutput("", policy, "")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -81,14 +127,52 @@ var setPolicy = &cobra.Command{
|
|||
ErrorOutput(err, fmt.Sprintf("Error reading the policy file: %s", err), output)
|
||||
}
|
||||
|
||||
request := &v1.SetPolicyRequest{Policy: string(policyBytes)}
|
||||
_, err = policy.NewPolicyManager(policyBytes, nil, views.Slice[types.NodeView]{})
|
||||
if err != nil {
|
||||
ErrorOutput(err, fmt.Sprintf("Error parsing the policy file: %s", err), output)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
||||
defer cancel()
|
||||
defer conn.Close()
|
||||
if bypass, _ := cmd.Flags().GetBool(bypassFlag); bypass {
|
||||
confirm := false
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
if !force {
|
||||
confirm = util.YesNo("DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?")
|
||||
}
|
||||
|
||||
if _, err := client.SetPolicy(ctx, request); err != nil {
|
||||
ErrorOutput(err, fmt.Sprintf("Failed to set ACL Policy: %s", err), output)
|
||||
if !confirm && !force {
|
||||
ErrorOutput(nil, "Aborting command", output)
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := types.LoadServerConfig()
|
||||
if err != nil {
|
||||
ErrorOutput(err, fmt.Sprintf("Failed loading config: %s", err), output)
|
||||
}
|
||||
|
||||
d, err := db.NewHeadscaleDatabase(
|
||||
cfg.Database,
|
||||
cfg.BaseDomain,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
ErrorOutput(err, fmt.Sprintf("Failed to open database: %s", err), output)
|
||||
}
|
||||
|
||||
_, err = d.SetPolicy(string(policyBytes))
|
||||
if err != nil {
|
||||
ErrorOutput(err, fmt.Sprintf("Failed to set ACL Policy: %s", err), output)
|
||||
}
|
||||
} else {
|
||||
request := &v1.SetPolicyRequest{Policy: string(policyBytes)}
|
||||
|
||||
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
||||
defer cancel()
|
||||
defer conn.Close()
|
||||
|
||||
if _, err := client.SetPolicy(ctx, request); err != nil {
|
||||
ErrorOutput(err, fmt.Sprintf("Failed to set ACL Policy: %s", err), output)
|
||||
}
|
||||
}
|
||||
|
||||
SuccessOutput(nil, "Policy updated.", "")
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import (
|
|||
"net/url"
|
||||
"strconv"
|
||||
|
||||
survey "github.com/AlecAivazis/survey/v2"
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
|
|
@ -161,16 +161,10 @@ var destroyUserCmd = &cobra.Command{
|
|||
confirm := false
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
if !force {
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf(
|
||||
"Do you want to remove the user %q (%d) and any associated preauthkeys?",
|
||||
user.GetName(), user.GetId(),
|
||||
),
|
||||
}
|
||||
err := survey.AskOne(prompt, &confirm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
confirm = util.YesNo(fmt.Sprintf(
|
||||
"Do you want to remove the user %q (%d) and any associated preauthkeys?",
|
||||
user.GetName(), user.GetId(),
|
||||
))
|
||||
}
|
||||
|
||||
if confirm || force {
|
||||
|
|
|
|||
|
|
@ -169,7 +169,14 @@ func ErrorOutput(errResult error, override string, outputFormat string) {
|
|||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%s\n", output(errOutput{errResult.Error()}, override, outputFormat))
|
||||
var errorMessage string
|
||||
if errResult != nil {
|
||||
errorMessage = errResult.Error()
|
||||
} else {
|
||||
errorMessage = override
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%s\n", output(errOutput{errorMessage}, override, outputFormat))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue