mirror of
https://github.com/jsiebens/ionscale.git
synced 2026-03-31 15:07:49 +01:00
feat: configure dns preferecens for tailnets
This commit is contained in:
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user