]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - cmd/safcm/sync_sync_test.go
1294a452dc65bbff13d74852789efdf356cc75e6
[safcm/safcm.git] / cmd / safcm / sync_sync_test.go
1 // Copyright (C) 2021  Simon Ruderich
2 //
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 package main
17
18 import (
19         "fmt"
20         "io"
21         "io/fs"
22         "log"
23         "os"
24         "path/filepath"
25         "testing"
26
27         "ruderich.org/simon/safcm"
28         "ruderich.org/simon/safcm/testutil"
29 )
30
31 func TestHostSyncReq(t *testing.T) {
32         cwd, err := os.Getwd()
33         if err != nil {
34                 t.Fatal(err)
35         }
36         defer os.Chdir(cwd)
37
38         tests := []struct {
39                 name      string
40                 project   string
41                 host      string
42                 detected  []string
43                 level     safcm.LogLevel
44                 exp       safcm.MsgSyncReq
45                 expEvents []string
46                 expErr    error
47         }{
48
49                 // NOTE: Also update MsgSyncReq in safcm-remote test cases
50                 // when changing anything here!
51
52                 {
53                         "project: host1",
54                         "project",
55                         "host1.example.org",
56                         nil,
57                         safcm.LogDebug3,
58                         safcm.MsgSyncReq{
59                                 Groups: []string{
60                                         "all",
61                                         "group",
62                                         "group3",
63                                         "remove",
64                                         "host1.example.org",
65                                 },
66                                 Files: map[string]*safcm.File{
67                                         "/": &safcm.File{Path: "/",
68                                                 OrigGroup: "group",
69                                                 Mode:      fs.ModeDir | 0755 | fs.ModeSetgid,
70                                                 Uid:       -1,
71                                                 Gid:       -1,
72                                                 TriggerCommands: []string{
73                                                         "touch /.update",
74                                                 },
75                                         },
76                                         "/etc": &safcm.File{
77                                                 OrigGroup: "group",
78                                                 Path:      "/etc",
79                                                 Mode:      fs.ModeDir | 0755,
80                                                 Uid:       -1,
81                                                 Gid:       -1,
82                                         },
83                                         "/etc/.hidden": &safcm.File{
84                                                 OrigGroup: "group",
85                                                 Path:      "/etc/.hidden",
86                                                 Mode:      0100 | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky,
87                                                 Uid:       -1,
88                                                 Gid:       -1,
89                                                 Data:      []byte("..."),
90                                         },
91                                         "/etc/motd": &safcm.File{
92                                                 OrigGroup: "group",
93                                                 Path:      "/etc/motd",
94                                                 Mode:      0644,
95                                                 Uid:       -1,
96                                                 Gid:       -1,
97                                                 Data:      []byte("Welcome to Host ONE\n\n\n\n\n\nall\n\n\nhost1.example.org\n\n\n\n"),
98                                         },
99                                         "/etc/rc.local": &safcm.File{
100                                                 OrigGroup: "group",
101                                                 Path:      "/etc/rc.local",
102                                                 Mode:      0700,
103                                                 Uid:       -1,
104                                                 Gid:       -1,
105                                                 Data:      []byte("#!/bin/sh\n"),
106                                                 TriggerCommands: []string{
107                                                         "/etc/rc.local",
108                                                 },
109                                         },
110                                         "/etc/resolv.conf": &safcm.File{
111                                                 OrigGroup: "group",
112                                                 Path:      "/etc/resolv.conf",
113                                                 Mode:      0641,
114                                                 User:      "user",
115                                                 Uid:       -1,
116                                                 Group:     "group",
117                                                 Gid:       -1,
118                                                 Data:      []byte("nameserver ::1\n"),
119                                                 TriggerCommands: []string{
120                                                         "echo resolv.conf updated",
121                                                 },
122                                         },
123                                         "/etc/test": &safcm.File{
124                                                 OrigGroup: "group",
125                                                 Path:      "/etc/test",
126                                                 Mode:      os.ModeSymlink | 0777,
127                                                 Uid:       -1,
128                                                 Gid:       -1,
129                                                 Data:      []byte("doesnt-exist"),
130                                         },
131                                 },
132                                 Packages: []string{
133                                         "unbound",
134                                         "unbound-anchor",
135                                 },
136                                 Services: []string{
137                                         "unbound",
138                                 },
139                                 Commands: []*safcm.Command{
140                                         {
141                                                 Cmd: "echo command one",
142                                         },
143                                         {
144                                                 Cmd: "echo -n command two",
145                                         },
146                                 },
147                         },
148                         []string{
149                                 "host1.example.org: <nil> 3 host groups: all group group3 host1.example.org remove",
150                                 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org",
151                         },
152                         nil,
153                 },
154
155                 {
156                         "project: host1 (log level info)",
157                         "project",
158                         "host1.example.org",
159                         nil,
160                         safcm.LogInfo,
161                         safcm.MsgSyncReq{
162                                 Groups: []string{
163                                         "all",
164                                         "group",
165                                         "group3",
166                                         "remove",
167                                         "host1.example.org",
168                                 },
169                                 Files: map[string]*safcm.File{
170                                         "/": &safcm.File{Path: "/",
171                                                 OrigGroup: "group",
172                                                 Mode:      fs.ModeDir | 0755 | fs.ModeSetgid,
173                                                 Uid:       -1,
174                                                 Gid:       -1,
175                                                 TriggerCommands: []string{
176                                                         "touch /.update",
177                                                 },
178                                         },
179                                         "/etc": &safcm.File{
180                                                 OrigGroup: "group",
181                                                 Path:      "/etc",
182                                                 Mode:      fs.ModeDir | 0755,
183                                                 Uid:       -1,
184                                                 Gid:       -1,
185                                         },
186                                         "/etc/.hidden": &safcm.File{
187                                                 OrigGroup: "group",
188                                                 Path:      "/etc/.hidden",
189                                                 Mode:      0100 | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky,
190                                                 Uid:       -1,
191                                                 Gid:       -1,
192                                                 Data:      []byte("..."),
193                                         },
194                                         "/etc/motd": &safcm.File{
195                                                 OrigGroup: "group",
196                                                 Path:      "/etc/motd",
197                                                 Mode:      0644,
198                                                 Uid:       -1,
199                                                 Gid:       -1,
200                                                 Data:      []byte("Welcome to Host ONE\n\n\n\n\n\nall\n\n\nhost1.example.org\n\n\n\n"),
201                                         },
202                                         "/etc/rc.local": &safcm.File{
203                                                 OrigGroup: "group",
204                                                 Path:      "/etc/rc.local",
205                                                 Mode:      0700,
206                                                 Uid:       -1,
207                                                 Gid:       -1,
208                                                 Data:      []byte("#!/bin/sh\n"),
209                                                 TriggerCommands: []string{
210                                                         "/etc/rc.local",
211                                                 },
212                                         },
213                                         "/etc/resolv.conf": &safcm.File{
214                                                 OrigGroup: "group",
215                                                 Path:      "/etc/resolv.conf",
216                                                 Mode:      0641,
217                                                 User:      "user",
218                                                 Uid:       -1,
219                                                 Group:     "group",
220                                                 Gid:       -1,
221                                                 Data:      []byte("nameserver ::1\n"),
222                                                 TriggerCommands: []string{
223                                                         "echo resolv.conf updated",
224                                                 },
225                                         },
226                                         "/etc/test": &safcm.File{
227                                                 OrigGroup: "group",
228                                                 Path:      "/etc/test",
229                                                 Mode:      os.ModeSymlink | 0777,
230                                                 Uid:       -1,
231                                                 Gid:       -1,
232                                                 Data:      []byte("doesnt-exist"),
233                                         },
234                                 },
235                                 Packages: []string{
236                                         "unbound",
237                                         "unbound-anchor",
238                                 },
239                                 Services: []string{
240                                         "unbound",
241                                 },
242                                 Commands: []*safcm.Command{
243                                         {
244                                                 Cmd: "echo command one",
245                                         },
246                                         {
247                                                 Cmd: "echo -n command two",
248                                         },
249                                 },
250                         },
251                         nil,
252                         nil,
253                 },
254
255                 {
256                         "conflict: file",
257                         "project-conflict-file",
258                         "host1.example.org",
259                         nil,
260                         safcm.LogDebug3,
261                         safcm.MsgSyncReq{},
262                         []string{
263                                 "host1.example.org: <nil> 3 host groups: all dns host1.example.org",
264                                 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org",
265                         },
266                         fmt.Errorf("groups dns and all both provide file \"/etc/resolv.conf\"\nUse 'group_order' in config.yaml to declare preference"),
267                 },
268                 {
269                         "conflict: file from detected group",
270                         "project-conflict-file",
271                         "host2.example.org",
272                         []string{
273                                 "detected_other",
274                         },
275                         safcm.LogDebug3,
276                         safcm.MsgSyncReq{},
277                         []string{
278                                 "host2.example.org: <nil> 3 host groups: all detected_other host2.example.org other",
279                                 "host2.example.org: <nil> 3 host group priorities (desc. order): host2.example.org",
280                         },
281                         fmt.Errorf("groups other and all both provide file \"/etc/resolv.conf\"\nUse 'group_order' in config.yaml to declare preference"),
282                 },
283
284                 {
285                         "conflict: dir",
286                         "project-conflict-dir",
287                         "host1.example.org",
288                         nil,
289                         safcm.LogDebug3,
290                         safcm.MsgSyncReq{},
291                         []string{
292                                 "host1.example.org: <nil> 3 host groups: all dns host1.example.org",
293                                 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org",
294                         },
295                         fmt.Errorf("groups dns and all both provide file \"/etc\"\nUse 'group_order' in config.yaml to declare preference"),
296                 },
297                 {
298                         "conflict: dir from detected group",
299                         "project-conflict-dir",
300                         "host2.example.org",
301                         []string{
302                                 "detected_other",
303                         },
304                         safcm.LogDebug3,
305                         safcm.MsgSyncReq{},
306                         []string{
307                                 "host2.example.org: <nil> 3 host groups: all detected_other host2.example.org other",
308                                 "host2.example.org: <nil> 3 host group priorities (desc. order): host2.example.org",
309                         },
310                         fmt.Errorf("groups other and all both provide file \"/etc\"\nUse 'group_order' in config.yaml to declare preference"),
311                 },
312
313                 {
314                         "group: cycle",
315                         "project-group-cycle",
316                         "host1.example.org",
317                         nil,
318                         safcm.LogDebug3,
319                         safcm.MsgSyncReq{},
320                         nil,
321                         fmt.Errorf("groups.yaml: cycle while expanding group \"group-b\""),
322                 },
323
324                 {
325                         "group_order",
326                         "project-group_order",
327                         "host1.example.org",
328                         nil,
329                         safcm.LogDebug3,
330                         safcm.MsgSyncReq{
331                                 Groups: []string{"all", "group-b", "group-a", "host1.example.org"},
332                                 Files: map[string]*safcm.File{
333                                         "/": {
334                                                 Path:      "/",
335                                                 Mode:      fs.ModeDir | 0755,
336                                                 Uid:       -1,
337                                                 Gid:       -1,
338                                                 OrigGroup: "host1.example.org",
339                                         },
340                                         "/etc": {
341                                                 Path:      "/etc",
342                                                 Mode:      fs.ModeDir | 0755,
343                                                 Uid:       -1,
344                                                 Gid:       -1,
345                                                 OrigGroup: "host1.example.org",
346                                         },
347                                         "/etc/dir-to-file": {
348                                                 Path:      "/etc/dir-to-file",
349                                                 Mode:      0644,
350                                                 Uid:       -1,
351                                                 Gid:       -1,
352                                                 Data:      []byte("dir-to-file: from group-a\n"),
353                                                 OrigGroup: "group-a",
354                                         },
355                                         "/etc/dir-to-filex": {
356                                                 OrigGroup: "group-b",
357                                                 Path:      "/etc/dir-to-filex",
358                                                 Mode:      0644,
359                                                 Uid:       -1,
360                                                 Gid:       -1,
361                                                 Data:      []byte("dir-to-filex\n"),
362                                         },
363                                         "/etc/dir-to-link": {
364                                                 Path:      "/etc/dir-to-link",
365                                                 Mode:      fs.ModeSymlink | 0777,
366                                                 Uid:       -1,
367                                                 Gid:       -1,
368                                                 Data:      []byte("target"),
369                                                 OrigGroup: "group-a",
370                                         },
371                                         "/etc/dir-to-linkx": {
372                                                 OrigGroup: "group-b",
373                                                 Path:      "/etc/dir-to-linkx",
374                                                 Mode:      0644,
375                                                 Uid:       -1,
376                                                 Gid:       -1,
377                                                 Data:      []byte("dir-to-linkx\n"),
378                                         },
379                                         "/etc/file-to-dir": {
380                                                 Path:      "/etc/file-to-dir",
381                                                 Mode:      fs.ModeDir | 0755,
382                                                 Uid:       -1,
383                                                 Gid:       -1,
384                                                 OrigGroup: "group-a",
385                                         },
386                                         "/etc/file-to-dir/file": {
387                                                 Path:      "/etc/file-to-dir/file",
388                                                 Mode:      0644,
389                                                 Uid:       -1,
390                                                 Gid:       -1,
391                                                 Data:      []byte("file: from group-a\n"),
392                                                 OrigGroup: "group-a",
393                                         },
394                                         "/etc/file-to-dir/dir": {
395                                                 Path:      "/etc/file-to-dir/dir",
396                                                 Mode:      fs.ModeDir | 0755,
397                                                 Uid:       -1,
398                                                 Gid:       -1,
399                                                 OrigGroup: "group-a",
400                                         },
401                                         "/etc/file-to-dir/dir/file2": {
402                                                 Path:      "/etc/file-to-dir/dir/file2",
403                                                 Mode:      0644,
404                                                 Uid:       -1,
405                                                 Gid:       -1,
406                                                 Data:      []byte("file2: from group-a\n"),
407                                                 OrigGroup: "group-a",
408                                         },
409                                         "/etc/motd": {
410                                                 Path:      "/etc/motd",
411                                                 Mode:      0644,
412                                                 Uid:       -1,
413                                                 Gid:       -1,
414                                                 Data:      []byte("motd: from host1\n"),
415                                                 OrigGroup: "host1.example.org",
416                                         },
417                                 },
418                         },
419                         []string{
420                                 "host1.example.org: <nil> 3 host groups: all group-a group-b host1.example.org",
421                                 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org group-a group-b all",
422                                 `host1.example.org: <nil> 4 files: "/etc": group group-a overwrites triggers from group group-b`,
423                                 `host1.example.org: <nil> 4 files: "/etc": group host1.example.org overwrites triggers from group group-a`,
424                         },
425                         nil,
426                 },
427         }
428
429         for _, tc := range tests {
430                 t.Run(tc.name, func(t *testing.T) {
431                         err = os.Chdir(filepath.Join(cwd,
432                                 "testdata", tc.project))
433                         if err != nil {
434                                 t.Fatal(err)
435                         }
436
437                         // `safcm fixperms` in case user has strict umask
438                         log.SetOutput(io.Discard)
439                         err := MainFixperms()
440                         if err != nil {
441                                 t.Fatal(err)
442                         }
443                         log.SetOutput(os.Stderr)
444
445                         cfg, allHosts, allGroups, err := LoadBaseFiles()
446                         if err != nil {
447                                 t.Fatal(err)
448                         }
449                         cfg.LogLevel = tc.level
450
451                         var events []string
452                         ch := make(chan Event)
453                         done := make(chan struct{})
454                         go func() {
455                                 for {
456                                         x, ok := <-ch
457                                         if !ok {
458                                                 break
459                                         }
460                                         if x.ConnEvent.Type != 0 {
461                                                 panic("unexpected ConnEvent")
462                                         }
463                                         events = append(events,
464                                                 fmt.Sprintf("%s: %v %d %s",
465                                                         x.Host.Name,
466                                                         x.Error, x.Log.Level,
467                                                         x.Log.Text))
468                                 }
469                                 done <- struct{}{}
470                         }()
471
472                         s := &Sync{
473                                 host:      allHosts.Map[tc.host],
474                                 config:    cfg,
475                                 allHosts:  allHosts,
476                                 allGroups: allGroups,
477                                 events:    ch,
478                         }
479
480                         res, err := s.hostSyncReq(tc.detected)
481
482                         testutil.AssertEqual(t, "res", res, tc.exp)
483                         testutil.AssertErrorEqual(t, "err", err, tc.expErr)
484
485                         close(ch)
486                         <-done
487                         testutil.AssertEqual(t, "events",
488                                 events, tc.expEvents)
489                 })
490         }
491 }