]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - cmd/safcm/sync_sync_test.go
config: return map from TransitivelyDetectedGroups()
[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                                                 OrigGroup: "group",
142                                                 Cmd:       "echo command one",
143                                         },
144                                         {
145                                                 OrigGroup: "group",
146                                                 Cmd:       "echo -n command two",
147                                         },
148                                 },
149                         },
150                         []string{
151                                 "host1.example.org: <nil> 3 host groups: all group group3 host1.example.org remove",
152                                 "host1.example.org: <nil> 3 host group priorities (descending): host1.example.org",
153                         },
154                         nil,
155                 },
156
157                 {
158                         "project: host1 (log level info)",
159                         "project",
160                         "host1.example.org",
161                         nil,
162                         safcm.LogInfo,
163                         safcm.MsgSyncReq{
164                                 Groups: []string{
165                                         "all",
166                                         "group",
167                                         "group3",
168                                         "remove",
169                                         "host1.example.org",
170                                 },
171                                 Files: map[string]*safcm.File{
172                                         "/": &safcm.File{Path: "/",
173                                                 OrigGroup: "group",
174                                                 Mode:      fs.ModeDir | 0755 | fs.ModeSetgid,
175                                                 Uid:       -1,
176                                                 Gid:       -1,
177                                                 TriggerCommands: []string{
178                                                         "touch /.update",
179                                                 },
180                                         },
181                                         "/etc": &safcm.File{
182                                                 OrigGroup: "group",
183                                                 Path:      "/etc",
184                                                 Mode:      fs.ModeDir | 0755,
185                                                 Uid:       -1,
186                                                 Gid:       -1,
187                                         },
188                                         "/etc/.hidden": &safcm.File{
189                                                 OrigGroup: "group",
190                                                 Path:      "/etc/.hidden",
191                                                 Mode:      0100 | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky,
192                                                 Uid:       -1,
193                                                 Gid:       -1,
194                                                 Data:      []byte("..."),
195                                         },
196                                         "/etc/motd": &safcm.File{
197                                                 OrigGroup: "group",
198                                                 Path:      "/etc/motd",
199                                                 Mode:      0644,
200                                                 Uid:       -1,
201                                                 Gid:       -1,
202                                                 Data:      []byte("Welcome to Host ONE\n\n\n\n\n\nall\n\n\nhost1.example.org\n\n\n\n"),
203                                         },
204                                         "/etc/rc.local": &safcm.File{
205                                                 OrigGroup: "group",
206                                                 Path:      "/etc/rc.local",
207                                                 Mode:      0700,
208                                                 Uid:       -1,
209                                                 Gid:       -1,
210                                                 Data:      []byte("#!/bin/sh\n"),
211                                                 TriggerCommands: []string{
212                                                         "/etc/rc.local",
213                                                 },
214                                         },
215                                         "/etc/resolv.conf": &safcm.File{
216                                                 OrigGroup: "group",
217                                                 Path:      "/etc/resolv.conf",
218                                                 Mode:      0641,
219                                                 User:      "user",
220                                                 Uid:       -1,
221                                                 Group:     "group",
222                                                 Gid:       -1,
223                                                 Data:      []byte("nameserver ::1\n"),
224                                                 TriggerCommands: []string{
225                                                         "echo resolv.conf updated",
226                                                 },
227                                         },
228                                         "/etc/test": &safcm.File{
229                                                 OrigGroup: "group",
230                                                 Path:      "/etc/test",
231                                                 Mode:      os.ModeSymlink | 0777,
232                                                 Uid:       -1,
233                                                 Gid:       -1,
234                                                 Data:      []byte("doesnt-exist"),
235                                         },
236                                 },
237                                 Packages: []string{
238                                         "unbound",
239                                         "unbound-anchor",
240                                 },
241                                 Services: []string{
242                                         "unbound",
243                                 },
244                                 Commands: []*safcm.Command{
245                                         {
246                                                 OrigGroup: "group",
247                                                 Cmd:       "echo command one",
248                                         },
249                                         {
250                                                 OrigGroup: "group",
251                                                 Cmd:       "echo -n command two",
252                                         },
253                                 },
254                         },
255                         nil,
256                         nil,
257                 },
258
259                 {
260                         "conflict: file",
261                         "project-conflict-file",
262                         "host1.example.org",
263                         nil,
264                         safcm.LogDebug3,
265                         safcm.MsgSyncReq{},
266                         []string{
267                                 "host1.example.org: <nil> 3 host groups: all dns host1.example.org",
268                                 "host1.example.org: <nil> 3 host group priorities (descending): host1.example.org",
269                         },
270                         fmt.Errorf("groups dns and all both provide file \"/etc/resolv.conf\"\nUse 'group_priority' in config.yaml to declare preference"),
271                 },
272                 {
273                         "conflict: file from detected group",
274                         "project-conflict-file",
275                         "host2.example.org",
276                         []string{
277                                 "detected_other",
278                         },
279                         safcm.LogDebug3,
280                         safcm.MsgSyncReq{},
281                         []string{
282                                 "host2.example.org: <nil> 3 host groups: all detected_other host2.example.org other",
283                                 "host2.example.org: <nil> 3 host group priorities (descending): host2.example.org",
284                         },
285                         fmt.Errorf("groups other and all both provide file \"/etc/resolv.conf\"\nUse 'group_priority' in config.yaml to declare preference"),
286                 },
287
288                 {
289                         "conflict: dir",
290                         "project-conflict-dir",
291                         "host1.example.org",
292                         nil,
293                         safcm.LogDebug3,
294                         safcm.MsgSyncReq{},
295                         []string{
296                                 "host1.example.org: <nil> 3 host groups: all dns host1.example.org",
297                                 "host1.example.org: <nil> 3 host group priorities (descending): host1.example.org",
298                         },
299                         fmt.Errorf("groups dns and all both provide file \"/etc\"\nUse 'group_priority' in config.yaml to declare preference"),
300                 },
301                 {
302                         "conflict: dir from detected group",
303                         "project-conflict-dir",
304                         "host2.example.org",
305                         []string{
306                                 "detected_other",
307                         },
308                         safcm.LogDebug3,
309                         safcm.MsgSyncReq{},
310                         []string{
311                                 "host2.example.org: <nil> 3 host groups: all detected_other host2.example.org other",
312                                 "host2.example.org: <nil> 3 host group priorities (descending): host2.example.org",
313                         },
314                         fmt.Errorf("groups other and all both provide file \"/etc\"\nUse 'group_priority' in config.yaml to declare preference"),
315                 },
316
317                 {
318                         "group: cycle",
319                         "project-group-cycle",
320                         "host1.example.org",
321                         nil,
322                         safcm.LogDebug3,
323                         safcm.MsgSyncReq{},
324                         nil,
325                         fmt.Errorf("groups.yaml: cycle while expanding group \"group-b\""),
326                 },
327
328                 {
329                         "group_priority",
330                         "project-group_priority",
331                         "host1.example.org",
332                         nil,
333                         safcm.LogDebug3,
334                         safcm.MsgSyncReq{
335                                 Groups: []string{"all", "group-b", "group-a", "host1.example.org"},
336                                 Files: map[string]*safcm.File{
337                                         "/": {
338                                                 Path:      "/",
339                                                 Mode:      fs.ModeDir | 0755,
340                                                 Uid:       -1,
341                                                 Gid:       -1,
342                                                 OrigGroup: "host1.example.org",
343                                         },
344                                         "/etc": {
345                                                 Path:      "/etc",
346                                                 Mode:      fs.ModeDir | 0755,
347                                                 Uid:       -1,
348                                                 Gid:       -1,
349                                                 OrigGroup: "host1.example.org",
350                                         },
351                                         "/etc/dir-to-file": {
352                                                 Path:      "/etc/dir-to-file",
353                                                 Mode:      0644,
354                                                 Uid:       -1,
355                                                 Gid:       -1,
356                                                 Data:      []byte("dir-to-file: from group-a\n"),
357                                                 OrigGroup: "group-a",
358                                         },
359                                         "/etc/dir-to-filex": {
360                                                 OrigGroup: "group-b",
361                                                 Path:      "/etc/dir-to-filex",
362                                                 Mode:      0644,
363                                                 Uid:       -1,
364                                                 Gid:       -1,
365                                                 Data:      []byte("dir-to-filex\n"),
366                                         },
367                                         "/etc/dir-to-link": {
368                                                 Path:      "/etc/dir-to-link",
369                                                 Mode:      fs.ModeSymlink | 0777,
370                                                 Uid:       -1,
371                                                 Gid:       -1,
372                                                 Data:      []byte("target"),
373                                                 OrigGroup: "group-a",
374                                         },
375                                         "/etc/dir-to-linkx": {
376                                                 OrigGroup: "group-b",
377                                                 Path:      "/etc/dir-to-linkx",
378                                                 Mode:      0644,
379                                                 Uid:       -1,
380                                                 Gid:       -1,
381                                                 Data:      []byte("dir-to-linkx\n"),
382                                         },
383                                         "/etc/file-to-dir": {
384                                                 Path:      "/etc/file-to-dir",
385                                                 Mode:      fs.ModeDir | 0755,
386                                                 Uid:       -1,
387                                                 Gid:       -1,
388                                                 OrigGroup: "group-a",
389                                         },
390                                         "/etc/file-to-dir/file": {
391                                                 Path:      "/etc/file-to-dir/file",
392                                                 Mode:      0644,
393                                                 Uid:       -1,
394                                                 Gid:       -1,
395                                                 Data:      []byte("file: from group-a\n"),
396                                                 OrigGroup: "group-a",
397                                         },
398                                         "/etc/file-to-dir/dir": {
399                                                 Path:      "/etc/file-to-dir/dir",
400                                                 Mode:      fs.ModeDir | 0755,
401                                                 Uid:       -1,
402                                                 Gid:       -1,
403                                                 OrigGroup: "group-a",
404                                         },
405                                         "/etc/file-to-dir/dir/file2": {
406                                                 Path:      "/etc/file-to-dir/dir/file2",
407                                                 Mode:      0644,
408                                                 Uid:       -1,
409                                                 Gid:       -1,
410                                                 Data:      []byte("file2: from group-a\n"),
411                                                 OrigGroup: "group-a",
412                                         },
413                                         "/etc/motd": {
414                                                 Path:      "/etc/motd",
415                                                 Mode:      0644,
416                                                 Uid:       -1,
417                                                 Gid:       -1,
418                                                 Data:      []byte("motd: from host1\n"),
419                                                 OrigGroup: "host1.example.org",
420                                         },
421                                 },
422                         },
423                         []string{
424                                 "host1.example.org: <nil> 3 host groups: all group-a group-b host1.example.org",
425                                 "host1.example.org: <nil> 3 host group priorities (descending): host1.example.org group-a group-b all",
426                                 `host1.example.org: <nil> 4 files: "/etc": group group-a overwrites triggers from group group-b`,
427                                 `host1.example.org: <nil> 4 files: "/etc": group host1.example.org overwrites triggers from group group-a`,
428                         },
429                         nil,
430                 },
431
432                 {
433                         "group_priority (single group)",
434                         "project-group_priority-single",
435                         "host1.example.org",
436                         nil,
437                         safcm.LogDebug3,
438                         safcm.MsgSyncReq{
439                                 Groups: []string{"all", "group-b", "group-a", "host1.example.org"},
440                                 Files: map[string]*safcm.File{
441                                         "/": {
442                                                 Path:      "/",
443                                                 Mode:      fs.ModeDir | 0755,
444                                                 Uid:       -1,
445                                                 Gid:       -1,
446                                                 OrigGroup: "group-a",
447                                         },
448                                         "/file.txt": {
449                                                 Path:      "/file.txt",
450                                                 Mode:      0644,
451                                                 Uid:       -1,
452                                                 Gid:       -1,
453                                                 Data:      []byte("file.txt: from group-a\n"),
454                                                 OrigGroup: "group-a",
455                                         },
456                                 },
457                         },
458                         []string{
459                                 "host1.example.org: <nil> 3 host groups: all group-a group-b host1.example.org",
460                                 "host1.example.org: <nil> 3 host group priorities (descending): host1.example.org group-a",
461                         },
462                         nil,
463                 },
464         }
465
466         for _, tc := range tests {
467                 t.Run(tc.name, func(t *testing.T) {
468                         err = os.Chdir(filepath.Join(cwd,
469                                 "testdata", tc.project))
470                         if err != nil {
471                                 t.Fatal(err)
472                         }
473
474                         // `safcm fixperms` in case user has strict umask
475                         log.SetOutput(io.Discard)
476                         err := MainFixperms()
477                         if err != nil {
478                                 t.Fatal(err)
479                         }
480                         log.SetOutput(os.Stderr)
481
482                         cfg, allHosts, allGroups, err := LoadBaseFiles()
483                         if err != nil {
484                                 t.Fatal(err)
485                         }
486                         cfg.LogLevel = tc.level
487
488                         var events []string
489                         ch := make(chan Event)
490                         done := make(chan struct{})
491                         go func() {
492                                 for {
493                                         x, ok := <-ch
494                                         if !ok {
495                                                 break
496                                         }
497                                         if x.ConnEvent.Type != 0 {
498                                                 panic("unexpected ConnEvent")
499                                         }
500                                         events = append(events,
501                                                 fmt.Sprintf("%s: %v %d %s",
502                                                         x.Host.Name,
503                                                         x.Error, x.Log.Level,
504                                                         x.Log.Text))
505                                 }
506                                 done <- struct{}{}
507                         }()
508
509                         s := &Sync{
510                                 host:      allHosts.Map[tc.host],
511                                 config:    cfg,
512                                 allHosts:  allHosts,
513                                 allGroups: allGroups,
514                                 events:    ch,
515                         }
516
517                         res, err := s.hostSyncReq(tc.detected)
518
519                         testutil.AssertEqual(t, "res", res, tc.exp)
520                         testutil.AssertErrorEqual(t, "err", err, tc.expErr)
521
522                         close(ch)
523                         <-done
524                         testutil.AssertEqual(t, "events",
525                                 events, tc.expEvents)
526                 })
527         }
528 }