Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added cli options to output ACLs #581

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
- Add -c option to specify config file from command line [#285](https://github.com/juanfont/headscale/issues/285) [#612](https://github.com/juanfont/headscale/pull/601)
- Add configuration option to allow Tailscale clients to use a random WireGuard port. [kb/1181/firewalls](https://tailscale.com/kb/1181/firewalls) [#624](https://github.com/juanfont/headscale/pull/624)
- Improve obtuse UX regarding missing configuration (`ephemeral_node_inactivity_timeout` not set) [#639](https://github.com/juanfont/headscale/pull/639)
- Add command to show current ACLs [#492](https://github.com/juanfont/headscale/issues/492)
- Fix nodes being shown as 'offline' in `tailscale status` [#648](https://github.com/juanfont/headscale/pull/648)
- Improve shutdown behaviour [#651](https://github.com/juanfont/headscale/pull/651)
- Drop Gin as web framework in Headscale [648](https://github.com/juanfont/headscale/pull/648) [677](https://github.com/juanfont/headscale/pull/677)
Expand Down
139 changes: 139 additions & 0 deletions acls.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strconv"
"strings"

v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/rs/zerolog/log"
"github.com/tailscale/hujson"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -123,6 +124,144 @@ func (h *Headscale) UpdateACLRules() error {
return nil
}

func (h *Headscale) ListACLPolicy() (*ACLPolicy, error) {
return h.aclPolicy, nil
}

func ACLProtoToStruct(v *v1.ACLPolicy) (*ACLPolicy, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the linter is complaining

parameter name 'v' is too short for the scope of its usage (varnamelen)

Replace vfor policy or something like this, for instance.

// v := req.GetPolicy()

// groups parsing
vgroups := v.GetGroups()
groups := make(map[string][]string, len(vgroups))
for n, i := range vgroups {
groups[n] = i.GetGroup()
}

// hosts parsing
vhosts := v.GetHosts()
hosts := make(map[string]netaddr.IPPrefix, len(vhosts))
for n, i := range vhosts {
addr, err := netaddr.ParseIPPrefix(i)
if err != nil {
return nil, err
}
hosts[n] = addr
}

// tag owners parsing
vtagowners := v.GetTagOwners()
tagowners := make(map[string][]string, len(vtagowners))
for n, i := range vtagowners {
tagowners[n] = i.GetTagOwners()
}

// ACLs parsing
vacls := (*v).GetAcls()
acls := make([]ACL, len(vacls))
for n, i := range vacls {
acls[n] = ACL{
Action: i.GetAction(),
Protocol: i.GetProtocol(),
Sources: i.GetSources(),
Destinations: i.GetDestinations(),
}
}

// ACL Tests parsing
vtests := v.GetAclTest()
tests := make([]ACLTest, len(vtests))
for n, i := range vtests {
tests[n] = ACLTest{
Source: i.GetSource(),
Accept: i.GetAccept(),
Deny: i.GetDeny(),
}
}

return &ACLPolicy{
Groups: groups,
Hosts: hosts,
TagOwners: tagowners,
ACLs: acls,
Tests: tests,
}, nil
}

func (policy *ACLPolicy) toProto() *v1.ACLPolicy {
protoACLPolicy := v1.ACLPolicy{
Groups: policy.Groups.toProto(),
Hosts: policy.Hosts.toProto(),
TagOwners: policy.TagOwners.toProto(),
}

// proto acls
protoACLPolicy.Acls = make([]*v1.ACL, len(policy.ACLs))
for k, v := range policy.ACLs {
protoACLPolicy.Acls[k] = v.toProto()
}

// proto acl tests
protoACLPolicy.AclTest = make([]*v1.ACLTest, len(policy.Tests))
for k, v := range policy.Tests {
protoACLPolicy.AclTest[k] = v.toProto()
}

return &protoACLPolicy
}

func (a *ACL) toProto() *v1.ACL {
protoACL := v1.ACL{
Action: a.Action,
Protocol: a.Protocol,
Sources: a.Sources,
Destinations: a.Destinations,
}

return &protoACL
}

func (a *ACLTest) toProto() *v1.ACLTest {
protoACLTest := v1.ACLTest{
Source: a.Source,
Accept: a.Accept,
Deny: a.Deny,
}

return &protoACLTest
}

func (g *Groups) toProto() map[string]*v1.Group {
protoGroups := make(map[string]*v1.Group, len(*g))
for k, v := range *g {
protoGroupSingle := &v1.Group{
Group: v,
}
protoGroups[k] = protoGroupSingle
}

return protoGroups
}

func (t *TagOwners) toProto() map[string]*v1.TagOwners {
protoTagOwners := make(map[string]*v1.TagOwners, len(*t))
for k, v := range *t {
protoTagOwner := &v1.TagOwners{
TagOwners: v,
}
protoTagOwners[k] = protoTagOwner
}
return protoTagOwners
}

func (h *Hosts) toProto() map[string]string {
protoHosts := make(map[string]string, len(*h))
for k, v := range *h {
protoHosts[k] = v.String()
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}

return protoHosts
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}


func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
rules := []tailcfg.FilterRule{}

Expand Down
24 changes: 12 additions & 12 deletions acls_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ import (

// ACLPolicy represents a Tailscale ACL Policy.
type ACLPolicy struct {
Groups Groups `json:"groups" yaml:"groups"`
Hosts Hosts `json:"hosts" yaml:"hosts"`
TagOwners TagOwners `json:"tagOwners" yaml:"tagOwners"`
ACLs []ACL `json:"acls" yaml:"acls"`
Tests []ACLTest `json:"tests" yaml:"tests"`
Groups Groups `json:"groups,omitempty" yaml:"groups,omitempty"`
Hosts Hosts `json:"hosts,omitempty" yaml:"hosts,omitempty"`
TagOwners TagOwners `json:"tagOwners,omitempty" yaml:"tagOwners,omitempty"`
ACLs []ACL `json:"acls,omitempty" yaml:"acls,omitempty"`
Tests []ACLTest `json:"tests,omitempty" yaml:"tests,omitempty"`
}

// ACL is a basic rule for the ACL Policy.
type ACL struct {
Action string `json:"action" yaml:"action"`
Protocol string `json:"proto" yaml:"proto"`
Sources []string `json:"src" yaml:"src"`
Destinations []string `json:"dst" yaml:"dst"`
Action string `json:"action,omitempty" yaml:"action,omitempty"`
Protocol string `json:"proto,omitempty" yaml:"proto,omitempty"`
Sources []string `json:"src,omitempty" yaml:"src,omitempty"`
Destinations []string `json:"dst,omitempty" yaml:"dst,omitempty"`
}

// Groups references a series of alias in the ACL rules.
Expand All @@ -37,9 +37,9 @@ type TagOwners map[string][]string

// ACLTest is not implemented, but should be use to check if a certain rule is allowed.
type ACLTest struct {
Source string `json:"src" yaml:"src"`
Accept []string `json:"accept" yaml:"accept"`
Deny []string `json:"deny,omitempty" yaml:"deny,omitempty"`
Source string `json:"src,omitempty" yaml:"src,omitempty"`
Accept []string `json:"accept,omitempty" yaml:"accept,omitempty"`
Deny []string `json:"deny,omitempty" yaml:"deny,omitempty"`
}

// UnmarshalJSON allows to parse the Hosts directly into netaddr objects.
Expand Down
78 changes: 78 additions & 0 deletions cmd/headscale/cli/acls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cli

import (
"fmt"

"github.com/juanfont/headscale"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(aclsCmd)
aclsCmd.AddCommand(listAclsCmd)
}

var aclsCmd = &cobra.Command{
Use: "acls",
Short: "Manage Access Control Lists (ACLs)",
Aliases: []string{"access-lists", "acl"},
}

var listAclsCmd = &cobra.Command{
Use: "list",
Short: "List ACLs",
Aliases: []string{"ls", "show"},
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
if output == `` {
output = `json`
}

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

request := &v1.ListACLPolicyRequest{}

response, err := client.ListACLPolicy(ctx, request)
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error getting ACL from server: %s", err),
output,
)

return
}

if response == nil {
SuccessOutput(
``,
`No policy defined.`,
``,
)

return
}

policy, err := headscale.ACLProtoToStruct(response.Policy)
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error parsing response from server: %s", err),
output,
)

return
}

SuccessOutput(
policy,
``,
output,
)

return
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not required

},
}
Loading