Compare commits

..

1 Commits

Author SHA1 Message Date
Pterodactyl CI
1040b7d8ef bump version for release 2022-11-22 20:54:03 +00:00
38 changed files with 1235 additions and 804 deletions

View File

@@ -13,7 +13,7 @@ on:
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
runs-on: ubuntu-22.04 runs-on: ubuntu-20.04
permissions: permissions:
actions: read actions: read

View File

@@ -4,36 +4,30 @@ on:
push: push:
branches: branches:
- develop - develop
release: tags:
types: - "v*"
- published
jobs: jobs:
build-and-push: build-and-push:
name: Build and Push name: Build and Push
runs-on: ubuntu-22.04 runs-on: ubuntu-20.04
# Always run against a tag, even if the commit into the tag has [docker skip] within the commit message. # 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'))" if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
steps: steps:
- name: Code checkout - name: Code Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Docker metadata - name: Docker Meta
id: docker_meta id: docker_meta
uses: docker/metadata-action@v4 uses: crazy-max/ghaction-docker-meta@v1
with: with:
images: ghcr.io/pterodactyl/wings images: ghcr.io/pterodactyl/wings
flavor: |
latest=false
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.action == 'published' && github.event.release.prerelease == false }}
type=ref,event=tag
type=ref,event=branch
- name: Setup QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Setup Docker buildx - name: Install buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
@@ -46,12 +40,12 @@ jobs:
- name: Get Build Information - name: Get Build Information
id: build_info id: build_info
run: | run: |
echo "version_tag=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT echo "::set-output name=version_tag::${GITHUB_REF/refs\/tags\/v/}"
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
- name: Build and Push (tag) - name: Build and push (latest)
uses: docker/build-push-action@v4 uses: docker/build-push-action@v2
if: "github.event_name == 'release' && github.event.action == 'published'" if: "!contains(github.ref, 'develop')"
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
@@ -62,9 +56,9 @@ jobs:
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.docker_meta.outputs.labels }}
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.docker_meta.outputs.tags }}
- name: Build and Push (develop) - name: Build and push (develop)
uses: docker/build-push-action@v4 uses: docker/build-push-action@v2
if: "github.event_name == 'push' && contains(github.ref, 'develop')" if: "contains(github.ref, 'develop')"
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile

View File

@@ -15,20 +15,45 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-22.04] os: [ubuntu-20.04]
go: ["1.19.11", "1.20.6"] go: ["1.18.8", "1.19.3"]
goos: [linux] goos: [linux]
goarch: [amd64, arm64] goarch: [amd64, arm64]
steps: steps:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v4 uses: actions/setup-go@v3
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
- name: Code checkout - name: Code Checkout
uses: actions/checkout@v3 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 - name: go mod download
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
@@ -61,14 +86,14 @@ jobs:
go test -race $(go list ./...) go test -race $(go list ./...)
- name: Upload Release Artifact - name: Upload Release Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }} if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }}
with: with:
name: wings_linux_${{ matrix.goarch }} name: wings_linux_${{ matrix.goarch }}
path: dist/wings path: dist/wings
- name: Upload Debug Artifact - name: Upload Debug Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }} if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }}
with: with:
name: wings_linux_${{ matrix.goarch }}_debug name: wings_linux_${{ matrix.goarch }}_debug

View File

@@ -8,16 +8,16 @@ on:
jobs: jobs:
release: release:
name: Release name: Release
runs-on: ubuntu-22.04 runs-on: ubuntu-20.04
steps: steps:
- name: Code Checkout - name: Code Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v4 uses: actions/setup-go@v3
with: with:
go-version: "1.19.11" go-version: "1.18.8"
- name: Build release binaries - name: Build release binaries
env: env:
@@ -34,6 +34,7 @@ jobs:
REF: ${{ github.ref }} REF: ${{ github.ref }}
run: | run: |
sed -n "/^## ${REF:10}/,/^## /{/^## /b;p}" CHANGELOG.md > ./RELEASE_CHANGELOG sed -n "/^## ${REF:10}/,/^## /{/^## /b;p}" CHANGELOG.md > ./RELEASE_CHANGELOG
echo "version_name=`sed -nr "s/^## (${REF:10} .*)$/\1/p" CHANGELOG.md`" > $GITHUB_OUTPUT
- name: Create checksum and add to changelog - name: Create checksum and add to changelog
run: | run: |
@@ -58,13 +59,15 @@ jobs:
- name: Create release - name: Create release
id: create_release id: create_release
uses: softprops/action-gh-release@v1 uses: actions/create-release@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
draft: true tag_name: ${{ github.ref }}
prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }} release_name: ${{ steps.extract_changelog.outputs.version_name }}
body_path: ./RELEASE_CHANGELOG body_path: ./RELEASE_CHANGELOG
draft: true
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
- name: Upload amd64 binary - name: Upload amd64 binary
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1

View File

