2 Commits

Author SHA1 Message Date
Massimo Lusetti
a6aa7ff81a Merge 92216a10ba into 2513a698f3 2024-12-01 13:16:58 -08:00
Massimo Lusetti
92216a10ba Add group-accessible-repos option
The group-accessible-repos option will let filesystem group id
be able to access files and dir within the restic repo
Default stick with old behaviour to be owner restricted
While here make dirMode and fileMode within Options struct
private
2024-11-02 11:08:29 +01:00
35 changed files with 256 additions and 618 deletions

View File

@@ -3,8 +3,8 @@ name: Bug report
about: Report a problem with rest-server to help us resolve it and improve
---
<!--
<!--
Welcome! - We kindly ask that you:
1. Fill out the issue template below - not doing so needs a good reason.
@@ -24,7 +24,6 @@ for tracking bugs and feature requests directly relating to the development of
the software itself, rather than the project.
Thanks for understanding, and for contributing to the project!
-->
@@ -32,26 +31,24 @@ Output of `rest-server --version` <!-- If using docker, output of `docker images
---------------------------------
Problem description / Steps to reproduce
----------------------------------------
How did you run rest-server exactly?
------------------------------------
<!--
This section should include at least:
* A description of the problem you are having with rest-server.
* The complete command line and any environment variables you used to
configure rest-server. Make sure to replace sensitive values!
configure rest-server's backend access. Make sure to replace sensitive values!
* The output of the commands, what rest-server prints gives may give us much
information to diagnose the problem!
* The more time you spend describing an easy way to reproduce the behavior (if
this is possible), the easier it is for the project developers to fix it!
-->
What backend/server/service did you use to store the repository?
----------------------------------------------------------------
Expected behavior
-----------------
@@ -59,20 +56,30 @@ Expected behavior
Describe what you'd like rest-server to do differently.
-->
Actual behavior
---------------
<!--
In this section, please try to concentrate on observations, so only describe
what you observed directly.
Please try to concentrate on observations, so only describe what you observed directly.
-->
Steps to reproduce the behavior
-------------------------------
<!--
The more time you spend describing an easy way to reproduce the behavior (if
this is possible), the easier it is for the project developers to fix it!
-->
Do you have any idea what may have caused this?
-----------------------------------------------
<!--
Did something noteworthy happen on your system, Internet connection, backend services, etc?
-->
Do you have an idea how to solve the issue?
-------------------------------------------
Did rest-server help you today? Did it make you happy in any way?

View File

@@ -1,10 +1,10 @@
---
name: Feature request
name: Feature request/enhancement
about: Suggest a new feature or enhancement for rest-server
---
<!--
<!--
Welcome! - We kindly ask that you:
1. Fill out the issue template below - not doing so needs a good reason.
@@ -18,7 +18,6 @@ for tracking bugs and feature requests directly relating to the development of
the software itself, rather than the project.
Thanks for understanding, and for contributing to the project!
-->
@@ -31,22 +30,24 @@ later to see what has changed in rest-server when we revisit this issue after so
time.
-->
What should rest-server do differently? Which functionality do you think we should add?
---------------------------------------------------------------------------------------
What should rest-server do differently?
---------------------------------------
<!--
Please describe the feature you'd like us to add here.
Please describe the feature you'd like to see added or changed here.
-->
What are you trying to do? What problem would this solve?
---------------------------------------------------------
What are you trying to do? What is your use case?
-------------------------------------------------
<!--
This section should contain a brief description what you're trying to do, which
would be possible after implementing the new feature.
-->
Did rest-server help you today? Did it make you happy in any way?
-----------------------------------------------------------------

View File

@@ -1,41 +1,42 @@
<!--
Thank you very much for contributing code or documentation to rest-server! Please
fill out the following questions to make it easier for us to review your
changes.
Thank you very much for contributing code or documentation to rest-server!
Please note that each PR should be preceded by an issue where the suggested
change can be discussed in general and without focus on specific code. That
way, work done in the PR will better match what's been agreed in the issue.
Please fill out the following questions to make it easier for us to review
your changes. You don't have to check all the checkboxes at once, instead
feel free to add more commits over time.
-->
What does this PR change? What problem does it solve?
-----------------------------------------------------
What is the purpose of this change? What does it change?
--------------------------------------------------------
<!--
Describe the changes and their purpose here, as detailed as needed.
Describe the changes here, as detailed as needed.
-->
Was the change previously discussed in an issue or on the forum?
----------------------------------------------------------------
Was the change discussed in an issue or in the forum before?
------------------------------------------------------------
<!--
Link issues and relevant forum posts here.
If this PR resolves an issue on GitHub, use "Closes #1234" so that the issue
is closed automatically when this PR is merged.
If this PR resolves an issue on GitHub, write "Closes #1234" such
that the issue is closed automatically when this PR is merged.
-->
Checklist
---------
<!--
You do not need to check all the boxes below all at once. Feel free to take
your time and add more commits. If you're done and ready for review, please
check the last box. Enable a checkbox by replacing [ ] with [x].
Please always follow these steps:
- Enable [maintainer edits](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork).
- Run `gofmt` on the code in all commits.
- Format all commit messages in the same style as [the other commits in the repository](https://github.com/restic/rest-server/blob/master/CONTRIBUTING.md#git-commits).
-->
- [ ] I have added tests for all code changes.
- [ ] I have added documentation for relevant changes (in the manual).
- [ ] There's a new file in `changelog/unreleased/` that describes the changes for our users (see [template](https://github.com/restic/rest-server/blob/master/changelog/TEMPLATE)).
- [ ] I'm done! This pull request is ready for review.
- [ ] I have enabled [maintainer edits for this PR](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)
- [ ] I have added tests for all changes in this PR
- [ ] I have added documentation for the changes (in the manual)
- [ ] There's a new file in `changelog/unreleased/` that describes the changes for our users (template [here](https://github.com/restic/rest-server/blob/master/changelog/TEMPLATE))
- [ ] I have run `gofmt` on the code in all commits
- [ ] All commit messages are formatted in the same style as [the other commits in the repo](https://github.com/restic/rest-server/commits/master)
- [ ] I'm done, this Pull Request is ready for review

View File

@@ -7,58 +7,48 @@ on:
# run tests for all pull requests
pull_request:
merge_group:
permissions:
contents: read
env:
latest_go: "1.24.x"
latest_go: "1.21.x"
GO111MODULE: on
jobs:
test:
strategy:
matrix:
include:
- job_name: Linux
go: 1.24.x
os: ubuntu-latest
check_changelog: true
- job_name: Linux (race)
go: 1.24.x
os: ubuntu-latest
test_opts: "-race"
- job_name: Linux
go: 1.23.x
os: ubuntu-latest
name: ${{ matrix.job_name }} Go ${{ matrix.go }}
runs-on: ${{ matrix.os }}
go:
- 1.18.x
- 1.19.x
- 1.20.x
- 1.21.x
runs-on: ubuntu-latest
name: Go ${{ matrix.go }}
env:
GOPROXY: https://proxy.golang.org
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Check out code
uses: actions/checkout@v4
- name: Build
run: |
go build ./cmd/rest-server
- name: Build with build.go
run: |
go run build.go --goos linux
go run build.go --goos windows
go run build.go --goos darwin
- name: Run local Tests
- name: Run tests
run: |
go test -cover ${{matrix.test_opts}} ./...
go test ./...
- name: Check changelog files with calens
run: |
@@ -67,30 +57,27 @@ jobs:
echo "check changelog files"
calens
if: matrix.check_changelog
lint:
name: lint
runs-on: ubuntu-latest
permissions:
contents: read
# allow annotating code in the PR
checks: write
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Go ${{ env.latest_go }}
uses: actions/setup-go@v5
with:
go-version: ${{ env.latest_go }}
- name: Check out code
uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.64.8
args: --verbose --timeout 5m
version: v1.51
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true
args: --verbose --timeout 10m
# only run golangci-lint for pull requests, otherwise ALL hints get
# reported. We need to slowly address all issues until we can enable
@@ -102,18 +89,3 @@ jobs:
echo "check if go.mod and go.sum are up to date"
go mod tidy
git diff --exit-code go.mod go.sum
analyze:
name: Analyze results
needs: [test, lint]
if: always()
permissions: # no need to access code
contents: none
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe
with:
jobs: ${{ toJSON(needs) }}

View File

@@ -10,10 +10,13 @@ linters:
# make sure all errors returned by functions are handled
- errcheck
# find unused code
- deadcode
# show how code can be simplified
- gosimple
# make sure code is formatted
# # make sure code is formatted
- gofmt
# examine code and report suspicious constructs, such as Printf calls whose
@@ -32,14 +35,15 @@ linters:
# find unused variables, functions, structs, types, etc.
- unused
# find unused struct fields
- structcheck
# find unused global variables
- varcheck
# parse and typecheck code
- typecheck
# ensure that http response bodies are closed
- bodyclose
- importas
issues:
# don't use the default exclude rules, this hides (among others) ignored
# errors from Close() calls
@@ -51,6 +55,3 @@ issues:
- exported (function|method|var|type|const) .* should have comment or be unexported
# revive: ignore constants in all caps
- don't use ALL_CAPS in Go names; use CamelCase
# revive: lots of packages don't have such a comment
- "package-comments: should have a package comment"
- "redefines-builtin-id:"

View File

@@ -21,27 +21,29 @@ before:
# build a single binary
builds:
- id: default
-
# make sure everything is statically linked by disabling cgo altogether
env: &build_env
env:
- CGO_ENABLED=0
# set the package for the main binary
main: ./cmd/rest-server
flags:
&build_flags # don't include any paths to source files in the resulting binary
# don't include any paths to source files in the resulting binary
- -trimpath
mod_timestamp: "{{ .CommitTimestamp }}"
mod_timestamp: '{{ .CommitTimestamp }}'
ldflags: &build_ldflags # set the version variable in the main package
ldflags:
# set the version variable in the main package
- "-s -w -X main.version={{ .Version }}"
# list all operating systems and architectures we build binaries for
goos:
- linux
- darwin
- windows
- freebsd
- netbsd
- openbsd
@@ -50,7 +52,7 @@ builds:
goarch:
- amd64
- "386"
- 386
- arm
- arm64
- mips
@@ -59,39 +61,23 @@ builds:
- ppc64
- ppc64le
goarm:
- "6"
- "7"
- id: windows-only
env: *build_env
main: ./cmd/rest-server
flags: *build_flags
mod_timestamp: "{{ .CommitTimestamp }}"
ldflags: *build_ldflags
goos:
- windows
goarch:
- amd64
- "386"
- arm
- arm64
- 6
- 7
# configure the resulting archives to create
archives:
- id: default
builds: [default, windows-only]
format: tar.gz
-
# package a directory which contains the source file
wrap_in_directory: true
builds_info: &archive_file_info
owner: root
group: root
mtime: "{{ .CommitDate }}"
mtime: '{{ .CommitDate }}'
mode: 0644
# add these files to all archives
files: &archive_files
files:
- src: LICENSE
dst: LICENSE
info: *archive_file_info
@@ -102,20 +88,13 @@ archives:
dst: CHANGELOG.md
info: *archive_file_info
- id: windows-only
builds: [windows-only]
formats: [zip]
wrap_in_directory: true
builds_info: *archive_file_info
files: *archive_files
# also build an archive of the source code
source:
enabled: true
# build a file containing the SHA256 hashes
checksum:
name_template: "SHA256SUMS"
name_template: 'SHA256SUMS'
# sign the checksum file
signs:
@@ -149,7 +128,7 @@ dockers:
- docker/entrypoint.sh
- image_templates:
- restic/rest-server:{{ .Version }}-i386
goarch: "386"
goarch: 386
build_flag_templates:
- "--platform=linux/386"
- "--pull"
@@ -225,20 +204,21 @@ dockers:
dockerfile: "Dockerfile.goreleaser"
extra_files: *extra_files
docker_manifests:
- name_template: "restic/rest-server:{{ .Version }}"
image_templates:
- "restic/rest-server:{{ .Version }}-amd64"
- "restic/rest-server:{{ .Version }}-i386"
- "restic/rest-server:{{ .Version }}-arm32v6"
- "restic/rest-server:{{ .Version }}-arm32v7"
- "restic/rest-server:{{ .Version }}-arm64v8"
- "restic/rest-server:{{ .Version }}-ppc64le"
- name_template: "restic/rest-server:latest"
image_templates:
- "restic/rest-server:{{ .Version }}-amd64"
- "restic/rest-server:{{ .Version }}-i386"
- "restic/rest-server:{{ .Version }}-arm32v6"
- "restic/rest-server:{{ .Version }}-arm32v7"
- "restic/rest-server:{{ .Version }}-arm64v8"
- "restic/rest-server:{{ .Version }}-ppc64le"
- name_template: "restic/rest-server:{{ .Version }}"
image_templates:
- "restic/rest-server:{{ .Version }}-amd64"
- "restic/rest-server:{{ .Version }}-i386"
- "restic/rest-server:{{ .Version }}-arm32v6"
- "restic/rest-server:{{ .Version }}-arm32v7"
- "restic/rest-server:{{ .Version }}-arm64v8"
- "restic/rest-server:{{ .Version }}-ppc64le"
- name_template: "restic/rest-server:latest"
image_templates:
- "restic/rest-server:{{ .Version }}-amd64"
- "restic/rest-server:{{ .Version }}-i386"
- "restic/rest-server:{{ .Version }}-arm32v6"
- "restic/rest-server:{{ .Version }}-arm32v7"
- "restic/rest-server:{{ .Version }}-arm64v8"
- "restic/rest-server:{{ .Version }}-ppc64le"

View File

@@ -1,98 +1,3 @@
Changelog for rest-server 0.14.0 (2025-05-31)
============================================
The following sections list the changes in rest-server 0.14.0 relevant
to users. The changes are ordered by importance.
Summary
-------
* Sec #318: Fix world-readable permissions on new `.htpasswd` files
* Chg #322: Update dependencies and require Go 1.23 or newer
* Enh #174: Support proxy-based authentication
* Enh #189: Support group accessible repositories
* Enh #295: Output status of append-only mode on startup
* Enh #315: Hardened tls settings
* Enh #321: Add zip archive format for Windows releases
Details
-------
* Security #318: Fix world-readable permissions on new `.htpasswd` files
On startup the rest-server Docker container creates an empty `.htpasswd` file if
none exists yet. This file was world-readable by default, which can be a
security risk, even though the file only contains hashed passwords.
This has been fixed such that new `.htpasswd` files are no longer
world-readabble.
The permissions of existing `.htpasswd` files must be manually changed if
relevant in your setup.
https://github.com/restic/rest-server/issues/318
https://github.com/restic/rest-server/pull/340
* Change #322: Update dependencies and require Go 1.23 or newer
All dependencies have been updated. Rest-server now requires Go 1.23 or newer to
build.
This also disables support for TLS versions older than TLS 1.2. On Windows,
rest-server now requires at least Windows 10 or Windows Server 2016. On macOS,
rest-server now requires at least macOS 11 Big Sur.
https://github.com/restic/rest-server/pull/322
https://github.com/restic/rest-server/pull/338
* Enhancement #174: Support proxy-based authentication
Rest-server now supports authentication via HTTP proxy headers. This feature can
be enabled by specifying the username header using the `--proxy-auth-username`
option (e.g., `--proxy-auth-username=X-Forwarded-User`).
When enabled, the server authenticates users based on the specified header and
disables Basic Auth. Note that proxy authentication is disabled when `--no-auth`
is set.
https://github.com/restic/rest-server/issues/174
https://github.com/restic/rest-server/pull/307
* Enhancement #189: Support group accessible repositories
Rest-server now supports making repositories accessible to the filesystem group
by setting the `--group-accessible-repos` option. Note that permissions of
existing files are not modified. To allow the group to read and write file, use
a umask of `007`. To only grant read access use `027`. To make an existing
repository group-accessible, use `chmod -R g+rwX /path/to/repo`.
https://github.com/restic/rest-server/issues/189
https://github.com/restic/rest-server/pull/308
* Enhancement #295: Output status of append-only mode on startup
Rest-server now displays the status of append-only mode during startup.
https://github.com/restic/rest-server/pull/295
* Enhancement #315: Hardened tls settings
Rest-server now uses a secure TLS cipher suite set by default. The minimum TLS
version is now TLS 1.2 and can be further increased using the new
`--tls-min-ver` option, allowing users to enforce stricter security
requirements.
https://github.com/restic/rest-server/pull/315
* Enhancement #321: Add zip archive format for Windows releases
Windows users can now download rest-server binaries in zip archive format (.zip)
in addition to the existing tar.gz archives.
https://github.com/restic/rest-server/issues/321
https://github.com/restic/rest-server/pull/346
Changelog for rest-server 0.13.0 (2024-07-26)
============================================

View File

@@ -11,7 +11,7 @@ Rest Server is a high performance HTTP server that implements restic's [REST bac
## Requirements
Rest Server requires Go 1.23 or higher to build. The only tested compiler is the official Go compiler.
Rest Server requires Go 1.18 or higher to build. The only tested compiler is the official Go compiler. Building server with `gccgo` may work, but is not supported.
The required version of restic backup client to use with `rest-server` is [v0.7.1](https://github.com/restic/restic/releases/tag/v0.7.1) or higher.
@@ -32,27 +32,24 @@ Usage:
rest-server [flags]
Flags:
--append-only enable append only mode
--cpu-profile string write CPU profile to file
--debug output debug messages
--group-accessible-repos let filesystem group be able to access repo files
-h, --help help for rest-server
--htpasswd-file string location of .htpasswd file (default: "<data directory>/.htpasswd)"
--listen string listen address (default ":8000")
--log filename write HTTP requests in the combined log format to the specified filename (use "-" for logging to stdout)
--max-size int the maximum size of the repository in bytes
--no-auth disable .htpasswd authentication
--no-verify-upload do not verify the integrity of uploaded data. DO NOT enable unless the rest-server runs on a very low-power device
--path string data directory (default "/tmp/restic")
--private-repos users can only access their private repo
--prometheus enable Prometheus metrics
--prometheus-no-auth disable auth for Prometheus /metrics endpoint
--proxy-auth-username string specifies the HTTP header containing the username for proxy-based authentication
--tls turn on TLS support
--tls-cert string TLS certificate path
--tls-key string TLS key path
--tls-min-ver string TLS min version, one of (1.2|1.3) (default "1.2")
-v, --version version for rest-server
--append-only enable append only mode
--cpu-profile string write CPU profile to file
--debug output debug messages
-h, --help help for rest-server
--htpasswd-file string location of .htpasswd file (default: "<data directory>/.htpasswd")
--listen string listen address (default ":8000")
--log filename write HTTP requests in the combined log format to the specified filename
--max-size int the maximum size of the repository in bytes
--no-auth disable .htpasswd authentication
--no-verify-upload do not verify the integrity of uploaded data. DO NOT enable unless the rest-server runs on a very low-power device
--path string data directory (default "/tmp/restic")
--private-repos users can only access their private repo
--prometheus enable Prometheus metrics
--prometheus-no-auth disable auth for Prometheus /metrics endpoint
--tls turn on TLS support
--tls-cert string TLS certificate path
--tls-key string TLS key path
-v, --version version for rest-server
```
By default the server persists backup data in the OS temporary directory (`/tmp/restic` on Linux/BSD and others, in `%TEMP%\\restic` in Windows, etc). **If `rest-server` is launched using the default path, all backups will be lost**. To start the server with a custom persistence directory and with authentication disabled:
@@ -71,7 +68,7 @@ If you want to disable authentication, you must add the `--no-auth` flag. If thi
NOTE: In older versions of rest-server (up to 0.9.7), this flag does not exist and the server disables authentication if `.htpasswd` is missing or cannot be opened.
By default the server uses HTTP protocol. This is not very secure since with Basic Authentication, user name and passwords will be sent in clear text in every request. In order to enable TLS support just add the `--tls` argument and add a private and public key at the root of your persistence directory. You may also specify private and public keys by `--tls-cert` and `--tls-key` and set the minimum TLS version to 1.3 using `--tls-min-ver 1.3`.
By default the server uses HTTP protocol. This is not very secure since with Basic Authentication, user name and passwords will be sent in clear text in every request. In order to enable TLS support just add the `--tls` argument and add a private and public key at the root of your persistence directory. You may also specify private and public keys by `--tls-cert` and `--tls-key`.
Signed certificate is normally required by the restic backend, but if you just want to test the feature you can generate password-less unsigned keys with the following command:
@@ -142,16 +139,6 @@ docker exec -it rest_server create_user myuser mypassword
docker exec -it rest_server delete_user myuser
```
## Proxy Authentication
See above for no authentication (`--no-auth`) and basic authentication.
To delegate authentication to a proxy, use the `--proxy-auth-username` flag. The specified header name, for example `X-Forwarded-User`,
must be present in the request headers and specifies the username. Basic authentication is disabled when this flag is set.
Warning: rest-server trusts the username in the header. It is the responsibility of the proxy
to ensure that the username is correct and cannot be forged by an attacker.
## Prometheus support and Grafana dashboard
@@ -160,10 +147,6 @@ The server can be started with `--prometheus` to expose [Prometheus](https://pro
This repository contains an example full stack Docker Compose setup with a Grafana dashboard in [examples/compose-with-grafana/](examples/compose-with-grafana/).
## Group-accessible Repositories
Rest-server supports making repositories accessible to the filesystem group by setting the `--group-accessible-repos` option. Note that permissions of existing files are not modified. To allow the group to read and write file, use a umask of `007`. To only grant read access use `027`. To make an existing repository group-accessible, use `chmod -R g+rwX /path/to/repo`.
## Why use Rest Server?
Compared to the SFTP backend, the REST backend has better performance, especially so if you can skip additional crypto overhead by using plain HTTP transport (restic already properly encrypts all data it sends, so using HTTPS is mostly about authentication).

View File

@@ -34,7 +34,7 @@
use another config file):
goreleaser \
release --parallelism 4 \
release \
--release-notes <(calens --template changelog/CHANGELOG-GitHub.tmpl --version "${VERSION}")
7. Set a new version in `main.go` and commit the result:

View File

@@ -1 +1 @@
0.14.0
0.13.0

View File

@@ -58,7 +58,7 @@ var config = Config{
Namespace: "github.com/restic/rest-server", // subdir of GOPATH, e.g. "github.com/foo/bar"
Main: "github.com/restic/rest-server/cmd/rest-server", // package name for the main package
Tests: []string{"./..."}, // tests to run
MinVersion: GoVersion{Major: 1, Minor: 23, Patch: 0}, // minimum Go version supported
MinVersion: GoVersion{Major: 1, Minor: 15, Patch: 0}, // minimum Go version supported
}
// Config configures the build.

View File

@@ -1,10 +0,0 @@
Enhancement: Support group accessible repositories
Rest-server now supports making repositories accessible to the filesystem group
by setting the `--group-accessible-repos` option. Note that permissions of
existing files are not modified. To allow the group to read and write file,
use a umask of `007`. To only grant read access use `027`. To make an existing
repository group-accessible, use `chmod -R g+rwX /path/to/repo`.
https://github.com/restic/rest-server/issues/189
https://github.com/restic/rest-server/pull/308

View File

@@ -1,13 +0,0 @@
Security: Fix world-readable permissions on new `.htpasswd` files
On startup the rest-server Docker container creates an empty `.htpasswd` file
if none exists yet. This file was world-readable by default, which can be
a security risk, even though the file only contains hashed passwords.
This has been fixed such that new `.htpasswd` files are no longer world-readabble.
The permissions of existing `.htpasswd` files must be manually changed if
relevant in your setup.
https://github.com/restic/rest-server/issues/318
https://github.com/restic/rest-server/pull/340

View File

@@ -1,7 +0,0 @@
Enhancement: Add zip archive format for Windows releases
Windows users can now download rest-server binaries in zip archive format (.zip)
in addition to the existing tar.gz archives.
https://github.com/restic/rest-server/issues/321
https://github.com/restic/rest-server/pull/346

View File

@@ -1,5 +0,0 @@
Enhancement: Output status of append-only mode on startup
Rest-server now displays the status of append-only mode during startup.
https://github.com/restic/rest-server/pull/295

View File

@@ -1,12 +0,0 @@
Enhancement: Support proxy-based authentication
Rest-server now supports authentication via HTTP proxy headers. This feature can
be enabled by specifying the username header using the `--proxy-auth-username`
option (e.g., `--proxy-auth-username=X-Forwarded-User`).
When enabled, the server authenticates users based on the specified header and
disables Basic Auth. Note that proxy authentication is disabled when `--no-auth`
is set.
https://github.com/restic/rest-server/issues/174
https://github.com/restic/rest-server/pull/307

View File

@@ -1,7 +0,0 @@
Enhancement: Hardened tls settings
Rest-server now uses a secure TLS cipher suite set by default. The minimum TLS
version is now TLS 1.2 and can be further increased using the new `--tls-min-ver`
option, allowing users to enforce stricter security requirements.
https://github.com/restic/rest-server/pull/315

View File

@@ -1,11 +0,0 @@
Change: Update dependencies and require Go 1.23 or newer
All dependencies have been updated. Rest-server now requires Go 1.23 or newer
to build.
This also disables support for TLS versions older than TLS 1.2. On Windows,
rest-server now requires at least Windows 10 or Windows Server 2016. On macOS,
rest-server now requires at least macOS 11 Big Sur.
https://github.com/restic/rest-server/pull/322
https://github.com/restic/rest-server/pull/338

View File

@@ -0,0 +1,5 @@
Enhancement: Output status of append only mode on startup
Rest-server now outputs whether append only mode has been enabled on startup.
https://github.com/restic/rest-server/pull/295

View File

@@ -61,10 +61,7 @@ func TestUnixSocket(t *testing.T) {
if err != nil {
return err
}
err = resp.Body.Close()
if err != nil {
return err
}
resp.Body.Close()
if resp.StatusCode != test.StatusCode {
return fmt.Errorf("expected %d from server, instead got %d (path %s)", test.StatusCode, resp.StatusCode, test.Path)
}

View File

@@ -2,7 +2,6 @@ package main
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log"
@@ -23,7 +22,7 @@ import (
type restServerApp struct {
CmdRoot *cobra.Command
Server restserver.Server
CPUProfile string
CpuProfile string
listenerAddressMu sync.Mutex
listenerAddress net.Addr // set after startup
@@ -37,7 +36,7 @@ func newRestServerApp() *restServerApp {
Short: "Run a REST server for use with restic",
SilenceErrors: true,
SilenceUsage: true,
Args: func(_ *cobra.Command, args []string) error {
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return fmt.Errorf("rest-server expects no arguments - unknown argument: %s", args[0])
}
@@ -46,15 +45,14 @@ func newRestServerApp() *restServerApp {
Version: fmt.Sprintf("rest-server %s compiled with %v on %v/%v\n", version, runtime.Version(), runtime.GOOS, runtime.GOARCH),
},
Server: restserver.Server{
Path: filepath.Join(os.TempDir(), "restic"),
Listen: ":8000",
TLSMinVer: "1.2",
Path: filepath.Join(os.TempDir(), "restic"),
Listen: ":8000",
},
}
rv.CmdRoot.RunE = rv.runRoot
flags := rv.CmdRoot.Flags()
flags.StringVar(&rv.CPUProfile, "cpu-profile", rv.CPUProfile, "write CPU profile to file")
flags.StringVar(&rv.CpuProfile, "cpu-profile", rv.CpuProfile, "write CPU profile to file")
flags.BoolVar(&rv.Server.Debug, "debug", rv.Server.Debug, "output debug messages")
flags.StringVar(&rv.Server.Listen, "listen", rv.Server.Listen, "listen address")
flags.StringVar(&rv.Server.Log, "log", rv.Server.Log, "write HTTP requests in the combined log format to the specified `filename` (use \"-\" for logging to stdout)")
@@ -63,22 +61,20 @@ func newRestServerApp() *restServerApp {
flags.BoolVar(&rv.Server.TLS, "tls", rv.Server.TLS, "turn on TLS support")
flags.StringVar(&rv.Server.TLSCert, "tls-cert", rv.Server.TLSCert, "TLS certificate path")
flags.StringVar(&rv.Server.TLSKey, "tls-key", rv.Server.TLSKey, "TLS key path")
flags.StringVar(&rv.Server.TLSMinVer, "tls-min-ver", rv.Server.TLSMinVer, "TLS min version, one of (1.2|1.3)")
flags.BoolVar(&rv.Server.NoAuth, "no-auth", rv.Server.NoAuth, "disable authentication")
flags.BoolVar(&rv.Server.NoAuth, "no-auth", rv.Server.NoAuth, "disable .htpasswd authentication")
flags.StringVar(&rv.Server.HtpasswdPath, "htpasswd-file", rv.Server.HtpasswdPath, "location of .htpasswd file (default: \"<data directory>/.htpasswd)\"")
flags.StringVar(&rv.Server.ProxyAuthUsername, "proxy-auth-username", rv.Server.ProxyAuthUsername, "specifies the HTTP header containing the username for proxy-based authentication")
flags.BoolVar(&rv.Server.NoVerifyUpload, "no-verify-upload", rv.Server.NoVerifyUpload,
"do not verify the integrity of uploaded data. DO NOT enable unless the rest-server runs on a very low-power device")
flags.BoolVar(&rv.Server.AppendOnly, "append-only", rv.Server.AppendOnly, "enable append only mode")
flags.BoolVar(&rv.Server.PrivateRepos, "private-repos", rv.Server.PrivateRepos, "users can only access their private repo")
flags.BoolVar(&rv.Server.Prometheus, "prometheus", rv.Server.Prometheus, "enable Prometheus metrics")
flags.BoolVar(&rv.Server.PrometheusNoAuth, "prometheus-no-auth", rv.Server.PrometheusNoAuth, "disable auth for Prometheus /metrics endpoint")
flags.BoolVar(&rv.Server.GroupAccessibleRepos, "group-accessible-repos", rv.Server.GroupAccessibleRepos, "let filesystem group be able to access repo files")
flags.BoolVar(&rv.Server.GroupAccessibleRepos, "group-accessible-repos", rv.Server.GroupAccessibleRepos, "let filesystem group be able to read repo files")
return rv
}
var version = "0.14.0"
var version = "0.13.0"
func (app *restServerApp) tlsSettings() (bool, string, string, error) {
var key, cert string
@@ -108,19 +104,17 @@ func (app *restServerApp) ListenerAddress() net.Addr {
return app.listenerAddress
}
func (app *restServerApp) runRoot(_ *cobra.Command, _ []string) error {
func (app *restServerApp) runRoot(cmd *cobra.Command, args []string) error {
log.SetFlags(0)
log.Printf("Data directory: %s", app.Server.Path)
if app.CPUProfile != "" {
f, err := os.Create(app.CPUProfile)
if app.CpuProfile != "" {
f, err := os.Create(app.CpuProfile)
if err != nil {
return err
}
defer func() {
_ = f.Close()
}()
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
return err
@@ -134,11 +128,7 @@ func (app *restServerApp) runRoot(_ *cobra.Command, _ []string) error {
if app.Server.NoAuth {
log.Println("Authentication disabled")
} else {
if app.Server.ProxyAuthUsername == "" {
log.Println("Authentication enabled")
} else {
log.Println("Proxy Authentication enabled.")
}
log.Println("Authentication enabled")
}
handler, err := restserver.NewHandler(&app.Server)
@@ -179,29 +169,8 @@ func (app *restServerApp) runRoot(_ *cobra.Command, _ []string) error {
app.listenerAddress = listener.Addr()
app.listenerAddressMu.Unlock()
tlscfg := &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
},
}
switch app.Server.TLSMinVer {
case "1.2":
tlscfg.MinVersion = tls.VersionTLS12
case "1.3":
tlscfg.MinVersion = tls.VersionTLS13
default:
return fmt.Errorf("Unsupported TLS min version: %s. Allowed versions are 1.2 or 1.3", app.Server.TLSMinVer)
}
srv := &http.Server{
Handler: handler,
TLSConfig: tlscfg,
Handler: handler,
}
// run server in background

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
@@ -93,7 +94,7 @@ func TestTLSSettings(t *testing.T) {
}
func TestGetHandler(t *testing.T) {
dir, err := os.MkdirTemp("", "rest-server-test")
dir, err := ioutil.TempDir("", "rest-server-test")
if err != nil {
t.Fatal(err)
}
@@ -118,14 +119,8 @@ func TestGetHandler(t *testing.T) {
t.Errorf("NoAuth=true: expected no error, got %v", err)
}
// With NoAuth = false, no .htpasswd and ProxyAuth = X-Remote-User
_, err = getHandler(&restserver.Server{Path: dir, ProxyAuthUsername: "X-Remote-User"})
if err != nil {
t.Errorf("NoAuth=false, ProxyAuthUsername = X-Remote-User: expected no error, got %v", err)
}
// With NoAuth = false and custom .htpasswd
htpFile, err := os.CreateTemp(dir, "custom")
htpFile, err := ioutil.TempFile(dir, "custom")
if err != nil {
t.Fatal(err)
}
@@ -142,7 +137,7 @@ func TestGetHandler(t *testing.T) {
// Create .htpasswd
htpasswd := filepath.Join(dir, ".htpasswd")
err = os.WriteFile(htpasswd, []byte(""), 0644)
err = ioutil.WriteFile(htpasswd, []byte(""), 0644)
if err != nil {
t.Fatal(err)
}
@@ -267,10 +262,7 @@ func TestHttpListen(t *testing.T) {
if err != nil {
return err
}
err = resp.Body.Close()
if err != nil {
return err
}
resp.Body.Close()
if resp.StatusCode != test.StatusCode {
return fmt.Errorf("expected %d from server, instead got %d (path %s)", test.StatusCode, resp.StatusCode, test.Path)
}

View File

@@ -9,8 +9,8 @@ fi
if [ -z "$2" ]; then
# password from prompt
htpasswd -B "$PASSWORD_FILE" "$1"
htpasswd -B $PASSWORD_FILE $1
else
# read password from command line
htpasswd -B -b "$PASSWORD_FILE" "$1" "$2"
htpasswd -B -b $PASSWORD_FILE $1 $2
fi

View File

@@ -5,4 +5,4 @@ if [ -z "$1" ]; then
exit 1
fi
htpasswd -D "$PASSWORD_FILE" "$1"
htpasswd -D $PASSWORD_FILE $1

View File

@@ -6,7 +6,7 @@ if [ -n "$DISABLE_AUTHENTICATION" ]; then
OPTIONS="--no-auth $OPTIONS"
else
if [ ! -f "$PASSWORD_FILE" ]; then
( umask 027 && touch "$PASSWORD_FILE" )
touch "$PASSWORD_FILE"
fi
if [ ! -s "$PASSWORD_FILE" ]; then

View File

@@ -26,10 +26,8 @@ RestartSec=5
# The following line must be customised to your individual requirements.
ReadWritePaths=/path/to/backups
# Files in the data repository are only user accessible by default. Default to
# `UMask=077` for consistency. To make created files group-readable, set to
# `UMask=007` and pass `--group-accessible-repos` to rest-server via `ExecStart`.
UMask=077
# Makes created files group-readable, but inaccessible by others
UMask=027
# If your system doesn't support all of the features below (e.g. because of
# the use of an older version of systemd), you may wish to comment-out

28
go.mod
View File

@@ -1,28 +1,28 @@
module github.com/restic/rest-server
go 1.23.0
go 1.18
require (
github.com/coreos/go-systemd/v22 v22.5.0
github.com/gorilla/handlers v1.5.2
github.com/minio/sha256-simd v1.0.1
github.com/miolini/datacounter v1.0.3
github.com/prometheus/client_golang v1.22.0
github.com/spf13/cobra v1.9.1
golang.org/x/crypto v0.38.0
github.com/prometheus/client_golang v1.18.0
github.com/spf13/cobra v1.8.1
golang.org/x/crypto v0.27.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
golang.org/x/sys v0.33.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.25.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
)

68
go.sum
View File

@@ -1,56 +1,46 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/miolini/datacounter v1.0.3 h1:tanOZPVblGXQl7/bSZWoEM8l4KK83q24qwQLMrO/HOA=
github.com/miolini/datacounter v1.0.3/go.mod h1:C45dc2hBumHjDpEU64IqPwR6TDyPVpzOqqRTN7zmBUA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -22,10 +22,8 @@ type Server struct {
CPUProfile string
TLSKey string
TLSCert string
TLSMinVer string
TLS bool
NoAuth bool
ProxyAuthUsername string
AppendOnly bool
PrivateRepos bool
Prometheus bool

View File

@@ -6,6 +6,7 @@ import (
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
@@ -164,7 +165,7 @@ func createOverwriteDeleteSeq(t testing.TB, path string, data string) []TestRequ
return req
}
func createTestHandler(t *testing.T, conf *Server) (http.Handler, string, string, string, func()) {
func createTestHandler(t *testing.T, conf Server) (http.Handler, string, string, string, func()) {
buf := make([]byte, 32)
_, err := io.ReadFull(rand.Reader, buf)
if err != nil {
@@ -175,7 +176,7 @@ func createTestHandler(t *testing.T, conf *Server) (http.Handler, string, string
fileID := hex.EncodeToString(dataHash[:])
// setup the server with a local backend in a temporary directory
tempdir, err := os.MkdirTemp("", "rest-server-test-")
tempdir, err := ioutil.TempDir("", "rest-server-test-")
if err != nil {
t.Fatal(err)
}
@@ -189,7 +190,7 @@ func createTestHandler(t *testing.T, conf *Server) (http.Handler, string, string
}
conf.Path = tempdir
mux, err := NewHandler(conf)
mux, err := NewHandler(&conf)
if err != nil {
t.Fatalf("error from NewHandler: %v", err)
}
@@ -198,7 +199,7 @@ func createTestHandler(t *testing.T, conf *Server) (http.Handler, string, string
// TestResticAppendOnlyHandler runs tests on the restic handler code, especially in append-only mode.
func TestResticAppendOnlyHandler(t *testing.T) {
mux, data, fileID, _, cleanup := createTestHandler(t, &Server{
mux, data, fileID, _, cleanup := createTestHandler(t, Server{
AppendOnly: true,
NoAuth: true,
Debug: true,
@@ -299,7 +300,7 @@ func createIdempotentDeleteSeq(t testing.TB, path string, data string) []TestReq
// TestResticHandler runs tests on the restic handler code, especially in append-only mode.
func TestResticHandler(t *testing.T) {
mux, data, fileID, _, cleanup := createTestHandler(t, &Server{
mux, data, fileID, _, cleanup := createTestHandler(t, Server{
NoAuth: true,
Debug: true,
PanicOnError: true,
@@ -330,7 +331,7 @@ func TestResticHandler(t *testing.T) {
// TestResticErrorHandler runs tests on the restic handler error handling.
func TestResticErrorHandler(t *testing.T) {
mux, _, _, tempdir, cleanup := createTestHandler(t, &Server{
mux, _, _, tempdir, cleanup := createTestHandler(t, Server{
AppendOnly: true,
NoAuth: true,
Debug: true,
@@ -379,7 +380,7 @@ func TestResticErrorHandler(t *testing.T) {
}
func TestEmptyList(t *testing.T) {
mux, _, _, _, cleanup := createTestHandler(t, &Server{
mux, _, _, _, cleanup := createTestHandler(t, Server{
AppendOnly: true,
NoAuth: true,
Debug: true,
@@ -403,7 +404,7 @@ func TestEmptyList(t *testing.T) {
}
func TestListWithUnexpectedFiles(t *testing.T) {
mux, _, _, tempdir, cleanup := createTestHandler(t, &Server{
mux, _, _, tempdir, cleanup := createTestHandler(t, Server{
AppendOnly: true,
NoAuth: true,
Debug: true,
@@ -509,7 +510,7 @@ func newDelayedErrorReader(err error) *delayErrorReader {
}
}
func (d *delayErrorReader) Read(_ []byte) (int, error) {
func (d *delayErrorReader) Read(p []byte) (int, error) {
d.firstReadOnce.Do(func() {
// close the channel to signal that the first read has happened
close(d.FirstRead)
@@ -521,7 +522,7 @@ func (d *delayErrorReader) Read(_ []byte) (int, error) {
// TestAbortedRequest runs tests with concurrent upload requests for the same file.
func TestAbortedRequest(t *testing.T) {
// the race condition doesn't happen for append-only repositories
mux, _, _, _, cleanup := createTestHandler(t, &Server{
mux, _, _, _, cleanup := createTestHandler(t, Server{
NoAuth: true,
Debug: true,
PanicOnError: true,

View File

@@ -1,6 +1,7 @@
package restserver
import (
"io/ioutil"
"os"
"testing"
)
@@ -11,7 +12,7 @@ func TestValidate(t *testing.T) {
rawPwd := "test"
wrongPwd := "wrong"
tmpfile, err := os.CreateTemp("", "rest-validate-")
tmpfile, err := ioutil.TempFile("", "rest-validate-")
if err != nil {
t.Fatal(err)
}

17
mux.go
View File

@@ -41,17 +41,10 @@ func (s *Server) checkAuth(r *http.Request) (username string, ok bool) {
if s.NoAuth {
return username, true
}
if s.ProxyAuthUsername != "" {
username = r.Header.Get(s.ProxyAuthUsername)
if username == "" {
return "", false
}
} else {
var password string
username, password, ok = r.BasicAuth()
if !ok || !s.htpasswdFile.Validate(username, password) {
return "", false
}
var password string
username, password, ok = r.BasicAuth()
if !ok || !s.htpasswdFile.Validate(username, password) {
return "", false
}
return username, true
}
@@ -73,7 +66,7 @@ func (s *Server) wrapMetricsAuth(f http.HandlerFunc) http.HandlerFunc {
// NewHandler returns the master HTTP multiplexer/router.
func NewHandler(server *Server) (http.Handler, error) {
if !server.NoAuth && server.ProxyAuthUsername == "" {
if !server.NoAuth {
var err error
if server.HtpasswdPath == "" {
server.HtpasswdPath = filepath.Join(server.Path, ".htpasswd")

View File

@@ -1,80 +0,0 @@
package restserver
import (
"net/http/httptest"
"testing"
)
func TestCheckAuth(t *testing.T) {
tests := []struct {
name string
server *Server
requestHeaders map[string]string
basicAuth bool
basicUser string
basicPassword string
expectedUser string
expectedOk bool
}{
{
name: "NoAuth enabled",
server: &Server{
NoAuth: true,
},
expectedOk: true,
},
{
name: "Proxy Auth successful",
server: &Server{
ProxyAuthUsername: "X-Remote-User",
},
requestHeaders: map[string]string{
"X-Remote-User": "restic",
},
expectedUser: "restic",
expectedOk: true,
},
{
name: "Proxy Auth empty header",
server: &Server{
ProxyAuthUsername: "X-Remote-User",
},
requestHeaders: map[string]string{
"X-Remote-User": "",
},
expectedOk: false,
},
{
name: "Proxy Auth missing header",
server: &Server{
ProxyAuthUsername: "X-Remote-User",
},
expectedOk: false,
},
{
name: "Proxy Auth send but not enabled",
server: &Server{},
requestHeaders: map[string]string{
"X-Remote-User": "restic",
},
expectedOk: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
for header, value := range tt.requestHeaders {
req.Header.Set(header, value)
}
if tt.basicAuth {
req.SetBasicAuth(tt.basicUser, tt.basicPassword)
}
username, ok := tt.server.checkAuth(req)
if username != tt.expectedUser || ok != tt.expectedOk {
t.Errorf("expected (%v, %v), got (%v, %v)", tt.expectedUser, tt.expectedOk, username, ok)
}
})
}
}

View File

@@ -113,7 +113,7 @@ func tallySize(path string) (int64, error) {
path = "."
}
var size int64
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net/http"
@@ -54,12 +55,10 @@ const DefaultDirMode os.FileMode = 0700
// overridden in the Options
const DefaultFileMode os.FileMode = 0600
// GroupAccessibleDirMode is the file mode used for directory creation when
// group access is enabled
// File mode used for directory creation when group access is enabled
const GroupAccessibleDirMode os.FileMode = 0770
// GroupAccessibleFileMode is the file mode used for file creation when
// group access is enabled
// File mode used for file creation when group access is enabled
const GroupAccessibleFileMode os.FileMode = 0660
// New creates a new Handler for a single Restic backup repo.
@@ -266,7 +265,7 @@ func (h *Handler) wrapFileWriter(r *http.Request, w io.Writer) (io.Writer, int,
}
// checkConfig checks whether a configuration exists.
func (h *Handler) checkConfig(w http.ResponseWriter, _ *http.Request) {
func (h *Handler) checkConfig(w http.ResponseWriter, r *http.Request) {
if h.opt.Debug {
log.Println("checkConfig()")
}
@@ -282,13 +281,13 @@ func (h *Handler) checkConfig(w http.ResponseWriter, _ *http.Request) {
}
// getConfig allows for a config to be retrieved.
func (h *Handler) getConfig(w http.ResponseWriter, _ *http.Request) {
func (h *Handler) getConfig(w http.ResponseWriter, r *http.Request) {
if h.opt.Debug {
log.Println("getConfig()")
}
cfg := h.getSubPath("config")
bytes, err := os.ReadFile(cfg)
bytes, err := ioutil.ReadFile(cfg)
if err != nil {
h.fileAccessError(w, err)
return
@@ -329,7 +328,7 @@ func (h *Handler) saveConfig(w http.ResponseWriter, r *http.Request) {
}
// deleteConfig removes a config.
func (h *Handler) deleteConfig(w http.ResponseWriter, _ *http.Request) {
func (h *Handler) deleteConfig(w http.ResponseWriter, r *http.Request) {
if h.opt.Debug {
log.Println("deleteConfig()")
}
@@ -384,7 +383,7 @@ func (h *Handler) listBlobsV1(w http.ResponseWriter, r *http.Request) {
}
path := h.getSubPath(objectType)
items, err := os.ReadDir(path)
items, err := ioutil.ReadDir(path)
if err != nil {
h.fileAccessError(w, err)
return
@@ -398,8 +397,8 @@ func (h *Handler) listBlobsV1(w http.ResponseWriter, r *http.Request) {
continue
}
subpath := filepath.Join(path, i.Name())
var subitems []os.DirEntry
subitems, err = os.ReadDir(subpath)
var subitems []os.FileInfo
subitems, err = ioutil.ReadDir(subpath)
if err != nil {
h.fileAccessError(w, err)
return
@@ -443,7 +442,7 @@ func (h *Handler) listBlobsV2(w http.ResponseWriter, r *http.Request) {
}
path := h.getSubPath(objectType)
items, err := os.ReadDir(path)
items, err := ioutil.ReadDir(path)
if err != nil {
h.fileAccessError(w, err)
return
@@ -457,27 +456,17 @@ func (h *Handler) listBlobsV2(w http.ResponseWriter, r *http.Request) {
continue
}
subpath := filepath.Join(path, i.Name())
var subitems []os.DirEntry
subitems, err = os.ReadDir(subpath)
var subitems []os.FileInfo
subitems, err = ioutil.ReadDir(subpath)
if err != nil {
h.fileAccessError(w, err)
return
}
for _, f := range subitems {
fi, err := f.Info()
if err != nil {
h.fileAccessError(w, err)
return
}
blobs = append(blobs, Blob{Name: f.Name(), Size: fi.Size()})
blobs = append(blobs, Blob{Name: f.Name(), Size: f.Size()})
}
} else {
fi, err := i.Info()
if err != nil {
h.fileAccessError(w, err)
return
}
blobs = append(blobs, Blob{Name: i.Name(), Size: fi.Size()})
blobs = append(blobs, Blob{Name: i.Name(), Size: i.Size()})
}
}
@@ -677,7 +666,7 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) {
h.sendMetric(objectType, BlobWrite, uint64(written))
}
// tempFile implements a custom version of os.CreateTemp which allows modifying the file permissions
// tempFile implements a custom version of ioutil.TempFile which allows modifying the file permissions
func tempFile(fn string, perm os.FileMode) (f *os.File, err error) {
for i := 0; i < 10; i++ {
name := fn + strconv.FormatInt(rand.Int63(), 10)