460 lines
12 KiB
Go
460 lines
12 KiB
Go
|
// 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
|
||
|
}
|