mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 15:10:23 +00:00
add support for log events
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
43d011f125
commit
4eded56d5f
34 changed files with 856 additions and 139 deletions
|
@ -48,7 +48,7 @@ You can also purchase support plans from the [SFTPGo website](https://sftpgo.com
|
|||
|
||||
SFTPGo is an Open Source project and you can of course use it for free but please don't ask for free support as well.
|
||||
|
||||
We will check the reported issues to see if you are experiencing a bug and if so we'll will fix it, but will only provide support to project [sponsors/donors](#sponsors).
|
||||
We will check the reported issues to see if you are experiencing a bug and if so, it may or may not be fixed, we only provide support to project [sponsors/donors](#sponsors).
|
||||
|
||||
If you report an invalid issue or ask for step-by-step support, your issue will remain open with no answer or will be closed as invalid without further explanation. Thanks for understanding.
|
||||
|
||||
|
|
|
@ -467,6 +467,7 @@ The configuration file contains the following sections:
|
|||
- `fs_events`, list of strings. Defines the filesystem events that will be notified to this plugin.
|
||||
- `provider_events`, list of strings. Defines the provider events that will be notified to this plugin.
|
||||
- `provider_objects`, list if strings. Defines the provider objects that will be notified to this plugin.
|
||||
- `log_events`, list of integers. Defines the log events that will be notified to this plugin. `1` means "Login failed", `2` means "Login with non-existent user", `3` means "No login tried", `4` means "Algorithm negotiation failed".
|
||||
- `retry_max_time`, integer. Defines the maximum number of seconds an event can be late. SFTPGo adds a timestamp to each event and add to an internal queue any events that a the plugin fails to handle (the plugin returns an error or it is not running). If a plugin fails to handle an event that is too late, based on this configuration, it will be discarded. SFTPGo will try to resend queued events every 30 seconds. 0 means no retry.
|
||||
- `retry_queue_max_size`, integer. Defines the maximum number of events that the internal queue can hold. Once the queue is full, the events that cannot be sent to the plugin will be discarded. 0 means no limit.
|
||||
- `kms_options`, struct. Defines the options for kms plugins.
|
||||
|
|
|
@ -63,5 +63,5 @@ The logs can be divided into the following categories:
|
|||
- `username`, string. Can be empty if the connection is closed before an authentication attempt
|
||||
- `client_ip` string.
|
||||
- `protocol` string. Possible values are `SSH`, `FTP`, `DAV`
|
||||
- `login_type` string. Can be `publickey`, `password`, `keyboard-interactive`, `publickey+password`, `publickey+keyboard-interactive` or `no_auth_tryed`
|
||||
- `login_type` string. Can be `publickey`, `password`, `keyboard-interactive`, `publickey+password`, `publickey+keyboard-interactive` or `no_auth_tried`
|
||||
- `error` string. Optional error description
|
||||
|
|
|
@ -8,7 +8,7 @@ If the hook defines an external program it can reads the following environment v
|
|||
|
||||
- `SFTPGO_LOGIND_USER`, it contains the user serialized as JSON. The username is empty if the connection is closed for authentication timeout
|
||||
- `SFTPGO_LOGIND_IP`
|
||||
- `SFTPGO_LOGIND_METHOD`, possible values are `publickey`, `password`, `keyboard-interactive`, `publickey+password`, `publickey+keyboard-interactive`, `TLSCertificate`, `TLSCertificate+password` or `no_auth_tryed`, `IDP` (external identity provider)
|
||||
- `SFTPGO_LOGIND_METHOD`, possible values are `publickey`, `password`, `keyboard-interactive`, `publickey+password`, `publickey+keyboard-interactive`, `TLSCertificate`, `TLSCertificate+password` or `no_auth_tried`, `IDP` (external identity provider)
|
||||
- `SFTPGO_LOGIND_STATUS`, 1 means login OK, 0 login KO
|
||||
- `SFTPGO_LOGIND_PROTOCOL`, possible values are `SSH`, `FTP`, `DAV`, `HTTP`, `OIDC` (OpenID Connect)
|
||||
|
||||
|
|
28
go.mod
28
go.mod
|
@ -10,14 +10,14 @@ require (
|
|||
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736
|
||||
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.23
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.22
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.25
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.24
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.65
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.67
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.11
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.1
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.11
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.3.3
|
||||
github.com/coreos/go-oidc/v3 v3.5.0
|
||||
|
@ -53,7 +53,7 @@ require (
|
|||
github.com/rs/cors v1.9.0
|
||||
github.com/rs/xid v1.5.0
|
||||
github.com/rs/zerolog v1.29.1
|
||||
github.com/sftpgo/sdk v0.1.3
|
||||
github.com/sftpgo/sdk v0.1.4-0.20230512160325-38e59551f700
|
||||
github.com/shirou/gopsutil/v3 v3.23.4
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/cobra v1.7.0
|
||||
|
@ -68,21 +68,21 @@ require (
|
|||
go.etcd.io/bbolt v1.3.7
|
||||
go.uber.org/automaxprocs v1.5.2
|
||||
gocloud.dev v0.29.0
|
||||
golang.org/x/crypto v0.8.0
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/crypto v0.9.0
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/oauth2 v0.8.0
|
||||
golang.org/x/sys v0.8.0
|
||||
golang.org/x/term v0.8.0
|
||||
golang.org/x/time v0.3.0
|
||||
google.golang.org/api v0.121.0
|
||||
google.golang.org/api v0.122.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.1 // indirect
|
||||
cloud.google.com/go/compute v1.19.1 // indirect
|
||||
cloud.google.com/go v0.110.2 // indirect
|
||||
cloud.google.com/go/compute v1.19.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.0.0 // indirect
|
||||
cloud.google.com/go/iam v1.0.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
|
@ -157,7 +157,7 @@ require (
|
|||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/tools v0.8.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
|
@ -170,5 +170,5 @@ require (
|
|||
replace (
|
||||
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
|
||||
github.com/robfig/cron/v3 => github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0
|
||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20230408075646-704a7f627371
|
||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20230512104844-219592fc3028
|
||||
)
|
||||
|
|
55
go.sum
55
go.sum
|
@ -39,8 +39,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
|
|||
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
|
||||
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
|
||||
cloud.google.com/go v0.109.0/go.mod h1:2sYycXt75t/CSB5R9M2wPU1tJmire7AQZTPtITcGBVE=
|
||||
cloud.google.com/go v0.110.1 h1:oDJ19Fu9TX9Xs06iyCw4yifSqZ7JQ8BeuVHcTmWQlOA=
|
||||
cloud.google.com/go v0.110.1/go.mod h1:uc+V/WjzxQ7vpkxfJhgW4Q4axWXyfAerpQOuSNDZyFw=
|
||||
cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA=
|
||||
cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw=
|
||||
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
|
||||
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
|
||||
cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o=
|
||||
|
@ -124,8 +124,8 @@ cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARy
|
|||
cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
|
||||
cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=
|
||||
cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
|
||||
cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
|
||||
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
|
||||
cloud.google.com/go/compute v1.19.2 h1:GbJtPo8OKVHbVep8jvM57KidbYHxeE68LOVqouNLrDY=
|
||||
cloud.google.com/go/compute v1.19.2/go.mod h1:5f5a+iC1IriXYauaQ0EyQmEAEq9CGRnV5xJSQSlTV08=
|
||||
cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
|
||||
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
|
||||
|
@ -218,8 +218,8 @@ cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHD
|
|||
cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg=
|
||||
cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE=
|
||||
cloud.google.com/go/iam v0.10.0/go.mod h1:nXAECrMt2qHpF6RZUZseteD6QyanL68reN4OXPw0UWM=
|
||||
cloud.google.com/go/iam v1.0.0 h1:hlQJMovyJJwYjZcTohUH4o1L8Z8kYz+E+W/zktiLCBc=
|
||||
cloud.google.com/go/iam v1.0.0/go.mod h1:ikbQ4f1r91wTmBmmOtBCOtuEOei6taatNXytzB7Cxew=
|
||||
cloud.google.com/go/iam v1.0.1 h1:lyeCAU6jpnVNrE9zGQkTl3WgNgK/X+uWwaw0kynZJMU=
|
||||
cloud.google.com/go/iam v1.0.1/go.mod h1:yR3tmSL8BcZB4bxByRv2jkSIahVmCtfKZwLYGBalRE8=
|
||||
cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc=
|
||||
cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A=
|
||||
cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM=
|
||||
|
@ -565,17 +565,17 @@ github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3eP
|
|||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.12/go.mod h1:J36fOhj1LQBr+O4hJCiT8FwVvieeoSGOtPuvhKlsNu8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.23 h1:gc3lPsAnZpwfi2exupmgHfva0JiAY2BWDg5JWYlmA28=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.23/go.mod h1:rx0ruaQ+gk3OrLFHRRx56lA//XxP8K8uPzeNiKNuWVY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.25 h1:JuYyZcnMPBiFqn87L2cRppo+rNwgah6YwD3VuyvaW6Q=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.25/go.mod h1:dZnYpD5wTW/dQF0rRNLVypB396zWCcPiBIvdvSWHEg4=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.12/go.mod h1:37HG2MBroXK3jXfxVGtbM2J48ra2+Ltu+tmwr/jO0KA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.22 h1:Hp9rwJS4giQ48xqonRV/s7QcDf/wxF6UY7osRmBabvI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.22/go.mod h1:BfNcm6A9nSd+bzejDcMJ5RE+k6WbkCwWkQil7q4heRk=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.24 h1:PjiYyls3QdCrzqUN35jMWtUK1vqVZ+zLfdOa/UPFDp0=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.24/go.mod h1:jYPYi99wUOPIFi0rhiOvXeSEReVOzBqFNOX5bXYoG2o=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22/go.mod h1:YGSIJyQ6D6FjKMQh16hVFSIUD54L4F7zTGePqYMYYJU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 h1:jJPgroehGvjrde3XufFIJUZVK5A2L9a3KwSFgKy9n8w=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.51/go.mod h1:7Grl2gV+dx9SWrUIgwwlUvU40t7+lOSbx34XwfmsTkY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.65 h1:4irvSxFf0u7pQdtpmUoDSjvMNpOG/8yDUq3orwd9qdg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.65/go.mod h1:BAWKiL53LT19UMewYr9YhZ8xPO69u6NwmGUjSjRwUdM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.67 h1:fI9/5BDEaAv/pv1VO1X1n3jfP9it+IGqWsCuuBQI8wM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.67/go.mod h1:zQClPRIwQZfJlZq6WZve+s4Tb4JW+3V6eS+4+KrYeP8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28/go.mod h1:3lwChorpIM/BhImY/hy+Z6jekmN92cXGPI1QJasVPYY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
|
||||
|
@ -619,8 +619,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1/go.mod h1:O1YSOg3aekZibh2Sn
|
|||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 h1:PkHIIJs8qvq0e5QybnZoG1K/9QTrLr9OsqCIo59jOBA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.3/go.mod h1:b+psTJn33Q4qGoDaM7ZiOVVG8uVjGI6HaZ8WBHdgDgU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.11 h1:uBE+Zj478pfxV98L6SEpvxYiADNjTlMNY714PJLE7uo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.11/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 h1:2DQLAKDteoEDI8zpCzqBMaZlJuoE9iTYD0gFmXVax9E=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8=
|
||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
|
@ -877,8 +877,8 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ
|
|||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0 h1:EW9gIJRmt9lzk66Fhh4S8VEtURA6QHZqGeSRE9Nb2/U=
|
||||
github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/drakkan/crypto v0.0.0-20230408075646-704a7f627371 h1:e2fWtTFAkFfNOeqww6HsEhtxETjGUBKnmIbMNB7V8mg=
|
||||
github.com/drakkan/crypto v0.0.0-20230408075646-704a7f627371/go.mod h1:svd5Kbdx1UEmxh6mV0H38ASBeI90vEuujcyP74bw210=
|
||||
github.com/drakkan/crypto v0.0.0-20230512104844-219592fc3028 h1:qUrs/afB0gubJUY5kOmxLx1euFlXn9yUMUhli7Njob8=
|
||||
github.com/drakkan/crypto v0.0.0-20230512104844-219592fc3028/go.mod h1:FPowDKc1rEQhN3Xf48AhpBr8eSNzpEYaAQczEYcuAVU=
|
||||
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA=
|
||||
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
|
||||
github.com/drakkan/webdav v0.0.0-20230227175313-32996838bcd8 h1:tdkLkSKtYd3WSDsZXGJDKsakiNstLQJPN5HjnqCkf2c=
|
||||
|
@ -1842,8 +1842,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
|
|||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=
|
||||
github.com/sftpgo/sdk v0.1.3 h1:o/9herRbrDH6sQwfpKlV3AV0R7qJgOe/x4yQnEIWIHk=
|
||||
github.com/sftpgo/sdk v0.1.3/go.mod h1:gDxDaU3rhp9Y92ddsE7SbQ8jdBNNWK1DKlp5eHXrsb8=
|
||||
github.com/sftpgo/sdk v0.1.4-0.20230512160325-38e59551f700 h1:jL6mfKAaFv862AnBUxIfTH9wmnuPjbWyjHQUGDo+Xt0=
|
||||
github.com/sftpgo/sdk v0.1.4-0.20230512160325-38e59551f700/go.mod h1:gDxDaU3rhp9Y92ddsE7SbQ8jdBNNWK1DKlp5eHXrsb8=
|
||||
github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o=
|
||||
github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8=
|
||||
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||
|
@ -2250,8 +2250,8 @@ golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmL
|
|||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
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=
|
||||
|
@ -2283,8 +2283,8 @@ golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri
|
|||
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
|
||||
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
||||
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
|
||||
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
||||
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
|
||||
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
|
||||
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=
|
||||
|
@ -2301,8 +2301,8 @@ golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
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=
|
||||
|
@ -2468,7 +2468,6 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
|||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -2590,8 +2589,8 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
|||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
||||
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
||||
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -2668,8 +2667,8 @@ google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/
|
|||
google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
|
||||
google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
|
||||
google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
|
||||
google.golang.org/api v0.121.0 h1:8Oopoo8Vavxx6gt+sgs8s8/X60WBAtKQq6JqnkF+xow=
|
||||
google.golang.org/api v0.121.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
|
||||
google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es=
|
||||
google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
|
||||
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=
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
"github.com/sftpgo/sdk/plugin/notifier"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/command"
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
|
@ -965,12 +966,14 @@ func (conns *ActiveConnections) Remove(connectionID string) {
|
|||
conn.GetLocalAddress(), conn.GetRemoteAddress(), err, lastIdx)
|
||||
if conn.GetProtocol() == ProtocolFTP && conn.GetUsername() == "" && !util.Contains(ftpLoginCommands, conn.GetCommand()) {
|
||||
ip := util.GetIPFromRemoteAddress(conn.GetRemoteAddress())
|
||||
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTryed, conn.GetProtocol(),
|
||||
dataprovider.ErrNoAuthTryed.Error())
|
||||
metric.AddNoAuthTryed()
|
||||
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTried, ProtocolFTP,
|
||||
dataprovider.ErrNoAuthTried.Error())
|
||||
metric.AddNoAuthTried()
|
||||
AddDefenderEvent(ip, ProtocolFTP, HostEventNoLoginTried)
|
||||
dataprovider.ExecutePostLoginHook(&dataprovider.User{}, dataprovider.LoginMethodNoAuthTryed, ip,
|
||||
conn.GetProtocol(), dataprovider.ErrNoAuthTryed)
|
||||
dataprovider.ExecutePostLoginHook(&dataprovider.User{}, dataprovider.LoginMethodNoAuthTried, ip,
|
||||
ProtocolFTP, dataprovider.ErrNoAuthTried)
|
||||
plugin.Handler.NotifyLogEvent(notifier.LogEventTypeNoLoginTried, ProtocolFTP, "", ip, "",
|
||||
dataprovider.ErrNoAuthTried)
|
||||
}
|
||||
Config.checkPostDisconnectHook(conn.GetRemoteAddress(), conn.GetProtocol(), conn.GetUsername(),
|
||||
conn.GetID(), conn.GetConnectionTime())
|
||||
|
|
|
@ -325,7 +325,7 @@ func (c *RetentionCheck) checkEmptyDirRemoval(folderPath string) {
|
|||
files, err := c.conn.ListDir(folderPath)
|
||||
if err == nil && len(files) == 0 {
|
||||
err = c.conn.RemoveDir(folderPath)
|
||||
c.conn.Log(logger.LevelDebug, "tryed to remove empty dir %q, error: %v", folderPath, err)
|
||||
c.conn.Log(logger.LevelDebug, "tried to remove empty dir %q, error: %v", folderPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -964,6 +964,21 @@ func getNotifierPluginFromEnv(idx int, pluginConfig *plugin.Config) bool {
|
|||
isSet = true
|
||||
}
|
||||
|
||||
notifierLogEventsString, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_PLUGINS__%v__NOTIFIER_OPTIONS__LOG_EVENTS", idx))
|
||||
if ok {
|
||||
var notifierLogEvents []int
|
||||
for _, e := range notifierLogEventsString {
|
||||
ev, err := strconv.Atoi(e)
|
||||
if err == nil {
|
||||
notifierLogEvents = append(notifierLogEvents, ev)
|
||||
}
|
||||
}
|
||||
if len(notifierLogEvents) > 0 {
|
||||
pluginConfig.NotifierOptions.LogEvents = notifierLogEvents
|
||||
isSet = true
|
||||
}
|
||||
}
|
||||
|
||||
notifierRetryMaxTime, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_PLUGINS__%v__NOTIFIER_OPTIONS__RETRY_MAX_TIME", idx), 0)
|
||||
if ok {
|
||||
pluginConfig.NotifierOptions.RetryMaxTime = int(notifierRetryMaxTime)
|
||||
|
|
|
@ -698,6 +698,7 @@ func TestPluginsFromEnv(t *testing.T) {
|
|||
os.Setenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__FS_EVENTS", "upload,download")
|
||||
os.Setenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__PROVIDER_EVENTS", "add,update")
|
||||
os.Setenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__PROVIDER_OBJECTS", "user,admin")
|
||||
os.Setenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__LOG_EVENTS", "a,1,2")
|
||||
os.Setenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__RETRY_MAX_TIME", "2")
|
||||
os.Setenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__RETRY_QUEUE_MAX_SIZE", "1000")
|
||||
os.Setenv("SFTPGO_PLUGINS__0__CMD", "plugin_start_cmd")
|
||||
|
@ -712,6 +713,7 @@ func TestPluginsFromEnv(t *testing.T) {
|
|||
os.Unsetenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__FS_EVENTS")
|
||||
os.Unsetenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__PROVIDER_EVENTS")
|
||||
os.Unsetenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__PROVIDER_OBJECTS")
|
||||
os.Unsetenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__LOG_EVENTS")
|
||||
os.Unsetenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__RETRY_MAX_TIME")
|
||||
os.Unsetenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__RETRY_QUEUE_MAX_SIZE")
|
||||
os.Unsetenv("SFTPGO_PLUGINS__0__CMD")
|
||||
|
@ -738,6 +740,9 @@ func TestPluginsFromEnv(t *testing.T) {
|
|||
require.Len(t, pluginConf.NotifierOptions.ProviderObjects, 2)
|
||||
require.Equal(t, "user", pluginConf.NotifierOptions.ProviderObjects[0])
|
||||
require.Equal(t, "admin", pluginConf.NotifierOptions.ProviderObjects[1])
|
||||
require.Len(t, pluginConf.NotifierOptions.LogEvents, 2)
|
||||
require.Equal(t, 1, pluginConf.NotifierOptions.LogEvents[0])
|
||||
require.Equal(t, 2, pluginConf.NotifierOptions.LogEvents[1])
|
||||
require.Equal(t, 2, pluginConf.NotifierOptions.RetryMaxTime)
|
||||
require.Equal(t, 1000, pluginConf.NotifierOptions.RetryQueueMaxSize)
|
||||
require.Equal(t, "plugin_start_cmd", pluginConf.Cmd)
|
||||
|
|
|
@ -159,8 +159,8 @@ var (
|
|||
LoginMethodTLSCertificate, LoginMethodTLSCertificateAndPwd}
|
||||
// SSHMultiStepsLoginMethods defines the supported Multi-Step Authentications
|
||||
SSHMultiStepsLoginMethods = []string{SSHLoginMethodKeyAndPassword, SSHLoginMethodKeyAndKeyboardInt}
|
||||
// ErrNoAuthTryed defines the error for connection closed before authentication
|
||||
ErrNoAuthTryed = errors.New("no auth tryed")
|
||||
// ErrNoAuthTried defines the error for connection closed before authentication
|
||||
ErrNoAuthTried = errors.New("no auth tried")
|
||||
// ErrNotImplemented defines the error for features not supported for a particular data provider
|
||||
ErrNotImplemented = errors.New("feature not supported with the configured data provider")
|
||||
// ValidProtocols defines all the valid protcols
|
||||
|
|
|
@ -76,7 +76,7 @@ const (
|
|||
|
||||
// Available login methods
|
||||
const (
|
||||
LoginMethodNoAuthTryed = "no_auth_tryed"
|
||||
LoginMethodNoAuthTried = "no_auth_tried"
|
||||
LoginMethodPassword = "password"
|
||||
SSHLoginMethodPassword = "password-over-SSH"
|
||||
SSHLoginMethodPublicKey = "publickey"
|
||||
|
|
|
@ -25,11 +25,13 @@ import (
|
|||
"sync"
|
||||
|
||||
ftpserver "github.com/fclairamb/ftpserverlib"
|
||||
"github.com/sftpgo/sdk/plugin/notifier"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/common"
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||
"github.com/drakkan/sftpgo/v2/internal/metric"
|
||||
"github.com/drakkan/sftpgo/v2/internal/plugin"
|
||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
||||
"github.com/drakkan/sftpgo/v2/internal/version"
|
||||
)
|
||||
|
@ -426,10 +428,13 @@ func updateLoginMetrics(user *dataprovider.User, ip, loginMethod string, err err
|
|||
logger.ConnectionFailedLog(user.Username, ip, loginMethod,
|
||||
common.ProtocolFTP, err.Error())
|
||||
event := common.HostEventLoginFailed
|
||||
logEv := notifier.LogEventTypeLoginFailed
|
||||
if errors.Is(err, util.ErrNotFound) {
|
||||
event = common.HostEventUserNotFound
|
||||
logEv = notifier.LogEventTypeLoginNoUser
|
||||
}
|
||||
common.AddDefenderEvent(ip, common.ProtocolFTP, event)
|
||||
plugin.Handler.NotifyLogEvent(logEv, common.ProtocolFTP, user.Username, ip, "", err)
|
||||
}
|
||||
metric.AddLoginResult(loginMethod, err)
|
||||
dataprovider.ExecutePostLoginHook(user, loginMethod, ip, common.ProtocolFTP, err)
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/sftpgo/sdk/plugin/eventsearcher"
|
||||
"github.com/sftpgo/sdk/plugin/notifier"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
"github.com/drakkan/sftpgo/v2/internal/plugin"
|
||||
|
@ -67,11 +68,10 @@ func getCommonSearchParamsFromRequest(r *http.Request) (eventsearcher.CommonSear
|
|||
}
|
||||
c.EndTimestamp = ts
|
||||
}
|
||||
c.Actions = getCommaSeparatedQueryParam(r, "actions")
|
||||
c.Username = r.URL.Query().Get("username")
|
||||
c.IP = r.URL.Query().Get("ip")
|
||||
c.InstanceIDs = getCommaSeparatedQueryParam(r, "instance_ids")
|
||||
c.ExcludeIDs = getCommaSeparatedQueryParam(r, "exclude_ids")
|
||||
c.FromID = r.URL.Query().Get("from_id")
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ func getFsSearchParamsFromRequest(r *http.Request) (eventsearcher.FsEventSearch,
|
|||
}
|
||||
s.FsProvider = val
|
||||
}
|
||||
s.Actions = getCommaSeparatedQueryParam(r, "actions")
|
||||
s.SSHCmd = r.URL.Query().Get("ssh_cmd")
|
||||
s.Bucket = r.URL.Query().Get("bucket")
|
||||
s.Endpoint = r.URL.Query().Get("endpoint")
|
||||
|
@ -115,11 +116,31 @@ func getProviderSearchParamsFromRequest(r *http.Request) (eventsearcher.Provider
|
|||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.Actions = getCommaSeparatedQueryParam(r, "actions")
|
||||
s.ObjectName = r.URL.Query().Get("object_name")
|
||||
s.ObjectTypes = getCommaSeparatedQueryParam(r, "object_types")
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func getLogSearchParamsFromRequest(r *http.Request) (eventsearcher.LogEventSearch, error) {
|
||||
var err error
|
||||
s := eventsearcher.LogEventSearch{}
|
||||
s.CommonSearchParams, err = getCommonSearchParamsFromRequest(r)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.Protocols = getCommaSeparatedQueryParam(r, "protocols")
|
||||
events := getCommaSeparatedQueryParam(r, "events")
|
||||
for _, ev := range events {
|
||||
evType, err := strconv.ParseUint(ev, 10, 32)
|
||||
if err == nil {
|
||||
s.Events = append(s.Events, int32(evType))
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func searchFsEvents(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
claims, err := getTokenClaims(r)
|
||||
|
@ -143,7 +164,7 @@ func searchFsEvents(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
data, _, _, err := plugin.Handler.SearchFsEvents(&filters)
|
||||
data, err := plugin.Handler.SearchFsEvents(&filters)
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
|
@ -178,7 +199,40 @@ func searchProviderEvents(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
data, _, _, err := plugin.Handler.SearchProviderEvents(&filters)
|
||||
data, err := plugin.Handler.SearchProviderEvents(&filters)
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.Write(data) //nolint:errcheck
|
||||
}
|
||||
|
||||
func searchLogEvents(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
claims, err := getTokenClaims(r)
|
||||
if err != nil || claims.Username == "" {
|
||||
sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var filters eventsearcher.LogEventSearch
|
||||
if filters, err = getLogSearchParamsFromRequest(r); err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
filters.Role = getRoleFilterForEventSearch(r, claims.Role)
|
||||
|
||||
if getBoolQueryParam(r, "csv_export") {
|
||||
filters.Limit = 100
|
||||
if err := exportLogEvents(w, &filters); err != nil {
|
||||
panic(http.ErrAbortHandler)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
data, err := plugin.Handler.SearchLogEvents(&filters)
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
|
@ -202,7 +256,7 @@ func exportFsEvents(w http.ResponseWriter, filters *eventsearcher.FsEventSearch)
|
|||
}
|
||||
results := make([]fsEvent, 0, filters.Limit)
|
||||
for {
|
||||
data, _, _, err := plugin.Handler.SearchFsEvents(filters)
|
||||
data, err := plugin.Handler.SearchFsEvents(filters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -218,7 +272,7 @@ func exportFsEvents(w http.ResponseWriter, filters *eventsearcher.FsEventSearch)
|
|||
break
|
||||
}
|
||||
filters.StartTimestamp = results[len(results)-1].Timestamp
|
||||
filters.ExcludeIDs = []string{results[len(results)-1].ID}
|
||||
filters.FromID = results[len(results)-1].ID
|
||||
results = nil
|
||||
}
|
||||
csvWriter.Flush()
|
||||
|
@ -239,7 +293,44 @@ func exportProviderEvents(w http.ResponseWriter, filters *eventsearcher.Provider
|
|||
}
|
||||
results := make([]providerEvent, 0, filters.Limit)
|
||||
for {
|
||||
data, _, _, err := plugin.Handler.SearchProviderEvents(filters)
|
||||
data, err := plugin.Handler.SearchProviderEvents(filters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, &results); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, event := range results {
|
||||
if err := csvWriter.Write(event.getCSVData()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(results) < filters.Limit || len(results) == 0 {
|
||||
break
|
||||
}
|
||||
filters.FromID = results[len(results)-1].ID
|
||||
filters.StartTimestamp = results[len(results)-1].Timestamp
|
||||
results = nil
|
||||
}
|
||||
csvWriter.Flush()
|
||||
return csvWriter.Error()
|
||||
}
|
||||
|
||||
func exportLogEvents(w http.ResponseWriter, filters *eventsearcher.LogEventSearch) error {
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=logs-%s.csv", time.Now().Format("2006-01-02T15-04-05")))
|
||||
w.Header().Set("Content-Type", "text/csv")
|
||||
w.Header().Set("Accept-Ranges", "none")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
ev := logEvent{}
|
||||
csvWriter := csv.NewWriter(w)
|
||||
err := csvWriter.Write(ev.getCSVHeader())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
results := make([]logEvent, 0, filters.Limit)
|
||||
for {
|
||||
data, err := plugin.Handler.SearchLogEvents(filters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -255,7 +346,7 @@ func exportProviderEvents(w http.ResponseWriter, filters *eventsearcher.Provider
|
|||
break
|
||||
}
|
||||
filters.StartTimestamp = results[len(results)-1].Timestamp
|
||||
filters.ExcludeIDs = []string{results[len(results)-1].ID}
|
||||
filters.FromID = results[len(results)-1].ID
|
||||
results = nil
|
||||
}
|
||||
csvWriter.Flush()
|
||||
|
@ -349,3 +440,39 @@ func (e *providerEvent) getCSVData() []string {
|
|||
return []string{timestamp.Format(time.RFC3339Nano), e.Action, e.ObjectType, e.ObjectName,
|
||||
e.Username, e.IP}
|
||||
}
|
||||
|
||||
type logEvent struct {
|
||||
ID string `json:"id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Event int `json:"event"`
|
||||
Protocol string `json:"protocol"`
|
||||
Username string `json:"username,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Role string `json:"role,omitempty"`
|
||||
}
|
||||
|
||||
func (e *logEvent) getCSVHeader() []string {
|
||||
return []string{"Time", "Event", "Protocol", "User", "IP", "Message"}
|
||||
}
|
||||
|
||||
func (e *logEvent) getCSVData() []string {
|
||||
timestamp := time.Unix(0, e.Timestamp).UTC()
|
||||
return []string{timestamp.Format(time.RFC3339Nano), getLogEventString(notifier.LogEventType(e.Event)),
|
||||
e.Protocol, e.Username, e.IP, e.Message}
|
||||
}
|
||||
|
||||
func getLogEventString(event notifier.LogEventType) string {
|
||||
switch event {
|
||||
case notifier.LogEventTypeLoginFailed:
|
||||
return "Login failed"
|
||||
case notifier.LogEventTypeLoginNoUser:
|
||||
return "Login with non-existent user"
|
||||
case notifier.LogEventTypeNoLoginTried:
|
||||
return "No login tried"
|
||||
case notifier.LogEventTypeNotNegotiated:
|
||||
return "Algorithm negotiation failed"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/sftpgo/sdk/plugin/notifier"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/common"
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
|
@ -614,6 +615,11 @@ func updateLoginMetrics(user *dataprovider.User, loginMethod, ip string, err err
|
|||
if err != nil && err != common.ErrInternalFailure && err != common.ErrNoCredentials {
|
||||
logger.ConnectionFailedLog(user.Username, ip, loginMethod, protocol, err.Error())
|
||||
err = handleDefenderEventLoginFailed(ip, err)
|
||||
logEv := notifier.LogEventTypeLoginFailed
|
||||
if errors.Is(err, util.ErrNotFound) {
|
||||
logEv = notifier.LogEventTypeLoginNoUser
|
||||
}
|
||||
plugin.Handler.NotifyLogEvent(logEv, protocol, user.Username, ip, "", err)
|
||||
}
|
||||
metric.AddLoginResult(loginMethod, err)
|
||||
dataprovider.ExecutePostLoginHook(user, loginMethod, ip, protocol, err)
|
||||
|
|
|
@ -91,6 +91,7 @@ const (
|
|||
metadataChecksPath = "/api/v2/metadata/users/checks"
|
||||
fsEventsPath = "/api/v2/events/fs"
|
||||
providerEventsPath = "/api/v2/events/provider"
|
||||
logEventsPath = "/api/v2/events/logs"
|
||||
sharesPath = "/api/v2/shares"
|
||||
eventActionsPath = "/api/v2/eventactions"
|
||||
eventRulesPath = "/api/v2/eventrules"
|
||||
|
@ -148,6 +149,7 @@ const (
|
|||
webEventsPathDefault = "/web/admin/events"
|
||||
webEventsFsSearchPathDefault = "/web/admin/events/fs"
|
||||
webEventsProviderSearchPathDefault = "/web/admin/events/provider"
|
||||
webEventsLogSearchPathDefault = "/web/admin/events/logs"
|
||||
webConfigsPathDefault = "/web/admin/configs"
|
||||
webClientLoginPathDefault = "/web/client/login"
|
||||
webClientOIDCLoginPathDefault = "/web/client/oidclogin"
|
||||
|
@ -243,6 +245,7 @@ var (
|
|||
webEventsPath string
|
||||
webEventsFsSearchPath string
|
||||
webEventsProviderSearchPath string
|
||||
webEventsLogSearchPath string
|
||||
webConfigsPath string
|
||||
webDefenderHostsPath string
|
||||
webClientLoginPath string
|
||||
|
@ -1142,6 +1145,7 @@ func updateWebAdminURLs(baseURL string) {
|
|||
webEventsPath = path.Join(baseURL, webEventsPathDefault)
|
||||
webEventsFsSearchPath = path.Join(baseURL, webEventsFsSearchPathDefault)
|
||||
webEventsProviderSearchPath = path.Join(baseURL, webEventsProviderSearchPathDefault)
|
||||
webEventsLogSearchPath = path.Join(baseURL, webEventsLogSearchPathDefault)
|
||||
webConfigsPath = path.Join(baseURL, webConfigsPathDefault)
|
||||
webStaticFilesPath = path.Join(baseURL, webStaticFilesPathDefault)
|
||||
webOpenAPIPath = path.Join(baseURL, webOpenAPIPathDefault)
|
||||
|
|
|
@ -125,6 +125,7 @@ const (
|
|||
metadataBasePath = "/api/v2/metadata/users"
|
||||
fsEventsPath = "/api/v2/events/fs"
|
||||
providerEventsPath = "/api/v2/events/provider"
|
||||
logEventsPath = "/api/v2/events/logs"
|
||||
sharesPath = "/api/v2/shares"
|
||||
eventActionsPath = "/api/v2/eventactions"
|
||||
eventRulesPath = "/api/v2/eventrules"
|
||||
|
@ -9869,12 +9870,65 @@ func TestSearchEvents(t *testing.T) {
|
|||
}
|
||||
exportFunc()
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, logEventsPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
events = make([]map[string]any, 0)
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &events)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, events, 1) {
|
||||
ev := events[0]
|
||||
for _, field := range []string{"id", "timestamp", "event", "ip", "message", "role", "instance_id"} {
|
||||
_, ok := ev[field]
|
||||
assert.True(t, ok, field)
|
||||
}
|
||||
}
|
||||
req, err = http.NewRequest(http.MethodGet, logEventsPath+"?events=a,1", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
// CSV export
|
||||
req, err = http.NewRequest(http.MethodGet, logEventsPath+"?csv_export=true", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Equal(t, "text/csv", rr.Header().Get("Content-Type"))
|
||||
// the test eventsearcher plugin returns error if start_timestamp < 0
|
||||
req, err = http.NewRequest(http.MethodGet, logEventsPath+"?start_timestamp=-1", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusInternalServerError, rr)
|
||||
// CSV export with error
|
||||
exportFunc = func() {
|
||||
defer func() {
|
||||
rcv := recover()
|
||||
assert.Equal(t, http.ErrAbortHandler, rcv)
|
||||
}()
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, logEventsPath+"?start_timestamp=-1&csv_export=true", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
}
|
||||
exportFunc()
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, providerEventsPath+"?limit=2000", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, logEventsPath+"?limit=2000", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, fsEventsPath+"?start_timestamp=a", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, token)
|
||||
|
|
|
@ -44,6 +44,7 @@ import (
|
|||
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||
"github.com/rs/xid"
|
||||
"github.com/sftpgo/sdk"
|
||||
"github.com/sftpgo/sdk/plugin/notifier"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
@ -785,6 +786,11 @@ func TestInvalidToken(t *testing.T) {
|
|||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Invalid token claims")
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
searchLogEvents(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Invalid token claims")
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
addIPListEntry(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
|
@ -3224,6 +3230,14 @@ func TestHTTPSRedirect(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetLogEventString(t *testing.T) {
|
||||
assert.Equal(t, "Login failed", getLogEventString(notifier.LogEventTypeLoginFailed))
|
||||
assert.Equal(t, "Login with non-existent user", getLogEventString(notifier.LogEventTypeLoginNoUser))
|
||||
assert.Equal(t, "No login tried", getLogEventString(notifier.LogEventTypeNoLoginTried))
|
||||
assert.Equal(t, "Algorithm negotiation failed", getLogEventString(notifier.LogEventTypeNotNegotiated))
|
||||
assert.Empty(t, getLogEventString(0))
|
||||
}
|
||||
|
||||
func isSharedProviderSupported() bool {
|
||||
// SQLite shares the implementation with other SQL-based provider but it makes no sense
|
||||
// to use it outside test cases
|
||||
|
|
|
@ -1322,6 +1322,8 @@ func (s *httpdServer) initializeRouter() {
|
|||
Get(fsEventsPath, searchFsEvents)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler).
|
||||
Get(providerEventsPath, searchProviderEvents)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler).
|
||||
Get(logEventsPath, searchLogEvents)
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)).
|
||||
Get(apiKeysPath, getAPIKeys)
|
||||
router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)).
|
||||
|
@ -1724,6 +1726,8 @@ func (s *httpdServer) setupWebAdminRoutes() {
|
|||
Get(webEventsFsSearchPath, searchFsEvents)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler, s.refreshCookie).
|
||||
Get(webEventsProviderSearchPath, searchProviderEvents)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler, s.refreshCookie).
|
||||
Get(webEventsLogSearchPath, searchLogEvents)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Get(webIPListsPath, s.handleWebIPListsPage)
|
||||
router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), compressor.Handler, s.refreshCookie).
|
||||
Get(webIPListsPath+"/{type}", getIPListEntries)
|
||||
|
|
|
@ -388,6 +388,7 @@ type eventsPage struct {
|
|||
basePage
|
||||
FsEventsSearchURL string
|
||||
ProviderEventsSearchURL string
|
||||
LogEventsSearchURL string
|
||||
}
|
||||
|
||||
type configsPage struct {
|
||||
|
@ -3944,6 +3945,7 @@ func (s *httpdServer) handleWebGetEvents(w http.ResponseWriter, r *http.Request)
|
|||
basePage: s.getBasePageData(pageEventsTitle, webEventsPath, r),
|
||||
FsEventsSearchURL: webEventsFsSearchPath,
|
||||
ProviderEventsSearchURL: webEventsProviderSearchPath,
|
||||
LogEventsSearchURL: webEventsLogSearchPath,
|
||||
}
|
||||
renderAdminTemplate(w, templateEvents, data)
|
||||
}
|
||||
|
|
|
@ -108,9 +108,9 @@ var (
|
|||
Help: "The total number of login attempts",
|
||||
})
|
||||
|
||||
// totalNoAuthTryed is te metric that reports the total number of clients disconnected
|
||||
// totalNoAuthTried is te metric that reports the total number of clients disconnected
|
||||
// for inactivity before trying to login
|
||||
totalNoAuthTryed = promauto.NewCounter(prometheus.CounterOpts{
|
||||
totalNoAuthTried = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "sftpgo_no_auth_total",
|
||||
Help: "The total number of clients disconnected for inactivity before trying to login",
|
||||
})
|
||||
|
@ -984,10 +984,10 @@ func AddLoginResult(authMethod string, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// AddNoAuthTryed increments the metric for clients disconnected
|
||||
// AddNoAuthTried increments the metric for clients disconnected
|
||||
// for inactivity before trying to login
|
||||
func AddNoAuthTryed() {
|
||||
totalNoAuthTryed.Inc()
|
||||
func AddNoAuthTried() {
|
||||
totalNoAuthTried.Inc()
|
||||
}
|
||||
|
||||
// HTTPRequestServed increments the metrics for HTTP requests
|
||||
|
|
|
@ -64,9 +64,9 @@ func AddLoginAttempt(_ string) {}
|
|||
// AddLoginResult increments the metrics for login results
|
||||
func AddLoginResult(_ string, _ error) {}
|
||||
|
||||
// AddNoAuthTryed increments the metric for clients disconnected
|
||||
// AddNoAuthTried increments the metric for clients disconnected
|
||||
// for inactivity before trying to login
|
||||
func AddNoAuthTryed() {}
|
||||
func AddNoAuthTried() {}
|
||||
|
||||
// HTTPRequestServed increments the metrics for HTTP requests
|
||||
func HTTPRequestServed(_ int) {}
|
||||
|
|
|
@ -33,6 +33,7 @@ type NotifierConfig struct {
|
|||
FsEvents []string `json:"fs_events" mapstructure:"fs_events"`
|
||||
ProviderEvents []string `json:"provider_events" mapstructure:"provider_events"`
|
||||
ProviderObjects []string `json:"provider_objects" mapstructure:"provider_objects"`
|
||||
LogEvents []int `json:"log_events" mapstructure:"log_events"`
|
||||
RetryMaxTime int `json:"retry_max_time" mapstructure:"retry_max_time"`
|
||||
RetryQueueMaxSize int `json:"retry_queue_max_size" mapstructure:"retry_queue_max_size"`
|
||||
}
|
||||
|
@ -51,6 +52,7 @@ type eventsQueue struct {
|
|||
sync.RWMutex
|
||||
fsEvents []*notifier.FsEvent
|
||||
providerEvents []*notifier.ProviderEvent
|
||||
logEvents []*notifier.LogEvent
|
||||
}
|
||||
|
||||
func (q *eventsQueue) addFsEvent(event *notifier.FsEvent) {
|
||||
|
@ -67,6 +69,13 @@ func (q *eventsQueue) addProviderEvent(event *notifier.ProviderEvent) {
|
|||
q.providerEvents = append(q.providerEvents, event)
|
||||
}
|
||||
|
||||
func (q *eventsQueue) addLogEvent(event *notifier.LogEvent) {
|
||||
q.Lock()
|
||||
defer q.Unlock()
|
||||
|
||||
q.logEvents = append(q.logEvents, event)
|
||||
}
|
||||
|
||||
func (q *eventsQueue) popFsEvent() *notifier.FsEvent {
|
||||
q.Lock()
|
||||
defer q.Unlock()
|
||||
|
@ -97,11 +106,26 @@ func (q *eventsQueue) popProviderEvent() *notifier.ProviderEvent {
|
|||
return ev
|
||||
}
|
||||
|
||||
func (q *eventsQueue) popLogEvent() *notifier.LogEvent {
|
||||
q.Lock()
|
||||
defer q.Unlock()
|
||||
|
||||
if len(q.logEvents) == 0 {
|
||||
return nil
|
||||
}
|
||||
truncLen := len(q.logEvents) - 1
|
||||
ev := q.logEvents[truncLen]
|
||||
q.logEvents[truncLen] = nil
|
||||
q.logEvents = q.logEvents[:truncLen]
|
||||
|
||||
return ev
|
||||
}
|
||||
|
||||
func (q *eventsQueue) getSize() int {
|
||||
q.RLock()
|
||||
defer q.RUnlock()
|
||||
|
||||
return len(q.providerEvents) + len(q.fsEvents)
|
||||
return len(q.providerEvents) + len(q.fsEvents) + len(q.logEvents)
|
||||
}
|
||||
|
||||
type notifierPlugin struct {
|
||||
|
@ -225,6 +249,19 @@ func (p *notifierPlugin) notifyProviderAction(event *notifier.ProviderEvent, obj
|
|||
}()
|
||||
}
|
||||
|
||||
func (p *notifierPlugin) notifyLogEvent(event *notifier.LogEvent) {
|
||||
if !util.Contains(p.config.NotifierOptions.LogEvents, int(event.Event)) {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
Handler.addTask()
|
||||
defer Handler.removeTask()
|
||||
|
||||
p.sendLogEvent(event)
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *notifierPlugin) sendFsEvent(event *notifier.FsEvent) {
|
||||
if err := p.notifier.NotifyFsEvent(event); err != nil {
|
||||
logger.Warn(logSender, "", "unable to send fs action notification to plugin %v: %v", p.config.Cmd, err)
|
||||
|
@ -243,6 +280,15 @@ func (p *notifierPlugin) sendProviderEvent(event *notifier.ProviderEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
func (p *notifierPlugin) sendLogEvent(event *notifier.LogEvent) {
|
||||
if err := p.notifier.NotifyLogEvent(event); err != nil {
|
||||
logger.Warn(logSender, "", "unable to send log event to plugin %v: %v", p.config.Cmd, err)
|
||||
if p.canQueueEvent(event.Timestamp) {
|
||||
p.queue.addLogEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *notifierPlugin) sendQueuedEvents() {
|
||||
queueSize := p.queue.getSize()
|
||||
if queueSize == 0 {
|
||||
|
@ -264,5 +310,12 @@ func (p *notifierPlugin) sendQueuedEvents() {
|
|||
}(providerEv)
|
||||
providerEv = p.queue.popProviderEvent()
|
||||
}
|
||||
logEv := p.queue.popLogEvent()
|
||||
for logEv != nil {
|
||||
go func(ev *notifier.LogEvent) {
|
||||
p.sendLogEvent(ev)
|
||||
}(logEv)
|
||||
logEv = p.queue.popLogEvent()
|
||||
}
|
||||
logger.Debug(logSender, "", "queued events sent for notifier %q, new events size: %v", p.config.Cmd, p.queue.getSize())
|
||||
}
|
||||
|
|
|
@ -291,15 +291,38 @@ func (m *Manager) NotifyProviderEvent(event *notifier.ProviderEvent, object Rend
|
|||
}
|
||||
}
|
||||
|
||||
// NotifyLogEvent sends the log event notifications using any defined notifier plugins
|
||||
func (m *Manager) NotifyLogEvent(event notifier.LogEventType, protocol, username, ip, role string, err error) {
|
||||
if !m.hasNotifiers {
|
||||
return
|
||||
}
|
||||
m.notifLock.RLock()
|
||||
defer m.notifLock.RUnlock()
|
||||
|
||||
e := ¬ifier.LogEvent{
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Event: event,
|
||||
Protocol: protocol,
|
||||
Username: username,
|
||||
IP: ip,
|
||||
Message: err.Error(),
|
||||
Role: role,
|
||||
}
|
||||
|
||||
for _, n := range m.notifiers {
|
||||
n.notifyLogEvent(e)
|
||||
}
|
||||
}
|
||||
|
||||
// HasSearcher returns true if an event searcher plugin is defined
|
||||
func (m *Manager) HasSearcher() bool {
|
||||
return m.hasSearcher
|
||||
}
|
||||
|
||||
// SearchFsEvents returns the filesystem events matching the specified filters
|
||||
func (m *Manager) SearchFsEvents(searchFilters *eventsearcher.FsEventSearch) ([]byte, []string, []string, error) {
|
||||
func (m *Manager) SearchFsEvents(searchFilters *eventsearcher.FsEventSearch) ([]byte, error) {
|
||||
if !m.hasSearcher {
|
||||
return nil, nil, nil, ErrNoSearcher
|
||||
return nil, ErrNoSearcher
|
||||
}
|
||||
m.searcherLock.RLock()
|
||||
plugin := m.searcher
|
||||
|
@ -309,9 +332,9 @@ func (m *Manager) SearchFsEvents(searchFilters *eventsearcher.FsEventSearch) ([]
|
|||
}
|
||||
|
||||
// SearchProviderEvents returns the provider events matching the specified filters
|
||||
func (m *Manager) SearchProviderEvents(searchFilters *eventsearcher.ProviderEventSearch) ([]byte, []string, []string, error) {
|
||||
func (m *Manager) SearchProviderEvents(searchFilters *eventsearcher.ProviderEventSearch) ([]byte, error) {
|
||||
if !m.hasSearcher {
|
||||
return nil, nil, nil, ErrNoSearcher
|
||||
return nil, ErrNoSearcher
|
||||
}
|
||||
m.searcherLock.RLock()
|
||||
plugin := m.searcher
|
||||
|
@ -320,6 +343,18 @@ func (m *Manager) SearchProviderEvents(searchFilters *eventsearcher.ProviderEven
|
|||
return plugin.searchear.SearchProviderEvents(searchFilters)
|
||||
}
|
||||
|
||||
// SearchLogEvents returns the log events matching the specified filters
|
||||
func (m *Manager) SearchLogEvents(searchFilters *eventsearcher.LogEventSearch) ([]byte, error) {
|
||||
if !m.hasSearcher {
|
||||
return nil, ErrNoSearcher
|
||||
}
|
||||
m.searcherLock.RLock()
|
||||
plugin := m.searcher
|
||||
m.searcherLock.RUnlock()
|
||||
|
||||
return plugin.searchear.SearchLogEvents(searchFilters)
|
||||
}
|
||||
|
||||
// HasMetadater returns true if a metadata plugin is defined
|
||||
func (m *Manager) HasMetadater() bool {
|
||||
return m.hasMetadater
|
||||
|
|
|
@ -32,12 +32,14 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
"github.com/sftpgo/sdk/plugin/notifier"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/common"
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||
"github.com/drakkan/sftpgo/v2/internal/metric"
|
||||
"github.com/drakkan/sftpgo/v2/internal/plugin"
|
||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
||||
"github.com/drakkan/sftpgo/v2/internal/vfs"
|
||||
)
|
||||
|
@ -762,19 +764,27 @@ func checkAuthError(ip string, err error) {
|
|||
if errors.As(err, &sftpAuthErr) {
|
||||
if sftpAuthErr.getLoginMethod() == dataprovider.SSHLoginMethodPublicKey {
|
||||
event := common.HostEventLoginFailed
|
||||
logEv := notifier.LogEventTypeLoginFailed
|
||||
if errors.Is(err, util.ErrNotFound) {
|
||||
event = common.HostEventUserNotFound
|
||||
logEv = notifier.LogEventTypeLoginNoUser
|
||||
}
|
||||
common.AddDefenderEvent(ip, common.ProtocolSSH, event)
|
||||
plugin.Handler.NotifyLogEvent(logEv, common.ProtocolSSH, "", ip, "", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTryed, common.ProtocolSSH, err.Error())
|
||||
metric.AddNoAuthTryed()
|
||||
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTried, common.ProtocolSSH, err.Error())
|
||||
metric.AddNoAuthTried()
|
||||
common.AddDefenderEvent(ip, common.ProtocolSSH, common.HostEventNoLoginTried)
|
||||
dataprovider.ExecutePostLoginHook(&dataprovider.User{}, dataprovider.LoginMethodNoAuthTryed, ip, common.ProtocolSSH, err)
|
||||
dataprovider.ExecutePostLoginHook(&dataprovider.User{}, dataprovider.LoginMethodNoAuthTried, ip, common.ProtocolSSH, err)
|
||||
logEv := notifier.LogEventTypeNoLoginTried
|
||||
if errors.Is(err, ssh.ErrNoCommonAlgo) {
|
||||
logEv = notifier.LogEventTypeNotNegotiated
|
||||
}
|
||||
plugin.Handler.NotifyLogEvent(logEv, common.ProtocolSSH, "", ip, "", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1230,10 +1240,13 @@ func updateLoginMetrics(user *dataprovider.User, ip, method string, err error) {
|
|||
// record failed login key auth only once for session if the
|
||||
// authentication fails in checkAuthError
|
||||
event := common.HostEventLoginFailed
|
||||
logEv := notifier.LogEventTypeLoginFailed
|
||||
if errors.Is(err, util.ErrNotFound) {
|
||||
event = common.HostEventUserNotFound
|
||||
logEv = notifier.LogEventTypeLoginNoUser
|
||||
}
|
||||
common.AddDefenderEvent(ip, common.ProtocolSSH, event)
|
||||
plugin.Handler.NotifyLogEvent(logEv, common.ProtocolSSH, user.Username, ip, "", err)
|
||||
}
|
||||
}
|
||||
metric.AddLoginResult(method, err)
|
||||
|
|
|
@ -48,7 +48,7 @@ type webDavFile struct {
|
|||
info os.FileInfo
|
||||
startOffset int64
|
||||
isFinished bool
|
||||
readTryed atomic.Bool
|
||||
readTried atomic.Bool
|
||||
}
|
||||
|
||||
func newWebDavFile(baseTransfer *common.BaseTransfer, pipeWriter *vfs.PipeWriter, pipeReader *pipeat.PipeReaderAt) *webDavFile {
|
||||
|
@ -70,7 +70,7 @@ func newWebDavFile(baseTransfer *common.BaseTransfer, pipeWriter *vfs.PipeWriter
|
|||
startOffset: 0,
|
||||
info: nil,
|
||||
}
|
||||
f.readTryed.Store(false)
|
||||
f.readTried.Store(false)
|
||||
return f
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,7 @@ func (f *webDavFile) checkFirstRead() error {
|
|||
f.Connection.Log(logger.LevelDebug, "download for file %q denied by pre action: %v", f.GetVirtualPath(), err)
|
||||
return f.Connection.GetPermissionDeniedError()
|
||||
}
|
||||
f.readTryed.Store(true)
|
||||
f.readTried.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,7 @@ func (f *webDavFile) Read(p []byte) (n int, err error) {
|
|||
if f.AbortTransfer.Load() {
|
||||
return 0, errTransferAborted
|
||||
}
|
||||
if !f.readTryed.Load() {
|
||||
if !f.readTried.Load() {
|
||||
if err := f.checkFirstRead(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -417,7 +417,7 @@ func (f *webDavFile) setFinished() error {
|
|||
|
||||
func (f *webDavFile) isTransfer() bool {
|
||||
if f.GetType() == common.TransferDownload {
|
||||
return f.readTryed.Load()
|
||||
return f.readTried.Load()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -33,11 +33,13 @@ import (
|
|||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/rs/cors"
|
||||
"github.com/rs/xid"
|
||||
"github.com/sftpgo/sdk/plugin/notifier"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/common"
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||
"github.com/drakkan/sftpgo/v2/internal/metric"
|
||||
"github.com/drakkan/sftpgo/v2/internal/plugin"
|
||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
||||
)
|
||||
|
||||
|
@ -414,10 +416,13 @@ func updateLoginMetrics(user *dataprovider.User, ip, loginMethod string, err err
|
|||
if err != nil && err != common.ErrInternalFailure && err != common.ErrNoCredentials {
|
||||
logger.ConnectionFailedLog(user.Username, ip, loginMethod, common.ProtocolWebDAV, err.Error())
|
||||
event := common.HostEventLoginFailed
|
||||
logEv := notifier.LogEventTypeLoginFailed
|
||||
if errors.Is(err, util.ErrNotFound) {
|
||||
event = common.HostEventUserNotFound
|
||||
logEv = notifier.LogEventTypeLoginNoUser
|
||||
}
|
||||
common.AddDefenderEvent(ip, common.ProtocolWebDAV, event)
|
||||
plugin.Handler.NotifyLogEvent(logEv, common.ProtocolWebDAV, user.Username, ip, "", err)
|
||||
}
|
||||
metric.AddLoginResult(loginMethod, err)
|
||||
dataprovider.ExecutePostLoginHook(user, loginMethod, ip, common.ProtocolWebDAV, err)
|
||||
|
|
|
@ -2594,13 +2594,10 @@ paths:
|
|||
explode: false
|
||||
required: false
|
||||
- in: query
|
||||
name: exclude_ids
|
||||
name: from_id
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: 'the event id must not be included among those specified. This is useful for cursor based pagination. Empty or missing means omit this filter. Values must be specified comma separated'
|
||||
explode: false
|
||||
type: string
|
||||
description: 'the event id to start from. This is useful for cursor based pagination. Empty or missing means omit this filter.'
|
||||
required: false
|
||||
- in: query
|
||||
name: role
|
||||
|
@ -2728,13 +2725,10 @@ paths:
|
|||
explode: false
|
||||
required: false
|
||||
- in: query
|
||||
name: exclude_ids
|
||||
name: from_id
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: 'the event id must not be included among those specified. This is useful for cursor based pagination. Empty or missing means omit this filter. Values must be specified comma separated'
|
||||
explode: false
|
||||
type: string
|
||||
description: 'the event id to start from. This is useful for cursor based pagination. Empty or missing means omit this filter.'
|
||||
required: false
|
||||
- in: query
|
||||
name: role
|
||||
|
@ -2797,6 +2791,131 @@ paths:
|
|||
$ref: '#/components/responses/InternalServerError'
|
||||
default:
|
||||
$ref: '#/components/responses/DefaultResponse'
|
||||
/events/log:
|
||||
get:
|
||||
tags:
|
||||
- events
|
||||
summary: Get log events
|
||||
description: 'Returns an array with one or more log events applying the specified filters. This API is only available if you configure an "eventsearcher" plugin'
|
||||
operationId: get_log_events
|
||||
parameters:
|
||||
- in: query
|
||||
name: start_timestamp
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 0
|
||||
default: 0
|
||||
required: false
|
||||
description: 'the event timestamp, unix timestamp in nanoseconds, must be greater than or equal to the specified one. 0 or missing means omit this filter'
|
||||
- in: query
|
||||
name: end_timestamp
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 0
|
||||
default: 0
|
||||
required: false
|
||||
description: 'the event timestamp, unix timestamp in nanoseconds, must be less than or equal to the specified one. 0 or missing means omit this filter'
|
||||
- in: query
|
||||
name: events
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/LogEventType'
|
||||
description: 'the log events must be included among those specified. Empty or missing means omit this filter. Events must be specified comma separated'
|
||||
explode: false
|
||||
required: false
|
||||
- in: query
|
||||
name: username
|
||||
schema:
|
||||
type: string
|
||||
description: 'the event username must be the same as the one specified. Empty or missing means omit this filter'
|
||||
required: false
|
||||
- in: query
|
||||
name: ip
|
||||
schema:
|
||||
type: string
|
||||
description: 'the event IP must be the same as the one specified. Empty or missing means omit this filter'
|
||||
required: false
|
||||
- in: query
|
||||
name: protocols
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/EventProtocols'
|
||||
description: 'the event protocol must be included among those specified. Empty or missing means omit this filter. Values must be specified comma separated'
|
||||
explode: false
|
||||
required: false
|
||||
- in: query
|
||||
name: instance_ids
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: 'the event instance id must be included among those specified. Empty or missing means omit this filter. Values must be specified comma separated'
|
||||
explode: false
|
||||
required: false
|
||||
- in: query
|
||||
name: from_id
|
||||
schema:
|
||||
type: string
|
||||
description: 'the event id to start from. This is useful for cursor based pagination. Empty or missing means omit this filter.'
|
||||
required: false
|
||||
- in: query
|
||||
name: role
|
||||
schema:
|
||||
type: string
|
||||
description: 'User role. Empty or missing means omit this filter. Ignored if the admin has a role'
|
||||
required: false
|
||||
- in: query
|
||||
name: csv_export
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
description: 'If enabled, events are exported as a CSV file'
|
||||
- in: query
|
||||
name: limit
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 1000
|
||||
default: 100
|
||||
required: false
|
||||
description: 'The maximum number of items to return. Max value is 1000, default is 100'
|
||||
- in: query
|
||||
name: order
|
||||
required: false
|
||||
description: Ordering events by timestamp. Default DESC
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- ASC
|
||||
- DESC
|
||||
example: DESC
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/LogEvent'
|
||||
text/csv:
|
||||
schema:
|
||||
type: string
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
default:
|
||||
$ref: '#/components/responses/DefaultResponse'
|
||||
/apikeys:
|
||||
get:
|
||||
security:
|
||||
|
@ -5143,6 +5262,19 @@ components:
|
|||
- roles
|
||||
- ip_lists
|
||||
- configs
|
||||
LogEventType:
|
||||
type: integer
|
||||
enum:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
description: >
|
||||
Event status:
|
||||
* `1` - Login failed
|
||||
* `2` - Login failed non-existent user
|
||||
* `3` - No login tried
|
||||
* `4` - Algorithm negotiation failed
|
||||
FsEventStatus:
|
||||
type: integer
|
||||
enum:
|
||||
|
@ -6787,6 +6919,8 @@ components:
|
|||
type: string
|
||||
open_flags:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
instance_id:
|
||||
type: string
|
||||
ProviderEvent:
|
||||
|
@ -6812,6 +6946,31 @@ components:
|
|||
type: string
|
||||
format: byte
|
||||
description: 'base64 of the JSON serialized object with sensitive fields removed'
|
||||
role:
|
||||
type: string
|
||||
instance_id:
|
||||
type: string
|
||||
LogEvent:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
timestamp:
|
||||
type: integer
|
||||
format: int64
|
||||
description: 'unix timestamp in nanoseconds'
|
||||
event:
|
||||
$ref: '#/components/schemas/LogEventType'
|
||||
protocol:
|
||||
$ref: '#/components/schemas/EventProtocols'
|
||||
username:
|
||||
type: string
|
||||
ip:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
instance_id:
|
||||
type: string
|
||||
KeyValue:
|
||||
|
|
|
@ -44,6 +44,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
<select class="form-control selectpicker" id="idEventType" name="events_type" onchange="onEventChanged(this.value)">
|
||||
<option value="1" selected>Fs events</option>
|
||||
<option value="2">Provider events</option>
|
||||
<option value="3">Other events</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
|
@ -59,7 +60,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-4">
|
||||
<select class="form-control selectpicker fs-events" id="idProtocols" name="protocols" title="Protocols" multiple>
|
||||
<select class="form-control selectpicker fs-events" id="idStatuses" name="statuses" title="Statuses" multiple>
|
||||
<option value="1">OK</option>
|
||||
<option value="2">KO</option>
|
||||
<option value="3">Quota exceeded</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<select class="form-control selectpicker fs-events log-events" id="idProtocols" name="protocols" title="Protocols" multiple>
|
||||
<option value="SFTP">SFTP</option>
|
||||
<option value="SCP">SCP</option>
|
||||
<option value="SSH">SSH</option>
|
||||
|
@ -72,13 +80,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
<option value="EventAction">EventAction</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<select class="form-control selectpicker fs-events" id="idStatuses" name="statuses" title="Statuses" multiple>
|
||||
<option value="1">OK</option>
|
||||
<option value="2">KO</option>
|
||||
<option value="3">Quota exceeded</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<div class="input-group">
|
||||
<input type="text" id="dateTimeRange" class="form-control bg-light border-0" aria-describedby="search-button">
|
||||
|
@ -129,6 +130,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive log-events">
|
||||
<table class="table table-hover nowrap" id="dataTableLog" width="100%" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Time</th>
|
||||
<th>Action</th>
|
||||
<th>User</th>
|
||||
<th>Proto</th>
|
||||
<th>IP</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="paginationContainer" class="m-4 d-none">
|
||||
<nav aria-label="Pagination">
|
||||
<ul class="pagination justify-content-end">
|
||||
|
@ -160,6 +177,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
let dateFn = $.fn.dataTable.render.datetime();
|
||||
let isFsDataTableInitialized = false;
|
||||
let isProviderDataTableInitialized = false;
|
||||
let isLogDataTableInitialized = false;
|
||||
const pageSize = 20;
|
||||
const paginationData = new Map();
|
||||
|
||||
|
@ -278,12 +296,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
return;
|
||||
}
|
||||
table = $('#dataTableFs').DataTable();
|
||||
} else {
|
||||
} else if (eventType == 2) {
|
||||
if (!isProviderDataTableInitialized){
|
||||
initProviderDatatable();
|
||||
return;
|
||||
}
|
||||
table = $('#dataTableProvider').DataTable();
|
||||
} else {
|
||||
if (!isLogDataTableInitialized){
|
||||
initLogDatatable();
|
||||
return;
|
||||
}
|
||||
table = $('#dataTableLog').DataTable();
|
||||
}
|
||||
table.clear().draw();
|
||||
table.ajax.url(getSearchURL(false)).load();
|
||||
|
@ -313,15 +337,28 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
if (statuses.length > 0){
|
||||
url+="&statuses="+encodeURIComponent(String(statuses));
|
||||
}
|
||||
} else {
|
||||
} else if (eventType == 2) {
|
||||
url = "{{.ProviderEventsSearchURL}}?omit_object_data=true&limit="+limit;
|
||||
} else {
|
||||
url = "{{.LogEventsSearchURL}}?limit="+limit;
|
||||
let protocols = [];
|
||||
$('#idProtocols').find('option:selected').each(function(){
|
||||
protocols.push($(this).val());
|
||||
});
|
||||
if (protocols.length > 0){
|
||||
url+="&protocols="+encodeURIComponent(String(protocols));
|
||||
}
|
||||
}
|
||||
let actions = [];
|
||||
$('#idActions').find('option:selected').each(function(){
|
||||
actions.push($(this).val());
|
||||
});
|
||||
if (actions.length > 0){
|
||||
url+="&actions="+encodeURIComponent(String(actions));
|
||||
if (eventType == 3){
|
||||
url+="&events="+encodeURIComponent(String(actions));
|
||||
} else {
|
||||
url+="&actions="+encodeURIComponent(String(actions));
|
||||
}
|
||||
}
|
||||
let username = $('#idUsername').val();
|
||||
if (username){
|
||||
|
@ -332,26 +369,26 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
url+="&ip="+encodeURIComponent(ip);
|
||||
}
|
||||
let drp = $('#dateTimeRange').data('daterangepicker');
|
||||
let excludeIds = [];
|
||||
let fromID = "";
|
||||
let start_ts;
|
||||
if (!csvExport && paginationData.get("prevClicked") && paginationData.has("lastId") && paginationData.has("lastTs")){
|
||||
order = "ASC";
|
||||
start_ts = paginationData.get("lastTs");
|
||||
excludeIds.push(paginationData.get("lastId"));
|
||||
fromID = paginationData.get("lastId");
|
||||
} else {
|
||||
start_ts = drp.startDate.valueOf()*1000000;
|
||||
}
|
||||
let end_ts;
|
||||
if (!csvExport && paginationData.get("nextClicked") && paginationData.has("firstId") && paginationData.has("firstTs")){
|
||||
end_ts = paginationData.get("firstTs");
|
||||
excludeIds.push(paginationData.get("firstId"));
|
||||
fromID = paginationData.get("firstId");
|
||||
} else {
|
||||
end_ts = drp.endDate.valueOf()*1000000;
|
||||
}
|
||||
url+="&start_timestamp="+encodeURIComponent(start_ts);
|
||||
url+="&end_timestamp="+encodeURIComponent(end_ts);
|
||||
if (excludeIds.length > 0){
|
||||
url+="&exclude_ids="+encodeURIComponent(String(excludeIds));
|
||||
if (fromID){
|
||||
url+="&from_id="+encodeURIComponent(fromID);
|
||||
}
|
||||
url+="&order="+order;
|
||||
if (csvExport){
|
||||
|
@ -360,6 +397,120 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
return url;
|
||||
}
|
||||
|
||||
function initLogDatatable(){
|
||||
$('#errorMsg').hide();
|
||||
let tableLog = $('#dataTableLog').DataTable({
|
||||
"ajax": {
|
||||
"url": getSearchURL(false),
|
||||
"dataSrc": handleResponseData,
|
||||
"error": function ($xhr, textStatus, errorThrown) {
|
||||
$(".dataTables_processing").hide();
|
||||
let txt = "Failed to get log events";
|
||||
if ($xhr) {
|
||||
let json = $xhr.responseJSON;
|
||||
if (json) {
|
||||
if (json.message){
|
||||
txt += ": " + json.message;
|
||||
} else {
|
||||
txt += ": " + json.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#errorTxt').text(txt);
|
||||
$('#errorMsg').show();
|
||||
}
|
||||
},
|
||||
"deferRender": true,
|
||||
"processing": true,
|
||||
"columns": [
|
||||
{ "data": "id" },
|
||||
{
|
||||
"data": "timestamp",
|
||||
"render": function (data, type, row) {
|
||||
if (type === 'display') {
|
||||
return dateFn(data/1000000,type);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": "event",
|
||||
"render": function (data, type, row) {
|
||||
if (type === 'display') {
|
||||
switch (data){
|
||||
case 1:
|
||||
return "Login failed";
|
||||
case 2:
|
||||
return "Login with non-existent user";
|
||||
case 3:
|
||||
return "No login tried";
|
||||
case 4:
|
||||
return "Algorithm negotiation failed";
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": "username",
|
||||
"defaultContent": "",
|
||||
"render": function (data, type, row) {
|
||||
if (type === 'display') {
|
||||
if (!data){
|
||||
return "";
|
||||
}
|
||||
return escapeHTML(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": "protocol",
|
||||
"defaultContent": ""
|
||||
},
|
||||
{
|
||||
"data": "ip",
|
||||
"defaultContent": ""
|
||||
},
|
||||
{
|
||||
"data": "message",
|
||||
"defaultContent": "",
|
||||
"render": function (data, type, row) {
|
||||
if (type === 'display') {
|
||||
if (!data){
|
||||
return "";
|
||||
}
|
||||
return '<span style="white-space:normal">' + escapeHTML(data) + "</span>"
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
],
|
||||
"buttons": [],
|
||||
"lengthChange": false,
|
||||
"columnDefs": [
|
||||
{
|
||||
"targets": [0],
|
||||
"visible": false,
|
||||
"searchable": false
|
||||
},
|
||||
],
|
||||
"responsive": true,
|
||||
"searching": false,
|
||||
"paging": false,
|
||||
"info": false,
|
||||
"ordering": false,
|
||||
"language": {
|
||||
"loadingRecords": "",
|
||||
"emptyTable": "No logs found"
|
||||
}
|
||||
});
|
||||
|
||||
new $.fn.dataTable.FixedHeader(tableLog);
|
||||
|
||||
isLogDataTableInitialized = true;
|
||||
}
|
||||
|
||||
function initProviderDatatable(){
|
||||
$('#errorMsg').hide();
|
||||
let tableProvider = $('#dataTableProvider').DataTable({
|
||||
|
@ -597,10 +748,30 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
$('#idUsername').val("");
|
||||
$('#idIp').val("");
|
||||
$('.provider-events').hide();
|
||||
$('.log-events').hide();
|
||||
$('.fs-events').show();
|
||||
onSearchClicked();
|
||||
}
|
||||
|
||||
function selectLogEvents(){
|
||||
let idActions = $('#idActions');
|
||||
idActions.selectpicker('deselectAll');
|
||||
idActions.find('option').remove();
|
||||
idActions.find('li').remove();
|
||||
idActions.append($('<option>').val('1').text('Login failed'));
|
||||
idActions.append($('<option>').val('2').text('Login with non-existent user'));
|
||||
idActions.append($('<option>').val('3').text('No login tried'));
|
||||
idActions.append($('<option>').val('4').text('Algorithm negotiation failed'));
|
||||
idActions.selectpicker('refresh');
|
||||
|
||||
$('#idUsername').val("");
|
||||
$('#idIp').val("");
|
||||
$('.provider-events').hide();
|
||||
$('.fs-events').hide();
|
||||
$('.log-events').show();
|
||||
onSearchClicked();
|
||||
}
|
||||
|
||||
function selectProviderEvents(){
|
||||
let idActions = $('#idActions');
|
||||
idActions.selectpicker('deselectAll');
|
||||
|
@ -614,6 +785,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
$('#idUsername').val("");
|
||||
$('#idIp').val("");
|
||||
$('.fs-events').hide();
|
||||
$('.log-events').hide();
|
||||
$('.provider-events').show();
|
||||
onSearchClicked();
|
||||
}
|
||||
|
@ -626,6 +798,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
case '2':
|
||||
selectProviderEvents();
|
||||
break;
|
||||
case '3':
|
||||
selectLogEvents();
|
||||
break;
|
||||
default:
|
||||
console.log(`unsupported event type: ${val}`);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ go 1.20
|
|||
|
||||
require (
|
||||
github.com/hashicorp/go-plugin v1.4.10-0.20230403150917-e889c1ba1044
|
||||
github.com/sftpgo/sdk v0.1.3
|
||||
github.com/sftpgo/sdk v0.1.4-0.20230512160325-38e59551f700
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -16,10 +16,10 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.54.0 // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
)
|
||||
|
|
|
@ -31,13 +31,13 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
|||
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sftpgo/sdk v0.1.3 h1:o/9herRbrDH6sQwfpKlV3AV0R7qJgOe/x4yQnEIWIHk=
|
||||
github.com/sftpgo/sdk v0.1.3/go.mod h1:gDxDaU3rhp9Y92ddsE7SbQ8jdBNNWK1DKlp5eHXrsb8=
|
||||
github.com/sftpgo/sdk v0.1.4-0.20230512160325-38e59551f700 h1:jL6mfKAaFv862AnBUxIfTH9wmnuPjbWyjHQUGDo+Xt0=
|
||||
github.com/sftpgo/sdk v0.1.4-0.20230512160325-38e59551f700/go.mod h1:gDxDaU3rhp9Y92ddsE7SbQ8jdBNNWK1DKlp5eHXrsb8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -45,15 +45,15 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
|
|
|
@ -49,11 +49,23 @@ type providerEvent struct {
|
|||
InstanceID string `json:"instance_id,omitempty"`
|
||||
}
|
||||
|
||||
type logEvent struct {
|
||||
ID string `json:"id" gorm:"primaryKey"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Event int `json:"event"`
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Role string `json:"role,omitempty"`
|
||||
InstanceID string `json:"instance_id,omitempty"`
|
||||
}
|
||||
|
||||
type Searcher struct{}
|
||||
|
||||
func (s *Searcher) SearchFsEvents(filters *eventsearcher.FsEventSearch) ([]byte, []string, []string, error) {
|
||||
func (s *Searcher) SearchFsEvents(filters *eventsearcher.FsEventSearch) ([]byte, error) {
|
||||
if filters.StartTimestamp < 0 {
|
||||
return nil, nil, nil, errNotSupported
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
results := []fsEvent{
|
||||
|
@ -84,15 +96,15 @@ func (s *Searcher) SearchFsEvents(filters *eventsearcher.FsEventSearch) ([]byte,
|
|||
|
||||
data, err := json.Marshal(results)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil, nil, nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s *Searcher) SearchProviderEvents(filters *eventsearcher.ProviderEventSearch) ([]byte, []string, []string, error) {
|
||||
func (s *Searcher) SearchProviderEvents(filters *eventsearcher.ProviderEventSearch) ([]byte, error) {
|
||||
if filters.StartTimestamp < 0 {
|
||||
return nil, nil, nil, errNotSupported
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
var objectData []byte
|
||||
|
@ -117,10 +129,36 @@ func (s *Searcher) SearchProviderEvents(filters *eventsearcher.ProviderEventSear
|
|||
|
||||
data, err := json.Marshal(results)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil, nil, nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s *Searcher) SearchLogEvents(filters *eventsearcher.LogEventSearch) ([]byte, error) {
|
||||
if filters.StartTimestamp < 0 {
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
results := []logEvent{
|
||||
{
|
||||
ID: "1",
|
||||
Timestamp: 100,
|
||||
Event: 1,
|
||||
Protocol: "SSH",
|
||||
IP: "127.0.1.1",
|
||||
Message: "Invalid credentials",
|
||||
Role: "role3",
|
||||
InstanceID: "instance2",
|
||||
},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(results)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
|
@ -16,10 +16,10 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.54.0 // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
)
|
||||
|
|
|
@ -36,8 +36,8 @@ github.com/sftpgo/sdk v0.1.3/go.mod h1:gDxDaU3rhp9Y92ddsE7SbQ8jdBNNWK1DKlp5eHXrs
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -45,15 +45,15 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
|
|
Loading…
Reference in a new issue