diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 5b75cfb9..44157e20 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -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/ diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index db888ceb..dddb0434 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -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" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 04ae2ccc..7e29c477 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/docker/README.md b/docker/README.md index f4dc82de..c64292b0 100644 --- a/docker/README.md +++ b/docker/README.md @@ -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) diff --git a/go.mod b/go.mod index 8f1ae6d6..775908c1 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 499305f6..80e33a1b 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/common/eventmanager.go b/internal/common/eventmanager.go index e13200ea..23fafed2 100644 --- a/internal/common/eventmanager.go +++ b/internal/common/eventmanager.go @@ -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 { diff --git a/internal/common/eventmanager_test.go b/internal/common/eventmanager_test.go index f065f384..7fb839ba 100644 --- a/internal/common/eventmanager_test.go +++ b/internal/common/eventmanager_test.go @@ -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) +} diff --git a/internal/common/protocol_test.go b/internal/common/protocol_test.go index 256d8609..980ac9b8 100644 --- a/internal/common/protocol_test.go +++ b/internal/common/protocol_test.go @@ -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", diff --git a/internal/dataprovider/eventrule.go b/internal/dataprovider/eventrule.go index 294962ab..ceca4cdb 100644 --- a/internal/dataprovider/eventrule.go +++ b/internal/dataprovider/eventrule.go @@ -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 } diff --git a/internal/httpd/httpd_test.go b/internal/httpd/httpd_test.go index d354d465..11e42184 100644 --- a/internal/httpd/httpd_test.go +++ b/internal/httpd/httpd_test.go @@ -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{ diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go index 81c4fdf3..461fa85d 100644 --- a/internal/httpd/webadmin.go +++ b/internal/httpd/webadmin.go @@ -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"), diff --git a/internal/httpdtest/httpdtest.go b/internal/httpdtest/httpdtest.go index 3a7967cc..578afc5e 100644 --- a/internal/httpdtest/httpdtest.go +++ b/internal/httpdtest/httpdtest.go @@ -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 { diff --git a/internal/version/version.go b/internal/version/version.go index c059747a..a2d0e35a 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -17,7 +17,7 @@ package version import "strings" -const version = "2.3.3-dev" +const version = "2.3.4-dev" var ( commit = "" diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 9426474d..3d32828c 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -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: diff --git a/pkgs/choco/sftpgo.nuspec b/pkgs/choco/sftpgo.nuspec index 39779634..535a26cc 100644 --- a/pkgs/choco/sftpgo.nuspec +++ b/pkgs/choco/sftpgo.nuspec @@ -3,17 +3,17 @@ sftpgo - 2.3.3 + 2.3.4 https://github.com/drakkan/sftpgo/tree/main/pkgs/choco asheroto SFTPGo Nicola Murino https://github.com/drakkan/sftpgo - https://cdn.statically.io/gh/drakkan/sftpgo/v2.3.3/static/img/logo.png + https://cdn.statically.io/gh/drakkan/sftpgo/v2.3.4/static/img/logo.png https://github.com/drakkan/sftpgo/blob/main/LICENSE false https://github.com/drakkan/sftpgo - https://github.com/drakkan/sftpgo/tree/v2.3.3/docs + https://github.com/drakkan/sftpgo/tree/v2.3.4/docs https://github.com/drakkan/sftpgo/issues sftp sftp-server ftp webdav s3 azure-blob google-cloud-storage cloud-storage scp data-at-rest-encryption multi-factor-authentication multi-step-authentication Fully featured and highly configurable SFTP server with optional HTTP/S,FTP/S and WebDAV support. @@ -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). - https://github.com/drakkan/sftpgo/releases/tag/v2.3.3 + https://github.com/drakkan/sftpgo/releases/tag/v2.3.4 diff --git a/pkgs/choco/tools/ChocolateyInstall.ps1 b/pkgs/choco/tools/ChocolateyInstall.ps1 index 7538e00e..b6570d75 100644 --- a/pkgs/choco/tools/ChocolateyInstall.ps1 +++ b/pkgs/choco/tools/ChocolateyInstall.ps1 @@ -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 "---------------------------" \ No newline at end of file diff --git a/pkgs/debian/changelog b/pkgs/debian/changelog index 13b06791..a5a174cc 100644 --- a/pkgs/debian/changelog +++ b/pkgs/debian/changelog @@ -1,3 +1,9 @@ +sftpgo (2.3.4-1ppa1) bionic; urgency=medium + + * New upstream release + + -- Nicola Murino Thu, 01 Sep 2022 16:56:20 +0200 + sftpgo (2.3.3-1ppa1) bionic; urgency=medium * New upstream release diff --git a/templates/webadmin/eventaction.html b/templates/webadmin/eventaction.html index 04afd930..6f6a6519 100644 --- a/templates/webadmin/eventaction.html +++ b/templates/webadmin/eventaction.html @@ -213,22 +213,14 @@ along with this program. If not, see . -
- -
- - - Placeholders are supported - -
-
-
+ aria-describedby="httpTimeoutHelpBlock" value="{{.Action.Options.HTTPConfig.Timeout}}"> + + Ignored for multipart requests with files as attachments. +
@@ -240,6 +232,100 @@ along with this program. If not, see . +
+ +
+ + + Placeholders are supported. Ignored for HTTP get requested. Leave empty for multipart requests. + +
+
+ +
+
+ Multipart body +
+
+
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.
+
+
+ {{range $idx, $val := .Action.Options.HTTPConfig.Parts}} +
+
+
+
+ +
+
+ +
+
+ + + One header per line as "key: value", example: "Content-Type: application/json", without quotes. Content type for files is automatically detected + +
+
+
+ +
+
+
+
+ +
+
+
+
+
+ {{else}} +
+
+
+
+ +
+
+ +
+
+ + + One header per line as "key: value", example: "Content-Type: application/json", without quotes. Content type for files is automatically detected + +
+
+
+ +
+
+
+
+ +
+
+
+
+
+ {{end}} +
+
+
+ +
+
+
+
@@ -745,6 +831,50 @@ along with this program. If not, see . $(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(` +
+
+
+
+ +
+
+ +
+
+ + + One header per line as "key: value", example: "Content-Type: application/json", without quotes. Content type for files is automatically detected + +
+
+
+ +
+
+
+
+ +
+
+
+
+
+ `); + }); + + $("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) { diff --git a/templates/webadmin/fsconfig.html b/templates/webadmin/fsconfig.html index ff28baa6..a88ab8fc 100644 --- a/templates/webadmin/fsconfig.html +++ b/templates/webadmin/fsconfig.html @@ -455,7 +455,7 @@ along with this program. If not, see .
+ aria-describedby="SFTPFingerprintsHelpBlock">{{range .SFTPConfig.Fingerprints}}{{.}} {{end}} 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!