first commit

This commit is contained in:
yo 2022-07-21 19:03:24 +02:00
commit 187b76e95d
15 changed files with 2795 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.envrc
__*
openldap-log-parser.bin
pkg/
*.zip
*.tgz
*.tar.gz

29
LICENSE Normal file
View File

@ -0,0 +1,29 @@
Copyright (C) 2022, yo000 <johan@nosd.in>
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 the Mumble Developers 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 FOUNDATION 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.

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# openldap-log-parser
Parse openldap log, and output json format
At the moment, openldap-log-parser focus on these log levels:
- 256 (stats log connections/operations/results)
## Install
Copy `openldap-log-parser` into your PATH and set executable flag.
## Usage
Input openldap logs as os stdin.
``` console
# cat /var/log/slapd.log | ./openldap-log-parser | jq
```
Use -f flag to flatten json structure:
``` console
# cat /var/log/slapd.log | ./openldap-log-parser -f | jq
```
Use "-o filename.json" to write output to file.
## Piping rsyslog to openldap-log-parser
You can feed syslog to openldap-log-parser by using "omprog" rsyslog module, with template "RSYSLOG_FileFormat" :
``` console
module(load="omprog")
[...]
if $programname == "slapd" then
action(
type="omprog"
binary="/usr/local/bin/openldap-log-parser -f -o /var/log/slapd.log.json"
template="RSYSLOG_FileFormat")
```

25
go.mod Normal file
View File

@ -0,0 +1,25 @@
module git.nosd.in/yo/openldap-log-parser
go 1.18
replace git.nosd.in/yo/openldap-log-parser => /home/yo/Dev/go/openldap-log-parser
require (
github.com/prometheus/client_golang v1.12.2
github.com/spf13/cobra v1.5.0
github.com/tabalt/pidfile v1.1.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
google.golang.org/protobuf v1.26.0 // indirect
)

581
go.sum Normal file
View File

@ -0,0 +1,581 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
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/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
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=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tabalt/pidfile v1.1.0 h1:Q7qQGZ4MoAXE+rvM5tB4/eAIrawewYewByhMiPoDE50=
github.com/tabalt/pidfile v1.1.0/go.mod h1:7F1QwNrjfAApsuX4Nyah3RsbHVAdY/D9qZWp0nnJ/Uw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yo000/postfix-log-parser v1.2.10 h1:QnbOK7CjBZJj5AOKQiQFW5QntpTAzARUluaqP0l7fgo=
github.com/yo000/postfix-log-parser v1.2.10/go.mod h1:37aL8BWaTvPrSrACNBAcUnt9J2SxJmVtpB6yxuUwibE=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

209
openldap-log-parser.go Normal file
View File

@ -0,0 +1,209 @@
package openldaplog
import (
"fmt"
"time"
"errors"
"regexp"
"strconv"
)
const (
SyslogPri = `(?:<\d{1,3}>)?`
TimeFormat = "Jan 2 15:04:05"
TimeFormatISO8601 = "2006-01-02T15:04:05.999999-07:00"
// group[1]
TimeRE = `([A-Za-z]{3}\s*[0-9]{1,2} [0-9]{2}:[0-9]{2}:[0-9]{2}|^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:[+-][0-2]\d:[0-5]\d|Z))`
IPv4RE = `(?:\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}`
// FIXME: Not very strict
IPv6RE = `(?:(?:[0-9a-fA-F]{1,4}\:){7})[0-9a-fA-F]{1,4}`
HostRE = `([0-9A-Za-z\-\_\.]*)`
ProcessRE = `(slapd\[[0-9]{1,5}\])`
// group[4]
ConnIdRE = `conn=([0-9]{4,10})`
ConnFdRE = `(?:fd=([0-9]{1,10}))?`
OperationIdRE = `(?:op=([0-9]{1,10}))?`
AcceptRE = `(?:ACCEPT from IP=(` + IPv4RE + `)\:([0-9]{1,5}) \(IP=(` + IPv4RE + `)\:([0-9]{1,5})\))?`
// group[11]
STARTTLSRE = `(STARTTLS$)?`
// group[12], group[13]
BindMethodRE = `(?:BIND dn="(.*)" method=([0-9]{1,3}))?`
BindMechRE = `(?:BIND dn=".*" mech=([a-zA-Z]*) (?:bind_ssf=([0-9]{1,3}) )?ssf=([0-9]{1,3}))?`
// group[17], group[18], group[19], group[20], group[21], group[22], group[23]
ResultRE = `(?:(RESULT) (?:tag=([0-9]{1,3})|oid=([0-9\.]*)) err=([0-9]{1,3}) (?:qtime=([0-9\.]{1,10}) )?(?:etime=([0-9\.]{1,10}) )?text=(.*))?`
SearchBaseRE = `(?:SRCH base="(.*)" scope=([0-2]) deref=([0-3]) filter="(.*)")?`
// group[28]
SearchAttrRE = `(?:SRCH attr=(.*))?`
// group[29], group[30], group[31], group[32], group[33], group[34], group[35]
SearchResultRE = `(?:(SEARCH RESULT) tag=([0-9]{1,3}) err=([0-9]{1,3}) (?:qtime=([0-9\.]{1,10}) )?(?:etime=([0-9\.]{1,10}) )?nentries=([0-9]{1,9}) text=(.*))?`
ModDnRE = `(?:MOD dn="(.*)")?`
ModAttrRE = `(?:MOD attr=(.*))?`
// group[38]
PassModRE = `(?:PASSMOD id="(.*)" new)?`
UnbindRE = `(UNBIND)?`
// group[40]
ConnClosedRE = `(closed)?(?: \(connection lost\))?`
LogLineRE = SyslogPri + TimeRE + ` ` + HostRE + ` ` + ProcessRE + ` ` + ConnIdRE + ` ` + ConnFdRE + OperationIdRE + ` ` +
AcceptRE + STARTTLSRE + BindMethodRE + BindMechRE + ResultRE + SearchBaseRE + SearchAttrRE + SearchResultRE + ModDnRE + ModAttrRE +
PassModRE + UnbindRE + ConnClosedRE
)
type (
OpenldapLog struct {
LogFormat LogFormat
Regexp *regexp.Regexp
}
LogFormat struct {
Time *time.Time `json:"time"`
Hostname string `json:"hostname"`
Process string `json:"process"`
ClientIp string `json:"client_ip"`
ClientPort int `json:"client_port"`
ServerIp string `json:"server_ip"`
ServerPort int `json:"server_port"`
BindDN string `json:"bind_dn"`
ConnId int `json:"conn_id"`
ConnFd int `json:"conn_fd"`
OpId int `json:"op_id"`
OpType string `json:"op_type"`
BindMethod string `json:"bind_method"`
BindMech string `json:"bind_mech"`
BindSSF string `json:"bind_ssf"`
SSF string `json:"ssf"`
ModDN string `json:"mod_dn"`
ModAttr string `json:"mod_attr"`
PassModDN string `json:"passmod_dn"`
Result bool
ResTag string `json:"result_tag"`
ResOid string `json:"result_oid"`
ResErr int `json:"result_err"`
ResQTime string `json:"result_qtime"`
ResETime string `json:"result_etime"`
ResText string `json:"result_text"`
SearchResult bool
SearchBase string `json:"search_base"`
SearchScope string `json:"search_scope"`
SearchDeref string `json:"search_deref"`
SearchFilter string `json:"search_filter"`
SearchAttr string `json:"search_attr"`
SearchResTag string `json:"search_res_tag"`
SearchResErr int `json:"search_res_err"`
SearchResQTime string `json:"search_res_qtime"`
SearchResETime string `json:"search_res_etime"`
SearchResNEntries int `json:"search_res_nentries"`
SearchResText string `json:"search_res_text"`
}
)
var (
gDebug bool
)
func NewOpenldapLog(debug bool) *OpenldapLog {
if debug {
gDebug = true
fmt.Printf("DEBUG: Regexp will display on next line\n")
fmt.Printf("%s\n", LogLineRE)
}
return &OpenldapLog{
Regexp: regexp.MustCompile(LogLineRE),
}
}
func (o *OpenldapLog) Parse(text []byte) (LogFormat, error) {
re := o.Regexp.Copy()
group := re.FindSubmatch(text)
if len(group) == 0 {
err := errors.New("Error: Line do not match regex")
return LogFormat{}, err
}
var t time.Time
t, err := time.ParseInLocation(TimeFormat, string(group[1]), time.Local)
if err != nil {
t, err = time.ParseInLocation(TimeFormatISO8601, string(group[1]), time.Local)
if err != nil {
return LogFormat{}, err
}
}
cid,_ := strconv.Atoi(string(group[4]))
cfd,_ := strconv.Atoi(string(group[5]))
opid,_ := strconv.Atoi(string(group[6]))
cport,_ := strconv.Atoi(string(group[8]))
sport,_ := strconv.Atoi(string(group[10]))
result := false
if string(group[17]) == "RESULT" {
result = true
}
rerr,_ := strconv.Atoi(string(group[20]))
sresult := false
if string(group[29]) == "SEARCH RESULT" {
sresult = true
}
serr,_ := strconv.Atoi(string(group[31]))
srentries, _ := strconv.Atoi(string(group[34]))
logFormat := LogFormat{
Time: &t,
Hostname: string(group[2]),
Process: string(group[3]),
ConnId: cid,
ConnFd: cfd,
OpId: opid,
ClientIp: string(group[7]),
ClientPort: cport,
ServerIp: string(group[9]),
ServerPort: sport,
// "STARTTLS"
BindDN: string(group[12]),
BindMethod: string(group[13]),
BindMech: string(group[14]),
BindSSF: string(group[15]),
SSF: string(group[16]),
Result: result,
ResTag: string(group[18]),
ResOid: string(group[19]),
ResErr: rerr,
ResQTime: string(group[21]),
ResETime: string(group[22]),
ResText: string(group[23]),
SearchBase: string(group[24]),
SearchScope: string(group[25]),
SearchDeref: string(group[26]),
SearchFilter: string(group[27]),
SearchAttr: string(group[28]),
SearchResult: sresult,
SearchResTag: string(group[30]),
SearchResErr: serr,
SearchResQTime: string(group[32]),
SearchResETime: string(group[33]),
SearchResNEntries: srentries,
SearchResText: string(group[35]),
ModDN: string(group[36]),
ModAttr: string(group[37]),
PassModDN: string(group[38]),
}
// Now handle Operation Type
if len(group[11]) > 0 || len(group[13]) > 0 || len(group[14]) > 0 || len(group[15]) > 0 {
logFormat.OpType = "bind"
} else if len(group[11]) > 0 {
logFormat.OpType = "starttls"
} else if len(group[24]) > 0 {
logFormat.OpType = "search"
} else if len(group[7]) > 0 {
logFormat.OpType = "accept"
} else if len(group[36]) > 0 || len(group[37]) > 0 {
logFormat.OpType = "mod"
} else if len(group[38]) > 0 {
logFormat.OpType = "passmod"
} else if len(group[39]) > 0 {
logFormat.OpType = "unbind"
} else if len(group[40]) > 0 {
logFormat.OpType = "close"
}
return logFormat, nil
}