@@ -1,69 +1,5 @@
# Changelog # 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
* Timeout when stopping a server before a transfer begins has been reduced to 15 seconds from 1 minute
* Removed insecure SSH protocols for use with the SFTP server
### Fixed
* Unnecessary Docker client connections being left open, causing a slow leak of file descriptors
* Files being left open in parts of the server's filesystem, causing a leak of file descriptors
* IPv6 addresses being corrupted by flawed port stripping logic for activity logs, old entries with malformed IPs will be deleted from the local SQLite database automatically
* A server that times out while being stopped at the beginning of a transfer no longer causes the server to become stuck in a transferring state
## v1.11.0
### Added (since 1.7.2)
* More detailed information returned by the `/api/system` endpoint when using the `?v=2` query parameter.
### Changed (since 1.7.2)
* Send re-installation status separately from installation status.
* Wings release versions will now follow the major and minor version of the Panel.
* Transfers no longer buffer to disk, instead they are fully streamed with only a small amount of memory used for buffering.
* Release binaries are no longer compressed with UPX.
* Use `POST` instead of `GET` for sending the status of a transfer to the Panel.
### Fixed (since 1.7.2)
* Fixed servers outgoing IP not being updated whenever a server's primary allocation is changed when using the Force Outgoing IP option.
* Fixed servers being terminated rather than gracefully stopped when a signal is used to stop the container rather than a command.
* Fixed file not found errors being treated as an internal error, they are now treated as a 404.
* Wings can be run with Podman instead of Docker, this is still experimental and not recommended for production use.
* Archive progress is now reported correctly.
* Labels for containers can now be set by the Panel.
* Fixed servers becoming deadlocked when the target node of a transfer goes offline.
## v1.11.0-rc.2 ## v1.11.0-rc.2
### Added ### Added
* More detailed information returned by the `/api/system` endpoint when using the `?v=2` query parameter. * More detailed information returned by the `/api/system` endpoint when using the `?v=2` query parameter.
@@ -87,18 +23,6 @@
* Archive progress is now reported correctly. * Archive progress is now reported correctly.
* Labels for containers can now be set by the Panel. * 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 ## v1.7.2
### Fixed ### Fixed
* The S3 backup driver now supports Cloudflare R2 * The S3 backup driver now supports Cloudflare R2

View File

@@ -1,5 +1,5 @@
# Stage 1 (Build) # Stage 1 (Build)
FROM golang:1.19-alpine AS builder FROM golang:1.18-alpine AS builder
ARG VERSION ARG VERSION
RUN apk add --update --no-cache git make RUN apk add --update --no-cache git make

View File

