From 55b3c0b5398b36b159b032289005d2e43dafaa0e Mon Sep 17 00:00:00 2001 From: Marc Guasch Date: Tue, 29 Oct 2024 03:26:42 -0700 Subject: [PATCH 01/10] Add translate_guid processor --- CHANGELOG.next.asciidoc | 1 + libbeat/docs/processors-list.asciidoc | 6 + libbeat/processors/translate_guid/config.go | 40 +++++ libbeat/processors/translate_guid/doc.go | 20 +++ .../docs/translate_guid.asciidoc | 48 ++++++ libbeat/processors/translate_guid/ldap.go | 145 ++++++++++++++++++ .../translate_guid/translateguid.go | 126 +++++++++++++++ 7 files changed, 386 insertions(+) create mode 100644 libbeat/processors/translate_guid/config.go create mode 100644 libbeat/processors/translate_guid/doc.go create mode 100644 libbeat/processors/translate_guid/docs/translate_guid.asciidoc create mode 100644 libbeat/processors/translate_guid/ldap.go create mode 100644 libbeat/processors/translate_guid/translateguid.go diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 797db99b7ca..8eb756e4bdf 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -111,6 +111,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff] - Set timeout of 1 minute for FQDN requests {pull}37756[37756] - Fix issue where old data could be saved in the memory queue after acknowledgment, increasing memory use {pull}41356[41356] - Ensure Elasticsearch output can always recover from network errors {pull}40794[40794] +- Add `translate_guid` processor for windows platforms. {pull}41472[41472] *Auditbeat* diff --git a/libbeat/docs/processors-list.asciidoc b/libbeat/docs/processors-list.asciidoc index 4105666049d..63985fd8f5a 100644 --- a/libbeat/docs/processors-list.asciidoc +++ b/libbeat/docs/processors-list.asciidoc @@ -131,6 +131,9 @@ endif::[] ifndef::no_timestamp_processor[] * <> endif::[] +ifndef::no_translate_guid_processor[] +* <> +endif::[] ifndef::no_translate_sid_processor[] * <> endif::[] @@ -279,6 +282,9 @@ endif::[] ifndef::no_timestamp_processor[] include::{libbeat-processors-dir}/timestamp/docs/timestamp.asciidoc[] endif::[] +ifndef::no_translate_guid_processor[] +include::{libbeat-processors-dir}/translate_guid/docs/translate_guid.asciidoc[] +endif::[] ifndef::no_translate_sid_processor[] include::{libbeat-processors-dir}/translate_sid/docs/translate_sid.asciidoc[] endif::[] diff --git a/libbeat/processors/translate_guid/config.go b/libbeat/processors/translate_guid/config.go new file mode 100644 index 00000000000..51b12fec2b0 --- /dev/null +++ b/libbeat/processors/translate_guid/config.go @@ -0,0 +1,40 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package translate_guid + +import ( + "github.com/elastic/elastic-agent-libs/transport/tlscommon" +) + +type config struct { + Field string `config:"field" validate:"required"` + TargetField string `config:"target_field"` + LDAPAddress string `config:"ldap_address" validate:"required"` + LDAPBaseDN string `config:"ldap_base_dn" validate:"required"` + LDAPUser string `config:"ldap_user"` + LDAPPassword string `config:"ldap_password"` + LDAPSearchTimeLimit int `config:"ldap_search_time_limit"` + LDAPTLS *tlscommon.Config `config:"ldap_ssl"` + + IgnoreMissing bool `config:"ignore_missing"` + IgnoreFailure bool `config:"ignore_failure"` +} + +func defaultConfig() config { + return config{LDAPSearchTimeLimit: 30} +} diff --git a/libbeat/processors/translate_guid/doc.go b/libbeat/processors/translate_guid/doc.go new file mode 100644 index 00000000000..b0d4ec27efb --- /dev/null +++ b/libbeat/processors/translate_guid/doc.go @@ -0,0 +1,20 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package translate_guid provides a Beat processor for converting Windows +// Global Unique Identifiers (GUIDs) to object names. +package translate_guid diff --git a/libbeat/processors/translate_guid/docs/translate_guid.asciidoc b/libbeat/processors/translate_guid/docs/translate_guid.asciidoc new file mode 100644 index 00000000000..7405dd7765b --- /dev/null +++ b/libbeat/processors/translate_guid/docs/translate_guid.asciidoc @@ -0,0 +1,48 @@ +[[processor-translate-guid]] +=== Translate GUID + +++++ +translate_guid +++++ + +The `translate_guid` processor translates an LDAP Global Unique Identifier (GUID) +into its common name. + +Every object on an Active Directory is issued a GUID. Internal processes +refer to their GUID's rather than the object's name and these values +sometimes appear in logs. + +If the GUID is invalid (malformed) or does not map to any object on the domain +then this will result in the processor returning an error unless `ignore_failure` +is set. + +[source,yaml] +---- +processors: + - translate_guid: + field: winlog.event_data.ObjectGuid + ldap_address: "ldap://" + ldap_base_dn: "dc=example,dc=com" + ignore_missing: true + ignore_failure: true +---- + +The `translate_guid` processor has the following configuration settings: + +.Translate GUID options +[options="header"] +|====== +| Name | Required | Default | Description +| `field` | yes | | Source field containing a GUID. +| `target_field` | no | | Target field for the common name. If not set it will be replaced in place. +| `ldap_address` | yes | | LDAP server address. eg: `ldap://ds.example.com:389` +| `ldap_base_dn` | yes | | LDAP base DN. eg: `dc=example,dc=com` +| `ldap_user` | no | | LDAP user. +| `ldap_password` | no | | LDAP password. +| `ldap_search_time_limit` | no | 30 | LDAP search time limit in seconds. +| `ldap_ssl`* | no | 30 | LDAP TLS/SSL connection settings. +| `ignore_missing` | no | false | Ignore errors when the source field is missing. +| `ignore_failure` | no | false | Ignore all errors produced by the processor. +|====== + +* Also see <> for a full description of the `ldap_ssl` options. diff --git a/libbeat/processors/translate_guid/ldap.go b/libbeat/processors/translate_guid/ldap.go new file mode 100644 index 00000000000..8ffb351f7c1 --- /dev/null +++ b/libbeat/processors/translate_guid/ldap.go @@ -0,0 +1,145 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package translate_guid + +import ( + "crypto/tls" + "fmt" + "strings" + "sync" + + "github.com/go-ldap/ldap/v3" +) + +// ldapClient manages a single reusable LDAP connection +type ldapClient struct { + conn *ldap.Conn + mu sync.Mutex + *ldapConfig +} + +type ldapConfig struct { + address string + baseDN string + username string + password string + searchTimeLimit int + tlsConfig *tls.Config +} + +// newLDAPClient initializes a new ldapClient with a single connection +func newLDAPClient(config *ldapConfig) (*ldapClient, error) { + client := &ldapClient{ldapConfig: config} + + // Establish initial connection + if err := client.connect(); err != nil { + return nil, err + } + + return client, nil +} + +// connect establishes a new connection to the LDAP server +func (client *ldapClient) connect() error { + client.mu.Lock() + defer client.mu.Unlock() + + // Connect with or without TLS based on configuration + var conn *ldap.Conn + var err error + if client.tlsConfig != nil { + conn, err = ldap.DialTLS("tcp", client.address, client.tlsConfig) + } else { + conn, err = ldap.Dial("tcp", client.address) + } + if err != nil { + return fmt.Errorf("failed to dial LDAP server: %v", err) + } + + if client.password != "" { + err = conn.Bind(client.username, client.password) + } else { + err = conn.UnauthenticatedBind(client.username) + } + + if err != nil { + conn.Close() + return fmt.Errorf("failed to bind to LDAP server: %v", err) + } + + client.conn = conn + return nil +} + +// reconnect checks the connection's health and reconnects if necessary +func (client *ldapClient) reconnect() error { + client.mu.Lock() + defer client.mu.Unlock() + + // Check if the connection is still alive + if client.conn.IsClosing() { + return client.connect() + } + return nil +} + +// findObjectByGUID searches for an AD object by GUID and returns its Common Name (CN) +func (client *ldapClient) findObjectByGUID(objectGUID string) (string, error) { + // Ensure the connection is alive or reconnect if necessary + if err := client.reconnect(); err != nil { + return "", fmt.Errorf("failed to reconnect: %v", err) + } + + client.mu.Lock() + defer client.mu.Unlock() + + // Format the GUID filter and perform the search + filter := fmt.Sprintf("(objectGUID=%s)", encodeGUID(objectGUID)) + searchRequest := ldap.NewSearchRequest( + client.baseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, client.searchTimeLimit, false, + filter, []string{"cn"}, nil, + ) + + // Execute search + result, err := client.conn.Search(searchRequest) + if err != nil { + return "", fmt.Errorf("search failed: %v", err) + } + if len(result.Entries) == 0 { + return "", fmt.Errorf("no entries found for GUID %s", objectGUID) + } + + // Retrieve the CN attribute + cn := result.Entries[0].GetAttributeValue("cn") + return cn, nil +} + +// encodeGUID converts a GUID into LDAP filter format +func encodeGUID(guid string) string { + return fmt.Sprintf("\\%s", strings.Trim(guid, "{}")) +} + +// close closes the LDAP connection +func (client *ldapClient) close() { + client.mu.Lock() + defer client.mu.Unlock() + if client.conn != nil { + client.conn.Close() + } +} diff --git a/libbeat/processors/translate_guid/translateguid.go b/libbeat/processors/translate_guid/translateguid.go new file mode 100644 index 00000000000..42816e46dbb --- /dev/null +++ b/libbeat/processors/translate_guid/translateguid.go @@ -0,0 +1,126 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package translate_guid + +import ( + "errors" + "fmt" + + "github.com/elastic/beats/v7/libbeat/beat" + "github.com/elastic/beats/v7/libbeat/processors" + jsprocessor "github.com/elastic/beats/v7/libbeat/processors/script/javascript/module/processor" + conf "github.com/elastic/elastic-agent-libs/config" + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + "github.com/elastic/elastic-agent-libs/transport/tlscommon" +) + +const logName = "processor.translate_guid" + +var errInvalidType = errors.New("GUID field value is not a string") + +func init() { + processors.RegisterPlugin("translate_guid", New) + jsprocessor.RegisterPlugin("TranslateGUID", New) +} + +type processor struct { + config + client *ldapClient + log *logp.Logger +} + +// New returns a new translate_guid processor for converting windows GUID values +// to object names. +func New(cfg *conf.C) (beat.Processor, error) { + c := defaultConfig() + if err := cfg.Unpack(&c); err != nil { + return nil, fmt.Errorf("fail to unpack the translate_guid configuration: %w", err) + } + + return newFromConfig(c) +} + +func newFromConfig(c config) (*processor, error) { + ldapConfig := &ldapConfig{ + address: c.LDAPAddress, + baseDN: c.LDAPBaseDN, + username: c.LDAPUser, + password: c.LDAPPassword, + searchTimeLimit: c.LDAPSearchTimeLimit, + } + if c.LDAPTLS != nil { + tlsConfig, err := tlscommon.LoadTLSConfig(c.LDAPTLS) + if err != nil { + return nil, fmt.Errorf("could not load provided LDAP TLS configuration: %w", err) + } + ldapConfig.tlsConfig = tlsConfig.ToConfig() + } + client, err := newLDAPClient(ldapConfig) + if err != nil { + return nil, err + } + return &processor{ + config: c, + client: client, + log: logp.NewLogger(logName), + }, nil +} + +func (p *processor) String() string { + return fmt.Sprintf("translate_guid=[field=%s, ldap_address=%s, ldap_base_dn=%s, ldap_user=%s]", + p.Field, p.LDAPAddress, p.LDAPBaseDN, p.LDAPUser) +} + +func (p *processor) Run(event *beat.Event) (*beat.Event, error) { + err := p.translateGUID(event) + if err == nil || p.IgnoreFailure || (p.IgnoreMissing && errors.Is(err, mapstr.ErrKeyNotFound)) { + return event, nil + } + return event, err +} + +func (p *processor) translateGUID(event *beat.Event) error { + v, err := event.GetValue(p.Field) + if err != nil { + return err + } + + guidString, ok := v.(string) + if !ok { + return errInvalidType + } + + // XXX: May want to introduce an in-memory cache if the lookups are time consuming. + cn, err := p.client.findObjectByGUID(guidString) + if err != nil { + return err + } + + field := p.Field + if p.TargetField != "" { + field = p.TargetField + } + _, err = event.PutValue(field, cn) + return err +} + +func (p *processor) Close() error { + p.client.close() + return nil +} From 6b17a76ae18ae4c64b85050f41d31afa38594fab Mon Sep 17 00:00:00 2001 From: Marc Guasch Date: Tue, 29 Oct 2024 09:14:12 -0700 Subject: [PATCH 02/10] Add cache to docs --- .../docs/translate_guid.asciidoc | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/libbeat/processors/translate_guid/docs/translate_guid.asciidoc b/libbeat/processors/translate_guid/docs/translate_guid.asciidoc index 7405dd7765b..98c0ec93cf9 100644 --- a/libbeat/processors/translate_guid/docs/translate_guid.asciidoc +++ b/libbeat/processors/translate_guid/docs/translate_guid.asciidoc @@ -46,3 +46,36 @@ The `translate_guid` processor has the following configuration settings: |====== * Also see <> for a full description of the `ldap_ssl` options. + +If the searches are slow or you expect a high amount of GUID's to be found, consider using a cache processor to speed processing: + + +[source,yaml] +------------------------------------------------------------------------------- +processors: + - cache: + backend: + memory: + id: ldapguids + get: + key_field: guid.cn + target_field: winlog.common_name + ignore_missing: true + - if: + not: + - has_fields: winlog.common_name + then: + - translate_guid: + field: winlog.event_data.ObjectGuid + target_field: winlog.common_name + ldap_address: "ldap://" + ldap_base_dn: "dc=example,dc=com" + - cache: + backend: + memory: + id: ldapguids + capacity: 10000 + put: + key_field: guid.cn + value_field: winlog.common_name +------------------------------------------------------------------------------- \ No newline at end of file From f30ad4a53d35eca24c65e13e30780d3082856496 Mon Sep 17 00:00:00 2001 From: Marc Guasch Date: Thu, 31 Oct 2024 10:27:08 +0100 Subject: [PATCH 03/10] Add more config options --- libbeat/cmd/instance/imports_common.go | 1 + libbeat/processors/translate_guid/config.go | 11 ++++-- .../docs/translate_guid.asciidoc | 35 +++++++++++-------- libbeat/processors/translate_guid/ldap.go | 30 +++++++--------- .../translate_guid/translateguid.go | 11 +++--- 5 files changed, 48 insertions(+), 40 deletions(-) diff --git a/libbeat/cmd/instance/imports_common.go b/libbeat/cmd/instance/imports_common.go index be4174c0ea0..7fc6953cc95 100644 --- a/libbeat/cmd/instance/imports_common.go +++ b/libbeat/cmd/instance/imports_common.go @@ -43,6 +43,7 @@ import ( _ "github.com/elastic/beats/v7/libbeat/processors/registered_domain" _ "github.com/elastic/beats/v7/libbeat/processors/script" _ "github.com/elastic/beats/v7/libbeat/processors/syslog" + _ "github.com/elastic/beats/v7/libbeat/processors/translate_guid" _ "github.com/elastic/beats/v7/libbeat/processors/translate_sid" _ "github.com/elastic/beats/v7/libbeat/processors/urldecode" _ "github.com/elastic/beats/v7/libbeat/publisher/includes" // Register publisher pipeline modules diff --git a/libbeat/processors/translate_guid/config.go b/libbeat/processors/translate_guid/config.go index 51b12fec2b0..c18e8e89071 100644 --- a/libbeat/processors/translate_guid/config.go +++ b/libbeat/processors/translate_guid/config.go @@ -26,8 +26,10 @@ type config struct { TargetField string `config:"target_field"` LDAPAddress string `config:"ldap_address" validate:"required"` LDAPBaseDN string `config:"ldap_base_dn" validate:"required"` - LDAPUser string `config:"ldap_user"` - LDAPPassword string `config:"ldap_password"` + LDAPBindUser string `config:"ldap_bind_user"` + LDAPBindPassword string `config:"ldap_bind_password"` + LDAPGUIDAttribute string `config:"ldap_guid_attribute" validate:"required"` + LDAPMappedAttribute string `config:"ldap_mapped_attribute" validate:"required"` LDAPSearchTimeLimit int `config:"ldap_search_time_limit"` LDAPTLS *tlscommon.Config `config:"ldap_ssl"` @@ -36,5 +38,8 @@ type config struct { } func defaultConfig() config { - return config{LDAPSearchTimeLimit: 30} + return config{ + LDAPGUIDAttribute: "objectGUID", + LDAPMappedAttribute: "cn", + LDAPSearchTimeLimit: 30} } diff --git a/libbeat/processors/translate_guid/docs/translate_guid.asciidoc b/libbeat/processors/translate_guid/docs/translate_guid.asciidoc index 98c0ec93cf9..cc6e0dc95b1 100644 --- a/libbeat/processors/translate_guid/docs/translate_guid.asciidoc +++ b/libbeat/processors/translate_guid/docs/translate_guid.asciidoc @@ -8,7 +8,7 @@ The `translate_guid` processor translates an LDAP Global Unique Identifier (GUID) into its common name. -Every object on an Active Directory is issued a GUID. Internal processes +Every object on an Active Directory or an LDAP server is issued a GUID. Internal processes refer to their GUID's rather than the object's name and these values sometimes appear in logs. @@ -16,6 +16,11 @@ If the GUID is invalid (malformed) or does not map to any object on the domain then this will result in the processor returning an error unless `ignore_failure` is set. +Both the attribute referring to the GUID and the one to map it to, can be changed. + +The result of this operation is an array of values, given that a single attribute +can hold multiple values. + [source,yaml] ---- processors: @@ -32,17 +37,19 @@ The `translate_guid` processor has the following configuration settings: .Translate GUID options [options="header"] |====== -| Name | Required | Default | Description -| `field` | yes | | Source field containing a GUID. -| `target_field` | no | | Target field for the common name. If not set it will be replaced in place. -| `ldap_address` | yes | | LDAP server address. eg: `ldap://ds.example.com:389` -| `ldap_base_dn` | yes | | LDAP base DN. eg: `dc=example,dc=com` -| `ldap_user` | no | | LDAP user. -| `ldap_password` | no | | LDAP password. -| `ldap_search_time_limit` | no | 30 | LDAP search time limit in seconds. -| `ldap_ssl`* | no | 30 | LDAP TLS/SSL connection settings. -| `ignore_missing` | no | false | Ignore errors when the source field is missing. -| `ignore_failure` | no | false | Ignore all errors produced by the processor. +| Name | Required | Default | Description +| `field` | yes | | Source field containing a GUID. +| `target_field` | no | | Target field for the common name. If not set it will be replaced in place. +| `ldap_address` | yes | | LDAP server address. eg: `ldap://ds.example.com:389` +| `ldap_base_dn` | yes | | LDAP base DN. eg: `dc=example,dc=com` +| `ldap_bind_user` | no | | LDAP user. +| `ldap_bind_password` | no | | LDAP password. +| `ldap_guid_attribute` | yes | `objectGUID` | LDAP attribute containing the GUID. +| `ldap_mapped_attribute` | yes | `cn` | LDAP attribute that will be mapped to the GUID. +| `ldap_search_time_limit` | no | 30 | LDAP search time limit in seconds. +| `ldap_ssl`* | no | 30 | LDAP TLS/SSL connection settings. +| `ignore_missing` | no | false | Ignore errors when the source field is missing. +| `ignore_failure` | no | false | Ignore all errors produced by the processor. |====== * Also see <> for a full description of the `ldap_ssl` options. @@ -58,7 +65,7 @@ processors: memory: id: ldapguids get: - key_field: guid.cn + key_field: winlog.event_data.ObjectGuid target_field: winlog.common_name ignore_missing: true - if: @@ -76,6 +83,6 @@ processors: id: ldapguids capacity: 10000 put: - key_field: guid.cn + key_field: winlog.event_data.ObjectGuid value_field: winlog.common_name ------------------------------------------------------------------------------- \ No newline at end of file diff --git a/libbeat/processors/translate_guid/ldap.go b/libbeat/processors/translate_guid/ldap.go index 8ffb351f7c1..b4becf6b62c 100644 --- a/libbeat/processors/translate_guid/ldap.go +++ b/libbeat/processors/translate_guid/ldap.go @@ -20,7 +20,6 @@ package translate_guid import ( "crypto/tls" "fmt" - "strings" "sync" "github.com/go-ldap/ldap/v3" @@ -38,6 +37,8 @@ type ldapConfig struct { baseDN string username string password string + guidAttr string + mappedAttr string searchTimeLimit int tlsConfig *tls.Config } @@ -60,13 +61,11 @@ func (client *ldapClient) connect() error { defer client.mu.Unlock() // Connect with or without TLS based on configuration - var conn *ldap.Conn - var err error + var opts []ldap.DialOpt if client.tlsConfig != nil { - conn, err = ldap.DialTLS("tcp", client.address, client.tlsConfig) - } else { - conn, err = ldap.Dial("tcp", client.address) + opts = append(opts, ldap.DialWithTLSConfig(client.tlsConfig)) } + conn, err := ldap.DialURL(client.address, opts...) if err != nil { return fmt.Errorf("failed to dial LDAP server: %v", err) } @@ -99,42 +98,37 @@ func (client *ldapClient) reconnect() error { } // findObjectByGUID searches for an AD object by GUID and returns its Common Name (CN) -func (client *ldapClient) findObjectByGUID(objectGUID string) (string, error) { +func (client *ldapClient) findObjectByGUID(objectGUID string) ([]string, error) { // Ensure the connection is alive or reconnect if necessary if err := client.reconnect(); err != nil { - return "", fmt.Errorf("failed to reconnect: %v", err) + return nil, fmt.Errorf("failed to reconnect: %v", err) } client.mu.Lock() defer client.mu.Unlock() // Format the GUID filter and perform the search - filter := fmt.Sprintf("(objectGUID=%s)", encodeGUID(objectGUID)) + filter := fmt.Sprintf("(%s=%s)", client.guidAttr, objectGUID) searchRequest := ldap.NewSearchRequest( client.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, client.searchTimeLimit, false, - filter, []string{"cn"}, nil, + filter, []string{client.mappedAttr}, nil, ) // Execute search result, err := client.conn.Search(searchRequest) if err != nil { - return "", fmt.Errorf("search failed: %v", err) + return nil, fmt.Errorf("search failed: %v", err) } if len(result.Entries) == 0 { - return "", fmt.Errorf("no entries found for GUID %s", objectGUID) + return nil, fmt.Errorf("no entries found for GUID %s", objectGUID) } // Retrieve the CN attribute - cn := result.Entries[0].GetAttributeValue("cn") + cn := result.Entries[0].GetAttributeValues(client.mappedAttr) return cn, nil } -// encodeGUID converts a GUID into LDAP filter format -func encodeGUID(guid string) string { - return fmt.Sprintf("\\%s", strings.Trim(guid, "{}")) -} - // close closes the LDAP connection func (client *ldapClient) close() { client.mu.Lock() diff --git a/libbeat/processors/translate_guid/translateguid.go b/libbeat/processors/translate_guid/translateguid.go index 42816e46dbb..61365b994c6 100644 --- a/libbeat/processors/translate_guid/translateguid.go +++ b/libbeat/processors/translate_guid/translateguid.go @@ -60,8 +60,10 @@ func newFromConfig(c config) (*processor, error) { ldapConfig := &ldapConfig{ address: c.LDAPAddress, baseDN: c.LDAPBaseDN, - username: c.LDAPUser, - password: c.LDAPPassword, + username: c.LDAPBindUser, + password: c.LDAPBindPassword, + guidAttr: c.LDAPGUIDAttribute, + mappedAttr: c.LDAPMappedAttribute, searchTimeLimit: c.LDAPSearchTimeLimit, } if c.LDAPTLS != nil { @@ -83,8 +85,8 @@ func newFromConfig(c config) (*processor, error) { } func (p *processor) String() string { - return fmt.Sprintf("translate_guid=[field=%s, ldap_address=%s, ldap_base_dn=%s, ldap_user=%s]", - p.Field, p.LDAPAddress, p.LDAPBaseDN, p.LDAPUser) + return fmt.Sprintf("translate_guid=[field=%s, ldap_address=%s, ldap_base_dn=%s, ldap_bind_user=%s, ldap_guid_attribute=%s, ldap_mapped_attribute=%s]", + p.Field, p.LDAPAddress, p.LDAPBaseDN, p.LDAPBindUser, p.LDAPGUIDAttribute, p.LDAPMappedAttribute) } func (p *processor) Run(event *beat.Event) (*beat.Event, error) { @@ -106,7 +108,6 @@ func (p *processor) translateGUID(event *beat.Event) error { return errInvalidType } - // XXX: May want to introduce an in-memory cache if the lookups are time consuming. cn, err := p.client.findObjectByGUID(guidString) if err != nil { return err From b5688d6b0793d0ece07dcc6552e67a37c67efeb0 Mon Sep 17 00:00:00 2001 From: Marc Guasch Date: Thu, 31 Oct 2024 11:14:50 +0100 Subject: [PATCH 04/10] Add integration test for translate_guid processor --- .../tests/integration/translate_guid_test.go | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 filebeat/tests/integration/translate_guid_test.go diff --git a/filebeat/tests/integration/translate_guid_test.go b/filebeat/tests/integration/translate_guid_test.go new file mode 100644 index 00000000000..9316e28f1f1 --- /dev/null +++ b/filebeat/tests/integration/translate_guid_test.go @@ -0,0 +1,217 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build integration + +package integration + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "testing" + "time" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + "github.com/go-ldap/ldap/v3" + "github.com/stretchr/testify/require" + + "github.com/elastic/beats/v7/libbeat/tests/integration" + "github.com/elastic/elastic-agent-autodiscover/docker" +) + +const translateguidCfg = ` +filebeat.inputs: + - type: filestream + id: "test-translateguidCfg" + paths: + - %s + +queue.mem: + flush.min_events: 1 + flush.timeout: 0.1s + +path.home: %s + +output.file: + path: ${path.home} + filename: "output-file" + +logging: + metrics: + enabled: false + +processors: + - add_fields: + fields: + guid: '%s' + - translate_guid: + field: fields.guid + target_field: fields.common_name + ldap_address: 'ldap://localhost:1389' + ldap_base_dn: 'dc=example,dc=org' + ldap_bind_user: 'cn=admin,dc=example,dc=org' + ldap_bind_password: 'adminpassword' + ldap_guid_attribute: 'entryUUID' +` + +func TestTranslateGUIDWithLDAP(t *testing.T) { + startOpenldapContainer(t) + + var entryUUID string + require.Eventually(t, func() bool { + var err error + entryUUID, err = getLDAPUserEntryUUID() + if err != nil { + return false + } + return true + }, 10*time.Second, time.Second) + + filebeat := integration.NewBeat( + t, + "filebeat", + "../../filebeat.test", + ) + tempDir := filebeat.TempDir() + + // 1. Generate the log file path + logFilePath := path.Join(tempDir, "log.log") + integration.GenerateLogFile(t, logFilePath, 1, false) + + // 2. Write configuration file and start Filebeat + filebeat.WriteConfigFile( + fmt.Sprintf(translateguidCfg, logFilePath, tempDir, entryUUID), + ) + filebeat.Start() + + var outputFile string + require.Eventually(t, func() bool { + outputFiles, err := filepath.Glob(path.Join(tempDir, "output-file-*.ndjson")) + if err != nil { + return false + } + if len(outputFiles) != 1 { + return false + } + outputFile = outputFiles[0] + return true + }, 10*time.Second, time.Second) + + // 3. Wait for the event with the expected translated guid + filebeat.WaitFileContains( + outputFile, + fmt.Sprintf(`"fields":{"guid":"%s","common_name":["User1","user01"]}`, entryUUID), + 10*time.Second, + ) +} + +func startOpenldapContainer(t *testing.T) { + ctx := context.Background() + c, err := docker.NewClient(client.DefaultDockerHost, nil, nil) + if err != nil { + t.Fatal(err) + } + + reader, err := c.ImagePull(ctx, "bitnami/openldap:2", image.PullOptions{}) + if err != nil { + t.Fatal(err) + } + io.Copy(os.Stdout, reader) + reader.Close() + + resp, err := c.ContainerCreate(ctx, + &container.Config{ + Image: "bitnami/openldap:2", + ExposedPorts: nat.PortSet{ + "1389/tcp": struct{}{}, + }, + Env: []string{ + "LDAP_URI=ldap://openldap:1389", + "LDAP_BASE=dc=example,dc=org", + "LDAP_BIND_DN=cn=admin,dc=example,dc=org", + "LDAP_BIND_PASSWORD=adminpassword", + }, + }, + &container.HostConfig{ + PortBindings: nat.PortMap{ + "1389/tcp": []nat.PortBinding{ + { + HostIP: "0.0.0.0", + HostPort: "1389", + }, + }, + }, + }, nil, nil, "") + if err != nil { + t.Fatal(err) + } + + if err := c.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { + defer c.Close() + if err := c.ContainerRemove(ctx, resp.ID, container.RemoveOptions{RemoveVolumes: true, Force: true}); err != nil { + t.Error(err) + } + }) +} + +func getLDAPUserEntryUUID() (string, error) { + // Connect to the LDAP server + l, err := ldap.DialURL("ldap://localhost:1389") + if err != nil { + return "", fmt.Errorf("failed to connect to LDAP server: %v", err) + } + defer l.Close() + + err = l.Bind("cn=admin,dc=example,dc=org", "adminpassword") + if err != nil { + return "", fmt.Errorf("failed to bind to LDAP server: %v", err) + } + + searchRequest := ldap.NewSearchRequest( + "dc=example,dc=org", + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false, + "(cn=User1)", []string{"entryUUID"}, nil, + ) + + sr, err := l.Search(searchRequest) + if err != nil { + return "", fmt.Errorf("failed to execute search: %v", err) + } + + // Process search results + if len(sr.Entries) == 0 { + return "", errors.New("no entries found for the specified username.") + } + entry := sr.Entries[0] + entryUUID := entry.GetAttributeValue("entryUUID") + if entryUUID == "" { + return "", errors.New("entryUUID is empty") + } + return entryUUID, nil +} From 06f067c945edab0f29d27d37c7e4db096e953d01 Mon Sep 17 00:00:00 2001 From: Marc Guasch Date: Thu, 31 Oct 2024 12:18:35 +0100 Subject: [PATCH 05/10] Rename processor --- .../config.go | 6 ++-- .../doc.go | 5 ++-- .../docs/translate_ldap_attribute.asciidoc} | 28 +++++++++++-------- .../ldap.go | 14 +++++----- .../translate_ldap_attribute.go} | 26 ++++++++--------- 5 files changed, 41 insertions(+), 38 deletions(-) rename libbeat/processors/{translate_guid => translate_ldap_attribute}/config.go (91%) rename libbeat/processors/{translate_guid => translate_ldap_attribute}/doc.go (81%) rename libbeat/processors/{translate_guid/docs/translate_guid.asciidoc => translate_ldap_attribute/docs/translate_ldap_attribute.asciidoc} (75%) rename libbeat/processors/{translate_guid => translate_ldap_attribute}/ldap.go (89%) rename libbeat/processors/{translate_guid/translateguid.go => translate_ldap_attribute/translate_ldap_attribute.go} (76%) diff --git a/libbeat/processors/translate_guid/config.go b/libbeat/processors/translate_ldap_attribute/config.go similarity index 91% rename from libbeat/processors/translate_guid/config.go rename to libbeat/processors/translate_ldap_attribute/config.go index c18e8e89071..b6b46410e98 100644 --- a/libbeat/processors/translate_guid/config.go +++ b/libbeat/processors/translate_ldap_attribute/config.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package translate_guid +package translate_ldap_attribute import ( "github.com/elastic/elastic-agent-libs/transport/tlscommon" @@ -28,7 +28,7 @@ type config struct { LDAPBaseDN string `config:"ldap_base_dn" validate:"required"` LDAPBindUser string `config:"ldap_bind_user"` LDAPBindPassword string `config:"ldap_bind_password"` - LDAPGUIDAttribute string `config:"ldap_guid_attribute" validate:"required"` + LDAPSearchAttribute string `config:"ldap_search_attribute" validate:"required"` LDAPMappedAttribute string `config:"ldap_mapped_attribute" validate:"required"` LDAPSearchTimeLimit int `config:"ldap_search_time_limit"` LDAPTLS *tlscommon.Config `config:"ldap_ssl"` @@ -39,7 +39,7 @@ type config struct { func defaultConfig() config { return config{ - LDAPGUIDAttribute: "objectGUID", + LDAPSearchAttribute: "objectGUID", LDAPMappedAttribute: "cn", LDAPSearchTimeLimit: 30} } diff --git a/libbeat/processors/translate_guid/doc.go b/libbeat/processors/translate_ldap_attribute/doc.go similarity index 81% rename from libbeat/processors/translate_guid/doc.go rename to libbeat/processors/translate_ldap_attribute/doc.go index b0d4ec27efb..70ceee7297d 100644 --- a/libbeat/processors/translate_guid/doc.go +++ b/libbeat/processors/translate_ldap_attribute/doc.go @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. -// Package translate_guid provides a Beat processor for converting Windows +// Package translate_ldap_attribute provides a Beat processor for converting +// LDAP attributes from one to another. It is typically used for converting Windows // Global Unique Identifiers (GUIDs) to object names. -package translate_guid +package translate_ldap_attribute diff --git a/libbeat/processors/translate_guid/docs/translate_guid.asciidoc b/libbeat/processors/translate_ldap_attribute/docs/translate_ldap_attribute.asciidoc similarity index 75% rename from libbeat/processors/translate_guid/docs/translate_guid.asciidoc rename to libbeat/processors/translate_ldap_attribute/docs/translate_ldap_attribute.asciidoc index cc6e0dc95b1..fd205599acc 100644 --- a/libbeat/processors/translate_guid/docs/translate_guid.asciidoc +++ b/libbeat/processors/translate_ldap_attribute/docs/translate_ldap_attribute.asciidoc @@ -2,29 +2,32 @@ === Translate GUID ++++ -translate_guid +translate_ldap_attribute ++++ -The `translate_guid` processor translates an LDAP Global Unique Identifier (GUID) -into its common name. +The `translate_ldap_attribute` processor translates an LDAP attributes between eachother. +It is typically used to translate AD Global Unique Identifiers (GUID) +into their common names. Every object on an Active Directory or an LDAP server is issued a GUID. Internal processes refer to their GUID's rather than the object's name and these values sometimes appear in logs. -If the GUID is invalid (malformed) or does not map to any object on the domain +If the search attribute is invalid (malformed) or does not map to any object on the domain then this will result in the processor returning an error unless `ignore_failure` is set. -Both the attribute referring to the GUID and the one to map it to, can be changed. - The result of this operation is an array of values, given that a single attribute can hold multiple values. +Note: the search attribute is expected to map to a single object. If it doesn't, +no error will be returned, but only results of the first entry will be added +to the event. + [source,yaml] ---- processors: - - translate_guid: + - translate_ldap_attribute: field: winlog.event_data.ObjectGuid ldap_address: "ldap://" ldap_base_dn: "dc=example,dc=com" @@ -32,7 +35,7 @@ processors: ignore_failure: true ---- -The `translate_guid` processor has the following configuration settings: +The `translate_ldap_attribute` processor has the following configuration settings: .Translate GUID options [options="header"] @@ -44,8 +47,8 @@ The `translate_guid` processor has the following configuration settings: | `ldap_base_dn` | yes | | LDAP base DN. eg: `dc=example,dc=com` | `ldap_bind_user` | no | | LDAP user. | `ldap_bind_password` | no | | LDAP password. -| `ldap_guid_attribute` | yes | `objectGUID` | LDAP attribute containing the GUID. -| `ldap_mapped_attribute` | yes | `cn` | LDAP attribute that will be mapped to the GUID. +| `ldap_search_attribute` | yes | `objectGUID` | LDAP attribute to search by. +| `ldap_mapped_attribute` | yes | `cn` | LDAP attribute to map to. | `ldap_search_time_limit` | no | 30 | LDAP search time limit in seconds. | `ldap_ssl`* | no | 30 | LDAP TLS/SSL connection settings. | `ignore_missing` | no | false | Ignore errors when the source field is missing. @@ -54,7 +57,8 @@ The `translate_guid` processor has the following configuration settings: * Also see <> for a full description of the `ldap_ssl` options. -If the searches are slow or you expect a high amount of GUID's to be found, consider using a cache processor to speed processing: +If the searches are slow or you expect a high amount of different key attributes to be found, +consider using a cache processor to speed processing: [source,yaml] @@ -72,7 +76,7 @@ processors: not: - has_fields: winlog.common_name then: - - translate_guid: + - translate_ldap_attribute: field: winlog.event_data.ObjectGuid target_field: winlog.common_name ldap_address: "ldap://" diff --git a/libbeat/processors/translate_guid/ldap.go b/libbeat/processors/translate_ldap_attribute/ldap.go similarity index 89% rename from libbeat/processors/translate_guid/ldap.go rename to libbeat/processors/translate_ldap_attribute/ldap.go index b4becf6b62c..9df10da2936 100644 --- a/libbeat/processors/translate_guid/ldap.go +++ b/libbeat/processors/translate_ldap_attribute/ldap.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package translate_guid +package translate_ldap_attribute import ( "crypto/tls" @@ -37,7 +37,7 @@ type ldapConfig struct { baseDN string username string password string - guidAttr string + searchAttr string mappedAttr string searchTimeLimit int tlsConfig *tls.Config @@ -97,8 +97,8 @@ func (client *ldapClient) reconnect() error { return nil } -// findObjectByGUID searches for an AD object by GUID and returns its Common Name (CN) -func (client *ldapClient) findObjectByGUID(objectGUID string) ([]string, error) { +// findObjectBy searches for an object and returns its mapped values. +func (client *ldapClient) findObjectBy(searchBy string) ([]string, error) { // Ensure the connection is alive or reconnect if necessary if err := client.reconnect(); err != nil { return nil, fmt.Errorf("failed to reconnect: %v", err) @@ -107,8 +107,8 @@ func (client *ldapClient) findObjectByGUID(objectGUID string) ([]string, error) client.mu.Lock() defer client.mu.Unlock() - // Format the GUID filter and perform the search - filter := fmt.Sprintf("(%s=%s)", client.guidAttr, objectGUID) + // Format the filter and perform the search + filter := fmt.Sprintf("(%s=%s)", client.searchAttr, searchBy) searchRequest := ldap.NewSearchRequest( client.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, client.searchTimeLimit, false, @@ -121,7 +121,7 @@ func (client *ldapClient) findObjectByGUID(objectGUID string) ([]string, error) return nil, fmt.Errorf("search failed: %v", err) } if len(result.Entries) == 0 { - return nil, fmt.Errorf("no entries found for GUID %s", objectGUID) + return nil, fmt.Errorf("no entries found for search attribute %s", searchBy) } // Retrieve the CN attribute diff --git a/libbeat/processors/translate_guid/translateguid.go b/libbeat/processors/translate_ldap_attribute/translate_ldap_attribute.go similarity index 76% rename from libbeat/processors/translate_guid/translateguid.go rename to libbeat/processors/translate_ldap_attribute/translate_ldap_attribute.go index 61365b994c6..dec72263cfd 100644 --- a/libbeat/processors/translate_guid/translateguid.go +++ b/libbeat/processors/translate_ldap_attribute/translate_ldap_attribute.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package translate_guid +package translate_ldap_attribute import ( "errors" @@ -30,13 +30,13 @@ import ( "github.com/elastic/elastic-agent-libs/transport/tlscommon" ) -const logName = "processor.translate_guid" +const logName = "processor.translate_ldap_attribute" -var errInvalidType = errors.New("GUID field value is not a string") +var errInvalidType = errors.New("search attribute field value is not a string") func init() { - processors.RegisterPlugin("translate_guid", New) - jsprocessor.RegisterPlugin("TranslateGUID", New) + processors.RegisterPlugin("translate_ldap_attribute", New) + jsprocessor.RegisterPlugin("TranslateLDAPAttribute", New) } type processor struct { @@ -45,12 +45,10 @@ type processor struct { log *logp.Logger } -// New returns a new translate_guid processor for converting windows GUID values -// to object names. func New(cfg *conf.C) (beat.Processor, error) { c := defaultConfig() if err := cfg.Unpack(&c); err != nil { - return nil, fmt.Errorf("fail to unpack the translate_guid configuration: %w", err) + return nil, fmt.Errorf("fail to unpack the translate_ldap_attribute configuration: %w", err) } return newFromConfig(c) @@ -62,7 +60,7 @@ func newFromConfig(c config) (*processor, error) { baseDN: c.LDAPBaseDN, username: c.LDAPBindUser, password: c.LDAPBindPassword, - guidAttr: c.LDAPGUIDAttribute, + searchAttr: c.LDAPSearchAttribute, mappedAttr: c.LDAPMappedAttribute, searchTimeLimit: c.LDAPSearchTimeLimit, } @@ -85,19 +83,19 @@ func newFromConfig(c config) (*processor, error) { } func (p *processor) String() string { - return fmt.Sprintf("translate_guid=[field=%s, ldap_address=%s, ldap_base_dn=%s, ldap_bind_user=%s, ldap_guid_attribute=%s, ldap_mapped_attribute=%s]", - p.Field, p.LDAPAddress, p.LDAPBaseDN, p.LDAPBindUser, p.LDAPGUIDAttribute, p.LDAPMappedAttribute) + return fmt.Sprintf("translate_ldap_attribute=[field=%s, ldap_address=%s, ldap_base_dn=%s, ldap_bind_user=%s, ldap_search_attribute=%s, ldap_mapped_attribute=%s]", + p.Field, p.LDAPAddress, p.LDAPBaseDN, p.LDAPBindUser, p.LDAPSearchAttribute, p.LDAPMappedAttribute) } func (p *processor) Run(event *beat.Event) (*beat.Event, error) { - err := p.translateGUID(event) + err := p.translateLDAPAttr(event) if err == nil || p.IgnoreFailure || (p.IgnoreMissing && errors.Is(err, mapstr.ErrKeyNotFound)) { return event, nil } return event, err } -func (p *processor) translateGUID(event *beat.Event) error { +func (p *processor) translateLDAPAttr(event *beat.Event) error { v, err := event.GetValue(p.Field) if err != nil { return err @@ -108,7 +106,7 @@ func (p *processor) translateGUID(event *beat.Event) error { return errInvalidType } - cn, err := p.client.findObjectByGUID(guidString) + cn, err := p.client.findObjectBy(guidString) if err != nil { return err } From bff2480e9e844c78f4b4e190a7ac54cd1072280d Mon Sep 17 00:00:00 2001 From: Marc Guasch Date: Thu, 31 Oct 2024 12:23:11 +0100 Subject: [PATCH 06/10] Fix reference to use new processor name --- CHANGELOG.next.asciidoc | 2 +- filebeat/tests/integration/translate_guid_test.go | 2 +- libbeat/cmd/instance/imports_common.go | 2 +- libbeat/docs/processors-list.asciidoc | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 8eb756e4bdf..753167fbfdc 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -111,7 +111,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff] - Set timeout of 1 minute for FQDN requests {pull}37756[37756] - Fix issue where old data could be saved in the memory queue after acknowledgment, increasing memory use {pull}41356[41356] - Ensure Elasticsearch output can always recover from network errors {pull}40794[40794] -- Add `translate_guid` processor for windows platforms. {pull}41472[41472] +- Add `translate_ldap_attribute` processor. {pull}41472[41472] *Auditbeat* diff --git a/filebeat/tests/integration/translate_guid_test.go b/filebeat/tests/integration/translate_guid_test.go index 9316e28f1f1..fcf141ea146 100644 --- a/filebeat/tests/integration/translate_guid_test.go +++ b/filebeat/tests/integration/translate_guid_test.go @@ -66,7 +66,7 @@ processors: - add_fields: fields: guid: '%s' - - translate_guid: + - translate_ldap_attribute: field: fields.guid target_field: fields.common_name ldap_address: 'ldap://localhost:1389' diff --git a/libbeat/cmd/instance/imports_common.go b/libbeat/cmd/instance/imports_common.go index 7fc6953cc95..eb33bc27fe3 100644 --- a/libbeat/cmd/instance/imports_common.go +++ b/libbeat/cmd/instance/imports_common.go @@ -43,7 +43,7 @@ import ( _ "github.com/elastic/beats/v7/libbeat/processors/registered_domain" _ "github.com/elastic/beats/v7/libbeat/processors/script" _ "github.com/elastic/beats/v7/libbeat/processors/syslog" - _ "github.com/elastic/beats/v7/libbeat/processors/translate_guid" + _ "github.com/elastic/beats/v7/libbeat/processors/translate_ldap_attribute" _ "github.com/elastic/beats/v7/libbeat/processors/translate_sid" _ "github.com/elastic/beats/v7/libbeat/processors/urldecode" _ "github.com/elastic/beats/v7/libbeat/publisher/includes" // Register publisher pipeline modules diff --git a/libbeat/docs/processors-list.asciidoc b/libbeat/docs/processors-list.asciidoc index 63985fd8f5a..341875f9f96 100644 --- a/libbeat/docs/processors-list.asciidoc +++ b/libbeat/docs/processors-list.asciidoc @@ -131,8 +131,8 @@ endif::[] ifndef::no_timestamp_processor[] * <> endif::[] -ifndef::no_translate_guid_processor[] -* <> +ifndef::no_translate_ldap_attribute_processor[] +* <> endif::[] ifndef::no_translate_sid_processor[] * <> @@ -282,8 +282,8 @@ endif::[] ifndef::no_timestamp_processor[] include::{libbeat-processors-dir}/timestamp/docs/timestamp.asciidoc[] endif::[] -ifndef::no_translate_guid_processor[] -include::{libbeat-processors-dir}/translate_guid/docs/translate_guid.asciidoc[] +ifndef::no_translate_ldap_attribute_processor[] +include::{libbeat-processors-dir}/translate_ldap_attribute/docs/translate_ldap_attribute.asciidoc[] endif::[] ifndef::no_translate_sid_processor[] include::{libbeat-processors-dir}/translate_sid/docs/translate_sid.asciidoc[] From e2827a2dba70e86d7bafa357b19f4fbf82cc2e61 Mon Sep 17 00:00:00 2001 From: Marc Guasch Date: Thu, 31 Oct 2024 12:25:35 +0100 Subject: [PATCH 07/10] Rename field in test --- filebeat/tests/integration/translate_guid_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filebeat/tests/integration/translate_guid_test.go b/filebeat/tests/integration/translate_guid_test.go index fcf141ea146..4b834fd59db 100644 --- a/filebeat/tests/integration/translate_guid_test.go +++ b/filebeat/tests/integration/translate_guid_test.go @@ -73,7 +73,7 @@ processors: ldap_base_dn: 'dc=example,dc=org' ldap_bind_user: 'cn=admin,dc=example,dc=org' ldap_bind_password: 'adminpassword' - ldap_guid_attribute: 'entryUUID' + ldap_search_attribute: 'entryUUID' ` func TestTranslateGUIDWithLDAP(t *testing.T) { From 7c5fe2ebedd0c26cfe86956a4908461d200b39b2 Mon Sep 17 00:00:00 2001 From: Marc Guasch Date: Mon, 4 Nov 2024 08:40:06 +0100 Subject: [PATCH 08/10] Add codeowners, and various format fixes --- .github/CODEOWNERS | 1 + ...late_guid_test.go => translate_ldap_attribute_test.go} | 4 +++- libbeat/processors/translate_ldap_attribute/ldap.go | 8 ++++---- 3 files changed, 8 insertions(+), 5 deletions(-) rename filebeat/tests/integration/{translate_guid_test.go => translate_ldap_attribute_test.go} (98%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 49b9f58a4b6..8f060dc48cf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -68,6 +68,7 @@ CHANGELOG* /libbeat/processors/dns/ @elastic/sec-deployment-and-devices /libbeat/processors/registered_domain/ @elastic/sec-deployment-and-devices /libbeat/processors/syslog/ @elastic/sec-deployment-and-devices +/libbeat/processors/translate_ldap_attribute/ @elastic/sec-windows-platform /libbeat/processors/translate_sid/ @elastic/sec-windows-platform /libbeat/reader/syslog/ @elastic/sec-deployment-and-devices /libbeat/scripts @elastic/ingest-eng-prod diff --git a/filebeat/tests/integration/translate_guid_test.go b/filebeat/tests/integration/translate_ldap_attribute_test.go similarity index 98% rename from filebeat/tests/integration/translate_guid_test.go rename to filebeat/tests/integration/translate_ldap_attribute_test.go index 4b834fd59db..cd55bd49bbb 100644 --- a/filebeat/tests/integration/translate_guid_test.go +++ b/filebeat/tests/integration/translate_ldap_attribute_test.go @@ -138,7 +138,9 @@ func startOpenldapContainer(t *testing.T) { if err != nil { t.Fatal(err) } - io.Copy(os.Stdout, reader) + if _, err = io.Copy(os.Stdout, reader); err != nil { + t.Fatal(err) + } reader.Close() resp, err := c.ContainerCreate(ctx, diff --git a/libbeat/processors/translate_ldap_attribute/ldap.go b/libbeat/processors/translate_ldap_attribute/ldap.go index 9df10da2936..f83200e6652 100644 --- a/libbeat/processors/translate_ldap_attribute/ldap.go +++ b/libbeat/processors/translate_ldap_attribute/ldap.go @@ -67,7 +67,7 @@ func (client *ldapClient) connect() error { } conn, err := ldap.DialURL(client.address, opts...) if err != nil { - return fmt.Errorf("failed to dial LDAP server: %v", err) + return fmt.Errorf("failed to dial LDAP server: %w", err) } if client.password != "" { @@ -78,7 +78,7 @@ func (client *ldapClient) connect() error { if err != nil { conn.Close() - return fmt.Errorf("failed to bind to LDAP server: %v", err) + return fmt.Errorf("failed to bind to LDAP server: %w", err) } client.conn = conn @@ -101,7 +101,7 @@ func (client *ldapClient) reconnect() error { func (client *ldapClient) findObjectBy(searchBy string) ([]string, error) { // Ensure the connection is alive or reconnect if necessary if err := client.reconnect(); err != nil { - return nil, fmt.Errorf("failed to reconnect: %v", err) + return nil, fmt.Errorf("failed to reconnect: %w", err) } client.mu.Lock() @@ -118,7 +118,7 @@ func (client *ldapClient) findObjectBy(searchBy string) ([]string, error) { // Execute search result, err := client.conn.Search(searchRequest) if err != nil { - return nil, fmt.Errorf("search failed: %v", err) + return nil, fmt.Errorf("search failed: %w", err) } if len(result.Entries) == 0 { return nil, fmt.Errorf("no entries found for search attribute %s", searchBy) From 0d877407fa39031036535db8c67883c412609fa9 Mon Sep 17 00:00:00 2001 From: Marc Guasch Date: Mon, 4 Nov 2024 09:33:21 +0100 Subject: [PATCH 09/10] Fix test lint issues --- .../integration/translate_ldap_attribute_test.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/filebeat/tests/integration/translate_ldap_attribute_test.go b/filebeat/tests/integration/translate_ldap_attribute_test.go index cd55bd49bbb..e2b0f877efc 100644 --- a/filebeat/tests/integration/translate_ldap_attribute_test.go +++ b/filebeat/tests/integration/translate_ldap_attribute_test.go @@ -83,10 +83,7 @@ func TestTranslateGUIDWithLDAP(t *testing.T) { require.Eventually(t, func() bool { var err error entryUUID, err = getLDAPUserEntryUUID() - if err != nil { - return false - } - return true + return err == nil }, 10*time.Second, time.Second) filebeat := integration.NewBeat( @@ -186,13 +183,13 @@ func getLDAPUserEntryUUID() (string, error) { // Connect to the LDAP server l, err := ldap.DialURL("ldap://localhost:1389") if err != nil { - return "", fmt.Errorf("failed to connect to LDAP server: %v", err) + return "", fmt.Errorf("failed to connect to LDAP server: %w", err) } defer l.Close() err = l.Bind("cn=admin,dc=example,dc=org", "adminpassword") if err != nil { - return "", fmt.Errorf("failed to bind to LDAP server: %v", err) + return "", fmt.Errorf("failed to bind to LDAP server: %w", err) } searchRequest := ldap.NewSearchRequest( @@ -203,7 +200,7 @@ func getLDAPUserEntryUUID() (string, error) { sr, err := l.Search(searchRequest) if err != nil { - return "", fmt.Errorf("failed to execute search: %v", err) + return "", fmt.Errorf("failed to execute search: %w", err) } // Process search results From 6f8aaaeff2eb305bab3376af1f75c06e4df0227f Mon Sep 17 00:00:00 2001 From: Marc Guasch Date: Tue, 5 Nov 2024 14:20:28 +0100 Subject: [PATCH 10/10] Update libbeat/processors/translate_ldap_attribute/docs/translate_ldap_attribute.asciidoc --- .../docs/translate_ldap_attribute.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libbeat/processors/translate_ldap_attribute/docs/translate_ldap_attribute.asciidoc b/libbeat/processors/translate_ldap_attribute/docs/translate_ldap_attribute.asciidoc index fd205599acc..aff1125f43a 100644 --- a/libbeat/processors/translate_ldap_attribute/docs/translate_ldap_attribute.asciidoc +++ b/libbeat/processors/translate_ldap_attribute/docs/translate_ldap_attribute.asciidoc @@ -42,7 +42,7 @@ The `translate_ldap_attribute` processor has the following configuration setting |====== | Name | Required | Default | Description | `field` | yes | | Source field containing a GUID. -| `target_field` | no | | Target field for the common name. If not set it will be replaced in place. +| `target_field` | no | | Target field for the mapped attribute value. If not set it will be replaced in place. | `ldap_address` | yes | | LDAP server address. eg: `ldap://ds.example.com:389` | `ldap_base_dn` | yes | | LDAP base DN. eg: `dc=example,dc=com` | `ldap_bind_user` | no | | LDAP user.