View File

@ -0,0 +1,920 @@
package cmd
import (
"os"
"fmt"
"log"
"net"
"sync"
"time"
"bufio"
"errors"
"runtime"
"strings"
"syscall"
"net/http"
"os/signal"
"encoding/json"
"github.com/spf13/cobra"
"github.com/tabalt/pidfile"
"github.com/prometheus/client_golang/prometheus"
openldaplog "git.nosd.in/yo/openldap-log-parser"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func init() {}
type (
OpenLdapConnection struct {
Time *time.Time `json:"time"`
Hostname string `json:"hostname"`
Process string `json:"process"`
Operations []*Operation `json:"operations"`
ClientIp string `json:"client_ip"`
ClientPort int `json:"client_port"`
ServerIp string `json:"server_ip"`
ServerPort int `json:"server_port"`
ConnId int `json:"conn_id"`
ConnFd int `json:"conn_fd"`
BindDN *string `json:"bind_dn"`
BindMethod *string `json:"bind_method"`
BindMech *string `json:"bind_mech"`
BindSSF *string `json:"bind_ssf"`
SSF *string `json:"ssf"`
StartTLS bool `json:"starttls"`
}
Operation struct {
Time *time.Time `json:"time"`
OpType string `json:"op_type"`
OpId *int `json:"op_id,omitempty"`
BindDN string `json:"bind_dn,omitempty"`
BindMethod string `json:"bind_method,omitempty"`
BindMech string `json:"bind_mech,omitempty"`
BindSSF string `json:"bind_ssf,omitempty"`
SSF string `json:"ssf,omitempty"`
ModDN string `json:"mod_dn,omitempty"`
ModAttr string `json:"mod_attr,omitempty"`
PassModDN string `json:"passmod_dn,omitempty"`
ResTag string `json:"result_tag,omitempty"`
ResOid string `json:"result_oid,omitempty"`
// To use "omitempty" on int, they have to be pointers
// This way it willl be displayed when set to 0, and not display when not set (null)
ResErr *int `json:"result_err,omitempty"`
ResQTime string `json:"result_qtime,omitempty"`
ResETime string `json:"result_etime,omitempty"`
ResNEntries *int `json:"result_nentries,omitempty"`
ResText string `json:"result_text,omitempty"`
SearchBase string `json:"search_base,omitempty"`
SearchScope string `json:"search_scope,omitempty"`
SearchDeref string `json:"search_deref,omitempty"`
SearchFilter string `json:"search_filter,omitempty"`
SearchAttr string `json:"search_attr,omitempty"`
SearchResTag string `json:"search_res_tag,omitempty"`
SearchResErr *int `json:"search_res_err,omitempty"`
SearchResQTime string `json:"search_res_qtime,omitempty"`
SearchResETime string `json:"search_res_etime,omitempty"`
SearchResNEntries *int `json:"search_res_nentries,omitempty"`
SearchResText string `json:"search_res_text,omitempty"`
}
OpenLdapConnectionFlat struct {
Time *time.Time `json:"time"`
Hostname string `json:"hostname"`
Process string `json:"process"`
ClientIp string `json:"client_ip"`
ClientPort int `json:"client_port"`
ServerIp string `json:"server_ip"`
ServerPort int `json:"server_port"`
BindDN string `json:"bind_dn,omitempty"`
ConnId int `json:"conn_id"`
ConnFd int `json:"conn_fd"`
OpId *int `json:"op_id,omitempty"`
OpType string `json:"op_type"`
BindMethod string `json:"bind_method,omitempty"`
BindMech string `json:"bind_mech,omitempty"`
BindSSF string `json:"bind_ssf,omitempty"`
SSF string `json:"ssf,omitempty"`
StartTLS bool `json:"starttls,omitempty"`
ModDN string `json:"mod_dn,omitempty"`
ModAttr string `json:"mod_attr,omitempty"`
PassModDN string `json:"passmod_dn,omitempty"`
ResTag string `json:"result_tag,omitempty"`
ResOid string `json:"result_oid,omitempty"`
ResErr *int `json:"result_err,omitempty"`
ResQTime string `json:"result_qtime,omitempty"`
ResETime string `json:"result_etime,omitempty"`
ResText string `json:"result_text,omitempty"`
SearchBase string `json:"search_base,omitempty"`
SearchScope string `json:"search_scope,omitempty"`
SearchDeref string `json:"search_deref,omitempty"`
SearchFilter string `json:"search_filter,omitempty"`
SearchAttr string `json:"search_attr,omitempty"`
SearchResTag string `json:"search_res_tag,omitempty"`
SearchResErr *int `json:"search_res_err,omitempty"`
SearchResQTime string `json:"search_res_qtime,omitempty"`
SearchResETime string `json:"search_res_etime,omitempty"`
SearchResNEntries *int `json:"search_res_nentries,omitempty"`
SearchResText string `json:"search_res_text,omitempty"`
}
)
var (
File os.File
Writer *bufio.Writer
Version = "0.6.5"
BuildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "openldaplogparser_build_info",
Help: "Constant 1 value labeled by version and goversion from which openldap-log-parser was built",
}, []string{"version", "goversion"})
StartTime = promauto.NewGauge(prometheus.GaugeOpts{
Name: "openldaplogparser_time_start_seconds",
Help: "Process start time in UNIX timestamp (seconds)",
})
LineReadCnt = promauto.NewCounter(prometheus.CounterOpts{
Name: "openldaplogparser_line_read_count",
Help: "Number of lines read",
})
LineIncorrectCnt = promauto.NewCounter(prometheus.CounterOpts{
Name: "openldaplogparser_line_incorrect_count",
Help: "Number of lines with incorrect format",
})
LineOutCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_line_out_count",
Help: "Number of lines written to ouput",
}, []string{"host"})
ConnectedClientCnt = promauto.NewGauge(prometheus.GaugeOpts{
Name: "openldaplogparser_client_count",
Help: "Number of connected clients",
})
AcceptCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_accept_count",
Help: "Number of ACCEPT commands executed",
}, []string{"host"})
BindCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_bind_count",
Help: "Number of BIND commands executed",
}, []string{"host"})
SearchCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_search_count",
Help: "Number of SRCH commands executed",
}, []string{"host"})
ModCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_mod_count",
Help: "Number of MOD commands executed",
}, []string{"host"})
PassModCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_passmod_count",
Help: "Number of PASSMOD commands executed",
}, []string{"host"})
UnbindCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_unbind_count",
Help: "Number of UNBIND commands executed",
}, []string{"host"})
CloseCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_close_count",
Help: "Number of closed connections",
}, []string{"host"})
rootCmd = &cobra.Command{
Use: "openldap-log-parser",
Short: "OpenLDAP Log Parser v" + Version + ". Parse openldap log, and output json format",
Run: func(cmd *cobra.Command, args []string) {
processLogs(cmd, args)
},
}
gFlatten bool
gOutputFile string
gPidFilePath string
gSyslogListenAddress string
gPromListenAddress string
gPromMetricPath string
gDebug bool
)
func Execute() {
if err := rootCmd.Execute(); err != nil {
rootCmd.SetOutput(os.Stderr)
rootCmd.Println(err)
os.Exit(1)
}
}
// Flatten OpenLdapConnection by creating an item for each operation
func OlcToFlat(olc *OpenLdapConnection) []OpenLdapConnectionFlat {
var olcf = make([]OpenLdapConnectionFlat, len(olc.Operations))
for i := range olc.Operations {
olcf[i] = OpenLdapConnectionFlat{
Time: olc.Time,
Hostname: olc.Hostname,
Process: olc.Process,
ClientIp: olc.ClientIp,
ClientPort: olc.ClientPort,
ServerIp: olc.ServerIp,
ServerPort: olc.ServerPort,
ConnId: olc.ConnId,
ConnFd: olc.ConnFd,
OpType: olc.Operations[i].OpType,
OpId: olc.Operations[i].OpId,
}
if olc.BindDN != nil {
olcf[i].BindDN = *olc.BindDN
}
switch olc.Operations[i].OpType {
case "starttls":
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "bind":
olcf[i].BindMethod = olc.Operations[i].BindMethod
olcf[i].BindMech = olc.Operations[i].BindMech
olcf[i].BindSSF = olc.Operations[i].BindSSF
olcf[i].SSF = olc.Operations[i].SSF
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "search":
olcf[i].SearchBase = olc.Operations[i].SearchBase
olcf[i].SearchScope = olc.Operations[i].SearchScope
olcf[i].SearchDeref = olc.Operations[i].SearchDeref
olcf[i].SearchFilter = olc.Operations[i].SearchFilter
olcf[i].SearchAttr = olc.Operations[i].SearchAttr
olcf[i].SearchResTag = olc.Operations[i].SearchResTag
olcf[i].SearchResErr = olc.Operations[i].SearchResErr
olcf[i].SearchResQTime = olc.Operations[i].SearchResQTime
olcf[i].SearchResETime = olc.Operations[i].SearchResETime
olcf[i].SearchResNEntries = olc.Operations[i].SearchResNEntries
olcf[i].SearchResText = olc.Operations[i].SearchResText
case "mod":
olcf[i].ModDN = olc.Operations[i].ModDN
olcf[i].ModAttr = olc.Operations[i].ModAttr
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "passmod":
olcf[i].PassModDN = olc.Operations[i].PassModDN
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
}
}
return olcf
}
func NewWriter(file string) (*bufio.Writer, *os.File, error) {
if len(file) > 0 {
var f *os.File
var err error
if _, err = os.Stat(file); err == nil {
f, err = os.OpenFile(file, os.O_APPEND|os.O_WRONLY, 0640)
} else if os.IsNotExist(err) {
f, err = os.OpenFile(file, os.O_CREATE|os.O_WRONLY, 0640)
}
if err != nil {
return nil, nil, err
}
Writer = bufio.NewWriter(f)
return Writer, f, nil
} else {
Writer = bufio.NewWriter(os.Stdout)
return Writer, nil, nil
}
}
func writeOut(msg string, filename string) error {
_, err := fmt.Fprintln(Writer, msg)
Writer.Flush()
if err != nil {
return err
}
var tmpOlc OpenLdapConnection
json.Unmarshal([]byte(msg), &tmpOlc)
LineOutCnt.WithLabelValues(tmpOlc.Hostname).Inc()
return nil
}
// Every 24H, remove sent, milter-rejected and deferred that entered queue more than 5 days ago
/*
func periodicallyCleanMQueue(mqueue map[int]*PostfixLogParser, mqMtx *sync.Mutex) {
var ok int
for range time.Tick(time.Hour * 24) {
// Do we need read lock?
for _, inmail := range mqueue {
ok = 0
// Check all mails were sent (multiple destinations mails)
// or rejected
for _, outmail := range inmail.Messages {
if outmail.Status == "sent" || outmail.Status == "milter-reject" {
ok += 1
} else if outmail.Status == "deferred" {
if inmail.Time.Add(time.Hour * 5 * 24).Before(time.Now()) {
ok += 1
}
}
}
if ok == len(inmail.Messages) {
mqMtx.Lock()
delete(mqueue, inmail.MessageId)
mqMtx.Unlock()
}
}
}
}
*/
func initConfig() {}
func init() {
rootCmd.Version = Version
rootCmd.Flags().BoolVarP(&gFlatten, "flatten", "f", false, "Flatten output for using with syslog")
rootCmd.Flags().StringVarP(&gOutputFile, "out", "o", "", "Output to file, append if exists")
rootCmd.Flags().StringVarP(&gPidFilePath, "pidfile", "p", "", "pid file path")
rootCmd.Flags().StringVarP(&gSyslogListenAddress, "syslog.listen-address", "s", "do-not-listen", "Address to listen on for syslog incoming messages. Default is to parse stdin")
rootCmd.Flags().StringVarP(&gPromListenAddress, "prom.listen-address", "l", "do-not-listen", "Address to listen on for prometheus metrics")
rootCmd.Flags().StringVarP(&gPromMetricPath, "prom.telemetry-path", "m", "/metrics", "Path under which to expose metrics.")
rootCmd.Flags().BoolVarP(&gDebug, "debug", "d", false, "debug mode")
cobra.OnInitialize(initConfig)
}
/*
* This is the function doing the work.
* Each input is stored in a map - indexed by "hostname+conn_id" so we can handle many hosts -
* then written to output when we recognize it as the last line
*/
func parseStoreAndWrite(input []byte, mq map[string]*OpenLdapConnection, mqMtx *sync.Mutex,
outfMtx *sync.Mutex, o *openldaplog.OpenldapLog) error {
logFormat, err := o.Parse(input)
if err != nil {
// Incorrect line, just skip it
if err.Error() == "Error: Line do not match regex" {
LineIncorrectCnt.Inc()
return err
}
return err
}
/*
2022-07-18T17:18:19.785512+02:00 ldap.domain.org slapd[82581] conn=16113 fd=34 ACCEPT from IP=10.11.12.13:55689 (IP=0.0.0.0:389)
*/
if logFormat.ClientIp != "" {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
}
var ops []*Operation
ops = append(ops, op)
olc := &OpenLdapConnection{
Time: logFormat.Time,
Hostname: logFormat.Hostname,
Process: logFormat.Process,
ConnId: logFormat.ConnId,
ConnFd: logFormat.ConnFd,
ClientIp: logFormat.ClientIp,
ClientPort: logFormat.ClientPort,
ServerIp: logFormat.ServerIp,
ServerPort: logFormat.ServerPort,
Operations: ops,
}
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[0])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = nil
}
mqMtx.Lock()
mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)] = olc
mqMtx.Unlock()
AcceptCnt.WithLabelValues(olc.Hostname).Inc()
}
/*
2022-07-18T14:35:17.381223+02:00 ldap.domain.org slapd slapd[82581] conn=16113 op=0 STARTTLS
If we don't have the initial connect, we will discard logs
*/
if logFormat.OpType == "starttls" {
opexist := false
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
// We may be here for the result of STARTTLS operation
for i := range olc.Operations {
if *olc.Operations[i].OpId == logFormat.OpId {
opexist = true
break
}
}
if false == opexist {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
}
olc.Operations = append(olc.Operations, op)
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785570+02:00 ldap.domain.org slapd[82581] conn=16113 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" method=128
If we don't have the initial connect, we will discard logs
*/
if logFormat.BindDN != "" && logFormat.BindMethod != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
// FIXME: What if this bind is not successful?
olc.BindDN = &logFormat.BindDN
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
BindDN: logFormat.BindDN,
BindMethod: logFormat.BindMethod,
}
olc.Operations = append(olc.Operations, op)
BindCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.786218+02:00 ldap.domain.org slapd[82581] conn=16113 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" mech=SIMPLE ssf=0
*/
if logFormat.BindDN != "" && logFormat.BindMech != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].BindMech = logFormat.BindMech
olc.Operations[i].BindSSF = logFormat.BindSSF
olc.Operations[i].SSF = logFormat.SSF
}
}
}
mqMtx.Unlock()
}
/*
// Can be the result of many operation types
2022-07-18T17:18:19.785681+02:00 ldap.domain.org slapd[82581] conn=16113 op=0 RESULT tag=97 err=0 text=
*/
if logFormat.Result == true {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].ResTag = logFormat.ResTag
olc.Operations[i].ResOid = logFormat.ResOid
olc.Operations[i].ResErr = &logFormat.ResErr
olc.Operations[i].ResQTime = logFormat.ResQTime
olc.Operations[i].ResETime = logFormat.ResETime
olc.Operations[i].ResText = logFormat.ResText
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[i])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = append(olc.Operations[:i], olc.Operations[i+1:]...)
}
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785881+02:00 ldap.domain.org slapd[82581] conn=16113 op=2 SRCH base="ou=users,dc=domain,dc=org" scope=2 deref=0 filter="(cn=pika)"
*/
if logFormat.SearchBase != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
SearchBase: logFormat.SearchBase,
SearchScope: logFormat.SearchScope,
SearchDeref: logFormat.SearchDeref,
SearchFilter: logFormat.SearchFilter,
}
olc.Operations = append(olc.Operations, op)
SearchCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785897+02:00 ldap.domain.org slapd[82581] conn=16113 op=2 SRCH attr=dn
*/
if logFormat.SearchAttr != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].SearchAttr = logFormat.SearchAttr
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785989+02:00 ldap.domain.org slapd[82581] conn=16113 op=2 SEARCH RESULT tag=101 err=0 nentries=1 text=
*/
if logFormat.SearchResult == true {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].SearchResTag = logFormat.SearchResTag
olc.Operations[i].SearchResErr = &logFormat.SearchResErr
olc.Operations[i].SearchResQTime = logFormat.SearchResQTime
olc.Operations[i].SearchResETime = logFormat.SearchResETime
olc.Operations[i].SearchResNEntries = &logFormat.SearchResNEntries
olc.Operations[i].SearchResText = logFormat.SearchResText
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[i])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = append(olc.Operations[:i], olc.Operations[i+1:]...)
}
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T14:35:17.381223+02:00 ldap.domain.org slapd slapd[82581] conn=16113 op=3 MOD dn="cn=coincoin,dc=domain,dc=org"
If we don't have the initial connect, we will discard logs
*/
if logFormat.ModDN != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
ModDN: logFormat.ModDN,
}
olc.Operations = append(olc.Operations, op)
ModCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
/*
2022-07-18T14:35:17.381233+02:00 ldap.domain.org slapd[82581] conn=16113 op=3 MOD attr=description
If we don't have the initial connect, we will discard logs
*/
if logFormat.ModAttr != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].ModAttr = logFormat.ModAttr
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T11:13:17.521717+02:00 ldap.domain.org slapd[82581] conn=16113 op=4 PASSMOD id="cn=pika,ou=users,dc=domain,dc=org" new
If we don't have the initial connect, we will discard logs
*/
if logFormat.PassModDN != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
PassModDN: logFormat.PassModDN,
}
olc.Operations = append(olc.Operations, op)
PassModCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785681+02:00 ldap.domain.org slapd[82581] conn=16113 op=8 UNBIND
*/
if logFormat.OpType == "unbind" {
mqMtx.Lock()
// unbind is a new operation
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
}
olc.Operations = append(olc.Operations, op)
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[len(olc.Operations)-1])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = olc.Operations[:len(olc.Operations)-1]
UnbindCnt.WithLabelValues(olc.Hostname).Inc()
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785681+02:00 ldap.domain.org slapd[82581] conn=16113 fd=34 closed
*/
// If gFlatten == false && We do not catch "closed", then the connection will never be displayed
if logFormat.OpType == "close" {
mqMtx.Lock()
// close is a new operation with no op_id
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
}
olc.Operations = append(olc.Operations, op)
// Dump to stdout if gFlatten...
if gFlatten == true {
jsonBytes, err := json.Marshal(OlcToFlat(olc)[len(olc.Operations)-1])
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = olc.Operations[:len(olc.Operations)-1]
} else {
jsonBytes, err := json.Marshal(olc)
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
}
CloseCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
return nil
}
func scanAndProcess(scanner *bufio.Scanner, isStdin bool, conn net.Conn, mQueue map[string]*OpenLdapConnection,
mqMtx *sync.Mutex, outfMtx *sync.Mutex, o *openldaplog.OpenldapLog) error {
for {
// If input is made via TCP Conn, we need to read from a connected net.Conn
if scanner == nil || (isStdin == false && conn == nil) {
return errors.New("Invalid input")
}
if false == scanner.Scan() {
// After Scan returns false, the Err method will return any error that occurred during scanning, except that if it was io.EOF, Err will return nil
if err := scanner.Err(); err != nil {
log.Printf("Error reading data: %v\n", err.Error())
}
if isStdin == false {
log.Printf("No more data, closing connection.\n")
// Should we?
conn.Close()
}
// input is dead, abort mission!
return errors.New("Read error")
}
// Extend timeout after successful read (so we got an idle timeout)
if isStdin == false && conn != nil {
conn.SetReadDeadline(time.Now().Add(time.Duration(600) * time.Second))
}
LineReadCnt.Inc()
read := scanner.Bytes()
if gDebug {
fmt.Printf("DEBUG: Received %v\n", string(read))
}
err := parseStoreAndWrite(read, mQueue, mqMtx, outfMtx, o)
if err != nil {
if err.Error() != "Error: Line do not match regex" {
return err
} else {
log.Printf("input do not match regex: %s\n", string(read))
}
}
}
return nil
}
func processLogs(cmd *cobra.Command, args []string) {
//var scanner *bufio.Scanner
var listener net.Listener
// Output file mutex
var outfMtx sync.Mutex
// mQueue mutex
var mqMtx sync.Mutex
var useStdin bool
// create map of messages
mQueue := make(map[string]*OpenLdapConnection)
BuildInfo.WithLabelValues(Version, runtime.Version()).Set(1)
StartTime.Set(float64(time.Now().Unix()))
// Prometheus exporter
if gPromListenAddress != "do-not-listen" {
go func() {
http.Handle(gPromMetricPath, promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`
<html>
<head><title>Openldap-log-parser Exporter</title></head>
<body>
<h1>Openldap-log-parser Exporter</h1>
<p><a href='` + gPromMetricPath + `'>Metrics</a></p>
</body>
</html>`))
})
log.Fatal(http.ListenAndServe(gPromListenAddress, nil))
}()
}
// Create PID file
if len(gPidFilePath) > 0 {
if pid, err := pidfile.Create(gPidFilePath); err != nil {
log.Fatal(err)
} else {
defer pid.Clear()
}
}
// initialize
o := openldaplog.NewOpenldapLog(gDebug)
// Get a writer, file or stdout
_, File, err := NewWriter(gOutputFile)
if err != nil {
cmd.SetOutput(os.Stderr)
cmd.Println(err)
os.Exit(1)
}
// Manage output file rotation when receiving SIGUSR1
if len(gOutputFile) > 0 {
sig := make(chan os.Signal)
signal.Notify(sig, syscall.SIGUSR1)
go func() {
for {
<-sig
outfMtx.Lock()
fmt.Println("SIGUSR1 received, recreating output file")
File.Close()
_, File, err = NewWriter(gOutputFile)
if err != nil {
outfMtx.Unlock()
cmd.SetOutput(os.Stderr)
cmd.Println(err)
os.Exit(1)
}
outfMtx.Unlock()
}
}()
}
// Cleaner thread
//go periodicallyCleanMQueue(mQueue, &mqMtx)
// Initialize Stdin input...
if true == strings.EqualFold(gSyslogListenAddress, "do-not-listen") {
useStdin = true
scanner := bufio.NewScanner(os.Stdin)
scanAndProcess(scanner, useStdin, nil, mQueue, &mqMtx, &outfMtx, o)
// ...or manages incoming connections
} else {
if gDebug {
fmt.Printf("DEBUG: Listening on %s\n", gSyslogListenAddress)
}
listener, err = net.Listen("tcp", gSyslogListenAddress)
if err != nil {
log.Fatal(fmt.Sprintf("Error listening on %s: %v\n", gSyslogListenAddress, err))
}
for {
connClt, err := listener.Accept()
if err != nil {
log.Printf("Error accepting: %v", err)
// Loop
continue
}
if gDebug {
fmt.Printf("DEBUG: Accept connection from %s\n", connClt.RemoteAddr().String())
}
scanner := bufio.NewScanner(connClt)
ConnectedClientCnt.Inc()
go scanAndProcess(scanner, useStdin, connClt, mQueue, &mqMtx, &outfMtx, o)
ConnectedClientCnt.Dec()
}
}
if File != nil {
outfMtx.Lock()
File.Close()
outfMtx.Unlock()
}
}

