diff --git a/docker/README.md b/docker/README.md
index d5490fa8..f4dc82de 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -206,4 +206,9 @@ These tags provide the standard image with the addition of all "official" plugin
## Helm Chart
-An helm chart is [available](https://artifacthub.io/packages/helm/sagikazarmark/sftpgo). You can find the source code [here](https://github.com/sagikazarmark/helm-charts/tree/master/charts/sftpgo).
+Some helm charts are available:
+
+- [sagikazarmark/sftpgo](https://artifacthub.io/packages/helm/sagikazarmark/sftpgo)
+- [truecharts/sftpgo](https://artifacthub.io/packages/helm/truecharts/sftpgo)
+
+These charts are not maintained by the SFTPGo project and any issues with the charts should be raised to the upstream repo.
diff --git a/docs/eventmanager.md b/docs/eventmanager.md
index 824c1338..fc3c0028 100644
--- a/docs/eventmanager.md
+++ b/docs/eventmanager.md
@@ -41,6 +41,7 @@ The following trigger events are supported:
- `Provider events`, for example `add`, `update`, `delete` user or other resources.
- `Schedules`.
- `IP Blocked`, this event can be generated if you enable the [defender](./defender.md).
+- `Certificate`, this event is generated when a certificate is renewed using the built-in ACME protocol. Both successful and failed renewals are notified.
You can further restrict a rule by specifying additional conditions that must be met before the rule’s actions are taken. For example you can react to uploads only if they are performed by a particular user or using a specified protocol.
@@ -60,3 +61,4 @@ Some actions are not supported for some triggers, rules containing incompatible
- `Provider events`, user quota reset, transfer quota reset, data retention check and filesystem actions can be executed only if we modify a user. They will be executed for the affected user. Folder quota reset can be executed only for folders. Filesystem actions are not executed for `delete` user events because the actions is executed after the user deletion.
- `Schedules`, filesystem actions cannot be executed, they require a user.
- `IP Blocked`, user quota reset, folder quota reset, transfer quota reset, data retention check and filesystem actions cannot be executed, we only have an IP.
+- `Certificate`, user quota reset, folder quota reset, transfer quota reset, data retention check and filesystem actions cannot be executed.
diff --git a/go.mod b/go.mod
index 53a868e0..7fcdf339 100644
--- a/go.mod
+++ b/go.mod
@@ -8,15 +8,15 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
- github.com/aws/aws-sdk-go-v2 v1.16.10
- github.com/aws/aws-sdk-go-v2/config v1.16.0
- github.com/aws/aws-sdk-go-v2/credentials v1.12.12
- github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.11
- github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.24
- github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.11
- github.com/aws/aws-sdk-go-v2/service/s3 v1.27.4
- github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.16
- github.com/aws/aws-sdk-go-v2/service/sts v1.16.12
+ github.com/aws/aws-sdk-go-v2 v1.16.11
+ github.com/aws/aws-sdk-go-v2/config v1.16.1
+ github.com/aws/aws-sdk-go-v2/credentials v1.12.13
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12
+ github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.25
+ github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.12
+ github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5
+ github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.17
+ github.com/aws/aws-sdk-go-v2/service/sts v1.16.13
github.com/cockroachdb/cockroach-go/v2 v2.2.15
github.com/coreos/go-oidc/v3 v3.2.0
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
@@ -66,7 +66,7 @@ require (
go.uber.org/automaxprocs v1.5.1
gocloud.dev v0.26.0
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
- golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced
+ golang.org/x/net v0.0.0-20220811182439-13a9a731de15
golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
@@ -81,15 +81,15 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.4 // indirect
- github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.17 // indirect
- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.11 // indirect
- github.com/aws/aws-sdk-go-v2/internal/ini v1.3.18 // indirect
- github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.8 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.4 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.12 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.11 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.11 // indirect
- github.com/aws/aws-sdk-go-v2/service/sso v1.11.15 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.9 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.5 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.13 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.12 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.11.16 // indirect
github.com/aws/smithy-go v1.12.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
@@ -155,7 +155,7 @@ require (
golang.org/x/tools v0.1.12 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/genproto v0.0.0-20220810155839-1856144b1d9c // indirect
+ google.golang.org/genproto v0.0.0-20220812140447-cec7f5303424 // indirect
google.golang.org/grpc v1.48.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
@@ -168,5 +168,5 @@ replace (
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20220723143649-81550382d55e
- golang.org/x/net => github.com/drakkan/net v0.0.0-20220811173512-bde04f9047cc
+ golang.org/x/net => github.com/drakkan/net v0.0.0-20220812153436-025c6c7680ee
)
diff --git a/go.sum b/go.sum
index f2620424..d00ad533 100644
--- a/go.sum
+++ b/go.sum
@@ -144,64 +144,64 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
-github.com/aws/aws-sdk-go-v2 v1.16.10 h1:+yDD0tcuHRQZgqONkpDwzepqmElQaSlFPymHRHR9mrc=
-github.com/aws/aws-sdk-go-v2 v1.16.10/go.mod h1:WTACcleLz6VZTp7fak4EO5b9Q4foxbn+8PIz3PmyKlo=
+github.com/aws/aws-sdk-go-v2 v1.16.11 h1:xM1ZPSvty3xVmdxiGr7ay/wlqv+MWhH0rMlyLdbC0YQ=
+github.com/aws/aws-sdk-go-v2 v1.16.11/go.mod h1:WTACcleLz6VZTp7fak4EO5b9Q4foxbn+8PIz3PmyKlo=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.4 h1:zfT11pa7ifu/VlLDpmc5OY2W4nYmnKkFDGeMVnmqAI0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.4/go.mod h1:ES0I1GBs+YYgcDS1ek47Erbn4TOL811JKqBXtgzqyZ8=
github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg=
-github.com/aws/aws-sdk-go-v2/config v1.16.0 h1:LxHC50cwOLxYo67NEpwpNUiOi6ngXfDpEETphSZ6bAw=
-github.com/aws/aws-sdk-go-v2/config v1.16.0/go.mod h1:eatrtwIm5WdvASoYCy5oPkinfiwiYFg2jLG9tJoKzkE=
+github.com/aws/aws-sdk-go-v2/config v1.16.1 h1:jasqFPOoNPXHOYGEEuvyT87ACiXhD3OkQckIm5uqi5I=
+github.com/aws/aws-sdk-go-v2/config v1.16.1/go.mod h1:4SKzBMiB8lV0fw2w7eDBo/LjQyHFITN4vUUuqpurFmI=
github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g=
-github.com/aws/aws-sdk-go-v2/credentials v1.12.12 h1:iShu6VaWZZZfUZvlGtRjl+g1lWk44g1QmiCTD4KS0jI=
-github.com/aws/aws-sdk-go-v2/credentials v1.12.12/go.mod h1:vFHC2HifIWHebmoVsfpqliKuqbAY2LaVlvy03JzF4c4=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.13 h1:cuPzIsjKAWBUAAk8ZUR2l02Sxafl9hiaMsc7tlnjwAY=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.13/go.mod h1:9fDEemXizwXrxPU1MTzv69LP/9D8HVl5qHAQO9A9ikY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.11 h1:zZHPdM2x09/0F8D7XyVvQnP2/jaW7bEMmtcSCPYq/iI=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.11/go.mod h1:38Asv/UyQbDNpSXCurZRlDMjzIl6J+wUe8vY3TtUuzA=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12 h1:wgJBHO58Pc1V1QAnzdVM3JK3WbE/6eUF0JxCZ+/izz0=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12/go.mod h1:aZ4vZnyUuxedC7eD4JyEHpGnCz+O2sHQEx3VvAwklSE=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.24 h1:9MflwbI3Ua4PFyCNo39nnJ2ZYaQ/GabPUPdutegSJUs=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.24/go.mod h1:W970x9QKWWb0Y30Num5dFFji/qRQSt0UP4UzbM3sYCo=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.25 h1:ShUxLkMxarXylGxfYwg8p+xEKY+C1y54oUU3wFsUMFo=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.25/go.mod h1:cam5wV1ebd3ZVuh2r2CA8FtSAA/eUMtRH4owk0ygfFs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.17 h1:U8DZvyFFesBmK62dYC6BRXm4Cd/wPP3aPcecu3xv/F4=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.17/go.mod h1:6qtGip7sJEyvgsLjphRZWF9qPe3xJf1mL/MM01E35Wc=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18 h1:OmiwoVyLKEqqD5GvB683dbSqxiOfvx4U2lDZhG2Esc4=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18/go.mod h1:348MLhzV1GSlZSMusdwQpXKbhD7X2gbI/TxwAPKkYZQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.11 h1:GMp98usVW5tzQhxd26KWhoNQPlR2noIlfbzqjVGBhLU=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.11/go.mod h1:cYAfnB+9ZkmZWpQWmPDsuIGm4EA+6k2ZVtxKjw/XJBY=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12 h1:5mvQDtNWtI6H56+E4LUnLWEmATMB7oEh+Z9RurtIuC0=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12/go.mod h1:ckaCVTEdGAxO6KwTGzgskxR1xM+iJW4lxMyDFVda2Fc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.3.18 h1:/spg6h3tG4pefphbvhpgdMtFMegSajPPSEJd1t8lnpc=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.3.18/go.mod h1:hTHq8hL4bAxJyng364s9d4IUGXZOs7Y5LSqAhIiIQ2A=
-github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.8 h1:9PY5a+kHQzC6d9eR+KLNSJP3DHDLYmPFA5/+eSDBo9o=
-github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.8/go.mod h1:pcQfUOFVK4lMnSzgX3dCA81UsA9YCilRUSYgkjSU2i8=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19 h1:g5qq9sgtEzt2szMaDqQO6fqKe026T6dHTFJp5NsPzkQ=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19/go.mod h1:cVHo8KTuHjShb9V8/VjH3S/8+xPu16qx8fdGwmotJhE=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.9 h1:agLpf3vtYX1rtKTrOGpevdP3iC2W0hKDmzmhhxJzL+A=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.9/go.mod h1:cv+n1mdyh+0B8tAtlEBzTYFA2Uv15SISEn6kabYhIgE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.4 h1:akfcyqM9SvrBKWZOkBcXAGDrHfKaEP4Aca8H/bCiLW8=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.4/go.mod h1:oehQLbMQkppKLXvpx/1Eo0X47Fe+0971DXC9UjGnKcI=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.5 h1:g1ITJ9i9ixa+/WVggLNK20KyliAA8ltnuxfZEDfo2hM=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.5/go.mod h1:oehQLbMQkppKLXvpx/1Eo0X47Fe+0971DXC9UjGnKcI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8=
-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.12 h1:eNQYkKjDSLDjIbBQ85rIkjpBGgnavrl/U3YKDdxAz14=
-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.12/go.mod h1:k2HaF2yfT082M+kKo3Xdf4rd5HGKvDmrPC5Kwzc2KUw=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.13 h1:3GamN8jcdz/a3nvL/ZVtoH/6xxeshfsiXj5O+6GW4Rg=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.13/go.mod h1:89CSPn69UECDLVn0H6FwKNgbtirksl8C8i3aBeeeihw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.11 h1:GkYtp4gi4wdWUV+pPetjk5y2aDxbr0t8n5OjVBwZdII=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.11/go.mod h1:OEofCUKF7Hri4ShOCokF6k6hGq9PCB2sywt/9rLSXjY=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12 h1:7iPTTX4SAI2U2VOogD7/gmHlsgnYSgoNHt7MSQXtG2M=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12/go.mod h1:1TODGhheLWjpQWSuhYuAUWYTCKwEjx2iblIFKDHjeTc=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc=
-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.11 h1:ZBLEKweAzBBtJa8H+MTFfVyvo+eHdM8xec5oTm9IlqI=
-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.11/go.mod h1:mNS1VHxYXPNqxIdCTxf87j9ROfTMa4fNpIkA+iAfz0g=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.12 h1:QFjSOmHSb77qRTv7KI9UFon9X5wLWY5/M+6la3dTcZc=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.12/go.mod h1:MADjAN0GHFDuc5lRa5Y5ki+oIO/w7X4qczHy+OUx0IA=
github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g=
-github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.11 h1:GFxBWTb0DLD+PkhVPvNWtPsGBFusifSwHb2uDrIV0E0=
-github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.11/go.mod h1:jETcaK7szguipGK6ibOHjRemfxelIygcSUZe+xv9Vp8=
+github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.12 h1:KgwQKIp/yb9xCXVb+lZdPwoPLG621v+0bGm7pBJyhIQ=
+github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.12/go.mod h1:ZlZaygKJuKAxT4OUuoKCVPWil0+QALcb8fZxsMVO1b4=
github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.27.4 h1:0RPAahwT63znFepvhfS+/WYtT+gEuAwaeNcCrzTQMH0=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.27.4/go.mod h1:wcpDmROpK5W7oWI6JcJIYGrVpHbF/Pu+FHxyBXyoa1E=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5 h1:h9qqTedYnA9JcWjKyLV6UYIMSdp91ExLCUbjbpDLH7A=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5/go.mod h1:J8SS5Tp/zeLxaubB0xGfKnVrvssNBNLwTipreTKLhjQ=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o=
-github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.16 h1:+8J3OA/fUAAKpSyI6lAPyPhZVleLxDmuT2dv4lVHK20=
-github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.16/go.mod h1:vveF0vVbSg0WNZNsi27F0Tbyx9JB8NyExl5Iv0RKLcY=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.17 h1:x4JtJ0TaVVCoNc3bUtv0W5VvMLFiQ1++ReiRfSxRYf8=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.17/go.mod h1:HvF8QZUW+evBsd/SJn4VA0WWW5qVMKxPpWiRRK4w3eM=
github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw=
github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM=
github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI+N/H1I1ynHwXnO6FowXJc0=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU=
-github.com/aws/aws-sdk-go-v2/service/sso v1.11.15 h1:HaIE5/TtKr66qZTJpvMifDxH4lRt2JZawbkLYOo1F+Y=
-github.com/aws/aws-sdk-go-v2/service/sso v1.11.15/go.mod h1:dDVD4ElJRTQXx7dOQ59EkqGyNU9tnwy1RKln+oLIOTU=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.16 h1:YK8L7TNlGwMWHYqLs+i6dlITpxqzq08FqQUy26nm+T8=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.16/go.mod h1:mS5xqLZc/6kc06IpXn5vRxdLaED+jEuaSRv5BxtnsiY=
github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8=
-github.com/aws/aws-sdk-go-v2/service/sts v1.16.12 h1:YU9UHPukkCCnETHEExOptF/BxPvGJKXO/NBx+RMQ/2A=
-github.com/aws/aws-sdk-go-v2/service/sts v1.16.12/go.mod h1:b53qpmhHk7mTL2J/tfG6f38neZiyBQSiNXGCuNKq4+4=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.13 h1:dl8T0PJlN92rvEGOEUiD0+YPYdPEaCZK0TqHukvSfII=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.13/go.mod h1:Ru3QVMLygVs/07UQ3YDur1AQZZp2tUNje8wfloFttC0=
github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
github.com/aws/smithy-go v1.12.1 h1:yQRC55aXN/y1W10HgwHle01DRuV9Dpf31iGkotjt3Ag=
github.com/aws/smithy-go v1.12.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
@@ -266,8 +266,8 @@ github.com/drakkan/crypto v0.0.0-20220723143649-81550382d55e h1:ZvOJ5DqEUZig5lGl
github.com/drakkan/crypto v0.0.0-20220723143649-81550382d55e/go.mod h1:SiM6ypd8Xu1xldObYtbDztuUU7xUzMnUULfphXFZmro=
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/net v0.0.0-20220811173512-bde04f9047cc h1:nWhdNJ31a4S7oBCwIRRPY/QfpOdHl3i3irjrJXrfM7w=
-github.com/drakkan/net v0.0.0-20220811173512-bde04f9047cc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+github.com/drakkan/net v0.0.0-20220812153436-025c6c7680ee h1:hTHRVJ//MvApWBRVLrZOCfh+row8txg1G9BJVKsq+qk=
+github.com/drakkan/net v0.0.0-20220812153436-025c6c7680ee/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d h1:kNk/KRhszPJASp7WvjagNW254aKK643Lu8/fr4/ukiM=
github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4=
@@ -1226,8 +1226,8 @@ google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljW
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
-google.golang.org/genproto v0.0.0-20220810155839-1856144b1d9c h1:IooGDWedfLC6KLczH/uduUsKQP42ZZYhKx+zd50L1Sk=
-google.golang.org/genproto v0.0.0-20220810155839-1856144b1d9c/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220812140447-cec7f5303424 h1:zZnTt15U44/Txe/9cN/tVbteBkPMiyXK48hPsKRmqj4=
+google.golang.org/genproto v0.0.0-20220812140447-cec7f5303424/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
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=
diff --git a/internal/acme/acme.go b/internal/acme/acme.go
index 82b20fc3..c17c3d08 100644
--- a/internal/acme/acme.go
+++ b/internal/acme/acme.go
@@ -45,6 +45,7 @@ import (
"github.com/go-acme/lego/v4/registration"
"github.com/robfig/cron/v3"
+ "github.com/drakkan/sftpgo/v2/internal/common"
"github.com/drakkan/sftpgo/v2/internal/ftpd"
"github.com/drakkan/sftpgo/v2/internal/httpd"
"github.com/drakkan/sftpgo/v2/internal/logger"
@@ -561,6 +562,24 @@ func (c *Configuration) getCertificates() error {
return nil
}
+func (c *Configuration) notifyCertificateRenewal(domain string, err error) {
+ if domain == "" {
+ domain = strings.Join(c.Domains, ",")
+ }
+ params := common.EventParams{
+ Name: domain,
+ Timestamp: time.Now().UnixNano(),
+ }
+ if err != nil {
+ params.Status = 2
+ params.Event = "Certificate renewal failed"
+ } else {
+ params.Status = 1
+ params.Event = "Successful certificate renewal"
+ }
+ common.HandleCertificateEvent(params)
+}
+
func (c *Configuration) renewCertificates() error {
lockTime, err := c.getLockTime()
if err != nil {
@@ -573,22 +592,28 @@ func (c *Configuration) renewCertificates() error {
}
err = c.setLockTime()
if err != nil {
+ c.notifyCertificateRenewal("", err)
return err
}
account, client, err := c.setup()
if err != nil {
+ c.notifyCertificateRenewal("", err)
return err
}
if account.Registration == nil {
acmeLog(logger.LevelError, "cannot renew certificates, your account is not registered")
- return fmt.Errorf("cannot renew certificates, your account is not registered")
+ err = errors.New("cannot renew certificates, your account is not registered")
+ c.notifyCertificateRenewal("", err)
+ return err
}
var errRenew error
needReload := false
for _, domain := range c.Domains {
certificates, err := c.loadCertificatesForDomain(domain)
if err != nil {
- return err
+ c.notifyCertificateRenewal(domain, err)
+ errRenew = err
+ continue
}
cert := certificates[0]
if !c.needRenewal(cert, domain) {
@@ -596,8 +621,10 @@ func (c *Configuration) renewCertificates() error {
}
err = c.obtainAndSaveCertificate(client, domain)
if err != nil {
+ c.notifyCertificateRenewal(domain, err)
errRenew = err
} else {
+ c.notifyCertificateRenewal(domain, nil)
needReload = true
}
}
diff --git a/internal/common/defenderdb.go b/internal/common/defenderdb.go
index 9c15675f..40f03cf1 100644
--- a/internal/common/defenderdb.go
+++ b/internal/common/defenderdb.go
@@ -112,6 +112,7 @@ func (d *dbDefender) AddEvent(ip string, event HostEvent) {
Event: ipBlockedEventName,
IP: ip,
Timestamp: time.Now().UnixNano(),
+ Status: 1,
})
}
}
diff --git a/internal/common/defendermem.go b/internal/common/defendermem.go
index d457cd35..4d669b5a 100644
--- a/internal/common/defendermem.go
+++ b/internal/common/defendermem.go
@@ -213,6 +213,7 @@ func (d *memoryDefender) AddEvent(ip string, event HostEvent) {
Event: ipBlockedEventName,
IP: ip,
Timestamp: time.Now().UnixNano(),
+ Status: 1,
})
} else {
d.hosts[ip] = hs
diff --git a/internal/common/eventmanager.go b/internal/common/eventmanager.go
index b3baf2a2..ecf67bf4 100644
--- a/internal/common/eventmanager.go
+++ b/internal/common/eventmanager.go
@@ -72,16 +72,22 @@ func init() {
})
}
+// HandleCertificateEvent checks and executes action rules for certificate events
+func HandleCertificateEvent(params EventParams) {
+ eventManager.handleCertificateEvent(params)
+}
+
// eventRulesContainer stores event rules by trigger
type eventRulesContainer struct {
sync.RWMutex
- lastLoad int64
- FsEvents []dataprovider.EventRule
- ProviderEvents []dataprovider.EventRule
- Schedules []dataprovider.EventRule
- IPBlockedEvents []dataprovider.EventRule
- schedulesMapping map[string][]cron.EntryID
- concurrencyGuard chan struct{}
+ lastLoad int64
+ FsEvents []dataprovider.EventRule
+ ProviderEvents []dataprovider.EventRule
+ Schedules []dataprovider.EventRule
+ IPBlockedEvents []dataprovider.EventRule
+ CertificateEvents []dataprovider.EventRule
+ schedulesMapping map[string][]cron.EntryID
+ concurrencyGuard chan struct{}
}
func (r *eventRulesContainer) addAsyncTask() {
@@ -138,6 +144,15 @@ func (r *eventRulesContainer) removeRuleInternal(name string) {
return
}
}
+ for idx := range r.CertificateEvents {
+ if r.CertificateEvents[idx].Name == name {
+ lastIdx := len(r.CertificateEvents) - 1
+ r.CertificateEvents[idx] = r.CertificateEvents[lastIdx]
+ r.CertificateEvents = r.CertificateEvents[:lastIdx]
+ eventManagerLog(logger.LevelDebug, "removed rule %q from certificate events", name)
+ return
+ }
+ }
for idx := range r.Schedules {
if r.Schedules[idx].Name == name {
if schedules, ok := r.schedulesMapping[name]; ok {
@@ -177,6 +192,9 @@ func (r *eventRulesContainer) addUpdateRuleInternal(rule dataprovider.EventRule)
case dataprovider.EventTriggerIPBlocked:
r.IPBlockedEvents = append(r.IPBlockedEvents, rule)
eventManagerLog(logger.LevelDebug, "added rule %q to IP blocked events", rule.Name)
+ case dataprovider.EventTriggerCertificate:
+ r.CertificateEvents = append(r.CertificateEvents, rule)
+ eventManagerLog(logger.LevelDebug, "added rule %q to certificate events", rule.Name)
case dataprovider.EventTriggerSchedule:
for _, schedule := range rule.Conditions.Schedules {
cronSpec := schedule.GetCronSpec()
@@ -217,8 +235,8 @@ func (r *eventRulesContainer) loadRules() {
r.addUpdateRuleInternal(rule)
}
}
- eventManagerLog(logger.LevelDebug, "event rules updated, fs events: %d, provider events: %d, schedules: %d, ip blocked events: %d",
- len(r.FsEvents), len(r.ProviderEvents), len(r.Schedules), len(r.IPBlockedEvents))
+ eventManagerLog(logger.LevelDebug, "event rules updated, fs events: %d, provider events: %d, schedules: %d, ip blocked events: %d, certificate events: %d",
+ len(r.FsEvents), len(r.ProviderEvents), len(r.Schedules), len(r.IPBlockedEvents), len(r.CertificateEvents))
r.setLastLoadTime(modTime)
}
@@ -362,6 +380,28 @@ func (r *eventRulesContainer) handleIPBlockedEvent(params EventParams) {
}
}
+func (r *eventRulesContainer) handleCertificateEvent(params EventParams) {
+ r.RLock()
+ defer r.RUnlock()
+
+ if len(r.CertificateEvents) == 0 {
+ return
+ }
+ var rules []dataprovider.EventRule
+ for _, rule := range r.CertificateEvents {
+ if err := rule.CheckActionsConsistency(""); err == nil {
+ rules = append(rules, rule)
+ } else {
+ eventManagerLog(logger.LevelWarn, "rule %q skipped: %v, event %q",
+ rule.Name, err, params.Event)
+ }
+ }
+
+ if len(rules) > 0 {
+ go executeAsyncRulesActions(rules, params)
+ }
+}
+
// EventParams defines the supported event parameters
type EventParams struct {
Name string
diff --git a/internal/common/protocol_test.go b/internal/common/protocol_test.go
index 50f5bfb0..eff2795a 100644
--- a/internal/common/protocol_test.go
+++ b/internal/common/protocol_test.go
@@ -3530,6 +3530,125 @@ func TestEventRuleFsActions(t *testing.T) {
assert.NoError(t, err)
}
+func TestEventRuleCertificate(t *testing.T) {
+ smtpCfg := smtp.Config{
+ Host: "127.0.0.1",
+ Port: 2525,
+ From: "notify@example.com",
+ TemplatesPath: "templates",
+ }
+ err := smtpCfg.Initialize(configDir)
+ require.NoError(t, err)
+ lastReceivedEmail.reset()
+
+ a1 := dataprovider.BaseEventAction{
+ Name: "action1",
+ Type: dataprovider.ActionTypeEmail,
+ Options: dataprovider.BaseEventActionOptions{
+ EmailConfig: dataprovider.EventActionEmailConfig{
+ Recipients: []string{"test@example.com"},
+ Subject: `"{{Event}}"`,
+ Body: "Domain: {{Name}} Timestamp: {{Timestamp}}",
+ },
+ },
+ }
+ action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
+ assert.NoError(t, err)
+
+ a2 := dataprovider.BaseEventAction{
+ Name: "action2",
+ Type: dataprovider.ActionTypeFolderQuotaReset,
+ }
+ action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
+ assert.NoError(t, err)
+
+ r1 := dataprovider.EventRule{
+ Name: "test rule certificate",
+ Trigger: dataprovider.EventTriggerCertificate,
+ Actions: []dataprovider.EventAction{
+ {
+ BaseEventAction: dataprovider.BaseEventAction{
+ Name: action1.Name,
+ },
+ Order: 1,
+ },
+ },
+ }
+ rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
+ assert.NoError(t, err)
+ r2 := dataprovider.EventRule{
+ Name: "test rule 2",
+ Trigger: dataprovider.EventTriggerCertificate,
+ Actions: []dataprovider.EventAction{
+ {
+ BaseEventAction: dataprovider.BaseEventAction{
+ Name: action1.Name,
+ },
+ Order: 1,
+ },
+ {
+ BaseEventAction: dataprovider.BaseEventAction{
+ Name: action2.Name,
+ },
+ Order: 2,
+ },
+ },
+ }
+ rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
+ assert.NoError(t, err)
+
+ common.HandleCertificateEvent(common.EventParams{
+ Name: "example.com",
+ Timestamp: time.Now().UnixNano(),
+ Status: 1,
+ Event: "Successful certificate renewal",
+ })
+ assert.Eventually(t, func() bool {
+ return lastReceivedEmail.get().From != ""
+ }, 3000*time.Millisecond, 100*time.Millisecond)
+ email := lastReceivedEmail.get()
+ assert.Len(t, email.To, 1)
+ assert.True(t, util.Contains(email.To, "test@example.com"))
+ assert.Contains(t, string(email.Data), `Subject: "Successful certificate renewal"`)
+ assert.Contains(t, string(email.Data), `Domain: example.com Timestamp`)
+
+ lastReceivedEmail.reset()
+ common.HandleCertificateEvent(common.EventParams{
+ Name: "example.com",
+ Timestamp: time.Now().UnixNano(),
+ Status: 2,
+ Event: "Certificate renewal failed",
+ })
+ assert.Eventually(t, func() bool {
+ return lastReceivedEmail.get().From != ""
+ }, 3000*time.Millisecond, 100*time.Millisecond)
+ email = lastReceivedEmail.get()
+ assert.Len(t, email.To, 1)
+ assert.True(t, util.Contains(email.To, "test@example.com"))
+ assert.Contains(t, string(email.Data), `Subject: "Certificate renewal failed"`)
+ assert.Contains(t, string(email.Data), `Domain: example.com Timestamp`)
+
+ _, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
+ assert.NoError(t, err)
+ _, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
+ assert.NoError(t, err)
+ _, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
+ assert.NoError(t, err)
+ _, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
+ assert.NoError(t, err)
+ // ignored no more certificate rules
+ common.HandleCertificateEvent(common.EventParams{
+ Name: "example.com",
+ Timestamp: time.Now().UnixNano(),
+ Status: 1,
+ Event: "Successful certificate renewal",
+ })
+
+ smtpCfg = smtp.Config{}
+ err = smtpCfg.Initialize(configDir)
+ require.NoError(t, err)
+}
+
func TestEventRuleIPBlocked(t *testing.T) {
oldConfig := config.GetCommonConfig()
diff --git a/internal/dataprovider/eventrule.go b/internal/dataprovider/eventrule.go
index 50951d87..4d396d59 100644
--- a/internal/dataprovider/eventrule.go
+++ b/internal/dataprovider/eventrule.go
@@ -85,11 +85,12 @@ const (
EventTriggerProviderEvent
EventTriggerSchedule
EventTriggerIPBlocked
+ EventTriggerCertificate
)
var (
supportedEventTriggers = []int{EventTriggerFsEvent, EventTriggerProviderEvent, EventTriggerSchedule,
- EventTriggerIPBlocked}
+ EventTriggerIPBlocked, EventTriggerCertificate}
)
func isEventTriggerValid(trigger int) bool {
@@ -104,6 +105,8 @@ func getTriggerTypeAsString(trigger int) string {
return "Provider event"
case EventTriggerIPBlocked:
return "IP blocked"
+ case EventTriggerCertificate:
+ return "Certificate renewal"
default:
return "Schedule"
}
@@ -885,7 +888,7 @@ func (c *EventConditions) validate(trigger int) error {
return err
}
}
- case EventTriggerIPBlocked:
+ case EventTriggerIPBlocked, EventTriggerCertificate:
c.FsEvents = nil
c.ProviderEvents = nil
c.Options.Names = nil
@@ -1013,13 +1016,13 @@ func (r *EventRule) validate() error {
return nil
}
-func (r *EventRule) checkIPBlockedActions() error {
+func (r *EventRule) checkIPBlockedAndCertificateActions() error {
unavailableActions := []int{ActionTypeUserQuotaReset, ActionTypeFolderQuotaReset, ActionTypeTransferQuotaReset,
ActionTypeDataRetentionCheck, ActionTypeFilesystem}
for _, action := range r.Actions {
if util.Contains(unavailableActions, action.Type) {
- return fmt.Errorf("action %q, type %q is not supported for IP blocked events",
- action.Name, getActionTypeAsString(action.Type))
+ return fmt.Errorf("action %q, type %q is not supported for event trigger %q",
+ action.Name, getActionTypeAsString(action.Type), getTriggerTypeAsString(r.Trigger))
}
}
return nil
@@ -1067,8 +1070,8 @@ func (r *EventRule) CheckActionsConsistency(providerObjectType string) error {
action.Name, getActionTypeAsString(action.Type))
}
}
- case EventTriggerIPBlocked:
- if err := r.checkIPBlockedActions(); err != nil {
+ case EventTriggerIPBlocked, EventTriggerCertificate:
+ if err := r.checkIPBlockedAndCertificateActions(); err != nil {
return err
}
}
diff --git a/internal/httpd/server.go b/internal/httpd/server.go
index b06aa619..990c4274 100644
--- a/internal/httpd/server.go
+++ b/internal/httpd/server.go
@@ -95,7 +95,7 @@ func (s *httpdServer) listenAndServe() error {
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
IdleTimeout: 60 * time.Second,
- MaxHeaderBytes: 1 << 16, // 64KB
+ MaxHeaderBytes: 1 << 18, // 256KB
ErrorLog: log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
}
if certMgr != nil && s.binding.EnableHTTPS {
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 1b56b982..402181f4 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -4343,6 +4343,8 @@ components:
- 5
- 6
- 7
+ - 8
+ - 9
description: |
Supported event action types:
* `1` - HTTP
@@ -4352,17 +4354,23 @@ components:
* `5` - User quota reset
* `6` - Folder quota reset
* `7` - Transfer quota reset
+ * `8` - Data retention check
+ * `9` - Filesystem
EventTriggerTypes:
type: integer
enum:
- 1
- 2
- 3
+ - 4
+ - 5
description: |
Supported event trigger types:
* `1` - Filesystem event
* `2` - Provider event
* `3` - Schedule
+ * `4` - IP blocked
+ * `5` - Certificate renewal
LoginMethods:
type: string
enum:
diff --git a/templates/webadmin/eventaction.html b/templates/webadmin/eventaction.html
index 0ced8df9..589fca6c 100644
--- a/templates/webadmin/eventaction.html
+++ b/templates/webadmin/eventaction.html
@@ -511,7 +511,7 @@ along with this program. If not, see
- {{`{{Name}}`}} => Username, folder name, or admin username for provider actions. + {{`{{Name}}`}} => Username, folder name, admin username for provider events, domain name for certificate events.
{{`{{Event}}`}} => Event name, for example "upload", "download" for filesystem events or "add", "update" for provider events.
diff --git a/templates/webadmin/eventrule.html b/templates/webadmin/eventrule.html
index 83b0a383..a1027c13 100644
--- a/templates/webadmin/eventrule.html
+++ b/templates/webadmin/eventrule.html
@@ -545,6 +545,8 @@ along with this program. If not, see