// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package dev_tools // This file contains tests that can be run on the generated packages. // To run these tests use `go test package_test.go`. import ( "archive/tar" "archive/zip" "bytes" "compress/gzip" "flag" "fmt" "io" "os" "path/filepath" "regexp" "strings" "testing" "github.com/blakesmith/ar" "github.com/cavaliercoder/go-rpm" ) const ( expectedConfigMode = os.FileMode(0600) expectedManifestMode = os.FileMode(0644) expectedModuleFileMode = expectedManifestMode expectedModuleDirMode = os.FileMode(0755) expectedConfigUID = 0 expectedConfigGID = 0 ) var ( configFilePattern = regexp.MustCompile(`.*beat\.yml|apm-server\.yml`) manifestFilePattern = regexp.MustCompile(`manifest.yml`) modulesDirPattern = regexp.MustCompile(`module/.+`) modulesDDirPattern = regexp.MustCompile(`modules.d/$`) modulesDFilePattern = regexp.MustCompile(`modules.d/.+`) systemdUnitFilePattern = regexp.MustCompile(`/lib/systemd/system/.*\.service`) ) var ( files = flag.String("files", "../build/distributions/*/*", "filepath glob containing package files") modules = flag.Bool("modules", false, "check modules folder contents") modulesd = flag.Bool("modules.d", false, "check modules.d folder contents") ) func TestRPM(t *testing.T) { rpms := getFiles(t, regexp.MustCompile(`\.rpm$`)) for _, rpm := range rpms { checkRPM(t, rpm) } } func TestDeb(t *testing.T) { debs := getFiles(t, regexp.MustCompile(`\.deb$`)) buf := new(bytes.Buffer) for _, deb := range debs { checkDeb(t, deb, buf) } } func TestTar(t *testing.T) { tars := getFiles(t, regexp.MustCompile(`\.tar\.gz$`)) for _, tar := range tars { checkTar(t, tar) } } func TestZip(t *testing.T) { zips := getFiles(t, regexp.MustCompile(`^\w+beat-\S+.zip$`)) for _, zip := range zips { checkZip(t, zip) } } // Sub-tests func checkRPM(t *testing.T, file string) { p, err := readRPM(file) if err != nil { t.Error(err) return } checkConfigPermissions(t, p) checkConfigOwner(t, p) checkManifestPermissions(t, p) checkManifestOwner(t, p) checkModulesPermissions(t, p) checkModulesPresent(t, "/usr/share", p) checkModulesDPresent(t, "/etc/", p) checkModulesOwner(t, p) checkSystemdUnitPermissions(t, p) } func checkDeb(t *testing.T, file string, buf *bytes.Buffer) { p, err := readDeb(file, buf) if err != nil { t.Error(err) return } checkConfigPermissions(t, p) checkConfigOwner(t, p) checkManifestPermissions(t, p) checkManifestOwner(t, p) checkModulesPresent(t, "./usr/share", p) checkModulesDPresent(t, "./etc/", p) checkModulesPermissions(t, p) checkModulesOwner(t, p) checkSystemdUnitPermissions(t, p) } func checkTar(t *testing.T, file string) { p, err := readTar(file) if err != nil { t.Error(err) return } checkConfigPermissions(t, p) checkConfigOwner(t, p) checkManifestPermissions(t, p) checkModulesPresent(t, "", p) checkModulesDPresent(t, "", p) checkModulesPermissions(t, p) checkModulesOwner(t, p) } func checkZip(t *testing.T, file string) { p, err := readZip(file) if err != nil { t.Error(err) return } checkConfigPermissions(t, p) checkManifestPermissions(t, p) checkModulesPresent(t, "", p) checkModulesDPresent(t, "", p) checkModulesPermissions(t, p) } // Verify that the main configuration file is installed with a 0600 file mode. func checkConfigPermissions(t *testing.T, p *packageFile) { t.Run(p.Name+" config file permissions", func(t *testing.T) { for _, entry := range p.Contents { if configFilePattern.MatchString(entry.File) { mode := entry.Mode.Perm() if expectedConfigMode != mode { t.Errorf("file %v has wrong permissions: expected=%v actual=%v", entry.File, expectedConfigMode, mode) } return } } t.Errorf("no config file found matching %v", configFilePattern) }) } func checkConfigOwner(t *testing.T, p *packageFile) { t.Run(p.Name+" config file owner", func(t *testing.T) { for _, entry := range p.Contents { if configFilePattern.MatchString(entry.File) { if expectedConfigUID != entry.UID { t.Errorf("file %v should be owned by user %v, owner=%v", entry.File, expectedConfigGID, entry.UID) } if expectedConfigGID != entry.GID { t.Errorf("file %v should be owned by group %v, group=%v", entry.File, expectedConfigGID, entry.GID) } return } } t.Errorf("no config file found matching %v", configFilePattern) }) } // Verify that the modules manifest.yml files are installed with a 0644 file mode. func checkManifestPermissions(t *testing.T, p *packageFile) { t.Run(p.Name+" manifest file permissions", func(t *testing.T) { for _, entry := range p.Contents { if manifestFilePattern.MatchString(entry.File) { mode := entry.Mode.Perm() if expectedManifestMode != mode { t.Errorf("file %v has wrong permissions: expected=%v actual=%v", entry.File, expectedManifestMode, mode) } } } }) } // Verify that the manifest owner is root func checkManifestOwner(t *testing.T, p *packageFile) { t.Run(p.Name+" manifest file owner", func(t *testing.T) { for _, entry := range p.Contents { if manifestFilePattern.MatchString(entry.File) { if expectedConfigUID != entry.UID { t.Errorf("file %v should be owned by user %v, owner=%v", entry.File, expectedConfigGID, entry.UID) } if expectedConfigGID != entry.GID { t.Errorf("file %v should be owned by group %v, group=%v", entry.File, expectedConfigGID, entry.GID) } } } }) } // Verify the permissions of the modules.d dir and its contents. func checkModulesPermissions(t *testing.T, p *packageFile) { t.Run(p.Name+" modules.d file permissions", func(t *testing.T) { for _, entry := range p.Contents { if modulesDFilePattern.MatchString(entry.File) { mode := entry.Mode.Perm() if expectedModuleFileMode != mode { t.Errorf("file %v has wrong permissions: expected=%v actual=%v", entry.File, expectedModuleFileMode, mode) } } else if modulesDDirPattern.MatchString(entry.File) { mode := entry.Mode.Perm() if expectedModuleDirMode != mode { t.Errorf("file %v has wrong permissions: expected=%v actual=%v", entry.File, expectedModuleDirMode, mode) } } } }) } // Verify the owner of the modules.d dir and its contents. func checkModulesOwner(t *testing.T, p *packageFile) { t.Run(p.Name+" modules.d file owner", func(t *testing.T) { for _, entry := range p.Contents { if modulesDFilePattern.MatchString(entry.File) || modulesDDirPattern.MatchString(entry.File) { if expectedConfigUID != entry.UID { t.Errorf("file %v should be owned by user %v, owner=%v", entry.File, expectedConfigGID, entry.UID) } if expectedConfigGID != entry.GID { t.Errorf("file %v should be owned by group %v, group=%v", entry.File, expectedConfigGID, entry.GID) } } } }) } // Verify that modules folder is present and has module files in func checkModulesPresent(t *testing.T, prefix string, p *packageFile) { if *modules { checkModules(t, "modules", prefix, modulesDirPattern, p) } } // Verify that modules.d folder is present and has module files in func checkModulesDPresent(t *testing.T, prefix string, p *packageFile) { if *modulesd { checkModules(t, "modules.d", prefix, modulesDFilePattern, p) } } func checkModules(t *testing.T, name, prefix string, r *regexp.Regexp, p *packageFile) { t.Run(fmt.Sprintf("%s %s contents", p.Name, name), func(t *testing.T) { minExpectedModules := 4 total := 0 for _, entry := range p.Contents { if strings.HasPrefix(entry.File, prefix) && r.MatchString(entry.File) { total++ } } if total < minExpectedModules { t.Errorf("not enough modules found under %s: actual=%d, expected>=%d", name, total, minExpectedModules) } }) } // Verify that the systemd unit file has a mode of 0644. It should not be // executable. func checkSystemdUnitPermissions(t *testing.T, p *packageFile) { const expectedMode = os.FileMode(0644) t.Run(p.Name+" systemd unit file permissions", func(t *testing.T) { for _, entry := range p.Contents { if systemdUnitFilePattern.MatchString(entry.File) { mode := entry.Mode.Perm() if expectedMode != mode { t.Errorf("file %v has wrong permissions: expected=%v actual=%v", entry.File, expectedMode, mode) } return } } t.Errorf("no systemd unit file found matching %v", configFilePattern) }) } // Helpers type packageFile struct { Name string Contents map[string]packageEntry } type packageEntry struct { File string UID int GID int Mode os.FileMode } func getFiles(t *testing.T, pattern *regexp.Regexp) []string { matches, err := filepath.Glob(*files) if err != nil { t.Fatal(err) } files := matches[:0] for _, f := range matches { if pattern.MatchString(filepath.Base(f)) { files = append(files, f) } } return files } func readRPM(rpmFile string) (*packageFile, error) { p, err := rpm.OpenPackageFile(rpmFile) if err != nil { return nil, err } contents := p.Files() pf := &packageFile{Name: filepath.Base(rpmFile), Contents: map[string]packageEntry{}} for _, file := range contents { pf.Contents[file.Name()] = packageEntry{ File: file.Name(), Mode: file.Mode(), } } return pf, nil } // readDeb reads the data.tar.gz file from the .deb. func readDeb(debFile string, dataBuffer *bytes.Buffer) (*packageFile, error) { file, err := os.Open(debFile) if err != nil { return nil, err } defer file.Close() arReader := ar.NewReader(file) for { header, err := arReader.Next() if err != nil { if err == io.EOF { break } return nil, err } if strings.HasPrefix(header.Name, "data.tar.gz") { dataBuffer.Reset() _, err := io.Copy(dataBuffer, arReader) if err != nil { return nil, err } gz, err := gzip.NewReader(dataBuffer) if err != nil { return nil, err } defer gz.Close() return readTarContents(filepath.Base(debFile), gz) } } return nil, io.EOF } func readTar(tarFile string) (*packageFile, error) { file, err := os.Open(tarFile) if err != nil { return nil, err } defer file.Close() var fileReader io.ReadCloser = file if strings.HasSuffix(tarFile, ".gz") { if fileReader, err = gzip.NewReader(file); err != nil { return nil, err } defer fileReader.Close() } return readTarContents(filepath.Base(tarFile), fileReader) } func readTarContents(tarName string, data io.Reader) (*packageFile, error) { tarReader := tar.NewReader(data) p := &packageFile{Name: tarName, Contents: map[string]packageEntry{}} for { header, err := tarReader.Next() if err != nil { if err == io.EOF { break } return nil, err } p.Contents[header.Name] = packageEntry{ File: header.Name, UID: header.Uid, GID: header.Gid, Mode: os.FileMode(header.Mode), } } return p, nil } func readZip(zipFile string) (*packageFile, error) { r, err := zip.OpenReader(zipFile) if err != nil { return nil, err } defer r.Close() p := &packageFile{Name: filepath.Base(zipFile), Contents: map[string]packageEntry{}} for _, f := range r.File { p.Contents[f.Name] = packageEntry{ File: f.Name, Mode: f.Mode(), } } return p, nil }