View File

@ -0,0 +1,941 @@
package cmd
import (
"os"
"fmt"
"log"
"net"
"sync"
"time"
"bufio"
"errors"
"runtime"
"strings"
"syscall"
"net/http"
"os/signal"
"encoding/json"
"github.com/spf13/cobra"
"github.com/tabalt/pidfile"
"github.com/prometheus/client_golang/prometheus"
openldaplog "git.nosd.in/yo/openldap-log-parser"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func init() {}
type (
OpenLdapConnection struct {
Time *time.Time `json:"time"`
Hostname string `json:"hostname"`
Process string `json:"process"`
Operations []*Operation `json:"operations"`
ClientIp string `json:"client_ip"`
ClientPort int `json:"client_port"`
ServerIp string `json:"server_ip"`
ServerPort int `json:"server_port"`
ConnId int `json:"conn_id"`
ConnFd int `json:"conn_fd"`
BindDN *string `json:"bind_dn"`
BindMethod *string `json:"bind_method"`
BindMech *string `json:"bind_mech"`
BindSSF *string `json:"bind_ssf"`
SSF *string `json:"ssf"`
StartTLS bool `json:"starttls"`
}
Operation struct {
Time *time.Time `json:"time"`
OpType string `json:"op_type"`
OpId *int `json:"op_id,omitempty"`
BindDN string `json:"bind_dn,omitempty"`
BindMethod string `json:"bind_method,omitempty"`
BindMech string `json:"bind_mech,omitempty"`
BindSSF string `json:"bind_ssf,omitempty"`
SSF string `json:"ssf,omitempty"`
ModDN string `json:"mod_dn,omitempty"`
ModAttr string `json:"mod_attr,omitempty"`
PassModDN string `json:"passmod_dn,omitempty"`
ResTag string `json:"result_tag,omitempty"`
ResOid string `json:"result_oid,omitempty"`
// To use "omitempty" on int, they have to be pointers
// This way it willl be displayed when set to 0, and not display when not set (null)
ResErr *int `json:"result_err,omitempty"`
ResQTime string `json:"result_qtime,omitempty"`
ResETime string `json:"result_etime,omitempty"`
ResNEntries *int `json:"result_nentries,omitempty"`
ResText string `json:"result_text,omitempty"`
SearchBase string `json:"search_base,omitempty"`
SearchScope string `json:"search_scope,omitempty"`
SearchDeref string `json:"search_deref,omitempty"`
SearchFilter string `json:"search_filter,omitempty"`
SearchAttr string `json:"search_attr,omitempty"`
SearchResTag string `json:"search_res_tag,omitempty"`
SearchResErr *int `json:"search_res_err,omitempty"`
SearchResQTime string `json:"search_res_qtime,omitempty"`
SearchResETime string `json:"search_res_etime,omitempty"`
SearchResNEntries *int `json:"search_res_nentries,omitempty"`
SearchResText string `json:"search_res_text,omitempty"`
}
OpenLdapConnectionFlat struct {
Time *time.Time `json:"time"`
Hostname string `json:"hostname"`
Process string `json:"process"`
ClientIp string `json:"client_ip"`
ClientPort int `json:"client_port"`
ServerIp string `json:"server_ip"`
ServerPort int `json:"server_port"`
BindDN string `json:"bind_dn,omitempty"`
ConnId int `json:"conn_id"`
ConnFd int `json:"conn_fd"`
OpId *int `json:"op_id,omitempty"`
OpType string `json:"op_type"`
BindMethod string `json:"bind_method,omitempty"`
BindMech string `json:"bind_mech,omitempty"`
BindSSF string `json:"bind_ssf,omitempty"`
SSF string `json:"ssf,omitempty"`
StartTLS bool `json:"starttls,omitempty"`
ModDN string `json:"mod_dn,omitempty"`
ModAttr string `json:"mod_attr,omitempty"`
PassModDN string `json:"passmod_dn,omitempty"`
ResTag string `json:"result_tag,omitempty"`
ResOid string `json:"result_oid,omitempty"`
ResErr *int `json:"result_err,omitempty"`
ResQTime string `json:"result_qtime,omitempty"`
ResETime string `json:"result_etime,omitempty"`
ResText string `json:"result_text,omitempty"`
SearchBase string `json:"search_base,omitempty"`
SearchScope string `json:"search_scope,omitempty"`
SearchDeref string `json:"search_deref,omitempty"`
SearchFilter string `json:"search_filter,omitempty"`
SearchAttr string `json:"search_attr,omitempty"`
SearchResTag string `json:"search_res_tag,omitempty"`
SearchResErr *int `json:"search_res_err,omitempty"`
SearchResQTime string `json:"search_res_qtime,omitempty"`
SearchResETime string `json:"search_res_etime,omitempty"`
SearchResNEntries *int `json:"search_res_nentries,omitempty"`
SearchResText string `json:"search_res_text,omitempty"`
}
)
var (
File os.File
Writer *bufio.Writer
Version = "0.7.0a"
BuildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "openldaplogparser_build_info",
Help: "Constant 1 value labeled by version and goversion from which openldap-log-parser was built",
}, []string{"version", "goversion"})
StartTime = promauto.NewGauge(prometheus.GaugeOpts{
Name: "openldaplogparser_time_start_seconds",
Help: "Process start time in UNIX timestamp (seconds)",
})
LineReadCnt = promauto.NewCounter(prometheus.CounterOpts{
Name: "openldaplogparser_line_read_count",
Help: "Number of lines read",
})
LineIncorrectCnt = promauto.NewCounter(prometheus.CounterOpts{
Name: "openldaplogparser_line_incorrect_count",
Help: "Number of lines with incorrect format",
})
LineOutCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_line_out_count",
Help: "Number of lines written to ouput",
}, []string{"host"})
ConnectedClientCnt = promauto.NewGauge(prometheus.GaugeOpts{
Name: "openldaplogparser_client_count",
Help: "Number of connected clients",
})
AcceptCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_accept_count",
Help: "Number of ACCEPT commands executed",
}, []string{"host"})
BindCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_bind_count",
Help: "Number of BIND commands executed",
}, []string{"host"})
SearchCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_search_count",
Help: "Number of SRCH commands executed",
}, []string{"host"})
ModCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_mod_count",
Help: "Number of MOD commands executed",
}, []string{"host"})
PassModCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_passmod_count",
Help: "Number of PASSMOD commands executed",
}, []string{"host"})
UnbindCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_unbind_count",
Help: "Number of UNBIND commands executed",
}, []string{"host"})
CloseCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_close_count",
Help: "Number of closed connections",
}, []string{"host"})
rootCmd = &cobra.Command{
Use: "openldap-log-parser",
Short: "OpenLDAP Log Parser v" + Version + ". Parse openldap log, and output json format",
Run: func(cmd *cobra.Command, args []string) {
processLogs(cmd, args)
},
}
gFlatten bool
gOutputFile string
gPidFilePath string
gSyslogListenAddress string
gPromListenAddress string
gPromMetricPath string
gDebug bool
gDispUnkConn bool
)
func Execute() {
if err := rootCmd.Execute(); err != nil {
rootCmd.SetOutput(os.Stderr)
rootCmd.Println(err)
os.Exit(1)
}
}
// Flatten OpenLdapConnection by creating an item for each operation
func OlcToFlat(olc *OpenLdapConnection) []OpenLdapConnectionFlat {
var olcf = make([]OpenLdapConnectionFlat, len(olc.Operations))
for i := range olc.Operations {
olcf[i] = OpenLdapConnectionFlat{
Time: olc.Time,
Hostname: olc.Hostname,
Process: olc.Process,
ClientIp: olc.ClientIp,
ClientPort: olc.ClientPort,
ServerIp: olc.ServerIp,
ServerPort: olc.ServerPort,
ConnId: olc.ConnId,
ConnFd: olc.ConnFd,
OpType: olc.Operations[i].OpType,
OpId: olc.Operations[i].OpId,
}
if olc.BindDN != nil {
olcf[i].BindDN = *olc.BindDN
}
switch olc.Operations[i].OpType {
case "starttls":
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "bind":
olcf[i].BindMethod = olc.Operations[i].BindMethod
olcf[i].BindMech = olc.Operations[i].BindMech
olcf[i].BindSSF = olc.Operations[i].BindSSF
olcf[i].SSF = olc.Operations[i].SSF
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "search":
olcf[i].SearchBase = olc.Operations[i].SearchBase
olcf[i].SearchScope = olc.Operations[i].SearchScope
olcf[i].SearchDeref = olc.Operations[i].SearchDeref
olcf[i].SearchFilter = olc.Operations[i].SearchFilter
olcf[i].SearchAttr = olc.Operations[i].SearchAttr
olcf[i].SearchResTag = olc.Operations[i].SearchResTag
olcf[i].SearchResErr = olc.Operations[i].SearchResErr
olcf[i].SearchResQTime = olc.Operations[i].SearchResQTime
olcf[i].SearchResETime = olc.Operations[i].SearchResETime
olcf[i].SearchResNEntries = olc.Operations[i].SearchResNEntries
olcf[i].SearchResText = olc.Operations[i].SearchResText
case "mod":
olcf[i].ModDN = olc.Operations[i].ModDN
olcf[i].ModAttr = olc.Operations[i].ModAttr
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "passmod":
olcf[i].PassModDN = olc.Operations[i].PassModDN
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
}
}
return olcf
}
func NewWriter(file string) (*bufio.Writer, *os.File, error) {
if len(file) > 0 {
var f *os.File
var err error
if _, err = os.Stat(file); err == nil {
f, err = os.OpenFile(file, os.O_APPEND|os.O_WRONLY, 0640)
} else if os.IsNotExist(err) {
f, err = os.OpenFile(file, os.O_CREATE|os.O_WRONLY, 0640)
}
if err != nil {
return nil, nil, err
}
Writer = bufio.NewWriter(f)
return Writer, f, nil
} else {
Writer = bufio.NewWriter(os.Stdout)
return Writer, nil, nil
}
}
func writeOut(msg string, filename string) error {
_, err := fmt.Fprintln(Writer, msg)
Writer.Flush()
if err != nil {
return err
}
var tmpOlc OpenLdapConnection
json.Unmarshal([]byte(msg), &tmpOlc)
LineOutCnt.WithLabelValues(tmpOlc.Hostname).Inc()
return nil
}
// Every 24H, remove sent, milter-rejected and deferred that entered queue more than 5 days ago
/*
func periodicallyCleanMQueue(mqueue map[int]*PostfixLogParser, mqMtx *sync.Mutex) {
var ok int
for range time.Tick(time.Hour * 24) {
// Do we need read lock?
for _, inmail := range mqueue {
ok = 0
// Check all mails were sent (multiple destinations mails)
// or rejected
for _, outmail := range inmail.Messages {
if outmail.Status == "sent" || outmail.Status == "milter-reject" {
ok += 1
} else if outmail.Status == "deferred" {
if inmail.Time.Add(time.Hour * 5 * 24).Before(time.Now()) {
ok += 1
}
}
}
if ok == len(inmail.Messages) {
mqMtx.Lock()
delete(mqueue, inmail.MessageId)
mqMtx.Unlock()
}
}
}
}
*/
func initConfig() {}
func init() {
rootCmd.Version = Version
rootCmd.Flags().BoolVarP(&gFlatten, "flatten", "f", false, "Flatten output for using with syslog")
rootCmd.Flags().StringVarP(&gOutputFile, "out", "o", "", "Output to file, append if exists")
rootCmd.Flags().StringVarP(&gPidFilePath, "pidfile", "p", "", "pid file path")
rootCmd.Flags().StringVarP(&gSyslogListenAddress, "syslog.listen-address", "s", "do-not-listen", "Address to listen on for syslog incoming messages. Default is to parse stdin")
rootCmd.Flags().StringVarP(&gPromListenAddress, "prom.listen-address", "l", "do-not-listen", "Address to listen on for prometheus metrics")
rootCmd.Flags().StringVarP(&gPromMetricPath, "prom.telemetry-path", "m", "/metrics", "Path under which to expose metrics.")
rootCmd.Flags().BoolVarP(&gDebug, "debug", "d", false, "debug mode")
rootCmd.Flags().BoolVarP(&gDebug, "unknown", "u", false, "display operations without connection (b/c accept was not seen)")
cobra.OnInitialize(initConfig)
}
/*
* This is the function doing the work.
* Each input is stored in a map - indexed by "hostname+conn_id" so we can handle many hosts -
* then written to output when we recognize it as the last line
*/
func parseStoreAndWrite(input []byte, mq map[string]*OpenLdapConnection, mqMtx *sync.Mutex,
outfMtx *sync.Mutex, o *openldaplog.OpenldapLog) error {
logFormat, err := o.Parse(input)
if err != nil {
// Incorrect line, just skip it
if err.Error() == "Error: Line do not match regex" {
LineIncorrectCnt.Inc()
return err
}
return err
}
/*
2022-07-18T17:18:19.785512+02:00 ldap.domain.org slapd[82581] conn=16113 fd=34 ACCEPT from IP=10.11.12.13:55689 (IP=0.0.0.0:389)
*/
if logFormat.ClientIp != "" {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
}
var ops []*Operation
ops = append(ops, op)
olc := &OpenLdapConnection{
Time: logFormat.Time,
Hostname: logFormat.Hostname,
Process: logFormat.Process,
ConnId: logFormat.ConnId,
ConnFd: logFormat.ConnFd,
ClientIp: logFormat.ClientIp,
ClientPort: logFormat.ClientPort,
ServerIp: logFormat.ServerIp,
ServerPort: logFormat.ServerPort,
Operations: ops,
}
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[0])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = nil
}
mqMtx.Lock()
mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)] = olc
mqMtx.Unlock()
AcceptCnt.WithLabelValues(olc.Hostname).Inc()
}
/*
2022-07-18T14:35:17.381223+02:00 ldap.domain.org slapd slapd[82581] conn=16113 op=0 STARTTLS
If we don't have the initial connect, we will discard logs
*/
if logFormat.OpType == "starttls" {
opexist := false
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
// We may be here for the result of STARTTLS operation
for i := range olc.Operations {
if *olc.Operations[i].OpId == logFormat.OpId {
opexist = true
break
}
}
if false == opexist {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
}
olc.Operations = append(olc.Operations, op)
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785570+02:00 ldap.domain.org slapd[82581] conn=16113 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" method=128
If we don't have the initial connect, we will discard logs
*/
if logFormat.BindDN != "" && logFormat.BindMethod != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
// FIXME: What if this bind is not successful?
olc.BindDN = &logFormat.BindDN
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
BindDN: logFormat.BindDN,
BindMethod: logFormat.BindMethod,
}
olc.Operations = append(olc.Operations, op)
BindCnt.WithLabelValues(olc.Hostname).Inc()
} else {
if gDispUnkConn == true {
// use conn_id = 0
olc, ok := mq[fmt.Sprintf("%s:0", logFormat.Hostname)]
if false == ok {
// Create connection with conn_id = 0
}
olc.BindDN = &logFormat.BindDN
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
BindDN: logFormat.BindDN,
BindMethod: logFormat.BindMethod,
}
olc.Operations = append(olc.Operations, op)
BindCntUnk.WithLabelValues(olc.Hostname).Inc()
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.786218+02:00 ldap.domain.org slapd[82581] conn=16113 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" mech=SIMPLE ssf=0
*/
if logFormat.BindDN != "" && logFormat.BindMech != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].BindMech = logFormat.BindMech
olc.Operations[i].BindSSF = logFormat.BindSSF
olc.Operations[i].SSF = logFormat.SSF
}
}
}
mqMtx.Unlock()
}
/*
// Can be the result of many operation types
2022-07-18T17:18:19.785681+02:00 ldap.domain.org slapd[82581] conn=16113 op=0 RESULT tag=97 err=0 text=
*/
if logFormat.Result == true {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].ResTag = logFormat.ResTag
olc.Operations[i].ResOid = logFormat.ResOid
olc.Operations[i].ResErr = &logFormat.ResErr
olc.Operations[i].ResQTime = logFormat.ResQTime
olc.Operations[i].ResETime = logFormat.ResETime
olc.Operations[i].ResText = logFormat.ResText
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[i])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = append(olc.Operations[:i], olc.Operations[i+1:]...)
}
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785881+02:00 ldap.domain.org slapd[82581] conn=16113 op=2 SRCH base="ou=users,dc=domain,dc=org" scope=2 deref=0 filter="(cn=pika)"
*/
if logFormat.SearchBase != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
SearchBase: logFormat.SearchBase,
SearchScope: logFormat.SearchScope,
SearchDeref: logFormat.SearchDeref,
SearchFilter: logFormat.SearchFilter,
}
olc.Operations = append(olc.Operations, op)
SearchCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785897+02:00 ldap.domain.org slapd[82581] conn=16113 op=2 SRCH attr=dn
*/
if logFormat.SearchAttr != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].SearchAttr = logFormat.SearchAttr
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785989+02:00 ldap.domain.org slapd[82581] conn=16113 op=2 SEARCH RESULT tag=101 err=0 nentries=1 text=
*/
if logFormat.SearchResult == true {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].SearchResTag = logFormat.SearchResTag
olc.Operations[i].SearchResErr = &logFormat.SearchResErr
olc.Operations[i].SearchResQTime = logFormat.SearchResQTime
olc.Operations[i].SearchResETime = logFormat.SearchResETime
olc.Operations[i].SearchResNEntries = &logFormat.SearchResNEntries
olc.Operations[i].SearchResText = logFormat.SearchResText
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[i])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = append(olc.Operations[:i], olc.Operations[i+1:]...)
}
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T14:35:17.381223+02:00 ldap.domain.org slapd slapd[82581] conn=16113 op=3 MOD dn="cn=coincoin,dc=domain,dc=org"
If we don't have the initial connect, we will discard logs
*/
if logFormat.ModDN != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
ModDN: logFormat.ModDN,
}
olc.Operations = append(olc.Operations, op)
ModCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
/*
2022-07-18T14:35:17.381233+02:00 ldap.domain.org slapd[82581] conn=16113 op=3 MOD attr=description
If we don't have the initial connect, we will discard logs
*/
if logFormat.ModAttr != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].ModAttr = logFormat.ModAttr
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T11:13:17.521717+02:00 ldap.domain.org slapd[82581] conn=16113 op=4 PASSMOD id="cn=pika,ou=users,dc=domain,dc=org" new
If we don't have the initial connect, we will discard logs
*/
if logFormat.PassModDN != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
PassModDN: logFormat.PassModDN,
}
olc.Operations = append(olc.Operations, op)
PassModCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785681+02:00 ldap.domain.org slapd[82581] conn=16113 op=8 UNBIND
*/
if logFormat.OpType == "unbind" {
mqMtx.Lock()
// unbind is a new operation
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
}
olc.Operations = append(olc.Operations, op)
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[len(olc.Operations)-1])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = olc.Operations[:len(olc.Operations)-1]
UnbindCnt.WithLabelValues(olc.Hostname).Inc()
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785681+02:00 ldap.domain.org slapd[82581] conn=16113 fd=34 closed
*/
// If gFlatten == false && We do not catch "closed", then the connection will never be displayed
if logFormat.OpType == "close" {
mqMtx.Lock()
// close is a new operation with no op_id
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
}
olc.Operations = append(olc.Operations, op)
// Dump to stdout if gFlatten...
if gFlatten == true {
jsonBytes, err := json.Marshal(OlcToFlat(olc)[len(olc.Operations)-1])
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = olc.Operations[:len(olc.Operations)-1]
} else {
jsonBytes, err := json.Marshal(olc)
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
}
CloseCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
return nil
}
func scanAndProcess(scanner *bufio.Scanner, isStdin bool, conn net.Conn, mQueue map[string]*OpenLdapConnection,
mqMtx *sync.Mutex, outfMtx *sync.Mutex, o *openldaplog.OpenldapLog) error {
for {
// If input is made via TCP Conn, we need to read from a connected net.Conn
if scanner == nil || (isStdin == false && conn == nil) {
return errors.New("Invalid input")
}
if false == scanner.Scan() {
// After Scan returns false, the Err method will return any error that occurred during scanning, except that if it was io.EOF, Err will return nil
if err := scanner.Err(); err != nil {
log.Printf("Error reading data: %v\n", err.Error())
}
if isStdin == false {
log.Printf("No more data, closing connection.\n")
// Should we?
conn.Close()
}
// input is dead, abort mission!
return errors.New("Read error")
}
// Extend timeout after successful read (so we got an idle timeout)
if isStdin == false && conn != nil {
conn.SetReadDeadline(time.Now().Add(time.Duration(600) * time.Second))
}
LineReadCnt.Inc()
read := scanner.Bytes()
if gDebug {
fmt.Printf("DEBUG: Received %v\n", string(read))
}
err := parseStoreAndWrite(read, mQueue, mqMtx, outfMtx, o)
if err != nil {
if err.Error() != "Error: Line do not match regex" {
return err
} else {
log.Printf("input do not match regex: %s\n", string(read))
}
}
}
return nil
}
func processLogs(cmd *cobra.Command, args []string) {
//var scanner *bufio.Scanner
var listener net.Listener
// Output file mutex
var outfMtx sync.Mutex
// mQueue mutex
var mqMtx sync.Mutex
var useStdin bool
// create map of messages
mQueue := make(map[string]*OpenLdapConnection)
BuildInfo.WithLabelValues(Version, runtime.Version()).Set(1)
StartTime.Set(float64(time.Now().Unix()))
// Prometheus exporter
if gPromListenAddress != "do-not-listen" {
go func() {
http.Handle(gPromMetricPath, promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`
<html>
<head><title>Openldap-log-parser Exporter</title></head>
<body>
<h1>Openldap-log-parser Exporter</h1>
<p><a href='` + gPromMetricPath + `'>Metrics</a></p>
</body>
</html>`))
})
log.Fatal(http.ListenAndServe(gPromListenAddress, nil))
}()
}
// Create PID file
if len(gPidFilePath) > 0 {
if pid, err := pidfile.Create(gPidFilePath); err != nil {
log.Fatal(err)
} else {
defer pid.Clear()
}
}
// initialize
o := openldaplog.NewOpenldapLog(gDebug)
// Get a writer, file or stdout
_, File, err := NewWriter(gOutputFile)
if err != nil {
cmd.SetOutput(os.Stderr)
cmd.Println(err)
os.Exit(1)
}
// Manage output file rotation when receiving SIGUSR1
if len(gOutputFile) > 0 {
sig := make(chan os.Signal)
signal.Notify(sig, syscall.SIGUSR1)
go func() {
for {
<-sig
outfMtx.Lock()
fmt.Println("SIGUSR1 received, recreating output file")
File.Close()
_, File, err = NewWriter(gOutputFile)
if err != nil {
outfMtx.Unlock()
cmd.SetOutput(os.Stderr)
cmd.Println(err)
os.Exit(1)
}
outfMtx.Unlock()
}
}()
}
// Cleaner thread
//go periodicallyCleanMQueue(mQueue, &mqMtx)
// Initialize Stdin input...
if true == strings.EqualFold(gSyslogListenAddress, "do-not-listen") {
useStdin = true
scanner := bufio.NewScanner(os.Stdin)
scanAndProcess(scanner, useStdin, nil, mQueue, &mqMtx, &outfMtx, o)
// ...or manages incoming connections
} else {
if gDebug {
fmt.Printf("DEBUG: Listening on %s\n", gSyslogListenAddress)
}
listener, err = net.Listen("tcp", gSyslogListenAddress)
if err != nil {
log.Fatal(fmt.Sprintf("Error listening on %s: %v\n", gSyslogListenAddress, err))
}
for {
connClt, err := listener.Accept()
if err != nil {
log.Printf("Error accepting: %v", err)
// Loop
continue
}
if gDebug {
fmt.Printf("DEBUG: Accept connection from %s\n", connClt.RemoteAddr().String())
}
scanner := bufio.NewScanner(connClt)
ConnectedClientCnt.Inc()
go scanAndProcess(scanner, useStdin, connClt, mQueue, &mqMtx, &outfMtx, o)
ConnectedClientCnt.Dec()
}
}
if File != nil {
outfMtx.Lock()
File.Close()
outfMtx.Unlock()
}
}

