diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index f517aad4..974a90d5 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -382,7 +382,7 @@ jobs: gzip output/man/man1/* cp sftpgo output/ - - uses: uraimo/run-on-arch-action@v2.1.1 + - uses: uraimo/run-on-arch-action@v2 if: ${{ matrix.arch != 'amd64' }} name: Build for ${{ matrix.arch }} id: build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 455e690e..4a265726 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -326,7 +326,7 @@ jobs: env: SFTPGO_VERSION: ${{ steps.get_version.outputs.SFTPGO_VERSION }} - - uses: uraimo/run-on-arch-action@v2.1.1 + - uses: uraimo/run-on-arch-action@v2 if: ${{ matrix.arch != 'amd64' }} name: Build for ${{ matrix.arch }} id: build diff --git a/README.md b/README.md index f0853eb8..1a9b146a 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,6 @@ Several storage backends are supported: local filesystem, encrypted local filesy ## Features -
- - Support for serving local filesystem, encrypted local filesystem, S3 Compatible Object Storage, Google Cloud Storage, Azure Blob Storage or other SFTP accounts over SFTP/SCP/FTP/WebDAV. - Virtual folders are supported: a virtual folder can use any of the supported storage backends. So you can have, for example, an S3 user that exposes a GCS bucket (or part of it) on a specified path and an encrypted local filesystem on another one. Virtual folders can be private or shared among multiple users, for shared virtual folders you can define different quota limits for each user. - Configurable [custom commands and/or HTTP hooks](./docs/custom-actions.md) on file upload, pre-upload, download, pre-download, delete, pre-delete, rename, mkdir, rmdir on SSH commands and on user add, update and delete. @@ -42,6 +40,7 @@ Several storage backends are supported: local filesystem, encrypted local filesy - Per-user and per-directory shell like patterns filters: files can be allowed, denied or hidden based on shell like patterns. - Automatically terminating idle connections. - Automatic blocklist management using the built-in [defender](./docs/defender.md). +- Geo-IP filtering using a [plugin](https://github.com/sftpgo/sftpgo-plugin-geoipfilter). - Atomic uploads are configurable. - Per-user files/folders ownership mapping: you can map all the users to the system account that runs SFTPGo (all platforms are supported) or you can run SFTPGo as root user and map each user or group of users to a different system account (\*NIX only). - Support for Git repositories over SSH. @@ -60,8 +59,6 @@ Several storage backends are supported: local filesystem, encrypted local filesy - Log files are accurate and they are saved in the easily parsable JSON format ([more information](./docs/logs.md)). - SFTPGo supports a [plugin system](./docs/plugins.md) and therefore can be extended using external plugins. -
- ## Platforms SFTPGo is developed and tested on Linux. After each commit, the code is automatically built and tested on Linux, macOS and Windows using a [GitHub Action](./.github/workflows/development.yml). The test cases are regularly manually executed and passed on FreeBSD. Other *BSD variants should work too. diff --git a/go.mod b/go.mod index 1c5cfe5b..62ef1388 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,12 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 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.0 - github.com/aws/aws-sdk-go-v2/config v1.15.1 - github.com/aws/aws-sdk-go-v2/credentials v1.11.0 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.26.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.16.1 + github.com/aws/aws-sdk-go-v2 v1.16.1 + github.com/aws/aws-sdk-go-v2/config v1.15.2 + github.com/aws/aws-sdk-go-v2/credentials v1.11.1 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.2 + github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2 + github.com/aws/aws-sdk-go-v2/service/sts v1.16.2 github.com/cockroachdb/cockroach-go/v2 v2.2.8 github.com/coreos/go-oidc/v3 v3.1.0 github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 @@ -75,17 +75,17 @@ require ( cloud.google.com/go/compute v1.5.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.0 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.7 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.8 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.11.1 // indirect - github.com/aws/smithy-go v1.11.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.11.2 // indirect + github.com/aws/smithy-go v1.11.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 @@ -147,7 +147,7 @@ require ( golang.org/x/tools v0.1.10 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220323144105-ec3c684e5b14 // indirect + google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb // indirect google.golang.org/grpc v1.45.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect diff --git a/go.sum b/go.sum index ea1e5493..14c26cf6 100644 --- a/go.sum +++ b/go.sum @@ -137,51 +137,51 @@ 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.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.16.0 h1:cBAYjiiexRAg9v2z9vb6IdxAa7ef4KCtjW7w7e3GxGo= -github.com/aws/aws-sdk-go-v2 v1.16.0/go.mod h1:lJYcuZZEHWNIb6ugJjbQY1fykdoobWbOS7kJYb4APoI= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.0 h1:J/tiyHbl07LL4/1i0rFrW5pbLMvo7M6JrekBUNpLeT4= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.0/go.mod h1:ohZjRmiToJ4NybwWTGOCbzlUQU8dxSHxYKzuX7k5l6Y= +github.com/aws/aws-sdk-go-v2 v1.16.1 h1:udzee98w8H6ikRgtFdVN9JzzYEbi/quFfSvduZETJIU= +github.com/aws/aws-sdk-go-v2 v1.16.1/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM= github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY= -github.com/aws/aws-sdk-go-v2/config v1.15.1 h1:hTIZFepYESYyowQUBo47lu69WSxsYqGUILY9Nu8+7pY= -github.com/aws/aws-sdk-go-v2/config v1.15.1/go.mod h1:MZHGbuW2WnqIOQQBKu2ZkhTjuutZSTnn56TDq4QyydE= +github.com/aws/aws-sdk-go-v2/config v1.15.2 h1:4oGcm1yqqtTc2Z8YpwehwjSiBA3TR0iZbFCgNlXcVFQ= +github.com/aws/aws-sdk-go-v2/config v1.15.2/go.mod h1:S1p1xf7DGVp0srNq0BakyxfirOldPQeDVlx7+fllyok= github.com/aws/aws-sdk-go-v2/credentials v1.4.0/go.mod h1:dgGR+Qq7Wjcd4AOAW5Rf5Tnv3+x7ed6kETXyS9WCuAY= -github.com/aws/aws-sdk-go-v2/credentials v1.11.0 h1:gc4Uhs80s60nmLon5Z4JXWinX2BkAGT0YROoUT8h8U4= -github.com/aws/aws-sdk-go-v2/credentials v1.11.0/go.mod h1:EdV1ZFgtZ4XM5RDHWcRWK8H+xW5duNVBqWj2oLu7tRo= +github.com/aws/aws-sdk-go-v2/credentials v1.11.1 h1:uR323+M7ca3v2GKXbFSwWbNA3kLjjFzaalL6W4rpB9s= +github.com/aws/aws-sdk-go-v2/credentials v1.11.1/go.mod h1:pYrHWfKUoWTmbr+xTf6ZoWeyyvLAQ5BPT3aL+nKlTpE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0/go.mod h1:CpNzHK9VEFUCknu50kkB8z58AH2B5DvPP7ea1LHve/Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.1 h1:F9Je1nq5YXfMOv6451NHvMf6U0iTWeMnsG0MMIQoUmk= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.1/go.mod h1:Yph0XsTbQ5GGZ2+mO1a03P/SO9fdX3t1nejIp2tq79g= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.1 h1:2lGuOytsLs4N2z1UmZ9s7BuhHMcZxNkm612YsLHK/8g= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.1/go.mod h1:hkzjqerOQhhBGAL/DdmKLsP8hGZvtyvukCLvrU5twz4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.7 h1:KUErSJgdqmqAPBWAp6Zx9CjL0YXfytXJeXcsWnuCM1c= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.7/go.mod h1:oB9nZcxH1cGq7NPGurVJwxrO2vmJ9mmEBayCwcAlmT8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.1 h1:feVfa9eJonhJiss7g51ikjNB2DrUzbNZNvPL8pw/54k= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.1/go.mod h1:K4vz7lRYCyLYpYAMCLObODahFgARdD3YVa0MvQte9Co= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.2 h1:+AULPOLHEDjH2TcNKpixl4gt26hFOdlUuuisZUBFczA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.2/go.mod h1:jmsqNRVo2XlUTNXG/NF7hM7o2gd2jhfg8vdJ135d4XA= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.2 h1:PTFTblDWY/HdQ6ix5+to1uLARgLLuYbzKGLQnIdE5Us= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.2/go.mod h1:j4OwU2Gb7yaQaidJRpdlIRYX93jBCWhVYIgMlPjf89o= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8 h1:CDaO90VZVBAL1sK87S5oSPIrp7yZqORv1hPIi2UsTMk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8/go.mod h1:LnTQMTqbKsbtt+UI5+wPsB7jedW+2ZgozoPG8k6cMxg= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2 h1:XXR3cdOcKRCTZf6ctcqpMf+go1BdzTm6+T9Ul5zxcMI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2/go.mod h1:1x4ZP3Z8odssdhuLI+/1Tqw6Pt/VAaP4Tr8EUxHvPXE= github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2/go.mod h1:BQV0agm+JEhqR+2RT5e1XTFIDcAAV0eW6z2trp+iduw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.8 h1:adr3PfiggFtqgFofAMUFCtdvwzpf3QxPES4ezK4M3iI= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.8/go.mod h1:wLbQYt36AJqaRZUQiCNXzbtkNigyPfKHrotHuIDiCy8= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.0 h1:uhb7moM7VjqIEpWzTpCvceLDSwrWpaleXm39OnVjuLE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.0/go.mod h1:pA2St3Pu2Ldy6fBPY45Azoh1WBG4oS7eIKOd4XN7Meg= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.1 h1:iq77O8kBduROlzJ6mhN8zqxXxctDZ1PnWY0kyCfYMGc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.1/go.mod h1:1R7cjoiEG9cgMCBpIOuCyZWT0Dn87vNUeAaC8Reiaow= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.9 h1:8umg6LSQ/b0+ZTq+Ro8K7VLGVwd7kiYQtIACpf2N/Yo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.9/go.mod h1:kASRBzoVW4I8KUmGCjsowAqVor9QU9DuTUABVducrTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 h1:T4pFel53bkHjL2mMo+4DKE6r6AuoZnM0fg7k1/ratr4= +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/checksum v1.1.2 h1:VoMBHtQZygRs8mcQNDrfmn09vFH2ccjf79nGJ0xuUfo= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.2/go.mod h1:2Fzbfwkx7z4yue1Lz6KDSKG84UpOcUKFl3VAtSF/gcg= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0/go.mod h1:R1KK+vY8AfalhG1AOu5e35pOD2SdoPKQCFLTvnxiohk= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.1 h1:B/SPX7J+Y0Yrcjv60Nhbh1gC2uBN47SfN8JYre6Mp4M= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.1/go.mod h1:2Hhr9Eh1gJzDatwACX/ozAZ/ljq5vzvPRu5cdu25tzc= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.1 h1:sjASzLVAG9okVe5HgLur36itaOY4UC90VZNXAtcn0+s= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.1/go.mod h1:ELGakx1J1qEJqWkwiN0jh3CMDTN6v17kOgN8kZgo6LQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2 h1:RrN7V0r8+lUUKZM4OAoCOIZqjPLZPOl6wuwMd2QIryI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2/go.mod h1:7hwSi01X5Yj9H0qLQljrn8OSdLwwSym1aQCfGn1tDQQ= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2 h1:yxr9h06slG9fdVmO3CpBVuFVD73AeUHLmBxhCr3T3+E= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2/go.mod h1:rvV/Jr4T8H3kMMw/9fFQw9kxqb70YKihA0oWuUFd3K8= github.com/aws/aws-sdk-go-v2/service/kms v1.5.0/go.mod h1:w7JuP9Oq1IKMFQPkNe3V6s9rOssXzOVEMNEqK1L1bao= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.1 h1:FSoVdUKHxAzZKXuemm+7vVj3eOUY5u01SSwwkfhWpqA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.1/go.mod h1:ZS+sYP2DfetYS7n42MLAoz8x50T/w5su+rZmhaqTubg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2 h1:Op/A+5+D1K0bmwH3BStYbp/7iod9Rdfm9898A0qYxLc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2/go.mod h1:Ao1W746VIMdV1WhEkjeVa5JzlaE1JkxJ46facHX9kzs= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.6.0/go.mod h1:B+7C5UKdVq1ylkI/A6O8wcurFtaux0R1njePNPtKwoA= github.com/aws/aws-sdk-go-v2/service/ssm v1.10.0/go.mod h1:4dXS5YNqI3SNbetQ7X7vfsMlX6ZnboJA2dulBwJx7+g= github.com/aws/aws-sdk-go-v2/service/sso v1.4.0/go.mod h1:+1fpWnL96DL23aXPpMGbsmKe8jLTEfbjuQoA4WS1VaA= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.1 h1:DyHctRsJIAWIvom1Itb4T84D2jwpIu+KIi3d0SFaswg= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.1/go.mod h1:CvFTucADIx7U/M44vjLs/ZttpQHdpxwK+62+dUGhDeY= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.2 h1:8fVz1c9B/63w7O0kxbrCTT69iV4DgXnFumarPCZ3Cns= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.2/go.mod h1:GdCj3+FzI3D5tauOzz8n3YjN70XvgZz82PVVtJXmDds= github.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.1 h1:xsOtPAvHqhvQvBza5ohaUcfq1LceH2lZKMUGZJKiZiM= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.1/go.mod h1:Aq2/Qggh2oemSfyHH+EO4UBbgWG6zFCXLHYI4ILTY7w= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.2 h1:qgK5htfKByTiPxS/diZ/mTCfDwGAVuyjRdqu6VoCh80= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.2/go.mod h1:RoMljzynmRe3jyOsRgqIMTzyhpAv6XNxu549M1X4Mdo= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.11.1 h1:IQ+lPZVkSM3FRtyaDox41R8YS6iwPMYIreejOgPW49g= -github.com/aws/smithy-go v1.11.1/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= +github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= +github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= 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= @@ -1131,8 +1131,8 @@ google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2 google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220323144105-ec3c684e5b14 h1:17TOyVD+9MLIDtDJW9PdtMuVT7gNLEkN+G/xFYjZmr8= -google.golang.org/genproto v0.0.0-20220323144105-ec3c684e5b14/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb h1:0m9wktIpOxGw+SSKmydXWB3Z3GTfcPP6+q75HCQa6HI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= diff --git a/httpd/auth_utils.go b/httpd/auth_utils.go index 93ef43df..47fef7bf 100644 --- a/httpd/auth_utils.go +++ b/httpd/auth_utils.go @@ -159,11 +159,11 @@ func (c *jwtTokenClaims) hasPerm(perm string) bool { return util.IsStringInSlice(perm, c.Permissions) } -func (c *jwtTokenClaims) createToken(tokenAuth *jwtauth.JWTAuth, audience tokenAudience) (jwt.Token, string, error) { +func (c *jwtTokenClaims) createToken(tokenAuth *jwtauth.JWTAuth, audience tokenAudience, ip string) (jwt.Token, string, error) { claims := c.asMap() now := time.Now().UTC() - claims[jwt.JwtIDKey] = xid.New().String() + claims[jwt.JwtIDKey] = fmt.Sprintf("%s%s", xid.New().String(), ip) claims[jwt.NotBeforeKey] = now.Add(-30 * time.Second) claims[jwt.ExpirationKey] = now.Add(tokenDuration) claims[jwt.AudienceKey] = audience @@ -171,8 +171,8 @@ func (c *jwtTokenClaims) createToken(tokenAuth *jwtauth.JWTAuth, audience tokenA return tokenAuth.Encode(claims) } -func (c *jwtTokenClaims) createTokenResponse(tokenAuth *jwtauth.JWTAuth, audience tokenAudience) (map[string]interface{}, error) { - token, tokenString, err := c.createToken(tokenAuth, audience) +func (c *jwtTokenClaims) createTokenResponse(tokenAuth *jwtauth.JWTAuth, audience tokenAudience, ip string) (map[string]interface{}, error) { + token, tokenString, err := c.createToken(tokenAuth, audience, ip) if err != nil { return nil, err } @@ -184,8 +184,10 @@ func (c *jwtTokenClaims) createTokenResponse(tokenAuth *jwtauth.JWTAuth, audienc return response, nil } -func (c *jwtTokenClaims) createAndSetCookie(w http.ResponseWriter, r *http.Request, tokenAuth *jwtauth.JWTAuth, audience tokenAudience) error { - resp, err := c.createTokenResponse(tokenAuth, audience) +func (c *jwtTokenClaims) createAndSetCookie(w http.ResponseWriter, r *http.Request, tokenAuth *jwtauth.JWTAuth, + audience tokenAudience, ip string, +) error { + resp, err := c.createTokenResponse(tokenAuth, audience, ip) if err != nil { return err } diff --git a/httpd/httpd_test.go b/httpd/httpd_test.go index 3b4444cf..3ea2a7a7 100644 --- a/httpd/httpd_test.go +++ b/httpd/httpd_test.go @@ -8713,6 +8713,38 @@ func TestWebClientMaxConnections(t *testing.T) { common.Config.MaxTotalConnections = oldValue } +func TestTokenInvalidIPAddress(t *testing.T) { + user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated) + assert.NoError(t, err) + + webToken, err := getJWTWebClientTokenFromTestServerWithAddr(defaultUsername, defaultPassword, "1.1.1.1") + assert.NoError(t, err) + + req, err := http.NewRequest(http.MethodGet, webClientFilesPath, nil) + assert.NoError(t, err) + req.RemoteAddr = "1.1.1.2" + req.RequestURI = webClientFilesPath + setJWTCookieForReq(req, webToken) + rr := executeRequest(req) + checkResponseCode(t, http.StatusFound, rr) + + apiToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword) + assert.NoError(t, err) + + req, err = http.NewRequest(http.MethodGet, userDirsPath+"/?path=%2F", nil) + assert.NoError(t, err) + req.RemoteAddr = "2.2.2.2" + setBearerForReq(req, apiToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusUnauthorized, rr) + assert.Contains(t, rr.Body.String(), "Your token is not valid") + + _, err = httpdtest.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(user.GetHomeDir()) + assert.NoError(t, err) +} + func TestDefender(t *testing.T) { oldConfig := config.GetCommonConfig() @@ -17500,7 +17532,7 @@ func getJWTAPIUserTokenFromTestServer(username, password string) (string, error) req.SetBasicAuth(username, password) rr := executeRequest(req) if rr.Code != http.StatusOK { - return "", fmt.Errorf("unexpected status code %v", rr.Code) + return "", fmt.Errorf("unexpected status code %v", rr.Code) } responseHolder := make(map[string]interface{}) err := render.DecodeJSON(rr.Body, &responseHolder) diff --git a/httpd/internal_test.go b/httpd/internal_test.go index 671c05e9..af05faf8 100644 --- a/httpd/internal_test.go +++ b/httpd/internal_test.go @@ -629,7 +629,7 @@ func TestUpdateWebAdminInvalidClaims(t *testing.T) { Permissions: admin.Permissions, Signature: admin.GetSignature(), } - token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin) + token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin, "") assert.NoError(t, err) form := make(url.Values) @@ -746,7 +746,7 @@ func TestCreateTokenError(t *testing.T) { } req, _ := http.NewRequest(http.MethodGet, tokenPath, nil) - server.generateAndSendToken(rr, req, admin) + server.generateAndSendToken(rr, req, admin, "") assert.Equal(t, http.StatusInternalServerError, rr.Code) rr = httptest.NewRecorder() @@ -778,7 +778,7 @@ func TestCreateTokenError(t *testing.T) { assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) req, _ = http.NewRequest(http.MethodPost, webAdminSetupPath, nil) rr = httptest.NewRecorder() - server.loginAdmin(rr, req, &admin, false, nil) + server.loginAdmin(rr, req, &admin, false, nil, "") // req with no POST body req, _ = http.NewRequest(http.MethodGet, webAdminLoginPath+"?a=a%C3%AO%GG", nil) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -1991,7 +1991,7 @@ func TestWebUserInvalidClaims(t *testing.T) { Permissions: nil, Signature: user.GetSignature(), } - token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient) + token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient, "") assert.NoError(t, err) req, _ := http.NewRequest(http.MethodGet, webClientFilesPath, nil) @@ -2066,7 +2066,7 @@ func TestInvalidClaims(t *testing.T) { Permissions: nil, Signature: user.GetSignature(), } - token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient) + token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient, "") assert.NoError(t, err) form := make(url.Values) form.Set(csrfFormToken, createCSRFToken()) @@ -2086,7 +2086,7 @@ func TestInvalidClaims(t *testing.T) { Permissions: nil, Signature: admin.GetSignature(), } - token, err = c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin) + token, err = c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin, "") assert.NoError(t, err) form = make(url.Values) form.Set(csrfFormToken, createCSRFToken()) @@ -2134,7 +2134,7 @@ func TestSigningKey(t *testing.T) { Permissions: nil, Signature: user.GetSignature(), } - token, err := c.createTokenResponse(server1.tokenAuth, tokenAudienceWebClient) + token, err := c.createTokenResponse(server1.tokenAuth, tokenAudienceWebClient, "") assert.NoError(t, err) accessToken := token["access_token"].(string) assert.NotEmpty(t, accessToken) diff --git a/httpd/middleware.go b/httpd/middleware.go index e011e757..92975bd8 100644 --- a/httpd/middleware.go +++ b/httpd/middleware.go @@ -42,33 +42,29 @@ func validateJWTToken(w http.ResponseWriter, r *http.Request, audience tokenAudi isAPIToken := (audience == tokenAudienceAPI || audience == tokenAudienceAPIUser) - if err != nil || token == nil { - logger.Debug(logSender, "", "error getting jwt token: %v", err) + doRedirect := func(message string, err error) { if isAPIToken { - sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + sendAPIResponse(w, r, err, message, http.StatusUnauthorized) } else { http.Redirect(w, r, redirectPath, http.StatusFound) } + } + + if err != nil || token == nil { + logger.Debug(logSender, "", "error getting jwt token: %v", err) + doRedirect(http.StatusText(http.StatusUnauthorized), err) return errInvalidToken } err = jwt.Validate(token) if err != nil { logger.Debug(logSender, "", "error validating jwt token: %v", err) - if isAPIToken { - sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - } else { - http.Redirect(w, r, redirectPath, http.StatusFound) - } + doRedirect(http.StatusText(http.StatusUnauthorized), err) return errInvalidToken } if isTokenInvalidated(r) { logger.Debug(logSender, "", "the token has been invalidated") - if isAPIToken { - sendAPIResponse(w, r, nil, "Your token is no longer valid", http.StatusUnauthorized) - } else { - http.Redirect(w, r, redirectPath, http.StatusFound) - } + doRedirect("Your token is no longer valid", nil) return errInvalidToken } // a user with a partial token will be always redirected to the appropriate two factor auth page @@ -77,11 +73,13 @@ func validateJWTToken(w http.ResponseWriter, r *http.Request, audience tokenAudi } if !util.IsStringInSlice(audience, token.Audience()) { logger.Debug(logSender, "", "the token is not valid for audience %#v", audience) - if isAPIToken { - sendAPIResponse(w, r, nil, "Your token audience is not valid", http.StatusUnauthorized) - } else { - http.Redirect(w, r, redirectPath, http.StatusFound) - } + doRedirect("Your token audience is not valid", nil) + return errInvalidToken + } + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) + if ipAddr != "" && !strings.Contains(token.JwtID(), ipAddr) { + logger.Debug(logSender, "", "the token with id %#v is not valid for the ip address %#v", token.JwtID(), ipAddr) + doRedirect("Your token is not valid", nil) return errInvalidToken } return nil @@ -382,7 +380,8 @@ func authenticateAdminWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTA if !admin.Filters.AllowAPIKeyAuth { return fmt.Errorf("API key authentication disabled for admin %#v", admin.Username) } - if err := admin.CanLogin(util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil { + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) + if err := admin.CanLogin(ipAddr); err != nil { return err } c := jwtTokenClaims{ @@ -392,7 +391,7 @@ func authenticateAdminWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTA APIKeyID: keyID, } - resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPI) + resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPI, ipAddr) if err != nil { return err } @@ -446,7 +445,7 @@ func authenticateUserWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTAu APIKeyID: keyID, } - resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPIUser) + resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPIUser, ipAddr) if err != nil { updateLoginMetrics(&user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure) return err diff --git a/httpd/oidc.go b/httpd/oidc.go index a57aa88f..8774484f 100644 --- a/httpd/oidc.go +++ b/httpd/oidc.go @@ -496,7 +496,7 @@ func (s *httpdServer) oidcTokenAuthenticator(audience tokenAudience) func(next h Username: token.Username, Permissions: token.Permissions, } - _, tokenString, err := jwtTokenClaims.createToken(s.tokenAuth, audience) + _, tokenString, err := jwtTokenClaims.createToken(s.tokenAuth, audience, util.GetIPFromRemoteAddress(r.RemoteAddr)) if err != nil { setFlashMessage(w, r, "Unable to create cookie") if audience == tokenAudienceWebAdmin { diff --git a/httpd/oidc_test.go b/httpd/oidc_test.go index fded7f75..1f9f8cfc 100644 --- a/httpd/oidc_test.go +++ b/httpd/oidc_test.go @@ -666,7 +666,7 @@ func TestSkipOIDCAuth(t *testing.T) { jwtTokenClaims := jwtTokenClaims{ Username: "user", } - _, tokenString, err := jwtTokenClaims.createToken(server.tokenAuth, tokenAudienceWebClient) + _, tokenString, err := jwtTokenClaims.createToken(server.tokenAuth, tokenAudienceWebClient, "") assert.NoError(t, err) rr := httptest.NewRecorder() r, err := http.NewRequest(http.MethodGet, webClientLogoutPath, nil) diff --git a/httpd/server.go b/httpd/server.go index 35c93f65..d15cb475 100644 --- a/httpd/server.go +++ b/httpd/server.go @@ -413,6 +413,7 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter, s.renderTwoFactorRecoveryPage(w, "Two factory authentication is not enabled") return } + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) for idx, code := range admin.Filters.RecoveryCodes { if err := code.Secret.Decrypt(); err != nil { s.renderInternalServerErrorPage(w, r, fmt.Errorf("unable to decrypt recovery code: %w", err)) @@ -424,13 +425,13 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter, return } admin.Filters.RecoveryCodes[idx].Used = true - err = dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr)) + err = dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, ipAddr) if err != nil { logger.Warn(logSender, "", "unable to set the recovery code %#v as used: %v", recoveryCode, err) s.renderInternalServerErrorPage(w, r, errors.New("unable to set the recovery code as used")) return } - s.loginAdmin(w, r, &admin, true, s.renderTwoFactorRecoveryPage) + s.loginAdmin(w, r, &admin, true, s.renderTwoFactorRecoveryPage, ipAddr) return } } @@ -478,7 +479,7 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http s.renderTwoFactorPage(w, "Invalid authentication code") return } - s.loginAdmin(w, r, &admin, true, s.renderTwoFactorPage) + s.loginAdmin(w, r, &admin, true, s.renderTwoFactorPage, util.GetIPFromRemoteAddress(r.RemoteAddr)) } func (s *httpdServer) handleWebAdminLoginPost(w http.ResponseWriter, r *http.Request) { @@ -497,12 +498,13 @@ func (s *httpdServer) handleWebAdminLoginPost(w http.ResponseWriter, r *http.Req s.renderAdminLoginPage(w, err.Error()) return } - admin, err := dataprovider.CheckAdminAndPass(username, password, util.GetIPFromRemoteAddress(r.RemoteAddr)) + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) + admin, err := dataprovider.CheckAdminAndPass(username, password, ipAddr) if err != nil { s.renderAdminLoginPage(w, err.Error()) return } - s.loginAdmin(w, r, &admin, false, s.renderAdminLoginPage) + s.loginAdmin(w, r, &admin, false, s.renderAdminLoginPage, ipAddr) } func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, error string) { @@ -585,7 +587,7 @@ func (s *httpdServer) handleWebAdminPasswordResetPost(w http.ResponseWriter, r * return } - s.loginAdmin(w, r, admin, false, s.renderResetPwdPage) + s.loginAdmin(w, r, admin, false, s.renderResetPwdPage, util.GetIPFromRemoteAddress(r.RemoteAddr)) } func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Request) { @@ -629,12 +631,13 @@ func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Req Status: 1, Permissions: []string{dataprovider.PermAdminAny}, } - err = dataprovider.AddAdmin(&admin, username, util.GetIPFromRemoteAddress(r.RemoteAddr)) + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) + err = dataprovider.AddAdmin(&admin, username, ipAddr) if err != nil { s.renderAdminSetupPage(w, r, username, err.Error()) return } - s.loginAdmin(w, r, &admin, false, nil) + s.loginAdmin(w, r, &admin, false, nil, ipAddr) } func (s *httpdServer) loginUser( @@ -655,7 +658,7 @@ func (s *httpdServer) loginUser( audience = tokenAudienceWebClientPartial } - err := c.createAndSetCookie(w, r, s.tokenAuth, audience) + err := c.createAndSetCookie(w, r, s.tokenAuth, audience, ipAddr) if err != nil { logger.Warn(logSender, connectionID, "unable to set user login cookie %v", err) updateLoginMetrics(user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure) @@ -676,7 +679,7 @@ func (s *httpdServer) loginUser( func (s *httpdServer) loginAdmin( w http.ResponseWriter, r *http.Request, admin *dataprovider.Admin, - isSecondFactorAuth bool, errorFunc func(w http.ResponseWriter, error string), + isSecondFactorAuth bool, errorFunc func(w http.ResponseWriter, error string), ip string, ) { c := jwtTokenClaims{ Username: admin.Username, @@ -689,7 +692,7 @@ func (s *httpdServer) loginAdmin( audience = tokenAudienceWebAdminPartial } - err := c.createAndSetCookie(w, r, s.tokenAuth, audience) + err := c.createAndSetCookie(w, r, s.tokenAuth, audience, ip) if err != nil { logger.Warn(logSender, "", "unable to set admin login cookie %v", err) if errorFunc == nil { @@ -803,7 +806,7 @@ func (s *httpdServer) generateAndSendUserToken(w http.ResponseWriter, r *http.Re RequiredTwoFactorProtocols: user.Filters.TwoFactorAuthProtocols, } - resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPIUser) + resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPIUser, ipAddr) if err != nil { updateLoginMetrics(&user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure) @@ -823,7 +826,8 @@ func (s *httpdServer) getToken(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, nil, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } - admin, err := dataprovider.CheckAdminAndPass(username, password, util.GetIPFromRemoteAddress(r.RemoteAddr)) + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) + admin, err := dataprovider.CheckAdminAndPass(username, password, ipAddr) if err != nil { w.Header().Set(common.HTTPAuthenticationHeader, basicRealm) sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) @@ -854,17 +858,17 @@ func (s *httpdServer) getToken(w http.ResponseWriter, r *http.Request) { } } - s.generateAndSendToken(w, r, admin) + s.generateAndSendToken(w, r, admin, ipAddr) } -func (s *httpdServer) generateAndSendToken(w http.ResponseWriter, r *http.Request, admin dataprovider.Admin) { +func (s *httpdServer) generateAndSendToken(w http.ResponseWriter, r *http.Request, admin dataprovider.Admin, ip string) { c := jwtTokenClaims{ Username: admin.Username, Permissions: admin.Permissions, Signature: admin.GetSignature(), } - resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPI) + resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPI, ip) if err != nil { sendAPIResponse(w, r, err, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -914,7 +918,7 @@ func (s *httpdServer) refreshClientToken(w http.ResponseWriter, r *http.Request, tokenClaims.Permissions = user.Filters.WebClient logger.Debug(logSender, "", "cookie refreshed for user %#v", user.Username) - tokenClaims.createAndSetCookie(w, r, s.tokenAuth, tokenAudienceWebClient) //nolint:errcheck + tokenClaims.createAndSetCookie(w, r, s.tokenAuth, tokenAudienceWebClient, util.GetIPFromRemoteAddress(r.RemoteAddr)) //nolint:errcheck } func (s *httpdServer) refreshAdminToken(w http.ResponseWriter, r *http.Request, tokenClaims jwtTokenClaims) { @@ -930,13 +934,14 @@ func (s *httpdServer) refreshAdminToken(w http.ResponseWriter, r *http.Request, logger.Debug(logSender, "", "signature mismatch for admin %#v, unable to refresh cookie", admin.Username) return } - if !admin.CanLoginFromIP(util.GetIPFromRemoteAddress(r.RemoteAddr)) { + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) + if !admin.CanLoginFromIP(ipAddr) { logger.Debug(logSender, "", "admin %#v cannot login from %v, unable to refresh cookie", admin.Username, r.RemoteAddr) return } tokenClaims.Permissions = admin.Permissions logger.Debug(logSender, "", "cookie refreshed for admin %#v", admin.Username) - tokenClaims.createAndSetCookie(w, r, s.tokenAuth, tokenAudienceWebAdmin) //nolint:errcheck + tokenClaims.createAndSetCookie(w, r, s.tokenAuth, tokenAudienceWebAdmin, ipAddr) //nolint:errcheck } func (s *httpdServer) updateContextFromCookie(r *http.Request) *http.Request { diff --git a/sftpd/server.go b/sftpd/server.go index da89c527..e5fb509e 100644 --- a/sftpd/server.go +++ b/sftpd/server.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "net" @@ -509,7 +510,7 @@ func (c *Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Serve go func(in <-chan *ssh.Request, counter int64) { for req := range in { ok := false - connID := fmt.Sprintf("%v_%v", connectionID, counter) + connID := fmt.Sprintf("%s_%d", connectionID, counter) switch req.Type { case "subsystem": @@ -879,7 +880,7 @@ func (c *Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubK return nil, err } if !c.certChecker.IsUserAuthority(cert.SignatureKey) { - err = fmt.Errorf("ssh: certificate signed by unrecognized authority") + err = errors.New("ssh: certificate signed by unrecognized authority") user.Username = conn.User() updateLoginMetrics(&user, ipAddr, method, err) return nil, err