@@ -15,20 +15,21 @@ dependencies, and allowing users to authenticate with the same credentials they
## Sponsors ## Sponsors
I would like to extend my sincere thanks to the following sponsors for helping find Pterodactyl's development. I would like to extend my sincere thanks to the following sponsors for helping find Pterodactyl's developement.
[Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi) [Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi)
| Company | About | | Company | About |
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [**WISP**](https://wisp.gg) | Extra features. | | [**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. | | [**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. | | [**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! | | [**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! | | [**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. | | [**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. | | [**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. |
| [**DutchIS**](https://dutchis.net?ref=pterodactyl) | DutchIS provides instant infrastructure such as pay per use VPS hosting. Start your game hosting journey on DutchIS. | | [**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. |
| [**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 ## Documentation

View File

@@ -319,9 +319,6 @@ type Configuration struct {
// is only required by users running Wings without SSL certificates and using internal IP // 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. // addresses in order to connect. Most users should NOT enable this setting.
AllowCORSPrivateNetwork bool `json:"allow_cors_private_network" yaml:"allow_cors_private_network"` 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. // NewAtPath creates a new struct and set the path where it should be stored.

View File

@@ -58,7 +58,7 @@ func (e *Environment) Attach(ctx context.Context) error {
// Set the stream again with the container. // Set the stream again with the container.
if st, err := e.client.ContainerAttach(ctx, e.Id, opts); err != nil { if st, err := e.client.ContainerAttach(ctx, e.Id, opts); err != nil {
return errors.WrapIf(err, "environment/docker: error while attaching to container") return err
} else { } else {
e.SetStream(&st) e.SetStream(&st)
} }
@@ -143,7 +143,7 @@ func (e *Environment) Create() error {
if _, err := e.ContainerInspect(ctx); err == nil { if _, err := e.ContainerInspect(ctx); err == nil {
return nil return nil
} else if !client.IsErrNotFound(err) { } else if !client.IsErrNotFound(err) {
return errors.WrapIf(err, "environment/docker: failed to inspect container") return errors.Wrap(err, "environment/docker: failed to inspect container")
} }
// Try to pull the requested image before creating the container. // Try to pull the requested image before creating the container.

View File

@@ -161,7 +161,7 @@ func (e *Environment) ExitState() (uint32, bool, error) {
if client.IsErrNotFound(err) { if client.IsErrNotFound(err) {
return 1, false, nil return 1, false, nil
} }
return 0, false, errors.WrapIf(err, "environment/docker: failed to inspect container") return 0, false, err
} }
return uint32(c.State.ExitCode), c.State.OOMKilled, nil return uint32(c.State.ExitCode), c.State.OOMKilled, nil
} }

View File

@@ -103,7 +103,7 @@ func (e *Environment) Start(ctx context.Context) error {
// exists on the system, and rebuild the container if that is required for server booting to // exists on the system, and rebuild the container if that is required for server booting to
// occur. // occur.
if err := e.OnBeforeStart(ctx); err != nil { if err := e.OnBeforeStart(ctx); err != nil {
return errors.WrapIf(err, "environment/docker: failed to run pre-boot process") return errors.WithStackIf(err)
} }
// If we cannot start & attach to the container in 30 seconds something has gone // If we cannot start & attach to the container in 30 seconds something has gone
@@ -119,7 +119,7 @@ func (e *Environment) Start(ctx context.Context) error {
// By explicitly attaching to the instance before we start it, we can immediately // By explicitly attaching to the instance before we start it, we can immediately
// react to errors/output stopping/etc. when starting. // react to errors/output stopping/etc. when starting.
if err := e.Attach(actx); err != nil { if err := e.Attach(actx); err != nil {
return errors.WrapIf(err, "environment/docker: failed to attach to container") return err
} }
if err := e.client.ContainerStart(actx, e.Id, types.ContainerStartOptions{}); err != nil { if err := e.client.ContainerStart(actx, e.Id, types.ContainerStartOptions{}); err != nil {
@@ -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. // 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, // Using a negative timeout here will allow the container to stop gracefully,
// rather than forcefully terminating it. Value is in seconds, but -1 is // rather than forcefully terminating it, this value MUST be at least 1
// treated as indefinitely. // second, otherwise it will be ignored.
timeout := -1 timeout := -1 * time.Second
if err := e.client.ContainerStop(ctx, e.Id, container.StopOptions{Timeout: &timeout}); err != nil { if err := e.client.ContainerStop(ctx, e.Id, &timeout); err != nil {
// If the container does not exist just mark the process as stopped and return without // If the container does not exist just mark the process as stopped and return without
// an error. // an error.
if client.IsErrNotFound(err) { if client.IsErrNotFound(err) {

142
go.mod
View File

@@ -4,87 +4,85 @@ go 1.18
require ( require (
emperror.dev/errors v0.8.1 emperror.dev/errors v0.8.1
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.6
github.com/Jeffail/gabs/v2 v2.7.0 github.com/Jeffail/gabs/v2 v2.6.1
github.com/NYTimes/logrotate v1.0.0 github.com/NYTimes/logrotate v1.0.0
github.com/acobaugh/osrelease v0.1.0 github.com/acobaugh/osrelease v0.1.0
github.com/apex/log v1.9.0 github.com/apex/log v1.9.0
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/beevik/etree v1.2.0 github.com/beevik/etree v1.1.0
github.com/buger/jsonparser v1.1.1 github.com/buger/jsonparser v1.1.1
github.com/cenkalti/backoff/v4 v4.2.1 github.com/cenkalti/backoff/v4 v4.1.3
github.com/creasty/defaults v1.7.0 github.com/creasty/defaults v1.6.0
github.com/docker/docker v24.0.5+incompatible github.com/docker/docker v20.10.18+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/fatih/color v1.15.0 github.com/fatih/color v1.13.0
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd
github.com/gabriel-vasile/mimetype v1.4.2 github.com/gabriel-vasile/mimetype v1.4.1
github.com/gammazero/workerpool v1.1.3 github.com/gammazero/workerpool v1.1.3
github.com/gbrlsnchs/jwt/v3 v3.0.1 github.com/gbrlsnchs/jwt/v3 v3.0.1
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.8.1
github.com/glebarez/sqlite v1.9.0 github.com/glebarez/sqlite v1.4.8
github.com/go-co-op/gocron v1.30.1 github.com/go-co-op/gocron v1.17.0
github.com/goccy/go-json v0.10.2 github.com/goccy/go-json v0.9.11
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/iancoleman/strcase v0.3.0 github.com/iancoleman/strcase v0.2.0
github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0 github.com/icza/dyno v0.0.0-20220812133438-f0b6f8a18845
github.com/juju/ratelimit v1.0.2 github.com/juju/ratelimit v1.0.2
github.com/karrick/godirwalk v1.17.0 github.com/karrick/godirwalk v1.17.0
github.com/klauspost/compress v1.16.7 github.com/klauspost/compress v1.15.11
github.com/klauspost/pgzip v1.2.6 github.com/klauspost/pgzip v1.2.5
github.com/magiconair/properties v1.8.7 github.com/magiconair/properties v1.8.6
github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-colorable v0.1.13
github.com/mholt/archiver/v4 v4.0.0-alpha.8 github.com/mholt/archiver/v4 v4.0.0-alpha.7
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/sftp v1.13.5 github.com/pkg/sftp v1.13.5
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.5.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.0
golang.org/x/crypto v0.11.0 golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/sync v0.3.0 golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.25.2 gorm.io/gorm v1.23.10
) )
require ( require (
github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/hcsshim v0.10.0 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect github.com/Microsoft/hcsshim v0.9.4 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect github.com/andybalholm/brotli v1.0.4 // indirect
github.com/bodgit/sevenzip v1.4.3 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/bytedance/sonic v1.10.0-rc2 // indirect github.com/containerd/fifo v1.0.0 // 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/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/gammazero/deque v0.2.1 // indirect github.com/gammazero/deque v0.2.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/glebarez/go-sqlite v1.19.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.14.1 // indirect github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/gogo/protobuf v1.3.2 // 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/golang/snappy v0.0.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // 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/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // 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/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/magefile/mage v1.15.0 // indirect github.com/magefile/mage v1.14.0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -92,35 +90,35 @@ require (
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // 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/robfig/cron/v3 v3.0.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/therootcompany/xz v1.0.1 // indirect github.com/therootcompany/xz v1.0.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.7 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ulikunitz/xz v0.5.10 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect go.uber.org/atomic v1.10.0 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.8.0 // indirect
go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect
golang.org/x/arch v0.4.0 // indirect golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/mod v0.12.0 // indirect golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect
golang.org/x/net v0.12.0 // indirect golang.org/x/text v0.3.7 // 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/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.11.0 // indirect golang.org/x/tools v0.1.12 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/protobuf v1.31.0 // indirect google.golang.org/protobuf v1.28.1 // indirect
gotest.tools/v3 v3.0.2 // indirect modernc.org/libc v1.20.0 // indirect
modernc.org/libc v1.24.1 // indirect modernc.org/mathutil v1.5.0 // indirect
modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.4.0 // indirect
modernc.org/memory v1.6.0 // indirect modernc.org/sqlite v1.19.1 // indirect
modernc.org/sqlite v1.24.0 // indirect
) )

1147
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@ package cron
import ( import (
"context" "context"
"net"
"emperror.dev/errors" "emperror.dev/errors"
@@ -18,9 +17,9 @@ type activityCron struct {
max int max int
} }
// Run executes the cronjob and ensures we fetch and send all the stored activity to the // Run executes the cronjob and ensures we fetch and send all of the stored activity to the
// Panel instance. Once activity is sent it is deleted from the local database instance. Any // Panel instance. Once activity is sent it is deleted from the local database instance. Any
// SFTP specific events are not handled in this cron, they're handled separately to account // SFTP specific events are not handled in this cron, they're handled seperately to account
// for de-duplication and event merging. // for de-duplication and event merging.
func (ac *activityCron) Run(ctx context.Context) error { func (ac *activityCron) Run(ctx context.Context) error {
// Don't execute this cron if there is currently one running. Once this task is completed // Don't execute this cron if there is currently one running. Once this task is completed
@@ -35,6 +34,7 @@ func (ac *activityCron) Run(ctx context.Context) error {
Where("event NOT LIKE ?", "server:sftp.%"). Where("event NOT LIKE ?", "server:sftp.%").
Limit(ac.max). Limit(ac.max).
Find(&activity) Find(&activity)
if tx.Error != nil { if tx.Error != nil {
return errors.WithStack(tx.Error) return errors.WithStack(tx.Error)
} }
@@ -42,42 +42,15 @@ func (ac *activityCron) Run(ctx context.Context) error {
return nil return nil
} }
// ids to delete from the database. if err := ac.manager.Client().SendActivityLogs(ctx, activity); err != nil {
ids := make([]int, 0, len(activity))
// activities to send to the panel.
activities := make([]models.Activity, 0, len(activity))
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 ip := net.ParseIP(v.IP); ip == nil {
ids = append(ids, v.ID)
continue
}
activities = append(activities, v)
}
if len(ids) > 0 {
tx = database.Instance().WithContext(ctx).Where("id IN ?", ids).Delete(&models.Activity{})
if tx.Error != nil {
return errors.WithStack(tx.Error)
}
}
if len(activities) == 0 {
return nil
}
if err := ac.manager.Client().SendActivityLogs(ctx, activities); err != nil {
return errors.WrapIf(err, "cron: failed to send activity events to Panel") return errors.WrapIf(err, "cron: failed to send activity events to Panel")
} }
// Add all the successful activities to the list of IDs to delete. var ids []int
ids = make([]int, len(activities)) for _, v := range activity {
for i, v := range activities { ids = append(ids, v.ID)
ids[i] = v.ID
} }
// Delete all the activities that were sent to the Panel (or that were invalid).
tx = database.Instance().WithContext(ctx).Where("id IN ?", ids).Delete(&models.Activity{}) tx = database.Instance().WithContext(ctx).Where("id IN ?", ids).Delete(&models.Activity{})
if tx.Error != nil { if tx.Error != nil {
return errors.WithStack(tx.Error) return errors.WithStack(tx.Error)

View File

@@ -1,11 +1,11 @@
package models package models
import ( import (
"net"
"strings"
"time" "time"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/pterodactyl/wings/system"
) )
type Event string type Event string
@@ -57,9 +57,7 @@ func (a Activity) SetUser(u string) *Activity {
// is trimmed down to remove any extraneous data, and the timestamp is set to the current // is trimmed down to remove any extraneous data, and the timestamp is set to the current
// system time and then stored as UTC. // system time and then stored as UTC.
func (a *Activity) BeforeCreate(_ *gorm.DB) error { func (a *Activity) BeforeCreate(_ *gorm.DB) error {
if ip, _, err := net.SplitHostPort(strings.TrimSpace(a.IP)); err == nil { a.IP = system.TrimIPSuffix(a.IP)
a.IP = ip
}
if a.Timestamp.IsZero() { if a.Timestamp.IsZero() {
a.Timestamp = time.Now() a.Timestamp = time.Now()
} }

View File

@@ -115,7 +115,7 @@ func (c *client) SetTransferStatus(ctx context.Context, uuid string, successful
if successful { if successful {
state = "success" state = "success"
} }
resp, err := c.Post(ctx, fmt.Sprintf("/servers/%s/transfer/%s", uuid, state), nil) resp, err := c.Get(ctx, fmt.Sprintf("/servers/%s/transfer/%s", uuid, state), nil)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -95,7 +95,6 @@ func getDownloadFile(c *gin.Context) {
middleware.CaptureAndAbort(c, err) middleware.CaptureAndAbort(c, err)
return return
} }
defer f.Close()
c.Header("Content-Length", strconv.Itoa(int(st.Size()))) c.Header("Content-Length", strconv.Itoa(int(st.Size())))
c.Header("Content-Disposition", "attachment; filename="+strconv.Quote(st.Name())) c.Header("Content-Disposition", "attachment; filename="+strconv.Quote(st.Name()))

View File

@@ -6,7 +6,6 @@ import (
"strings" "strings"
"time" "time"
"emperror.dev/errors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
@@ -64,11 +63,11 @@ func postServerTransfer(c *gin.Context) {
if s.Environment.State() != environment.ProcessOfflineState { if s.Environment.State() != environment.ProcessOfflineState {
if err := s.Environment.WaitForStop( if err := s.Environment.WaitForStop(
s.Context(), s.Context(),
time.Second*15, time.Minute,
false, false,
); err != nil && !strings.Contains(strings.ToLower(err.Error()), "no such container") { ); err != nil && !strings.Contains(strings.ToLower(err.Error()), "no such container") {
s.SetTransferring(false) notifyPanelOfFailure()
middleware.CaptureAndAbort(c, errors.Wrap(err, "failed to stop server for transfer")) s.Log().WithError(err).Error("failed to stop server for transfer")
return return
} }
} }

View File

@@ -58,7 +58,7 @@ func getServerWebsocket(c *gin.Context) {
case <-ctx.Done(): case <-ctx.Done():
break break
case <-s.Context().Done(): case <-s.Context().Done():
_ = handler.Connection.WriteControl(ws.CloseMessage, ws.FormatCloseMessage(ws.CloseGoingAway, "server deleted"), time.Now().Add(time.Second*5)) handler.Connection.WriteControl(ws.CloseMessage, ws.FormatCloseMessage(ws.CloseGoingAway, "server deleted"), time.Now().Add(time.Second*5))
break break
} }
}() }()
@@ -83,7 +83,7 @@ func getServerWebsocket(c *gin.Context) {
go func(msg websocket.Message) { go func(msg websocket.Message) {
if err := handler.HandleInbound(ctx, msg); err != nil { if err := handler.HandleInbound(ctx, msg); err != nil {
_ = handler.SendErrorJson(msg, err) handler.SendErrorJson(msg, err)
} }
}(j) }(j)
} }

View File

@@ -113,21 +113,9 @@ func postCreateServer(c *gin.Context) {
c.Status(http.StatusAccepted) c.Status(http.StatusAccepted)
} }
type postUpdateConfigurationResponse struct {
Applied bool `json:"applied"`
}
// Updates the running configuration for this Wings instance. // Updates the running configuration for this Wings instance.
func postUpdateConfiguration(c *gin.Context) { func postUpdateConfiguration(c *gin.Context) {
cfg := config.Get() cfg := config.Get()
if cfg.IgnorePanelConfigUpdates {
c.JSON(http.StatusOK, postUpdateConfigurationResponse{
Applied: false,
})
return
}
if err := c.BindJSON(&cfg); err != nil { if err := c.BindJSON(&cfg); err != nil {
return return
} }
@@ -151,7 +139,5 @@ func postUpdateConfiguration(c *gin.Context) {
// Since we wrote it to the disk successfully now update the global configuration // Since we wrote it to the disk successfully now update the global configuration
// state to use this new configuration struct. // state to use this new configuration struct.
config.Set(cfg) config.Set(cfg)
c.JSON(http.StatusOK, postUpdateConfigurationResponse{ c.Status(http.StatusNoContent)
Applied: true,
})
} }

View File

@@ -85,7 +85,6 @@ func (b *LocalBackup) Restore(ctx context.Context, _ io.Reader, callback Restore
if err != nil { if err != nil {
return err return err
} }
defer f.Close()
var reader io.Reader = f var reader io.Reader = f
// Steal the logic we use for making backups which will be applied when restoring // Steal the logic we use for making backups which will be applied when restoring

View File

@@ -6,8 +6,6 @@ import (
"sync" "sync"
"time" "time"
"emperror.dev/errors"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
) )
@@ -59,7 +57,7 @@ func (s *Server) handleServerCrash() error {
exitCode, oomKilled, err := s.Environment.ExitState() exitCode, oomKilled, err := s.Environment.ExitState()
if err != nil { if err != nil {
return errors.Wrap(err, "failed to get exit state for server process") return err
} }
// If the system is not configured to detect a clean exit code as a crash, and the // If the system is not configured to detect a clean exit code as a crash, and the
@@ -87,5 +85,5 @@ func (s *Server) handleServerCrash() error {
s.crasher.SetLastCrash(time.Now()) s.crasher.SetLastCrash(time.Now())
return errors.Wrap(s.HandlePowerAction(PowerActionStart), "failed to start server after crash detection") return s.HandlePowerAction(PowerActionStart)
} }

View File

@@ -3,7 +3,6 @@ package filesystem
import ( import (
"archive/tar" "archive/tar"
"context" "context"
"fmt"
"io" "io"
"io/fs" "io/fs"
"os" "os"
@@ -67,8 +66,6 @@ type Archive struct {
// Files specifies the files to archive, this takes priority over the Ignore option, if // 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. // unspecified, all files in the BasePath will be archived unless Ignore is set.
//
// All items in Files must be absolute within BasePath.
Files []string Files []string
// Progress wraps the writer of the archive to pass through the progress tracker. // Progress wraps the writer of the archive to pass through the progress tracker.
@@ -100,14 +97,6 @@ func (a *Archive) Create(ctx context.Context, dst string) error {
// Stream . // Stream .
func (a *Archive) Stream(ctx context.Context, w io.Writer) error { 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 // Choose which compression level to use based on the compression_level configuration option
var compressionLevel int var compressionLevel int
switch config.Get().System.Backups.CompressionLevel { switch config.Get().System.Backups.CompressionLevel {
@@ -201,11 +190,9 @@ 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 { func (a *Archive) withFilesCallback(tw *TarProgress) func(path string, de *godirwalk.Dirent) error {
return a.callback(tw, func(p string, rp string) error { return a.callback(tw, func(p string, rp string) error {
for _, f := range a.Files { for _, f := range a.Files {
// Allow exact file matches, otherwise check if file is within a parent directory. // If the given doesn't match, or doesn't have the same prefix continue
// // to the next item in the loop.
// The slashes are added in the prefix checks to prevent partial name matches from being if p != f && !strings.HasPrefix(strings.TrimSuffix(p, "/")+"/", f) {
// included in the archive.
if f != p && !strings.HasPrefix(strings.TrimSuffix(p, "/")+"/", strings.TrimSuffix(f, "/")+"/") {
continue continue
} }

View File

@@ -1,133 +0,0 @@
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
}

View File

@@ -91,7 +91,7 @@ func (fs *Filesystem) SpaceAvailableForDecompression(ctx context.Context, dir st
// waiting an unnecessary amount of time on this call. // waiting an unnecessary amount of time on this call.
dirSize, err := fs.DiskUsage(false) dirSize, err := fs.DiskUsage(false)
fsys, err := archiver.FileSystem(ctx, source) fsys, err := archiver.FileSystem(source)
if err != nil { if err != nil {
if errors.Is(err, archiver.ErrNoMatch) { if errors.Is(err, archiver.ErrNoMatch) {
return newFilesystemError(ErrCodeUnknownArchive, err) return newFilesystemError(ErrCodeUnknownArchive, err)
@@ -148,7 +148,7 @@ func (fs *Filesystem) DecompressFileUnsafe(ctx context.Context, dir string, file
if err != nil { if err != nil {
return err return err
} }
defer f.Close() // TODO: defer file close?
// Identify the type of archive we are dealing with. // Identify the type of archive we are dealing with.
format, input, err := archiver.Identify(filepath.Base(file), f) format, input, err := archiver.Identify(filepath.Base(file), f)

View File

@@ -183,7 +183,7 @@ func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
} }
if !e.IsDir() { if !e.IsDir() {
_ = syscall.Lstat(p, &st) syscall.Lstat(p, &st)
atomic.AddInt64(&size, st.Size) atomic.AddInt64(&size, st.Size)
} }

View File

@@ -92,9 +92,6 @@ func (fs *Filesystem) Touch(p string, flag int) (*os.File, error) {
if err == nil { if err == nil {
return f, nil return f, nil
} }
if f != nil {
_ = f.Close()
}
// If the error is not because it doesn't exist then we just need to bail at this point. // If the error is not because it doesn't exist then we just need to bail at this point.
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
return nil, errors.Wrap(err, "server/filesystem: touch: failed to open file handle") return nil, errors.Wrap(err, "server/filesystem: touch: failed to open file handle")
@@ -165,7 +162,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. // Adjust the disk usage to account for the old size and the new size of the file.
fs.addDisk(sz - currentSize) fs.addDisk(sz - currentSize)
return fs.unsafeChown(cleaned) return fs.Chown(cleaned)
} }
// Creates a new directory (name) at a specified path (p) for the server. // Creates a new directory (name) at a specified path (p) for the server.
@@ -223,12 +220,7 @@ func (fs *Filesystem) Chown(path string) error {
if err != nil { if err != nil {
return err 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 { if fs.isTest {
return nil return nil
} }
@@ -237,19 +229,19 @@ func (fs *Filesystem) unsafeChown(path string) error {
gid := config.Get().System.User.Gid gid := config.Get().System.User.Gid
// Start by just chowning the initial path that we received. // Start by just chowning the initial path that we received.
if err := os.Chown(path, uid, gid); err != nil { if err := os.Chown(cleaned, uid, gid); err != nil {
return errors.Wrap(err, "server/filesystem: chown: failed to chown path") 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 // If this is not a directory we can now return from the function, there is nothing
// left that we need to do. // left that we need to do.
if st, err := os.Stat(path); err != nil || !st.IsDir() { if st, err := os.Stat(cleaned); err != nil || !st.IsDir() {
return nil return nil
} }
// If this was a directory, begin walking over its contents recursively and ensure that all // 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. // of the subfiles and directories get their permissions updated as well.
err := godirwalk.Walk(path, &godirwalk.Options{ err = godirwalk.Walk(cleaned, &godirwalk.Options{
Unsorted: true, Unsorted: true,
Callback: func(p string, e *godirwalk.Dirent) error { Callback: func(p string, e *godirwalk.Dirent) error {
// Do not attempt to chown a symlink. Go's os.Chown function will affect the symlink // Do not attempt to chown a symlink. Go's os.Chown function will affect the symlink
@@ -266,6 +258,7 @@ func (fs *Filesystem) unsafeChown(path string) error {
return os.Chown(p, uid, gid) return os.Chown(p, uid, gid)
}, },
}) })
return errors.Wrap(err, "server/filesystem: chown: failed to chown during walk function") return errors.Wrap(err, "server/filesystem: chown: failed to chown during walk function")
} }
@@ -387,9 +380,10 @@ func (fs *Filesystem) TruncateRootDirectory() error {
// Delete removes a file or folder from the system. Prevents the user from // Delete removes a file or folder from the system. Prevents the user from
// accidentally (or maliciously) removing their root server data directory. // accidentally (or maliciously) removing their root server data directory.
func (fs *Filesystem) Delete(p string) error { 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 // 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 // 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 the data // not be able to delete a file that is a symlink pointing to a location outside of the data
// directory. // directory.
// //
// We also want to avoid resolving a symlink that points _within_ the data directory and thus // We also want to avoid resolving a symlink that points _within_ the data directory and thus
@@ -406,66 +400,26 @@ func (fs *Filesystem) Delete(p string) error {
return errors.New("cannot delete root server directory") return errors.New("cannot delete root server directory")
} }
st, err := os.Lstat(resolved) if st, err := os.Lstat(resolved); err != nil {
if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
fs.error(err).Warn("error while attempting to stat file before deletion") fs.error(err).Warn("error while attempting to stat file before deletion")
return err
}
// 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
}
t, err := filepath.EvalSymlinks(try)
if err == nil {
if !fs.unsafeIsInDataDirectory(t) {
return NewBadPathResolution(p, t)
}
break
}
}
// 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 { } else {
fs.addDisk(-st.Size()) 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)
}
}(&wg, st, resolved)
}
} }
wg.Wait()
return os.RemoveAll(resolved) return os.RemoveAll(resolved)
} }

View File

@@ -537,80 +537,6 @@ 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() { g.AfterEach(func() {
rfs.reset() rfs.reset()

View File

@@ -2,7 +2,6 @@ package filesystem
import ( import (
"context" "context"
iofs "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -34,6 +33,8 @@ func (fs *Filesystem) IsIgnored(paths ...string) error {
// This logic is actually copied over from the SFTP server code. Ideally that eventually // 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. // either gets ported into this application, or is able to make use of this package.
func (fs *Filesystem) SafePath(p string) (string, error) { func (fs *Filesystem) SafePath(p string) (string, error) {
var nonExistentPathResolution string
// Start with a cleaned up path before checking the more complex bits. // Start with a cleaned up path before checking the more complex bits.
r := fs.unsafeFilePath(p) r := fs.unsafeFilePath(p)
@@ -43,24 +44,47 @@ func (fs *Filesystem) SafePath(p string) (string, error) {
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return "", errors.Wrap(err, "server/filesystem: failed to evaluate symlink") return "", errors.Wrap(err, "server/filesystem: failed to evaluate symlink")
} else if os.IsNotExist(err) { } else if os.IsNotExist(err) {
// The target of one of the symlinks (EvalSymlinks is recursive) does not exist. // The requested directory doesn't exist, so at this point we need to iterate up the
// So we get what target path does not exist and check if it's within the data // path chain until we hit a directory that _does_ exist and can be validated.
// directory. If it is, we return the original path, otherwise we return an error. parts := strings.Split(filepath.Dir(r), "/")
pErr, ok := err.(*iofs.PathError)
if !ok { var try string
return "", errors.Wrap(err, "server/filesystem: failed to evaluate symlink") // 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
}
t, err := filepath.EvalSymlinks(try)
if err == nil {
nonExistentPathResolution = t
break
}
} }
ep = pErr.Path }
// 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
} }
// If the requested directory from EvalSymlinks begins with the server root directory go // 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 // ahead and return it. If not we'll return an error which will block any further action
// on the file. // on the file.
if fs.unsafeIsInDataDirectory(ep) { if fs.unsafeIsInDataDirectory(ep) {
// Returning the original path here instead of the resolved path ensures that return ep, nil
// 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) return "", NewBadPathResolution(p, r)

View File

@@ -115,14 +115,6 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
panic(err) 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 { if err := os.Symlink(filepath.Join(rfs.root, "/malicious_dir"), filepath.Join(rfs.root, "/server/external_dir")); err != nil {
panic(err) panic(err)
} }
@@ -136,22 +128,6 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue() 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() { g.It("cannot write a file to a directory symlinked outside the root", func() {
r := bytes.NewReader([]byte("testing")) r := bytes.NewReader([]byte("testing"))

View File

@@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"fmt"
"html/template" "html/template"
"io" "io"
"os" "os"
@@ -426,6 +427,10 @@ func (ip *InstallationProcess) Execute() (string, error) {
} }
cfg := config.Get() 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)) tmpfsSize := strconv.Itoa(int(cfg.Docker.TmpfsSize))
hostConf := &container.HostConfig{ hostConf := &container.HostConfig{
Mounts: []mount.Mount{ Mounts: []mount.Mount{
@@ -448,6 +453,7 @@ func (ip *InstallationProcess) Execute() (string, error) {
}, },
DNS: cfg.Docker.Network.Dns, DNS: cfg.Docker.Network.Dns,
LogConfig: cfg.Docker.ContainerLogConfig(), LogConfig: cfg.Docker.ContainerLogConfig(),
Privileged: true,
NetworkMode: container.NetworkMode(cfg.Docker.Network.Mode), NetworkMode: container.NetworkMode(cfg.Docker.Network.Mode),
UsernsMode: container.UsernsMode(cfg.Docker.UsernsMode), UsernsMode: container.UsernsMode(cfg.Docker.UsernsMode),
} }

View File

@@ -147,7 +147,6 @@ func (s *Server) Context() context.Context {
// server instance. // server instance.
func (s *Server) GetEnvironmentVariables() []string { func (s *Server) GetEnvironmentVariables() []string {
out := []string{ out := []string{
// TODO: allow this to be overridden by the user.
fmt.Sprintf("TZ=%s", config.Get().System.Timezone), fmt.Sprintf("TZ=%s", config.Get().System.Timezone),
fmt.Sprintf("STARTUP=%s", s.Config().Invocation), fmt.Sprintf("STARTUP=%s", s.Config().Invocation),
fmt.Sprintf("SERVER_MEMORY=%d", s.MemoryLimit()), fmt.Sprintf("SERVER_MEMORY=%d", s.MemoryLimit()),

View File

@@ -128,13 +128,6 @@ func (t *Transfer) PushArchiveToTarget(url, token string) ([]byte, error) {
t.Log().Debug("sending archive to destination") t.Log().Debug("sending archive to destination")
client := http.Client{Timeout: 0} client := http.Client{Timeout: 0}
res, err := client.Do(req) res, err := client.Do(req)
if err != nil {
t.Log().Debug("error while sending archive to destination")
return nil, err
}
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code from destination: %d", res.StatusCode)
}
t.Log().Debug("waiting for stream to complete") t.Log().Debug("waiting for stream to complete")
select { select {
case <-ctx.Done(): case <-ctx.Done():

View File

@@ -68,21 +68,6 @@ func (c *SFTPServer) Run() error {
} }
conf := &ssh.ServerConfig{ conf := &ssh.ServerConfig{
Config: ssh.Config{
KeyExchanges: []string{
"curve25519-sha256", "curve25519-sha256@libssh.org",
"ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521",
"diffie-hellman-group14-sha256",
},
Ciphers: []string{
"aes128-gcm@openssh.com",
"chacha20-poly1305@openssh.com",
"aes128-ctr", "aes192-ctr", "aes256-ctr",
},
MACs: []string{
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256",
},
},
NoClientAuth: false, NoClientAuth: false,
MaxAuthTries: 6, MaxAuthTries: 6,
PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {

View File

@@ -1,3 +1,3 @@
package system package system
var Version = "1.11.7" var Version = "1.11.0-rc.2"

View File

@@ -15,16 +15,7 @@ func MutexLocked(m *sync.RWMutex) bool {
state := v.FieldByName("w").FieldByName("state") state := v.FieldByName("w").FieldByName("state")
readerCountField := v.FieldByName("readerCount") return state.Int()&1 == 1 || v.FieldByName("readerCount").Int() > 0
// 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) { func TestSink(t *testing.T) {

29
system/strings.go Normal file
View File

@@ -0,0 +1,29 @@
package system
import (
"math/rand"
"regexp"
"strings"
)
var ipTrimRegex = regexp.MustCompile(`(:\d*)?$`)
const characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
// RandomString generates a random string of alpha-numeric characters using a
// pseudo-random number generator. The output of this function IS NOT cryptographically
// secure, it is used solely for generating random strings outside a security context.
func RandomString(n int) string {
var b strings.Builder
b.Grow(n)
for i := 0; i < n; i++ {
b.WriteByte(characters[rand.Intn(len(characters))])
}
return b.String()
}
// TrimIPSuffix removes the internal port value from an IP address to ensure we're only
// ever working directly with the IP address.
func TrimIPSuffix(s string) string {
return ipTrimRegex.ReplaceAllString(s, "")
}

View File

@@ -127,7 +127,6 @@ func GetDockerInfo(ctx context.Context) (types.Version, types.Info, error) {
if err != nil { if err != nil {
return types.Version{}, types.Info{}, err return types.Version{}, types.Info{}, err
} }
defer c.Close()
dockerVersion, err := c.ServerVersion(ctx) dockerVersion, err := c.ServerVersion(ctx)
if err != nil { if err != nil {