pkg/rule_file_hash.go (86 lines of code) (raw):
package pkg
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"github.com/Azure/golden"
"github.com/spf13/afero"
)
var _ Rule = &FileHashRule{}
type FileHashRule struct {
*golden.BaseBlock
*BaseRule
Glob string `hcl:"glob"`
Hash string `hcl:"hash"`
Algorithm string `hcl:"algorithm,optional" default:"sha1"`
FailOnHashMismatch bool `hcl:"fail_on_hash_mismatch,optional"`
HashMismatchFiles []string `attribute:"hash_mismatch_files"`
}
func (fhr *FileHashRule) Type() string {
return "file_hash"
}
func (fhr *FileHashRule) ExecuteDuringPlan() error {
// Use Glob to find files matching the path pattern
fs := FsFactory()
files, err := afero.Glob(fs, fhr.Glob)
if err != nil {
return err
}
if len(files) == 0 {
fhr.setCheckError(fmt.Errorf("no files match path pattern: %s", fhr.Glob))
return nil
}
matchFound := false
for _, file := range files {
fileData, err := afero.ReadFile(fs, file)
if err != nil {
return err
}
// Calculate the hash of the file data
var h hash.Hash
switch fhr.Algorithm {
case "md5":
h = md5.New()
case "sha256":
h = sha256.New()
case "sha512":
h = sha512.New()
case "sha1":
fallthrough
default: // Default to sha1
h = sha1.New()
}
h.Write(fileData)
computedHash := fmt.Sprintf("%x", h.Sum(nil))
if computedHash == fhr.Hash {
matchFound = true
continue
}
fhr.HashMismatchFiles = append(fhr.HashMismatchFiles, file)
}
if !fhr.FailOnHashMismatch && matchFound {
return nil
}
if len(fhr.HashMismatchFiles) == 0 {
return nil
}
fhr.setCheckError(fmt.Errorf("file with glob %s and different hash than %s found", fhr.Glob, fhr.Hash))
return nil
}
func (fhr *FileHashRule) Validate() error {
if fhr.Glob == "" {
return fmt.Errorf("glob is required")
}
if fhr.Hash == "" {
return fmt.Errorf("hash is required")
}
if fhr.Algorithm != "" {
switch fhr.Algorithm {
case "md5", "sha1", "sha256", "sha512":
// valid
default:
return fmt.Errorf("invalid algorithm: %s", fhr.Algorithm)
}
}
return nil
}