From 55d6702ed48427e59f3e3e2522e38a87aa52b975 Mon Sep 17 00:00:00 2001 From: Dan Kortschak Date: Mon, 5 Feb 2024 12:43:03 +1030 Subject: [PATCH] x-pack/filebeat/input/entityanalytics/provider/activedirectory/internal/activedirectory: new package The activedirectory package provides a query interface to Active Directory user and group entities. Queries can be either a complete pull of the directory, or a subset of entries since a checkpoint time. --- CHANGELOG-developer.next.asciidoc | 3 +- NOTICE.txt | 132 ++++++++ go.mod | 3 + go.sum | 16 + .../activedirectory/activedirectory.go | 309 ++++++++++++++++++ .../activedirectory/activedirectory_test.go | 112 +++++++ 6 files changed, 574 insertions(+), 1 deletion(-) create mode 100644 x-pack/filebeat/input/entityanalytics/provider/activedirectory/internal/activedirectory/activedirectory.go create mode 100644 x-pack/filebeat/input/entityanalytics/provider/activedirectory/internal/activedirectory/activedirectory_test.go diff --git a/CHANGELOG-developer.next.asciidoc b/CHANGELOG-developer.next.asciidoc index 4e650a193d16..93238cc145b4 100644 --- a/CHANGELOG-developer.next.asciidoc +++ b/CHANGELOG-developer.next.asciidoc @@ -178,7 +178,8 @@ The list below covers the major changes between 7.0.0-rc2 and main only. - Add initial infrastructure for a caching enrichment processor. {pull}36619[36619] - Add file-backed cache for cache enrichment processor. {pull}36686[36686] {pull}36696[36696] - Elide retryable HTTP client construction in Filebeat HTTPJSON and CEL inputs if not needed. {pull}36916[36916] -- Allow assignment of packetbeat protocols to interfaces. {issue}36574[36564] {pull}[] +- Allow assignment of packetbeat protocols to interfaces. {issue}36574[36564] {pull}36852[36852] +- Add Active Directory entity collector for Filebeat entity analytics. {pull}37854[37854] ==== Deprecated diff --git a/NOTICE.txt b/NOTICE.txt index eea974cedd13..79312f244ef4 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -16205,6 +16205,38 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +Dependency : github.com/go-ldap/ldap/v3 +Version: v3.4.6 +Licence type (autodetected): MIT +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/go-ldap/ldap/v3@v3.4.6/LICENSE: + +The MIT License (MIT) + +Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com) +Portions copyright (c) 2015-2016 go-ldap Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + -------------------------------------------------------------------------------- Dependency : github.com/go-sql-driver/mysql Version: v1.6.0 @@ -30944,6 +30976,37 @@ Contents of probable licence file $GOMODCACHE/github.com/!azure/go-autorest/trac limitations under the License. +-------------------------------------------------------------------------------- +Dependency : github.com/Azure/go-ntlmssp +Version: v0.0.0-20221128193559-754e69321358 +Licence type (autodetected): MIT +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/!azure/go-ntlmssp@v0.0.0-20221128193559-754e69321358/LICENSE: + +The MIT License (MIT) + +Copyright (c) 2016 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + -------------------------------------------------------------------------------- Dependency : github.com/AzureAD/microsoft-authentication-library-for-go Version: v0.9.0 @@ -31303,6 +31366,43 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------------------------------------------------------------------- +Dependency : github.com/alexbrainman/sspi +Version: v0.0.0-20210105120005-909beea2cc74 +Licence type (autodetected): BSD-3-Clause +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/alexbrainman/sspi@v0.0.0-20210105120005-909beea2cc74/LICENSE: + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + -------------------------------------------------------------------------------- Dependency : github.com/andybalholm/brotli Version: v1.0.5 @@ -38260,6 +38360,38 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------------------------------------------------------------------- +Dependency : github.com/go-asn1-ber/asn1-ber +Version: v1.5.5 +Licence type (autodetected): MIT +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/go-asn1-ber/asn1-ber@v1.5.5/LICENSE: + +The MIT License (MIT) + +Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com) +Portions copyright (c) 2015-2016 go-asn1-ber Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + -------------------------------------------------------------------------------- Dependency : github.com/go-faker/faker/v4 Version: v4.2.0 diff --git a/go.mod b/go.mod index 1c29491fbba4..dafae56d0316 100644 --- a/go.mod +++ b/go.mod @@ -209,6 +209,7 @@ require ( github.com/elastic/mito v1.8.0 github.com/elastic/toutoumomoma v0.0.0-20221026030040-594ef30cb640 github.com/foxcpp/go-mockdns v0.0.0-20201212160233-ede2f9158d15 + github.com/go-ldap/ldap/v3 v3.4.6 github.com/google/cel-go v0.19.0 github.com/googleapis/gax-go/v2 v2.12.0 github.com/gorilla/handlers v1.5.1 @@ -245,6 +246,7 @@ require ( github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0 // indirect github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect github.com/andybalholm/brotli v1.0.5 // indirect @@ -281,6 +283,7 @@ require ( github.com/fearful-symmetry/gomsr v0.0.1 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect + github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/go.sum b/go.sum index 039364a70a4d..567b50b4fdf9 100644 --- a/go.sum +++ b/go.sum @@ -178,6 +178,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0 h1:UE9n9rkJF62ArLb1F3DEjRt8O3jLwMWdSoypKV4f3MU= github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= @@ -237,6 +239,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/andrewkroh/goja v0.0.0-20190128172624-dd2ac4456e20 h1:7rj9qZ63knnVo2ZeepYHvHuRdG76f3tRUTdIQDzRBeI= github.com/andrewkroh/goja v0.0.0-20190128172624-dd2ac4456e20/go.mod h1:cI59GRkC2FRaFYtgbYEqMlgnnfvAwXzjojyZKXwklNg= @@ -770,6 +774,8 @@ github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= +github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi v4.1.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-faker/faker/v4 v4.2.0 h1:dGebOupKwssrODV51E0zbMrv5e2gO9VWSLNC1WDCpWg= github.com/go-faker/faker/v4 v4.2.0/go.mod h1:F/bBy8GH9NxOxMInug5Gx4WYeG6fHJZ8Ol/dhcpRub4= @@ -788,6 +794,8 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= +github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -2048,6 +2056,7 @@ golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2190,6 +2199,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2356,7 +2366,9 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -2366,6 +2378,8 @@ golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2381,6 +2395,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/x-pack/filebeat/input/entityanalytics/provider/activedirectory/internal/activedirectory/activedirectory.go b/x-pack/filebeat/input/entityanalytics/provider/activedirectory/internal/activedirectory/activedirectory.go new file mode 100644 index 000000000000..e949523f57a2 --- /dev/null +++ b/x-pack/filebeat/input/entityanalytics/provider/activedirectory/internal/activedirectory/activedirectory.go @@ -0,0 +1,309 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// Package activedirectory provides Active Directory user and group query support. +package activedirectory + +import ( + "crypto/tls" + "errors" + "fmt" + "net" + "strconv" + "strings" + "time" + + "github.com/go-ldap/ldap/v3" +) + +var ( + ErrInvalidDistinguishedName = errors.New("invalid base distinguished name") + ErrGroups = errors.New("failed to get group details") + ErrUsers = errors.New("failed to get user details") +) + +var cnUsers = &ldap.RelativeDN{Attributes: []*ldap.AttributeTypeAndValue{{Type: "CN", Value: "Users"}}} + +// Entry is an Active Directory user entry with associated group membership. +type Entry struct { + ID string `json:"id"` + User map[string]any `json:"user"` + Groups []any `json:"groups,omitempty"` + Status string `json:"status"` + WhenChanged time.Time `json:"whenChanged"` +} + +// GetDetails returns all the users in the Active directory with the provided base +// on the host with the given ldap url (ldap://, ldaps://, ldapi:// or cldap://). +// Group membership details are collected and added to the returned documents. If +// the group query fails, the user details query will still be attempted, but +// a non-nil error indicating the failure will be returned. If since is non-zero +// only records with whenChanged since that time will be returned. since is +// expected to be configured in a time zone the Active Directory server will +// understand, most likely UTC. +func GetDetails(url, user, pass string, base *ldap.DN, since time.Time, pagingSize uint32, dialer *net.Dialer, tlsconfig *tls.Config) ([]Entry, error) { + if base == nil || len(base.RDNs) == 0 { + return nil, fmt.Errorf("%w: no path", ErrInvalidDistinguishedName) + } + baseDN := base.String() + if !base.RDNs[0].Equal(cnUsers) { + return nil, fmt.Errorf("%w: %s does not have %s", ErrInvalidDistinguishedName, baseDN, cnUsers) + } + + var opts []ldap.DialOpt + if dialer != nil { + opts = append(opts, ldap.DialWithDialer(dialer)) + } + if tlsconfig != nil { + opts = append(opts, ldap.DialWithTLSConfig(tlsconfig)) + } + conn, err := ldap.DialURL(url, opts...) + if err != nil { + return nil, err + } + + err = conn.Bind(user, pass) + if err != nil { + return nil, err + } + defer conn.Unbind() + + var errs []error + + // Format update epoch moment. + var sinceFmtd string + if !since.IsZero() { + const denseTimeLayout = "20060102150405.0Z" // Differs from the const below in resolution and behaviour. + sinceFmtd = since.Format(denseTimeLayout) + } + + // Get groups in the directory. + var groups directory + grpFilter := "(objectClass=group)" + if sinceFmtd != "" { + grpFilter = "(&(objectClass=group)(whenChanged>=" + sinceFmtd + "))" + } + grps, err := search(conn, baseDN, grpFilter, pagingSize) + if err != nil { + // Allow continuation if groups query fails, but warn. + errs = []error{fmt.Errorf("%w: %w", ErrGroups, err)} + groups.Entries = entries{} + } else { + groups = collate(grps, nil) + } + + // Get users in the directory... + userFilter := "(objectClass=user)" + if sinceFmtd != "" { + userFilter = "(&(objectClass=user)(whenChanged>=" + sinceFmtd + "))" + } + usrs, err := search(conn, baseDN, userFilter, pagingSize) + if err != nil { + errs = append(errs, fmt.Errorf("%w: %w", ErrUsers, err)) + return nil, errors.Join(errs...) + } + // ...and apply group membership. + users := collate(usrs, groups.Entries) + + // Assemble into a set of documents. + docs := make([]Entry, 0, len(users.Entries)) + for id, u := range users.Entries { + user := u["user"].(map[string]any) + var groups []any + switch g := u["groups"].(type) { + case nil: + case []any: + // Do not bother concretising these. + groups = g + } + docs = append(docs, Entry{ID: id, User: user, Groups: groups, WhenChanged: whenChanged(user, groups)}) + } + return docs, nil +} + +func whenChanged(user map[string]any, groups []any) time.Time { + l, _ := user["whenChanged"].(time.Time) + for _, g := range groups { + g, ok := g.(map[string]any) + if !ok { + continue + } + gl, ok := g["whenChanged"].(time.Time) + if !ok { + continue + } + if gl.After(l) { + l = gl + } + } + return l +} + +// search performs an LDAP filter search on conn at the LDAP base. If paging +// is non-zero, page sizing will be used. See [ldap.Conn.SearchWithPaging] for +// details. +func search(conn *ldap.Conn, base, filter string, pagingSize uint32) (*ldap.SearchResult, error) { + srch := &ldap.SearchRequest{ + BaseDN: base, + Scope: ldap.ScopeWholeSubtree, + DerefAliases: ldap.NeverDerefAliases, + SizeLimit: 0, + TimeLimit: 0, + TypesOnly: false, + Filter: filter, + Attributes: nil, + Controls: nil, + } + if pagingSize != 0 { + return conn.SearchWithPaging(srch, pagingSize) + } + return conn.Search(srch) +} + +// entries is a set of LDAP entries keyed on the entities distinguished name +// and then the name of the attribute. +type entries map[string]map[string]any + +type directory struct { + Entries entries `json:"entries"` + Referrals []string `json:"referrals"` + Controls []string `json:"controls"` +} + +// collate renders an LDAP search result in to a map[string]any, annotating with +// group information if it is available. Fields with known types will be converted +// from strings to the known type. +// Also included in the returned map is the sets of referrals and controls. +func collate(resp *ldap.SearchResult, groups entries) directory { + dir := directory{ + Entries: make(entries), + } + for _, e := range resp.Entries { + u := map[string]any{"dn": e.DN} // Do we want this? It is already apparently present in "distinguishedName". + m := u + if groups != nil { + m = map[string]any{"user": u} + } + for _, attr := range e.Attributes { + val := entype(attr) + u[attr.Name] = val + if groups != nil && attr.Name == "memberOf" { + switch val := val.(type) { + case []string: + if len(val) != 0 { + grps := make([]any, 0, len(val)) + for _, n := range val { + g, ok := groups[n] + if !ok { + continue + } + grps = append(grps, g) + } + if len(grps) != 0 { + m["groups"] = grps + } + } + + case string: + g, ok := groups[val] + if ok { + m["groups"] = []any{g} + } + } + } + } + dir.Entries[e.DN] = m + } + + // Do we want this information? If not, remove this stanza. If we + // do, we should include the information in the Entries returned + // by the exposed API. + if len(resp.Referrals) != 0 { + dir.Referrals = resp.Referrals + } + if len(resp.Controls) != 0 { + dir.Controls = make([]string, 0, len(resp.Controls)) + for _, e := range resp.Controls { + if e == nil { + continue + } + dir.Controls = append(dir.Controls, e.String()) + } + } + + return dir +} + +// entype converts LDAP attributes with known types to their known type if +// possible, falling back to the string if not. +func entype(attr *ldap.EntryAttribute) any { + if len(attr.Values) == 0 { + return attr.Values + } + switch attr.Name { + case "isCriticalSystemObject", "showInAdvancedViewOnly": + if len(attr.Values) != 1 { + return attr.Values + } + switch { + case strings.EqualFold(attr.Values[0], "true"): + return true + case strings.EqualFold(attr.Values[0], "false"): + return false + default: + return attr.Values[0] + } + case "whenCreated", "whenChanged", "dSCorePropagationData": + var times []time.Time + if len(attr.Values) > 1 { + times = make([]time.Time, 0, len(attr.Values)) + } + for _, v := range attr.Values { + const denseTimeLayout = "20060102150405.999999999Z" + t, err := time.Parse(denseTimeLayout, v) + if err != nil { + return attr.Values + } + if len(attr.Values) == 1 { + return t + } + times = append(times, t) + } + return times + case "accountExpires", "lastLogon", "lastLogonTimestamp", "pwdLastSet": + var times []time.Time + if len(attr.Values) > 1 { + times = make([]time.Time, 0, len(attr.Values)) + } + for _, v := range attr.Values { + ts, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return attr.Values + } + if len(attr.Values) == 1 { + return fromWindowsNT(ts) + } + times = append(times, fromWindowsNT(ts)) + } + return times + case "objectGUID", "objectSid": + if len(attr.ByteValues) == 1 { + return attr.ByteValues[0] + } + return attr.ByteValues + } + if len(attr.Values) == 1 { + return attr.Values[0] + } + return attr.Values +} + +// epochDelta is the unix epoch in ldap time. +const epochDelta = 116444736000000000 + +var unixEpoch = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) + +func fromWindowsNT(ts int64) time.Time { + return unixEpoch.Add(time.Duration(ts-epochDelta) * 100) +} diff --git a/x-pack/filebeat/input/entityanalytics/provider/activedirectory/internal/activedirectory/activedirectory_test.go b/x-pack/filebeat/input/entityanalytics/provider/activedirectory/internal/activedirectory/activedirectory_test.go new file mode 100644 index 000000000000..80f3d79efa87 --- /dev/null +++ b/x-pack/filebeat/input/entityanalytics/provider/activedirectory/internal/activedirectory/activedirectory_test.go @@ -0,0 +1,112 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package activedirectory + +import ( + "encoding/json" + "flag" + "os" + "sort" + "testing" + "time" + + "github.com/go-ldap/ldap/v3" +) + +var logResponses = flag.Bool("log_response", false, "use to log users/groups returned from the API") + +// Invoke test with something like this: +// +// AD_BASE=CN=Users,DC=,DC=local AD_URL=ldap:// AD_USER=CN=Administrator,CN=Users,DC=,DC=local AD_PASS= go test -v -log_response +func Test(t *testing.T) { + url, ok := os.LookupEnv("AD_URL") + if !ok { + t.Skip("activedirectory tests require ${AD_URL} to be set") + } + baseDN, ok := os.LookupEnv("AD_BASE") + if !ok { + t.Skip("activedirectory tests require ${AD_BASE} to be set") + } + user, ok := os.LookupEnv("AD_USER") + if !ok { + t.Skip("activedirectory tests require ${AD_USER} to be set") + } + pass, ok := os.LookupEnv("AD_PASS") + if !ok { + t.Skip("activedirectory tests require ${AD_PASS} to be set") + } + + base, err := ldap.ParseDN(baseDN) + if err != nil { + t.Fatalf("invalid base distinguished name: %v", err) + } + + var times []time.Time + t.Run("full", func(t *testing.T) { + users, err := GetDetails(url, user, pass, base, time.Time{}, 0, nil, nil) + if err != nil { + t.Fatalf("unexpected error from GetDetails: %v", err) + } + + if len(users) == 0 { + t.Error("expected non-empty result from query") + } + found := false + var gotUsers []any + for _, e := range users { + dn := e.User["distinguishedName"] + gotUsers = append(gotUsers, dn) + if dn == user { + found = true + } + + when, ok := e.User["whenChanged"].(time.Time) + if ok { + times = append(times, when) + } + } + if !found { + t.Errorf("expected login user to be found in directory: got:%q", gotUsers) + } + + if !*logResponses { + return + } + b, err := json.MarshalIndent(users, "", "\t") + if err != nil { + t.Errorf("failed to marshal users for logging: %v", err) + } + t.Logf("user: %s", b) + }) + + t.Run("update", func(t *testing.T) { + sort.Slice(times, func(i, j int) bool { return times[i].Before(times[j]) }) + since := times[0].Add(time.Second) // Step past first entry by a small amount within LDAP resolution. + var want int + // ... and count all entries since then. + for _, when := range times[1:] { + if !since.After(when) { + want++ + } + } + users, err := GetDetails(url, user, pass, base, since, 0, nil, nil) + if err != nil { + t.Fatalf("unexpected error from GetDetails: %v", err) + } + + if len(users) != want { + t.Errorf("unexpected number of results from query since %v: got:%d want:%d", since, len(users), want) + } + + if !*logResponses && !t.Failed() { + return + } + b, err := json.MarshalIndent(users, "", "\t") + if err != nil { + t.Errorf("failed to marshal users for logging: %v", err) + } + t.Logf("user: %s", b) + }) +}