Browse Source

check that the jwt token is used by the same IP for which it

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino 3 years ago
parent
commit
d955ddcef9
13 changed files with 154 additions and 118 deletions
  1. 1 1
      .github/workflows/development.yml
  2. 1 1
      .github/workflows/release.yml
  3. 1 4
      README.md
  4. 18 18
      go.mod
  5. 36 36
      go.sum
  6. 8 6
      httpd/auth_utils.go
  7. 33 1
      httpd/httpd_test.go
  8. 7 7
      httpd/internal_test.go
  9. 20 21
      httpd/middleware.go
  10. 1 1
      httpd/oidc.go
  11. 1 1
      httpd/oidc_test.go
  12. 24 19
      httpd/server.go
  13. 3 2
      sftpd/server.go

+ 1 - 1
.github/workflows/development.yml

@@ -382,7 +382,7 @@ jobs:
           gzip output/man/man1/*
           gzip output/man/man1/*
           cp sftpgo output/
           cp sftpgo output/
 
 
-      - uses: uraimo/run-on-arch-action@v2.1.1
+      - uses: uraimo/run-on-arch-action@v2
         if: ${{ matrix.arch != 'amd64' }}
         if: ${{ matrix.arch != 'amd64' }}
         name: Build for ${{ matrix.arch }}
         name: Build for ${{ matrix.arch }}
         id: build
         id: build

+ 1 - 1
.github/workflows/release.yml

@@ -326,7 +326,7 @@ jobs:
         env:
         env:
           SFTPGO_VERSION: ${{ steps.get_version.outputs.SFTPGO_VERSION }}
           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' }}
         if: ${{ matrix.arch != 'amd64' }}
         name: Build for ${{ matrix.arch }}
         name: Build for ${{ matrix.arch }}
         id: build
         id: build

+ 1 - 4
README.md

@@ -11,8 +11,6 @@ Several storage backends are supported: local filesystem, encrypted local filesy
 
 
 ## Features
 ## Features
 
 
-<details>
-
 - 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.
 - 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.
 - 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.
 - 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.
 - 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.
 - Automatically terminating idle connections.
 - Automatic blocklist management using the built-in [defender](./docs/defender.md).
 - 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.
 - 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).
 - 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.
 - 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)).
 - 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.
 - SFTPGo supports a [plugin system](./docs/plugins.md) and therefore can be extended using external plugins.
 
 
-</details>
-
 ## Platforms
 ## 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.
 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.

+ 18 - 18
go.mod

@@ -8,12 +8,12 @@ require (
 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0
 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0
 	github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
 	github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
 	github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
 	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/cockroachdb/cockroach-go/v2 v2.2.8
 	github.com/coreos/go-oidc/v3 v3.1.0
 	github.com/coreos/go-oidc/v3 v3.1.0
 	github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
 	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/compute v1.5.0 // indirect
 	cloud.google.com/go/iam v0.3.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/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/beorn7/perks v1.0.1 // indirect
 	github.com/boombuler/barcode v1.0.1 // indirect
 	github.com/boombuler/barcode v1.0.1 // indirect
 	github.com/cenkalti/backoff v2.2.1+incompatible // 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/tools v0.1.10 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/appengine v1.6.7 // 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/grpc v1.45.0 // indirect
 	google.golang.org/protobuf v1.28.0 // indirect
 	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect

+ 36 - 36
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.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 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.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.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.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.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.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.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/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/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/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.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.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.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/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 v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 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-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-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-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.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=

+ 8 - 6
httpd/auth_utils.go

@@ -159,11 +159,11 @@ func (c *jwtTokenClaims) hasPerm(perm string) bool {
 	return util.IsStringInSlice(perm, c.Permissions)
 	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()
 	claims := c.asMap()
 	now := time.Now().UTC()
 	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.NotBeforeKey] = now.Add(-30 * time.Second)
 	claims[jwt.ExpirationKey] = now.Add(tokenDuration)
 	claims[jwt.ExpirationKey] = now.Add(tokenDuration)
 	claims[jwt.AudienceKey] = audience
 	claims[jwt.AudienceKey] = audience
@@ -171,8 +171,8 @@ func (c *jwtTokenClaims) createToken(tokenAuth *jwtauth.JWTAuth, audience tokenA
 	return tokenAuth.Encode(claims)
 	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 {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -184,8 +184,10 @@ func (c *jwtTokenClaims) createTokenResponse(tokenAuth *jwtauth.JWTAuth, audienc
 	return response, nil
 	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 {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 33 - 1
httpd/httpd_test.go

@@ -8713,6 +8713,38 @@ func TestWebClientMaxConnections(t *testing.T) {
 	common.Config.MaxTotalConnections = oldValue
 	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) {
 func TestDefender(t *testing.T) {
 	oldConfig := config.GetCommonConfig()
 	oldConfig := config.GetCommonConfig()
 
 
@@ -17500,7 +17532,7 @@ func getJWTAPIUserTokenFromTestServer(username, password string) (string, error)
 	req.SetBasicAuth(username, password)
 	req.SetBasicAuth(username, password)
 	rr := executeRequest(req)
 	rr := executeRequest(req)
 	if rr.Code != http.StatusOK {
 	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{})
 	responseHolder := make(map[string]interface{})
 	err := render.DecodeJSON(rr.Body, &responseHolder)
 	err := render.DecodeJSON(rr.Body, &responseHolder)

+ 7 - 7
httpd/internal_test.go

@@ -629,7 +629,7 @@ func TestUpdateWebAdminInvalidClaims(t *testing.T) {
 		Permissions: admin.Permissions,
 		Permissions: admin.Permissions,
 		Signature:   admin.GetSignature(),
 		Signature:   admin.GetSignature(),
 	}
 	}
-	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin)
+	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin, "")
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
 	form := make(url.Values)
 	form := make(url.Values)
@@ -746,7 +746,7 @@ func TestCreateTokenError(t *testing.T) {
 	}
 	}
 	req, _ := http.NewRequest(http.MethodGet, tokenPath, nil)
 	req, _ := http.NewRequest(http.MethodGet, tokenPath, nil)
 
 
-	server.generateAndSendToken(rr, req, admin)
+	server.generateAndSendToken(rr, req, admin, "")
 	assert.Equal(t, http.StatusInternalServerError, rr.Code)
 	assert.Equal(t, http.StatusInternalServerError, rr.Code)
 
 
 	rr = httptest.NewRecorder()
 	rr = httptest.NewRecorder()
@@ -778,7 +778,7 @@ func TestCreateTokenError(t *testing.T) {
 	assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
 	assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
 	req, _ = http.NewRequest(http.MethodPost, webAdminSetupPath, nil)
 	req, _ = http.NewRequest(http.MethodPost, webAdminSetupPath, nil)
 	rr = httptest.NewRecorder()
 	rr = httptest.NewRecorder()
-	server.loginAdmin(rr, req, &admin, false, nil)
+	server.loginAdmin(rr, req, &admin, false, nil, "")
 	// req with no POST body
 	// req with no POST body
 	req, _ = http.NewRequest(http.MethodGet, webAdminLoginPath+"?a=a%C3%AO%GG", nil)
 	req, _ = http.NewRequest(http.MethodGet, webAdminLoginPath+"?a=a%C3%AO%GG", nil)
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
@@ -1991,7 +1991,7 @@ func TestWebUserInvalidClaims(t *testing.T) {
 		Permissions: nil,
 		Permissions: nil,
 		Signature:   user.GetSignature(),
 		Signature:   user.GetSignature(),
 	}
 	}
-	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient)
+	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient, "")
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
 	req, _ := http.NewRequest(http.MethodGet, webClientFilesPath, nil)
 	req, _ := http.NewRequest(http.MethodGet, webClientFilesPath, nil)
@@ -2066,7 +2066,7 @@ func TestInvalidClaims(t *testing.T) {
 		Permissions: nil,
 		Permissions: nil,
 		Signature:   user.GetSignature(),
 		Signature:   user.GetSignature(),
 	}
 	}
-	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient)
+	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient, "")
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	form := make(url.Values)
 	form := make(url.Values)
 	form.Set(csrfFormToken, createCSRFToken())
 	form.Set(csrfFormToken, createCSRFToken())
@@ -2086,7 +2086,7 @@ func TestInvalidClaims(t *testing.T) {
 		Permissions: nil,
 		Permissions: nil,
 		Signature:   admin.GetSignature(),
 		Signature:   admin.GetSignature(),
 	}
 	}
-	token, err = c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin)
+	token, err = c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin, "")
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	form = make(url.Values)
 	form = make(url.Values)
 	form.Set(csrfFormToken, createCSRFToken())
 	form.Set(csrfFormToken, createCSRFToken())
@@ -2134,7 +2134,7 @@ func TestSigningKey(t *testing.T) {
 		Permissions: nil,
 		Permissions: nil,
 		Signature:   user.GetSignature(),
 		Signature:   user.GetSignature(),
 	}
 	}
-	token, err := c.createTokenResponse(server1.tokenAuth, tokenAudienceWebClient)
+	token, err := c.createTokenResponse(server1.tokenAuth, tokenAudienceWebClient, "")
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	accessToken := token["access_token"].(string)
 	accessToken := token["access_token"].(string)
 	assert.NotEmpty(t, accessToken)
 	assert.NotEmpty(t, accessToken)

+ 20 - 21
httpd/middleware.go

@@ -42,33 +42,29 @@ func validateJWTToken(w http.ResponseWriter, r *http.Request, audience tokenAudi
 
 
 	isAPIToken := (audience == tokenAudienceAPI || audience == tokenAudienceAPIUser)
 	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 {
 		if isAPIToken {
-			sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
+			sendAPIResponse(w, r, err, message, http.StatusUnauthorized)
 		} else {
 		} else {
 			http.Redirect(w, r, redirectPath, http.StatusFound)
 			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
 		return errInvalidToken
 	}
 	}
 
 
 	err = jwt.Validate(token)
 	err = jwt.Validate(token)
 	if err != nil {
 	if err != nil {
 		logger.Debug(logSender, "", "error validating jwt token: %v", err)
 		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
 		return errInvalidToken
 	}
 	}
 	if isTokenInvalidated(r) {
 	if isTokenInvalidated(r) {
 		logger.Debug(logSender, "", "the token has been invalidated")
 		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
 		return errInvalidToken
 	}
 	}
 	// a user with a partial token will be always redirected to the appropriate two factor auth page
 	// 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()) {
 	if !util.IsStringInSlice(audience, token.Audience()) {
 		logger.Debug(logSender, "", "the token is not valid for audience %#v", 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 errInvalidToken
 	}
 	}
 	return nil
 	return nil
@@ -382,7 +380,8 @@ func authenticateAdminWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTA
 	if !admin.Filters.AllowAPIKeyAuth {
 	if !admin.Filters.AllowAPIKeyAuth {
 		return fmt.Errorf("API key authentication disabled for admin %#v", admin.Username)
 		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
 		return err
 	}
 	}
 	c := jwtTokenClaims{
 	c := jwtTokenClaims{
@@ -392,7 +391,7 @@ func authenticateAdminWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTA
 		APIKeyID:    keyID,
 		APIKeyID:    keyID,
 	}
 	}
 
 
-	resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPI)
+	resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPI, ipAddr)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -446,7 +445,7 @@ func authenticateUserWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTAu
 		APIKeyID:    keyID,
 		APIKeyID:    keyID,
 	}
 	}
 
 
-	resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPIUser)
+	resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPIUser, ipAddr)
 	if err != nil {
 	if err != nil {
 		updateLoginMetrics(&user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure)
 		updateLoginMetrics(&user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure)
 		return err
 		return err

+ 1 - 1
httpd/oidc.go

@@ -496,7 +496,7 @@ func (s *httpdServer) oidcTokenAuthenticator(audience tokenAudience) func(next h
 				Username:    token.Username,
 				Username:    token.Username,
 				Permissions: token.Permissions,
 				Permissions: token.Permissions,
 			}
 			}
-			_, tokenString, err := jwtTokenClaims.createToken(s.tokenAuth, audience)
+			_, tokenString, err := jwtTokenClaims.createToken(s.tokenAuth, audience, util.GetIPFromRemoteAddress(r.RemoteAddr))
 			if err != nil {
 			if err != nil {
 				setFlashMessage(w, r, "Unable to create cookie")
 				setFlashMessage(w, r, "Unable to create cookie")
 				if audience == tokenAudienceWebAdmin {
 				if audience == tokenAudienceWebAdmin {

+ 1 - 1
httpd/oidc_test.go

@@ -666,7 +666,7 @@ func TestSkipOIDCAuth(t *testing.T) {
 	jwtTokenClaims := jwtTokenClaims{
 	jwtTokenClaims := jwtTokenClaims{
 		Username: "user",
 		Username: "user",
 	}
 	}
-	_, tokenString, err := jwtTokenClaims.createToken(server.tokenAuth, tokenAudienceWebClient)
+	_, tokenString, err := jwtTokenClaims.createToken(server.tokenAuth, tokenAudienceWebClient, "")
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	rr := httptest.NewRecorder()
 	rr := httptest.NewRecorder()
 	r, err := http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
 	r, err := http.NewRequest(http.MethodGet, webClientLogoutPath, nil)

+ 24 - 19
httpd/server.go

@@ -413,6 +413,7 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter,
 		s.renderTwoFactorRecoveryPage(w, "Two factory authentication is not enabled")
 		s.renderTwoFactorRecoveryPage(w, "Two factory authentication is not enabled")
 		return
 		return
 	}
 	}
+	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 	for idx, code := range admin.Filters.RecoveryCodes {
 	for idx, code := range admin.Filters.RecoveryCodes {
 		if err := code.Secret.Decrypt(); err != nil {
 		if err := code.Secret.Decrypt(); err != nil {
 			s.renderInternalServerErrorPage(w, r, fmt.Errorf("unable to decrypt recovery code: %w", err))
 			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
 				return
 			}
 			}
 			admin.Filters.RecoveryCodes[idx].Used = true
 			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 {
 			if err != nil {
 				logger.Warn(logSender, "", "unable to set the recovery code %#v as used: %v", recoveryCode, err)
 				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"))
 				s.renderInternalServerErrorPage(w, r, errors.New("unable to set the recovery code as used"))
 				return
 				return
 			}
 			}
-			s.loginAdmin(w, r, &admin, true, s.renderTwoFactorRecoveryPage)
+			s.loginAdmin(w, r, &admin, true, s.renderTwoFactorRecoveryPage, ipAddr)
 			return
 			return
 		}
 		}
 	}
 	}
@@ -478,7 +479,7 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http
 		s.renderTwoFactorPage(w, "Invalid authentication code")
 		s.renderTwoFactorPage(w, "Invalid authentication code")
 		return
 		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) {
 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())
 		s.renderAdminLoginPage(w, err.Error())
 		return
 		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 {
 	if err != nil {
 		s.renderAdminLoginPage(w, err.Error())
 		s.renderAdminLoginPage(w, err.Error())
 		return
 		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) {
 func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, error string) {
@@ -585,7 +587,7 @@ func (s *httpdServer) handleWebAdminPasswordResetPost(w http.ResponseWriter, r *
 		return
 		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) {
 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,
 		Status:      1,
 		Permissions: []string{dataprovider.PermAdminAny},
 		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 {
 	if err != nil {
 		s.renderAdminSetupPage(w, r, username, err.Error())
 		s.renderAdminSetupPage(w, r, username, err.Error())
 		return
 		return
 	}
 	}
-	s.loginAdmin(w, r, &admin, false, nil)
+	s.loginAdmin(w, r, &admin, false, nil, ipAddr)
 }
 }
 
 
 func (s *httpdServer) loginUser(
 func (s *httpdServer) loginUser(
@@ -655,7 +658,7 @@ func (s *httpdServer) loginUser(
 		audience = tokenAudienceWebClientPartial
 		audience = tokenAudienceWebClientPartial
 	}
 	}
 
 
-	err := c.createAndSetCookie(w, r, s.tokenAuth, audience)
+	err := c.createAndSetCookie(w, r, s.tokenAuth, audience, ipAddr)
 	if err != nil {
 	if err != nil {
 		logger.Warn(logSender, connectionID, "unable to set user login cookie %v", err)
 		logger.Warn(logSender, connectionID, "unable to set user login cookie %v", err)
 		updateLoginMetrics(user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure)
 		updateLoginMetrics(user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure)
@@ -676,7 +679,7 @@ func (s *httpdServer) loginUser(
 
 
 func (s *httpdServer) loginAdmin(
 func (s *httpdServer) loginAdmin(
 	w http.ResponseWriter, r *http.Request, admin *dataprovider.Admin,
 	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{
 	c := jwtTokenClaims{
 		Username:    admin.Username,
 		Username:    admin.Username,
@@ -689,7 +692,7 @@ func (s *httpdServer) loginAdmin(
 		audience = tokenAudienceWebAdminPartial
 		audience = tokenAudienceWebAdminPartial
 	}
 	}
 
 
-	err := c.createAndSetCookie(w, r, s.tokenAuth, audience)
+	err := c.createAndSetCookie(w, r, s.tokenAuth, audience, ip)
 	if err != nil {
 	if err != nil {
 		logger.Warn(logSender, "", "unable to set admin login cookie %v", err)
 		logger.Warn(logSender, "", "unable to set admin login cookie %v", err)
 		if errorFunc == nil {
 		if errorFunc == nil {
@@ -803,7 +806,7 @@ func (s *httpdServer) generateAndSendUserToken(w http.ResponseWriter, r *http.Re
 		RequiredTwoFactorProtocols: user.Filters.TwoFactorAuthProtocols,
 		RequiredTwoFactorProtocols: user.Filters.TwoFactorAuthProtocols,
 	}
 	}
 
 
-	resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPIUser)
+	resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPIUser, ipAddr)
 
 
 	if err != nil {
 	if err != nil {
 		updateLoginMetrics(&user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure)
 		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)
 		sendAPIResponse(w, r, nil, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
 		return
 		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 {
 	if err != nil {
 		w.Header().Set(common.HTTPAuthenticationHeader, basicRealm)
 		w.Header().Set(common.HTTPAuthenticationHeader, basicRealm)
 		sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
 		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{
 	c := jwtTokenClaims{
 		Username:    admin.Username,
 		Username:    admin.Username,
 		Permissions: admin.Permissions,
 		Permissions: admin.Permissions,
 		Signature:   admin.GetSignature(),
 		Signature:   admin.GetSignature(),
 	}
 	}
 
 
-	resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPI)
+	resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPI, ip)
 
 
 	if err != nil {
 	if err != nil {
 		sendAPIResponse(w, r, err, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
 		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
 	tokenClaims.Permissions = user.Filters.WebClient
 	logger.Debug(logSender, "", "cookie refreshed for user %#v", user.Username)
 	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) {
 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)
 		logger.Debug(logSender, "", "signature mismatch for admin %#v, unable to refresh cookie", admin.Username)
 		return
 		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)
 		logger.Debug(logSender, "", "admin %#v cannot login from %v, unable to refresh cookie", admin.Username, r.RemoteAddr)
 		return
 		return
 	}
 	}
 	tokenClaims.Permissions = admin.Permissions
 	tokenClaims.Permissions = admin.Permissions
 	logger.Debug(logSender, "", "cookie refreshed for admin %#v", admin.Username)
 	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 {
 func (s *httpdServer) updateContextFromCookie(r *http.Request) *http.Request {

+ 3 - 2
sftpd/server.go

@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/hex"
 	"encoding/hex"
 	"encoding/json"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"net"
 	"net"
@@ -509,7 +510,7 @@ func (c *Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Serve
 		go func(in <-chan *ssh.Request, counter int64) {
 		go func(in <-chan *ssh.Request, counter int64) {
 			for req := range in {
 			for req := range in {
 				ok := false
 				ok := false
-				connID := fmt.Sprintf("%v_%v", connectionID, counter)
+				connID := fmt.Sprintf("%s_%d", connectionID, counter)
 
 
 				switch req.Type {
 				switch req.Type {
 				case "subsystem":
 				case "subsystem":
@@ -879,7 +880,7 @@ func (c *Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubK
 			return nil, err
 			return nil, err
 		}
 		}
 		if !c.certChecker.IsUserAuthority(cert.SignatureKey) {
 		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()
 			user.Username = conn.User()
 			updateLoginMetrics(&user, ipAddr, method, err)
 			updateLoginMetrics(&user, ipAddr, method, err)
 			return nil, err
 			return nil, err