blob: 533e767560645c4cfc174db9ee7a91fbb41e93e1 [file] [log] [blame]
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +04001package controllers
2
3import (
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +04004 "encoding/json"
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +04005 "fmt"
6 "io"
7 "io/fs"
8 "os"
9 "text/template"
10
11 "github.com/Masterminds/sprig/v3"
12 "github.com/go-git/go-billy/v5"
13 "github.com/go-git/go-billy/v5/util"
14)
15
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +040016const dodoConfigFilename = "dodo.json"
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040017const zoneConfigFilename = "coredns.conf"
18const rootConfigFilename = "coredns.conf"
19const importAllConfigFiles = "import */" + zoneConfigFilename
20
21type ZoneStore interface {
22 ConfigPath() string
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +040023 CreateConfigFile() error
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040024 AddDNSSec(key DNSSecKey) error
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +040025 AddTextRecord(entry, txt string) error
26 DeleteTextRecord(entry, txt string) error
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040027}
28
29type ZoneConfig struct {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +040030 Zone string `json:"zone,omitempty"`
31 PublicIPs []string `json:"publicIPs,omitempty"`
Giorgi Lekveishvili109a5672023-12-07 16:05:42 +040032 PrivateIP string `json:"privateIP,omitempty"`
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +040033 Nameservers []string `json:"nameservers,omitempty"`
34 DNSSec *DNSSecKey `json:"dnsSec,omitempty"`
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040035}
36
37type ZoneStoreFactory interface {
38 ConfigPath() string
39 Create(zone ZoneConfig) (ZoneStore, error)
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +040040 Get(zone string) (ZoneStore, error)
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040041 Debug()
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +040042 Purge()
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040043}
44
45type fsZoneStoreFactory struct {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +040046 fs billy.Filesystem
47 zones map[string]ZoneStore
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040048}
49
50func NewFSZoneStoreFactory(fs billy.Filesystem) (ZoneStoreFactory, error) {
51 if err := util.WriteFile(fs, rootConfigFilename, []byte(importAllConfigFiles), os.ModePerm); err != nil {
52 return nil, err
53 }
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +040054 f, err := fs.ReadDir(".")
55 if err != nil {
56 return nil, err
57 }
58 zf := fsZoneStoreFactory{fs: fs, zones: make(map[string]ZoneStore)}
59 for _, i := range f {
60 if i.IsDir() {
61 var zone ZoneConfig
62 r, err := fs.Open(fs.Join(i.Name(), dodoConfigFilename))
63 if err != nil {
64 continue // TODO(gio): clean up the dir to enforce config file
65 }
66 defer r.Close()
67 if err := json.NewDecoder(r).Decode(&zone); err != nil {
68 return nil, err
69 }
70 zfs, err := fs.Chroot(zone.Zone)
71 if err != nil {
72 return nil, err
73 }
74 z, err := NewFSZoneStore(zone, zfs)
75 zf.zones[zone.Zone] = z
76 }
77 }
78 return &zf, nil
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040079}
80
81func (f *fsZoneStoreFactory) ConfigPath() string {
82 return f.fs.Join(f.fs.Root(), rootConfigFilename)
83}
84
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +040085func (f *fsZoneStoreFactory) Purge() {
86 items, _ := f.fs.ReadDir(".")
87 for _, i := range items {
88 f.fs.Remove(i.Name())
89 }
90}
91
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040092func (f *fsZoneStoreFactory) Debug() {
93 fmt.Println("------------")
94 util.Walk(f.fs, ".", func(path string, info fs.FileInfo, err error) error {
95 fmt.Println(path)
96 if !info.IsDir() {
97 r, err := f.fs.Open(path)
98 if err != nil {
99 return err
100 }
101 defer r.Close()
102 _, err = io.Copy(os.Stdout, r)
103 return err
104 }
105 return nil
106 })
107 fmt.Println("++++++++++++++")
108}
109
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400110func (f *fsZoneStoreFactory) Get(zone string) (ZoneStore, error) {
111 if z, ok := f.zones[zone]; ok {
112 return z, nil
113 }
114 return nil, fmt.Errorf("%s zone not found", zone)
115}
116
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400117func (f *fsZoneStoreFactory) Create(zone ZoneConfig) (ZoneStore, error) {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400118 if z, ok := f.zones[zone.Zone]; ok {
119 return z, nil
120 }
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400121 if err := f.fs.MkdirAll(zone.Zone, fs.ModePerm); err != nil {
122 return nil, err
123 }
124 zfs, err := f.fs.Chroot(zone.Zone)
125 if err != nil {
126 return nil, err
127 }
128 z, err := NewFSZoneStore(zone, zfs)
129 if err != nil {
130 defer func() {
131 if err := f.fs.Remove(zone.Zone); err != nil {
132 fmt.Printf("Failed to remove zone directory: %s\n", err.Error())
133 }
134 }()
135 }
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400136 f.zones[zone.Zone] = z
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400137 return z, nil
138}
139
140type fsZoneStore struct {
141 zone ZoneConfig
142 fs billy.Filesystem
143}
144
145func NewFSZoneStore(zone ZoneConfig, fs billy.Filesystem) (ZoneStore, error) {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400146 return &fsZoneStore{zone, fs}, nil
147}
148
149func (s *fsZoneStore) CreateConfigFile() error {
150 {
151 w, err := s.fs.Create(dodoConfigFilename)
152 if err != nil {
153 return err
154 }
155 defer w.Close()
156 if err := json.NewEncoder(w).Encode(s.zone); err != nil {
157 return err
158 }
159 }
160 zone := s.zone
161 fs := s.fs
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400162 if zone.DNSSec != nil {
163 sec := zone.DNSSec
164 if err := util.WriteFile(fs, sec.Basename+".key", sec.Key, 0644); err != nil {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400165 return err
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400166 }
167 if err := util.WriteFile(fs, sec.Basename+".private", sec.Private, 0600); err != nil {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400168 return err
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400169 }
170 }
171 conf, err := fs.Create(zoneConfigFilename)
172 if err != nil {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400173 return err
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400174 }
175 defer conf.Close()
176 configTmpl, err := template.New("config").Funcs(sprig.TxtFuncMap()).Parse(`
177{{ .zone.Zone }}:53 {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400178 file {{ .rootDir }}/zone.db {
179 reload 1s
180 }
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400181 errors
182 {{ if .zone.DNSSec }}
183 dnssec {
184 key file {{ .rootDir}}/{{ .zone.DNSSec.Basename }}
185 }
186 {{ end }}
187 log
188 health {
189 lameduck 5s
190 }
191 ready
192 cache 30
193 loop
194 reload
195 loadbalance
196}`)
197 if err != nil {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400198 return err
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400199 }
200 if err := configTmpl.Execute(conf, map[string]any{
201 "zone": zone,
202 "rootDir": fs.Root(),
203 }); err != nil {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400204 return err
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400205 }
206 recordsTmpl, err := template.New("records").Funcs(sprig.TxtFuncMap()).Parse(`
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400207{{ $zone := .zone }}
208{{ $zone }}. IN SOA ns1.{{ $zone }}. hostmaster.{{ $zone }}. {{ .nowUnix }} 7200 3600 1209600 3600
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400209{{ range $i, $ns := .nameservers }}
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400210ns{{ add1 $i }}.{{ $zone }}. 10800 IN A {{ $ns }}
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400211{{ end }}
212{{ range .publicIngressIPs }}
Giorgi Lekveishvili109a5672023-12-07 16:05:42 +0400213{{ $zone }}. 10800 IN A {{ . }}
214*.{{ $zone }}. 10800 IN A {{ . }}
215*.*.{{ $zone }}. 10800 IN A {{ . }}
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400216{{ end }}
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400217*.p.{{ $zone }}. 10800 IN A {{ .privateIngressIP }}
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400218`)
219 records, err := fs.Create("zone.db")
220 if err != nil {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400221 return err
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400222 }
223 defer records.Close()
224 if err := recordsTmpl.Execute(records, map[string]any{
225 "zone": zone.Zone,
226 "publicIngressIPs": zone.PublicIPs,
227 "privateIngressIP": zone.PrivateIP,
228 "nameservers": zone.Nameservers,
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400229 "nowUnix": NowUnix(),
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400230 }); err != nil {
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400231 return err
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400232 }
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400233 return nil
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400234}
235
236func (s *fsZoneStore) ConfigPath() string {
237 return s.fs.Join(s.fs.Root(), zoneConfigFilename)
238}
239
240func (s *fsZoneStore) AddDNSSec(key DNSSecKey) error {
241 return nil
242}
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400243
244func (s *fsZoneStore) AddTextRecord(entry, txt string) error {
245 s.fs.Remove("txt")
246 r, err := s.fs.Open("zone.db")
247 if err != nil {
248 return err
249 }
250 defer r.Close()
251 z, err := NewZoneFile(r)
252 if err != nil {
253 return err
254 }
Giorgi Lekveishvili109a5672023-12-07 16:05:42 +0400255 var fqdn = fmt.Sprintf("%s.%s.", entry, s.zone.Zone)
256 z.CreateOrReplaceTxtRecord(fqdn, txt)
257 for _, ip := range s.zone.PublicIPs {
258 z.CreateARecord(fqdn, ip)
259 }
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400260 w, err := s.fs.Create("zone.db")
261 if err != nil {
262 return err
263 }
264 defer w.Close()
265 if err := z.Write(w); err != nil {
266 return err
267 }
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400268 return nil
269}
270
271func (s *fsZoneStore) DeleteTextRecord(entry, txt string) error {
272 r, err := s.fs.Open("zone.db")
273 if err != nil {
274 return err
275 }
276 defer r.Close()
277 z, err := NewZoneFile(r)
278 if err != nil {
279 return err
280 }
Giorgi Lekveishvili109a5672023-12-07 16:05:42 +0400281 fqdn := fmt.Sprintf("%s.%s.", entry, s.zone.Zone)
282 z.DeleteTxtRecord(fqdn, txt)
283 z.DeleteRecordsFor(fqdn)
Giorgi Lekveishvilie58fc592023-12-07 13:24:07 +0400284 w, err := s.fs.Create("zone.db")
285 if err != nil {
286 return err
287 }
288 defer w.Close()
289 if err := z.Write(w); err != nil {
290 return err
291 }
292 return nil
293}