diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ac6f907..f8c469b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## X.Y.Z (Unreleased) * Add new change notes here +* Add support for local windows event log source for installed collectors ## 3.1.1 (July 8, 2025) diff --git a/sumologic/provider.go b/sumologic/provider.go index 1f8348d3..d5e50978 100644 --- a/sumologic/provider.go +++ b/sumologic/provider.go @@ -123,6 +123,7 @@ func Provider() *schema.Provider { "sumologic_source_template": resourceSumologicSourceTemplate(), "sumologic_azure_metrics_source": resourceSumologicGenericPollingSource(), "sumologic_scan_budget": resourceSumologicScanBudget(), + "sumologic_local_windows_event_log_source": resourceSumologicLocalWindowsEventLogSource(), }, DataSourcesMap: map[string]*schema.Resource{ "sumologic_cse_log_mapping_vendor_product": dataSourceCSELogMappingVendorAndProduct(), diff --git a/sumologic/resource_sumologic_local_windows_event_log_source.go b/sumologic/resource_sumologic_local_windows_event_log_source.go new file mode 100644 index 00000000..4812fcf3 --- /dev/null +++ b/sumologic/resource_sumologic_local_windows_event_log_source.go @@ -0,0 +1,158 @@ +package sumologic + +import ( + "fmt" + "log" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceSumologicLocalWindowsEventLogSource() *schema.Resource { + LocalWindowsEventLogSource := resourceSumologicSource() + LocalWindowsEventLogSource.Create = resourceSumologicLocalWindowsEventLogSourceCreate + LocalWindowsEventLogSource.Read = resourceSumologicLocalWindowsEventLogSourceRead + LocalWindowsEventLogSource.Update = resourceSumologicLocalWindowsEventLogSourceUpdate + LocalWindowsEventLogSource.Importer = &schema.ResourceImporter{ + State: resourceSumologicSourceImport, + } + + // Windows Event Log specific fields + LocalWindowsEventLogSource.Schema["log_names"] = &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "List of Windows log types to collect (e.g., Security, Application, System)", + } + + LocalWindowsEventLogSource.Schema["render_messages"] = &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "When using legacy format, indicates if full event messages are collected", + } + + LocalWindowsEventLogSource.Schema["event_format"] = &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 0, + ValidateFunc: validation.IntInSlice([]int{0, 1}), + Description: "0 for legacy format (XML), 1 for JSON format", + } + + LocalWindowsEventLogSource.Schema["event_message"] = &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntInSlice([]int{0, 1, 2}), + Description: "0 for complete message, 1 for message title, 2 for metadata only. Required if event_format is 0", + } + + LocalWindowsEventLogSource.Schema["deny_list"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Comma-separated list of event IDs to deny", + } + + LocalWindowsEventLogSource.Schema["allow_list"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Comma-separated list of event IDs to allow", + } + + return LocalWindowsEventLogSource +} + +func resourceSumologicLocalWindowsEventLogSourceCreate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + if d.Id() == "" { + source := resourceToLocalWindowsEventLogSource(d) + collectorID := d.Get("collector_id").(int) + + id, err := c.CreateLocalWindowsEventLogSource(source, collectorID) + if err != nil { + return err + } + + d.SetId(strconv.Itoa(id)) + } + + return resourceSumologicLocalWindowsEventLogSourceRead(d, meta) +} + +func resourceSumologicLocalWindowsEventLogSourceUpdate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + source := resourceToLocalWindowsEventLogSource(d) + + err := c.UpdateLocalWindowsEventLogSource(source, d.Get("collector_id").(int)) + + if err != nil { + return err + } + + return resourceSumologicLocalWindowsEventLogSourceRead(d, meta) +} + +func resourceToLocalWindowsEventLogSource(d *schema.ResourceData) LocalWindowsEventLogSource { + + source := resourceToSource(d) + source.Type = "LocalWindowsEventLog" + + LocalWindowsEventLogSource := LocalWindowsEventLogSource{ + Source: source, + LogNames: d.Get("log_names").([]interface{}), + RenderMessages: d.Get("render_messages").(bool), + EventFormat: d.Get("event_format").(int), + } + + // Handle optional deny_list + if DenyList, ok := d.GetOk("deny_list"); ok { + LocalWindowsEventLogSource.DenyList = DenyList.(string) + } + + // Handle optional allow_list + if AllowList, ok := d.GetOk("allow_list"); ok { + LocalWindowsEventLogSource.AllowList = AllowList.(string) + } + + // Handle optional event_message field + if eventMessage, ok := d.GetOk("event_message"); ok { + eventMessageInt := eventMessage.(int) + LocalWindowsEventLogSource.EventMessage = &eventMessageInt + } + + return LocalWindowsEventLogSource + +} + +func resourceSumologicLocalWindowsEventLogSourceRead(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + id, _ := strconv.Atoi(d.Id()) + source, err := c.GetLocalWindowsEventLogSource(d.Get("collector_id").(int), id) + + if err != nil { + return err + } + + if source == nil { + log.Printf("[WARN] Local Windows Event Log source not found, removing from state: %v - %v", id, err) + d.SetId("") + return nil + } + + if err := resourceSumologicSourceRead(d, source.Source); err != nil { + return fmt.Errorf("%s", err) + } + d.Set("log_names", source.LogNames) + d.Set("render_messages", source.RenderMessages) + d.Set("event_format", source.EventFormat) + d.Set("deny_list", source.DenyList) + d.Set("allow_list", source.AllowList) + d.Set("event_format", source.EventFormat) + d.Set("event_message", source.EventMessage) + + return nil +} diff --git a/sumologic/sumologic_local_windows_event_log_source.go b/sumologic/sumologic_local_windows_event_log_source.go new file mode 100644 index 00000000..1c75c5fa --- /dev/null +++ b/sumologic/sumologic_local_windows_event_log_source.go @@ -0,0 +1,83 @@ +package sumologic + +import ( + "encoding/json" + "fmt" +) + +type LocalWindowsEventLogSource struct { + Source + LogNames []interface{} `json:"logNames"` + RenderMessages bool `json:"renderMessages"` + EventFormat int `json:"eventFormat"` + EventMessage *int `json:"eventMessage,omitempty"` + DenyList string `json:"denylist,omitempty"` + AllowList string `json:"allowlist,omitempty"` +} + +func (s *Client) CreateLocalWindowsEventLogSource(source LocalWindowsEventLogSource, collectorID int) (int, error) { + + type LocalWindowsEventLogSourceMessage struct { + Source LocalWindowsEventLogSource `json:"source"` + } + + request := LocalWindowsEventLogSourceMessage{ + Source: source, + } + + urlPath := fmt.Sprintf("v1/collectors/%d/sources", collectorID) + body, err := s.Post(urlPath, request) + + if err != nil { + return -1, err + } + + var response LocalWindowsEventLogSourceMessage + + err = json.Unmarshal(body, &response) + if err != nil { + return -1, err + } + + return response.Source.ID, nil +} + +func (s *Client) GetLocalWindowsEventLogSource(collectorID, sourceID int) (*LocalWindowsEventLogSource, error) { + body, err := s.Get(fmt.Sprintf("v1/collectors/%d/sources/%d", collectorID, sourceID)) + if err != nil { + return nil, err + } + + if body == nil { + return nil, nil + } + + type LocalWindowsEventLogSourceResponse struct { + Source LocalWindowsEventLogSource `json:"source"` + } + + var response LocalWindowsEventLogSourceResponse + err = json.Unmarshal(body, &response) + if err != nil { + return nil, err + } + + return &response.Source, nil + +} + +func (s *Client) UpdateLocalWindowsEventLogSource(source LocalWindowsEventLogSource, collectorID int) error { + + type LocalWindowsEventLogMessage struct { + Source LocalWindowsEventLogSource `json:"source"` + } + + request := LocalWindowsEventLogMessage{ + Source: source, + } + + urlPath := fmt.Sprintf("v1/collectors/%d/sources/%d", collectorID, source.ID) + _, err := s.Put(urlPath, request) + + return err +} diff --git a/website/docs/r/local_windows_event_source.html.markdown b/website/docs/r/local_windows_event_source.html.markdown new file mode 100644 index 00000000..438694d6 --- /dev/null +++ b/website/docs/r/local_windows_event_source.html.markdown @@ -0,0 +1,87 @@ +--- +layout: "sumologic" +page_title: "SumoLogic: sumologic_local_windows_event_log_source" +description: |- + Provides a Sumologic Local Windows Event Log Source. +--- + +# sumologic_local_windows_event_source +Provides a [Sumologic Local Windows Event Log Source][1]. + +Note that installed collector sources must be treated as a special case as the user must have a pipeline to install them outside of terraform as it is not possible to install a local collector via the API, that must be done locally on the instance. Make sure the collector is in cloud managed not local json file mode to allow for API based configuration. + +Use the installed collector data source to map to installed collector instances by name or id. + +## Example Usage + +Example: 1 This will configure JSON format with "concise" setting and pickup System and Application logs with /os/windows/events as the sourcecateogry. + +```hcl +data "sumologic_collector" "installed_collector" { + name = "terraform_source_testing" +} + +resource "sumologic_local_windows_event_log_source" "local" { + name = "windows_logs" + description = "windows system and application logs in json format" + category = "/os/windows/events" + collector_id = "${data.sumologic_collector.installed_collector.id}" + log_names = ["System","Application"] + event_format = 1 // 0 = XML, 1 = JSON +} +``` + +Example 2: Using custom logs and a deny list +```hcl +resource "sumologic_local_windows_event_log_source" "local" { + name = "windows_logs" + description = "windows logs in json format" + category = "/os/windows/events" + collector_id = "${data.sumologic_collector.installed_collector.id}" + log_names = ["System","Application","Microsoft-Windows-PowerShell/Operational", "Microsoft-Windows-TaskScheduler/Operational"] + deny_list = "9999,7890" + event_format = 1 // 0 = XML, 1 = JSON +} +``` + + + +## Argument Reference + +The following arguments are supported: + + * `name` - (Required) The name of the local file source. This is required, and has to be unique. Changing this will force recreation the source. + * `description` - (Optional) The description of the source. + * `log_names` - List of Windows log types to collect (e.g., Security, Application, System) + * `render_messages` - When using legacy format, indicates if full event messages are collected + * `event_format` - 0 for legacy format (XML), 1 for JSON format. Default 0. + * `event_message` - 0 for complete message, 1 for message title, 2 for metadata only. Required if event_format is 0 + * `deny_list` - Comma-separated list of event IDs to deny + * `category` - (Optional) The default source category for the source. + * `fields` - (Optional) Map containing [key/value pairs][2]. + * `denylist` - (Optional) Comma-separated list of valid path expressions from which logs will not be collected. + +### See also + * [Common Source Properties](https://github.com/terraform-providers/terraform-provider-sumologic/tree/master/website#common-source-properties) + +## Attributes Reference +The following attributes are exported: + + * `id` - The internal ID of the local file source. + +## Import +Local file sources can be imported using the collector and source IDs, e.g.: + +```hcl +terraform import sumologic_local_windows_event_source.test 123/456 +``` + +Local file sources can also be imported using the collector name and source name, e.g.: + +```hcl +terraform import sumologic_local_windows_event_source.test my-test-collector/my-test-source +``` + +[1]: https://help.sumologic.com/docs/send-data/installed-collectors/sources/local-windows-event-log-source/ +[2]: https://help.sumologic.com/Manage/Fields +[3]: https://help.sumologic.com/docs/send-data/use-json-configure-sources/json-parameters-installed-sources/#local-windows-event-logsource \ No newline at end of file