mirror of
https://github.com/restic/rest-server.git
synced 2025-12-06 17:15:45 -08:00
Compare commits
58 Commits
v0.13.0
...
654fa16cb2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
654fa16cb2 | ||
|
|
cc352125b8 | ||
|
|
822a8dca64 | ||
|
|
334ddf15ea | ||
|
|
2f31e10ceb | ||
|
|
ad130de021 | ||
|
|
2aaa048aba | ||
|
|
b6ec6f45cc | ||
|
|
2a77536ce5 | ||
|
|
0adcfa2619 | ||
|
|
9f8bb0c87c | ||
|
|
5faeedf050 | ||
|
|
7294612990 | ||
|
|
25066228ee | ||
|
|
72a7319fae | ||
|
|
df5330773f | ||
|
|
2bb4d251e2 | ||
|
|
f018e99109 | ||
|
|
95538fe956 | ||
|
|
4e6193ceee | ||
|
|
4c368ae1fb | ||
|
|
0ed9de379e | ||
|
|
451c4831f9 | ||
|
|
1610cf6cef | ||
|
|
3d35116b3c | ||
|
|
eee73d3bc1 | ||
|
|
df7b13e18f | ||
|
|
2d3e02017b | ||
|
|
2b6f0b39fc | ||
|
|
dbf5253ac2 | ||
|
|
fa15677855 | ||
|
|
19aa0845c0 | ||
|
|
04b52b0cee | ||
|
|
f2d406ff2e | ||
|
|
8ad7cfa60a | ||
|
|
4f17744d6c | ||
|
|
68ae5d1c0b | ||
|
|
0dfc772cdb | ||
|
|
b0a9a0452e | ||
|
|
f053e33486 | ||
|
|
10a06dcbf1 | ||
|
|
b05b44cb2c | ||
|
|
a976a2145b | ||
|
|
751d2841f3 | ||
|
|
5938f9aacd | ||
|
|
376392a89c | ||
|
|
13f461740d | ||
|
|
e9e6529345 | ||
|
|
a0110bb902 | ||
|
|
82c5a314f9 | ||
|
|
9fc5066fc4 | ||
|
|
3e0737a8bd | ||
|
|
9195526406 | ||
|
|
2513a698f3 | ||
|
|
bdfb047edf | ||
|
|
e35c6e39d9 | ||
|
|
da5bb66030 | ||
|
|
664d997006 |
39
.github/ISSUE_TEMPLATE/BUG.md
vendored
39
.github/ISSUE_TEMPLATE/BUG.md
vendored
@@ -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,6 +24,7 @@ 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,24 +32,26 @@ Output of `rest-server --version` <!-- If using docker, output of `docker images
|
||||
---------------------------------
|
||||
|
||||
|
||||
How did you run rest-server exactly?
|
||||
------------------------------------
|
||||
|
||||
Problem description / Steps to reproduce
|
||||
----------------------------------------
|
||||
|
||||
<!--
|
||||
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's backend access. Make sure to replace sensitive values!
|
||||
configure rest-server. 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
|
||||
-----------------
|
||||
|
||||
@@ -56,30 +59,20 @@ Expected behavior
|
||||
Describe what you'd like rest-server to do differently.
|
||||
-->
|
||||
|
||||
|
||||
Actual behavior
|
||||
---------------
|
||||
|
||||
<!--
|
||||
Please try to concentrate on observations, so only describe what you observed directly.
|
||||
In this section, 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?
|
||||
-----------------------------------------------
|
||||
|
||||
|
||||
Do you have an idea how to solve the issue?
|
||||
-------------------------------------------
|
||||
<!--
|
||||
Did something noteworthy happen on your system, Internet connection, backend services, etc?
|
||||
-->
|
||||
|
||||
|
||||
Did rest-server help you today? Did it make you happy in any way?
|
||||
|
||||
17
.github/ISSUE_TEMPLATE/FEATURE.md
vendored
17
.github/ISSUE_TEMPLATE/FEATURE.md
vendored
@@ -1,10 +1,10 @@
|
||||
---
|
||||
name: Feature request/enhancement
|
||||
name: Feature request
|
||||
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,6 +18,7 @@ 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!
|
||||
|
||||
-->
|
||||
|
||||
|
||||
@@ -30,24 +31,22 @@ later to see what has changed in rest-server when we revisit this issue after so
|
||||
time.
|
||||
-->
|
||||
|
||||
|
||||
What should rest-server do differently?
|
||||
---------------------------------------
|
||||
What should rest-server do differently? Which functionality do you think we should add?
|
||||
---------------------------------------------------------------------------------------
|
||||
|
||||
<!--
|
||||
Please describe the feature you'd like to see added or changed here.
|
||||
Please describe the feature you'd like us to add here.
|
||||
-->
|
||||
|
||||
|
||||
What are you trying to do? What is your use case?
|
||||
-------------------------------------------------
|
||||
What are you trying to do? What problem would this solve?
|
||||
---------------------------------------------------------
|
||||
|
||||
<!--
|
||||
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?
|
||||
-----------------------------------------------------------------
|
||||
|
||||
|
||||
51
.github/PULL_REQUEST_TEMPLATE.md
vendored
51
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,42 +1,41 @@
|
||||
<!--
|
||||
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.
|
||||
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.
|
||||
-->
|
||||
|
||||
|
||||
What is the purpose of this change? What does it change?
|
||||
--------------------------------------------------------
|
||||
What does this PR change? What problem does it solve?
|
||||
-----------------------------------------------------
|
||||
|
||||
<!--
|
||||
Describe the changes here, as detailed as needed.
|
||||
Describe the changes and their purpose here, as detailed as needed.
|
||||
-->
|
||||
|
||||
|
||||
Was the change discussed in an issue or in the forum before?
|
||||
------------------------------------------------------------
|
||||
Was the change previously discussed in an issue or on the forum?
|
||||
----------------------------------------------------------------
|
||||
|
||||
<!--
|
||||
Link issues and relevant forum posts here.
|
||||
|
||||
If this PR resolves an issue on GitHub, write "Closes #1234" such
|
||||
that the issue is closed automatically when this PR is merged.
|
||||
If this PR resolves an issue on GitHub, use "Closes #1234" so that the issue
|
||||
is closed automatically when this PR is merged.
|
||||
-->
|
||||
|
||||
|
||||
Checklist
|
||||
---------
|
||||
|
||||
- [ ] 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
|
||||
<!--
|
||||
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.
|
||||
|
||||
80
.github/workflows/tests.yml
vendored
80
.github/workflows/tests.yml
vendored
@@ -7,48 +7,58 @@ on:
|
||||
|
||||
# run tests for all pull requests
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
latest_go: "1.21.x"
|
||||
latest_go: "1.24.x"
|
||||
GO111MODULE: on
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go:
|
||||
- 1.18.x
|
||||
- 1.19.x
|
||||
- 1.20.x
|
||||
- 1.21.x
|
||||
runs-on: ubuntu-latest
|
||||
name: Go ${{ matrix.go }}
|
||||
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 }}
|
||||
|
||||
env:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Go ${{ matrix.go }}
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
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 tests
|
||||
- name: Run local Tests
|
||||
run: |
|
||||
go test ./...
|
||||
go test -cover ${{matrix.test_opts}} ./...
|
||||
|
||||
- name: Check changelog files with calens
|
||||
run: |
|
||||
@@ -57,27 +67,30 @@ 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@v5
|
||||
|
||||
- name: Set up Go ${{ env.latest_go }}
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
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.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
|
||||
version: v1.64.8
|
||||
args: --verbose --timeout 5m
|
||||
|
||||
# only run golangci-lint for pull requests, otherwise ALL hints get
|
||||
# reported. We need to slowly address all issues until we can enable
|
||||
@@ -89,3 +102,18 @@ 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) }}
|
||||
|
||||
@@ -10,13 +10,10 @@ 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
|
||||
@@ -35,15 +32,14 @@ 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
|
||||
@@ -55,3 +51,6 @@ 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:"
|
||||
|
||||
@@ -21,29 +21,27 @@ before:
|
||||
|
||||
# build a single binary
|
||||
builds:
|
||||
-
|
||||
- id: default
|
||||
# make sure everything is statically linked by disabling cgo altogether
|
||||
env:
|
||||
env: &build_env
|
||||
- CGO_ENABLED=0
|
||||
|
||||
# set the package for the main binary
|
||||
main: ./cmd/rest-server
|
||||
|
||||
flags:
|
||||
# don't include any paths to source files in the resulting binary
|
||||
&build_flags # don't include any paths to source files in the resulting binary
|
||||
- -trimpath
|
||||
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
|
||||
ldflags:
|
||||
# set the version variable in the main package
|
||||
ldflags: &build_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
|
||||
@@ -52,7 +50,7 @@ builds:
|
||||
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- "386"
|
||||
- arm
|
||||
- arm64
|
||||
- mips
|
||||
@@ -61,40 +59,63 @@ builds:
|
||||
- ppc64
|
||||
- ppc64le
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
- "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
|
||||
|
||||
# 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:
|
||||
files: &archive_files
|
||||
- src: LICENSE
|
||||
dst: .
|
||||
dst: LICENSE
|
||||
info: *archive_file_info
|
||||
- src: README.md
|
||||
dst: .
|
||||
dst: README.md
|
||||
info: *archive_file_info
|
||||
- src: CHANGELOG.md
|
||||
dst: .
|
||||
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:
|
||||
@@ -128,7 +149,7 @@ dockers:
|
||||
- docker/entrypoint.sh
|
||||
- image_templates:
|
||||
- restic/rest-server:{{ .Version }}-i386
|
||||
goarch: 386
|
||||
goarch: "386"
|
||||
build_flag_templates:
|
||||
- "--platform=linux/386"
|
||||
- "--pull"
|
||||
@@ -204,21 +225,20 @@ 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"
|
||||
|
||||
95
CHANGELOG.md
95
CHANGELOG.md
@@ -1,3 +1,98 @@
|
||||
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)
|
||||
============================================
|
||||
|
||||
|
||||
57
README.md
57
README.md
@@ -11,7 +11,7 @@ Rest Server is a high performance HTTP server that implements restic's [REST bac
|
||||
|
||||
## Requirements
|
||||
|
||||
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.
|
||||
Rest Server requires Go 1.23 or higher to build. The only tested compiler is the official Go compiler.
|
||||
|
||||
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,24 +32,27 @@ Usage:
|
||||
rest-server [flags]
|
||||
|
||||
Flags:
|
||||
--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
|
||||
--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
|
||||
```
|
||||
|
||||
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:
|
||||
@@ -68,7 +71,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`.
|
||||
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`.
|
||||
|
||||
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:
|
||||
|
||||
@@ -139,6 +142,16 @@ 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
|
||||
|
||||
@@ -147,6 +160,10 @@ 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).
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
use another config file):
|
||||
|
||||
goreleaser \
|
||||
release \
|
||||
release --parallelism 4 \
|
||||
--release-notes <(calens --template changelog/CHANGELOG-GitHub.tmpl --version "${VERSION}")
|
||||
|
||||
7. Set a new version in `main.go` and commit the result:
|
||||
|
||||
2
build.go
2
build.go
@@ -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: 15, Patch: 0}, // minimum Go version supported
|
||||
MinVersion: GoVersion{Major: 1, Minor: 23, Patch: 0}, // minimum Go version supported
|
||||
}
|
||||
|
||||
// Config configures the build.
|
||||
|
||||
10
changelog/0.14.0_2025-05-31/issue-189
Normal file
10
changelog/0.14.0_2025-05-31/issue-189
Normal file
@@ -0,0 +1,10 @@
|
||||
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
|
||||
13
changelog/0.14.0_2025-05-31/issue-318
Normal file
13
changelog/0.14.0_2025-05-31/issue-318
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
7
changelog/0.14.0_2025-05-31/issue-321
Normal file
7
changelog/0.14.0_2025-05-31/issue-321
Normal file
@@ -0,0 +1,7 @@
|
||||
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
|
||||
5
changelog/0.14.0_2025-05-31/pull-295
Normal file
5
changelog/0.14.0_2025-05-31/pull-295
Normal file
@@ -0,0 +1,5 @@
|
||||
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
|
||||
12
changelog/0.14.0_2025-05-31/pull-307
Normal file
12
changelog/0.14.0_2025-05-31/pull-307
Normal file
@@ -0,0 +1,12 @@
|
||||
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
|
||||
7
changelog/0.14.0_2025-05-31/pull-315
Normal file
7
changelog/0.14.0_2025-05-31/pull-315
Normal file
@@ -0,0 +1,7 @@
|
||||
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
|
||||
11
changelog/0.14.0_2025-05-31/pull-322
Normal file
11
changelog/0.14.0_2025-05-31/pull-322
Normal file
@@ -0,0 +1,11 @@
|
||||
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
|
||||
@@ -61,7 +61,10 @@ func TestUnixSocket(t *testing.T) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != test.StatusCode {
|
||||
return fmt.Errorf("expected %d from server, instead got %d (path %s)", test.StatusCode, resp.StatusCode, test.Path)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -22,7 +23,7 @@ import (
|
||||
type restServerApp struct {
|
||||
CmdRoot *cobra.Command
|
||||
Server restserver.Server
|
||||
CpuProfile string
|
||||
CPUProfile string
|
||||
|
||||
listenerAddressMu sync.Mutex
|
||||
listenerAddress net.Addr // set after startup
|
||||
@@ -36,7 +37,7 @@ func newRestServerApp() *restServerApp {
|
||||
Short: "Run a REST server for use with restic",
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
Args: func(_ *cobra.Command, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return fmt.Errorf("rest-server expects no arguments - unknown argument: %s", args[0])
|
||||
}
|
||||
@@ -45,14 +46,15 @@ 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",
|
||||
Path: filepath.Join(os.TempDir(), "restic"),
|
||||
Listen: ":8000",
|
||||
TLSMinVer: "1.2",
|
||||
},
|
||||
}
|
||||
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)")
|
||||
@@ -61,19 +63,22 @@ 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.BoolVar(&rv.Server.NoAuth, "no-auth", rv.Server.NoAuth, "disable .htpasswd authentication")
|
||||
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.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")
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
var version = "0.13.0"
|
||||
var version = "0.14.0-dev"
|
||||
|
||||
func (app *restServerApp) tlsSettings() (bool, string, string, error) {
|
||||
var key, cert string
|
||||
@@ -103,17 +108,19 @@ func (app *restServerApp) ListenerAddress() net.Addr {
|
||||
return app.listenerAddress
|
||||
}
|
||||
|
||||
func (app *restServerApp) runRoot(cmd *cobra.Command, args []string) error {
|
||||
func (app *restServerApp) runRoot(_ *cobra.Command, _ []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 f.Close()
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
return err
|
||||
@@ -127,7 +134,11 @@ func (app *restServerApp) runRoot(cmd *cobra.Command, args []string) error {
|
||||
if app.Server.NoAuth {
|
||||
log.Println("Authentication disabled")
|
||||
} else {
|
||||
log.Println("Authentication enabled")
|
||||
if app.Server.ProxyAuthUsername == "" {
|
||||
log.Println("Authentication enabled")
|
||||
} else {
|
||||
log.Println("Proxy Authentication enabled.")
|
||||
}
|
||||
}
|
||||
|
||||
handler, err := restserver.NewHandler(&app.Server)
|
||||
@@ -135,12 +146,24 @@ func (app *restServerApp) runRoot(cmd *cobra.Command, args []string) error {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
if app.Server.AppendOnly {
|
||||
log.Println("Append only mode enabled")
|
||||
} else {
|
||||
log.Println("Append only mode disabled")
|
||||
}
|
||||
|
||||
if app.Server.PrivateRepos {
|
||||
log.Println("Private repositories enabled")
|
||||
} else {
|
||||
log.Println("Private repositories disabled")
|
||||
}
|
||||
|
||||
if app.Server.GroupAccessibleRepos {
|
||||
log.Println("Group accessible repos enabled")
|
||||
} else {
|
||||
log.Println("Group accessible repos disabled")
|
||||
}
|
||||
|
||||
enabledTLS, privateKey, publicKey, err := app.tlsSettings()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -156,8 +179,29 @@ func (app *restServerApp) runRoot(cmd *cobra.Command, args []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,
|
||||
Handler: handler,
|
||||
TLSConfig: tlscfg,
|
||||
}
|
||||
|
||||
// run server in background
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -94,7 +93,7 @@ func TestTLSSettings(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetHandler(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "rest-server-test")
|
||||
dir, err := os.MkdirTemp("", "rest-server-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -119,8 +118,14 @@ 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 := ioutil.TempFile(dir, "custom")
|
||||
htpFile, err := os.CreateTemp(dir, "custom")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -137,7 +142,7 @@ func TestGetHandler(t *testing.T) {
|
||||
|
||||
// Create .htpasswd
|
||||
htpasswd := filepath.Join(dir, ".htpasswd")
|
||||
err = ioutil.WriteFile(htpasswd, []byte(""), 0644)
|
||||
err = os.WriteFile(htpasswd, []byte(""), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -262,7 +267,10 @@ func TestHttpListen(t *testing.T) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != test.StatusCode {
|
||||
return fmt.Errorf("expected %d from server, instead got %d (path %s)", test.StatusCode, resp.StatusCode, test.Path)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,4 +5,4 @@ if [ -z "$1" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
htpasswd -D $PASSWORD_FILE $1
|
||||
htpasswd -D "$PASSWORD_FILE" "$1"
|
||||
|
||||
@@ -6,7 +6,7 @@ if [ -n "$DISABLE_AUTHENTICATION" ]; then
|
||||
OPTIONS="--no-auth $OPTIONS"
|
||||
else
|
||||
if [ ! -f "$PASSWORD_FILE" ]; then
|
||||
touch "$PASSWORD_FILE"
|
||||
( umask 027 && touch "$PASSWORD_FILE" )
|
||||
fi
|
||||
|
||||
if [ ! -s "$PASSWORD_FILE" ]; then
|
||||
|
||||
@@ -26,8 +26,10 @@ RestartSec=5
|
||||
# The following line must be customised to your individual requirements.
|
||||
ReadWritePaths=/path/to/backups
|
||||
|
||||
# Makes created files group-readable, but inaccessible by others
|
||||
UMask=027
|
||||
# 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
|
||||
|
||||
# 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
28
go.mod
@@ -1,28 +1,28 @@
|
||||
module github.com/restic/rest-server
|
||||
|
||||
go 1.18
|
||||
go 1.23.0
|
||||
|
||||
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.18.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
golang.org/x/crypto v0.25.0
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
golang.org/x/crypto v0.38.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // 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.22.0 // indirect
|
||||
google.golang.org/protobuf v1.33.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
|
||||
)
|
||||
|
||||
68
go.sum
68
go.sum
@@ -1,46 +1,56 @@
|
||||
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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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/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.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
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/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/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/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/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/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/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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.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=
|
||||
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=
|
||||
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=
|
||||
|
||||
53
handlers.go
53
handlers.go
@@ -15,23 +15,26 @@ import (
|
||||
|
||||
// Server encapsulates the rest-server's settings and repo management logic
|
||||
type Server struct {
|
||||
Path string
|
||||
HtpasswdPath string
|
||||
Listen string
|
||||
Log string
|
||||
CPUProfile string
|
||||
TLSKey string
|
||||
TLSCert string
|
||||
TLS bool
|
||||
NoAuth bool
|
||||
AppendOnly bool
|
||||
PrivateRepos bool
|
||||
Prometheus bool
|
||||
PrometheusNoAuth bool
|
||||
Debug bool
|
||||
MaxRepoSize int64
|
||||
PanicOnError bool
|
||||
NoVerifyUpload bool
|
||||
Path string
|
||||
HtpasswdPath string
|
||||
Listen string
|
||||
Log string
|
||||
CPUProfile string
|
||||
TLSKey string
|
||||
TLSCert string
|
||||
TLSMinVer string
|
||||
TLS bool
|
||||
NoAuth bool
|
||||
ProxyAuthUsername string
|
||||
AppendOnly bool
|
||||
PrivateRepos bool
|
||||
Prometheus bool
|
||||
PrometheusNoAuth bool
|
||||
Debug bool
|
||||
MaxRepoSize int64
|
||||
PanicOnError bool
|
||||
NoVerifyUpload bool
|
||||
GroupAccessibleRepos bool
|
||||
|
||||
htpasswdFile *HtpasswdFile
|
||||
quotaManager *quota.Manager
|
||||
@@ -88,12 +91,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Pass the request to the repo.Handler
|
||||
opt := repo.Options{
|
||||
AppendOnly: s.AppendOnly,
|
||||
Debug: s.Debug,
|
||||
QuotaManager: s.quotaManager, // may be nil
|
||||
PanicOnError: s.PanicOnError,
|
||||
NoVerifyUpload: s.NoVerifyUpload,
|
||||
FsyncWarning: &s.fsyncWarning,
|
||||
AppendOnly: s.AppendOnly,
|
||||
Debug: s.Debug,
|
||||
QuotaManager: s.quotaManager, // may be nil
|
||||
PanicOnError: s.PanicOnError,
|
||||
NoVerifyUpload: s.NoVerifyUpload,
|
||||
FsyncWarning: &s.fsyncWarning,
|
||||
GroupAccessible: s.GroupAccessibleRepos,
|
||||
}
|
||||
if s.Prometheus {
|
||||
opt.BlobMetricFunc = makeBlobMetricFunc(username, folderPath)
|
||||
@@ -158,7 +162,8 @@ func join(base string, names ...string) (string, error) {
|
||||
// splitURLPath splits the URL path into a folderPath of the subrepo, and
|
||||
// a remainder that can be passed to repo.Handler.
|
||||
// Example: /foo/bar/locks/0123... will be split into:
|
||||
// ["foo", "bar"] and "/locks/0123..."
|
||||
//
|
||||
// ["foo", "bar"] and "/locks/0123..."
|
||||
func splitURLPath(urlPath string, maxDepth int) (folderPath []string, remainder string) {
|
||||
if !strings.HasPrefix(urlPath, "/") {
|
||||
// Really should start with "/"
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -165,7 +164,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 {
|
||||
@@ -176,7 +175,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 := ioutil.TempDir("", "rest-server-test-")
|
||||
tempdir, err := os.MkdirTemp("", "rest-server-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -190,7 +189,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)
|
||||
}
|
||||
@@ -199,7 +198,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,
|
||||
@@ -300,7 +299,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,
|
||||
@@ -331,7 +330,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,
|
||||
@@ -380,7 +379,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,
|
||||
@@ -404,7 +403,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,
|
||||
@@ -510,7 +509,7 @@ func newDelayedErrorReader(err error) *delayErrorReader {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *delayErrorReader) Read(p []byte) (int, error) {
|
||||
func (d *delayErrorReader) Read(_ []byte) (int, error) {
|
||||
d.firstReadOnce.Do(func() {
|
||||
// close the channel to signal that the first read has happened
|
||||
close(d.FirstRead)
|
||||
@@ -522,7 +521,7 @@ func (d *delayErrorReader) Read(p []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,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package restserver
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
@@ -12,7 +11,7 @@ func TestValidate(t *testing.T) {
|
||||
rawPwd := "test"
|
||||
wrongPwd := "wrong"
|
||||
|
||||
tmpfile, err := ioutil.TempFile("", "rest-validate-")
|
||||
tmpfile, err := os.CreateTemp("", "rest-validate-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
17
mux.go
17
mux.go
@@ -41,10 +41,17 @@ func (s *Server) checkAuth(r *http.Request) (username string, ok bool) {
|
||||
if s.NoAuth {
|
||||
return username, true
|
||||
}
|
||||
var password string
|
||||
username, password, ok = r.BasicAuth()
|
||||
if !ok || !s.htpasswdFile.Validate(username, password) {
|
||||
return "", false
|
||||
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
|
||||
}
|
||||
}
|
||||
return username, true
|
||||
}
|
||||
@@ -66,7 +73,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 {
|
||||
if !server.NoAuth && server.ProxyAuthUsername == "" {
|
||||
var err error
|
||||
if server.HtpasswdPath == "" {
|
||||
server.HtpasswdPath = filepath.Join(server.Path, ".htpasswd")
|
||||
|
||||
80
mux_test.go
Normal file
80
mux_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func tallySize(path string) (int64, error) {
|
||||
path = "."
|
||||
}
|
||||
var size int64
|
||||
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
81
repo/repo.go
81
repo/repo.go
@@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
@@ -29,8 +28,6 @@ import (
|
||||
type Options struct {
|
||||
AppendOnly bool // if set, delete actions are not allowed
|
||||
Debug bool
|
||||
DirMode os.FileMode
|
||||
FileMode os.FileMode
|
||||
NoVerifyUpload bool
|
||||
|
||||
// If set, we will panic when an internal server error happens. This
|
||||
@@ -40,6 +37,13 @@ type Options struct {
|
||||
BlobMetricFunc BlobMetricFunc
|
||||
QuotaManager *quota.Manager
|
||||
FsyncWarning *sync.Once
|
||||
|
||||
// If set makes files group accessible
|
||||
GroupAccessible bool
|
||||
|
||||
// Defaults dir and file mode
|
||||
dirMode os.FileMode
|
||||
fileMode os.FileMode
|
||||
}
|
||||
|
||||
// DefaultDirMode is the file mode used for directory creation if not
|
||||
@@ -50,6 +54,14 @@ 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
|
||||
const GroupAccessibleDirMode os.FileMode = 0770
|
||||
|
||||
// GroupAccessibleFileMode is the 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.
|
||||
// path is the full filesystem path to this repo directory.
|
||||
// opt is a set of options.
|
||||
@@ -57,12 +69,15 @@ func New(path string, opt Options) (*Handler, error) {
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("path is required")
|
||||
}
|
||||
if opt.DirMode == 0 {
|
||||
opt.DirMode = DefaultDirMode
|
||||
}
|
||||
if opt.FileMode == 0 {
|
||||
opt.FileMode = DefaultFileMode
|
||||
|
||||
opt.dirMode = DefaultDirMode
|
||||
opt.fileMode = DefaultFileMode
|
||||
|
||||
if opt.GroupAccessible {
|
||||
opt.dirMode = GroupAccessibleDirMode
|
||||
opt.fileMode = GroupAccessibleFileMode
|
||||
}
|
||||
|
||||
h := Handler{
|
||||
path: path,
|
||||
opt: opt,
|
||||
@@ -251,7 +266,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, r *http.Request) {
|
||||
func (h *Handler) checkConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
if h.opt.Debug {
|
||||
log.Println("checkConfig()")
|
||||
}
|
||||
@@ -267,13 +282,13 @@ func (h *Handler) checkConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// getConfig allows for a config to be retrieved.
|
||||
func (h *Handler) getConfig(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Handler) getConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
if h.opt.Debug {
|
||||
log.Println("getConfig()")
|
||||
}
|
||||
cfg := h.getSubPath("config")
|
||||
|
||||
bytes, err := ioutil.ReadFile(cfg)
|
||||
bytes, err := os.ReadFile(cfg)
|
||||
if err != nil {
|
||||
h.fileAccessError(w, err)
|
||||
return
|
||||
@@ -289,7 +304,7 @@ func (h *Handler) saveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
cfg := h.getSubPath("config")
|
||||
|
||||
f, err := os.OpenFile(cfg, os.O_CREATE|os.O_WRONLY|os.O_EXCL, h.opt.FileMode)
|
||||
f, err := os.OpenFile(cfg, os.O_CREATE|os.O_WRONLY|os.O_EXCL, h.opt.fileMode)
|
||||
if err != nil && os.IsExist(err) {
|
||||
if h.opt.Debug {
|
||||
log.Print(err)
|
||||
@@ -314,7 +329,7 @@ func (h *Handler) saveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// deleteConfig removes a config.
|
||||
func (h *Handler) deleteConfig(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Handler) deleteConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
if h.opt.Debug {
|
||||
log.Println("deleteConfig()")
|
||||
}
|
||||
@@ -369,7 +384,7 @@ func (h *Handler) listBlobsV1(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
path := h.getSubPath(objectType)
|
||||
|
||||
items, err := ioutil.ReadDir(path)
|
||||
items, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
h.fileAccessError(w, err)
|
||||
return
|
||||
@@ -383,8 +398,8 @@ func (h *Handler) listBlobsV1(w http.ResponseWriter, r *http.Request) {
|
||||
continue
|
||||
}
|
||||
subpath := filepath.Join(path, i.Name())
|
||||
var subitems []os.FileInfo
|
||||
subitems, err = ioutil.ReadDir(subpath)
|
||||
var subitems []os.DirEntry
|
||||
subitems, err = os.ReadDir(subpath)
|
||||
if err != nil {
|
||||
h.fileAccessError(w, err)
|
||||
return
|
||||
@@ -428,7 +443,7 @@ func (h *Handler) listBlobsV2(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
path := h.getSubPath(objectType)
|
||||
|
||||
items, err := ioutil.ReadDir(path)
|
||||
items, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
h.fileAccessError(w, err)
|
||||
return
|
||||
@@ -442,17 +457,27 @@ func (h *Handler) listBlobsV2(w http.ResponseWriter, r *http.Request) {
|
||||
continue
|
||||
}
|
||||
subpath := filepath.Join(path, i.Name())
|
||||
var subitems []os.FileInfo
|
||||
subitems, err = ioutil.ReadDir(subpath)
|
||||
var subitems []os.DirEntry
|
||||
subitems, err = os.ReadDir(subpath)
|
||||
if err != nil {
|
||||
h.fileAccessError(w, err)
|
||||
return
|
||||
}
|
||||
for _, f := range subitems {
|
||||
blobs = append(blobs, Blob{Name: f.Name(), Size: f.Size()})
|
||||
fi, err := f.Info()
|
||||
if err != nil {
|
||||
h.fileAccessError(w, err)
|
||||
return
|
||||
}
|
||||
blobs = append(blobs, Blob{Name: f.Name(), Size: fi.Size()})
|
||||
}
|
||||
} else {
|
||||
blobs = append(blobs, Blob{Name: i.Name(), Size: i.Size()})
|
||||
fi, err := i.Info()
|
||||
if err != nil {
|
||||
h.fileAccessError(w, err)
|
||||
return
|
||||
}
|
||||
blobs = append(blobs, Blob{Name: i.Name(), Size: fi.Size()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,15 +570,15 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
tmpFn := filepath.Join(filepath.Dir(path), objectID+".rest-server-temp")
|
||||
tf, err := tempFile(tmpFn, h.opt.FileMode)
|
||||
tf, err := tempFile(tmpFn, h.opt.fileMode)
|
||||
if os.IsNotExist(err) {
|
||||
// the error is caused by a missing directory, create it and retry
|
||||
mkdirErr := os.MkdirAll(filepath.Dir(path), h.opt.DirMode)
|
||||
mkdirErr := os.MkdirAll(filepath.Dir(path), h.opt.dirMode)
|
||||
if mkdirErr != nil {
|
||||
log.Print(mkdirErr)
|
||||
} else {
|
||||
// try again
|
||||
tf, err = tempFile(tmpFn, h.opt.FileMode)
|
||||
tf, err = tempFile(tmpFn, h.opt.fileMode)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
@@ -652,7 +677,7 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) {
|
||||
h.sendMetric(objectType, BlobWrite, uint64(written))
|
||||
}
|
||||
|
||||
// tempFile implements a custom version of ioutil.TempFile which allows modifying the file permissions
|
||||
// tempFile implements a custom version of os.CreateTemp 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)
|
||||
@@ -750,13 +775,13 @@ func (h *Handler) createRepo(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
log.Printf("Creating repository directories in %s\n", h.path)
|
||||
|
||||
if err := os.MkdirAll(h.path, h.opt.DirMode); err != nil {
|
||||
if err := os.MkdirAll(h.path, h.opt.dirMode); err != nil {
|
||||
h.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, d := range ObjectTypes {
|
||||
if err := os.Mkdir(filepath.Join(h.path, d), h.opt.DirMode); err != nil && !os.IsExist(err) {
|
||||
if err := os.Mkdir(filepath.Join(h.path, d), h.opt.dirMode); err != nil && !os.IsExist(err) {
|
||||
h.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
@@ -764,7 +789,7 @@ func (h *Handler) createRepo(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
for i := 0; i < 256; i++ {
|
||||
dirPath := filepath.Join(h.path, "data", fmt.Sprintf("%02x", i))
|
||||
if err := os.Mkdir(dirPath, h.opt.DirMode); err != nil && !os.IsExist(err) {
|
||||
if err := os.Mkdir(dirPath, h.opt.dirMode); err != nil && !os.IsExist(err) {
|
||||
h.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user