View File

@ -0,0 +1,10 @@
// Copyright © 2022 yo000 <johan@nosd.in>
//
package main
import "git.nosd.in/yo/openldap-log-parser/openldap-log-parser/cmd"
func main() {
cmd.Execute()
}

13
test/slapd-2.log Normal file
View File

@ -0,0 +1,13 @@
2022-07-20T10:03:42.856796+02:00 ldap.domain.org slapd[82581] conn=1699 fd=41 ACCEPT from IP=10.11.12.14:30390 (IP=0.0.0.0:389)
2022-07-20T10:03:42.856847+02:00 ldap.domain.org slapd[82581] conn=1699 op=0 BIND dn="cn=meuh,ou=users,dc=domain,dc=org" method=128
2022-07-20T10:03:42.856916+02:00 ldap.domain.org slapd[82581] conn=1699 op=0 BIND dn="cn=meuh,ou=users,dc=domain,dc=org" mech=SIMPLE ssf=0
2022-07-20T10:03:42.856995+02:00 ldap.domain.org slapd[82581] conn=1699 op=0 RESULT tag=97 err=0 text=
2022-07-20T10:03:42.857200+02:00 ldap.domain.org slapd[82581] conn=1699 op=1 SRCH base="dc=domain,dc=org" scope=2 deref=0 filter="(cn=cuicui)"
2022-07-20T10:03:42.857241+02:00 ldap.domain.org slapd[82581] conn=1699 op=1 SRCH attr=dn
2022-07-20T10:03:42.857386+02:00 ldap.domain.org slapd[82581] conn=1699 op=1 SEARCH RESULT tag=101 err=0 nentries=1 text=
2022-07-20T10:03:42.857561+02:00 ldap.domain.org slapd[82581] conn=1699 op=2 BIND anonymous mech=implicit ssf=0
2022-07-20T10:03:42.857572+02:00 ldap.domain.org slapd[82581] conn=1699 op=2 BIND dn="cn=cuicui,ou=users,dc=domain,dc=org" method=128
2022-07-20T10:03:42.857605+02:00 ldap.domain.org slapd[82581] conn=1699 op=2 BIND dn="cn=cuicui,ou=users,dc=domain,dc=org" mech=SIMPLE ssf=0
2022-07-20T10:03:42.857653+02:00 ldap.domain.org slapd[82581] conn=1699 op=2 RESULT tag=97 err=0 text=
2022-07-20T10:03:42.857891+02:00 ldap.domain.org slapd[82581] conn=1699 fd=41 closed (connection lost)

