diff --git a/orbit/changes/11244-cis-audit-table b/orbit/changes/11244-cis-audit-table new file mode 100644 index 0000000000..c65bc5bd05 --- /dev/null +++ b/orbit/changes/11244-cis-audit-table @@ -0,0 +1 @@ +* New table was added to support CIS audit process diff --git a/orbit/pkg/table/cis_audit/cis_audit_windows.go b/orbit/pkg/table/cis_audit/cis_audit_windows.go new file mode 100644 index 0000000000..02c3179a93 --- /dev/null +++ b/orbit/pkg/table/cis_audit/cis_audit_windows.go @@ -0,0 +1,720 @@ +//go:build windows +// +build windows + +package cisaudit + +import ( + "bytes" + "context" + "encoding/csv" + "fmt" + "io/ioutil" + "os" + "os/exec" + "os/user" + "path/filepath" + "strings" + "sync" + + "github.com/osquery/osquery-go/plugin/table" + "github.com/rs/zerolog/log" + "golang.org/x/sys/windows" + "golang.org/x/text/encoding/unicode" + "golang.org/x/text/transform" + "gopkg.in/ini.v1" +) + +var ( + // CIS items commands initialization + commandsInit sync.Once + + // Map to store command handlers + commandHandlers map[string]CommandHandler +) + +// CommandHandler is a function type that returns the value for a CIS item +type CommandHandler func() (string, error) + +// Audit items stores data from auditpol utility +type AuditItem struct { + Subcategory string + GUID string + NoAuditing bool + Success bool + Failure bool + Raw string +} + +// SeceditData stores data from secedit utility +type SeceditData struct { + Unicode struct { + Unicode bool + } + SystemAccess struct { + MinimumPasswordAge string + MaximumPasswordAge string + MinimumPasswordLength string + PasswordComplexity string + PasswordHistorySize string + LockoutBadCount string + ResetLockoutCount string + LockoutDuration string + RequireLogonToChangePassword string + ForceLogoffWhenHourExpire string + NewAdministratorName string + NewGuestName string + ClearTextPassword string + LSAAnonymousNameLookup string + EnableAdminAccount string + EnableGuestAccount string + } + EventAudit struct { + AuditSystemEvents string + AuditLogonEvents string + AuditObjectAccess string + AuditPrivilegeUse string + AuditPolicyChange string + AuditAccountManage string + AuditProcessTracking string + AuditDSAccess string + AuditAccountLogon string + } + PrivilegeRights struct { + SeNetworkLogonRight string + SeBackupPrivilege string + SeChangeNotifyPrivilege string + SeSystemtimePrivilege string + SeCreatePagefilePrivilege string + SeDebugPrivilege string + SeRemoteShutdownPrivilege string + SeAuditPrivilege string + SeIncreaseQuotaPrivilege string + SeIncreaseBasePriorityPrivilege string + SeLoadDriverPrivilege string + SeDenyBatchLogonRight string + SeDenyServiceLogonRight string + SeBatchLogonRight string + SeServiceLogonRight string + SeInteractiveLogonRight string + SeSecurityPrivilege string + SeSystemEnvironmentPrivilege string + SeProfileSingleProcessPrivilege string + SeSystemProfilePrivilege string + SeAssignPrimaryTokenPrivilege string + SeRestorePrivilege string + SeShutdownPrivilege string + SeTakeOwnershipPrivilege string + SeDenyNetworkLogonRight string + SeDenyInteractiveLogonRight string + SeUndockPrivilege string + SeManageVolumePrivilege string + SeRemoteInteractiveLogonRight string + SeImpersonatePrivilege string + SeCreateGlobalPrivilege string + SeIncreaseWorkingSetPrivilege string + SeTimeZonePrivilege string + SeCreateSymbolicLinkPrivilege string + SeDelegateSessionUserImpersonatePrivilege string + } +} + +// Columns is the schema of the table +func Columns() []table.ColumnDefinition { + return []table.ColumnDefinition{ + table.TextColumn("item"), + table.TextColumn("value"), + } +} + +// Generate is called to return the results for the table at query time. +// Constraints for generating can be retrieved from the queryContext. +func Generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { + // input item query constraint + var inputItem string + + // item output value + var inputValue string + + // error handling + var err error + + // one-time commands handlers initialization + registerCommandsHandlers() + + // checking if 'item' is in the where clause + if constraintList, present := queryContext.Constraints["item"]; present { + for _, constraint := range constraintList.Constraints { + if constraint.Operator == table.OperatorEquals { + inputItem = constraint.Expression // this input as to be kept as-is and returned on the same input column due to a sqlite requirement + log.Debug().Msgf("cis_audit input item requested: %s\n", inputItem) + } + } + } + + // Getting the input value if supported + if len(inputItem) > 0 { + inputValue, err = getValueCisItem(inputItem) + if err != nil { + return nil, err + } + } + + // returning item and its value + return []map[string]string{ + { + "item": inputItem, + "value": inputValue, + }, + }, nil +} + +// getPreProcessedFileContent returns an UTF-16 byte array +// This is useful when reading data from MS-Windows systems that generate UTF-16BE files +func getPreProcessedFileContent(path string) ([]byte, error) { + // Read the file into a []byte: + raw, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + // make an tranformer that converts MS-Win default to UTF8 + win16be := unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM) + + // make a transformer that is like win16be, but abides by BOM + utf16bom := unicode.BOMOverride(win16be.NewDecoder()) + + // make a Reader that uses utf16bom + unicodeReader := transform.NewReader(bytes.NewReader(raw), utf16bom) + + // decode and return data + decoded, err := ioutil.ReadAll(unicodeReader) + if err != nil { + return []byte(""), err + } + + // replace newlines with unix style + fileContent := strings.Replace(string(decoded), "\r\n", "\n", -1) + + return []byte(fileContent), nil +} + +// getSystem32Dir returns the path to the "system32" directory on Windows +func getSystem32Dir() (string, error) { + system32Path, err := windows.GetSystemDirectory() + if err != nil { + return "", err + } + + return system32Path, nil +} + +// getSeceditData returns data from the "secedit.exe" utility +func getSeceditData() (SeceditData, error) { + var data SeceditData + + // Get the path to the system32 directory + system32Dir, err := getSystem32Dir() + if err != nil { + return data, fmt.Errorf("path to system32 could not be determined: %w", err) + } + + // Build the fullpath to the "secedit.exe" executable + seceditPath := filepath.Join(system32Dir, "secedit.exe") + + // Get temporary directory + cacheDir, err := os.UserCacheDir() + if err != nil { + return data, fmt.Errorf("get UserCacheDir failed: %s", err) + } + + // Create temporary directory + tempDir, err := ioutil.TempDir(cacheDir, "secedit-") + if err != nil { + return data, fmt.Errorf("failed to create temporary directory: %w", err) + } + defer os.RemoveAll(tempDir) + + // Execute "secedit.exe" to export the current security configuration + outputInfPath := filepath.Join(tempDir, "output.inf") + cmd := exec.Command(seceditPath, "/export", "/cfg", outputInfPath) + if err := cmd.Run(); err != nil { + return data, fmt.Errorf("failed to execute secedit.exe: %w", err) + } + + // Read the exported file + fileContent, err := getPreProcessedFileContent(outputInfPath) + if err != nil { + return data, fmt.Errorf("failed to preprocess .inf file: %w", err) + } + + // Load the .inf file content + cfg, err := ini.Load(fileContent) + if err != nil { + fmt.Printf("Error: %v\n", err) + return data, err + } + + // Parse System Access section + if systemAccessSection := cfg.Section("System Access"); systemAccessSection != nil { + data.SystemAccess.MinimumPasswordAge = systemAccessSection.Key("MinimumPasswordAge").String() + data.SystemAccess.MaximumPasswordAge = systemAccessSection.Key("MaximumPasswordAge").String() + data.SystemAccess.MinimumPasswordLength = systemAccessSection.Key("MinimumPasswordLength").String() + data.SystemAccess.PasswordComplexity = systemAccessSection.Key("PasswordComplexity").String() + data.SystemAccess.PasswordHistorySize = systemAccessSection.Key("PasswordHistorySize").String() + data.SystemAccess.LockoutBadCount = systemAccessSection.Key("LockoutBadCount").String() + data.SystemAccess.ResetLockoutCount = systemAccessSection.Key("ResetLockoutCount").String() + data.SystemAccess.LockoutDuration = systemAccessSection.Key("LockoutDuration").String() + data.SystemAccess.RequireLogonToChangePassword = systemAccessSection.Key("RequireLogonToChangePassword").String() + data.SystemAccess.ForceLogoffWhenHourExpire = systemAccessSection.Key("ForceLogoffWhenHourExpire").String() + data.SystemAccess.NewAdministratorName = systemAccessSection.Key("NewAdministratorName").String() + data.SystemAccess.NewGuestName = systemAccessSection.Key("NewGuestName").String() + data.SystemAccess.ClearTextPassword = systemAccessSection.Key("ClearTextPassword").String() + data.SystemAccess.LSAAnonymousNameLookup = systemAccessSection.Key("LSAAnonymousNameLookup").String() + data.SystemAccess.EnableAdminAccount = systemAccessSection.Key("EnableAdminAccount").String() + data.SystemAccess.EnableGuestAccount = systemAccessSection.Key("EnableGuestAccount").String() + } + + // Parse Event Audit section + if eventAuditSection := cfg.Section("Event Audit"); eventAuditSection != nil { + data.EventAudit.AuditSystemEvents = eventAuditSection.Key("AuditSystemEvents").String() + data.EventAudit.AuditLogonEvents = eventAuditSection.Key("AuditLogonEvents").String() + data.EventAudit.AuditObjectAccess = eventAuditSection.Key("AuditObjectAccess").String() + data.EventAudit.AuditPrivilegeUse = eventAuditSection.Key("AuditPrivilegeUse").String() + data.EventAudit.AuditPolicyChange = eventAuditSection.Key("AuditPolicyChange").String() + data.EventAudit.AuditAccountManage = eventAuditSection.Key("AuditAccountManage").String() + data.EventAudit.AuditProcessTracking = eventAuditSection.Key("AuditProcessTracking").String() + data.EventAudit.AuditDSAccess = eventAuditSection.Key("AuditDSAccess").String() + data.EventAudit.AuditAccountLogon = eventAuditSection.Key("AuditAccountLogon").String() + } + + // Parse Privilege Rights section + if privilegeRightsSection := cfg.Section("Privilege Rights"); privilegeRightsSection != nil { + data.PrivilegeRights.SeNetworkLogonRight = getGroupNames(privilegeRightsSection.Key("SeNetworkLogonRight").String()) + data.PrivilegeRights.SeBackupPrivilege = getGroupNames(privilegeRightsSection.Key("SeBackupPrivilege").String()) + data.PrivilegeRights.SeChangeNotifyPrivilege = getGroupNames(privilegeRightsSection.Key("SeChangeNotifyPrivilege").String()) + data.PrivilegeRights.SeSystemtimePrivilege = getGroupNames(privilegeRightsSection.Key("SeSystemtimePrivilege").String()) + data.PrivilegeRights.SeCreatePagefilePrivilege = getGroupNames(privilegeRightsSection.Key("SeCreatePagefilePrivilege").String()) + data.PrivilegeRights.SeDebugPrivilege = getGroupNames(privilegeRightsSection.Key("SeDebugPrivilege").String()) + data.PrivilegeRights.SeRemoteShutdownPrivilege = getGroupNames(privilegeRightsSection.Key("SeRemoteShutdownPrivilege").String()) + data.PrivilegeRights.SeAuditPrivilege = getGroupNames(privilegeRightsSection.Key("SeAuditPrivilege").String()) + data.PrivilegeRights.SeIncreaseQuotaPrivilege = getGroupNames(privilegeRightsSection.Key("SeIncreaseQuotaPrivilege").String()) + data.PrivilegeRights.SeIncreaseBasePriorityPrivilege = getGroupNames(privilegeRightsSection.Key("SeIncreaseBasePriorityPrivilege").String()) + data.PrivilegeRights.SeLoadDriverPrivilege = getGroupNames(privilegeRightsSection.Key("SeLoadDriverPrivilege").String()) + data.PrivilegeRights.SeDenyBatchLogonRight = getGroupNames(privilegeRightsSection.Key("SeDenyBatchLogonRight").String()) + data.PrivilegeRights.SeDenyServiceLogonRight = getGroupNames(privilegeRightsSection.Key("SeDenyServiceLogonRight").String()) + data.PrivilegeRights.SeBatchLogonRight = getGroupNames(privilegeRightsSection.Key("SeBatchLogonRight").String()) + data.PrivilegeRights.SeServiceLogonRight = getGroupNames(privilegeRightsSection.Key("SeServiceLogonRight").String()) + data.PrivilegeRights.SeInteractiveLogonRight = getGroupNames(privilegeRightsSection.Key("SeInteractiveLogonRight").String()) + data.PrivilegeRights.SeSecurityPrivilege = getGroupNames(privilegeRightsSection.Key("SeSecurityPrivilege").String()) + data.PrivilegeRights.SeSystemEnvironmentPrivilege = getGroupNames(privilegeRightsSection.Key("SeSystemEnvironmentPrivilege").String()) + data.PrivilegeRights.SeProfileSingleProcessPrivilege = getGroupNames(privilegeRightsSection.Key("SeProfileSingleProcessPrivilege").String()) + data.PrivilegeRights.SeSystemProfilePrivilege = getGroupNames(privilegeRightsSection.Key("SeSystemProfilePrivilege").String()) + data.PrivilegeRights.SeAssignPrimaryTokenPrivilege = getGroupNames(privilegeRightsSection.Key("SeAssignPrimaryTokenPrivilege").String()) + data.PrivilegeRights.SeRestorePrivilege = getGroupNames(privilegeRightsSection.Key("SeRestorePrivilege").String()) + data.PrivilegeRights.SeShutdownPrivilege = getGroupNames(privilegeRightsSection.Key("SeShutdownPrivilege").String()) + data.PrivilegeRights.SeTakeOwnershipPrivilege = getGroupNames(privilegeRightsSection.Key("SeTakeOwnershipPrivilege").String()) + data.PrivilegeRights.SeDenyNetworkLogonRight = getGroupNames(privilegeRightsSection.Key("SeDenyNetworkLogonRight").String()) + data.PrivilegeRights.SeDenyInteractiveLogonRight = getGroupNames(privilegeRightsSection.Key("SeDenyInteractiveLogonRight").String()) + data.PrivilegeRights.SeUndockPrivilege = getGroupNames(privilegeRightsSection.Key("SeUndockPrivilege").String()) + data.PrivilegeRights.SeManageVolumePrivilege = getGroupNames(privilegeRightsSection.Key("SeManageVolumePrivilege").String()) + data.PrivilegeRights.SeRemoteInteractiveLogonRight = getGroupNames(privilegeRightsSection.Key("SeRemoteInteractiveLogonRight").String()) + data.PrivilegeRights.SeImpersonatePrivilege = getGroupNames(privilegeRightsSection.Key("SeImpersonatePrivilege").String()) + data.PrivilegeRights.SeCreateGlobalPrivilege = getGroupNames(privilegeRightsSection.Key("SeCreateGlobalPrivilege").String()) + data.PrivilegeRights.SeIncreaseWorkingSetPrivilege = getGroupNames(privilegeRightsSection.Key("SeIncreaseWorkingSetPrivilege").String()) + data.PrivilegeRights.SeTimeZonePrivilege = getGroupNames(privilegeRightsSection.Key("SeTimeZonePrivilege").String()) + data.PrivilegeRights.SeCreateSymbolicLinkPrivilege = getGroupNames(privilegeRightsSection.Key("SeCreateSymbolicLinkPrivilege").String()) + data.PrivilegeRights.SeDelegateSessionUserImpersonatePrivilege = getGroupNames(privilegeRightsSection.Key("SeDelegateSessionUserImpersonatePrivilege").String()) + } + return data, nil +} + +// Best effor helper to extract the group names from an input string +func getGroupNames(input string) string { + var output string + + // remove global occurences of * character + input = strings.ReplaceAll(input, "*", "") + + // split input by comma + groups := strings.Split(input, ",") + for _, group := range groups { + userGroup, _ := user.LookupGroupId(group) + if userGroup != nil && len(userGroup.Name) > 0 { + output += userGroup.Name + "," + } else { + output += group + "," + } + } + + return output +} + +// containsAny checks if any of the given substrings are present in the input string +// It returns true if at least one substring is found, otherwise it returns false +func containsAny(input string, substrings []string) bool { + for _, substring := range substrings { + if strings.Contains(input, substring) { + return true + } + } + return false +} + +// contains checks if the given substring is present in the input string. +// It returns true if the substring is found, otherwise it returns false. +func contains(input string, substring string) bool { + return containsAny(input, []string{substring}) +} + +// ParseAuditOutput parses the output of the auditpol.exe command +func parseAuditOutput(input string) ([]AuditItem, error) { + // expected items per line + const expectedItemsPerLine = 6 + + // parse the CSV string into a slice of AuditItem structs + reader := csv.NewReader(strings.NewReader(input)) + reader.FieldsPerRecord = expectedItemsPerLine + + // read all lines + lines, err := reader.ReadAll() + if err != nil { + return nil, err + } + + // parse the CSV lines into AuditItem structs + var auditItems []AuditItem + for i, line := range lines { + + // Check if the line has the expected number of items + if len(line) < expectedItemsPerLine { + return nil, fmt.Errorf("invalid line at index %d", i) + } + + // Skip header + if i == 0 { + continue + } + + // Parse the line + item := AuditItem{ + Subcategory: line[2], + GUID: line[3], + NoAuditing: contains(line[4], "No Auditing"), + Success: contains(line[4], "Success"), + Failure: contains(line[4], "Failure"), + Raw: line[4], + } + + auditItems = append(auditItems, item) + } + + return auditItems, nil +} + +// getAuditItems returns a slice of AuditItem structs +func getAuditItems() ([]AuditItem, error) { + // Get the path to the "system32" directory + system32Dir, err := getSystem32Dir() + if err != nil { + return nil, fmt.Errorf("path to system32 could not be determined: %w", err) + } + + // Build the fullpath to the "auditpol.exe" executable + auditpolPath := filepath.Join(system32Dir, "auditpol.exe") + + cmd := exec.Command(auditpolPath, "/get", "/category:*", "/r") + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + // Execute the auditpol command + err = cmd.Run() + if err != nil { + return nil, fmt.Errorf("command execution failed: %v, %s", err, stderr.String()) + } + + // Parse the output + auditItems, err := parseAuditOutput(stdout.String()) + if err != nil { + return nil, fmt.Errorf("parsing output failed: %v", err) + } + + return auditItems, nil +} + +// Register the CIS items command handlers +func registerCommandsHandlers() { + // initialize the commands handlers map + commandsInit.Do(func() { + commandHandlers = make(map[string]CommandHandler) + + registerCommandHandler("1.2.1", handler_cis_1_2_1) + registerCommandHandler("1.2.2", handler_cis_1_2_2) + registerCommandHandler("1.2.3", handler_cis_1_2_3) + registerCommandHandler("2.2.4", handler_cis_2_2_4) + registerCommandHandler("2.2.6", handler_cis_2_2_6) + registerCommandHandler("2.2.9", handler_cis_2_2_9) + registerCommandHandler("2.2.17", handler_cis_2_2_17) + registerCommandHandler("2.2.18", handler_cis_2_2_18) + registerCommandHandler("2.2.28", handler_cis_2_2_28) + registerCommandHandler("2.2.29", handler_cis_2_2_29) + registerCommandHandler("2.2.33", handler_cis_2_2_33) + registerCommandHandler("2.2.35", handler_cis_2_2_35) + registerCommandHandler("2.2.36", handler_cis_2_2_36) + registerCommandHandler("2.2.38", handler_cis_2_2_38) + registerCommandHandler("2.3.10.1", handler_cis_2_3_10_1) + registerCommandHandler("2.3.11.6", handler_cis_2_3_11_6) + registerCommandHandler("17.5.1", handler_cis_17_5_1) + registerCommandHandler("17.5.2", handler_cis_17_5_2) + registerCommandHandler("17.5.3", handler_cis_17_5_3) + registerCommandHandler("17.5.4", handler_cis_17_5_4) + registerCommandHandler("17.5.5", handler_cis_17_5_5) + registerCommandHandler("17.5.6", handler_cis_17_5_6) + }) +} + +// registerCommandHandler registers a new command handler for the given command +func registerCommandHandler(command string, handler CommandHandler) { + commandHandlers[command] = handler +} + +// Helper to access the command handlers map +func getValueCisItem(item string) (string, error) { + var output string + var err error + + if handler, exists := commandHandlers[item]; exists { + output, err = handler() + if err != nil { + return "", fmt.Errorf("cis command handler err: %v", err) + } + } + + return output, nil +} + +// getAuditItem helps to access audit array +func getAuditItem(subcategory string) (string, error) { + var output string + + // Getting audit items + items, err := getAuditItems() + if err != nil { + return "", err + } + + // Find the item and save raw content if present + for _, item := range items { + if item.Subcategory == subcategory { + output = item.Raw + break + } + } + + return output, nil +} + +// Command handler for CIS item 1.2.1 +func handler_cis_1_2_1() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + + return data.SystemAccess.LockoutDuration, nil +} + +// Command handler for CIS item 1.2.2 +func handler_cis_1_2_2() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.SystemAccess.LockoutBadCount, nil +} + +// Command handler for CIS item 1.2.3 +func handler_cis_1_2_3() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.SystemAccess.ResetLockoutCount, nil +} + +// Command handler for CIS item 2.2.4 +func handler_cis_2_2_4() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.PrivilegeRights.SeIncreaseQuotaPrivilege, nil +} + +// Command handler for CIS item 2.2.6 +func handler_cis_2_2_6() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.PrivilegeRights.SeRemoteInteractiveLogonRight, nil +} + +// Command handler for CIS item 2.2.9 +func handler_cis_2_2_9() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.PrivilegeRights.SeTimeZonePrivilege, nil +} + +// Command handler for CIS item 2.2.17 +func handler_cis_2_2_17() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.PrivilegeRights.SeDenyBatchLogonRight, nil +} + +// Command handler for CIS item 2.2.18 +func handler_cis_2_2_18() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.PrivilegeRights.SeDenyServiceLogonRight, nil +} + +// Command handler for CIS item 2.2.28 +func handler_cis_2_2_28() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.PrivilegeRights.SeBatchLogonRight, nil +} + +// Command handler for CIS item 2.2.29 +func handler_cis_2_2_29() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.PrivilegeRights.SeServiceLogonRight, nil +} + +// Command handler for CIS item 2.2.33 +func handler_cis_2_2_33() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.PrivilegeRights.SeManageVolumePrivilege, nil +} + +// Command handler for CIS item 2.2.35 +func handler_cis_2_2_35() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.PrivilegeRights.SeSystemProfilePrivilege, nil +} + +// Command handler for CIS item 2.2.36 +func handler_cis_2_2_36() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.PrivilegeRights.SeAssignPrimaryTokenPrivilege, nil +} + +// Command handler for CIS item 2.2.38 +func handler_cis_2_2_38() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.PrivilegeRights.SeShutdownPrivilege, nil +} + +// Command handler for CIS item 2.3.10.1 +func handler_cis_2_3_10_1() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.SystemAccess.LSAAnonymousNameLookup, nil +} + +// Command handler for CIS item 2.3.11.6 +func handler_cis_2_3_11_6() (string, error) { + data, err := getSeceditData() + if err != nil { + return "", err + } + return data.SystemAccess.ForceLogoffWhenHourExpire, nil +} + +// Command handler for CIS item 17.5.1 +func handler_cis_17_5_1() (string, error) { + output, err := getAuditItem("Account Lockout") + if err != nil { + return "", err + } + + return output, nil +} + +// Command handler for CIS item 17.5.2 +func handler_cis_17_5_2() (string, error) { + output, err := getAuditItem("Group Membership") + if err != nil { + return "", err + } + + return output, nil +} + +// Command handler for CIS item 17.5.3 +func handler_cis_17_5_3() (string, error) { + output, err := getAuditItem("Logoff") + if err != nil { + return "", err + } + + return output, nil +} + +// Command handler for CIS item 17.5.4 +func handler_cis_17_5_4() (string, error) { + output, err := getAuditItem("Logon") + if err != nil { + return "", err + } + + return output, nil +} + +// Command handler for CIS item 17.5.5 +func handler_cis_17_5_5() (string, error) { + output, err := getAuditItem("Other Logon/Logoff Events") + if err != nil { + return "", err + } + + return output, nil +} + +// Command handler for CIS item 17.5.6 +func handler_cis_17_5_6() (string, error) { + output, err := getAuditItem("Special Logon") + if err != nil { + return "", err + } + + return output, nil +} diff --git a/orbit/pkg/table/cis_audit/cis_audit_windows_test.go b/orbit/pkg/table/cis_audit/cis_audit_windows_test.go new file mode 100644 index 0000000000..eea06378cf --- /dev/null +++ b/orbit/pkg/table/cis_audit/cis_audit_windows_test.go @@ -0,0 +1,92 @@ +//go:build windows +// +build windows + +package cisaudit + +import ( + "runtime" + "testing" + + "github.com/osquery/osquery-go/plugin/table" + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" +) + +func TestGenerateItemNotPresent(t *testing.T) { + ctx := context.Background() + + queryContext := table.QueryContext{ + Constraints: make(map[string]table.ConstraintList), + } + result, err := Generate(ctx, queryContext) + assert.Nil(t, err) + assert.Equal(t, len(result), 1) + assert.Empty(t, result[0]["item"]) + assert.Empty(t, result[0]["value"]) +} + +func TestGenerateItemConstrainIsPresentAndResponseMaintainsValue(t *testing.T) { + ctx := context.Background() + + queryContext := table.QueryContext{ + Constraints: map[string]table.ConstraintList{ + "item": { + Constraints: []table.Constraint{ + { + Operator: table.OperatorEquals, + Expression: "value", + }, + }, + }, + }, + } + result, err := Generate(ctx, queryContext) + assert.Nil(t, err) + assert.Equal(t, len(result), 1) + assert.Equal(t, result[0]["item"], "value") +} + +func TestGenerateItemInvalidInput(t *testing.T) { + ctx := context.Background() + + queryContext := table.QueryContext{ + Constraints: map[string]table.ConstraintList{ + "item": { + Constraints: []table.Constraint{ + { + Operator: table.OperatorEquals, + Expression: "9.9.9.9.9.9", + }, + }, + }, + }, + } + result, err := Generate(ctx, queryContext) + assert.Nil(t, err) + assert.Equal(t, len(result), 1) + assert.Empty(t, result[0]["value"]) +} + +func TestGenerateItemValid(t *testing.T) { + ctx := context.Background() + + queryContext := table.QueryContext{ + Constraints: map[string]table.ConstraintList{ + "item": { + Constraints: []table.Constraint{ + { + Operator: table.OperatorEquals, + Expression: "1.2.1", + }, + }, + }, + }, + } + _, err := Generate(ctx, queryContext) + + if runtime.GOOS == "windows" { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } +} diff --git a/orbit/pkg/table/extension_windows.go b/orbit/pkg/table/extension_windows.go index 74d5a2d748..943d913dbd 100644 --- a/orbit/pkg/table/extension_windows.go +++ b/orbit/pkg/table/extension_windows.go @@ -3,6 +3,7 @@ package table import ( + "github.com/fleetdm/fleet/v4/orbit/pkg/table/cis_audit" "github.com/fleetdm/fleet/v4/orbit/pkg/table/mdm" "github.com/osquery/osquery-go" "github.com/osquery/osquery-go/plugin/table" @@ -12,5 +13,6 @@ func PlatformTables() []osquery.OsqueryPlugin { return []osquery.OsqueryPlugin{ // Fleet tables table.NewPlugin("mdm_bridge", mdmbridge.Columns(), mdmbridge.Generate), + table.NewPlugin("cis_audit", cisaudit.Columns(), cisaudit.Generate), } } diff --git a/schema/osquery_fleet_schema.json b/schema/osquery_fleet_schema.json index 8901546e5c..95a648e49a 100644 --- a/schema/osquery_fleet_schema.json +++ b/schema/osquery_fleet_schema.json @@ -28064,6 +28064,31 @@ "url": "https://fleetdm.com/tables/mdm_bridge", "fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/mdm_bridge.yml" }, + { + "name": "cis_audit", + "platforms": [ + "windows" + ], + "description": "Enables querying CIS item values.", + "columns": [ + { + "name": "item", + "type": "text", + "required": false, + "description": "Contains the input CIS item to query. If empty, no CIS item is queried." + }, + { + "name": "value", + "type": "text", + "required": false, + "description": "Contains the value for the queried CIS item." + } + ], + "notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).", + "evented": false, + "url": "https://fleetdm.com/tables/cis_audit", + "fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/cis_audit.yml" + }, { "name": "munki_installs", "notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).

Code based on work by [Kolide](https://github.com/kolide/launcher).", diff --git a/schema/tables/cis_audit.yml b/schema/tables/cis_audit.yml new file mode 100644 index 0000000000..79055e0bda --- /dev/null +++ b/schema/tables/cis_audit.yml @@ -0,0 +1,15 @@ +name: cis_audit +platforms: + - windows +description: Enables querying CIS items values. +columns: + - name: item + type: text + required: false + description: Contains the input CIS item to query. If empty, no CIS item is queried. + - name: value + type: text + required: false + description: Contains the value for the queried CIS item. +notes: This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer). +evented: false