| Giorgi Lekveishvili | e8b2f01 | 2023-11-30 19:05:03 +0400 | [diff] [blame] | 1 | package controllers |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "io" |
| 6 | "io/fs" |
| 7 | "os" |
| 8 | "text/template" |
| 9 | |
| 10 | "github.com/Masterminds/sprig/v3" |
| 11 | "github.com/go-git/go-billy/v5" |
| 12 | "github.com/go-git/go-billy/v5/util" |
| 13 | ) |
| 14 | |
| 15 | const zoneConfigFilename = "coredns.conf" |
| 16 | const rootConfigFilename = "coredns.conf" |
| 17 | const importAllConfigFiles = "import */" + zoneConfigFilename |
| 18 | |
| 19 | type ZoneStore interface { |
| 20 | ConfigPath() string |
| 21 | AddDNSSec(key DNSSecKey) error |
| 22 | } |
| 23 | |
| 24 | type ZoneConfig struct { |
| 25 | Zone string |
| 26 | PublicIPs []string |
| 27 | PrivateIP string |
| 28 | Nameservers []string |
| 29 | DNSSec *DNSSecKey |
| 30 | } |
| 31 | |
| 32 | type ZoneStoreFactory interface { |
| 33 | ConfigPath() string |
| 34 | Create(zone ZoneConfig) (ZoneStore, error) |
| 35 | Debug() |
| 36 | } |
| 37 | |
| 38 | type fsZoneStoreFactory struct { |
| 39 | fs billy.Filesystem |
| 40 | } |
| 41 | |
| 42 | func NewFSZoneStoreFactory(fs billy.Filesystem) (ZoneStoreFactory, error) { |
| 43 | if err := util.WriteFile(fs, rootConfigFilename, []byte(importAllConfigFiles), os.ModePerm); err != nil { |
| 44 | return nil, err |
| 45 | } |
| 46 | return &fsZoneStoreFactory{fs}, nil |
| 47 | } |
| 48 | |
| 49 | func (f *fsZoneStoreFactory) ConfigPath() string { |
| 50 | return f.fs.Join(f.fs.Root(), rootConfigFilename) |
| 51 | } |
| 52 | |
| 53 | func (f *fsZoneStoreFactory) Debug() { |
| 54 | fmt.Println("------------") |
| 55 | util.Walk(f.fs, ".", func(path string, info fs.FileInfo, err error) error { |
| 56 | fmt.Println(path) |
| 57 | if !info.IsDir() { |
| 58 | r, err := f.fs.Open(path) |
| 59 | if err != nil { |
| 60 | return err |
| 61 | } |
| 62 | defer r.Close() |
| 63 | _, err = io.Copy(os.Stdout, r) |
| 64 | return err |
| 65 | } |
| 66 | return nil |
| 67 | }) |
| 68 | fmt.Println("++++++++++++++") |
| 69 | } |
| 70 | |
| 71 | func (f *fsZoneStoreFactory) Create(zone ZoneConfig) (ZoneStore, error) { |
| 72 | if err := f.fs.MkdirAll(zone.Zone, fs.ModePerm); err != nil { |
| 73 | return nil, err |
| 74 | } |
| 75 | zfs, err := f.fs.Chroot(zone.Zone) |
| 76 | if err != nil { |
| 77 | return nil, err |
| 78 | } |
| 79 | z, err := NewFSZoneStore(zone, zfs) |
| 80 | if err != nil { |
| 81 | defer func() { |
| 82 | if err := f.fs.Remove(zone.Zone); err != nil { |
| 83 | fmt.Printf("Failed to remove zone directory: %s\n", err.Error()) |
| 84 | } |
| 85 | }() |
| 86 | } |
| 87 | return z, nil |
| 88 | } |
| 89 | |
| 90 | type fsZoneStore struct { |
| 91 | zone ZoneConfig |
| 92 | fs billy.Filesystem |
| 93 | } |
| 94 | |
| 95 | func NewFSZoneStore(zone ZoneConfig, fs billy.Filesystem) (ZoneStore, error) { |
| 96 | if zone.DNSSec != nil { |
| 97 | sec := zone.DNSSec |
| 98 | if err := util.WriteFile(fs, sec.Basename+".key", sec.Key, 0644); err != nil { |
| 99 | return nil, err |
| 100 | } |
| 101 | if err := util.WriteFile(fs, sec.Basename+".private", sec.Private, 0600); err != nil { |
| 102 | return nil, err |
| 103 | } |
| 104 | } |
| 105 | conf, err := fs.Create(zoneConfigFilename) |
| 106 | if err != nil { |
| 107 | return nil, err |
| 108 | } |
| 109 | defer conf.Close() |
| 110 | configTmpl, err := template.New("config").Funcs(sprig.TxtFuncMap()).Parse(` |
| 111 | {{ .zone.Zone }}:53 { |
| 112 | file {{ .rootDir }}/zone.db |
| 113 | errors |
| 114 | {{ if .zone.DNSSec }} |
| 115 | dnssec { |
| 116 | key file {{ .rootDir}}/{{ .zone.DNSSec.Basename }} |
| 117 | } |
| 118 | {{ end }} |
| 119 | log |
| 120 | health { |
| 121 | lameduck 5s |
| 122 | } |
| 123 | ready |
| 124 | cache 30 |
| 125 | loop |
| 126 | reload |
| 127 | loadbalance |
| 128 | }`) |
| 129 | if err != nil { |
| 130 | return nil, err |
| 131 | } |
| 132 | if err := configTmpl.Execute(conf, map[string]any{ |
| 133 | "zone": zone, |
| 134 | "rootDir": fs.Root(), |
| 135 | }); err != nil { |
| 136 | return nil, err |
| 137 | } |
| 138 | recordsTmpl, err := template.New("records").Funcs(sprig.TxtFuncMap()).Parse(` |
| 139 | {{ .zone }}. IN SOA ns1.{{ .zone }}. hostmaster.{{ .zone }}. 2015082541 7200 3600 1209600 3600 |
| 140 | {{ range $i, $ns := .nameservers }} |
| 141 | ns{{ add1 $i }} 10800 IN A {{ $ns }} |
| 142 | {{ end }} |
| 143 | {{ range .publicIngressIPs }} |
| 144 | @ 10800 IN A {{ . }} |
| 145 | {{ end }} |
| 146 | * 10800 IN CNAME {{ .zone }}. |
| 147 | p 10800 IN CNAME {{ .zone }}. |
| 148 | *.p 10800 IN A {{ .privateIngressIP }} |
| 149 | `) |
| 150 | records, err := fs.Create("zone.db") |
| 151 | if err != nil { |
| 152 | return nil, err |
| 153 | } |
| 154 | defer records.Close() |
| 155 | if err := recordsTmpl.Execute(records, map[string]any{ |
| 156 | "zone": zone.Zone, |
| 157 | "publicIngressIPs": zone.PublicIPs, |
| 158 | "privateIngressIP": zone.PrivateIP, |
| 159 | "nameservers": zone.Nameservers, |
| 160 | }); err != nil { |
| 161 | return nil, err |
| 162 | } |
| 163 | return &fsZoneStore{zone, fs}, nil |
| 164 | } |
| 165 | |
| 166 | func (s *fsZoneStore) ConfigPath() string { |
| 167 | return s.fs.Join(s.fs.Root(), zoneConfigFilename) |
| 168 | } |
| 169 | |
| 170 | func (s *fsZoneStore) AddDNSSec(key DNSSecKey) error { |
| 171 | return nil |
| 172 | } |