2023-05-10 07:24:05 +00:00
|
|
|
package hscontrol
|
2021-07-03 09:55:32 +00:00
|
|
|
|
|
|
|
import (
|
2021-11-05 07:24:00 +00:00
|
|
|
"encoding/json"
|
2022-09-01 22:05:43 +00:00
|
|
|
"net/netip"
|
2021-07-03 09:55:32 +00:00
|
|
|
"strings"
|
|
|
|
|
2021-07-03 15:31:32 +00:00
|
|
|
"github.com/tailscale/hujson"
|
2022-02-27 08:04:48 +00:00
|
|
|
"gopkg.in/yaml.v3"
|
2021-07-03 09:55:32 +00:00
|
|
|
)
|
|
|
|
|
2021-11-13 08:39:04 +00:00
|
|
|
// ACLPolicy represents a Tailscale ACL Policy.
|
2021-07-03 09:55:32 +00:00
|
|
|
type ACLPolicy struct {
|
2022-08-24 10:53:55 +00:00
|
|
|
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"`
|
|
|
|
AutoApprovers AutoApprovers `json:"autoApprovers" yaml:"autoApprovers"`
|
2022-09-30 18:44:23 +00:00
|
|
|
SSHs []SSH `json:"ssh" yaml:"ssh"`
|
2021-07-03 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2021-11-13 08:39:04 +00:00
|
|
|
// ACL is a basic rule for the ACL Policy.
|
2021-07-03 09:55:32 +00:00
|
|
|
type ACL struct {
|
2022-06-08 11:40:15 +00:00
|
|
|
Action string `json:"action" yaml:"action"`
|
2022-08-04 08:47:00 +00:00
|
|
|
Protocol string `json:"proto" yaml:"proto"`
|
|
|
|
Sources []string `json:"src" yaml:"src"`
|
|
|
|
Destinations []string `json:"dst" yaml:"dst"`
|
2021-07-03 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2021-11-13 08:39:04 +00:00
|
|
|
// Groups references a series of alias in the ACL rules.
|
2021-07-03 09:55:32 +00:00
|
|
|
type Groups map[string][]string
|
|
|
|
|
2021-11-13 08:39:04 +00:00
|
|
|
// Hosts are alias for IP addresses or subnets.
|
2022-09-01 22:05:43 +00:00
|
|
|
type Hosts map[string]netip.Prefix
|
2021-07-03 09:55:32 +00:00
|
|
|
|
2023-01-17 16:43:44 +00:00
|
|
|
// TagOwners specify what users (users?) are allow to use certain tags.
|
2021-07-03 15:31:32 +00:00
|
|
|
type TagOwners map[string][]string
|
2021-07-03 09:55:32 +00:00
|
|
|
|
2021-11-13 08:39:04 +00:00
|
|
|
// ACLTest is not implemented, but should be use to check if a certain rule is allowed.
|
2021-07-03 09:55:32 +00:00
|
|
|
type ACLTest struct {
|
2022-08-04 08:47:00 +00:00
|
|
|
Source string `json:"src" yaml:"src"`
|
|
|
|
Accept []string `json:"accept" yaml:"accept"`
|
2022-06-08 11:40:15 +00:00
|
|
|
Deny []string `json:"deny,omitempty" yaml:"deny,omitempty"`
|
2021-07-03 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2023-01-17 16:43:44 +00:00
|
|
|
// AutoApprovers specify which users (users?), groups or tags have their advertised routes
|
2022-09-04 20:40:08 +00:00
|
|
|
// or exit node status automatically enabled.
|
2022-08-24 10:53:55 +00:00
|
|
|
type AutoApprovers struct {
|
|
|
|
Routes map[string][]string `json:"routes" yaml:"routes"`
|
|
|
|
ExitNode []string `json:"exitNode" yaml:"exitNode"`
|
|
|
|
}
|
|
|
|
|
2022-09-30 18:44:23 +00:00
|
|
|
// SSH controls who can ssh into which machines.
|
|
|
|
type SSH struct {
|
|
|
|
Action string `json:"action" yaml:"action"`
|
|
|
|
Sources []string `json:"src" yaml:"src"`
|
|
|
|
Destinations []string `json:"dst" yaml:"dst"`
|
|
|
|
Users []string `json:"users" yaml:"users"`
|
|
|
|
CheckPeriod string `json:"checkPeriod,omitempty" yaml:"checkPeriod,omitempty"`
|
|
|
|
}
|
|
|
|
|
2022-09-03 21:46:14 +00:00
|
|
|
// UnmarshalJSON allows to parse the Hosts directly into netip objects.
|
2021-11-14 19:32:03 +00:00
|
|
|
func (hosts *Hosts) UnmarshalJSON(data []byte) error {
|
|
|
|
newHosts := Hosts{}
|
2021-11-15 17:24:24 +00:00
|
|
|
hostIPPrefixMap := make(map[string]string)
|
2021-11-05 07:24:00 +00:00
|
|
|
ast, err := hujson.Parse(data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ast.Standardize()
|
|
|
|
data = ast.Pack()
|
2021-11-15 17:24:24 +00:00
|
|
|
err = json.Unmarshal(data, &hostIPPrefixMap)
|
2021-07-03 15:31:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2021-07-03 09:55:32 +00:00
|
|
|
}
|
2021-11-15 17:24:24 +00:00
|
|
|
for host, prefixStr := range hostIPPrefixMap {
|
2021-11-14 19:32:03 +00:00
|
|
|
if !strings.Contains(prefixStr, "/") {
|
|
|
|
prefixStr += "/32"
|
2021-07-03 09:55:32 +00:00
|
|
|
}
|
2022-09-01 22:05:43 +00:00
|
|
|
prefix, err := netip.ParsePrefix(prefixStr)
|
2021-07-03 09:55:32 +00:00
|
|
|
if err != nil {
|
2021-07-03 15:31:32 +00:00
|
|
|
return err
|
2021-07-03 09:55:32 +00:00
|
|
|
}
|
2021-11-14 19:32:03 +00:00
|
|
|
newHosts[host] = prefix
|
2021-07-03 09:55:32 +00:00
|
|
|
}
|
2021-11-14 19:32:03 +00:00
|
|
|
*hosts = newHosts
|
2021-11-14 15:46:09 +00:00
|
|
|
|
2021-07-03 15:31:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-09-03 21:46:14 +00:00
|
|
|
// UnmarshalYAML allows to parse the Hosts directly into netip objects.
|
2022-02-27 08:04:48 +00:00
|
|
|
func (hosts *Hosts) UnmarshalYAML(data []byte) error {
|
|
|
|
newHosts := Hosts{}
|
|
|
|
hostIPPrefixMap := make(map[string]string)
|
|
|
|
|
|
|
|
err := yaml.Unmarshal(data, &hostIPPrefixMap)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for host, prefixStr := range hostIPPrefixMap {
|
2022-09-01 22:05:43 +00:00
|
|
|
prefix, err := netip.ParsePrefix(prefixStr)
|
2022-02-27 08:04:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
newHosts[host] = prefix
|
|
|
|
}
|
|
|
|
*hosts = newHosts
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-11-13 08:39:04 +00:00
|
|
|
// IsZero is perhaps a bit naive here.
|
2023-04-26 08:58:26 +00:00
|
|
|
func (pol ACLPolicy) IsZero() bool {
|
|
|
|
if len(pol.Groups) == 0 && len(pol.Hosts) == 0 && len(pol.ACLs) == 0 {
|
2021-07-03 15:31:32 +00:00
|
|
|
return true
|
|
|
|
}
|
2021-11-14 15:46:09 +00:00
|
|
|
|
2021-07-03 15:31:32 +00:00
|
|
|
return false
|
2021-07-03 09:55:32 +00:00
|
|
|
}
|
2022-08-24 11:30:04 +00:00
|
|
|
|
2023-01-17 16:43:44 +00:00
|
|
|
// Returns the list of autoApproving users, groups or tags for a given IPPrefix.
|
2022-08-24 11:30:04 +00:00
|
|
|
func (autoApprovers *AutoApprovers) GetRouteApprovers(
|
2022-09-04 23:33:53 +00:00
|
|
|
prefix netip.Prefix,
|
2022-08-24 11:30:04 +00:00
|
|
|
) ([]string, error) {
|
|
|
|
if prefix.Bits() == 0 {
|
|
|
|
return autoApprovers.ExitNode, nil // 0.0.0.0/0, ::/0 or equivalent
|
|
|
|
}
|
|
|
|
|
|
|
|
approverAliases := []string{}
|
|
|
|
|
|
|
|
for autoApprovedPrefix, autoApproverAliases := range autoApprovers.Routes {
|
2022-09-04 23:33:53 +00:00
|
|
|
autoApprovedPrefix, err := netip.ParsePrefix(autoApprovedPrefix)
|
2022-08-24 11:30:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-11-01 11:00:40 +00:00
|
|
|
if prefix.Bits() >= autoApprovedPrefix.Bits() &&
|
2022-09-04 23:33:53 +00:00
|
|
|
autoApprovedPrefix.Contains(prefix.Masked().Addr()) {
|
2022-08-24 11:30:04 +00:00
|
|
|
approverAliases = append(approverAliases, autoApproverAliases...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return approverAliases, nil
|
|
|
|
}
|