feat: configure dns preferecens for tailnets

This commit is contained in:
Johan Siebens
2022-05-12 22:32:59 +02:00
parent e5c7a118a8
commit 52aa221cd0
13 changed files with 1047 additions and 86 deletions
+6
View File
@@ -14,6 +14,7 @@ type Signal struct {
PeerUpdated *uint64
PeersRemoved []uint64
ACLUpdated bool
DNSUpdated bool
}
type Broker interface {
@@ -22,6 +23,7 @@ type Broker interface {
SignalPeerUpdated(id uint64)
SignalPeersRemoved([]uint64)
SignalDNSUpdated()
SignalACLUpdated()
IsConnected(uint64) bool
@@ -88,6 +90,10 @@ func (h *broker) SignalPeersRemoved(ids []uint64) {
h.signalChannel <- &Signal{PeersRemoved: ids}
}
func (h *broker) SignalDNSUpdated() {
h.signalChannel <- &Signal{DNSUpdated: true}
}
func (h *broker) SignalACLUpdated() {
h.signalChannel <- &Signal{ACLUpdated: true}
}
+147
View File
@@ -0,0 +1,147 @@
package cmd
import (
"context"
"fmt"
"github.com/jsiebens/ionscale/pkg/gen/api"
"github.com/muesli/coral"
"strings"
)
func getDNSConfig() *coral.Command {
command := &coral.Command{
Use: "get-dns",
Short: "Get DNS configuration",
SilenceUsage: true,
}
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "")
command.RunE = func(command *coral.Command, args []string) error {
client, c, err := target.createGRPCClient()
if err != nil {
return err
}
defer safeClose(c)
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
req := api.GetDNSConfigRequest{TailnetId: tailnet.Id}
resp, err := client.GetDNSConfig(context.Background(), &req)
if err != nil {
return err
}
config := resp.Config
var allNameservers = config.Nameservers
for i, j := range config.Routes {
for _, n := range j.Routes {
allNameservers = append(allNameservers, fmt.Sprintf("%s:%s", i, n))
}
}
fmt.Printf("%-*v%v\n", 25, "Magic DNS Enabled:", config.MagicDns)
fmt.Printf("%-*v%v\n", 25, "Override Local DNS:", config.OverrideLocalDns)
fmt.Printf("%-*v%v\n", 25, "Nameservers:", strings.Join(allNameservers, ","))
return nil
}
return command
}
func setDNSConfig() *coral.Command {
command := &coral.Command{
Use: "set-dns",
Short: "Set DNS config",
SilenceUsage: true,
}
var nameservers []string
var magicDNS bool
var overrideLocalDNS bool
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "")
command.Flags().StringSliceVarP(&nameservers, "nameserver", "", []string{}, "")
command.Flags().BoolVarP(&magicDNS, "magic-dns", "", false, "")
command.Flags().BoolVarP(&overrideLocalDNS, "override-local-dns", "", false, "")
command.RunE = func(command *coral.Command, args []string) error {
client, c, err := target.createGRPCClient()
if err != nil {
return err
}
defer safeClose(c)
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
var globalNameservers []string
var routes = make(map[string]*api.Routes)
for _, n := range nameservers {
split := strings.Split(n, ":")
if len(split) == 2 {
r, ok := routes[split[0]]
if ok {
r.Routes = append(r.Routes, split[1])
} else {
routes[split[0]] = &api.Routes{Routes: []string{split[1]}}
}
} else {
globalNameservers = append(globalNameservers, n)
}
}
req := api.SetDNSConfigRequest{
TailnetId: tailnet.Id,
Config: &api.DNSConfig{
MagicDns: magicDNS,
OverrideLocalDns: overrideLocalDNS,
Nameservers: nameservers,
Routes: routes,
},
}
resp, err := client.SetDNSConfig(context.Background(), &req)
if err != nil {
return err
}
config := resp.Config
var allNameservers = config.Nameservers
for i, j := range config.Routes {
for _, n := range j.Routes {
allNameservers = append(allNameservers, fmt.Sprintf("%s:%s", i, n))
}
}
fmt.Printf("%-*v%v\n", 25, "Magic DNS Enabled:", config.MagicDns)
fmt.Printf("%-*v%v\n", 25, "Override Local DNS:", config.OverrideLocalDns)
fmt.Printf("%-*v%v\n", 25, "Nameservers:", strings.Join(allNameservers, ","))
return nil
}
return command
}
+2
View File
@@ -16,6 +16,8 @@ func tailnetCommand() *coral.Command {
command.AddCommand(listTailnetsCommand())
command.AddCommand(createTailnetsCommand())
command.AddCommand(getDNSConfig())
command.AddCommand(setDNSConfig())
command.AddCommand(getACLConfig())
command.AddCommand(setACLConfig())
+2
View File
@@ -15,6 +15,8 @@ type Repository interface {
GetTailnet(ctx context.Context, id uint64) (*Tailnet, error)
ListTailnets(ctx context.Context) ([]Tailnet, error)
GetDNSConfig(ctx context.Context, tailnetID uint64) (*DNSConfig, error)
SetDNSConfig(ctx context.Context, tailnetID uint64, config *DNSConfig) error
GetACLPolicy(ctx context.Context, tailnetID uint64) (*ACLPolicy, error)
SetACLPolicy(ctx context.Context, tailnetID uint64, policy *ACLPolicy) error
+20
View File
@@ -13,6 +13,26 @@ type TailnetConfig struct {
Value []byte
}
type DNSConfig struct {
MagicDNS bool
OverrideLocalDNS bool
Nameservers []string
Routes map[string][]string
}
func (r *repository) GetDNSConfig(ctx context.Context, tailnetID uint64) (*DNSConfig, error) {
var m DNSConfig
err := r.getConfig(ctx, "dns_config", tailnetID, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (r *repository) SetDNSConfig(ctx context.Context, tailnetID uint64, config *DNSConfig) error {
return r.setConfig(ctx, "dns_config", tailnetID, config)
}
func (r *repository) SetACLPolicy(ctx context.Context, tailnetID uint64, policy *ACLPolicy) error {
if err := r.setConfig(ctx, "acl_policy", tailnetID, policy); err != nil {
return err
+6
View File
@@ -262,6 +262,11 @@ func (h *PollNetMapHandler) createMapResponse(m *domain.Machine, binder bind.Bin
removedPeers = append(removedPeers, tailcfg.NodeID(p))
}
dnsConfig, err := h.repository.GetDNSConfig(ctx, m.TailnetID)
if err != nil {
return nil, nil, err
}
derpMap, err := h.repository.GetDERPMap(ctx)
if err != nil {
return nil, nil, err
@@ -276,6 +281,7 @@ func (h *PollNetMapHandler) createMapResponse(m *domain.Machine, binder bind.Bin
mapResponse = &tailcfg.MapResponse{
KeepAlive: false,
Node: node,
DNSConfig: mapping.ToDNSConfig(&m.Tailnet, dnsConfig),
PacketFilter: rules,
DERPMap: derpMap,
Domain: dnsname.SanitizeHostname(m.Tailnet.Name),
+45
View File
@@ -8,6 +8,7 @@ import (
"inet.af/netaddr"
"strconv"
"tailscale.com/tailcfg"
"tailscale.com/types/dnstype"
"tailscale.com/types/key"
"tailscale.com/util/dnsname"
)
@@ -27,6 +28,50 @@ func CopyViaJson[F any, T any](f F, t T) error {
return nil
}
func ToDNSConfig(tailnet *domain.Tailnet, c *domain.DNSConfig) *tailcfg.DNSConfig {
tailnetDomain := dnsname.SanitizeHostname(tailnet.Name)
resolvers := []dnstype.Resolver{}
for _, r := range c.Nameservers {
resolver := dnstype.Resolver{
Addr: r,
}
resolvers = append(resolvers, resolver)
}
config := &tailcfg.DNSConfig{}
var domains []string
if c.MagicDNS {
domains = append(domains, fmt.Sprintf("%s.%s", tailnetDomain, NetworkMagicDNSSuffix))
config.Proxied = true
}
if c.OverrideLocalDNS {
config.Resolvers = resolvers
} else {
config.FallbackResolvers = resolvers
}
if len(c.Routes) != 0 {
routes := make(map[string][]dnstype.Resolver)
for r, s := range c.Routes {
routeResolver := []dnstype.Resolver{}
for _, addr := range s {
resolver := dnstype.Resolver{Addr: addr}
routeResolver = append(routeResolver, resolver)
}
routes[r] = routeResolver
domains = append(domains, r)
}
config.Routes = routes
}
config.Domains = domains
return config
}
func ToNode(m *domain.Machine, connected bool) (*tailcfg.Node, error) {
nKey, err := util.ParseNodePublicKey(m.NodeKey)
if err != nil {
+84
View File
@@ -0,0 +1,84 @@
package service
import (
"context"
"github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/pkg/gen/api"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (s *Service) GetDNSConfig(ctx context.Context, req *api.GetDNSConfigRequest) (*api.GetDNSConfigResponse, error) {
tailnet, err := s.repository.GetTailnet(ctx, req.TailnetId)
if err != nil {
return nil, err
}
if tailnet == nil {
return nil, status.Error(codes.NotFound, "tailnet does not exist")
}
config, err := s.repository.GetDNSConfig(ctx, tailnet.ID)
if err != nil {
return nil, err
}
resp := &api.GetDNSConfigResponse{
Config: &api.DNSConfig{
MagicDns: config.MagicDNS,
OverrideLocalDns: config.OverrideLocalDNS,
Nameservers: config.Nameservers,
Routes: domainRoutesToApiRoutes(config.Routes),
},
}
return resp, nil
}
func (s *Service) SetDNSConfig(ctx context.Context, req *api.SetDNSConfigRequest) (*api.SetDNSConfigResponse, error) {
dnsConfig := req.Config
if dnsConfig.MagicDns && len(dnsConfig.Nameservers) == 0 {
return nil, status.Error(codes.InvalidArgument, "at least one global nameserver is required when enabling magic dns")
}
tailnet, err := s.repository.GetTailnet(ctx, req.TailnetId)
if err != nil {
return nil, err
}
if tailnet == nil {
return nil, status.Error(codes.NotFound, "tailnet does not exist")
}
config := domain.DNSConfig{
MagicDNS: dnsConfig.MagicDns,
OverrideLocalDNS: dnsConfig.OverrideLocalDns,
Nameservers: dnsConfig.Nameservers,
Routes: apiRoutesToDomainRoutes(dnsConfig.Routes),
}
if err := s.repository.SetDNSConfig(ctx, tailnet.ID, &config); err != nil {
return nil, err
}
s.brokers(tailnet.ID).SignalDNSUpdated()
resp := &api.SetDNSConfigResponse{Config: dnsConfig}
return resp, nil
}
func domainRoutesToApiRoutes(routes map[string][]string) map[string]*api.Routes {
var result = map[string]*api.Routes{}
for k, v := range routes {
result[k] = &api.Routes{Routes: v}
}
return result
}
func apiRoutesToDomainRoutes(routes map[string]*api.Routes) map[string][]string {
var result = map[string][]string{}
for k, v := range routes {
result[k] = v.Routes
}
return result
}