diff --git a/internal/domain/acl.go b/internal/domain/acl.go index b2a9489..0834f8e 100644 --- a/internal/domain/acl.go +++ b/internal/domain/acl.go @@ -8,6 +8,7 @@ import ( "gorm.io/gorm" "gorm.io/gorm/schema" "net/netip" + "reflect" "slices" "sort" "strconv" @@ -66,6 +67,16 @@ type Grant struct { App tailcfg.PeerCapMap `json:"app"` } +func (a *ACLPolicy) Equal(x *ACLPolicy) bool { + if a == nil && x == nil { + return true + } + if (a == nil) != (x == nil) { + return false + } + return reflect.DeepEqual(a, x) +} + func (a ACLPolicy) FindAutoApprovedIPs(routableIPs []netip.Prefix, tags []string, u *User) []netip.Prefix { if a.AutoApprovers == nil || len(routableIPs) == 0 { return nil diff --git a/internal/domain/dns_config.go b/internal/domain/dns_config.go index c64b407..f5948bc 100644 --- a/internal/domain/dns_config.go +++ b/internal/domain/dns_config.go @@ -6,6 +6,7 @@ import ( "fmt" "gorm.io/gorm" "gorm.io/gorm/schema" + "reflect" ) type DNSConfig struct { @@ -17,6 +18,22 @@ type DNSConfig struct { SearchDomains []string `json:"search_domains"` } +func (i *DNSConfig) Equal(x *DNSConfig) bool { + if i == nil && x == nil { + return true + } + if (i == nil) != (x == nil) { + return false + } + + return i.MagicDNS == x.MagicDNS && + i.HttpsCertsEnabled == x.HttpsCertsEnabled && + i.OverrideLocalDNS == x.OverrideLocalDNS && + reflect.DeepEqual(i.Nameservers, x.Nameservers) && + reflect.DeepEqual(i.Routes, x.Routes) && + reflect.DeepEqual(i.SearchDomains, x.SearchDomains) +} + func (i *DNSConfig) Scan(destination interface{}) error { switch value := destination.(type) { case []byte: diff --git a/internal/domain/iam.go b/internal/domain/iam.go index 93418a9..526d2ff 100644 --- a/internal/domain/iam.go +++ b/internal/domain/iam.go @@ -9,6 +9,7 @@ import ( "github.com/mitchellh/pointerstructure" "gorm.io/gorm" "gorm.io/gorm/schema" + "reflect" ) type Identity struct { @@ -68,6 +69,16 @@ func (i *IAMPolicy) EvaluatePolicy(identity *Identity) (bool, error) { return false, nil } +func (i *IAMPolicy) Equal(x *IAMPolicy) bool { + if i == nil && x == nil { + return true + } + if (i == nil) != (x == nil) { + return false + } + return reflect.DeepEqual(i, x) +} + func (i *IAMPolicy) Scan(destination interface{}) error { switch value := destination.(type) { case []byte: diff --git a/internal/service/acl.go b/internal/service/acl.go index 5c52732..f1e49c9 100644 --- a/internal/service/acl.go +++ b/internal/service/acl.go @@ -45,12 +45,17 @@ func (s *Service) SetACLPolicy(ctx context.Context, req *connect.Request[api.Set return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet does not exist")) } - var policy domain.ACLPolicy - if err := mapping.CopyViaJson(req.Msg.Policy, &policy); err != nil { + oldPolicy := tailnet.ACLPolicy + var newPolicy domain.ACLPolicy + if err := mapping.CopyViaJson(req.Msg.Policy, &newPolicy); err != nil { return nil, logError(err) } - tailnet.ACLPolicy = policy + if oldPolicy.Equal(&newPolicy) { + return connect.NewResponse(&api.SetACLPolicyResponse{}), nil + } + + tailnet.ACLPolicy = newPolicy if err := s.repository.SaveTailnet(ctx, tailnet); err != nil { return nil, logError(err) } diff --git a/internal/service/dns.go b/internal/service/dns.go index ebf8081..3350753 100644 --- a/internal/service/dns.go +++ b/internal/service/dns.go @@ -54,23 +54,21 @@ func (s *Service) SetDNSConfig(ctx context.Context, req *connect.Request[api.Set return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found")) } - tailnet.DNSConfig = apiDNSConfigToDomainDNSConfig(req.Msg.Config) + oldConfig := tailnet.DNSConfig + newConfig := apiDNSConfigToDomainDNSConfig(req.Msg.Config) + if oldConfig.Equal(&newConfig) { + return connect.NewResponse(&api.SetDNSConfigResponse{Config: domainDNSConfigToApiDNSConfig(tailnet)}), nil + } + + tailnet.DNSConfig = newConfig if err := s.repository.SaveTailnet(ctx, tailnet); err != nil { return nil, logError(err) } s.sessionManager.NotifyAll(tailnet.ID) - resp := &api.SetDNSConfigResponse{ - Config: domainDNSConfigToApiDNSConfig(tailnet), - } - - if dnsConfig.HttpsCerts && s.dnsProvider == nil { - resp.Message = "# HTTPS Certs cannot be enabled because a DNS provider is not properly configured" - } - - return connect.NewResponse(resp), nil + return connect.NewResponse(&api.SetDNSConfigResponse{Config: domainDNSConfigToApiDNSConfig(tailnet)}), nil } func domainRoutesToApiRoutes(routes map[string][]string) map[string]*api.Routes { diff --git a/internal/service/iam.go b/internal/service/iam.go index 8d8258a..8f74235 100644 --- a/internal/service/iam.go +++ b/internal/service/iam.go @@ -50,13 +50,20 @@ func (s *Service) SetIAMPolicy(ctx context.Context, req *connect.Request[api.Set return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid iam policy: %w", err)) } - tailnet.IAMPolicy = domain.IAMPolicy{ + 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) { + return connect.NewResponse(&api.SetIAMPolicyResponse{}), nil + } + + tailnet.IAMPolicy = newPolicy + if err := s.repository.SaveTailnet(ctx, tailnet); err != nil { return nil, logError(err) }