mirror of
https://github.com/jsiebens/ionscale.git
synced 2026-03-31 15:07:49 +01:00
feat: use hujson as data format for ACL and IAM policy
This commit is contained in:
+4
-30
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/go-edit/editor"
|
||||
@@ -25,12 +24,7 @@ func getACLConfigCommand() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
marshal, err := json.MarshalIndent(resp.Msg.Policy, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(marshal))
|
||||
fmt.Println(resp.Msg.Policy)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -53,12 +47,7 @@ func editACLConfigCommand() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
previous, err := json.MarshalIndent(resp.Msg.Policy, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
next, s, err := edit.LaunchTempFile("ionscale", ".json", bytes.NewReader(previous))
|
||||
next, s, err := edit.LaunchTempFile("ionscale", ".json", bytes.NewReader([]byte(resp.Msg.Policy)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -70,12 +59,7 @@ func editACLConfigCommand() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
var policy = &api.ACLPolicy{}
|
||||
if err := json.Unmarshal(next, policy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tc.Client().SetACLPolicy(cmd.Context(), connect.NewRequest(&api.SetACLPolicyRequest{TailnetId: tc.TailnetID(), Policy: policy}))
|
||||
_, err = tc.Client().SetACLPolicy(cmd.Context(), connect.NewRequest(&api.SetACLPolicyRequest{TailnetId: tc.TailnetID(), Policy: string(next)}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -105,17 +89,7 @@ func setACLConfigCommand() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
rawJson, err := hujson.Standardize(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var policy = &api.ACLPolicy{}
|
||||
if err := json.Unmarshal(rawJson, policy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tc.Client().SetACLPolicy(cmd.Context(), connect.NewRequest(&api.SetACLPolicyRequest{TailnetId: tc.TailnetID(), Policy: policy}))
|
||||
_, err = tc.Client().SetACLPolicy(cmd.Context(), connect.NewRequest(&api.SetACLPolicyRequest{TailnetId: tc.TailnetID(), Policy: string(content)}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+4
-36
@@ -2,13 +2,11 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/go-edit/editor"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tailscale/hujson"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -25,12 +23,7 @@ func getIAMPolicyCommand() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
marshal, err := json.MarshalIndent(resp.Msg.Policy, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(marshal))
|
||||
fmt.Println(resp.Msg.Policy)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -53,29 +46,14 @@ func editIAMPolicyCommand() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
previous, err := json.MarshalIndent(resp.Msg.Policy, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
next, s, err := edit.LaunchTempFile("ionscale", ".json", bytes.NewReader(previous))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
next, err = hujson.Standardize(next)
|
||||
next, s, err := edit.LaunchTempFile("ionscale", ".json", bytes.NewReader([]byte(resp.Msg.Policy)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer os.Remove(s)
|
||||
|
||||
var policy = &api.IAMPolicy{}
|
||||
if err := json.Unmarshal(next, policy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tc.Client().SetIAMPolicy(cmd.Context(), connect.NewRequest(&api.SetIAMPolicyRequest{TailnetId: tc.TailnetID(), Policy: policy}))
|
||||
_, err = tc.Client().SetIAMPolicy(cmd.Context(), connect.NewRequest(&api.SetIAMPolicyRequest{TailnetId: tc.TailnetID(), Policy: string(next)}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -105,17 +83,7 @@ func setIAMPolicyCommand() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
rawJson, err := hujson.Standardize(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var policy = &api.IAMPolicy{}
|
||||
if err := json.Unmarshal(rawJson, policy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tc.Client().SetIAMPolicy(cmd.Context(), connect.NewRequest(&api.SetIAMPolicyRequest{TailnetId: tc.TailnetID(), Policy: policy}))
|
||||
_, err = tc.Client().SetIAMPolicy(cmd.Context(), connect.NewRequest(&api.SetIAMPolicyRequest{TailnetId: tc.TailnetID(), Policy: string(content)}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+13
-4
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
idomain "github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/pkg/client/ionscale"
|
||||
"github.com/jsiebens/ionscale/pkg/defaults"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/rodaine/table"
|
||||
@@ -102,24 +103,32 @@ func createTailnetsCommand() *cobra.Command {
|
||||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
dnsConfig := defaults.DefaultDNSConfig()
|
||||
aclPolicy := defaults.DefaultACLPolicy()
|
||||
iamPolicy := &api.IAMPolicy{}
|
||||
aclPolicy := defaults.DefaultACLPolicy().Marshal()
|
||||
iamPolicy := "{}"
|
||||
|
||||
if len(domain) != 0 {
|
||||
domainToLower := strings.ToLower(domain)
|
||||
iamPolicy = &api.IAMPolicy{
|
||||
m, err := json.MarshalIndent(&ionscale.IAMPolicy{
|
||||
Filters: []string{fmt.Sprintf("domain == %s", domainToLower)},
|
||||
}, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iamPolicy = string(m)
|
||||
}
|
||||
|
||||
if len(email) != 0 {
|
||||
emailToLower := strings.ToLower(email)
|
||||
iamPolicy = &api.IAMPolicy{
|
||||
m, err := json.MarshalIndent(&ionscale.IAMPolicy{
|
||||
Emails: []string{emailToLower},
|
||||
Roles: map[string]string{
|
||||
emailToLower: string(idomain.UserRoleAdmin),
|
||||
},
|
||||
}, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iamPolicy = string(m)
|
||||
}
|
||||
|
||||
resp, err := tc.Client().CreateTailnet(cmd.Context(), connect.NewRequest(&api.CreateTailnetRequest{
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func m202403130830_json_to_text() *gormigrate.Migration {
|
||||
return &gormigrate.Migration{
|
||||
ID: "202403130830",
|
||||
Migrate: func(db *gorm.DB) error {
|
||||
type Tailnet struct {
|
||||
IAMPolicy string
|
||||
ACLPolicy string
|
||||
}
|
||||
|
||||
if err := db.Migrator().AlterColumn(&Tailnet{}, "IAMPolicy"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db.Migrator().AlterColumn(&Tailnet{}, "ACLPolicy"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Rollback: nil,
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ func Migrations() []*gormigrate.Migration {
|
||||
m202312290900_machine_indeces(),
|
||||
m202401061400_machine_indeces(),
|
||||
m202402120800_user_last_authenticated(),
|
||||
m202403130830_json_to_text(),
|
||||
}
|
||||
return migrations
|
||||
}
|
||||
|
||||
+2
-35
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/jsiebens/ionscale/pkg/client/ionscale"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
"net/netip"
|
||||
@@ -30,41 +31,7 @@ type AutoApprovers struct {
|
||||
}
|
||||
|
||||
type ACLPolicy struct {
|
||||
Groups map[string][]string `json:"groups,omitempty"`
|
||||
Hosts map[string]string `json:"hosts,omitempty"`
|
||||
ACLs []ACL `json:"acls,omitempty"`
|
||||
TagOwners map[string][]string `json:"tagowners,omitempty"`
|
||||
AutoApprovers *AutoApprovers `json:"autoApprovers,omitempty"`
|
||||
SSHRules []SSHRule `json:"ssh,omitempty"`
|
||||
NodeAttrs []NodeAttr `json:"nodeAttrs,omitempty"`
|
||||
Grants []Grant `json:"grants,omitempty"`
|
||||
}
|
||||
|
||||
type ACL struct {
|
||||
Action string `json:"action"`
|
||||
Proto string `json:"proto"`
|
||||
Src []string `json:"src"`
|
||||
Dst []string `json:"dst"`
|
||||
}
|
||||
|
||||
type SSHRule struct {
|
||||
Action string `json:"action"`
|
||||
Src []string `json:"src"`
|
||||
Dst []string `json:"dst"`
|
||||
Users []string `json:"users"`
|
||||
CheckPeriod string `json:"checkPeriod,omitempty"`
|
||||
}
|
||||
|
||||
type NodeAttr struct {
|
||||
Target []string `json:"target"`
|
||||
Attr []string `json:"attr"`
|
||||
}
|
||||
|
||||
type Grant struct {
|
||||
Src []string `json:"src"`
|
||||
Dst []string `json:"dst"`
|
||||
IP []tailcfg.ProtoPortRange `json:"ip"`
|
||||
App tailcfg.PeerCapMap `json:"app"`
|
||||
ionscale.ACLPolicy
|
||||
}
|
||||
|
||||
func (a *ACLPolicy) Equal(x *ACLPolicy) bool {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/jsiebens/ionscale/pkg/client/ionscale"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -12,16 +13,16 @@ func (a ACLPolicy) IsValidPeer(src *Machine, dest *Machine) bool {
|
||||
}
|
||||
|
||||
for _, acl := range a.ACLs {
|
||||
selfDestPorts, allDestPorts := a.translateDestinationAliasesToMachineNetPortRanges(acl.Dst, dest)
|
||||
selfDestPorts, allDestPorts := a.translateDestinationAliasesToMachineNetPortRanges(acl.Destination, dest)
|
||||
if len(selfDestPorts) != 0 {
|
||||
for _, alias := range acl.Src {
|
||||
for _, alias := range acl.Source {
|
||||
if len(a.translateSourceAliasToMachineIPs(alias, src, &dest.User)) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(allDestPorts) != 0 {
|
||||
for _, alias := range acl.Src {
|
||||
for _, alias := range acl.Source {
|
||||
if len(a.translateSourceAliasToMachineIPs(alias, src, nil)) != 0 {
|
||||
return true
|
||||
}
|
||||
@@ -30,16 +31,16 @@ func (a ACLPolicy) IsValidPeer(src *Machine, dest *Machine) bool {
|
||||
}
|
||||
|
||||
for _, grant := range a.Grants {
|
||||
selfIps, otherIps := a.translateDestinationAliasesToMachineIPs(grant.Dst, dest)
|
||||
selfIps, otherIps := a.translateDestinationAliasesToMachineIPs(grant.Destination, dest)
|
||||
if len(selfIps) != 0 {
|
||||
for _, alias := range grant.Src {
|
||||
for _, alias := range grant.Source {
|
||||
if len(a.translateSourceAliasToMachineIPs(alias, src, &dest.User)) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(otherIps) != 0 {
|
||||
for _, alias := range grant.Src {
|
||||
for _, alias := range grant.Source {
|
||||
if len(a.translateSourceAliasToMachineIPs(alias, src, nil)) != 0 {
|
||||
return true
|
||||
}
|
||||
@@ -89,23 +90,23 @@ func (a ACLPolicy) BuildFilterRules(peers []Machine, dst *Machine) []tailcfg.Fil
|
||||
|
||||
for _, acl := range a.ACLs {
|
||||
self, other := a.prepareFilterRulesFromACL(dst, acl)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Src, self, &dst.User)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Src, other, nil)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Source, self, &dst.User)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Source, other, nil)
|
||||
}
|
||||
|
||||
for _, acl := range a.Grants {
|
||||
self, other := a.prepareFilterRulesFromGrant(dst, acl)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Src, self, &dst.User)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Src, other, nil)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Source, self, &dst.User)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Source, other, nil)
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
func (a ACLPolicy) prepareFilterRulesFromACL(candidate *Machine, acl ACL) ([]tailcfg.FilterRule, []tailcfg.FilterRule) {
|
||||
proto := parseProtocol(acl.Proto)
|
||||
func (a ACLPolicy) prepareFilterRulesFromACL(candidate *Machine, acl ionscale.ACLEntry) ([]tailcfg.FilterRule, []tailcfg.FilterRule) {
|
||||
proto := parseProtocol(acl.Protocol)
|
||||
|
||||
selfDstPorts, otherDstPorts := a.translateDestinationAliasesToMachineNetPortRanges(acl.Dst, candidate)
|
||||
selfDstPorts, otherDstPorts := a.translateDestinationAliasesToMachineNetPortRanges(acl.Destination, candidate)
|
||||
|
||||
var selfFilterRules []tailcfg.FilterRule
|
||||
var otherFilterRules []tailcfg.FilterRule
|
||||
@@ -121,8 +122,8 @@ func (a ACLPolicy) prepareFilterRulesFromACL(candidate *Machine, acl ACL) ([]tai
|
||||
return selfFilterRules, otherFilterRules
|
||||
}
|
||||
|
||||
func (a ACLPolicy) prepareFilterRulesFromGrant(candidate *Machine, grant Grant) ([]tailcfg.FilterRule, []tailcfg.FilterRule) {
|
||||
selfIPs, otherIPs := a.translateDestinationAliasesToMachineIPs(grant.Dst, candidate)
|
||||
func (a ACLPolicy) prepareFilterRulesFromGrant(candidate *Machine, grant ionscale.ACLGrant) ([]tailcfg.FilterRule, []tailcfg.FilterRule) {
|
||||
selfIPs, otherIPs := a.translateDestinationAliasesToMachineIPs(grant.Destination, candidate)
|
||||
|
||||
var selfFilterRules []tailcfg.FilterRule
|
||||
var otherFilterRules []tailcfg.FilterRule
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/jsiebens/ionscale/pkg/client/ionscale"
|
||||
"strings"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
@@ -28,7 +29,7 @@ func (a ACLPolicy) BuildSSHPolicy(srcs []Machine, dst *Machine) *tailcfg.SSHPoli
|
||||
return result
|
||||
}
|
||||
|
||||
for _, rule := range a.SSHRules {
|
||||
for _, rule := range a.SSH {
|
||||
if rule.Action != "accept" && rule.Action != "check" {
|
||||
continue
|
||||
}
|
||||
@@ -48,7 +49,7 @@ func (a ACLPolicy) BuildSSHPolicy(srcs []Machine, dst *Machine) *tailcfg.SSHPoli
|
||||
selfUsers, otherUsers := a.expandSSHDstToSSHUsers(dst, rule)
|
||||
|
||||
if len(selfUsers) != 0 {
|
||||
principals := expandSrcAliases(rule.Src, rule.Action, &dst.User)
|
||||
principals := expandSrcAliases(rule.Source, rule.Action, &dst.User)
|
||||
if len(principals) != 0 {
|
||||
rules = append(rules, &tailcfg.SSHRule{
|
||||
Principals: principals,
|
||||
@@ -59,7 +60,7 @@ func (a ACLPolicy) BuildSSHPolicy(srcs []Machine, dst *Machine) *tailcfg.SSHPoli
|
||||
}
|
||||
|
||||
if len(otherUsers) != 0 {
|
||||
principals := expandSrcAliases(rule.Src, rule.Action, nil)
|
||||
principals := expandSrcAliases(rule.Source, rule.Action, nil)
|
||||
if len(principals) != 0 {
|
||||
rules = append(rules, &tailcfg.SSHRule{
|
||||
Principals: principals,
|
||||
@@ -113,13 +114,13 @@ func (a ACLPolicy) expandSSHSrcAlias(m *Machine, alias string, dstUser *User) []
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (a ACLPolicy) expandSSHDstToSSHUsers(m *Machine, rule SSHRule) (map[string]string, map[string]string) {
|
||||
func (a ACLPolicy) expandSSHDstToSSHUsers(m *Machine, rule ionscale.ACLSSH) (map[string]string, map[string]string) {
|
||||
users := buildSSHUsers(rule.Users)
|
||||
|
||||
var selfUsers map[string]string
|
||||
var otherUsers map[string]string
|
||||
|
||||
for _, d := range rule.Dst {
|
||||
for _, d := range rule.Destination {
|
||||
if strings.HasPrefix(d, "tag:") && m.HasTag(d) {
|
||||
otherUsers = users
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package domain
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/jsiebens/ionscale/pkg/client/ionscale"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"tailscale.com/tailcfg"
|
||||
"testing"
|
||||
@@ -13,12 +14,14 @@ func TestACLPolicy_BuildSSHPolicy_(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
SSHRules: []SSHRule{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"autogroup:members"},
|
||||
Dst: []string{"autogroup:self"},
|
||||
Users: []string{"autogroup:nonroot"},
|
||||
ionscale.ACLPolicy{
|
||||
SSH: []ionscale.ACLSSH{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"autogroup:members"},
|
||||
Destination: []string{"autogroup:self"},
|
||||
Users: []string{"autogroup:nonroot"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -52,17 +55,19 @@ func TestACLPolicy_BuildSSHPolicy_WithGroup(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:sre": {
|
||||
"john@example.com",
|
||||
ionscale.ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:sre": {
|
||||
"john@example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
SSHRules: []SSHRule{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"group:sre"},
|
||||
Dst: []string{"tag:web"},
|
||||
Users: []string{"autogroup:nonroot", "root"},
|
||||
SSH: []ionscale.ACLSSH{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"group:sre"},
|
||||
Destination: []string{"tag:web"},
|
||||
Users: []string{"autogroup:nonroot", "root"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -96,12 +101,14 @@ func TestACLPolicy_BuildSSHPolicy_WithMatchingUsers(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
SSHRules: []SSHRule{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"john@example.com"},
|
||||
Dst: []string{"john@example.com"},
|
||||
Users: []string{"autogroup:nonroot", "root"},
|
||||
ionscale.ACLPolicy{
|
||||
SSH: []ionscale.ACLSSH{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"john@example.com"},
|
||||
Destination: []string{"john@example.com"},
|
||||
Users: []string{"autogroup:nonroot", "root"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -132,15 +139,17 @@ func TestACLPolicy_BuildSSHPolicy_WithMatchingUsersInGroup(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:sre": {"jane@example.com", "john@example.com"},
|
||||
},
|
||||
SSHRules: []SSHRule{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"group:sre"},
|
||||
Dst: []string{"john@example.com"},
|
||||
Users: []string{"autogroup:nonroot", "root"},
|
||||
ionscale.ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:sre": {"jane@example.com", "john@example.com"},
|
||||
},
|
||||
SSH: []ionscale.ACLSSH{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"group:sre"},
|
||||
Destination: []string{"john@example.com"},
|
||||
Users: []string{"autogroup:nonroot", "root"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -171,12 +180,14 @@ func TestACLPolicy_BuildSSHPolicy_WithNoMatchingUsers(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
SSHRules: []SSHRule{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"jane@example.com"},
|
||||
Dst: []string{"john@example.com"},
|
||||
Users: []string{"autogroup:nonroot", "root"},
|
||||
ionscale.ACLPolicy{
|
||||
SSH: []ionscale.ACLSSH{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"jane@example.com"},
|
||||
Destination: []string{"john@example.com"},
|
||||
Users: []string{"autogroup:nonroot", "root"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -194,12 +205,14 @@ func TestACLPolicy_BuildSSHPolicy_WithTags(t *testing.T) {
|
||||
p3 := createMachine("nick@example.com", "tag:web")
|
||||
|
||||
policy := ACLPolicy{
|
||||
SSHRules: []SSHRule{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"john@example.com", "tag:web"},
|
||||
Dst: []string{"tag:web"},
|
||||
Users: []string{"ubuntu"},
|
||||
ionscale.ACLPolicy{
|
||||
SSH: []ionscale.ACLSSH{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"john@example.com", "tag:web"},
|
||||
Destination: []string{"tag:web"},
|
||||
Users: []string{"ubuntu"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -230,12 +243,14 @@ func TestACLPolicy_BuildSSHPolicy_WithTagsInDstAndAutogroupMemberInSrc(t *testin
|
||||
p3 := createMachine("nick@example.com", "tag:web")
|
||||
|
||||
policy := ACLPolicy{
|
||||
SSHRules: []SSHRule{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"autogroup:members"},
|
||||
Dst: []string{"tag:web"},
|
||||
Users: []string{"ubuntu"},
|
||||
ionscale.ACLPolicy{
|
||||
SSH: []ionscale.ACLSSH{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"autogroup:members"},
|
||||
Destination: []string{"tag:web"},
|
||||
Users: []string{"ubuntu"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -265,12 +280,14 @@ func TestACLPolicy_BuildSSHPolicy_WithUserInDstAndNonMatchingSrc(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
SSHRules: []SSHRule{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"jane@example.com"},
|
||||
Dst: []string{"john@example.com"},
|
||||
Users: []string{"autogroup:nonroot"},
|
||||
ionscale.ACLPolicy{
|
||||
SSH: []ionscale.ACLSSH{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"jane@example.com"},
|
||||
Destination: []string{"john@example.com"},
|
||||
Users: []string{"autogroup:nonroot"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -287,12 +304,14 @@ func TestACLPolicy_BuildSSHPolicy_WithUserInDstAndAutogroupMembersSrc(t *testing
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
SSHRules: []SSHRule{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"autogroup:members"},
|
||||
Dst: []string{"john@example.com"},
|
||||
Users: []string{"autogroup:nonroot"},
|
||||
ionscale.ACLPolicy{
|
||||
SSH: []ionscale.ACLSSH{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"autogroup:members"},
|
||||
Destination: []string{"john@example.com"},
|
||||
Users: []string{"autogroup:nonroot"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -323,12 +342,14 @@ func TestACLPolicy_BuildSSHPolicy_WithAutogroupSelfAndTagSrc(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com", "tag:web")
|
||||
|
||||
policy := ACLPolicy{
|
||||
SSHRules: []SSHRule{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"tag:web"},
|
||||
Dst: []string{"autogroup:self"},
|
||||
Users: []string{"autogroup:nonroot"},
|
||||
ionscale.ACLPolicy{
|
||||
SSH: []ionscale.ACLSSH{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"tag:web"},
|
||||
Destination: []string{"autogroup:self"},
|
||||
Users: []string{"autogroup:nonroot"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -345,12 +366,14 @@ func TestACLPolicy_BuildSSHPolicy_WithTagsAndActionCheck(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com", "tag:web")
|
||||
|
||||
policy := ACLPolicy{
|
||||
SSHRules: []SSHRule{
|
||||
{
|
||||
Action: "check",
|
||||
Src: []string{"tag:web"},
|
||||
Dst: []string{"tag:web"},
|
||||
Users: []string{"autogroup:nonroot"},
|
||||
ionscale.ACLPolicy{
|
||||
SSH: []ionscale.ACLSSH{
|
||||
{
|
||||
Action: "check",
|
||||
Source: []string{"tag:web"},
|
||||
Destination: []string{"tag:web"},
|
||||
Users: []string{"autogroup:nonroot"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+206
-160
@@ -3,6 +3,7 @@ package domain
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/jsiebens/ionscale/internal/addr"
|
||||
"github.com/jsiebens/ionscale/pkg/client/ionscale"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/netip"
|
||||
@@ -15,18 +16,20 @@ func TestACLPolicy_NodeAttributesWithWildcards(t *testing.T) {
|
||||
p1 := createMachine("john@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
NodeAttrs: []NodeAttr{
|
||||
{
|
||||
Target: []string{"*"},
|
||||
Attr: []string{
|
||||
"attr1",
|
||||
"attr2",
|
||||
ionscale.ACLPolicy{
|
||||
NodeAttrs: []ionscale.ACLNodeAttrGrant{
|
||||
{
|
||||
Target: []string{"*"},
|
||||
Attr: []string{
|
||||
"attr1",
|
||||
"attr2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Target: []string{"*"},
|
||||
Attr: []string{
|
||||
"attr3",
|
||||
{
|
||||
Target: []string{"*"},
|
||||
Attr: []string{
|
||||
"attr3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -46,21 +49,23 @@ func TestACLPolicy_NodeAttributesWithUserAndGroups(t *testing.T) {
|
||||
p1 := createMachine("john@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admins": []string{"john@example.com"},
|
||||
},
|
||||
NodeAttrs: []NodeAttr{
|
||||
{
|
||||
Target: []string{"john@example.com"},
|
||||
Attr: []string{
|
||||
"attr1",
|
||||
"attr2",
|
||||
},
|
||||
ionscale.ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admins": []string{"john@example.com"},
|
||||
},
|
||||
{
|
||||
Target: []string{"jane@example.com", "group:analytics", "group:admins"},
|
||||
Attr: []string{
|
||||
"attr3",
|
||||
NodeAttrs: []ionscale.ACLNodeAttrGrant{
|
||||
{
|
||||
Target: []string{"john@example.com"},
|
||||
Attr: []string{
|
||||
"attr1",
|
||||
"attr2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Target: []string{"jane@example.com", "group:analytics", "group:admins"},
|
||||
Attr: []string{
|
||||
"attr3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -80,21 +85,23 @@ func TestACLPolicy_NodeAttributesWithUserAndTags(t *testing.T) {
|
||||
p1 := createMachine("john@example.com", "tag:web")
|
||||
|
||||
policy := ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admins": []string{"john@example.com"},
|
||||
},
|
||||
NodeAttrs: []NodeAttr{
|
||||
{
|
||||
Target: []string{"john@example.com"},
|
||||
Attr: []string{
|
||||
"attr1",
|
||||
"attr2",
|
||||
},
|
||||
ionscale.ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admins": []string{"john@example.com"},
|
||||
},
|
||||
{
|
||||
Target: []string{"jane@example.com", "tag:web"},
|
||||
Attr: []string{
|
||||
"attr3",
|
||||
NodeAttrs: []ionscale.ACLNodeAttrGrant{
|
||||
{
|
||||
Target: []string{"john@example.com"},
|
||||
Attr: []string{
|
||||
"attr1",
|
||||
"attr2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Target: []string{"jane@example.com", "tag:web"},
|
||||
Attr: []string{
|
||||
"attr3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -111,7 +118,9 @@ func TestACLPolicy_BuildFilterRulesEmptyACL(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{},
|
||||
},
|
||||
}
|
||||
|
||||
dst := createMachine("john@example.com")
|
||||
@@ -127,11 +136,13 @@ func TestACLPolicy_BuildFilterRulesWildcards(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"*:*"},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"*:*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -162,17 +173,19 @@ func TestACLPolicy_BuildFilterRulesProto(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"*:22"},
|
||||
},
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"*:*"},
|
||||
Proto: "igmp",
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"*:22"},
|
||||
},
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"*:*"},
|
||||
Protocol: "igmp",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -217,20 +230,22 @@ func TestACLPolicy_BuildFilterRulesWithGroups(t *testing.T) {
|
||||
p3 := createMachine("joe@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admin": []string{"jane@example.com"},
|
||||
"group:audit": []string{"nick@example.com"},
|
||||
},
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"group:admin"},
|
||||
Dst: []string{"*:22"},
|
||||
ionscale.ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admin": []string{"jane@example.com"},
|
||||
"group:audit": []string{"nick@example.com"},
|
||||
},
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"group:audit"},
|
||||
Dst: []string{"*:8000-8080"},
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"group:admin"},
|
||||
Destination: []string{"*:22"},
|
||||
},
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"group:audit"},
|
||||
Destination: []string{"*:8000-8080"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -280,11 +295,13 @@ func TestACLPolicy_BuildFilterRulesWithAutoGroupMembers(t *testing.T) {
|
||||
p3 := createMachine("joe@example.com", "tag:web")
|
||||
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"autogroup:members"},
|
||||
Dst: []string{"*:22"},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"autogroup:members"},
|
||||
Destination: []string{"*:22"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -323,11 +340,13 @@ func TestACLPolicy_BuildFilterRulesWithAutoGroupMember(t *testing.T) {
|
||||
p3 := createMachine("joe@example.com", "tag:web")
|
||||
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"autogroup:member"},
|
||||
Dst: []string{"*:22"},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"autogroup:member"},
|
||||
Destination: []string{"*:22"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -367,11 +386,13 @@ func TestACLPolicy_BuildFilterRulesWithAutoGroupTagged(t *testing.T) {
|
||||
p3 := createMachine("joe@example.com", "tag:web")
|
||||
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"autogroup:tagged"},
|
||||
Dst: []string{"*:22"},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"autogroup:tagged"},
|
||||
Destination: []string{"*:22"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -408,11 +429,13 @@ func TestACLPolicy_BuildFilterRulesAutogroupSelf(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"autogroup:self:*"},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"autogroup:self:*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -453,11 +476,13 @@ func TestACLPolicy_BuildFilterRulesAutogroupSelfAndTags(t *testing.T) {
|
||||
p2 := createMachine("john@example.com", "tag:web")
|
||||
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"autogroup:self:*"},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"autogroup:self:*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -499,11 +524,13 @@ func TestACLPolicy_BuildFilterRulesAutogroupSelfAndOtherDestinations(t *testing.
|
||||
p3 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"autogroup:self:22", "john@example.com:80"},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"autogroup:self:22", "john@example.com:80"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -560,11 +587,13 @@ func TestACLPolicy_BuildFilterRulesAutogroupInternet(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"nick@example.com"},
|
||||
Dst: []string{"autogroup:internet:*"},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"nick@example.com"},
|
||||
Destination: []string{"autogroup:internet:*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -601,11 +630,13 @@ func TestACLPolicy_BuildFilterRulesAutogroupInternet(t *testing.T) {
|
||||
|
||||
func TestWithUser(t *testing.T) {
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"john@example.com:*"},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"john@example.com:*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -618,14 +649,16 @@ func TestWithUser(t *testing.T) {
|
||||
|
||||
func TestWithGroup(t *testing.T) {
|
||||
policy := ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admin": {"john@example.com"},
|
||||
},
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"group:admin:*"},
|
||||
ionscale.ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admin": {"john@example.com"},
|
||||
},
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"group:admin:*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -637,11 +670,13 @@ func TestWithGroup(t *testing.T) {
|
||||
|
||||
func TestWithTags(t *testing.T) {
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"tag:web:*"},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"tag:web:*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -657,15 +692,17 @@ func TestWithHosts(t *testing.T) {
|
||||
dst2 := createMachine("john@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
Hosts: map[string]string{
|
||||
"dst1": dst1.IPv4.String(),
|
||||
},
|
||||
ACLs: []ACL{
|
||||
ionscale.ACLPolicy{
|
||||
Hosts: map[string]string{
|
||||
"dst1": dst1.IPv4.String(),
|
||||
},
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"dst1:*"},
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"dst1:*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -695,12 +732,13 @@ func createMachine(user string, tags ...string) *Machine {
|
||||
|
||||
func TestACLPolicy_IsTagOwner(t *testing.T) {
|
||||
policy := ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:engineers": {"jane@example.com"},
|
||||
},
|
||||
TagOwners: map[string][]string{
|
||||
"tag:web": {"john@example.com", "group:engineers"},
|
||||
}}
|
||||
ionscale.ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:engineers": {"jane@example.com"},
|
||||
},
|
||||
TagOwners: map[string][]string{
|
||||
"tag:web": {"john@example.com", "group:engineers"},
|
||||
}}}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -780,15 +818,17 @@ func TestACLPolicy_FindAutoApprovedIPs(t *testing.T) {
|
||||
route3 := netip.MustParsePrefix("10.162.0.0/20")
|
||||
|
||||
policy := ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admins": {"jane@example.com"},
|
||||
},
|
||||
AutoApprovers: &AutoApprovers{
|
||||
Routes: map[string][]string{
|
||||
route1.String(): {"group:admins"},
|
||||
route2.String(): {"john@example.com", "tag:router"},
|
||||
ionscale.ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admins": {"jane@example.com"},
|
||||
},
|
||||
AutoApprovers: &ionscale.ACLAutoApprovers{
|
||||
Routes: map[string][]string{
|
||||
route1.String(): {"group:admins"},
|
||||
route2.String(): {"john@example.com", "tag:router"},
|
||||
},
|
||||
ExitNode: []string{"nick@example.com"},
|
||||
},
|
||||
ExitNode: []string{"nick@example.com"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -872,11 +912,13 @@ func TestACLPolicy_BuildFilterRulesWithAdvertisedRoutes(t *testing.T) {
|
||||
p1 := createMachine("john@example.com", "tag:trusted")
|
||||
|
||||
policy := ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"tag:trusted"},
|
||||
Dst: []string{"fd7a:115c:a1e0:b1a:0:1:a3c:0/120:*"},
|
||||
ionscale.ACLPolicy{
|
||||
ACLs: []ionscale.ACLEntry{
|
||||
{
|
||||
Action: "accept",
|
||||
Source: []string{"tag:trusted"},
|
||||
Destination: []string{"fd7a:115c:a1e0:b1a:0:1:a3c:0/120:*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -911,11 +953,13 @@ func TestACLPolicy_BuildFilterRulesWildcardGrants(t *testing.T) {
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
Grants: []Grant{
|
||||
{
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"*"},
|
||||
IP: ranges,
|
||||
ionscale.ACLPolicy{
|
||||
Grants: []ionscale.ACLGrant{
|
||||
{
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"*"},
|
||||
IP: ranges,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -955,12 +999,14 @@ func TestACLPolicy_BuildFilterRulesWithAppGrants(t *testing.T) {
|
||||
marshal, _ := json.Marshal(mycap)
|
||||
|
||||
policy := ACLPolicy{
|
||||
Grants: []Grant{
|
||||
{
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"*"},
|
||||
App: map[tailcfg.PeerCapability][]tailcfg.RawMessage{
|
||||
tailcfg.PeerCapability("localtest.me/cap/test"): {tailcfg.RawMessage(marshal)},
|
||||
ionscale.ACLPolicy{
|
||||
Grants: []ionscale.ACLGrant{
|
||||
{
|
||||
Source: []string{"*"},
|
||||
Destination: []string{"*"},
|
||||
App: map[tailcfg.PeerCapability][]tailcfg.RawMessage{
|
||||
tailcfg.PeerCapability("localtest.me/cap/test"): {tailcfg.RawMessage(marshal)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/tailscale/hujson"
|
||||
)
|
||||
|
||||
func NewHuJSON[T any](t *T) HuJSON[T] {
|
||||
marshal, _ := json.Marshal(t)
|
||||
return HuJSON[T]{
|
||||
v: string(marshal),
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func ParseHuJson[T any](v string) (*HuJSON[T], error) {
|
||||
ast, err := hujson.Parse([]byte(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ast.Format()
|
||||
formatted := string(ast.Pack())
|
||||
ast.Standardize()
|
||||
|
||||
t := new(T)
|
||||
if err := json.Unmarshal(ast.Pack(), t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &HuJSON[T]{v: formatted, t: t}, nil
|
||||
}
|
||||
|
||||
type HuJSON[T any] struct {
|
||||
v string
|
||||
t *T
|
||||
}
|
||||
|
||||
func (h *HuJSON[T]) Get() *T {
|
||||
return h.t
|
||||
}
|
||||
|
||||
func (h *HuJSON[T]) String() string {
|
||||
return h.v
|
||||
}
|
||||
|
||||
func (i *HuJSON[T]) Equal(x *HuJSON[T]) bool {
|
||||
if i == nil && x == nil {
|
||||
return true
|
||||
}
|
||||
if (i == nil) != (x == nil) {
|
||||
return false
|
||||
}
|
||||
return i.v == x.v
|
||||
}
|
||||
|
||||
func (h HuJSON[T]) Value() (driver.Value, error) {
|
||||
if len(h.v) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return h.v, nil
|
||||
}
|
||||
|
||||
func (h *HuJSON[T]) Scan(destination interface{}) error {
|
||||
var v string
|
||||
switch value := destination.(type) {
|
||||
case string:
|
||||
v = value
|
||||
case []byte:
|
||||
v = string(value)
|
||||
default:
|
||||
return fmt.Errorf("unexpected data type %T", destination)
|
||||
}
|
||||
|
||||
next, err := hujson.Standardize([]byte(v))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var n = new(T)
|
||||
if err := json.Unmarshal(next, n); err != nil {
|
||||
return err
|
||||
}
|
||||
h.v = v
|
||||
h.t = n
|
||||
return nil
|
||||
}
|
||||
@@ -13,8 +13,8 @@ type Tailnet struct {
|
||||
ID uint64 `gorm:"primary_key"`
|
||||
Name string
|
||||
DNSConfig DNSConfig
|
||||
IAMPolicy IAMPolicy
|
||||
ACLPolicy ACLPolicy
|
||||
IAMPolicy HuJSON[IAMPolicy]
|
||||
ACLPolicy HuJSON[ACLPolicy]
|
||||
DERPMap DERPMap
|
||||
ServiceCollectionEnabled bool
|
||||
FileSharingEnabled bool
|
||||
|
||||
@@ -447,7 +447,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, form
|
||||
ephemeral = false
|
||||
}
|
||||
|
||||
if err := tailnet.ACLPolicy.CheckTagOwners(registrationRequest.Data.Hostinfo.RequestTags, user); err != nil {
|
||||
if err := tailnet.ACLPolicy.Get().CheckTagOwners(registrationRequest.Data.Hostinfo.RequestTags, user); err != nil {
|
||||
registrationRequest.Authenticated = false
|
||||
registrationRequest.Error = err.Error()
|
||||
if err := h.repository.SaveRegistrationRequest(ctx, registrationRequest); err != nil {
|
||||
@@ -456,7 +456,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, form
|
||||
return c.Redirect(http.StatusFound, "/a/error?e=nto")
|
||||
}
|
||||
|
||||
autoAllowIPs := tailnet.ACLPolicy.FindAutoApprovedIPs(req.Hostinfo.RoutableIPs, tags, user)
|
||||
autoAllowIPs := tailnet.ACLPolicy.Get().FindAutoApprovedIPs(req.Hostinfo.RoutableIPs, tags, user)
|
||||
|
||||
var m *domain.Machine
|
||||
|
||||
@@ -573,7 +573,7 @@ func (h *AuthenticationHandlers) listAvailableTailnets(ctx context.Context, u *a
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range tailnets {
|
||||
approved, err := t.IAMPolicy.EvaluatePolicy(&domain.Identity{UserID: u.ID, Email: u.Name, Attr: u.Attr})
|
||||
approved, err := t.IAMPolicy.Get().EvaluatePolicy(&domain.Identity{UserID: u.ID, Email: u.Name, Attr: u.Attr})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, ma
|
||||
tailnet := authKey.Tailnet
|
||||
user := authKey.User
|
||||
|
||||
if err := tailnet.ACLPolicy.CheckTagOwners(req.Hostinfo.RequestTags, &user); err != nil {
|
||||
if err := tailnet.ACLPolicy.Get().CheckTagOwners(req.Hostinfo.RequestTags, &user); err != nil {
|
||||
response := tailcfg.RegisterResponse{MachineAuthorized: false, Error: err.Error()}
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
@@ -169,7 +169,7 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, ma
|
||||
advertisedTags := domain.SanitizeTags(req.Hostinfo.RequestTags)
|
||||
tags := append(registeredTags, advertisedTags...)
|
||||
|
||||
autoAllowIPs := tailnet.ACLPolicy.FindAutoApprovedIPs(req.Hostinfo.RoutableIPs, tags, &user)
|
||||
autoAllowIPs := tailnet.ACLPolicy.Get().FindAutoApprovedIPs(req.Hostinfo.RoutableIPs, tags, &user)
|
||||
|
||||
var m *domain.Machine
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ func ToDNSConfig(m *domain.Machine, tailnet *domain.Tailnet, c *domain.DNSConfig
|
||||
}
|
||||
|
||||
func ToNode(capVer tailcfg.CapabilityVersion, m *domain.Machine, tailnet *domain.Tailnet, taggedDevicesUser *domain.User, peer bool, connected bool, routeFilter func(m *domain.Machine) []netip.Prefix) (*tailcfg.Node, *tailcfg.UserProfile, error) {
|
||||
role := tailnet.IAMPolicy.GetRole(m.User)
|
||||
role := tailnet.IAMPolicy.Get().GetRole(m.User)
|
||||
|
||||
nKey, err := util.ParseNodePublicKey(m.NodeKey)
|
||||
if err != nil {
|
||||
@@ -179,7 +179,7 @@ func ToNode(capVer tailcfg.CapabilityVersion, m *domain.Machine, tailnet *domain
|
||||
var capabilities []tailcfg.NodeCapability
|
||||
capMap := make(tailcfg.NodeCapMap)
|
||||
|
||||
for _, c := range tailnet.ACLPolicy.NodeCapabilities(m) {
|
||||
for _, c := range tailnet.ACLPolicy.Get().NodeCapabilities(m) {
|
||||
capabilities = append(capabilities, c)
|
||||
capMap[c] = []tailcfg.RawMessage{}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func (h *PollNetMapper) CreateMapResponse(ctx context.Context, delta bool) (*Map
|
||||
|
||||
hostinfo := tailcfg.Hostinfo(m.HostInfo)
|
||||
tailnet := m.Tailnet
|
||||
policies := tailnet.ACLPolicy
|
||||
policies := tailnet.ACLPolicy.Get()
|
||||
dnsConfig := tailnet.DNSConfig
|
||||
|
||||
serviceUser, _, err := h.repository.GetOrCreateServiceUser(ctx, &tailnet)
|
||||
|
||||
+8
-13
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/mapping"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
)
|
||||
|
||||
@@ -23,12 +22,7 @@ func (s *Service) GetACLPolicy(ctx context.Context, req *connect.Request[api.Get
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet does not exist"))
|
||||
}
|
||||
|
||||
var policy api.ACLPolicy
|
||||
if err := mapping.CopyViaJson(&tailnet.ACLPolicy, &policy); err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&api.GetACLPolicyResponse{Policy: &policy}), nil
|
||||
return connect.NewResponse(&api.GetACLPolicyResponse{Policy: tailnet.ACLPolicy.String()}), nil
|
||||
}
|
||||
|
||||
func (s *Service) SetACLPolicy(ctx context.Context, req *connect.Request[api.SetACLPolicyRequest]) (*connect.Response[api.SetACLPolicyResponse], error) {
|
||||
@@ -45,17 +39,18 @@ func (s *Service) SetACLPolicy(ctx context.Context, req *connect.Request[api.Set
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet does not exist"))
|
||||
}
|
||||
|
||||
oldPolicy := tailnet.ACLPolicy
|
||||
var newPolicy domain.ACLPolicy
|
||||
if err := mapping.CopyViaJson(req.Msg.Policy, &newPolicy); err != nil {
|
||||
return nil, logError(err)
|
||||
newPolicy, err := domain.ParseHuJson[domain.ACLPolicy](req.Msg.Policy)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid acl policy: %w", err))
|
||||
}
|
||||
|
||||
if oldPolicy.Equal(&newPolicy) {
|
||||
oldPolicy := tailnet.ACLPolicy
|
||||
if oldPolicy.Equal(newPolicy) {
|
||||
return connect.NewResponse(&api.SetACLPolicyResponse{}), nil
|
||||
}
|
||||
|
||||
tailnet.ACLPolicy = newPolicy
|
||||
tailnet.ACLPolicy = *newPolicy
|
||||
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ func (s *Service) CreateAuthKey(ctx context.Context, req *connect.Request[api.Cr
|
||||
}
|
||||
|
||||
if !principal.IsSystemAdmin() {
|
||||
if err := tailnet.ACLPolicy.CheckTagOwners(req.Msg.Tags, principal.User); err != nil {
|
||||
if err := tailnet.ACLPolicy.Get().CheckTagOwners(req.Msg.Tags, principal.User); err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, err)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-34
@@ -22,14 +22,7 @@ func (s *Service) GetIAMPolicy(ctx context.Context, req *connect.Request[api.Get
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet does not exist"))
|
||||
}
|
||||
|
||||
policy := &api.IAMPolicy{
|
||||
Subs: tailnet.IAMPolicy.Subs,
|
||||
Emails: tailnet.IAMPolicy.Emails,
|
||||
Filters: tailnet.IAMPolicy.Filters,
|
||||
Roles: domainRolesMapToApiRolesMap(tailnet.IAMPolicy.Roles),
|
||||
}
|
||||
|
||||
return connect.NewResponse(&api.GetIAMPolicyResponse{Policy: policy}), nil
|
||||
return connect.NewResponse(&api.GetIAMPolicyResponse{Policy: tailnet.IAMPolicy.String()}), nil
|
||||
}
|
||||
|
||||
func (s *Service) SetIAMPolicy(ctx context.Context, req *connect.Request[api.SetIAMPolicyRequest]) (*connect.Response[api.SetIAMPolicyResponse], error) {
|
||||
@@ -46,23 +39,21 @@ func (s *Service) SetIAMPolicy(ctx context.Context, req *connect.Request[api.Set
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet does not exist"))
|
||||
}
|
||||
|
||||
if err := validateIamPolicy(req.Msg.Policy); err != nil {
|
||||
newPolicy, err := domain.ParseHuJson[domain.IAMPolicy](req.Msg.Policy)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid iam policy: %w", err))
|
||||
}
|
||||
|
||||
if err := validateIamPolicy(newPolicy.Get()); err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid iam policy: %w", err))
|
||||
}
|
||||
|
||||
oldPolicy := tailnet.IAMPolicy
|
||||
newPolicy := domain.IAMPolicy{
|
||||
Subs: req.Msg.Policy.Subs,
|
||||
Emails: req.Msg.Policy.Emails,
|
||||
Filters: req.Msg.Policy.Filters,
|
||||
Roles: apiRolesMapToDomainRolesMap(req.Msg.Policy.Roles),
|
||||
}
|
||||
|
||||
if oldPolicy.Equal(&newPolicy) {
|
||||
if oldPolicy.Equal(newPolicy) {
|
||||
return connect.NewResponse(&api.SetIAMPolicyResponse{}), nil
|
||||
}
|
||||
|
||||
tailnet.IAMPolicy = newPolicy
|
||||
tailnet.IAMPolicy = *newPolicy
|
||||
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, logError(err)
|
||||
@@ -70,19 +61,3 @@ func (s *Service) SetIAMPolicy(ctx context.Context, req *connect.Request[api.Set
|
||||
|
||||
return connect.NewResponse(&api.SetIAMPolicyResponse{}), nil
|
||||
}
|
||||
|
||||
func apiRolesMapToDomainRolesMap(values map[string]string) map[string]domain.UserRole {
|
||||
var result = map[string]domain.UserRole{}
|
||||
for k, v := range values {
|
||||
result[k] = domain.UserRole(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func domainRolesMapToApiRolesMap(values map[string]domain.UserRole) map[string]string {
|
||||
var result = map[string]string{}
|
||||
for k, v := range values {
|
||||
result[k] = string(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func exchangeToken(ctx context.Context, systemAdminKey *key.ServerPrivate, repos
|
||||
if err == nil && apiKey != nil {
|
||||
user := apiKey.User
|
||||
tailnet := apiKey.Tailnet
|
||||
role := tailnet.IAMPolicy.GetRole(user)
|
||||
role := tailnet.IAMPolicy.Get().GetRole(user)
|
||||
|
||||
return &domain.Principal{User: &apiKey.User, SystemRole: domain.SystemRoleNone, UserRole: role}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (s *Service) GetVersion(_ context.Context, _ *connect.Request[api.GetVersio
|
||||
}), nil
|
||||
}
|
||||
|
||||
func validateIamPolicy(p *api.IAMPolicy) error {
|
||||
func validateIamPolicy(p *domain.IAMPolicy) error {
|
||||
var mErr *multierror.Error
|
||||
for i, exp := range p.Filters {
|
||||
if _, err := grammar.Parse(fmt.Sprintf("filter %d", i), []byte(exp)); err != nil {
|
||||
|
||||
+33
-39
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/mapping"
|
||||
"github.com/jsiebens/ionscale/internal/util"
|
||||
"github.com/jsiebens/ionscale/pkg/defaults"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
@@ -17,8 +16,8 @@ func domainTailnetToApiTailnet(tailnet *domain.Tailnet) (*api.Tailnet, error) {
|
||||
t := &api.Tailnet{
|
||||
Id: tailnet.ID,
|
||||
Name: tailnet.Name,
|
||||
IamPolicy: new(api.IAMPolicy),
|
||||
AclPolicy: new(api.ACLPolicy),
|
||||
IamPolicy: tailnet.IAMPolicy.String(),
|
||||
AclPolicy: tailnet.ACLPolicy.String(),
|
||||
DnsConfig: domainDNSConfigToApiDNSConfig(tailnet),
|
||||
ServiceCollectionEnabled: tailnet.ServiceCollectionEnabled,
|
||||
FileSharingEnabled: tailnet.FileSharingEnabled,
|
||||
@@ -26,14 +25,6 @@ func domainTailnetToApiTailnet(tailnet *domain.Tailnet) (*api.Tailnet, error) {
|
||||
MachineAuthorizationEnabled: tailnet.MachineAuthorizationEnabled,
|
||||
}
|
||||
|
||||
if err := mapping.CopyViaJson(tailnet.IAMPolicy, t.IamPolicy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mapping.CopyViaJson(tailnet.ACLPolicy, t.AclPolicy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
@@ -51,12 +42,26 @@ func (s *Service) CreateTailnet(ctx context.Context, req *connect.Request[api.Cr
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("tailnet with name '%s' already exists", req.Msg.Name))
|
||||
}
|
||||
|
||||
if req.Msg.IamPolicy == nil {
|
||||
req.Msg.IamPolicy = defaults.DefaultIAMPolicy()
|
||||
iamPolicy := domain.NewHuJSON(&domain.IAMPolicy{})
|
||||
aclPolicy := domain.NewHuJSON(&domain.ACLPolicy{ACLPolicy: *defaults.DefaultACLPolicy()})
|
||||
|
||||
if req.Msg.IamPolicy != "" {
|
||||
newPolicy, err := domain.ParseHuJson[domain.IAMPolicy](req.Msg.IamPolicy)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid iam policy: %w", err))
|
||||
}
|
||||
if err := validateIamPolicy(newPolicy.Get()); err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid iam policy: %w", err))
|
||||
}
|
||||
iamPolicy = *newPolicy
|
||||
}
|
||||
|
||||
if req.Msg.AclPolicy == nil {
|
||||
req.Msg.AclPolicy = defaults.DefaultACLPolicy()
|
||||
if req.Msg.AclPolicy != "" {
|
||||
newPolicy, err := domain.ParseHuJson[domain.ACLPolicy](req.Msg.AclPolicy)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid acl policy: %w", err))
|
||||
}
|
||||
aclPolicy = *newPolicy
|
||||
}
|
||||
|
||||
if req.Msg.DnsConfig == nil {
|
||||
@@ -66,8 +71,8 @@ func (s *Service) CreateTailnet(ctx context.Context, req *connect.Request[api.Cr
|
||||
tailnet := &domain.Tailnet{
|
||||
ID: util.NextID(),
|
||||
Name: req.Msg.Name,
|
||||
IAMPolicy: domain.IAMPolicy{},
|
||||
ACLPolicy: domain.ACLPolicy{},
|
||||
IAMPolicy: iamPolicy,
|
||||
ACLPolicy: aclPolicy,
|
||||
DNSConfig: apiDNSConfigToDomainDNSConfig(req.Msg.DnsConfig),
|
||||
ServiceCollectionEnabled: req.Msg.ServiceCollectionEnabled,
|
||||
FileSharingEnabled: req.Msg.FileSharingEnabled,
|
||||
@@ -75,18 +80,6 @@ func (s *Service) CreateTailnet(ctx context.Context, req *connect.Request[api.Cr
|
||||
MachineAuthorizationEnabled: req.Msg.MachineAuthorizationEnabled,
|
||||
}
|
||||
|
||||
if err := validateIamPolicy(req.Msg.IamPolicy); err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid iam policy: %w", err))
|
||||
}
|
||||
|
||||
if err := mapping.CopyViaJson(req.Msg.IamPolicy, &tailnet.IAMPolicy); err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if err := mapping.CopyViaJson(req.Msg.AclPolicy, &tailnet.ACLPolicy); err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
@@ -116,22 +109,23 @@ func (s *Service) UpdateTailnet(ctx context.Context, req *connect.Request[api.Up
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
}
|
||||
|
||||
if req.Msg.IamPolicy != nil {
|
||||
if err := validateIamPolicy(req.Msg.IamPolicy); err != nil {
|
||||
if req.Msg.IamPolicy != "" {
|
||||
newPolicy, err := domain.ParseHuJson[domain.IAMPolicy](req.Msg.IamPolicy)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid iam policy: %w", err))
|
||||
}
|
||||
|
||||
tailnet.IAMPolicy = domain.IAMPolicy{}
|
||||
if err := mapping.CopyViaJson(req.Msg.IamPolicy, &tailnet.IAMPolicy); err != nil {
|
||||
return nil, logError(err)
|
||||
if err := validateIamPolicy(newPolicy.Get()); err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid iam policy: %w", err))
|
||||
}
|
||||
tailnet.IAMPolicy = *newPolicy
|
||||
}
|
||||
|
||||
if req.Msg.AclPolicy != nil {
|
||||
tailnet.ACLPolicy = domain.ACLPolicy{}
|
||||
if err := mapping.CopyViaJson(req.Msg.AclPolicy, &tailnet.ACLPolicy); err != nil {
|
||||
return nil, logError(err)
|
||||
if req.Msg.AclPolicy != "" {
|
||||
newPolicy, err := domain.ParseHuJson[domain.ACLPolicy](req.Msg.AclPolicy)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid acl policy: %w", err))
|
||||
}
|
||||
tailnet.ACLPolicy = *newPolicy
|
||||
}
|
||||
|
||||
if req.Msg.DnsConfig != nil {
|
||||
|
||||
@@ -34,7 +34,7 @@ func (s *Service) ListUsers(ctx context.Context, req *connect.Request[api.ListUs
|
||||
resp.Users = append(resp.Users, &api.User{
|
||||
Id: u.ID,
|
||||
Name: u.Name,
|
||||
Role: string(tailnet.IAMPolicy.GetRole(u)),
|
||||
Role: string(tailnet.IAMPolicy.Get().GetRole(u)),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user