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