Skip to content

Commit

Permalink
✨ feat: implements APIs for managing ACLs
Browse files Browse the repository at this point in the history
Headscale currently lacks the APIs to manange the ACLs. The only way
possible currently is to load the ACLs via file and changes to the
policy requires reloading the headscale process. This also makes it
difficult to integrate your headscale via APIs with no ACL management.

This commit introduces two APIs that allow your to get and set the
policy.
  • Loading branch information
pallabpain committed Mar 5, 2024
1 parent a244eab commit 0b3660c
Show file tree
Hide file tree
Showing 30 changed files with 1,669 additions and 464 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ integration_test/etc/config.dump.yaml
/site

__debug_bin

acl.json

93 changes: 93 additions & 0 deletions cmd/headscale/cli/acl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package cli

import (
"io"
"os"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"

v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/types"
)

func init() {
rootCmd.AddCommand(aclCmd)
aclCmd.AddCommand(getACL)

setACL.Flags().StringP("policy", "p", "", "Path to a policy file in JSON format")
if err := setACL.MarkFlagRequired("policy"); err != nil {
log.Fatal().Err(err).Msg("")
}
aclCmd.AddCommand(setACL)
}

var aclCmd = &cobra.Command{
Use: "acl",
Short: "Manage the Headscale ACL Policy",
}

var getACL = &cobra.Command{
Use: "get",
Short: "Print the current ACL Policy JSON",
Aliases: []string{"show", "view", "fetch"},
Run: func(cmd *cobra.Command, args []string) {
ctx, client, conn, cancel := getHeadscaleCLIClient()
defer cancel()
defer conn.Close()

request := &v1.GetACLRequest{}

response, err := client.GetACL(ctx, request)
if err != nil {
log.Fatal().Err(err).Msg("Cannot get ACL Policy")

return
}

SuccessOutput(response.GetPolicy(), "", "json")
},
}

var setACL = &cobra.Command{
Use: "set",
Short: "Updates the ACL Policy",
Long: `
Updates the existing ACL Policy with the provided policy. The policy must be a valid JSON object.
This command only works when the acl.policy_mode is set to "db", and the policy will be stored in the database.`,
Aliases: []string{"put", "update"},
Run: func(cmd *cobra.Command, args []string) {
policyPath, _ := cmd.Flags().GetString("policy")

f, err := os.Open(policyPath)
if err != nil {
log.Fatal().Err(err).Msg("Error opening the policy file")

return
}
defer f.Close()

policyBytes, err := io.ReadAll(f)
if err != nil {
log.Fatal().Err(err).Msg("Error reading the policy file")

return
}

acl := types.ACL{Policy: policyBytes}

request := &v1.SetACLRequest{Policy: acl.Proto().GetPolicy()}

ctx, client, conn, cancel := getHeadscaleCLIClient()
defer cancel()
defer conn.Close()

if _, err := client.SetACL(ctx, request); err != nil {
log.Fatal().Err(err).Msg("Failed to set ACL Policy")

return
}

SuccessOutput(nil, "ACL Policy updated.", "")
},
}
33 changes: 9 additions & 24 deletions cmd/headscale/cli/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import (
"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"
"github.com/rs/zerolog/log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"gopkg.in/yaml.v3"

v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
)

const (
Expand All @@ -39,21 +39,6 @@ func getHeadscaleApp() (*hscontrol.Headscale, error) {
return nil, err
}

// We are doing this here, as in the future could be cool to have it also hot-reload

if cfg.ACL.PolicyPath != "" {
aclPath := util.AbsolutePathFromConfigPath(cfg.ACL.PolicyPath)
pol, err := policy.LoadACLPolicyFromPath(aclPath)
if err != nil {
log.Fatal().
Str("path", aclPath).
Err(err).
Msg("Could not load the ACL policy")
}

app.ACLPolicy = pol
}

return app, nil
}

Expand Down Expand Up @@ -89,7 +74,7 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.

// 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
socket, err := os.OpenFile(cfg.UnixSocket, os.O_WRONLY, SocketWritePermissions) // nolint
if err != nil {
if os.IsPermission(err) {
log.Fatal().
Expand Down Expand Up @@ -152,7 +137,7 @@ func SuccessOutput(result interface{}, override string, outputFormat string) {
var err error
switch outputFormat {
case "json":
jsonBytes, err = json.MarshalIndent(result, "", "\t")
jsonBytes, err = json.MarshalIndent(result, "", " ")
if err != nil {
log.Fatal().Err(err).Msg("failed to unmarshal output")
}
Expand All @@ -167,13 +152,13 @@ func SuccessOutput(result interface{}, override string, outputFormat string) {
log.Fatal().Err(err).Msg("failed to unmarshal output")
}
default:
//nolint
// nolint
fmt.Println(override)

return
}

//nolint
// nolint
fmt.Println(string(jsonBytes))
}

Expand Down
18 changes: 14 additions & 4 deletions config-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,20 @@ log:
format: text
level: info

# Path to a file containg ACL policies.
# ACLs can be defined as YAML or HUJSON.
# https://tailscale.com/kb/1018/acls/
acl_policy_path: ""
## ACL
acl:
# The supported policy modes are "db" and "file".
# If "db" is selected, the policies will be stored in the database.
# You will be able to get and set the ACL via the ACL APIs.
#
# If "file" is selected, the policies will be loaded from a file
# located at `policy_path`. It will not be stored in the databse
# and you will not be able to get or set the ACL via the ACL APIs.
policy_mode: "db"
# Path to a file containg ACL policies.
# ACLs can be defined as YAML or HUJSON.
# https://tailscale.com/kb/1018/acls/
policy_path: ""

## DNS
#
Expand Down
Loading

0 comments on commit 0b3660c

Please sign in to comment.