Compare commits
31 Commits
v1.11.1
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bef46f62c | ||
|
|
438e5fdbe9 | ||
|
|
a866493d0a | ||
|
|
c9d92f7bac | ||
|
|
aa8ffdfcf7 | ||
|
|
8d7e23f542 | ||
|
|
bd26d6eefd | ||
|
|
9441d2a523 | ||
|
|
4d51de71c2 | ||
|
|
4b66a222cd | ||
|
|
b665c943a2 | ||
|
|
a50e4ce9d1 | ||
|
|
c76d68bc96 | ||
|
|
02cb64e31b | ||
|
|
639ad76be3 | ||
|
|
a373bf8eda | ||
|
|
74b1c46b7f | ||
|
|
5424c6718e | ||
|
|
43b3496f00 | ||
|
|
38c69ebfda | ||
|
|
234e11b28b | ||
|
|
ec6d6d83ea | ||
|
|
4d9fee383f | ||
|
|
429ac62dba | ||
|
|
020abec6f2 | ||
|
|
dac9685298 | ||
|
|
519d38f238 | ||
|
|
1d17233d6d | ||
|
|
774c0af0b0 | ||
|
|
71fbd9271e | ||
|
|
2d640209e5 |
2
.github/workflows/codeql.yaml
vendored
2
.github/workflows/codeql.yaml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
|
||||
6
.github/workflows/docker.yaml
vendored
6
.github/workflows/docker.yaml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Push
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
# Always run against a tag, even if the commit into the tag has [docker skip] within the commit message.
|
||||
if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
||||
steps:
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and Push (tag)
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
if: "github.event_name == 'release' && github.event.action == 'published'"
|
||||
with:
|
||||
context: .
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
|
||||
- name: Build and Push (develop)
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
if: "github.event_name == 'push' && contains(github.ref, 'develop')"
|
||||
with:
|
||||
context: .
|
||||
|
||||
33
.github/workflows/push.yaml
vendored
33
.github/workflows/push.yaml
vendored
@@ -15,45 +15,20 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04]
|
||||
go: ["1.18.10", "1.19.5"]
|
||||
os: [ubuntu-22.04]
|
||||
go: ["1.19.11", "1.20.6"]
|
||||
goos: [linux]
|
||||
goarch: [amd64, arm64]
|
||||
|
||||
steps:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Code Checkout
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Gather environment variables
|
||||
id: env
|
||||
run: |
|
||||
printf "Go Executable Path: $(which go)\n"
|
||||
printf "Go Version: $(go version)\n"
|
||||
printf "\n\nGo Environment:\n\n"
|
||||
go env
|
||||
printf "\n\nSystem Environment:\n\n"
|
||||
env
|
||||
printf "Git Version: $(git version)\n\n"
|
||||
echo "version_tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "go_cache=$(go env GOCACHE)" >> $GITHUB_OUTPUT
|
||||
echo "go_mod_cache=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
path: |
|
||||
${{ steps.env.outputs.go_cache }}
|
||||
${{ steps.env.outputs.go_mod_cache }}
|
||||
|
||||
- name: go mod download
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -8,16 +8,16 @@ on:
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.18.10"
|
||||
go-version: "1.19.11"
|
||||
|
||||
- name: Build release binaries
|
||||
env:
|
||||
|
||||
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,5 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## v1.11.7
|
||||
### Changed
|
||||
* Updated Go dependencies (this resolves an issue related to `http: invalid Host header` with Docker)
|
||||
* Wings is now built with go1.19.11
|
||||
|
||||
## v1.11.6
|
||||
### Fixed
|
||||
* CVE-2023-32080
|
||||
|
||||
## v1.11.5
|
||||
### Added
|
||||
* Added a config option to disable Wings config.yml updates from the Panel (https://github.com/pterodactyl/wings/commit/ec6d6d83ea3eb14995c24f001233e85b37ffb87b)
|
||||
|
||||
### Changed
|
||||
* Wings is now built with Go 1.19.7
|
||||
|
||||
### Fixed
|
||||
* Fixed archives containing partially matched file names (https://github.com/pterodactyl/wings/commit/43b3496f0001cec231c80af1f9a9b3417d04e8d4)
|
||||
|
||||
## v1.11.4
|
||||
### Fixed
|
||||
* CVE-2023-25168
|
||||
|
||||
## v1.11.3
|
||||
### Fixed
|
||||
* CVE-2023-25152
|
||||
|
||||
## v1.11.2
|
||||
### Fixed
|
||||
* Backups being restored from remote storage (s3) erroring out due to a closed stream.
|
||||
* Fix IP validation logic for activity logs filtering out valid IPs instead of invalid IPs
|
||||
|
||||
## v1.11.1
|
||||
### Changed
|
||||
* Release binaries are now built with Go 1.18.10
|
||||
@@ -55,6 +87,18 @@
|
||||
* Archive progress is now reported correctly.
|
||||
* Labels for containers can now be set by the Panel.
|
||||
|
||||
## v1.7.5
|
||||
### Fixed
|
||||
* CVE-2023-32080
|
||||
|
||||
## v1.7.4
|
||||
### Fixed
|
||||
* CVE-2023-25168
|
||||
|
||||
## v1.7.3
|
||||
### Fixed
|
||||
* CVE-2023-25152
|
||||
|
||||
## v1.7.2
|
||||
### Fixed
|
||||
* The S3 backup driver now supports Cloudflare R2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Stage 1 (Build)
|
||||
FROM golang:1.18-alpine AS builder
|
||||
FROM golang:1.19-alpine AS builder
|
||||
|
||||
ARG VERSION
|
||||
RUN apk add --update --no-cache git make
|
||||
|
||||
@@ -15,21 +15,20 @@ dependencies, and allowing users to authenticate with the same credentials they
|
||||
|
||||
## Sponsors
|
||||
|
||||
I would like to extend my sincere thanks to the following sponsors for helping find Pterodactyl's developement.
|
||||
I would like to extend my sincere thanks to the following sponsors for helping find Pterodactyl's development.
|
||||
[Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi)
|
||||
|
||||
| Company | About |
|
||||
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [**WISP**](https://wisp.gg) | Extra features. |
|
||||
| [**Fragnet**](https://fragnet.net) | Providing low latency, high-end game hosting solutions to gamers, game studios and eSports platforms. |
|
||||
| [**RocketNode**](https://rocketnode.com/) | Innovative game server hosting combined with a straightforward control panel, affordable prices, and Rocket-Fast support. |
|
||||
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
||||
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
||||
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
||||
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
||||
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. |
|
||||
| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. |
|
||||
| [**UltraServers**](https://ultraservers.com/) | Deploy premium games hosting with the click of a button. Manage and swap games with ease and let us take care of the rest. We currently support Minecraft, Rust, ARK, 7 Days to Die, Garys MOD, CS:GO, Satisfactory and others. |
|
||||
| [**DutchIS**](https://dutchis.net?ref=pterodactyl) | DutchIS provides instant infrastructure such as pay per use VPS hosting. Start your game hosting journey on DutchIS. |
|
||||
| [**Skoali**](https://skoali.com/) | Skoali is a French company that hosts game servers and other types of services (VPS, WEB, Dedicated servers, ...). We also have a free plan for Minecraft and Garry's Mod. |
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -319,6 +319,9 @@ type Configuration struct {
|
||||
// is only required by users running Wings without SSL certificates and using internal IP
|
||||
// addresses in order to connect. Most users should NOT enable this setting.
|
||||
AllowCORSPrivateNetwork bool `json:"allow_cors_private_network" yaml:"allow_cors_private_network"`
|
||||
|
||||
// IgnorePanelConfigUpdates causes confiuration updates that are sent by the panel to be ignored.
|
||||
IgnorePanelConfigUpdates bool `json:"ignore_panel_config_updates" yaml:"ignore_panel_config_updates"`
|
||||
}
|
||||
|
||||
// NewAtPath creates a new struct and set the path where it should be stored.
|
||||
|
||||
@@ -181,10 +181,10 @@ func (e *Environment) Stop(ctx context.Context) error {
|
||||
// and using a different logic pathway to wait for the container to stop successfully.
|
||||
//
|
||||
// Using a negative timeout here will allow the container to stop gracefully,
|
||||
// rather than forcefully terminating it, this value MUST be at least 1
|
||||
// second, otherwise it will be ignored.
|
||||
timeout := -1 * time.Second
|
||||
if err := e.client.ContainerStop(ctx, e.Id, &timeout); err != nil {
|
||||
// rather than forcefully terminating it. Value is in seconds, but -1 is
|
||||
// treated as indefinitely.
|
||||
timeout := -1
|
||||
if err := e.client.ContainerStop(ctx, e.Id, container.StopOptions{Timeout: &timeout}); err != nil {
|
||||
// If the container does not exist just mark the process as stopped and return without
|
||||
// an error.
|
||||
if client.IsErrNotFound(err) {
|
||||
|
||||
142
go.mod
142
go.mod
@@ -4,85 +4,87 @@ go 1.18
|
||||
|
||||
require (
|
||||
emperror.dev/errors v0.8.1
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||
github.com/Jeffail/gabs/v2 v2.6.1
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/Jeffail/gabs/v2 v2.7.0
|
||||
github.com/NYTimes/logrotate v1.0.0
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/apex/log v1.9.0
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/beevik/etree v1.2.0
|
||||
github.com/buger/jsonparser v1.1.1
|
||||
github.com/cenkalti/backoff/v4 v4.1.3
|
||||
github.com/creasty/defaults v1.6.0
|
||||
github.com/docker/docker v20.10.18+incompatible
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/creasty/defaults v1.7.0
|
||||
github.com/docker/docker v24.0.5+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd
|
||||
github.com/gabriel-vasile/mimetype v1.4.1
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf
|
||||
github.com/gabriel-vasile/mimetype v1.4.2
|
||||
github.com/gammazero/workerpool v1.1.3
|
||||
github.com/gbrlsnchs/jwt/v3 v3.0.1
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/glebarez/sqlite v1.4.8
|
||||
github.com/go-co-op/gocron v1.17.0
|
||||
github.com/goccy/go-json v0.9.11
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/glebarez/sqlite v1.9.0
|
||||
github.com/go-co-op/gocron v1.30.1
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/icza/dyno v0.0.0-20220812133438-f0b6f8a18845
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0
|
||||
github.com/juju/ratelimit v1.0.2
|
||||
github.com/karrick/godirwalk v1.17.0
|
||||
github.com/klauspost/compress v1.15.11
|
||||
github.com/klauspost/pgzip v1.2.5
|
||||
github.com/magiconair/properties v1.8.6
|
||||
github.com/klauspost/compress v1.16.7
|
||||
github.com/klauspost/pgzip v1.2.6
|
||||
github.com/magiconair/properties v1.8.7
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.7
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/sftp v1.13.5
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/sync v0.3.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.23.10
|
||||
gorm.io/gorm v1.25.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/Microsoft/hcsshim v0.9.4 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/fifo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.10.0 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.4.3 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.10.0-rc2 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/gammazero/deque v0.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gammazero/deque v0.2.1 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.19.1 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/magefile/mage v1.14.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/magefile/mage v1.15.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@@ -90,35 +92,35 @@ require (
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/tools v0.11.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
modernc.org/libc v1.20.0 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.4.0 // indirect
|
||||
modernc.org/sqlite v1.19.1 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gotest.tools/v3 v3.0.2 // indirect
|
||||
modernc.org/libc v1.24.1 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.6.0 // indirect
|
||||
modernc.org/sqlite v1.24.0 // indirect
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ func (ac *activityCron) Run(ctx context.Context) error {
|
||||
for _, v := range activity {
|
||||
// Delete any activity that has an invalid IP address. This is a fix for
|
||||
// a bug that truncated the last octet of an IPv6 address in the database.
|
||||
if err := net.ParseIP(v.IP); err != nil {
|
||||
if ip := net.ParseIP(v.IP); ip == nil {
|
||||
ids = append(ids, v.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -147,7 +147,6 @@ func postServerRestoreBackup(c *gin.Context) {
|
||||
middleware.CaptureAndAbort(c, err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
// Don't allow content types that we know are going to give us problems.
|
||||
if res.Header.Get("Content-Type") == "" || !strings.Contains("application/x-gzip application/gzip", res.Header.Get("Content-Type")) {
|
||||
_ = res.Body.Close()
|
||||
|
||||
@@ -113,9 +113,21 @@ func postCreateServer(c *gin.Context) {
|
||||
c.Status(http.StatusAccepted)
|
||||
}
|
||||
|
||||
type postUpdateConfigurationResponse struct {
|
||||
Applied bool `json:"applied"`
|
||||
}
|
||||
|
||||
// Updates the running configuration for this Wings instance.
|
||||
func postUpdateConfiguration(c *gin.Context) {
|
||||
cfg := config.Get()
|
||||
|
||||
if cfg.IgnorePanelConfigUpdates {
|
||||
c.JSON(http.StatusOK, postUpdateConfigurationResponse{
|
||||
Applied: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.BindJSON(&cfg); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -139,5 +151,7 @@ func postUpdateConfiguration(c *gin.Context) {
|
||||
// Since we wrote it to the disk successfully now update the global configuration
|
||||
// state to use this new configuration struct.
|
||||
config.Set(cfg)
|
||||
c.Status(http.StatusNoContent)
|
||||
c.JSON(http.StatusOK, postUpdateConfigurationResponse{
|
||||
Applied: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package filesystem
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
@@ -66,6 +67,8 @@ type Archive struct {
|
||||
|
||||
// Files specifies the files to archive, this takes priority over the Ignore option, if
|
||||
// unspecified, all files in the BasePath will be archived unless Ignore is set.
|
||||
//
|
||||
// All items in Files must be absolute within BasePath.
|
||||
Files []string
|
||||
|
||||
// Progress wraps the writer of the archive to pass through the progress tracker.
|
||||
@@ -97,6 +100,14 @@ func (a *Archive) Create(ctx context.Context, dst string) error {
|
||||
|
||||
// Stream .
|
||||
func (a *Archive) Stream(ctx context.Context, w io.Writer) error {
|
||||
for _, f := range a.Files {
|
||||
if strings.HasPrefix(f, a.BasePath) {
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("archive: all entries in Files must be absolute and within BasePath: %s\n", f)
|
||||
}
|
||||
|
||||
// Choose which compression level to use based on the compression_level configuration option
|
||||
var compressionLevel int
|
||||
switch config.Get().System.Backups.CompressionLevel {
|
||||
@@ -190,9 +201,11 @@ func (a *Archive) callback(tw *TarProgress, opts ...func(path string, relative s
|
||||
func (a *Archive) withFilesCallback(tw *TarProgress) func(path string, de *godirwalk.Dirent) error {
|
||||
return a.callback(tw, func(p string, rp string) error {
|
||||
for _, f := range a.Files {
|
||||
// If the given doesn't match, or doesn't have the same prefix continue
|
||||
// to the next item in the loop.
|
||||
if p != f && !strings.HasPrefix(strings.TrimSuffix(p, "/")+"/", f) {
|
||||
// Allow exact file matches, otherwise check if file is within a parent directory.
|
||||
//
|
||||
// The slashes are added in the prefix checks to prevent partial name matches from being
|
||||
// included in the archive.
|
||||
if f != p && !strings.HasPrefix(strings.TrimSuffix(p, "/")+"/", strings.TrimSuffix(f, "/")+"/") {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
133
server/filesystem/archive_test.go
Normal file
133
server/filesystem/archive_test.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
iofs "io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/franela/goblin"
|
||||
"github.com/mholt/archiver/v4"
|
||||
)
|
||||
|
||||
func TestArchive_Stream(t *testing.T) {
|
||||
g := Goblin(t)
|
||||
fs, rfs := NewFs()
|
||||
|
||||
g.Describe("Archive", func() {
|
||||
g.AfterEach(func() {
|
||||
// Reset the filesystem after each run.
|
||||
rfs.reset()
|
||||
})
|
||||
|
||||
g.It("throws an error when passed invalid file paths", func() {
|
||||
a := &Archive{
|
||||
BasePath: fs.Path(),
|
||||
Files: []string{
|
||||
// To use the archiver properly, this needs to be filepath.Join(BasePath, "yeet")
|
||||
// However, this test tests that we actually validate that behavior.
|
||||
"yeet",
|
||||
},
|
||||
}
|
||||
|
||||
g.Assert(a.Create(context.Background(), "")).IsNotNil()
|
||||
})
|
||||
|
||||
g.It("creates archive with intended files", func() {
|
||||
g.Assert(fs.CreateDirectory("test", "/")).IsNil()
|
||||
g.Assert(fs.CreateDirectory("test2", "/")).IsNil()
|
||||
|
||||
err := fs.Writefile("test/file.txt", strings.NewReader("hello, world!\n"))
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
err = fs.Writefile("test2/file.txt", strings.NewReader("hello, world!\n"))
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
err = fs.Writefile("test_file.txt", strings.NewReader("hello, world!\n"))
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
err = fs.Writefile("test_file.txt.old", strings.NewReader("hello, world!\n"))
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
a := &Archive{
|
||||
BasePath: fs.Path(),
|
||||
Files: []string{
|
||||
filepath.Join(fs.Path(), "test"),
|
||||
filepath.Join(fs.Path(), "test_file.txt"),
|
||||
},
|
||||
}
|
||||
|
||||
// Create the archive.
|
||||
archivePath := filepath.Join(rfs.root, "archive.tar.gz")
|
||||
g.Assert(a.Create(context.Background(), archivePath)).IsNil()
|
||||
|
||||
// Ensure the archive exists.
|
||||
_, err = os.Stat(archivePath)
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Open the archive.
|
||||
genericFs, err := archiver.FileSystem(context.Background(), archivePath)
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Assert that we are opening an archive.
|
||||
afs, ok := genericFs.(archiver.ArchiveFS)
|
||||
g.Assert(ok).IsTrue()
|
||||
|
||||
// Get the names of the files recursively from the archive.
|
||||
files, err := getFiles(afs, ".")
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Ensure the files in the archive match what we are expecting.
|
||||
expected := []string{
|
||||
"test_file.txt",
|
||||
"test/file.txt",
|
||||
}
|
||||
|
||||
// Sort the slices to ensure the comparison never fails if the
|
||||
// contents are sorted differently.
|
||||
sort.Strings(expected)
|
||||
sort.Strings(files)
|
||||
|
||||
g.Assert(files).Equal(expected)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func getFiles(f iofs.ReadDirFS, name string) ([]string, error) {
|
||||
var v []string
|
||||
|
||||
entries, err := f.ReadDir(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, e := range entries {
|
||||
entryName := e.Name()
|
||||
if name != "." {
|
||||
entryName = filepath.Join(name, entryName)
|
||||
}
|
||||
|
||||
if e.IsDir() {
|
||||
files, err := getFiles(f, entryName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if files == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
v = append(v, f)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
v = append(v, entryName)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func (fs *Filesystem) SpaceAvailableForDecompression(ctx context.Context, dir st
|
||||
// waiting an unnecessary amount of time on this call.
|
||||
dirSize, err := fs.DiskUsage(false)
|
||||
|
||||
fsys, err := archiver.FileSystem(source)
|
||||
fsys, err := archiver.FileSystem(ctx, source)
|
||||
if err != nil {
|
||||
if errors.Is(err, archiver.ErrNoMatch) {
|
||||
return newFilesystemError(ErrCodeUnknownArchive, err)
|
||||
|
||||
@@ -165,7 +165,7 @@ func (fs *Filesystem) Writefile(p string, r io.Reader) error {
|
||||
// Adjust the disk usage to account for the old size and the new size of the file.
|
||||
fs.addDisk(sz - currentSize)
|
||||
|
||||
return fs.Chown(cleaned)
|
||||
return fs.unsafeChown(cleaned)
|
||||
}
|
||||
|
||||
// Creates a new directory (name) at a specified path (p) for the server.
|
||||
@@ -223,7 +223,12 @@ func (fs *Filesystem) Chown(path string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.unsafeChown(cleaned)
|
||||
}
|
||||
|
||||
// unsafeChown chowns the given path, without checking if the path is safe. This should only be used
|
||||
// when the path has already been checked.
|
||||
func (fs *Filesystem) unsafeChown(path string) error {
|
||||
if fs.isTest {
|
||||
return nil
|
||||
}
|
||||
@@ -232,19 +237,19 @@ func (fs *Filesystem) Chown(path string) error {
|
||||
gid := config.Get().System.User.Gid
|
||||
|
||||
// Start by just chowning the initial path that we received.
|
||||
if err := os.Chown(cleaned, uid, gid); err != nil {
|
||||
if err := os.Chown(path, uid, gid); err != nil {
|
||||
return errors.Wrap(err, "server/filesystem: chown: failed to chown path")
|
||||
}
|
||||
|
||||
// If this is not a directory we can now return from the function, there is nothing
|
||||
// left that we need to do.
|
||||
if st, err := os.Stat(cleaned); err != nil || !st.IsDir() {
|
||||
if st, err := os.Stat(path); err != nil || !st.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If this was a directory, begin walking over its contents recursively and ensure that all
|
||||
// of the subfiles and directories get their permissions updated as well.
|
||||
err = godirwalk.Walk(cleaned, &godirwalk.Options{
|
||||
err := godirwalk.Walk(path, &godirwalk.Options{
|
||||
Unsorted: true,
|
||||
Callback: func(p string, e *godirwalk.Dirent) error {
|
||||
// Do not attempt to chown a symlink. Go's os.Chown function will affect the symlink
|
||||
@@ -261,7 +266,6 @@ func (fs *Filesystem) Chown(path string) error {
|
||||
return os.Chown(p, uid, gid)
|
||||
},
|
||||
})
|
||||
|
||||
return errors.Wrap(err, "server/filesystem: chown: failed to chown during walk function")
|
||||
}
|
||||
|
||||
@@ -383,10 +387,9 @@ func (fs *Filesystem) TruncateRootDirectory() error {
|
||||
// Delete removes a file or folder from the system. Prevents the user from
|
||||
// accidentally (or maliciously) removing their root server data directory.
|
||||
func (fs *Filesystem) Delete(p string) error {
|
||||
wg := sync.WaitGroup{}
|
||||
// This is one of the few (only?) places in the codebase where we're explicitly not using
|
||||
// the SafePath functionality when working with user provided input. If we did, you would
|
||||
// not be able to delete a file that is a symlink pointing to a location outside of the data
|
||||
// not be able to delete a file that is a symlink pointing to a location outside the data
|
||||
// directory.
|
||||
//
|
||||
// We also want to avoid resolving a symlink that points _within_ the data directory and thus
|
||||
@@ -403,25 +406,65 @@ func (fs *Filesystem) Delete(p string) error {
|
||||
return errors.New("cannot delete root server directory")
|
||||
}
|
||||
|
||||
if st, err := os.Lstat(resolved); err != nil {
|
||||
st, err := os.Lstat(resolved)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
fs.error(err).Warn("error while attempting to stat file before deletion")
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if !st.IsDir() {
|
||||
fs.addDisk(-st.Size())
|
||||
} else {
|
||||
wg.Add(1)
|
||||
go func(wg *sync.WaitGroup, st os.FileInfo, resolved string) {
|
||||
defer wg.Done()
|
||||
if s, err := fs.DirectorySize(resolved); err == nil {
|
||||
fs.addDisk(-s)
|
||||
|
||||
// The following logic is used to handle a case where a user attempts to
|
||||
// delete a file that does not exist through a directory symlink.
|
||||
// We don't want to reveal that the file does not exist, so we validate
|
||||
// the path of the symlink and return a bad path error if it is invalid.
|
||||
|
||||
// The requested file or directory doesn't exist, so at this point we
|
||||
// need to iterate up the path chain until we hit a directory that
|
||||
// _does_ exist and can be validated.
|
||||
parts := strings.Split(filepath.Dir(resolved), "/")
|
||||
|
||||
// Range over all the path parts and form directory paths from the end
|
||||
// moving up until we have a valid resolution, or we run out of paths to
|
||||
// try.
|
||||
for k := range parts {
|
||||
try := strings.Join(parts[:(len(parts)-k)], "/")
|
||||
if !fs.unsafeIsInDataDirectory(try) {
|
||||
break
|
||||
}
|
||||
}(&wg, st, resolved)
|
||||
|
||||
t, err := filepath.EvalSymlinks(try)
|
||||
if err == nil {
|
||||
if !fs.unsafeIsInDataDirectory(t) {
|
||||
return NewBadPathResolution(p, t)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
// Always return early if the file does not exist.
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the file is not a symlink, we need to check that it is not within a
|
||||
// symlinked directory that points outside the data directory.
|
||||
if st.Mode()&os.ModeSymlink == 0 {
|
||||
ep, err := filepath.EvalSymlinks(resolved)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
} else if !fs.unsafeIsInDataDirectory(ep) {
|
||||
return NewBadPathResolution(p, ep)
|
||||
}
|
||||
}
|
||||
|
||||
if st.IsDir() {
|
||||
if s, err := fs.DirectorySize(resolved); err == nil {
|
||||
fs.addDisk(-s)
|
||||
}
|
||||
} else {
|
||||
fs.addDisk(-st.Size())
|
||||
}
|
||||
|
||||
return os.RemoveAll(resolved)
|
||||
}
|
||||
|
||||
@@ -537,6 +537,80 @@ func TestFilesystem_Delete(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
g.It("deletes a symlink but not it's target within the root directory", func() {
|
||||
// Symlink to a file inside the root directory.
|
||||
err := os.Symlink(filepath.Join(rfs.root, "server/source.txt"), filepath.Join(rfs.root, "server/symlink.txt"))
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Delete the symlink itself.
|
||||
err = fs.Delete("symlink.txt")
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Ensure the symlink was deleted.
|
||||
_, err = os.Lstat(filepath.Join(rfs.root, "server/symlink.txt"))
|
||||
g.Assert(err).IsNotNil()
|
||||
|
||||
// Ensure the symlink target still exists.
|
||||
_, err = os.Lstat(filepath.Join(rfs.root, "server/source.txt"))
|
||||
g.Assert(err).IsNil()
|
||||
})
|
||||
|
||||
g.It("does not delete files symlinked outside of the root directory", func() {
|
||||
// Create a file outside the root directory.
|
||||
err := rfs.CreateServerFileFromString("/../source.txt", "test content")
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Create a symlink to the file outside the root directory.
|
||||
err = os.Symlink(filepath.Join(rfs.root, "source.txt"), filepath.Join(rfs.root, "/server/symlink.txt"))
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Delete the symlink. (This should pass as we will delete the symlink itself, not it's target)
|
||||
err = fs.Delete("symlink.txt")
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Ensure the file outside the root directory still exists.
|
||||
_, err = os.Lstat(filepath.Join(rfs.root, "source.txt"))
|
||||
g.Assert(err).IsNil()
|
||||
})
|
||||
|
||||
g.It("does not delete files symlinked through a directory outside of the root directory", func() {
|
||||
// Create a directory outside the root directory.
|
||||
err := os.Mkdir(filepath.Join(rfs.root, "foo"), 0o755)
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Create a file inside the directory that is outside the root.
|
||||
err = rfs.CreateServerFileFromString("/../foo/source.txt", "test content")
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Symlink the directory that is outside the root to a file inside the root.
|
||||
err = os.Symlink(filepath.Join(rfs.root, "foo"), filepath.Join(rfs.root, "server/symlink"))
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Delete a file inside the symlinked directory.
|
||||
err = fs.Delete("symlink/source.txt")
|
||||
g.Assert(err).IsNotNil()
|
||||
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
|
||||
|
||||
// Ensure the file outside the root directory still exists.
|
||||
_, err = os.Lstat(filepath.Join(rfs.root, "foo/source.txt"))
|
||||
g.Assert(err).IsNil()
|
||||
})
|
||||
|
||||
g.It("returns an error when trying to delete a non-existent file symlinked through a directory outside of the root directory", func() {
|
||||
// Create a directory outside the root directory.
|
||||
err := os.Mkdir(filepath.Join(rfs.root, "foo2"), 0o755)
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Symlink the directory that is outside the root to a file inside the root.
|
||||
err = os.Symlink(filepath.Join(rfs.root, "foo2"), filepath.Join(rfs.root, "server/symlink"))
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Delete a file inside the symlinked directory.
|
||||
err = fs.Delete("symlink/source.txt")
|
||||
g.Assert(err).IsNotNil()
|
||||
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
|
||||
})
|
||||
|
||||
g.AfterEach(func() {
|
||||
rfs.reset()
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
iofs "io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -33,8 +34,6 @@ func (fs *Filesystem) IsIgnored(paths ...string) error {
|
||||
// This logic is actually copied over from the SFTP server code. Ideally that eventually
|
||||
// either gets ported into this application, or is able to make use of this package.
|
||||
func (fs *Filesystem) SafePath(p string) (string, error) {
|
||||
var nonExistentPathResolution string
|
||||
|
||||
// Start with a cleaned up path before checking the more complex bits.
|
||||
r := fs.unsafeFilePath(p)
|
||||
|
||||
@@ -44,47 +43,24 @@ func (fs *Filesystem) SafePath(p string) (string, error) {
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", errors.Wrap(err, "server/filesystem: failed to evaluate symlink")
|
||||
} else if os.IsNotExist(err) {
|
||||
// The requested directory doesn't exist, so at this point we need to iterate up the
|
||||
// path chain until we hit a directory that _does_ exist and can be validated.
|
||||
parts := strings.Split(filepath.Dir(r), "/")
|
||||
|
||||
var try string
|
||||
// Range over all of the path parts and form directory pathings from the end
|
||||
// moving up until we have a valid resolution or we run out of paths to try.
|
||||
for k := range parts {
|
||||
try = strings.Join(parts[:(len(parts)-k)], "/")
|
||||
|
||||
if !fs.unsafeIsInDataDirectory(try) {
|
||||
break
|
||||
// The target of one of the symlinks (EvalSymlinks is recursive) does not exist.
|
||||
// So we get what target path does not exist and check if it's within the data
|
||||
// directory. If it is, we return the original path, otherwise we return an error.
|
||||
pErr, ok := err.(*iofs.PathError)
|
||||
if !ok {
|
||||
return "", errors.Wrap(err, "server/filesystem: failed to evaluate symlink")
|
||||
}
|
||||
|
||||
t, err := filepath.EvalSymlinks(try)
|
||||
if err == nil {
|
||||
nonExistentPathResolution = t
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the new path doesn't start with their root directory there is clearly an escape
|
||||
// attempt going on, and we should NOT resolve this path for them.
|
||||
if nonExistentPathResolution != "" {
|
||||
if !fs.unsafeIsInDataDirectory(nonExistentPathResolution) {
|
||||
return "", NewBadPathResolution(p, nonExistentPathResolution)
|
||||
}
|
||||
|
||||
// If the nonExistentPathResolution variable is not empty then the initial path requested
|
||||
// did not exist and we looped through the pathway until we found a match. At this point
|
||||
// we've confirmed the first matched pathway exists in the root server directory, so we
|
||||
// can go ahead and just return the path that was requested initially.
|
||||
return r, nil
|
||||
ep = pErr.Path
|
||||
}
|
||||
|
||||
// If the requested directory from EvalSymlinks begins with the server root directory go
|
||||
// ahead and return it. If not we'll return an error which will block any further action
|
||||
// on the file.
|
||||
if fs.unsafeIsInDataDirectory(ep) {
|
||||
return ep, nil
|
||||
// Returning the original path here instead of the resolved path ensures that
|
||||
// whatever the user is trying to do will work as expected. If we returned the
|
||||
// resolved path, the user would be unable to know that it is in fact a symlink.
|
||||
return r, nil
|
||||
}
|
||||
|
||||
return "", NewBadPathResolution(p, r)
|
||||
|
||||
@@ -115,6 +115,14 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := os.Symlink(filepath.Join(rfs.root, "malicious_does_not_exist.txt"), filepath.Join(rfs.root, "/server/symlinked_does_not_exist.txt")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := os.Symlink(filepath.Join(rfs.root, "/server/symlinked_does_not_exist.txt"), filepath.Join(rfs.root, "/server/symlinked_does_not_exist2.txt")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := os.Symlink(filepath.Join(rfs.root, "/malicious_dir"), filepath.Join(rfs.root, "/server/external_dir")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -128,6 +136,22 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
|
||||
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
|
||||
})
|
||||
|
||||
g.It("cannot write to a non-existent file symlinked outside the root", func() {
|
||||
r := bytes.NewReader([]byte("testing what the fuck"))
|
||||
|
||||
err := fs.Writefile("symlinked_does_not_exist.txt", r)
|
||||
g.Assert(err).IsNotNil()
|
||||
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
|
||||
})
|
||||
|
||||
g.It("cannot write to chained symlinks with target that does not exist outside the root", func() {
|
||||
r := bytes.NewReader([]byte("testing what the fuck"))
|
||||
|
||||
err := fs.Writefile("symlinked_does_not_exist2.txt", r)
|
||||
g.Assert(err).IsNotNil()
|
||||
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
|
||||
})
|
||||
|
||||
g.It("cannot write a file to a directory symlinked outside the root", func() {
|
||||
r := bytes.NewReader([]byte("testing"))
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"os"
|
||||
@@ -427,10 +426,6 @@ func (ip *InstallationProcess) Execute() (string, error) {
|
||||
}
|
||||
|
||||
cfg := config.Get()
|
||||
if cfg.System.User.Rootless.Enabled {
|
||||
conf.User = fmt.Sprintf("%d:%d", cfg.System.User.Rootless.ContainerUID, cfg.System.User.Rootless.ContainerGID)
|
||||
}
|
||||
|
||||
tmpfsSize := strconv.Itoa(int(cfg.Docker.TmpfsSize))
|
||||
hostConf := &container.HostConfig{
|
||||
Mounts: []mount.Mount{
|
||||
@@ -453,7 +448,6 @@ func (ip *InstallationProcess) Execute() (string, error) {
|
||||
},
|
||||
DNS: cfg.Docker.Network.Dns,
|
||||
LogConfig: cfg.Docker.ContainerLogConfig(),
|
||||
Privileged: true,
|
||||
NetworkMode: container.NetworkMode(cfg.Docker.Network.Mode),
|
||||
UsernsMode: container.UsernsMode(cfg.Docker.UsernsMode),
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package system
|
||||
|
||||
var Version = "develop"
|
||||
var Version = "1.11.7"
|
||||
|
||||
@@ -15,7 +15,16 @@ func MutexLocked(m *sync.RWMutex) bool {
|
||||
|
||||
state := v.FieldByName("w").FieldByName("state")
|
||||
|
||||
return state.Int()&1 == 1 || v.FieldByName("readerCount").Int() > 0
|
||||
readerCountField := v.FieldByName("readerCount")
|
||||
// go1.20 changed readerCount to an atomic
|
||||
// ref; https://github.com/golang/go/commit/e509452727b469d89a3fc4a7d1cbf9d3f110efee
|
||||
var readerCount int64
|
||||
if readerCountField.Kind() == reflect.Struct {
|
||||
readerCount = readerCountField.FieldByName("v").Int()
|
||||
} else {
|
||||
readerCount = readerCountField.Int()
|
||||
}
|
||||
return state.Int()&1 == 1 || readerCount > 0
|
||||
}
|
||||
|
||||
func TestSink(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user