2
test/slapd-accept.log Normal file
View File

@ -0,0 +1,2 @@
2022-07-18T09:23:20.160516+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 ACCEPT from IP=10.11.12.16:64482 (IP=0.0.0.0:389)
2022-07-18T09:23:20.226352+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 closed

6
test/slapd-bind.log Normal file
View File

@ -0,0 +1,6 @@
2022-07-18T09:23:20.160516+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 ACCEPT from IP=10.11.12.16:64482 (IP=0.0.0.0:389)
2022-07-18T09:25:35.224296+02:00 ldap.domain.org slapd[82581] conn=1512 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" method=128
2022-07-18T09:25:35.224329+02:00 ldap.domain.org slapd[82581] conn=1512 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" mech=SIMPLE ssf=0
2022-07-18T09:25:35.224353+02:00 ldap.domain.org slapd[82581] conn=1512 op=1 RESULT tag=97 err=0 text=
2022-07-18T09:25:35.225177+02:00 ldap.domain.org slapd[82581] conn=1512 op=2 UNBIND
2022-07-18T09:23:20.226352+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 closed

5
test/slapd-mod.log Normal file
View File

@ -0,0 +1,5 @@
2022-07-18T09:23:20.160516+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 ACCEPT from IP=10.11.12.16:64482 (IP=0.0.0.0:389)
2022-07-18T09:25:35.224767+02:00 ldap.domain.org slapd[82581] conn=1512 op=3 MOD dn="cn=coincoin,dc=domain,dc=org"
2022-07-18T09:25:35.224779+02:00 ldap.domain.org slapd[82581] conn=1512 op=3 MOD attr=description
2022-07-18T09:25:35.224843+02:00 ldap.domain.org slapd[82581] conn=1512 op=3 RESULT tag=103 err=0 text=
2022-07-18T09:23:20.226352+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 closed

