http actions: add multipart support

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-09-03 16:29:07 +02:00
parent 3267a50ae3
commit c2a65a9a74
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
20 changed files with 897 additions and 154 deletions

View file

@ -423,6 +423,12 @@ jobs:
gzip output/man/man1/*
cp sftpgo output/
- name: Get commit SHA
if: ${{ matrix.arch != 'amd64' }}
id: get_commit
run: echo ::set-output name=COMMIT::${GITHUB_SHA::8}
shell: bash
- uses: uraimo/run-on-arch-action@v2
if: ${{ matrix.arch != 'amd64' }}
name: Build for ${{ matrix.arch }}
@ -437,7 +443,7 @@ jobs:
shell: /bin/bash
install: |
apt-get update -q -y
apt-get install -q -y curl gcc git
apt-get install -q -y curl gcc
if [ ${{ matrix.go }} == 'latest' ]
then
GO_VERSION=$(curl -L https://go.dev/VERSION?m=text)
@ -457,7 +463,7 @@ jobs:
then
export GOARM=7
fi
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
mkdir -p output/{init,bash_completion,zsh_completion}
cp sftpgo.json output/
cp -r templates output/

View file

@ -95,6 +95,13 @@ jobs:
fi
TAGS="${TAGS},${DOCKER_IMAGE}:distroless"
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:distroless-slim"
elif [[ $DOCKER_PKG == debian-plugins ]]; then
if [[ -n $MAJOR && -n $MINOR ]]; then
TAGS="${TAGS},${DOCKER_IMAGE}:${MINOR}-plugins,${DOCKER_IMAGE}:${MAJOR}-plugins"
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:${MINOR}-plugins-slim,${DOCKER_IMAGE}:${MAJOR}-plugins-slim"
fi
TAGS="${TAGS},${DOCKER_IMAGE}:plugins"
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:plugins-slim"
else
if [[ -n $MAJOR && -n $MINOR ]]; then
TAGS="${TAGS},${DOCKER_IMAGE}:${MINOR}-alpine,${DOCKER_IMAGE}:${MAJOR}-alpine"

View file

@ -326,6 +326,12 @@ jobs:
env:
SFTPGO_VERSION: ${{ steps.get_version.outputs.SFTPGO_VERSION }}
- name: Get commit SHA
if: ${{ matrix.arch != 'amd64' }}
id: get_commit
run: echo ::set-output name=COMMIT::${GITHUB_SHA::8}
shell: bash
- uses: uraimo/run-on-arch-action@v2
if: ${{ matrix.arch != 'amd64' }}
name: Build for ${{ matrix.arch }}
@ -340,7 +346,7 @@ jobs:
shell: /bin/bash
install: |
apt-get update -q -y
apt-get install -q -y curl gcc git xz-utils
apt-get install -q -y curl gcc xz-utils
GO_DOWNLOAD_ARCH=${{ matrix.go-arch }}
if [ ${{ matrix.arch}} == 'armv7' ]
then
@ -350,7 +356,7 @@ jobs:
tar -C /usr/local -xzf go.tar.gz
run: |
export PATH=$PATH:/usr/local/go/bin
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
mkdir -p output/{init,sqlite,bash_completion,zsh_completion}
echo "For documentation please take a look here:" > output/README.txt
echo "" >> output/README.txt

View file

@ -4,11 +4,12 @@ SFTPGo provides an official Docker image, it is available on both [Docker Hub](h
## Supported tags and respective Dockerfile links
- [v2.3.3, v2.3, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile)
- [v2.3.3-alpine, v2.3-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile.alpine)
- [v2.3.3-slim, v2.3-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile)
- [v2.3.3-alpine-slim, v2.3-alpine-slim, v2-alpine-slim, alpine-slim](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile.alpine)
- [v2.3.3-distroless-slim, v2.3-distroless-slim, v2-distroless-slim, distroless-slim](https://github.com/drakkan/sftpgo/blob/v2.3.3/Dockerfile.distroless)
- [v2.3.4, v2.3, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile)
- [v2.3.4-plugins, v2.3-plugins, v2-plugins, plugins](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile)
- [v2.3.4-alpine, v2.3-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile.alpine)
- [v2.3.4-slim, v2.3-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile)
- [v2.3.4-alpine-slim, v2.3-alpine-slim, v2-alpine-slim, alpine-slim](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile.alpine)
- [v2.3.4-distroless-slim, v2.3-distroless-slim, v2-distroless-slim, distroless-slim](https://github.com/drakkan/sftpgo/blob/v2.3.4/Dockerfile.distroless)
- [edge](../Dockerfile)
- [edge-plugins](../Dockerfile)
- [edge-alpine](../Dockerfile.alpine)

53
go.mod
View file

@ -4,21 +4,21 @@ go 1.19
require (
cloud.google.com/go/storage v1.26.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.3
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.12
github.com/aws/aws-sdk-go-v2/config v1.17.3
github.com/aws/aws-sdk-go-v2/credentials v1.12.16
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.13
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.29
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.15
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.7
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.20
github.com/aws/aws-sdk-go-v2/service/sts v1.16.15
github.com/aws/aws-sdk-go-v2 v1.16.14
github.com/aws/aws-sdk-go-v2/config v1.17.5
github.com/aws/aws-sdk-go-v2/credentials v1.12.18
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.31
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.17
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.9
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.22
github.com/aws/aws-sdk-go-v2/service/sts v1.16.17
github.com/cockroachdb/cockroach-go/v2 v2.2.15
github.com/coreos/go-oidc/v3 v3.2.0
github.com/coreos/go-oidc/v3 v3.3.0
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
github.com/fclairamb/ftpserverlib v0.19.1
github.com/fclairamb/go-log v0.4.1
@ -44,7 +44,7 @@ require (
github.com/minio/sio v0.3.0
github.com/otiai10/copy v1.7.0
github.com/pires/go-proxyproto v0.6.2
github.com/pkg/sftp v1.13.5
github.com/pkg/sftp v1.13.6-0.20220831160757-628507938ec6
github.com/pquerna/otp v1.3.0
github.com/prometheus/client_golang v1.13.0
github.com/robfig/cron/v3 v3.0.1
@ -52,7 +52,7 @@ require (
github.com/rs/xid v1.4.0
github.com/rs/zerolog v1.28.0
github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657
github.com/shirou/gopsutil/v3 v3.22.7
github.com/shirou/gopsutil/v3 v3.22.8
github.com/spf13/afero v1.9.2
github.com/spf13/cobra v1.5.0
github.com/spf13/viper v1.12.0
@ -80,18 +80,18 @@ require (
cloud.google.com/go/iam v0.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.6 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.14 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.13 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.19 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.1 // indirect
github.com/aws/smithy-go v1.13.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.12 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.16 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.21 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3 // indirect
github.com/aws/smithy-go v1.13.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
@ -156,7 +156,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-20220829175752-36a9c930ecbf // indirect
google.golang.org/genproto v0.0.0-20220902135211-223410557253 // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
@ -167,7 +167,6 @@ require (
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-20220831070132-e3c36f2ab82b
golang.org/x/net => github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5
)

107
go.sum
View file

@ -90,8 +90,8 @@ github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
github.com/Azure/azure-sdk-for-go v59.3.0+incompatible h1:dPIm0BO4jsMXFcCI/sLTPkBtE7mk8WMuRHA0JeWhlcQ=
github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2 h1:lneMk5qtUMulXa/eVxjVd+/bDYMEDIqYpLzLa2/EsNI=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.3 h1:8LoU8N2lIUzkmstvwXvVfniMZlFbesfT2AmA1aqvRr8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.3/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0 h1:Yoicul8bnVdQrhDMTHxdEckRGX01XvwXDHUT9zYZ3k0=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
@ -142,69 +142,69 @@ 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.12 h1:wbMYa2PlFysFx2GLIQojr6FJV5+OWCM/BwyHXARxETA=
github.com/aws/aws-sdk-go-v2 v1.16.12/go.mod h1:C+Ym0ag2LIghJbXhfXZ0YEEp49rBWowxKzJLUoob0ts=
github.com/aws/aws-sdk-go-v2 v1.16.14 h1:db6GvO4Z2UqHt5gvT0lr6J5x5P+oQ7bdRzczVaRekMU=
github.com/aws/aws-sdk-go-v2 v1.16.14/go.mod h1:s/G+UV29dECbF5rf+RNj1xhlmvoNurGSr+McVSRj59w=
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.5 h1:7A1nDFvkVlBmMa69QMLkw/m/DDHm6PUluIYK61aQoOY=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.5/go.mod h1:DnlOnWR2YuzMXNSHHNuoklObUE3SwWlcRTGL/zL+Aj8=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.7 h1:/kxQjtZc7j67TMW/aFJfpsrlvFhsq3lNbX41qN5Tro4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.7/go.mod h1:KvHyNlxCjo9Y1Fsz+6Ex9OaN2jKijvMxzROxpW5Vctc=
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.17.3 h1:s1As/fiVMmM3CObC4GcSaSbkhm88S6a5qn8St3wgal0=
github.com/aws/aws-sdk-go-v2/config v1.17.3/go.mod h1:tRGUOfk9Rrf6UCJm5qDlL9AizSsgvteuKX4qajAV3pU=
github.com/aws/aws-sdk-go-v2/config v1.17.5 h1:+NS1BWvprx7nHcIk5o32LrZgifs/7Pm1V2nWjQgZ2H0=
github.com/aws/aws-sdk-go-v2/config v1.17.5/go.mod h1:H0cvPNDO3uExWts/9PDhD/0ne2esu1uaIulwn1vkwxM=
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.16 h1:HXczS88Pg36j8dq0KSjtHBPFs8gdRyBSS1hueeG/rxA=
github.com/aws/aws-sdk-go-v2/credentials v1.12.16/go.mod h1:eLJ+j1lwQdHJ0c56tRoDWcgss1e/laVmvW2AaOicuAw=
github.com/aws/aws-sdk-go-v2/credentials v1.12.18 h1:HF62tbhARhgLfvmfwUbL9qZ+dkbZYzbFdxBb3l5gr7Q=
github.com/aws/aws-sdk-go-v2/credentials v1.12.18/go.mod h1:O7n/CPagQ33rfG6h7vR/W02ammuc5CrsSM22cNZp9so=
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.13 h1:+uferi8SUDZtMloCDt24Zenyy/i71C/ua5mjUCpbpN0=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.13/go.mod h1:y0eXmsNBFIVjUE8ZBjES8myOHlMsXDz7qGT93+MVdjk=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15 h1:nkQ+aI0OCeYfzrBipL6ja/6VEbUnHQoZHBHtoK+Nzxw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15/go.mod h1:Oz2/qWINxIgSmoZT9adpxJy2UhpcOAI3TIyWgYMVSz0=
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.29 h1:VKi/79iKGaZ9pJTSuj/gNlzJdFczcGcsw9NDAT7I+hY=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.29/go.mod h1:ge60sLiMug/7ubLIbRyM9zNv5fR99ZzR+staDaM7+Tw=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.31 h1:Ggf7rvFS1s3/Nauv2mokAY+RfKsCAHvfiiZJoYd0lV0=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.31/go.mod h1:Iv2xOFdy8aFIxVKEdzo9puLXFaGNnjx5xzGYIlGzhuY=
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.19 h1:gC5mudiFrWGhzcdoWj1iCGUfrzCpQG0MQIQf0CXFFQQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.19/go.mod h1:llxE6bwUZhuCas0K7qGiu5OgMis3N7kdWtFSxoHmJ7E=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.21 h1:gRIXnmAVNyoRQywdNtpAkgY+f30QNzgF53Q5OobNZZs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.21/go.mod h1:XsmHMV9c512xgsW01q7H0ut+UQQQpWX8QsFbdLHDwaU=
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.13 h1:qezY57na06d6kSE7uuB0N7XEflu914AXx/hg2L8Ykcw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.13/go.mod h1:lB12mkZqCSo5PsdBFLNqc2M/OOYgNAy8UtaktyuWvE8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.15 h1:noAhOo2mMDyYhTx99aYPvQw16T3fQ/DiKAv9fzpIKH8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.15/go.mod h1:kjJ4CyD9M3Wq88GYg3IPfj67Rs0Uvz8aXK7MJ8BvE4I=
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.20 h1:GvszACAU8GSV3+Tant5GutW6smY8WavrP8ZuRS9Ku4Q=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.20/go.mod h1:bfTcsThj5a9P5pIGRy0QudJ8k4+issxXX+O6Djnd5Cs=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.10 h1:233xgzn4lsBeN7qgG+k2kLquzBk35WB+nIhPMeK0h/Q=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.10/go.mod h1:1nl/nuVB6+UOpiyYJBfyhCzsX8fJAL6fCVcbtPIIV4w=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22 h1:nF+E8HfYpOMw6M5oA9efB602VC00IHNQnB5CmFvZPvA=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22/go.mod h1:tltHVGy977LrSOgRR5aV9+miyno/Gul/uJNPKS7FzP4=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.12 h1:i0Tig01XGhXo/ki1BZUbRMhusGVCScEvaWdlFRWxAKk=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.12/go.mod h1:QPoxYMISvteeDH4A89gGWWlCA/Bz6oUDF7hGdPdOPuE=
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.6 h1:Z0Yw2qkgPZVGbOR70snGRAlBR0QIGPLkHoNhR4+7hbY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.6/go.mod h1:Slj62rcu4BKdMAH0wqeP0fUkW1b1bkCxcSP+ZY5cevE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.8 h1:NpixDFjwr1BZg2459mX07NZnVYGGp62Lb6AtVGOLNlo=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.8/go.mod h1:MJUgrBPfGB4yk2uWoImVqd9cklry1hATyJV/7gJ6JTk=
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.14 h1:NWR21daQBDyY4WChz4Gd78QuCPorUJiSHg7r1OWvfgA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.14/go.mod h1:Yz4G3rD1LtBcg6gIYtJtpoEjts9IZMHiamdm3F1xtNA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.16 h1:kHc3TqW5kJ9Vfd9YEwywrNrL87DItpvAohlP+OuzABY=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.16/go.mod h1:U/9ZCgIx6x6NTdFRt60qO3gxUxBx4gRi+S/Yc/n+7vc=
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.13 h1:ObfthqDyhe7rMAOa7pqft6974VHIk8BAJB7kYdoIfTA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.13/go.mod h1:V390DK4MQxLpDdXxFqizyz8KUxuWImkW/xzgXMz0yyk=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15 h1:xlf0J6DUgAj/ocvKQxCmad8Bu1lJuRbt5Wu+4G1xw1g=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15/go.mod h1:ZVJ7ejRl4+tkWMuCwjXoy0jd8fF5u3RCyWjSVjUIvQE=
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.13 h1:h1equp9qdWANft5cmtDUditRlALvE7tuaHs2RdSbsQg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.13/go.mod h1:3RA7cs1uHkbV3f6tMYy7u0OfkyVckZBM70wUS4h1MDk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.15 h1:v9f7NY7D19ssE2EM+m9yT1m5zdWHuRAsZaFh24GAkOk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.15/go.mod h1:gXfPo3nMoCbJKTZKDxv3rUhcYJjYT/K++jEqcWHjD/Q=
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.15 h1:ek8ACOAGvDWRm1kFCcj22soNkkLFh4WPBFv7BdWqebs=
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.15/go.mod h1:Zf+Tf40dskiGdwVJU2HIgln1vtnQF8QpsguBsbI5Uq8=
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.17 h1:b8nlmU7/7j+Tujr7X4YcJ0hb0hqQ/IeXCt8/CjJVO4A=
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.17/go.mod h1:kJoiz0fTRMsFZp4BICG6nC++aet5gG9jyjxcGlxxMUs=
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.7 h1:BlxqVULzNS7udJIwZBJdL8NNcLbSwgXv/WRJCVUaMm8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.7/go.mod h1:orjy5IRgBQnh9EI/lMW7YGF6eYk6re8HPFbL66a2DSo=
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.9 h1:imVonvre+AHMcDc3B9bPHHy5ZgjIkkYc/jyDBK8FHFw=
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.9/go.mod h1:0Gfmg8gjPhVPy/IXkLAmyKZbAue+2s11BWKH+oXggmg=
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.20 h1:j41VjMJNc5T9AWkLf/FdVtR46st2PZYB/6xoBBY2/8Q=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.20/go.mod h1:F2AUfGEOcxpOTzo/+Bur5PrtsvnhVQQbd4CGfPicOpw=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.22 h1:ggHTCgbIivTM85PFjv/rkJbchrmLSNL+Vcj5hg54TyM=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.22/go.mod h1:zT2j7Ndi+FcBX+zfYLDppqODSgSdKlquB3LPLPVDAts=
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.19 h1:WdCwfJmu23XiIDeZwclSyAorQe916M3LeHd53xqBjfA=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.19/go.mod h1:ytmEi5+qwcSNcV2pVA8PIb1DnKT/0Bu/K4nfJHwoM6c=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.1 h1:p48IfndYbRk3iDsoQAmVXdCKEM5+7Y50JAPikjwk8gI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.1/go.mod h1:NY+G+8PW0ISyJ7/6t5mgOe6qpJiwZa9Jix05WPscJjg=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.21 h1:7jUFr+7F4MzIjCZzy7ygRtXFQcQ0kAbT0gUvtUeAdyU=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.21/go.mod h1:q8nYq51W3gpZempYsAD83fPRlrOTMCwN+Ahg4BKFTXQ=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3 h1:UTTPNP3/WzZa7hoHP3Szb/Yl0bM3NoBrf5ABy1OArUM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3/go.mod h1:+IF75RMJh0+zqTGXGshyEGRsU2ImqWv6UuHGkHl6kEo=
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.15 h1:ApuR2BK9vf5/XXsImHBBsYJ6aUhmUhBHnZMPyhJo1jQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.16.15/go.mod h1:Y+BUV19q3OmQVqNUlbZ40zVi3NM6Biuxwkx/qdSD/CY=
github.com/aws/aws-sdk-go-v2/service/sts v1.16.17 h1:LVM2jzEQ8mhb2dhrFl4PJ3sa5+KcKT01dsMk2Ma9/FU=
github.com/aws/aws-sdk-go-v2/service/sts v1.16.17/go.mod h1:bQujK1n0V1D1Gz5uII1jaB1WDvhj4/T3tElsJnVXCR0=
github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
github.com/aws/smithy-go v1.13.0 h1:YfyEmSJLo7fAv8FbuDK4R8F9aAmi9DZ88Zb/KJJmUl0=
github.com/aws/smithy-go v1.13.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aws/smithy-go v1.13.2 h1:TBLKyeJfXTrTXRHmsv4qWt9IQGYyWThLYaJWSahTOGE=
github.com/aws/smithy-go v1.13.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -239,8 +239,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go/v2 v2.2.15 h1:6TeTC1JLSlHJWJCswWZ7mQyT16kY5mQSs53C2coQISI=
github.com/cockroachdb/cockroach-go/v2 v2.2.15/go.mod h1:xZ2VHjUEb/cySv0scXBx7YsBnHtLHkR1+w/w73b5i3M=
github.com/coreos/go-oidc/v3 v3.2.0 h1:2eR2MGR7thBXSQ2YbODlF0fcmgtliLCfr9iX6RW11fc=
github.com/coreos/go-oidc/v3 v3.2.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
github.com/coreos/go-oidc/v3 v3.3.0 h1:Y1LV3mP+QT3MEycATZpAiwfyN+uxZLqVbAHJUuOJEe4=
github.com/coreos/go-oidc/v3 v3.3.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
@ -268,8 +268,6 @@ github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHP
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5 h1:+sVMXrU1DiQLNDgz1KvybqHEzRf8KuX5xQW8fpii6rI=
github.com/drakkan/net v0.0.0-20220828084259-1562d1fb0fc5/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=
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001/go.mod h1:kltMsfRMTHSFdMbK66XdS8mfMW77+FZA1fGY1xYMF84=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -662,6 +660,9 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pkg/sftp v1.13.6-0.20220831160757-628507938ec6 h1:hxLT9qX4jw+GjGuPA6XHtooT1+nf/hr5anQtACaXZmY=
github.com/pkg/sftp v1.13.6-0.20220831160757-628507938ec6/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
@ -715,8 +716,8 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=
github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657 h1:UXTpae6d+G/VI3sVITl+58SK0F3ZULn9dlEPMXcyNKY=
github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657/go.mod h1:PTp1TfXa+95wHw9yuZu7BA3vmzLqbRkz3gBmMNnwFQg=
github.com/shirou/gopsutil/v3 v3.22.7 h1:flKnuCMfUUrO+oAvwAd6GKZgnPzr098VA/UJ14nhJd4=
github.com/shirou/gopsutil/v3 v3.22.7/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI=
github.com/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y=
github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
@ -938,6 +939,7 @@ golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1222,8 +1224,8 @@ google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/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-20220829175752-36a9c930ecbf h1:Q5xNKbTSFwkuaaGaR7CMcXEM5sy19KYdUU8iF8/iRC0=
google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220902135211-223410557253 h1:vXJMM8Shg7TGaYxZsQ++A/FOSlbDmDtWhS/o+3w/hj4=
google.golang.org/genproto v0.0.0-20220902135211-223410557253/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=
@ -1286,7 +1288,6 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -20,7 +20,10 @@ import (
"errors"
"fmt"
"io"
"mime"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"os"
"os/exec"
@ -49,7 +52,8 @@ const (
var (
// eventManager handle the supported event rules actions
eventManager eventRulesContainer
eventManager eventRulesContainer
multipartQuoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
)
func init() {
@ -455,7 +459,12 @@ func (p *EventParams) getStatusString() string {
// getUsers returns users with group settings not applied
func (p *EventParams) getUsers() ([]dataprovider.User, error) {
if p.sender == "" {
return dataprovider.DumpUsers()
users, err := dataprovider.DumpUsers()
if err != nil {
eventManagerLog(logger.LevelError, "unable to get users: %+v", err)
return users, errors.New("unable to get users")
}
return users, nil
}
user, err := p.getUserFromSender()
if err != nil {
@ -467,7 +476,8 @@ func (p *EventParams) getUsers() ([]dataprovider.User, error) {
func (p *EventParams) getUserFromSender() (dataprovider.User, error) {
user, err := dataprovider.UserExists(p.sender)
if err != nil {
return user, fmt.Errorf("error getting user %q: %w", p.sender, err)
eventManagerLog(logger.LevelError, "unable to get user %q: %+v", p.sender, err)
return user, fmt.Errorf("error getting user %q", p.sender)
}
return user, nil
}
@ -515,26 +525,45 @@ func (p *EventParams) getStringReplacements(addObjectData bool) []string {
return replacements
}
func getFileContent(conn *BaseConnection, virtualPath string, expectedSize int) ([]byte, error) {
func getFileReader(conn *BaseConnection, virtualPath string) (io.ReadCloser, func(), error) {
fs, fsPath, err := conn.GetFsAndResolvedPath(virtualPath)
if err != nil {
return nil, err
return nil, nil, err
}
f, r, cancelFn, err := fs.Open(fsPath, 0)
if err != nil {
return nil, err
return nil, nil, err
}
if cancelFn == nil {
cancelFn = func() {}
}
defer cancelFn()
var reader io.ReadCloser
if f != nil {
reader = f
} else {
reader = r
return f, cancelFn, nil
}
return r, cancelFn, nil
}
func writeFileContent(conn *BaseConnection, virtualPath string, w io.Writer) error {
reader, cancelFn, err := getFileReader(conn, virtualPath)
if err != nil {
return err
}
defer cancelFn()
defer reader.Close()
_, err = io.Copy(w, reader)
return err
}
func getFileContent(conn *BaseConnection, virtualPath string, expectedSize int) ([]byte, error) {
reader, cancelFn, err := getFileReader(conn, virtualPath)
if err != nil {
return nil, err
}
defer cancelFn()
defer reader.Close()
data := make([]byte, expectedSize)
@ -632,19 +661,103 @@ func getHTTPRuleActionEndpoint(c dataprovider.EventActionHTTPConfig, replacer *s
return c.Endpoint, nil
}
func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventParams) error {
if !c.Password.IsEmpty() {
if err := c.Password.TryDecrypt(); err != nil {
return fmt.Errorf("unable to decrypt password: %w", err)
func writeHTTPPart(m *multipart.Writer, part dataprovider.HTTPPart, h textproto.MIMEHeader,
conn *BaseConnection, replacer *strings.Replacer,
) error {
partWriter, err := m.CreatePart(h)
if err != nil {
eventManagerLog(logger.LevelError, "unable to create part %q, err: %v", part.Name, err)
return err
}
if part.Body != "" {
_, err = partWriter.Write([]byte(replaceWithReplacer(part.Body, replacer)))
if err != nil {
eventManagerLog(logger.LevelError, "unable to write part %q, err: %v", part.Name, err)
return err
}
return nil
}
err = writeFileContent(conn, util.CleanPath(replacer.Replace(part.Filepath)), partWriter)
if err != nil {
eventManagerLog(logger.LevelError, "unable to write file part %q, err: %v", part.Name, err)
return err
}
return nil
}
func getHTTPRuleActionBody(c dataprovider.EventActionHTTPConfig, replacer *strings.Replacer,
cancel context.CancelFunc, user dataprovider.User,
) (io.ReadCloser, string, error) {
var body io.ReadCloser
if c.Method == http.MethodGet {
return body, "", nil
}
if c.Body != "" {
return io.NopCloser(bytes.NewBufferString(replaceWithReplacer(c.Body, replacer))), "", nil
}
if len(c.Parts) > 0 {
r, w := io.Pipe()
m := multipart.NewWriter(w)
var conn *BaseConnection
if user.Username != "" {
var err error
user, err = getUserForEventAction(user)
if err != nil {
return body, "", err
}
connectionID := fmt.Sprintf("%s_%s", protocolEventAction, xid.New().String())
err = user.CheckFsRoot(connectionID)
if err != nil {
user.CloseFs() //nolint:errcheck
return body, "", fmt.Errorf("error getting multipart file/s, unable to check root fs for user %q: %w",
user.Username, err)
}
conn = NewBaseConnection(connectionID, protocolEventAction, "", "", user)
}
go func() {
defer w.Close()
defer user.CloseFs() //nolint:errcheck
for _, part := range c.Parts {
h := make(textproto.MIMEHeader)
if part.Body != "" {
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"`, multipartQuoteEscaper.Replace(part.Name)))
} else {
filePath := util.CleanPath(replacer.Replace(part.Filepath))
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
multipartQuoteEscaper.Replace(part.Name), multipartQuoteEscaper.Replace(path.Base(filePath))))
contentType := mime.TypeByExtension(path.Ext(filePath))
if contentType == "" {
contentType = "application/octet-stream"
}
h.Set("Content-Type", contentType)
}
for _, keyVal := range part.Headers {
h.Set(keyVal.Key, replaceWithReplacer(keyVal.Value, replacer))
}
if err := writeHTTPPart(m, part, h, conn, replacer); err != nil {
cancel()
return
}
}
m.Close()
}()
return r, m.FormDataContentType(), nil
}
return body, "", nil
}
func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventParams) error {
if err := c.TryDecryptPassword(); err != nil {
return err
}
addObjectData := false
if params.Object != nil {
if !addObjectData {
if strings.Contains(c.Body, "{{ObjectData}}") {
addObjectData = true
}
}
addObjectData = c.HasObjectData()
}
replacements := params.getStringReplacements(addObjectData)
@ -654,16 +767,32 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventPa
return err
}
var body io.Reader
if c.Body != "" && c.Method != http.MethodGet {
body = bytes.NewBufferString(replaceWithReplacer(c.Body, replacer))
ctx, cancel := c.GetContext()
defer cancel()
var user dataprovider.User
if c.HasMultipartFile() {
user, err = params.getUserFromSender()
if err != nil {
return err
}
}
req, err := http.NewRequest(c.Method, endpoint, body)
body, contentType, err := getHTTPRuleActionBody(c, replacer, cancel, user)
if err != nil {
return err
}
if body != nil {
defer body.Close()
}
req, err := http.NewRequestWithContext(ctx, c.Method, endpoint, body)
if err != nil {
return err
}
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
if c.Username != "" {
req.SetBasicAuth(replaceWithReplacer(c.Username, replacer), c.Password.GetAdditionalData())
req.SetBasicAuth(replaceWithReplacer(c.Username, replacer), c.Password.GetPayload())
}
for _, keyVal := range c.Headers {
req.Header.Set(keyVal.Key, replaceWithReplacer(keyVal.Value, replacer))
@ -676,11 +805,11 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventPa
if err != nil {
eventManagerLog(logger.LevelDebug, "unable to send http notification, endpoint: %s, elapsed: %s, err: %v",
endpoint, time.Since(startTime), err)
return err
return fmt.Errorf("error sending HTTP request: %w", err)
}
defer resp.Body.Close()
eventManagerLog(logger.LevelDebug, "http notification sent, endopoint: %s, elapsed: %s, status code: %d",
eventManagerLog(logger.LevelDebug, "http notification sent, endpoint: %s, elapsed: %s, status code: %d",
endpoint, time.Since(startTime), resp.StatusCode)
if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusNoContent {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
@ -761,14 +890,15 @@ func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params *Event
func getUserForEventAction(user dataprovider.User) (dataprovider.User, error) {
err := user.LoadAndApplyGroupSettings()
if err != nil {
return dataprovider.User{}, err
eventManagerLog(logger.LevelError, "unable to get group for user %q: %+v", user.Username, err)
return dataprovider.User{}, fmt.Errorf("unable to get groups for user %q", user.Username)
}
user.Filters.DisableFsChecks = false
user.Filters.FilePatterns = nil
for k := range user.Permissions {
user.Permissions[k] = []string{dataprovider.PermAny}
}
return user, err
return user, nil
}
func executeDeleteFileFsAction(conn *BaseConnection, item string, info os.FileInfo) error {

View file

@ -17,6 +17,8 @@ package common
import (
"crypto/rand"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path"
@ -349,6 +351,26 @@ func TestEventManagerErrors(t *testing.T) {
}}, []string{"/a", "/b"}, nil)
assert.Error(t, err)
_, _, err = getHTTPRuleActionBody(dataprovider.EventActionHTTPConfig{
Method: http.MethodPost,
Parts: []dataprovider.HTTPPart{
{
Name: "p1",
},
},
}, nil, nil, dataprovider.User{
BaseUser: sdk.BaseUser{
Username: "u",
},
Groups: []sdk.GroupMapping{
{
Name: groupName,
Type: sdk.GroupTypePrimary,
},
},
})
assert.Error(t, err)
dataRetentionAction := dataprovider.BaseEventAction{
Type: dataprovider.ActionTypeDataRetentionCheck,
Options: dataprovider.BaseEventActionOptions{
@ -469,6 +491,9 @@ func TestEventRuleActions(t *testing.T) {
}
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
assert.NoError(t, err)
action.Options.HTTPConfig.Method = http.MethodGet
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
assert.NoError(t, err)
action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v/404", httpAddr)
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
if assert.Error(t, err) {
@ -484,8 +509,22 @@ func TestEventRuleActions(t *testing.T) {
action.Options.HTTPConfig.Password = kms.NewSecret(sdkkms.SecretStatusSecretBox, "payload", "key", "data")
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unable to decrypt password")
assert.Contains(t, err.Error(), "unable to decrypt HTTP password")
}
action.Options.HTTPConfig.Password = kms.NewEmptySecret()
action.Options.HTTPConfig.Body = ""
action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
{
Name: "p1",
Filepath: "path",
},
}
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "error getting user")
}
action.Options.HTTPConfig.Parts = nil
action.Options.HTTPConfig.Body = "{{ObjectData}}"
// test disk and transfer quota reset
username1 := "user1"
username2 := "user2"
@ -849,6 +888,12 @@ func TestEventRuleActions(t *testing.T) {
assert.Contains(t, err.Error(), "no folder quota reset executed")
}
body, _, err := getHTTPRuleActionBody(dataprovider.EventActionHTTPConfig{
Method: http.MethodPost,
}, nil, nil, dataprovider.User{})
assert.NoError(t, err)
assert.Nil(t, body)
err = os.RemoveAll(folder1.MappedPath)
assert.NoError(t, err)
err = dataprovider.DeleteFolder(foldername1, "", "")
@ -979,7 +1024,19 @@ func TestFilesystemActionErrors(t *testing.T) {
assert.Error(t, err)
_, err = getFileContent(NewBaseConnection("", protocolEventAction, "", "", user), "/f.txt", 1234)
assert.Error(t, err)
err = executeHTTPRuleAction(dataprovider.EventActionHTTPConfig{
Endpoint: "http://127.0.0.1:9999/",
Method: http.MethodPost,
Parts: []dataprovider.HTTPPart{
{
Name: "p1",
Filepath: "/filepath",
},
},
}, &EventParams{
sender: username,
})
assert.Error(t, err)
user.FsConfig.Provider = sdk.LocalFilesystemProvider
user.Permissions["/"] = []string{dataprovider.PermUpload}
err = dataprovider.DeleteUser(username, "", "")
@ -1276,3 +1333,34 @@ func TestEventParamsStatusFromError(t *testing.T) {
params.AddError(os.ErrNotExist)
assert.Equal(t, 2, params.Status)
}
type testWriter struct {
errTest error
sentinel string
}
func (w *testWriter) Write(p []byte) (int, error) {
if w.errTest != nil {
return 0, w.errTest
}
if w.sentinel == string(p) {
return 0, io.ErrUnexpectedEOF
}
return len(p), nil
}
func TestWriteHTTPPartsError(t *testing.T) {
m := multipart.NewWriter(&testWriter{
errTest: io.ErrShortWrite,
})
err := writeHTTPPart(m, dataprovider.HTTPPart{}, nil, nil, nil)
assert.ErrorIs(t, err, io.ErrShortWrite)
body := "test body"
m = multipart.NewWriter(&testWriter{sentinel: body})
err = writeHTTPPart(m, dataprovider.HTTPPart{
Body: body,
}, nil, nil, nil)
assert.ErrorIs(t, err, io.ErrUnexpectedEOF)
}

View file

@ -169,6 +169,16 @@ func TestMain(m *testing.M) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Not found\n")
})
http.HandleFunc("/multipart", func(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(1048576)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "KO\n")
return
}
defer r.MultipartForm.RemoveAll() //nolint:errcheck
fmt.Fprintf(w, "OK\n")
})
if err := http.ListenAndServe(httpAddr, nil); err != nil {
logger.ErrorToConsole("could not start HTTP notification server: %v", err)
os.Exit(1)
@ -3815,6 +3825,94 @@ func TestEventRuleFsActions(t *testing.T) {
assert.NoError(t, err)
}
func TestEventActionHTTPMultipart(t *testing.T) {
a1 := dataprovider.BaseEventAction{
Name: "action1",
Type: dataprovider.ActionTypeHTTP,
Options: dataprovider.BaseEventActionOptions{
HTTPConfig: dataprovider.EventActionHTTPConfig{
Endpoint: fmt.Sprintf("http://%s/multipart", httpAddr),
Method: http.MethodPut,
Parts: []dataprovider.HTTPPart{
{
Name: "part1",
Headers: []dataprovider.KeyValue{
{
Key: "Content-Type",
Value: "application/json",
},
},
Body: `{"FilePath": "{{VirtualPath}}"}`,
},
{
Name: "file",
Filepath: "/{{VirtualPath}}",
},
},
},
},
}
action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
assert.NoError(t, err, string(resp))
r1 := dataprovider.EventRule{
Name: "test http multipart",
Trigger: dataprovider.EventTriggerFsEvent,
Conditions: dataprovider.EventConditions{
FsEvents: []string{"upload"},
},
Actions: []dataprovider.EventAction{
{
BaseEventAction: dataprovider.BaseEventAction{
Name: action1.Name,
},
Options: dataprovider.EventActionOptions{
ExecuteSync: true,
},
Order: 1,
},
},
}
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
f, err := client.Create(testFileName)
assert.NoError(t, err)
_, err = f.Write(testFileContent)
assert.NoError(t, err)
err = f.Close()
assert.NoError(t, err)
// now add an missing file to the http multipart action
action1.Options.HTTPConfig.Parts = append(action1.Options.HTTPConfig.Parts, dataprovider.HTTPPart{
Name: "file1",
Filepath: "/missing",
})
_, resp, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
assert.NoError(t, err, string(resp))
f, err = client.Create("testfile.txt")
assert.NoError(t, err)
_, err = f.Write(testFileContent)
assert.NoError(t, err)
err = f.Close()
assert.Error(t, err)
}
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestEventActionEmailAttachments(t *testing.T) {
smtpCfg := smtp.Config{
Host: "127.0.0.1",

View file

@ -15,6 +15,7 @@
package dataprovider
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
@ -200,6 +201,39 @@ type KeyValue struct {
Value string `json:"value"`
}
func (k *KeyValue) isNotValid() bool {
return k.Key == "" || k.Value == ""
}
// HTTPPart defines a part for HTTP multipart requests
type HTTPPart struct {
Name string `json:"name,omitempty"`
Filepath string `json:"filepath,omitempty"`
Headers []KeyValue `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
Order int `json:"-"`
}
func (p *HTTPPart) validate() error {
if p.Name == "" {
return util.NewValidationError("HTTP part name is required")
}
for _, kv := range p.Headers {
if kv.isNotValid() {
return util.NewValidationError("invalid HTTP part headers")
}
}
if p.Filepath == "" {
if p.Body == "" {
return util.NewValidationError("HTTP part body is required if no file path is provided")
}
} else {
p.Body = ""
p.Filepath = util.CleanPath(p.Filepath)
}
return nil
}
// EventActionHTTPConfig defines the configuration for an HTTP event target
type EventActionHTTPConfig struct {
Endpoint string `json:"endpoint,omitempty"`
@ -210,7 +244,34 @@ type EventActionHTTPConfig struct {
SkipTLSVerify bool `json:"skip_tls_verify,omitempty"`
Method string `json:"method,omitempty"`
QueryParameters []KeyValue `json:"query_parameters,omitempty"`
Body string `json:"post_body,omitempty"`
Body string `json:"body,omitempty"`
Parts []HTTPPart `json:"parts,omitempty"`
}
func (c *EventActionHTTPConfig) isTimeoutNotValid() bool {
if c.HasMultipartFile() {
return false
}
return c.Timeout < 1 || c.Timeout > 120
}
func (c *EventActionHTTPConfig) validateMultiparts() error {
for idx := range c.Parts {
if err := c.Parts[idx].validate(); err != nil {
return err
}
}
if len(c.Parts) > 0 {
if c.Body != "" {
return util.NewValidationError("multipart requests require no body. The request body is build from the specified parts")
}
for _, k := range c.Headers {
if strings.ToLower(k.Key) == "content-type" {
return util.NewValidationError("content type is automatically set for multipart requests")
}
}
}
return nil
}
func (c *EventActionHTTPConfig) validate(additionalData string) error {
@ -220,14 +281,17 @@ func (c *EventActionHTTPConfig) validate(additionalData string) error {
if !util.IsStringPrefixInSlice(c.Endpoint, []string{"http://", "https://"}) {
return util.NewValidationError("invalid HTTP endpoint schema: http and https are supported")
}
if c.Timeout < 1 || c.Timeout > 120 {
if c.isTimeoutNotValid() {
return util.NewValidationError(fmt.Sprintf("invalid HTTP timeout %d", c.Timeout))
}
for _, kv := range c.Headers {
if kv.Key == "" || kv.Value == "" {
if kv.isNotValid() {
return util.NewValidationError("invalid HTTP headers")
}
}
if err := c.validateMultiparts(); err != nil {
return err
}
if c.Password.IsRedacted() {
return util.NewValidationError("cannot save HTTP configuration with a redacted secret")
}
@ -242,18 +306,57 @@ func (c *EventActionHTTPConfig) validate(additionalData string) error {
return util.NewValidationError(fmt.Sprintf("unsupported HTTP method: %s", c.Method))
}
for _, kv := range c.QueryParameters {
if kv.Key == "" || kv.Value == "" {
if kv.isNotValid() {
return util.NewValidationError("invalid HTTP query parameters")
}
}
return nil
}
// GetContext returns the context and the cancel func to use for the HTTP request
func (c *EventActionHTTPConfig) GetContext() (context.Context, context.CancelFunc) {
if c.HasMultipartFile() {
return context.WithCancel(context.Background())
}
return context.WithTimeout(context.Background(), time.Duration(c.Timeout)*time.Second)
}
// HasObjectData returns true if the {{ObjectData}} placeholder is defined
func (c *EventActionHTTPConfig) HasObjectData() bool {
if strings.Contains(c.Body, "{{ObjectData}}") {
return true
}
for _, part := range c.Parts {
if strings.Contains(part.Body, "{{ObjectData}}") {
return true
}
}
return false
}
// HasMultipartFile returns true if a file must be uploaded via a multipart request
func (c *EventActionHTTPConfig) HasMultipartFile() bool {
for _, part := range c.Parts {
if part.Filepath != "" {
return true
}
}
return false
}
// TryDecryptPassword decrypts the password if encryptet
func (c *EventActionHTTPConfig) TryDecryptPassword() error {
if c.Password != nil && !c.Password.IsEmpty() {
if err := c.Password.TryDecrypt(); err != nil {
return fmt.Errorf("unable to decrypt HTTP password: %w", err)
}
}
return nil
}
// GetHTTPClient returns an HTTP client based on the config
func (c *EventActionHTTPConfig) GetHTTPClient() *http.Client {
client := &http.Client{
Timeout: time.Duration(c.Timeout) * time.Second,
}
client := &http.Client{}
if c.SkipTLSVerify {
transport := http.DefaultTransport.(*http.Transport).Clone()
if transport.TLSClientConfig != nil {
@ -288,7 +391,7 @@ func (c *EventActionCommandConfig) validate() error {
return util.NewValidationError(fmt.Sprintf("invalid command action timeout %d", c.Timeout))
}
for _, kv := range c.EnvVars {
if kv.Key == "" || kv.Value == "" {
if kv.isNotValid() {
return util.NewValidationError("invalid command env vars")
}
}
@ -589,6 +692,15 @@ func (o *BaseEventActionOptions) getACopy() BaseEventActionOptions {
IgnoreUserPermissions: folder.IgnoreUserPermissions,
})
}
httpParts := make([]HTTPPart, 0, len(o.HTTPConfig.Parts))
for _, part := range o.HTTPConfig.Parts {
httpParts = append(httpParts, HTTPPart{
Name: part.Name,
Filepath: part.Filepath,
Headers: cloneKeyValues(part.Headers),
Body: part.Body,
})
}
return BaseEventActionOptions{
HTTPConfig: EventActionHTTPConfig{
@ -601,6 +713,7 @@ func (o *BaseEventActionOptions) getACopy() BaseEventActionOptions {
Method: o.HTTPConfig.Method,
QueryParameters: cloneKeyValues(o.HTTPConfig.QueryParameters),
Body: o.HTTPConfig.Body,
Parts: httpParts,
},
CmdConfig: EventActionCommandConfig{
Cmd: o.CmdConfig.Cmd,
@ -1171,6 +1284,11 @@ func (r *EventRule) CheckActionsConsistency(providerObjectType string) error {
return errors.New("cannot send an email with attachments for a rule with no user associated")
}
}
if action.Type == ActionTypeHTTP && action.BaseEventAction.Options.HTTPConfig.HasMultipartFile() {
if !r.hasUserAssociated(providerObjectType) {
return errors.New("cannot upload file/s for a rule with no user associated")
}
}
}
return nil
}

View file

@ -1490,7 +1490,7 @@ func TestEventActionValidation(t *testing.T) {
Value: "application/json",
},
}
action.Options.HTTPConfig.Password = kms.NewSecret(sdkkms.SecretStatusRedacted, "paylod", "", "")
action.Options.HTTPConfig.Password = kms.NewSecret(sdkkms.SecretStatusRedacted, "payload", "", "")
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
assert.NoError(t, err)
assert.Contains(t, string(resp), "cannot save HTTP configuration with a redacted secret")
@ -1509,6 +1509,43 @@ func TestEventActionValidation(t *testing.T) {
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
assert.NoError(t, err)
assert.Contains(t, string(resp), "invalid HTTP query parameters")
action.Options.HTTPConfig.QueryParameters = nil
action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
{
Name: "",
},
}
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
assert.NoError(t, err)
assert.Contains(t, string(resp), "HTTP part name is required")
action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
{
Name: "p1",
},
}
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
assert.NoError(t, err)
assert.Contains(t, string(resp), "HTTP part body is required if no file path is provided")
action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
{
Name: "p1",
Filepath: "p",
},
}
action.Options.HTTPConfig.Body = "b"
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
assert.NoError(t, err)
assert.Contains(t, string(resp), "multipart requests require no body")
action.Options.HTTPConfig.Body = ""
action.Options.HTTPConfig.Headers = []dataprovider.KeyValue{
{
Key: "Content-Type",
Value: "application/json",
},
}
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
assert.NoError(t, err)
assert.Contains(t, string(resp), "content type is automatically set for multipart requests")
action.Type = dataprovider.ActionTypeCommand
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
@ -18899,8 +18936,23 @@ func TestWebEventAction(t *testing.T) {
assert.NotEmpty(t, actionGet.Options.HTTPConfig.Password.GetPayload())
assert.Empty(t, actionGet.Options.HTTPConfig.Password.GetKey())
assert.Empty(t, actionGet.Options.HTTPConfig.Password.GetAdditionalData())
// update and check that the password is preserved
// update and check that the password is preserved and the multipart fields
form.Set("http_password", redactedSecret)
form.Set("http_body", "")
form.Set("http_timeout", "0")
form.Del("http_header_key0")
form.Del("http_header_val0")
form.Set("http_part_name0", "part1")
form.Set("http_part_file0", "{{VirtualPath}}")
form.Set("http_part_headers0", "X-MyHeader: a:b,c")
form.Set("http_part_body0", "")
form.Set("http_part_namea", "ignored")
form.Set("http_part_filea", "{{VirtualPath}}")
form.Set("http_part_headersa", "X-MyHeader: a:b,c")
form.Set("http_part_bodya", "")
form.Set("http_part_name12", "part2")
form.Set("http_part_headers12", "Content-Type:application/json \r\n")
form.Set("http_part_body12", "{{ObjectData}}")
req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
bytes.NewBuffer([]byte(form.Encode())))
assert.NoError(t, err)
@ -18913,6 +18965,20 @@ func TestWebEventAction(t *testing.T) {
err = dbAction.Options.HTTPConfig.Password.Decrypt()
assert.NoError(t, err)
assert.Equal(t, defaultPassword, dbAction.Options.HTTPConfig.Password.GetPayload())
assert.Empty(t, dbAction.Options.HTTPConfig.Body)
assert.Equal(t, 0, dbAction.Options.HTTPConfig.Timeout)
if assert.Len(t, dbAction.Options.HTTPConfig.Parts, 2) {
assert.Equal(t, "part1", dbAction.Options.HTTPConfig.Parts[0].Name)
assert.Equal(t, "/{{VirtualPath}}", dbAction.Options.HTTPConfig.Parts[0].Filepath)
assert.Empty(t, dbAction.Options.HTTPConfig.Parts[0].Body)
assert.Equal(t, "X-MyHeader", dbAction.Options.HTTPConfig.Parts[0].Headers[0].Key)
assert.Equal(t, "a:b,c", dbAction.Options.HTTPConfig.Parts[0].Headers[0].Value)
assert.Equal(t, "part2", dbAction.Options.HTTPConfig.Parts[1].Name)
assert.Equal(t, "{{ObjectData}}", dbAction.Options.HTTPConfig.Parts[1].Body)
assert.Empty(t, dbAction.Options.HTTPConfig.Parts[1].Filepath)
assert.Equal(t, "Content-Type", dbAction.Options.HTTPConfig.Parts[1].Headers[0].Key)
assert.Equal(t, "application/json", dbAction.Options.HTTPConfig.Parts[1].Headers[0].Value)
}
// change action type
action.Type = dataprovider.ActionTypeCommand
action.Options.CmdConfig = dataprovider.EventActionCommandConfig{

View file

@ -23,6 +23,7 @@ import (
"net/url"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
@ -1877,6 +1878,46 @@ func getFoldersRetentionFromPostFields(r *http.Request) ([]dataprovider.FolderRe
return res, nil
}
func getHTTPPartsFromPostFields(r *http.Request) []dataprovider.HTTPPart {
var result []dataprovider.HTTPPart
for k := range r.Form {
if strings.HasPrefix(k, "http_part_name") {
partName := r.Form.Get(k)
if partName != "" {
idx := strings.TrimPrefix(k, "http_part_name")
order, err := strconv.Atoi(idx)
if err != nil {
continue
}
filePath := r.Form.Get(fmt.Sprintf("http_part_file%s", idx))
body := r.Form.Get(fmt.Sprintf("http_part_body%s", idx))
concatHeaders := getSliceFromDelimitedValues(r.Form.Get(fmt.Sprintf("http_part_headers%s", idx)), "\n")
var headers []dataprovider.KeyValue
for _, h := range concatHeaders {
values := strings.SplitN(h, ":", 2)
if len(values) > 1 {
headers = append(headers, dataprovider.KeyValue{
Key: strings.TrimSpace(values[0]),
Value: strings.TrimSpace(values[1]),
})
}
}
result = append(result, dataprovider.HTTPPart{
Name: partName,
Filepath: filePath,
Headers: headers,
Body: body,
Order: order,
})
}
}
}
sort.Slice(result, func(i, j int) bool {
return result[i].Order < result[j].Order
})
return result
}
func getEventActionOptionsFromPostFields(r *http.Request) (dataprovider.BaseEventActionOptions, error) {
httpTimeout, err := strconv.Atoi(r.Form.Get("http_timeout"))
if err != nil {
@ -1913,6 +1954,7 @@ func getEventActionOptionsFromPostFields(r *http.Request) (dataprovider.BaseEven
Method: r.Form.Get("http_method"),
QueryParameters: getKeyValsFromPostFields(r, "http_query_key", "http_query_val"),
Body: r.Form.Get("http_body"),
Parts: getHTTPPartsFromPostFields(r),
},
CmdConfig: dataprovider.EventActionCommandConfig{
Cmd: r.Form.Get("cmd_path"),

View file

@ -2208,6 +2208,27 @@ func compareKeyValues(expected, actual []dataprovider.KeyValue) error {
return nil
}
func compareHTTPparts(expected, actual []dataprovider.HTTPPart) error {
for _, p1 := range expected {
found := false
for _, p2 := range actual {
if p1.Name == p2.Name {
found = true
if err := compareKeyValues(p1.Headers, p2.Headers); err != nil {
return fmt.Errorf("http headers mismatch for part %q", p1.Name)
}
if p1.Body != p2.Body || p1.Filepath != p2.Filepath {
return fmt.Errorf("http part %q mismatch", p1.Name)
}
}
}
if !found {
return fmt.Errorf("expected http part %q not found", p1.Name)
}
}
return nil
}
func compareEventActionHTTPConfigFields(expected, actual dataprovider.EventActionHTTPConfig) error {
if expected.Endpoint != actual.Endpoint {
return errors.New("http endpoint mismatch")
@ -2236,7 +2257,10 @@ func compareEventActionHTTPConfigFields(expected, actual dataprovider.EventActio
if expected.Body != actual.Body {
return errors.New("http body mismatch")
}
return nil
if len(expected.Parts) != len(actual.Parts) {
return errors.New("http parts mismatch")
}
return compareHTTPparts(expected.Parts, actual.Parts)
}
func compareEventActionEmailConfigFields(expected, actual dataprovider.EventActionEmailConfig) error {

View file

@ -17,7 +17,7 @@ package version
import "strings"
const version = "2.3.3-dev"
const version = "2.3.4-dev"
var (
commit = ""

View file

@ -27,7 +27,7 @@ info:
SFTPGo supports groups to simplify the administration of multiple accounts by letting you assign settings once to a group, instead of multiple times to each individual user.
The SFTPGo WebClient allows end users to change their credentials, browse and manage their files in the browser and setup two-factor authentication which works with Authy, Google Authenticator and other compatible apps.
From the WebClient each authorized user can also create HTTP/S links to externally share files and folders securely, by setting limits to the number of downloads/uploads, protecting the share with a password, limiting access by source IP address, setting an automatic expiration date.
version: 2.3.3-dev
version: 2.3.4-dev
contact:
name: API support
url: 'https://github.com/drakkan/sftpgo'
@ -6013,6 +6013,21 @@ components:
type: string
value:
type: string
HTTPPart:
type: object
properties:
name:
type: string
headers:
type: array
items:
$ref: '#/components/schemas/KeyValue'
description: 'Additional headers. Content-Disposition header is automatically set. Content-Type header is automatically detect for files to attach'
filepath:
type: string
description: 'path to the file to be sent as an attachment'
body:
type: string
EventActionHTTPConfig:
type: object
properties:
@ -6033,6 +6048,7 @@ components:
type: integer
minimum: 1
maximum: 120
description: 'Ignored for multipart requests with files as attachments'
skip_tls_verify:
type: boolean
description: 'if enabled the HTTP client accepts any TLS certificate presented by the server and any host name in that certificate. In this mode, TLS is susceptible to man-in-the-middle attacks. This should be used only for testing.'
@ -6049,6 +6065,11 @@ components:
body:
type: string
description: HTTP POST/PUT body
parts:
type: array
items:
$ref: '#/components/schemas/HTTPPart'
description: 'Multipart requests allow to combine one or more sets of data into a single body. For each part, you can set a file path or a body as text. Placeholders are supported in file path, body, header values.'
EventActionCommandConfig:
type: object
properties:

View file

@ -3,17 +3,17 @@
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>sftpgo</id>
<version>2.3.3</version>
<version>2.3.4</version>
<packageSourceUrl>https://github.com/drakkan/sftpgo/tree/main/pkgs/choco</packageSourceUrl>
<owners>asheroto</owners>
<title>SFTPGo</title>
<authors>Nicola Murino</authors>
<projectUrl>https://github.com/drakkan/sftpgo</projectUrl>
<iconUrl>https://cdn.statically.io/gh/drakkan/sftpgo/v2.3.3/static/img/logo.png</iconUrl>
<iconUrl>https://cdn.statically.io/gh/drakkan/sftpgo/v2.3.4/static/img/logo.png</iconUrl>
<licenseUrl>https://github.com/drakkan/sftpgo/blob/main/LICENSE</licenseUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<projectSourceUrl>https://github.com/drakkan/sftpgo</projectSourceUrl>
<docsUrl>https://github.com/drakkan/sftpgo/tree/v2.3.3/docs</docsUrl>
<docsUrl>https://github.com/drakkan/sftpgo/tree/v2.3.4/docs</docsUrl>
<bugTrackerUrl>https://github.com/drakkan/sftpgo/issues</bugTrackerUrl>
<tags>sftp sftp-server ftp webdav s3 azure-blob google-cloud-storage cloud-storage scp data-at-rest-encryption multi-factor-authentication multi-step-authentication</tags>
<summary>Fully featured and highly configurable SFTP server with optional HTTP/S,FTP/S and WebDAV support.</summary>
@ -32,7 +32,7 @@ You can find more info [here](https://github.com/drakkan/sftpgo).
* This package installs SFTPGo as Windows Service.
* After the first installation please take a look at the [Getting Started Guide](https://github.com/drakkan/sftpgo/blob/main/docs/howto/getting-started.md).</description>
<releaseNotes>https://github.com/drakkan/sftpgo/releases/tag/v2.3.3</releaseNotes>
<releaseNotes>https://github.com/drakkan/sftpgo/releases/tag/v2.3.4</releaseNotes>
</metadata>
<files>
<file src="**" exclude="**\*.md;**\icon.png;**\icon.jpg;**\icon.svg" />

View file

@ -1,8 +1,8 @@
$ErrorActionPreference = 'Stop'
$packageName = 'sftpgo'
$softwareName = 'SFTPGo'
$url = 'https://github.com/drakkan/sftpgo/releases/download/v2.3.3/sftpgo_v2.3.3_windows_x86_64.exe'
$checksum = '5A6E798BDD920D7DE6110C8478A993C9E5A691E48F74D86021DF4745CB8A5FDC'
$url = 'https://github.com/drakkan/sftpgo/releases/download/v2.3.4/sftpgo_v2.3.4_windows_x86_64.exe'
$checksum = '68428CECD98DB2F111BB5B1293CF7807BA8DA2CEFDD7F38ACDCF7B7D50C781DC'
$silentArgs = '/VERYSILENT'
$validExitCodes = @(0)
@ -45,8 +45,8 @@ Write-Output ""
Write-Output "General information (README) location:"
Write-Output "`thttps://github.com/drakkan/sftpgo"
Write-Output "Getting start guide location:"
Write-Output "`thttps://github.com/drakkan/sftpgo/blob/v2.3.3/docs/howto/getting-started.md"
Write-Output "`thttps://github.com/drakkan/sftpgo/blob/v2.3.4/docs/howto/getting-started.md"
Write-Output "Detailed information (docs folder) location:"
Write-Output "`thttps://github.com/drakkan/sftpgo/tree/v2.3.3/docs"
Write-Output "`thttps://github.com/drakkan/sftpgo/tree/v2.3.4/docs"
Write-Output ""
Write-Output "---------------------------"

View file

@ -1,3 +1,9 @@
sftpgo (2.3.4-1ppa1) bionic; urgency=medium
* New upstream release
-- Nicola Murino <nicola.murino@gmail.com> Thu, 01 Sep 2022 16:56:20 +0200
sftpgo (2.3.3-1ppa1) bionic; urgency=medium
* New upstream release

View file

@ -213,22 +213,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div>
</div>
<div class="form-group row action-type action-http">
<label for="idHTTPBody" class="col-sm-2 col-form-label">Body</label>
<div class="col-sm-10">
<textarea class="form-control" id="idHTTPBody" name="http_body" rows="4" placeholder=""
aria-describedby="httpBodyHelpBlock">{{.Action.Options.HTTPConfig.Body}}</textarea>
<small id="httpBodyHelpBlock" class="form-text text-muted">
Placeholders are supported
</small>
</div>
</div>
<div class="form-group row action-type action-http">
<label for="idHTTPTimeout" class="col-sm-2 col-form-label">Timeout</label>
<div class="col-sm-10">
<input type="number" min="1" max="120" class="form-control" id="idHTTPTimeout" name="http_timeout" placeholder=""
value="{{.Action.Options.HTTPConfig.Timeout}}">
aria-describedby="httpTimeoutHelpBlock" value="{{.Action.Options.HTTPConfig.Timeout}}">
<small id="httpTimeoutHelpBlock" class="form-text text-muted">
Ignored for multipart requests with files as attachments.
</small>
</div>
</div>
@ -240,6 +232,100 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div>
</div>
<div class="form-group row action-type action-http">
<label for="idHTTPBody" class="col-sm-2 col-form-label">Body</label>
<div class="col-sm-10">
<textarea class="form-control" id="idHTTPBody" name="http_body" rows="4" placeholder=""
aria-describedby="httpBodyHelpBlock">{{.Action.Options.HTTPConfig.Body}}</textarea>
<small id="httpBodyHelpBlock" class="form-text text-muted">
Placeholders are supported. Ignored for HTTP get requested. Leave empty for multipart requests.
</small>
</div>
</div>
<div class="card bg-light mb-3 action-type action-http">
<div class="card-header">
<b>Multipart body</b>
</div>
<div class="card-body">
<h6 class="card-title mb-4">Multipart requests allow to combine one or more sets of data into a single body. For each part, you can set a file path or a body as text. Placeholders are supported in file path, body, header values.</h6>
<div class="form-group row">
<div class="col-md-12 form_field_http_part_outer">
{{range $idx, $val := .Action.Options.HTTPConfig.Parts}}
<div class="row form_field_http_part_outer_row">
<div class="col-md-12">
<div class="row">
<div class="form-group col-md-2">
<input type="text" class="form-control" id="idHTTPPartName{{$idx}}" name="http_part_name{{$idx}}" placeholder="Part name" value="{{$val.Name}}">
</div>
<div class="form-group col-md-3">
<input type="text" class="form-control" id="idHTTPPartFile{{$idx}}" name="http_part_file{{$idx}}" placeholder="File path" value="{{$val.Filepath}}">
</div>
<div class="form-group col-md-5">
<textarea class="form-control" id="idHTTPPartHeaders{{$idx}}" name="http_part_headers{{$idx}}" rows="2" placeholder="Additional part headers"
aria-describedby="httpPartHeadersHelpBlock{{$idx}}">{{range $val.Headers}}{{.Key}}: {{.Value}}&#010;{{end}}</textarea>
<small id="httpPartHeadersHelpBlock{{$idx}}" class="form-text text-muted">
One header per line as "key: value", example: "Content-Type: application/json", without quotes. Content type for files is automatically detected
</small>
</div>
<div class="form-group col-md-1"></div>
<div class="form-group col-md-1">
<button class="btn btn-circle btn-danger remove_http_part_btn_frm_field">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="row">
<div class="form-group col-md-10">
<textarea class="form-control" id="idHTTPPartBody{{$idx}}" name="http_part_body{{$idx}}" rows="3" placeholder="Part body">{{$val.Body}}</textarea>
</div>
</div>
<hr>
</div>
</div>
{{else}}
<div class="row form_field_http_part_outer_row">
<div class="col-md-12">
<div class="row">
<div class="form-group col-md-2">
<input type="text" class="form-control" id="idHTTPPartName0" name="http_part_name0" placeholder="Part name" value="">
</div>
<div class="form-group col-md-3">
<input type="text" class="form-control" id="idHTTPPartFile0" name="http_part_file0" placeholder="File path" value="">
</div>
<div class="form-group col-md-5">
<textarea class="form-control" id="idHTTPPartHeaders0" name="http_part_headers0" rows="2" placeholder="Additional part headers"
aria-describedby="httpPartHeadersHelpBlock0"></textarea>
<small id="httpPartHeadersHelpBlock0" class="form-text text-muted">
One header per line as "key: value", example: "Content-Type: application/json", without quotes. Content type for files is automatically detected
</small>
</div>
<div class="form-group col-md-1"></div>
<div class="form-group col-md-1">
<button class="btn btn-circle btn-danger remove_http_part_btn_frm_field">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="row">
<div class="form-group col-md-10">
<textarea class="form-control" id="idHTTPPartBody0" name="http_part_body0" rows="3" placeholder="Part body"></textarea>
</div>
</div>
<hr>
</div>
</div>
{{end}}
</div>
</div>
<div class="row mx-1">
<button type="button" class="btn btn-secondary add_new_http_part_field_btn">
<i class="fas fa-plus"></i> Add new HTTP part
</button>
</div>
</div>
</div>
<div class="form-group row action-type action-cmd">
<label for="idCmdPath" class="col-sm-2 col-form-label">Command</label>
<div class="col-sm-10">
@ -745,6 +831,50 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
$(this).closest(".form_field_fs_rename_outer_row").remove();
});
$("body").on("click", ".add_new_http_part_field_btn", function () {
var index = $(".form_field_http_part_outer").find(".form_field_http_part_outer_row").length;
while (document.getElementById("idHTTPPartName"+index) != null){
index++;
}
$(".form_field_http_part_outer").append(`
<div class="row form_field_http_part_outer_row">
<div class="col-md-12">
<div class="row">
<div class="form-group col-md-2">
<input type="text" class="form-control" id="idHTTPPartName${index}" name="http_part_name${index}" placeholder="Part name" value="">
</div>
<div class="form-group col-md-3">
<input type="text" class="form-control" id="idHTTPPartFile${index}" name="http_part_file${index}" placeholder="File path" value="">
</div>
<div class="form-group col-md-5">
<textarea class="form-control" id="idHTTPPartHeaders${index}" name="http_part_headers${index}" rows="2" placeholder="Additional part headers"
aria-describedby="httpPartHeadersHelpBlock${index}"></textarea>
<small id="httpPartHeadersHelpBlock${index}" class="form-text text-muted">
One header per line as "key: value", example: "Content-Type: application/json", without quotes. Content type for files is automatically detected
</small>
</div>
<div class="form-group col-md-1"></div>
<div class="form-group col-md-1">
<button class="btn btn-circle btn-danger remove_http_part_btn_frm_field">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="row">
<div class="form-group col-md-10">
<textarea class="form-control" id="idHTTPPartBody${index}" name="http_part_body${index}" rows="3" placeholder="Part body"></textarea>
</div>
</div>
<hr>
</div>
</div>
`);
});
$("body").on("click", ".remove_http_part_btn_frm_field", function () {
$(this).closest(".form_field_http_part_outer_row").remove();
});
function onTypeChanged(val){
$('.action-type').hide();
switch (val) {

View file

@ -455,7 +455,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<label for="idSFTPFingerprints" class="col-sm-2 col-form-label">Fingerprints</label>
<div class="col-sm-10">
<textarea class="form-control" id="idSFTPFingerprints" name="sftp_fingerprints" rows="3"
aria-describedby="SFTPFingerprintsHelpBlock">{{range .SFTPConfig.Fingerprints}}{{.}}&#10;{{end}}</textarea>
aria-describedby="SFTPFingerprintsHelpBlock">{{range .SFTPConfig.Fingerprints}}{{.}}&#010;{{end}}</textarea>
<small id="SFTPFingerprintsHelpBlock" class="form-text text-muted">
SHA256 fingerprints to validate when connecting to the external SFTP server, one per line. If empty any host key will be accepted: this is a security risk!
</small>