add rego integration to source policies
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
+261
@@ -0,0 +1,261 @@
|
||||
package gitobject
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1" //nolint:gosec // used for git object hashes
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type GitObject struct {
|
||||
Type string
|
||||
Headers map[string][]string
|
||||
Message string
|
||||
Signature string
|
||||
SignedData string
|
||||
Raw []byte
|
||||
}
|
||||
|
||||
type Actor struct {
|
||||
Name string
|
||||
Email string
|
||||
When *time.Time
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
Tree string
|
||||
Parents []string
|
||||
Author Actor
|
||||
Committer Actor
|
||||
Message string
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
Object string
|
||||
Type string
|
||||
Tag string
|
||||
Tagger Actor
|
||||
Message string
|
||||
}
|
||||
|
||||
func Parse(raw []byte) (*GitObject, error) {
|
||||
obj := &GitObject{Headers: make(map[string][]string), Raw: raw}
|
||||
lines := strings.Split(string(raw), "\n")
|
||||
if len(lines) == 0 {
|
||||
return nil, errors.Errorf("invalid empty git object")
|
||||
}
|
||||
|
||||
isTag := bytes.HasPrefix(raw, []byte("object "))
|
||||
if isTag {
|
||||
obj.Type = "tag"
|
||||
} else {
|
||||
obj.Type = "commit"
|
||||
}
|
||||
|
||||
var headersDone bool
|
||||
var messageDone bool
|
||||
var sigLines []string
|
||||
var messageLines []string
|
||||
var signedDataLines []string
|
||||
inSig := false
|
||||
|
||||
for _, l := range lines {
|
||||
if !headersDone {
|
||||
if l == "" {
|
||||
headersDone = true
|
||||
signedDataLines = append(signedDataLines, l)
|
||||
continue
|
||||
}
|
||||
if !isTag && strings.HasPrefix(l, "gpgsig ") {
|
||||
inSig = true
|
||||
sigLines = append(sigLines, strings.TrimPrefix(l, "gpgsig "))
|
||||
continue
|
||||
}
|
||||
if !isTag && strings.HasPrefix(l, "gpgsig-sha256 ") {
|
||||
inSig = true
|
||||
sigLines = append(sigLines, strings.TrimPrefix(l, "gpgsig-sha256 "))
|
||||
continue
|
||||
}
|
||||
if inSig {
|
||||
if v, ok := strings.CutPrefix(l, " "); ok {
|
||||
sigLines = append(sigLines, v)
|
||||
continue
|
||||
} else {
|
||||
inSig = false
|
||||
}
|
||||
}
|
||||
signedDataLines = append(signedDataLines, l)
|
||||
parts := strings.SplitN(l, " ", 2)
|
||||
if len(parts) == 2 {
|
||||
obj.Headers[parts[0]] = append(obj.Headers[parts[0]], parts[1])
|
||||
}
|
||||
continue
|
||||
}
|
||||
if isTag && (l == "-----BEGIN PGP SIGNATURE-----" || l == "-----BEGIN SSH SIGNATURE-----") {
|
||||
messageDone = true
|
||||
}
|
||||
if messageDone {
|
||||
sigLines = append(sigLines, l)
|
||||
} else {
|
||||
messageLines = append(messageLines, l)
|
||||
signedDataLines = append(signedDataLines, l)
|
||||
}
|
||||
}
|
||||
|
||||
obj.Message = strings.Join(messageLines, "\n")
|
||||
obj.Message = strings.TrimSuffix(obj.Message, "\n") // body ends with newline but no extra newline between message and signature
|
||||
if len(sigLines) > 0 {
|
||||
obj.Signature = strings.Join(sigLines, "\n")
|
||||
}
|
||||
|
||||
obj.SignedData = strings.Join(signedDataLines, "\n")
|
||||
if isTag {
|
||||
obj.SignedData += "\n"
|
||||
}
|
||||
|
||||
// basic validation
|
||||
requiredHeaders := []string{}
|
||||
switch obj.Type {
|
||||
case "commit":
|
||||
requiredHeaders = append(requiredHeaders, "tree", "author", "committer")
|
||||
case "tag":
|
||||
requiredHeaders = append(requiredHeaders, "object", "type", "tag", "tagger")
|
||||
}
|
||||
|
||||
for _, header := range requiredHeaders {
|
||||
if _, ok := obj.Headers[header]; !ok {
|
||||
return nil, errors.Errorf("invalid %s object: missing %s header", obj.Type, header)
|
||||
}
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (obj *GitObject) Checksum(hashFunc func() hash.Hash) ([]byte, error) {
|
||||
h := hashFunc()
|
||||
header := fmt.Sprintf("commit %d\u0000", len(obj.Raw))
|
||||
if obj.Type == "tag" {
|
||||
header = fmt.Sprintf("tag %d\u0000", len(obj.Raw))
|
||||
}
|
||||
data := append([]byte(header), obj.Raw...)
|
||||
h.Write(data)
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
func (obj *GitObject) VerifyChecksum(sha string) error {
|
||||
var hf func() hash.Hash
|
||||
switch len(sha) {
|
||||
case 40:
|
||||
hf = sha1.New
|
||||
case 64:
|
||||
hf = sha256.New
|
||||
default:
|
||||
return errors.Errorf("unsupported sha length %d", len(sha))
|
||||
}
|
||||
sum, err := obj.Checksum(hf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hexValue := hex.EncodeToString(sum); sha != hexValue {
|
||||
return errors.Errorf("checksum mismatch: expected %s, got %s", sha, hexValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *GitObject) ToCommit() (*Commit, error) {
|
||||
if obj.Type != "commit" {
|
||||
return nil, errors.Errorf("not a commit object")
|
||||
}
|
||||
c := &Commit{}
|
||||
if trees, ok := obj.Headers["tree"]; ok && len(trees) > 0 {
|
||||
c.Tree = trees[0]
|
||||
}
|
||||
if parents, ok := obj.Headers["parent"]; ok && len(parents) > 0 {
|
||||
c.Parents = parents
|
||||
}
|
||||
if authors, ok := obj.Headers["author"]; ok && len(authors) > 0 {
|
||||
c.Author = parseActor(authors[0])
|
||||
}
|
||||
if committers, ok := obj.Headers["committer"]; ok && len(committers) > 0 {
|
||||
c.Committer = parseActor(committers[0])
|
||||
}
|
||||
c.Message = obj.Message
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (obj *GitObject) ToTag() (*Tag, error) {
|
||||
if obj.Type != "tag" {
|
||||
return nil, errors.Errorf("not a tag object")
|
||||
}
|
||||
t := &Tag{}
|
||||
if objects, ok := obj.Headers["object"]; ok && len(objects) > 0 {
|
||||
t.Object = objects[0]
|
||||
}
|
||||
if types, ok := obj.Headers["type"]; ok && len(types) > 0 {
|
||||
t.Type = types[0]
|
||||
}
|
||||
if tags, ok := obj.Headers["tag"]; ok && len(tags) > 0 {
|
||||
t.Tag = tags[0]
|
||||
}
|
||||
if taggers, ok := obj.Headers["tagger"]; ok && len(taggers) > 0 {
|
||||
t.Tagger = parseActor(taggers[0])
|
||||
}
|
||||
t.Message = obj.Message
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func parseActor(s string) Actor {
|
||||
s = strings.TrimSpace(s)
|
||||
var a Actor
|
||||
|
||||
// find last angle brackets, because name can contain '<'
|
||||
start := strings.LastIndex(s, "<")
|
||||
end := strings.LastIndex(s, ">")
|
||||
if start == -1 || end == -1 || end < start {
|
||||
// malformed, treat as plain name
|
||||
a.Name = s
|
||||
return a
|
||||
}
|
||||
|
||||
a.Name = strings.TrimSpace(s[:start])
|
||||
a.Email = strings.TrimSpace(s[start+1 : end])
|
||||
|
||||
rest := strings.TrimSpace(s[end+1:])
|
||||
if rest == "" {
|
||||
return a
|
||||
}
|
||||
|
||||
parts := strings.Fields(rest)
|
||||
if len(parts) < 2 {
|
||||
return a
|
||||
}
|
||||
|
||||
unix, err := strconv.ParseInt(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
return a
|
||||
}
|
||||
|
||||
tz := parts[1]
|
||||
if len(tz) != 5 || (tz[0] != '+' && tz[0] != '-') {
|
||||
return a
|
||||
}
|
||||
|
||||
sign := 1
|
||||
if tz[0] == '-' {
|
||||
sign = -1
|
||||
}
|
||||
hh, _ := strconv.Atoi(tz[1:3])
|
||||
mm, _ := strconv.Atoi(tz[3:5])
|
||||
offset := sign * (hh*3600 + mm*60)
|
||||
|
||||
t := time.Unix(unix, 0).In(time.FixedZone("", offset))
|
||||
a.When = &t
|
||||
return a
|
||||
}
|
||||
Reference in New Issue
Block a user