mirror of
https://github.com/fleetdm/fleet
synced 2026-05-06 06:48:54 +00:00
492 lines
12 KiB
Go
492 lines
12 KiB
Go
package oval
|
|
|
|
import (
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
oval_input "github.com/fleetdm/fleet/v4/server/vulnerabilities/oval/input"
|
|
oval_parsed "github.com/fleetdm/fleet/v4/server/vulnerabilities/oval/parsed"
|
|
)
|
|
|
|
func parseDefinitions(platform Platform, inputFile string, outputFile string) error {
|
|
r, err := os.Open(inputFile)
|
|
if err != nil {
|
|
return fmt.Errorf("oval parser: %w", err)
|
|
}
|
|
defer r.Close()
|
|
|
|
var payload []byte
|
|
switch {
|
|
case platform.IsUbuntu():
|
|
payload, err = processUbuntuDef(r)
|
|
case platform.IsRedHat():
|
|
payload, err = processRhelDef(r)
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("oval parser: %w", err)
|
|
}
|
|
|
|
err = os.WriteFile(outputFile, payload, 0o644)
|
|
if err != nil {
|
|
return fmt.Errorf("oval parser: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// -----------------
|
|
// RHEL
|
|
// -----------------
|
|
func processRhelDef(r io.Reader) ([]byte, error) {
|
|
xmlResult, err := parseRhelXML(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result, err := mapToRhelResult(xmlResult)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
payload, err := json.Marshal(result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return payload, nil
|
|
}
|
|
|
|
func parseRhelXML(reader io.Reader) (*oval_input.RhelResultXML, error) {
|
|
r := &oval_input.RhelResultXML{
|
|
Variables: make(map[string]oval_input.ConstantVariableXML),
|
|
}
|
|
d := xml.NewDecoder(reader)
|
|
|
|
for {
|
|
t, err := d.Token()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
return r, nil
|
|
}
|
|
return nil, fmt.Errorf("decoding token: %v", err)
|
|
}
|
|
|
|
if t, ok := t.(xml.StartElement); ok {
|
|
if t.Name.Local == "definition" {
|
|
def := oval_input.DefinitionXML{}
|
|
if err = d.DecodeElement(&def, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
r.Definitions = append(r.Definitions, def)
|
|
}
|
|
if t.Name.Local == "rpminfo_test" {
|
|
tst := oval_input.RpmInfoTestXML{}
|
|
if err = d.DecodeElement(&tst, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
r.RpmInfoTests = append(r.RpmInfoTests, tst)
|
|
}
|
|
if t.Name.Local == "rpmverifyfile_test" {
|
|
tst := oval_input.RpmVerifyFileTestXML{}
|
|
if err = d.DecodeElement(&tst, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
r.RpmVerifyFileTests = append(r.RpmVerifyFileTests, tst)
|
|
}
|
|
if t.Name.Local == "rpminfo_object" {
|
|
sta := oval_input.PackageInfoTestObjectXML{}
|
|
if err = d.DecodeElement(&sta, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
r.RpmInfoTestObjects = append(r.RpmInfoTestObjects, sta)
|
|
}
|
|
if t.Name.Local == "rpminfo_state" {
|
|
obj := oval_input.RpmInfoStateXML{}
|
|
if err = d.DecodeElement(&obj, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
r.RpmInfoTestStates = append(r.RpmInfoTestStates, obj)
|
|
}
|
|
if t.Name.Local == "rpmverifyfile_object" {
|
|
obj := oval_input.RpmVerifyFileObjectXML{}
|
|
if err = d.DecodeElement(&obj, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
r.RpmVerifyFileObjects = append(r.RpmVerifyFileObjects, obj)
|
|
}
|
|
if t.Name.Local == "rpmverifyfile_state" {
|
|
sta := oval_input.RpmVerifyFileStateXML{}
|
|
if err = d.DecodeElement(&sta, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
r.RpmVerifyFileStates = append(r.RpmVerifyFileStates, sta)
|
|
}
|
|
if t.Name.Local == "constant_variable" {
|
|
cVar := oval_input.ConstantVariableXML{}
|
|
if err = d.DecodeElement(&cVar, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
r.Variables[cVar.Id] = cVar
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func mapToRhelResult(xmlResult *oval_input.RhelResultXML) (*oval_parsed.RhelResult, error) {
|
|
r := oval_parsed.NewRhelResult()
|
|
|
|
rpmInfoObjToTst := make(map[string][]int)
|
|
rpmInfoStaToTst := make(map[string][]int)
|
|
|
|
rpmVerifyObjToTst := make(map[string][]int)
|
|
rpmVerifyStaToTst := make(map[string][]int)
|
|
|
|
for _, d := range xmlResult.Definitions {
|
|
if len(d.Vulnerabilities) > 0 {
|
|
def, err := mapDefinition(d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r.Definitions = append(r.Definitions, *def)
|
|
}
|
|
}
|
|
|
|
// ------------
|
|
// RpmInfoTests
|
|
// ------------
|
|
for _, t := range xmlResult.RpmInfoTests {
|
|
id, tst, err := mapRpmInfoTest(t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rpmInfoObjToTst[t.Object.Id] = append(rpmInfoObjToTst[t.Object.Id], id)
|
|
for _, sta := range t.States {
|
|
rpmInfoStaToTst[sta.Id] = append(rpmInfoStaToTst[sta.Id], id)
|
|
}
|
|
r.RpmInfoTests[id] = tst
|
|
}
|
|
for _, o := range xmlResult.RpmInfoTestObjects {
|
|
obj, err := mapPackageInfoTestObject(o, xmlResult.Variables)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, tId := range rpmInfoObjToTst[o.Id] {
|
|
t, ok := r.RpmInfoTests[tId]
|
|
if ok {
|
|
t.Objects = obj
|
|
} else {
|
|
return nil, fmt.Errorf("test not found: %d", tId)
|
|
}
|
|
}
|
|
}
|
|
for _, s := range xmlResult.RpmInfoTestStates {
|
|
sta, err := mapRpmInfoState(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, tId := range rpmInfoStaToTst[s.Id] {
|
|
t, ok := r.RpmInfoTests[tId]
|
|
if ok {
|
|
t.States = append(t.States, *sta)
|
|
} else {
|
|
return nil, fmt.Errorf("test not found: %d", tId)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------
|
|
// RpmVerifyFileTests
|
|
// ------------------
|
|
for _, t := range xmlResult.RpmVerifyFileTests {
|
|
id, tst, err := mapRpmVerifyFileTest(t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rpmVerifyObjToTst[t.Object.Id] = append(rpmVerifyObjToTst[t.Object.Id], id)
|
|
for _, sta := range t.States {
|
|
rpmVerifyStaToTst[sta.Id] = append(rpmVerifyStaToTst[sta.Id], id)
|
|
}
|
|
r.RpmVerifyFileTests[id] = tst
|
|
}
|
|
for _, o := range xmlResult.RpmVerifyFileObjects {
|
|
obj, err := mapRpmVerifyFileObject(o)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, tId := range rpmVerifyObjToTst[o.Id] {
|
|
t, ok := r.RpmVerifyFileTests[tId]
|
|
if ok {
|
|
t.FilePath = *obj
|
|
} else {
|
|
return nil, fmt.Errorf("test not found: %d", tId)
|
|
}
|
|
}
|
|
}
|
|
for _, s := range xmlResult.RpmVerifyFileStates {
|
|
sta, err := mapRpmVerifyFileState(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, tId := range rpmVerifyStaToTst[s.Id] {
|
|
t, ok := r.RpmVerifyFileTests[tId]
|
|
if ok {
|
|
t.State = *sta
|
|
} else {
|
|
return nil, fmt.Errorf("test not found: %d", tId)
|
|
}
|
|
}
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// -----------------
|
|
// Ubuntu
|
|
// -----------------
|
|
|
|
func processUbuntuDef(r io.Reader) ([]byte, error) {
|
|
xmlResult, err := parseUbuntuXML(r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing ubuntu xml: %w", err)
|
|
}
|
|
|
|
result, err := mapToUbuntuResult(xmlResult)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mapping ubuntu result: %w", err)
|
|
}
|
|
|
|
payload, err := json.Marshal(result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshalling ubuntu result: %w", err)
|
|
}
|
|
|
|
return payload, nil
|
|
}
|
|
|
|
func parseUbuntuXML(reader io.Reader) (*oval_input.UbuntuResultXML, error) {
|
|
r := &oval_input.UbuntuResultXML{
|
|
Variables: make(map[string]oval_input.ConstantVariableXML),
|
|
VariableObjects: make(map[int]oval_input.VariableObjectXML),
|
|
}
|
|
d := xml.NewDecoder(reader)
|
|
|
|
for {
|
|
t, err := d.Token()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
return r, nil
|
|
}
|
|
return nil, fmt.Errorf("decoding token: %v", err)
|
|
}
|
|
|
|
if t, ok := t.(xml.StartElement); ok {
|
|
if t.Name.Local == "definition" {
|
|
def := oval_input.DefinitionXML{}
|
|
if err = d.DecodeElement(&def, &t); err != nil {
|
|
return nil, fmt.Errorf("decoding definition: %w", err)
|
|
}
|
|
r.Definitions = append(r.Definitions, def)
|
|
}
|
|
if t.Name.Local == "dpkginfo_test" {
|
|
tst := oval_input.DpkgInfoTestXML{}
|
|
if err = d.DecodeElement(&tst, &t); err != nil {
|
|
return nil, fmt.Errorf("decoding dpkginfo_test: %w", err)
|
|
}
|
|
r.DpkgInfoTests = append(r.DpkgInfoTests, tst)
|
|
}
|
|
if t.Name.Local == "dpkginfo_state" {
|
|
sta := oval_input.DpkgInfoStateXML{}
|
|
if err = d.DecodeElement(&sta, &t); err != nil {
|
|
return nil, fmt.Errorf("decoding dpkginfo_state: %w", err)
|
|
}
|
|
r.DpkgInfoStates = append(r.DpkgInfoStates, sta)
|
|
}
|
|
if t.Name.Local == "dpkginfo_object" {
|
|
obj := oval_input.PackageInfoTestObjectXML{}
|
|
if err = d.DecodeElement(&obj, &t); err != nil {
|
|
return nil, fmt.Errorf("decoding dpkginfo_object: %w", err)
|
|
}
|
|
r.DpkgInfoObjects = append(r.DpkgInfoObjects, obj)
|
|
}
|
|
if t.Name.Local == "uname_test" {
|
|
tst := oval_input.UnixUnameTestXML{}
|
|
if err = d.DecodeElement(&tst, &t); err != nil {
|
|
return nil, fmt.Errorf("decoding uname_test: %w", err)
|
|
}
|
|
r.UnameTests = append(r.UnameTests, tst)
|
|
}
|
|
if t.Name.Local == "uname_state" {
|
|
sta := oval_input.UnixUnameStateXML{}
|
|
if err = d.DecodeElement(&sta, &t); err != nil {
|
|
return nil, fmt.Errorf("decoding uname_state: %w", err)
|
|
}
|
|
r.UnameStates = append(r.UnameStates, sta)
|
|
}
|
|
if t.Name.Local == "variable_test" {
|
|
tst := oval_input.VariableTestXML{}
|
|
if err = d.DecodeElement(&tst, &t); err != nil {
|
|
return nil, fmt.Errorf("decoding variable_test: %w", err)
|
|
}
|
|
r.VariableTests = append(r.VariableTests, tst)
|
|
}
|
|
if t.Name.Local == "variable_object" {
|
|
obj := oval_input.VariableObjectXML{}
|
|
if err = d.DecodeElement(&obj, &t); err != nil {
|
|
return nil, fmt.Errorf("decoding variable_object: %w", err)
|
|
}
|
|
|
|
id, err := extractId(obj.Id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("extracting id: %w", err)
|
|
}
|
|
r.VariableObjects[id] = obj
|
|
}
|
|
|
|
if t.Name.Local == "variable_state" {
|
|
sta := oval_input.VariableStateXML{}
|
|
if err = d.DecodeElement(&sta, &t); err != nil {
|
|
return nil, fmt.Errorf("decoding variable_state: %w", err)
|
|
}
|
|
r.VariableStates = append(r.VariableStates, sta)
|
|
}
|
|
if t.Name.Local == "constant_variable" {
|
|
cVar := oval_input.ConstantVariableXML{}
|
|
if err = d.DecodeElement(&cVar, &t); err != nil {
|
|
return nil, fmt.Errorf("decoding constant_variable: %w", err)
|
|
}
|
|
r.Variables[cVar.Id] = cVar
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func mapToUbuntuResult(xmlResult *oval_input.UbuntuResultXML) (*oval_parsed.UbuntuResult, error) {
|
|
r := oval_parsed.NewUbuntuResult()
|
|
|
|
staToTst := make(map[string][]int)
|
|
objToTst := make(map[string][]int)
|
|
ustaToTst := make(map[string][]int)
|
|
vstaToTst := make(map[string][]int)
|
|
|
|
for _, d := range xmlResult.Definitions {
|
|
if len(d.Vulnerabilities) > 0 {
|
|
def, err := mapDefinition(d)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mapping definition: %w", err)
|
|
}
|
|
r.AddDefinition(*def)
|
|
}
|
|
}
|
|
|
|
for _, t := range xmlResult.DpkgInfoTests {
|
|
id, tst, err := mapDpkgInfoTest(t)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mapping dpkg info test: %w", err)
|
|
}
|
|
|
|
objToTst[t.Object.Id] = append(objToTst[t.Object.Id], id)
|
|
for _, sta := range t.States {
|
|
staToTst[sta.Id] = append(staToTst[sta.Id], id)
|
|
}
|
|
r.AddPackageTest(id, tst)
|
|
}
|
|
|
|
for _, o := range xmlResult.DpkgInfoObjects {
|
|
obj, err := mapPackageInfoTestObject(o, xmlResult.Variables)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mapping dpkg info object: %w", err)
|
|
}
|
|
|
|
for _, tId := range objToTst[o.Id] {
|
|
t, ok := r.PackageTests[tId]
|
|
if ok {
|
|
t.Objects = obj
|
|
} else {
|
|
return nil, fmt.Errorf("test not found: %d", tId)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, s := range xmlResult.DpkgInfoStates {
|
|
sta, err := mapDpkgInfoState(s)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mapping dpkg info state: %w", err)
|
|
}
|
|
for _, tId := range staToTst[s.Id] {
|
|
t, ok := r.PackageTests[tId]
|
|
if ok {
|
|
t.States = append(t.States, *sta)
|
|
} else {
|
|
return nil, fmt.Errorf("test not found: %d", tId)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, t := range xmlResult.UnameTests {
|
|
id, tst, err := mapUnixUnameTest(t)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mapping uname test: %w", err)
|
|
}
|
|
|
|
for _, sta := range t.States {
|
|
ustaToTst[sta.Id] = append(ustaToTst[sta.Id], id)
|
|
}
|
|
r.AddUnameTest(id, tst)
|
|
}
|
|
|
|
for _, s := range xmlResult.UnameStates {
|
|
sta := mapUnameState(s)
|
|
for _, tId := range ustaToTst[s.Id] {
|
|
t, ok := r.UnameTests[tId]
|
|
if ok {
|
|
t.States = append(t.States, *sta)
|
|
} else {
|
|
return nil, fmt.Errorf("test not found: %d", tId)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, t := range xmlResult.VariableTests {
|
|
id, tst, err := mapVariableTest(t)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mapping variable test: %w", err)
|
|
}
|
|
|
|
// Skip tests that are not used in any uname test
|
|
// (there should be none)
|
|
if obj, ok := xmlResult.VariableObjects[id]; ok {
|
|
id, err := extractId(obj.RefID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("extracting variable object id: %w", err)
|
|
}
|
|
if _, ok := r.UnameTests[id]; !ok {
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, sta := range t.States {
|
|
vstaToTst[sta.Id] = append(vstaToTst[sta.Id], id)
|
|
}
|
|
r.AddUnameTest(id, tst)
|
|
}
|
|
|
|
for _, s := range xmlResult.VariableStates {
|
|
sta := mapVariableState(s)
|
|
for _, tId := range vstaToTst[s.Id] {
|
|
t, ok := r.UnameTests[tId]
|
|
if ok {
|
|
t.States = append(t.States, *sta)
|
|
} else {
|
|
return nil, fmt.Errorf("test not found: %d", tId)
|
|
}
|
|
}
|
|
}
|
|
|
|
return r, nil
|
|
}
|