4
test/slapd-passmod.log Normal file
View File

@ -0,0 +1,4 @@
2022-07-18T09:23:20.160516+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 ACCEPT from IP=10.11.12.16:64482 (IP=0.0.0.0:389)
2022-07-18T09:25:35.224767+02:00 ldap.domain.org slapd[82581] conn=1512 op=4 PASSMOD id="cn=pika,ou=users,dc=domain,dc=org" new
2022-07-18T09:25:35.224843+02:00 ldap.domain.org slapd[82581] conn=1512 op=4 RESULT oid= err=0 text=
2022-07-18T09:23:20.226352+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 closed

5
test/slapd-search.log Normal file
View File

@ -0,0 +1,5 @@
2022-07-18T09:23:20.160516+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 ACCEPT from IP=10.11.12.16:64482 (IP=0.0.0.0:389)
2022-07-18T09:25:35.224767+02:00 ldap.domain.org slapd[82581] conn=1512 op=2 SRCH base="ou=users,dc=domain,dc=org" scope=2 deref=0 filter="(&(objectClass=person)(cn=pika))"
2022-07-18T09:25:35.224779+02:00 ldap.domain.org slapd[82581] conn=1512 op=2 SRCH attr=objectClass userPrincipalName userAccountControl mail rfc822Mailbox entryUUID uid cn
2022-07-18T09:25:35.224843+02:00 ldap.domain.org slapd[82581] conn=1512 op=2 SEARCH RESULT tag=101 err=0 nentries=0 text=
2022-07-18T09:23:20.226352+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 closed