[#1] Add basic structure and operations

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2022-04-11 12:35:06 +03:00 committed by Alex Vanin
parent eb642eae89
commit 9f752cd756
65 changed files with 11534 additions and 0 deletions

129
.github/logo.svg vendored Normal file
View file

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="logo_fs.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg57"
version="1.1"
viewBox="0 0 105 25"
height="25mm"
width="105mm">
<defs
id="defs51">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath434">
<path
d="M 0,0 H 1366 V 768 H 0 Z"
id="path432" />
</clipPath>
</defs>
<sodipodi:namedview
inkscape:window-maximized="0"
inkscape:window-y="0"
inkscape:window-x="130"
inkscape:window-height="1040"
inkscape:window-width="1274"
height="50mm"
units="mm"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="344.49897"
inkscape:cx="468.64708"
inkscape:zoom="0.7"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base" />
<metadata
id="metadata54">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<g
id="g424"
transform="matrix(0.35277777,0,0,-0.35277777,63.946468,10.194047)">
<path
d="m 0,0 v -8.093 h 12.287 v -3.94 H 0 V -24.067 H -4.534 V 3.898 H 15.677 V 0 Z"
style="fill:#00e396;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path426" />
</g>
<g
transform="matrix(0.35277777,0,0,-0.35277777,-315.43002,107.34005)"
id="g428">
<g
id="g430"
clip-path="url(#clipPath434)">
<g
id="g436"
transform="translate(1112.874,278.2981)">
<path
d="M 0,0 C 1.822,-0.932 3.354,-2.359 4.597,-4.28 L 1.165,-7.373 c -0.791,1.695 -1.779,2.924 -2.966,3.686 -1.186,0.763 -2.768,1.145 -4.745,1.145 -1.949,0 -3.461,-0.389 -4.534,-1.166 -1.074,-0.777 -1.61,-1.772 -1.61,-2.987 0,-1.13 0.523,-2.027 1.568,-2.69 1.045,-0.664 2.909,-1.236 5.593,-1.716 2.514,-0.452 4.512,-1.024 5.995,-1.716 1.483,-0.693 2.564,-1.554 3.242,-2.585 0.677,-1.031 1.016,-2.309 1.016,-3.834 0,-1.639 -0.466,-3.079 -1.398,-4.322 -0.932,-1.243 -2.239,-2.197 -3.919,-2.86 -1.681,-0.664 -3.623,-0.996 -5.826,-0.996 -5.678,0 -9.689,1.892 -12.033,5.678 l 3.178,3.178 c 0.903,-1.695 2.068,-2.939 3.495,-3.729 1.426,-0.791 3.199,-1.186 5.318,-1.186 2.005,0 3.58,0.345 4.724,1.038 1.144,0.692 1.716,1.674 1.716,2.945 0,1.017 -0.516,1.835 -1.547,2.457 -1.031,0.621 -2.832,1.172 -5.402,1.653 -2.571,0.479 -4.618,1.073 -6.143,1.779 -1.526,0.706 -2.635,1.582 -3.326,2.627 -0.693,1.045 -1.039,2.316 -1.039,3.813 0,1.582 0.438,3.023 1.314,4.322 0.875,1.299 2.14,2.33 3.792,3.093 1.653,0.763 3.58,1.144 5.783,1.144 C -4.018,1.398 -1.822,0.932 0,0"
style="fill:#00e396;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path438" />
</g>
<g
id="g440"
transform="translate(993.0239,277.5454)">
<path
d="m 0,0 c 2.054,-1.831 3.083,-4.465 3.083,-7.902 v -17.935 h -4.484 v 16.366 c 0,2.914 -0.626,5.024 -1.877,6.332 -1.253,1.308 -2.924,1.962 -5.016,1.962 -1.495,0 -2.896,-0.327 -4.204,-0.981 -1.308,-0.654 -2.381,-1.719 -3.222,-3.194 -0.841,-1.477 -1.261,-3.335 -1.261,-5.576 v -14.909 h -4.484 V 1.328 l 4.086,-1.674 0.118,-1.84 c 0.933,1.681 2.222,2.923 3.867,3.727 1.643,0.803 3.493,1.205 5.548,1.205 C -4.671,2.746 -2.055,1.83 0,0"
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path442" />
</g>
<g
id="g444"
transform="translate(1027.9968,264.0386)">
<path
d="m 0,0 h -21.128 c 0.261,-2.84 1.205,-5.044 2.83,-6.613 1.625,-1.57 3.727,-2.355 6.305,-2.355 2.054,0 3.763,0.356 5.128,1.065 1.363,0.71 2.288,1.738 2.774,3.083 l 3.755,-1.961 c -1.121,-1.981 -2.616,-3.495 -4.484,-4.54 -1.868,-1.046 -4.259,-1.569 -7.173,-1.569 -4.223,0 -7.538,1.289 -9.948,3.867 -2.41,2.578 -3.615,6.146 -3.615,10.704 0,4.558 1.149,8.127 3.447,10.705 2.298,2.578 5.557,3.867 9.779,3.867 2.615,0 4.876,-0.58 6.782,-1.738 1.905,-1.158 3.343,-2.728 4.315,-4.707 C -0.262,7.827 0.224,5.605 0.224,3.139 0.224,2.092 0.149,1.046 0,0 m -18.298,10.144 c -1.513,-1.457 -2.438,-3.512 -2.775,-6.165 h 16.982 c -0.3,2.615 -1.159,4.661 -2.578,6.137 -1.42,1.476 -3.307,2.214 -5.661,2.214 -2.466,0 -4.455,-0.728 -5.968,-2.186"
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path446" />
</g>
<g
id="g448"
transform="translate(1057.8818,276.4246)">
<path
d="m 0,0 c 2.41,-2.578 3.615,-6.147 3.615,-10.705 0,-4.558 -1.205,-8.126 -3.615,-10.704 -2.41,-2.578 -5.726,-3.867 -9.948,-3.867 -4.222,0 -7.537,1.289 -9.947,3.867 -2.41,2.578 -3.615,6.146 -3.615,10.704 0,4.558 1.205,8.127 3.615,10.705 2.41,2.578 5.725,3.867 9.947,3.867 C -5.726,3.867 -2.41,2.578 0,0 m -16.617,-2.858 c -1.607,-1.906 -2.41,-4.522 -2.41,-7.847 0,-3.326 0.803,-5.94 2.41,-7.846 1.607,-1.905 3.83,-2.858 6.669,-2.858 2.839,0 5.063,0.953 6.67,2.858 1.606,1.906 2.41,4.52 2.41,7.846 0,3.325 -0.804,5.941 -2.41,7.847 C -4.885,-0.953 -7.109,0 -9.948,0 c -2.839,0 -5.062,-0.953 -6.669,-2.858"
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path450" />
</g>
</g>
</g>
<g
id="g452"
transform="matrix(0.35277777,0,0,-0.35277777,5.8329581,6.5590171)">
<path
d="m 0,0 0.001,-38.946 25.286,-9.076 V -8.753 L 52.626,1.321 27.815,10.207 Z"
style="fill:#00e599;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path454" />
</g>
<g
id="g456"
transform="matrix(0.35277777,0,0,-0.35277777,15.479008,10.041927)">
<path
d="M 0,0 V -21.306 L 25.293,-30.364 25.282,9.347 Z"
style="fill:#00b091;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path458" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

70
.github/workflows/builds.yml vendored Normal file
View file

@ -0,0 +1,70 @@
name: Builds
on:
pull_request:
branches:
- master
types: [opened, synchronize]
paths-ignore:
- '**/*.md'
workflow_dispatch:
jobs:
build_cli:
name: Build CLI
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.16
- name: Restore Go modules from cache
uses: actions/cache@v2
with:
path: /home/runner/go/pkg/mod
key: deps-${{ hashFiles('go.sum') }}
- name: Update Go modules
run: make dep
- name: Build CLI
run: make
- name: Save binary
uses: actions/upload-artifact@v2
with:
name: neofs-http-gw
path: bin/neofs-http-gw
build_image:
needs: build_cli
name: Build Docker image
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
- name: Restore Go modules from cache
uses: actions/cache@v2
with:
path: /home/runner/go/pkg/mod
key: deps-${{ hashFiles('go.sum') }}
- name: Update Go modules
run: make dep
- name: Build Docker image
run: make image

67
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '35 8 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

21
.github/workflows/dco.yml vendored Normal file
View file

@ -0,0 +1,21 @@
name: DCO check
on:
pull_request:
branches:
- master
jobs:
commits_check_job:
runs-on: ubuntu-latest
name: Commits Check
steps:
- name: Get PR Commits
id: 'get-pr-commits'
uses: tim-actions/get-pr-commits@master
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: DCO Check
uses: tim-actions/dco@master
with:
commits: ${{ steps.get-pr-commits.outputs.commits }}

86
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,86 @@
name: Tests
on:
pull_request:
branches:
- master
types: [opened, synchronize]
paths-ignore:
- '**/*.md'
workflow_dispatch:
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: latest
cover:
name: Coverage
runs-on: ubuntu-20.04
env:
CGO_ENABLED: 1
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
- name: Restore Go modules from cache
uses: actions/cache@v2
with:
path: /home/runner/go/pkg/mod
key: deps-${{ hashFiles('go.sum') }}
- name: Update Go modules
run: make dep
- name: Test and write coverage profile
run: make cover
- name: Upload coverage results to Codecov
uses: codecov/codecov-action@v1
with:
fail_ci_if_error: false
path_to_write_report: ./coverage.txt
verbose: true
tests:
name: Tests
runs-on: ubuntu-20.04
strategy:
matrix:
go_versions: [ '1.16.x', '1.17.x' ]
fail-fast: false
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '${{ matrix.go_versions }}'
- name: Restore Go modules from cache
uses: actions/cache@v2
with:
path: /home/runner/go/pkg/mod
key: deps-${{ hashFiles('go.sum') }}
- name: Update Go modules
run: make dep
- name: Run tests
run: make test

12
.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
.idea
bin
temp
/plugins/
/vendor/
*.log
.cache
coverage.txt
coverage.html

59
.golangci.yml Normal file
View file

@ -0,0 +1,59 @@
# This file contains all available configuration options
# with their default values.
# options for analysis running
run:
# timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 5m
# include test files or not, default is true
tests: true
# output configuration options
output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
format: tab
# all available settings of specific linters
linters-settings:
exhaustive:
# indicates that switch statements are to be considered exhaustive if a
# 'default' case is present, even if all enum members aren't listed in the
# switch
default-signifies-exhaustive: true
govet:
# report about shadowed variables
check-shadowing: false
linters:
enable:
# mandatory linters
- govet
- revive
# some default golangci-lint linters
- deadcode
- errcheck
- gosimple
- ineffassign
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
# extra linters
- exhaustive
- godot
- gofmt
- whitespace
- goimports
disable-all: true
fast: false
issues:
include:
- EXC0002 # should have a comment
- EXC0003 # test/Test ... consider calling this
- EXC0004 # govet
- EXC0005 # C-style breaks

26
Dockerfile Normal file
View file

@ -0,0 +1,26 @@
FROM golang:1.17 as basebuilder
RUN set -x \
&& apt-get update \
&& apt-get install -y make
FROM basebuilder as builder
ENV GOGC off
ENV CGO_ENABLED 0
ARG VERSION=dev
ARG REPO=repository
WORKDIR /src
COPY . /src
RUN make
# Executable image
FROM scratch
WORKDIR /
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /src/bin/neofs-rest-gw /bin/neofs-rest-gw
ENTRYPOINT ["/bin/neofs-rest-gw"]

8
Dockerfile.dirty Normal file
View file

@ -0,0 +1,8 @@
FROM alpine
RUN apk add --update --no-cache bash ca-certificates
WORKDIR /
COPY bin/neofs-rest-gw /bin/neofs-rest-gw
CMD ["neofs-rest-gw"]

674
LICENSE Normal file
View file

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

136
Makefile Normal file
View file

@ -0,0 +1,136 @@
#!/usr/bin/make -f
REPO ?= "$(shell go list -m)"
VERSION ?= "$(shell git describe --tags 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")"
HUB_IMAGE ?= nspccdev/neofs-rest-gw
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
DOCKER_LIST_VERSION ?= v1.42
SWAGGER_VERSION ?= v0.29.0
SWAGGER_ARCH ?= linux_amd64
SWAGGER_URL = "$(shell curl -s https://api.github.com/repos/go-swagger/go-swagger/releases/tags/$(SWAGGER_VERSION) | \
jq -r '.assets[] | select(.name | contains("swagger_$(SWAGGER_ARCH)")) | .browser_download_url')"
# List of binaries to build. For now just one.
BINDIR = bin
DIRS = "$(BINDIR)"
BINS = "$(BINDIR)/neofs-rest-gw"
.PHONY: help all dep clean format test cover lint docker/lint
# Make all binaries
all: generate-server $(BINS)
$(BINS): $(DIRS) dep
@echo "⇒ Build $@"
CGO_ENABLED=0 \
GO111MODULE=on \
go build -v -trimpath \
-ldflags "-X cmd/neofs-rest-gw/main.Version=$(VERSION)" \
-o $@ ./cmd/neofs-rest-gw
$(DIRS):
@echo "⇒ Ensure dir: $@"
@mkdir -p $@
# Pull go dependencies
dep:
@printf "⇒ Download requirements: "
@CGO_ENABLED=0 \
GO111MODULE=on \
go mod download && echo OK
@printf "⇒ Tidy requirements: "
@CGO_ENABLED=0 \
GO111MODULE=on \
go mod tidy -v && echo OK
# Install swagger
swagger:
ifeq (,$(wildcard ./bin/swagger))
curl --create-dirs -o ./bin/swagger -L'#' $(SWAGGER_URL)
chmod +x ./bin/swagger
# curl --create-dirs -o ./bin/swagger $(SWAGGER_URL)
# chmod +x ./bin/swagger
endif
# Generate server by swagger spec
generate-server: swagger
./bin/swagger generate server -t gen -f ./spec/rest.yaml --exclude-main \
-A neofs-rest-gw -P models.Principal \
-C templates/server-config.yaml --template-dir templates
# Run tests
test:
@go test ./... -cover
# Run tests with race detection and produce coverage output
cover:
@go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic
@go tool cover -html=coverage.txt -o coverage.html
# Reformat code
format:
@echo "⇒ Processing gofmt check"
@gofmt -s -w ./
@echo "⇒ Processing goimports check"
@goimports -w ./
# Build clean Docker image
image:
@echo "⇒ Build NeoFS REST Gateway docker image "
@docker build \
--build-arg REPO=$(REPO) \
--build-arg VERSION=$(VERSION) \
--rm \
-f Dockerfile \
-t $(HUB_IMAGE):$(HUB_TAG) .
# Push Docker image to the hub
image-push:
@echo "⇒ Publish image"
@docker push $(HUB_IMAGE):$(HUB_TAG)
# Build dirty Docker image
image-dirty:
@echo "⇒ Build NeoFS REST Gateway dirty docker image "
@docker build \
--build-arg REPO=$(REPO) \
--build-arg VERSION=$(VERSION) \
--rm \
-f Dockerfile.dirty \
-t $(HUB_IMAGE)-dirty:$(HUB_TAG) .
# Run linters
lint:
@golangci-lint --timeout=5m run
# Run linters in Docker
docker-lint:
docker run --rm -it \
-v `pwd`:/src \
-u `stat -c "%u:%g" .` \
--env HOME=/src \
golangci/golangci-lint:$(DOCKER_LINT_VERSION) bash -c 'cd /src/ && make lint'
# Print version
version:
@echo $(VERSION)
# Show this help prompt
help:
@echo ' Usage:'
@echo ''
@echo ' make <target>'
@echo ''
@echo ' Targets:'
@echo ''
@awk '/^#/{ comment = substr($$0,3) } comment && /^[a-zA-Z][a-zA-Z0-9_-]+ ?:/{ print " ", $$1, comment }' $(MAKEFILE_LIST) | column -t -s ':' | grep -v 'IGNORE' | sort -u
# Clean up
clean:
rm -rf vendor
rm -rf $(BINDIR)

View file

@ -1 +1,79 @@
<p align="center">
<img src="./.github/logo.svg" width="500px" alt="NeoFS">
</p>
<p align="center">
<a href="https://fs.neo.org">NeoFS</a> is a decentralized distributed object storage integrated with the <a href="https://neo.org">NEO Blockchain</a>.
</p>
---
[![Report](https://goreportcard.com/badge/github.com/nspcc-dev/neofs-rest-gw)](https://goreportcard.com/report/github.com/nspcc-dev/neofs-rest-gw)
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/nspcc-dev/neofs-rest-gw?sort=semver)
![License](https://img.shields.io/github/license/nspcc-dev/neofs-rest-gw.svg?style=popout)
# neofs-rest-gw
NeoFS REST Gateway bridges NeoFS internal protocol and REST API server.
## Installation
### Building
Before building make sure you have the following tools:
* go
* make
* jq
* git
* curl
To build neofs-rest-gw call `make` the cloned repository (the binary will end up in `bin/neofs-rest-gw`).
Notable make targets:
```
dep Check and ensure dependencies
image Build clean docker image
image-dirty Build dirty docker image with host-built binaries
formats Run all code formatters
lint Run linters
version Show current version
generate-server Generate boilerplate by spec
```
### Docker
Or you can also use a [Docker image](https://hub.docker.com/r/nspccdev/neofs-rest-gw) provided for released
(and occasionally unreleased) versions of gateway (`:latest` points to the latest stable release).
## Execution
REST gateway itself is not a NeoFS node, so to access NeoFS it uses node's gRPC interface and you need to provide some
node that it will connect to. This can be done either via `-p` parameter or via `REST_GW_PEERS_<N>_ADDRESS` and
`REST_GW_PEERS_<N>_WEIGHT` environment variables (the gate supports multiple NeoFS nodes with weighted load balancing).
If you're launching REST gateway in bundle with [neofs-dev-env](https://github.com/nspcc-dev/neofs-dev-env), you can get
an IP address of the node in output of `make hosts` command
(with s0*.neofs.devenv name).
These two commands are functionally equivalent, they run the gate with one backend node (and otherwise default
settings):
```
$ neofs-rest-gw -p 192.168.130.72:8080
$ REST_GW_PEERS_0_ADDRESS=192.168.130.72:8080 neofs-rest-gw
```
It's also possible to specify uri scheme (grpc or grpcs) when using `-p`:
```
$ neofs-rest-gw -p grpc://192.168.130.72:8080
$ REST_GW_PEERS_0_ADDRESS=grpcs://192.168.130.72:8080 neofs-rest-gw
```
## Configuration
In general, everything available as CLI parameter can also be specified via environment variables, so they're not
specifically mentioned in most cases
(see `--help` also). If you prefer a config file you can use it in yaml format. See config [examples](./config) for
details.

1
VERSION Normal file
View file

@ -0,0 +1 @@
v0.1.0

347
cmd/neofs-rest-gw/config.go Normal file
View file

@ -0,0 +1,347 @@
package main
import (
"context"
"fmt"
"os"
"sort"
"strconv"
"strings"
"time"
"github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi"
"github.com/nspcc-dev/neofs-rest-gw/handlers"
"github.com/nspcc-dev/neofs-sdk-go/pool"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
const (
defaultRebalanceTimer = 15 * time.Second
defaultRequestTimeout = 15 * time.Second
defaultConnectTimeout = 30 * time.Second
// Timeouts.
cfgNodeDialTimeout = "node-dial-timeout"
cfgHealthcheckTimeout = "healthcheck-timeout"
cfgRebalance = "rebalance-timer"
// Logger.
cfgLoggerLevel = "logger.level"
// Wallet.
cfgWalletPath = "wallet.path"
cfgWalletAddress = "wallet.address"
cfgWalletPassphrase = "wallet.passphrase"
// Peers.
cfgPeers = "peers"
// Command line args.
cmdHelp = "help"
cmdVersion = "version"
cmdPprof = "pprof"
cmdMetrics = "metrics"
cmdWallet = "wallet"
cmdAddress = "address"
cmdConfig = "config"
)
var ignore = map[string]struct{}{
cfgPeers: {},
cmdHelp: {},
cmdVersion: {},
}
// Prefix is a prefix used for environment variables containing gateway
// configuration.
const Prefix = "REST_GW"
var (
// Version is gateway version.
Version = "dev"
)
func config() *viper.Viper {
v := viper.New()
v.AutomaticEnv()
v.SetEnvPrefix(Prefix)
v.AllowEmptyEnv(true)
v.SetConfigType("yaml")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
// flags setup:
flagSet := pflag.NewFlagSet("commandline", pflag.ExitOnError)
flagSet.SetOutput(os.Stdout)
flagSet.SortFlags = false
flagSet.Bool(cmdPprof, false, "enable pprof")
flagSet.Bool(cmdMetrics, false, "enable prometheus")
help := flagSet.BoolP(cmdHelp, "h", false, "show help")
version := flagSet.BoolP(cmdVersion, "v", false, "show version")
flagSet.StringP(cmdWallet, "w", "", `path to the wallet`)
flagSet.String(cmdAddress, "", `address of wallet account`)
config := flagSet.String(cmdConfig, "", "config path")
flagSet.Duration(cfgNodeDialTimeout, defaultConnectTimeout, "gRPC connect timeout")
flagSet.Duration(cfgHealthcheckTimeout, defaultRequestTimeout, "gRPC request timeout")
flagSet.Duration(cfgRebalance, defaultRebalanceTimer, "gRPC connection rebalance timer")
peers := flagSet.StringArrayP(cfgPeers, "p", nil, "NeoFS nodes")
restapi.BindDefaultFlags(flagSet)
// set defaults:
// logger:
v.SetDefault(cfgLoggerLevel, "debug")
if err := v.BindPFlags(flagSet); err != nil {
panic(err)
}
if err := flagSet.Parse(os.Args); err != nil {
panic(err)
}
switch {
case help != nil && *help:
fmt.Printf("NeoFS REST Gateway %s\n", Version)
flagSet.PrintDefaults()
fmt.Println()
fmt.Println("Default environments:")
fmt.Println()
cmdKeys := v.AllKeys()
sort.Strings(cmdKeys)
for i := range cmdKeys {
if _, ok := ignore[cmdKeys[i]]; ok {
continue
}
k := strings.Replace(cmdKeys[i], ".", "_", -1)
fmt.Printf("%s_%s = %v\n", Prefix, strings.ToUpper(k), v.Get(cmdKeys[i]))
}
os.Exit(0)
case version != nil && *version:
fmt.Printf("NeoFS REST Gateway %s\n", Version)
os.Exit(0)
}
if v.IsSet(cmdConfig) {
if cfgFile, err := os.Open(*config); err != nil {
panic(err)
} else if err := v.ReadConfig(cfgFile); err != nil {
panic(err)
}
}
if peers != nil && len(*peers) > 0 {
for i := range *peers {
v.SetDefault(cfgPeers+"."+strconv.Itoa(i)+".address", (*peers)[i])
v.SetDefault(cfgPeers+"."+strconv.Itoa(i)+".weight", 1)
v.SetDefault(cfgPeers+"."+strconv.Itoa(i)+".priority", 1)
}
}
return v
}
func getNeoFSKey(logger *zap.Logger, cfg *viper.Viper) (*keys.PrivateKey, error) {
walletPath := cfg.GetString(cmdWallet)
if len(walletPath) == 0 {
walletPath = cfg.GetString(cfgWalletPath)
}
if len(walletPath) == 0 {
logger.Info("no wallet path specified, creating ephemeral key automatically for this run")
return keys.NewPrivateKey()
}
w, err := wallet.NewWalletFromFile(walletPath)
if err != nil {
return nil, err
}
var password *string
if cfg.IsSet(cfgWalletPassphrase) {
pwd := cfg.GetString(cfgWalletPassphrase)
password = &pwd
}
address := cfg.GetString(cmdAddress)
if len(address) == 0 {
address = cfg.GetString(cfgWalletAddress)
}
return getKeyFromWallet(w, address, password)
}
func getKeyFromWallet(w *wallet.Wallet, addrStr string, password *string) (*keys.PrivateKey, error) {
var addr util.Uint160
var err error
if addrStr == "" {
addr = w.GetChangeAddress()
} else {
addr, err = flags.ParseAddress(addrStr)
if err != nil {
return nil, fmt.Errorf("invalid address")
}
}
acc := w.GetAccount(addr)
if acc == nil {
return nil, fmt.Errorf("couldn't find wallet account for %s", addrStr)
}
if password == nil {
pwd, err := input.ReadPassword("Enter password > ")
if err != nil {
return nil, fmt.Errorf("couldn't read password")
}
password = &pwd
}
if err := acc.Decrypt(*password, w.Scrypt); err != nil {
return nil, fmt.Errorf("couldn't decrypt account: %w", err)
}
return acc.PrivateKey(), nil
}
// newLogger constructs a zap.Logger instance for current application.
// Panics on failure.
//
// Logger is built from zap's production logging configuration with:
// * parameterized level (debug by default)
// * console encoding
// * ISO8601 time encoding
//
// Logger records a stack trace for all messages at or above fatal level.
//
// See also zapcore.Level, zap.NewProductionConfig, zap.AddStacktrace.
func newLogger(v *viper.Viper) *zap.Logger {
var lvl zapcore.Level
lvlStr := v.GetString(cfgLoggerLevel)
err := lvl.UnmarshalText([]byte(lvlStr))
if err != nil {
panic(fmt.Sprintf("incorrect logger level configuration %s (%v), "+
"value should be one of %v", lvlStr, err, [...]zapcore.Level{
zapcore.DebugLevel,
zapcore.InfoLevel,
zapcore.WarnLevel,
zapcore.ErrorLevel,
zapcore.DPanicLevel,
zapcore.PanicLevel,
zapcore.FatalLevel,
}))
}
c := zap.NewProductionConfig()
c.Level = zap.NewAtomicLevelAt(lvl)
c.Encoding = "console"
c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
l, err := c.Build(
zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)),
)
if err != nil {
panic(fmt.Sprintf("build zap logger instance: %v", err))
}
return l
}
func serverConfig(v *viper.Viper) *restapi.ServerConfig {
return &restapi.ServerConfig{
EnabledListeners: v.GetStringSlice(restapi.FlagScheme),
CleanupTimeout: v.GetDuration(restapi.FlagCleanupTimeout),
GracefulTimeout: v.GetDuration(restapi.FlagGracefulTimeout),
MaxHeaderSize: v.GetInt(restapi.FlagMaxHeaderSize),
ListenAddress: v.GetString(restapi.FlagListenAddress),
ListenLimit: v.GetInt(restapi.FlagListenLimit),
KeepAlive: v.GetDuration(restapi.FlagKeepAlive),
ReadTimeout: v.GetDuration(restapi.FlagReadTimeout),
WriteTimeout: v.GetDuration(restapi.FlagWriteTimeout),
TLSListenAddress: v.GetString(restapi.FlagTLSListenAddress),
TLSListenLimit: v.GetInt(restapi.FlagTLSListenLimit),
TLSKeepAlive: v.GetDuration(restapi.FlagTLSKeepAlive),
TLSReadTimeout: v.GetDuration(restapi.FlagTLSReadTimeout),
TLSWriteTimeout: v.GetDuration(restapi.FlagTLSWriteTimeout),
}
}
func newNeofsAPI(ctx context.Context, logger *zap.Logger, v *viper.Viper) (*handlers.API, error) {
key, err := getNeoFSKey(logger, v)
if err != nil {
return nil, err
}
var prm pool.InitParameters
prm.SetKey(&key.PrivateKey)
prm.SetNodeDialTimeout(v.GetDuration(cfgNodeDialTimeout))
prm.SetHealthcheckTimeout(v.GetDuration(cfgHealthcheckTimeout))
prm.SetClientRebalanceInterval(v.GetDuration(cfgRebalance))
for _, peer := range fetchPeers(logger, v) {
prm.AddNode(peer)
}
p, err := pool.NewPool(prm)
if err != nil {
return nil, err
}
if err = p.Dial(ctx); err != nil {
return nil, err
}
var apiPrm handlers.PrmAPI
apiPrm.Pool = p
apiPrm.Key = key
apiPrm.Logger = logger
return handlers.New(&apiPrm), nil
}
func fetchPeers(l *zap.Logger, v *viper.Viper) []pool.NodeParam {
var nodes []pool.NodeParam
for i := 0; ; i++ {
key := cfgPeers + "." + strconv.Itoa(i) + "."
address := v.GetString(key + "address")
weight := v.GetFloat64(key + "weight")
priority := v.GetInt(key + "priority")
if address == "" {
break
}
if weight <= 0 { // unspecified or wrong
weight = 1
}
if priority <= 0 { // unspecified or wrong
priority = 1
}
nodes = append(nodes, pool.NewNodeParam(priority, address, weight))
l.Info("added connection peer",
zap.String("address", address),
zap.Int("priority", priority),
zap.Float64("weight", weight),
)
}
return nodes
}

View file

@ -0,0 +1,461 @@
package main
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha512"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"testing"
"time"
"github.com/go-openapi/loads"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi"
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations"
"github.com/nspcc-dev/neofs-rest-gw/handlers"
"github.com/nspcc-dev/neofs-sdk-go/container"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
"github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/nspcc-dev/neofs-sdk-go/object/address"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
"github.com/nspcc-dev/neofs-sdk-go/policy"
"github.com/nspcc-dev/neofs-sdk-go/pool"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
const (
devenvPrivateKey = "1dd37fba80fec4e6a6f13fd708d8dcb3b29def768017052f6c930fa1c5d90bbb"
testListenAddress = "localhost:8082"
testHost = "http://" + testListenAddress
testNode = "localhost:8080"
// XNeofsTokenSignature header contains base64 encoded signature of the token body.
XNeofsTokenSignature = "X-Neofs-Token-Signature"
// XNeofsTokenSignatureKey header contains hex encoded public key that corresponds the signature of the token body.
XNeofsTokenSignatureKey = "X-Neofs-Token-Signature-Key"
// XNeofsTokenScope header contains operation scope for auth (bearer) token.
// It corresponds to 'object' or 'container' services in neofs.
XNeofsTokenScope = "X-Neofs-Token-Scope"
)
func TestIntegration(t *testing.T) {
rootCtx := context.Background()
aioImage := "nspccdev/neofs-aio-testcontainer:"
versions := []string{"0.24.0", "0.25.1", "0.27.5", "latest"}
key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey)
require.NoError(t, err)
for _, version := range versions {
ctx, cancel2 := context.WithCancel(rootCtx)
aioContainer := createDockerContainer(ctx, t, aioImage+version)
cancel := runServer(ctx, t)
clientPool := getPool(ctx, t, key)
CID, err := createContainer(ctx, t, clientPool)
require.NoError(t, err, version)
t.Run("rest put object "+version, func(t *testing.T) { restObjectPut(ctx, t, clientPool, CID) })
t.Run("rest put container"+version, func(t *testing.T) { restContainerPut(ctx, t, clientPool) })
t.Run("rest get container"+version, func(t *testing.T) { restContainerGet(ctx, t, clientPool, CID) })
cancel()
err = aioContainer.Terminate(ctx)
require.NoError(t, err)
cancel2()
<-ctx.Done()
}
}
func createDockerContainer(ctx context.Context, t *testing.T, image string) testcontainers.Container {
req := testcontainers.ContainerRequest{
Image: image,
WaitingFor: wait.NewLogStrategy("aio container started").WithStartupTimeout(30 * time.Second),
Name: "aio",
Hostname: "aio",
NetworkMode: "host",
}
aioC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
require.NoError(t, err)
return aioC
}
func runServer(ctx context.Context, t *testing.T) context.CancelFunc {
cancelCtx, cancel := context.WithCancel(ctx)
v := getDefaultConfig()
l := newLogger(v)
neofsAPI, err := newNeofsAPI(cancelCtx, l, v)
require.NoError(t, err)
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
require.NoError(t, err)
api := operations.NewNeofsRestGwAPI(swaggerSpec)
server := restapi.NewServer(api, serverConfig(v))
server.ConfigureAPI(neofsAPI.Configure)
go func() {
err := server.Serve()
require.NoError(t, err)
}()
return func() {
cancel()
err := server.Shutdown()
require.NoError(t, err)
}
}
func getDefaultConfig() *viper.Viper {
v := config()
v.SetDefault(cfgPeers+".0.address", testNode)
v.SetDefault(cfgPeers+".0.weight", 1)
v.SetDefault(cfgPeers+".0.priority", 1)
v.SetDefault(restapi.FlagListenAddress, testListenAddress)
return v
}
func getPool(ctx context.Context, t *testing.T, key *keys.PrivateKey) *pool.Pool {
var prm pool.InitParameters
prm.AddNode(pool.NewNodeParam(1, testNode, 1))
prm.SetKey(&key.PrivateKey)
prm.SetHealthcheckTimeout(5 * time.Second)
prm.SetNodeDialTimeout(5 * time.Second)
clientPool, err := pool.NewPool(prm)
require.NoError(t, err)
err = clientPool.Dial(ctx)
require.NoError(t, err)
return clientPool
}
func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) {
restrictByEACL(ctx, t, clientPool, cnrID)
key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey)
require.NoError(t, err)
b := models.Bearer{
Object: []*models.Record{{
Operation: models.NewOperation(models.OperationPUT),
Action: models.NewAction(models.ActionALLOW),
Filters: []*models.Filter{},
Targets: []*models.Target{{
Role: models.NewRole(models.RoleOTHERS),
Keys: []string{},
}},
}},
}
data, err := json.Marshal(&b)
require.NoError(t, err)
request0, err := http.NewRequest(http.MethodPost, testHost+"/v1/auth", bytes.NewReader(data))
require.NoError(t, err)
request0.Header.Add("Content-Type", "application/json")
request0.Header.Add(XNeofsTokenScope, string(models.TokenTypeObject))
request0.Header.Add(XNeofsTokenSignatureKey, hex.EncodeToString(key.PublicKey().Bytes()))
httpClient := http.Client{
Timeout: 5 * time.Second,
}
resp, err := httpClient.Do(request0)
require.NoError(t, err)
defer resp.Body.Close()
rr, err := io.ReadAll(resp.Body)
require.NoError(t, err)
fmt.Println(string(rr))
require.Equal(t, http.StatusOK, resp.StatusCode)
stokenResp := &models.TokenResponse{}
err = json.Unmarshal(rr, stokenResp)
require.NoError(t, err)
require.Equal(t, *stokenResp.Type, models.TokenTypeObject)
bearerBase64 := stokenResp.Token
fmt.Println(*bearerBase64)
binaryData, err := base64.StdEncoding.DecodeString(*bearerBase64)
require.NoError(t, err)
signatureData := signData(t, key, binaryData)
content := "content of file"
attrKey, attrValue := "User-Attribute", "user value"
attributes := map[string]string{
object.AttributeFileName: "newFile.txt",
attrKey: attrValue,
}
req := operations.PutObjectBody{
ContainerID: handlers.NewString(cnrID.String()),
FileName: handlers.NewString("newFile.txt"),
Payload: base64.StdEncoding.EncodeToString([]byte(content)),
}
body, err := json.Marshal(&req)
require.NoError(t, err)
fmt.Println(base64.StdEncoding.EncodeToString(signatureData))
fmt.Println(hex.EncodeToString(key.PublicKey().Bytes()))
request, err := http.NewRequest(http.MethodPut, testHost+"/v1/objects", bytes.NewReader(body))
require.NoError(t, err)
request.Header.Add("Content-Type", "application/json")
request.Header.Add(XNeofsTokenSignature, base64.StdEncoding.EncodeToString(signatureData))
request.Header.Add("Authorization", "Bearer "+*bearerBase64)
request.Header.Add(XNeofsTokenSignatureKey, hex.EncodeToString(key.PublicKey().Bytes()))
request.Header.Add("X-Attribute-"+attrKey, attrValue)
resp2, err := httpClient.Do(request)
require.NoError(t, err)
defer resp2.Body.Close()
rr2, err := io.ReadAll(resp2.Body)
require.NoError(t, err)
fmt.Println(string(rr2))
require.Equal(t, http.StatusOK, resp2.StatusCode)
addr := &operations.PutObjectOKBody{}
err = json.Unmarshal(rr2, addr)
require.NoError(t, err)
var CID cid.ID
err = CID.Parse(*addr.ContainerID)
require.NoError(t, err)
id := oid.NewID()
err = id.Parse(*addr.ObjectID)
require.NoError(t, err)
objectAddress := address.NewAddress()
objectAddress.SetContainerID(&CID)
objectAddress.SetObjectID(id)
payload := bytes.NewBuffer(nil)
var prm pool.PrmObjectGet
prm.SetAddress(*objectAddress)
res, err := clientPool.GetObject(ctx, prm)
require.NoError(t, err)
_, err = io.Copy(payload, res.Payload)
require.NoError(t, err)
require.Equal(t, content, payload.String())
for _, attribute := range res.Header.Attributes() {
require.Equal(t, attributes[attribute.Key()], attribute.Value(), attribute.Key())
}
}
func restContainerGet(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) {
httpClient := http.Client{Timeout: 5 * time.Second}
request, err := http.NewRequest(http.MethodGet, testHost+"/v1/containers/"+cnrID.String(), nil)
require.NoError(t, err)
request = request.WithContext(ctx)
resp, err := httpClient.Do(request)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
cnrInfo := &models.ContainerInfo{}
err = json.NewDecoder(resp.Body).Decode(cnrInfo)
require.NoError(t, err)
require.Equal(t, cnrID.String(), cnrInfo.ContainerID)
require.Equal(t, clientPool.OwnerID().String(), cnrInfo.OwnerID)
}
func signData(t *testing.T, key *keys.PrivateKey, data []byte) []byte {
h := sha512.Sum512(data)
x, y, err := ecdsa.Sign(rand.Reader, &key.PrivateKey, h[:])
require.NoError(t, err)
return elliptic.Marshal(elliptic.P256(), x, y)
}
func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool) {
key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey)
require.NoError(t, err)
b := models.Bearer{
Container: &models.Rule{
Verb: models.NewVerb(models.VerbPUT),
},
}
data, err := json.Marshal(&b)
require.NoError(t, err)
request0, err := http.NewRequest(http.MethodPost, testHost+"/v1/auth", bytes.NewReader(data))
require.NoError(t, err)
request0.Header.Add("Content-Type", "application/json")
request0.Header.Add(XNeofsTokenScope, "container")
request0.Header.Add(XNeofsTokenSignatureKey, hex.EncodeToString(key.PublicKey().Bytes()))
httpClient := http.Client{
Timeout: 30 * time.Second,
}
resp, err := httpClient.Do(request0)
require.NoError(t, err)
defer resp.Body.Close()
rr, err := io.ReadAll(resp.Body)
require.NoError(t, err)
fmt.Println(string(rr))
require.Equal(t, http.StatusOK, resp.StatusCode)
stokenResp := &models.TokenResponse{}
err = json.Unmarshal(rr, stokenResp)
require.NoError(t, err)
require.Equal(t, *stokenResp.Type, models.TokenTypeContainer)
bearerBase64 := stokenResp.Token
binaryData, err := base64.StdEncoding.DecodeString(*bearerBase64)
require.NoError(t, err)
signatureData := signData(t, key, binaryData)
attrKey, attrValue := "User-Attribute", "user value"
userAttributes := map[string]string{
attrKey: attrValue,
}
req := operations.PutContainerBody{
ContainerName: handlers.NewString("cnr"),
}
body, err := json.Marshal(&req)
require.NoError(t, err)
fmt.Println(base64.StdEncoding.EncodeToString(signatureData))
fmt.Println(hex.EncodeToString(key.PublicKey().Bytes()))
request, err := http.NewRequest(http.MethodPut, testHost+"/v1/containers", bytes.NewReader(body))
require.NoError(t, err)
request.Header.Add("Content-Type", "application/json")
request.Header.Add(XNeofsTokenSignature, base64.StdEncoding.EncodeToString(signatureData))
request.Header.Add("Authorization", "Bearer "+*bearerBase64)
request.Header.Add(XNeofsTokenSignatureKey, hex.EncodeToString(key.PublicKey().Bytes()))
request.Header.Add("X-Attribute-"+attrKey, attrValue)
resp2, err := httpClient.Do(request)
require.NoError(t, err)
defer resp2.Body.Close()
body, err = io.ReadAll(resp2.Body)
require.NoError(t, err)
fmt.Println(string(body))
require.Equal(t, http.StatusOK, resp2.StatusCode)
addr := &operations.PutContainerOKBody{}
err = json.Unmarshal(body, addr)
require.NoError(t, err)
var CID cid.ID
err = CID.Parse(*addr.ContainerID)
require.NoError(t, err)
fmt.Println(CID.String())
var prm pool.PrmContainerGet
prm.SetContainerID(CID)
cnr, err := clientPool.GetContainer(ctx, prm)
require.NoError(t, err)
cnrAttr := make(map[string]string, len(cnr.Attributes()))
for _, attribute := range cnr.Attributes() {
cnrAttr[attribute.Key()] = attribute.Value()
}
for key, val := range userAttributes {
require.Equal(t, val, cnrAttr[key])
}
}
func createContainer(ctx context.Context, t *testing.T, clientPool *pool.Pool) (*cid.ID, error) {
pp, err := policy.Parse("REP 1")
require.NoError(t, err)
cnr := container.New(
container.WithPolicy(pp),
container.WithCustomBasicACL(0x0FFFFFFF),
container.WithAttribute(container.AttributeName, "friendlyName"),
container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(time.Now().Unix(), 10)))
cnr.SetOwnerID(clientPool.OwnerID())
var waitPrm pool.WaitParams
waitPrm.SetPollInterval(3 * time.Second)
waitPrm.SetTimeout(15 * time.Second)
var prm pool.PrmContainerPut
prm.SetContainer(*cnr)
prm.SetWaitParams(waitPrm)
CID, err := clientPool.PutContainer(ctx, prm)
if err != nil {
return nil, err
}
fmt.Println(CID.String())
return CID, err
}
func restrictByEACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) {
table := new(eacl.Table)
table.SetCID(cnrID)
for op := eacl.OperationGet; op <= eacl.OperationRangeHash; op++ {
record := new(eacl.Record)
record.SetOperation(op)
record.SetAction(eacl.ActionDeny)
target := new(eacl.Target)
target.SetRole(eacl.RoleOthers)
record.SetTargets(*target)
table.AddRecord(record)
}
var waitPrm pool.WaitParams
waitPrm.SetPollInterval(3 * time.Second)
waitPrm.SetTimeout(15 * time.Second)
var prm pool.PrmContainerSetEACL
prm.SetTable(*table)
prm.SetWaitParams(waitPrm)
err := clientPool.SetEACL(ctx, prm)
require.NoError(t, err)
}

42
cmd/neofs-rest-gw/main.go Normal file
View file

@ -0,0 +1,42 @@
package main
import (
"context"
"github.com/go-openapi/loads"
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi"
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations"
"go.uber.org/zap"
)
func main() {
ctx := context.Background()
v := config()
logger := newLogger(v)
neofsAPI, err := newNeofsAPI(ctx, logger, v)
if err != nil {
logger.Fatal("init neofs", zap.Error(err))
}
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
if err != nil {
logger.Fatal("init spec", zap.Error(err))
}
api := operations.NewNeofsRestGwAPI(swaggerSpec)
server := restapi.NewServer(api, serverConfig(v))
defer func() {
if err = server.Shutdown(); err != nil {
logger.Error("shutdown", zap.Error(err))
}
}()
server.ConfigureAPI(neofsAPI.Configure)
// serve API
if err = server.Serve(); err != nil {
logger.Fatal("serve", zap.Error(err))
}
}

81
config/config.env Normal file
View file

@ -0,0 +1,81 @@
# Path to wallet.
REST_GW_WALLET_PATH=/path/to/wallet.json
# Account address. If omitted default one will be used.
REST_GW_ADDRESS=NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP
# Password to decrypt wallet.
REST_GW_PASSPHRASE=pwd
# Enable metrics.
REST_GW_METRICS=true
# Enable pprof.
REST_GW_PPROF=true
# Log level.
REST_GW_LOGGER_LEVEL=debug
# Nodes configuration.
# This configuration make gateway use the first node (grpc://s01.neofs.devenv:8080)
# while it's healthy. Otherwise, gateway use the second node (grpc://s01.neofs.devenv:8080)
# for 10% of requests and the third node for 90% of requests.
# Endpoint.
REST_GW_PEERS_0_ADDRESS=grpc://s01.neofs.devenv:8080
# Until nodes with the same priority level are healthy
# nodes with other priority are not used.
# Еhe lower the value, the higher the priority.
REST_GW_PEERS_0_PRIORITY=1
# Load distribution proportion for nodes with the same priority.
REST_GW_PEERS_0_WEIGHT=1
REST_GW_PEERS_1_ADDRESS=grpc://s02.neofs.devenv:8080
REST_GW_PEERS_1_PRIORITY=2
REST_GW_PEERS_1_WEIGHT=1
REST_GW_PEERS_2_ADDRESS=grpc://s03.neofs.devenv:8080
REST_GW_PEERS_2_PRIORITY=2
REST_GW_PEERS_3_WEIGHT=9
# Timeout to dial node.
node_dial_timeout=5s
# Timeout to check node health during rebalance.
healthcheck_timeout=5s
# Interval to check nodes health.
rebalance_timer=30s
# Grace period for which to wait before killing idle connections
REST_GW_CLEANUP_TIMEOUT=10s
# Grace period for which to wait before shutting down the server
REST_GW_GRACEFUL_TIMEOUT=15s
# Controls the maximum number of bytes the server will read parsing the request header's keys and values,
# including the request line. It does not limit the size of the request body.
REST_GW_MAX_HEADER_SIZE=1000000
# The IP and port to listen on.
REST_GW_LISTEN_ADDRESS=localhost:8080
# Limit the number of outstanding requests.
REST_GW_LISTEN_LIMIT=0
# Sets the TCP keep-alive timeouts on accepted connections.
# It prunes dead TCP connections ( e.g. closing laptop mid-download).
REST_GW_KEEP_ALIVE=3m
# Maximum duration before timing out read of the request.
REST_GW_READ_TIMEOUT=30s
# Maximum duration before timing out write of the response.
REST_GW_WRITE_TIMEOUT=30s
# The IP and port to listen on.
REST_GW_TLS_LISTEN_ADDRESS=localhost:8081
# The certificate file to use for secure connections.
REST_GW_TLS_CERTIFICATE=/path/to/tls/cert
# The private key file to use for secure connections (without passphrase).
REST_GW_TLS_KEY=/path/to/tls/key
# The certificate authority certificate file to be used with mutual tls auth.
REST_GW_TLS_CA=/path/to/tls/ca
# Limit the number of outstanding requests.
REST_GW_TLS_LISTEN_LIMIT=0
# Sets the TCP keep-alive timeouts on accepted connections.
# It prunes dead TCP connections ( e.g. closing laptop mid-download).
REST_GW_TLS_KEEP_ALIVE=3m
# Maximum duration before timing out read of the request.
REST_GW_TLS_READ_TIMEOUT=30s
# Maximum duration before timing out write of the response.
REST_GW_TLS_WRITE_TIMEOUT=30s

87
config/config.yaml Normal file
View file

@ -0,0 +1,87 @@
wallet:
# Path to wallet.
path: /path/to/wallet.json
# Account address. If omitted default one will be used.
address: NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP
# Password to decrypt wallet.
passphrase: pwd
# Enable metrics.
metrics: true
# Enable pprof.
pprof: true
logger:
# Log level.
level: debug
# Nodes configuration.
# This configuration make gateway use the first node (grpc://s01.neofs.devenv:8080)
# while it's healthy. Otherwise, gateway use the second node (grpc://s01.neofs.devenv:8080)
# for 10% of requests and the third node for 90% of requests.
peers:
0:
# Endpoint.
address: grpc://s01.neofs.devenv:8080
# Until nodes with the same priority level are healthy
# nodes with other priority are not used.
# Еhe lower the value, the higher the priority.
priority: 1
# Load distribution proportion for nodes with the same priority.
weight: 1
1:
address: grpc://s02.neofs.devenv:8080
priority: 2
weight: 1
2:
address: grpc://s03.neofs.devenv:8080
priority: 2
weight: 9
# Timeout to dial node.
node-dial-timeout: 5s
# Timeout to check node health during rebalance.
healthcheck-timeout: 5s
# Interval to check nodes health.
rebalance_timer: 30s
# The listeners to enable, this can be repeated and defaults to the schemes in the swagger spec.
scheme: [ http ]
# Grace period for which to wait before killing idle connections
cleanup-timeout: 10s
# Grace period for which to wait before shutting down the server
graceful-timeout: 15s
# Controls the maximum number of bytes the server will read parsing the request header's keys and values,
# including the request line. It does not limit the size of the request body.
max-header-size: 1000000
# The IP and port to listen on.
listen-address: localhost:8080
# Limit the number of outstanding requests.
listen-limit: 0
# Sets the TCP keep-alive timeouts on accepted connections.
# It prunes dead TCP connections ( e.g. closing laptop mid-download).
keep-alive: 3m
# Maximum duration before timing out read of the request.
read-timeout: 30s
# Maximum duration before timing out write of the response.
write-timeout: 30s
# The IP and port to listen on.
tls-listen-address: localhost:8081
# The certificate file to use for secure connections.
tls-certificate: /path/to/tls/cert
# The private key file to use for secure connections (without passphrase).
tls-key: /path/to/tls/key
# The certificate authority certificate file to be used with mutual tls auth.
tls-ca: /path/to/tls/ca
# Limit the number of outstanding requests.
tls-listen-limit: 0
# Sets the TCP keep-alive timeouts on accepted connections.
# It prunes dead TCP connections ( e.g. closing laptop mid-download).
tls-keep-alive: 3m
# Maximum duration before timing out read of the request.
tls-read-timeout: 30s
# Maximum duration before timing out write of the response.
tls-write-timeout: 30s

78
gen/models/action.go Normal file
View file

@ -0,0 +1,78 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// Action action
//
// swagger:model Action
type Action string
func NewAction(value Action) *Action {
return &value
}
// Pointer returns a pointer to a freshly-allocated Action.
func (m Action) Pointer() *Action {
return &m
}
const (
// ActionALLOW captures enum value "ALLOW"
ActionALLOW Action = "ALLOW"
// ActionDENY captures enum value "DENY"
ActionDENY Action = "DENY"
)
// for schema
var actionEnum []interface{}
func init() {
var res []Action
if err := json.Unmarshal([]byte(`["ALLOW","DENY"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
actionEnum = append(actionEnum, v)
}
}
func (m Action) validateActionEnum(path, location string, value Action) error {
if err := validate.EnumCase(path, location, value, actionEnum, true); err != nil {
return err
}
return nil
}
// Validate validates this action
func (m Action) Validate(formats strfmt.Registry) error {
var res []error
// value enum
if err := m.validateActionEnum("", "body", m); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validates this action based on context it is used
func (m Action) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

53
gen/models/attribute.go Normal file
View file

@ -0,0 +1,53 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// Attribute attribute
//
// swagger:model Attribute
type Attribute struct {
// key
Key string `json:"key,omitempty"`
// value
Value string `json:"value,omitempty"`
}
// Validate validates this attribute
func (m *Attribute) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this attribute based on context it is used
func (m *Attribute) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *Attribute) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Attribute) UnmarshalBinary(b []byte) error {
var res Attribute
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

162
gen/models/bearer.go Normal file
View file

@ -0,0 +1,162 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// Bearer bearer
//
// swagger:model Bearer
type Bearer struct {
// container
Container *Rule `json:"container,omitempty"`
// object
Object []*Record `json:"object"`
}
// Validate validates this bearer
func (m *Bearer) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateContainer(formats); err != nil {
res = append(res, err)
}
if err := m.validateObject(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Bearer) validateContainer(formats strfmt.Registry) error {
if swag.IsZero(m.Container) { // not required
return nil
}
if m.Container != nil {
if err := m.Container.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("container")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("container")
}
return err
}
}
return nil
}
func (m *Bearer) validateObject(formats strfmt.Registry) error {
if swag.IsZero(m.Object) { // not required
return nil
}
for i := 0; i < len(m.Object); i++ {
if swag.IsZero(m.Object[i]) { // not required
continue
}
if m.Object[i] != nil {
if err := m.Object[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("object" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("object" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this bearer based on the context it is used
func (m *Bearer) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateContainer(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateObject(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Bearer) contextValidateContainer(ctx context.Context, formats strfmt.Registry) error {
if m.Container != nil {
if err := m.Container.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("container")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("container")
}
return err
}
}
return nil
}
func (m *Bearer) contextValidateObject(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Object); i++ {
if m.Object[i] != nil {
if err := m.Object[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("object" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("object" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *Bearer) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Bearer) UnmarshalBinary(b []byte) error {
var res Bearer
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View file

@ -0,0 +1,132 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// ContainerInfo container info
// Example: {"attribute":[{"key":"Timestamp","value":"1648810072"},{"key":"Name","value":"container"}],"basicAcl":"0x1fbf9fff","containerId":"5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv","ownerId":"NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM","placementPolicy":"REP 2","version":"2.11"}
//
// swagger:model ContainerInfo
type ContainerInfo struct {
// attributes
Attributes []*Attribute `json:"attributes"`
// basic Acl
BasicACL string `json:"basicAcl,omitempty"`
// container Id
ContainerID string `json:"containerId,omitempty"`
// owner Id
OwnerID string `json:"ownerId,omitempty"`
// placement policy
PlacementPolicy string `json:"placementPolicy,omitempty"`
// version
Version string `json:"version,omitempty"`
}
// Validate validates this container info
func (m *ContainerInfo) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAttributes(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ContainerInfo) validateAttributes(formats strfmt.Registry) error {
if swag.IsZero(m.Attributes) { // not required
return nil
}
for i := 0; i < len(m.Attributes); i++ {
if swag.IsZero(m.Attributes[i]) { // not required
continue
}
if m.Attributes[i] != nil {
if err := m.Attributes[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("attributes" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("attributes" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this container info based on the context it is used
func (m *ContainerInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateAttributes(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ContainerInfo) contextValidateAttributes(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Attributes); i++ {
if m.Attributes[i] != nil {
if err := m.Attributes[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("attributes" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("attributes" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ContainerInfo) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ContainerInfo) UnmarshalBinary(b []byte) error {
var res ContainerInfo
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

27
gen/models/error.go Normal file
View file

@ -0,0 +1,27 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
)
// Error error
//
// swagger:model Error
type Error string
// Validate validates this error
func (m Error) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this error based on context it is used
func (m Error) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

198
gen/models/filter.go Normal file
View file

@ -0,0 +1,198 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// Filter filter
// Example: {"headerType":"OBJECT","key":"FileName","matchType":"STRING_NOT_EQUAL","value":"myfile"}
//
// swagger:model Filter
type Filter struct {
// header type
// Required: true
HeaderType *HeaderType `json:"headerType"`
// key
// Required: true
Key *string `json:"key"`
// match type
// Required: true
MatchType *MatchType `json:"matchType"`
// value
// Required: true
Value *string `json:"value"`
}
// Validate validates this filter
func (m *Filter) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateHeaderType(formats); err != nil {
res = append(res, err)
}
if err := m.validateKey(formats); err != nil {
res = append(res, err)
}
if err := m.validateMatchType(formats); err != nil {
res = append(res, err)
}
if err := m.validateValue(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Filter) validateHeaderType(formats strfmt.Registry) error {
if err := validate.Required("headerType", "body", m.HeaderType); err != nil {
return err
}
if err := validate.Required("headerType", "body", m.HeaderType); err != nil {
return err
}
if m.HeaderType != nil {
if err := m.HeaderType.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("headerType")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("headerType")
}
return err
}
}
return nil
}
func (m *Filter) validateKey(formats strfmt.Registry) error {
if err := validate.Required("key", "body", m.Key); err != nil {
return err
}
return nil
}
func (m *Filter) validateMatchType(formats strfmt.Registry) error {
if err := validate.Required("matchType", "body", m.MatchType); err != nil {
return err
}
if err := validate.Required("matchType", "body", m.MatchType); err != nil {
return err
}
if m.MatchType != nil {
if err := m.MatchType.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("matchType")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("matchType")
}
return err
}
}
return nil
}
func (m *Filter) validateValue(formats strfmt.Registry) error {
if err := validate.Required("value", "body", m.Value); err != nil {
return err
}
return nil
}
// ContextValidate validate this filter based on the context it is used
func (m *Filter) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateHeaderType(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateMatchType(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Filter) contextValidateHeaderType(ctx context.Context, formats strfmt.Registry) error {
if m.HeaderType != nil {
if err := m.HeaderType.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("headerType")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("headerType")
}
return err
}
}
return nil
}
func (m *Filter) contextValidateMatchType(ctx context.Context, formats strfmt.Registry) error {
if m.MatchType != nil {
if err := m.MatchType.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("matchType")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("matchType")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *Filter) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Filter) UnmarshalBinary(b []byte) error {
var res Filter
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

81
gen/models/header_type.go Normal file
View file

@ -0,0 +1,81 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// HeaderType header type
//
// swagger:model HeaderType
type HeaderType string
func NewHeaderType(value HeaderType) *HeaderType {
return &value
}
// Pointer returns a pointer to a freshly-allocated HeaderType.
func (m HeaderType) Pointer() *HeaderType {
return &m
}
const (
// HeaderTypeREQUEST captures enum value "REQUEST"
HeaderTypeREQUEST HeaderType = "REQUEST"
// HeaderTypeOBJECT captures enum value "OBJECT"
HeaderTypeOBJECT HeaderType = "OBJECT"
// HeaderTypeSERVICE captures enum value "SERVICE"
HeaderTypeSERVICE HeaderType = "SERVICE"
)
// for schema
var headerTypeEnum []interface{}
func init() {
var res []HeaderType
if err := json.Unmarshal([]byte(`["REQUEST","OBJECT","SERVICE"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
headerTypeEnum = append(headerTypeEnum, v)
}
}
func (m HeaderType) validateHeaderTypeEnum(path, location string, value HeaderType) error {
if err := validate.EnumCase(path, location, value, headerTypeEnum, true); err != nil {
return err
}
return nil
}
// Validate validates this header type
func (m HeaderType) Validate(formats strfmt.Registry) error {
var res []error
// value enum
if err := m.validateHeaderTypeEnum("", "body", m); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validates this header type based on context it is used
func (m HeaderType) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

78
gen/models/match_type.go Normal file
View file

@ -0,0 +1,78 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// MatchType match type
//
// swagger:model MatchType
type MatchType string
func NewMatchType(value MatchType) *MatchType {
return &value
}
// Pointer returns a pointer to a freshly-allocated MatchType.
func (m MatchType) Pointer() *MatchType {
return &m
}
const (
// MatchTypeSTRINGEQUAL captures enum value "STRING_EQUAL"
MatchTypeSTRINGEQUAL MatchType = "STRING_EQUAL"
// MatchTypeSTRINGNOTEQUAL captures enum value "STRING_NOT_EQUAL"
MatchTypeSTRINGNOTEQUAL MatchType = "STRING_NOT_EQUAL"
)
// for schema
var matchTypeEnum []interface{}
func init() {
var res []MatchType
if err := json.Unmarshal([]byte(`["STRING_EQUAL","STRING_NOT_EQUAL"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
matchTypeEnum = append(matchTypeEnum, v)
}
}
func (m MatchType) validateMatchTypeEnum(path, location string, value MatchType) error {
if err := validate.EnumCase(path, location, value, matchTypeEnum, true); err != nil {
return err
}
return nil
}
// Validate validates this match type
func (m MatchType) Validate(formats strfmt.Registry) error {
var res []error
// value enum
if err := m.validateMatchTypeEnum("", "body", m); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validates this match type based on context it is used
func (m MatchType) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

93
gen/models/operation.go Normal file
View file

@ -0,0 +1,93 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// Operation operation
//
// swagger:model Operation
type Operation string
func NewOperation(value Operation) *Operation {
return &value
}
// Pointer returns a pointer to a freshly-allocated Operation.
func (m Operation) Pointer() *Operation {
return &m
}
const (
// OperationGET captures enum value "GET"
OperationGET Operation = "GET"
// OperationHEAD captures enum value "HEAD"
OperationHEAD Operation = "HEAD"
// OperationPUT captures enum value "PUT"
OperationPUT Operation = "PUT"
// OperationDELETE captures enum value "DELETE"
OperationDELETE Operation = "DELETE"
// OperationSEARCH captures enum value "SEARCH"
OperationSEARCH Operation = "SEARCH"
// OperationRANGE captures enum value "RANGE"
OperationRANGE Operation = "RANGE"
// OperationRANGEHASH captures enum value "RANGEHASH"
OperationRANGEHASH Operation = "RANGEHASH"
)
// for schema
var operationEnum []interface{}
func init() {
var res []Operation
if err := json.Unmarshal([]byte(`["GET","HEAD","PUT","DELETE","SEARCH","RANGE","RANGEHASH"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
operationEnum = append(operationEnum, v)
}
}
func (m Operation) validateOperationEnum(path, location string, value Operation) error {
if err := validate.EnumCase(path, location, value, operationEnum, true); err != nil {
return err
}
return nil
}
// Validate validates this operation
func (m Operation) Validate(formats strfmt.Registry) error {
var res []error
// value enum
if err := m.validateOperationEnum("", "body", m); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validates this operation based on context it is used
func (m Operation) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

27
gen/models/principal.go Normal file
View file

@ -0,0 +1,27 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
)
// Principal principal
//
// swagger:model Principal
type Principal string
// Validate validates this principal
func (m Principal) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this principal based on context it is used
func (m Principal) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

283
gen/models/record.go Normal file
View file

@ -0,0 +1,283 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// Record record
// Example: {"action":"ALLOW","filters":[],"operation":"GET","targets":[{"keys":[],"role":"OTHERS"}]}
//
// swagger:model Record
type Record struct {
// action
// Required: true
Action *Action `json:"action"`
// filters
// Required: true
Filters []*Filter `json:"filters"`
// operation
// Required: true
Operation *Operation `json:"operation"`
// targets
// Required: true
Targets []*Target `json:"targets"`
}
// Validate validates this record
func (m *Record) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAction(formats); err != nil {
res = append(res, err)
}
if err := m.validateFilters(formats); err != nil {
res = append(res, err)
}
if err := m.validateOperation(formats); err != nil {
res = append(res, err)
}
if err := m.validateTargets(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Record) validateAction(formats strfmt.Registry) error {
if err := validate.Required("action", "body", m.Action); err != nil {
return err
}
if err := validate.Required("action", "body", m.Action); err != nil {
return err
}
if m.Action != nil {
if err := m.Action.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("action")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("action")
}
return err
}
}
return nil
}
func (m *Record) validateFilters(formats strfmt.Registry) error {
if err := validate.Required("filters", "body", m.Filters); err != nil {
return err
}
for i := 0; i < len(m.Filters); i++ {
if swag.IsZero(m.Filters[i]) { // not required
continue
}
if m.Filters[i] != nil {
if err := m.Filters[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("filters" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("filters" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *Record) validateOperation(formats strfmt.Registry) error {
if err := validate.Required("operation", "body", m.Operation); err != nil {
return err
}
if err := validate.Required("operation", "body", m.Operation); err != nil {
return err
}
if m.Operation != nil {
if err := m.Operation.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("operation")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("operation")
}
return err
}
}
return nil
}
func (m *Record) validateTargets(formats strfmt.Registry) error {
if err := validate.Required("targets", "body", m.Targets); err != nil {
return err
}
for i := 0; i < len(m.Targets); i++ {
if swag.IsZero(m.Targets[i]) { // not required
continue
}
if m.Targets[i] != nil {
if err := m.Targets[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("targets" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("targets" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this record based on the context it is used
func (m *Record) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateAction(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateFilters(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateOperation(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateTargets(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Record) contextValidateAction(ctx context.Context, formats strfmt.Registry) error {
if m.Action != nil {
if err := m.Action.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("action")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("action")
}
return err
}
}
return nil
}
func (m *Record) contextValidateFilters(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Filters); i++ {
if m.Filters[i] != nil {
if err := m.Filters[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("filters" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("filters" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *Record) contextValidateOperation(ctx context.Context, formats strfmt.Registry) error {
if m.Operation != nil {
if err := m.Operation.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("operation")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("operation")
}
return err
}
}
return nil
}
func (m *Record) contextValidateTargets(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Targets); i++ {
if m.Targets[i] != nil {
if err := m.Targets[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("targets" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("targets" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *Record) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Record) UnmarshalBinary(b []byte) error {
var res Record
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

81
gen/models/role.go Normal file
View file

@ -0,0 +1,81 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// Role role
//
// swagger:model Role
type Role string
func NewRole(value Role) *Role {
return &value
}
// Pointer returns a pointer to a freshly-allocated Role.
func (m Role) Pointer() *Role {
return &m
}
const (
// RoleUSER captures enum value "USER"
RoleUSER Role = "USER"
// RoleSYSTEM captures enum value "SYSTEM"
RoleSYSTEM Role = "SYSTEM"
// RoleOTHERS captures enum value "OTHERS"
RoleOTHERS Role = "OTHERS"
)
// for schema
var roleEnum []interface{}
func init() {
var res []Role
if err := json.Unmarshal([]byte(`["USER","SYSTEM","OTHERS"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
roleEnum = append(roleEnum, v)
}
}
func (m Role) validateRoleEnum(path, location string, value Role) error {
if err := validate.EnumCase(path, location, value, roleEnum, true); err != nil {
return err
}
return nil
}
// Validate validates this role
func (m Role) Validate(formats strfmt.Registry) error {
var res []error
// value enum
if err := m.validateRoleEnum("", "body", m); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validates this role based on context it is used
func (m Role) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

114
gen/models/rule.go Normal file
View file

@ -0,0 +1,114 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// Rule rule
//
// swagger:model Rule
type Rule struct {
// container Id
ContainerID string `json:"containerId,omitempty"`
// verb
// Required: true
Verb *Verb `json:"verb"`
}
// Validate validates this rule
func (m *Rule) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateVerb(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Rule) validateVerb(formats strfmt.Registry) error {
if err := validate.Required("verb", "body", m.Verb); err != nil {
return err
}
if err := validate.Required("verb", "body", m.Verb); err != nil {
return err
}
if m.Verb != nil {
if err := m.Verb.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("verb")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("verb")
}
return err
}
}
return nil
}
// ContextValidate validate this rule based on the context it is used
func (m *Rule) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateVerb(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Rule) contextValidateVerb(ctx context.Context, formats strfmt.Registry) error {
if m.Verb != nil {
if err := m.Verb.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("verb")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("verb")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *Rule) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Rule) UnmarshalBinary(b []byte) error {
var res Rule
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

129
gen/models/target.go Normal file
View file

@ -0,0 +1,129 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// Target target
// Example: {"keys":["021dc56fc6d81d581ae7605a8e00e0e0bab6cbad566a924a527339475a97a8e38e"],"role":"USER"}
//
// swagger:model Target
type Target struct {
// keys
// Required: true
Keys []string `json:"keys"`
// role
// Required: true
Role *Role `json:"role"`
}
// Validate validates this target
func (m *Target) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateKeys(formats); err != nil {
res = append(res, err)
}
if err := m.validateRole(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Target) validateKeys(formats strfmt.Registry) error {
if err := validate.Required("keys", "body", m.Keys); err != nil {
return err
}
return nil
}
func (m *Target) validateRole(formats strfmt.Registry) error {
if err := validate.Required("role", "body", m.Role); err != nil {
return err
}
if err := validate.Required("role", "body", m.Role); err != nil {
return err
}
if m.Role != nil {
if err := m.Role.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("role")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("role")
}
return err
}
}
return nil
}
// ContextValidate validate this target based on the context it is used
func (m *Target) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateRole(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Target) contextValidateRole(ctx context.Context, formats strfmt.Registry) error {
if m.Role != nil {
if err := m.Role.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("role")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("role")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *Target) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Target) UnmarshalBinary(b []byte) error {
var res Target
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View file

@ -0,0 +1,129 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// TokenResponse token response
// Example: [{"token":"sometoken-todo-add","type":"object"},{"token":"ChCpanIBJCpJuJz42KOmGMSnEhsKGTWquaX2Lq6GhhO4faOYkLD0f9WkXuYJlq4aBAhnGAMiIQJgFcIEghQB5lq3AJZOVswInwc1IGhlQ7NCUh4DFO3UATIECAEQAQ==","type":"container"}]
//
// swagger:model TokenResponse
type TokenResponse struct {
// token
// Required: true
Token *string `json:"token"`
// type
// Required: true
Type *TokenType `json:"type"`
}
// Validate validates this token response
func (m *TokenResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateToken(formats); err != nil {
res = append(res, err)
}
if err := m.validateType(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *TokenResponse) validateToken(formats strfmt.Registry) error {
if err := validate.Required("token", "body", m.Token); err != nil {
return err
}
return nil
}
func (m *TokenResponse) validateType(formats strfmt.Registry) error {
if err := validate.Required("type", "body", m.Type); err != nil {
return err
}
if err := validate.Required("type", "body", m.Type); err != nil {
return err
}
if m.Type != nil {
if err := m.Type.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("type")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("type")
}
return err
}
}
return nil
}
// ContextValidate validate this token response based on the context it is used
func (m *TokenResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateType(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *TokenResponse) contextValidateType(ctx context.Context, formats strfmt.Registry) error {
if m.Type != nil {
if err := m.Type.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("type")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("type")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *TokenResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *TokenResponse) UnmarshalBinary(b []byte) error {
var res TokenResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

78
gen/models/token_type.go Normal file
View file

@ -0,0 +1,78 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// TokenType token type
//
// swagger:model TokenType
type TokenType string
func NewTokenType(value TokenType) *TokenType {
return &value
}
// Pointer returns a pointer to a freshly-allocated TokenType.
func (m TokenType) Pointer() *TokenType {
return &m
}
const (
// TokenTypeObject captures enum value "object"
TokenTypeObject TokenType = "object"
// TokenTypeContainer captures enum value "container"
TokenTypeContainer TokenType = "container"
)
// for schema
var tokenTypeEnum []interface{}
func init() {
var res []TokenType
if err := json.Unmarshal([]byte(`["object","container"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
tokenTypeEnum = append(tokenTypeEnum, v)
}
}
func (m TokenType) validateTokenTypeEnum(path, location string, value TokenType) error {
if err := validate.EnumCase(path, location, value, tokenTypeEnum, true); err != nil {
return err
}
return nil
}
// Validate validates this token type
func (m TokenType) Validate(formats strfmt.Registry) error {
var res []error
// value enum
if err := m.validateTokenTypeEnum("", "body", m); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validates this token type based on context it is used
func (m TokenType) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

81
gen/models/verb.go Normal file
View file

@ -0,0 +1,81 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// Verb verb
//
// swagger:model Verb
type Verb string
func NewVerb(value Verb) *Verb {
return &value
}
// Pointer returns a pointer to a freshly-allocated Verb.
func (m Verb) Pointer() *Verb {
return &m
}
const (
// VerbPUT captures enum value "PUT"
VerbPUT Verb = "PUT"
// VerbDELETE captures enum value "DELETE"
VerbDELETE Verb = "DELETE"
// VerbSETEACL captures enum value "SETEACL"
VerbSETEACL Verb = "SETEACL"
)
// for schema
var verbEnum []interface{}
func init() {
var res []Verb
if err := json.Unmarshal([]byte(`["PUT","DELETE","SETEACL"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
verbEnum = append(verbEnum, v)
}
}
func (m Verb) validateVerbEnum(path, location string, value Verb) error {
if err := validate.EnumCase(path, location, value, verbEnum, true); err != nil {
return err
}
return nil
}
// Validate validates this verb
func (m Verb) Validate(formats strfmt.Registry) error {
var res []error
// value enum
if err := m.validateVerbEnum("", "body", m); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validates this verb based on context it is used
func (m Verb) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

19
gen/restapi/doc.go Normal file
View file

@ -0,0 +1,19 @@
// Code generated by go-swagger; DO NOT EDIT.
// Package restapi REST API NeoFS
//
// REST API NeoFS
// Schemes:
// http
// Host: localhost:8090
// BasePath: /v1
// Version: v1
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// swagger:meta
package restapi

1122
gen/restapi/embedded_spec.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// AuthHandlerFunc turns a function with the right signature into a auth handler
type AuthHandlerFunc func(AuthParams) middleware.Responder
// Handle executing the request and returning a response
func (fn AuthHandlerFunc) Handle(params AuthParams) middleware.Responder {
return fn(params)
}
// AuthHandler interface for that can handle valid auth params
type AuthHandler interface {
Handle(AuthParams) middleware.Responder
}
// NewAuth creates a new http.Handler for the auth operation
func NewAuth(ctx *middleware.Context, handler AuthHandler) *Auth {
return &Auth{Context: ctx, Handler: handler}
}
/* Auth swagger:route POST /auth auth
Form bearer token to futher requests
*/
type Auth struct {
Context *middleware.Context
Handler AuthHandler
}
func (o *Auth) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewAuthParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View file

@ -0,0 +1,198 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
)
// NewAuthParams creates a new AuthParams object
// with the default values initialized.
func NewAuthParams() AuthParams {
var (
// initialize parameters with default values
xNeofsTokenLifetimeDefault = int64(100)
)
return AuthParams{
XNeofsTokenLifetime: &xNeofsTokenLifetimeDefault,
}
}
// AuthParams contains all the bound params for the auth operation
// typically these are obtained from a http.Request
//
// swagger:parameters auth
type AuthParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*Token lifetime in epoch
In: header
Default: 100
*/
XNeofsTokenLifetime *int64
/*Supported operation scope for token
Required: true
In: header
*/
XNeofsTokenScope string
/*Public key of user
Required: true
In: header
*/
XNeofsTokenSignatureKey string
/*Bearer token
Required: true
In: body
*/
Token *models.Bearer
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewAuthParams() beforehand.
func (o *AuthParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if err := o.bindXNeofsTokenLifetime(r.Header[http.CanonicalHeaderKey("X-Neofs-Token-Lifetime")], true, route.Formats); err != nil {
res = append(res, err)
}
if err := o.bindXNeofsTokenScope(r.Header[http.CanonicalHeaderKey("X-Neofs-Token-Scope")], true, route.Formats); err != nil {
res = append(res, err)
}
if err := o.bindXNeofsTokenSignatureKey(r.Header[http.CanonicalHeaderKey("X-Neofs-Token-Signature-Key")], true, route.Formats); err != nil {
res = append(res, err)
}
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.Bearer
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("token", "body", ""))
} else {
res = append(res, errors.NewParseError("token", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(context.Background())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Token = &body
}
}
} else {
res = append(res, errors.Required("token", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindXNeofsTokenLifetime binds and validates parameter XNeofsTokenLifetime from header.
func (o *AuthParams) bindXNeofsTokenLifetime(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewAuthParams()
return nil
}
value, err := swag.ConvertInt64(raw)
if err != nil {
return errors.InvalidType("X-Neofs-Token-Lifetime", "header", "int64", raw)
}
o.XNeofsTokenLifetime = &value
return nil
}
// bindXNeofsTokenScope binds and validates parameter XNeofsTokenScope from header.
func (o *AuthParams) bindXNeofsTokenScope(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("X-Neofs-Token-Scope", "header", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
if err := validate.RequiredString("X-Neofs-Token-Scope", "header", raw); err != nil {
return err
}
o.XNeofsTokenScope = raw
if err := o.validateXNeofsTokenScope(formats); err != nil {
return err
}
return nil
}
// validateXNeofsTokenScope carries on validations for parameter XNeofsTokenScope
func (o *AuthParams) validateXNeofsTokenScope(formats strfmt.Registry) error {
if err := validate.EnumCase("X-Neofs-Token-Scope", "header", o.XNeofsTokenScope, []interface{}{"object", "container"}, true); err != nil {
return err
}
return nil
}
// bindXNeofsTokenSignatureKey binds and validates parameter XNeofsTokenSignatureKey from header.
func (o *AuthParams) bindXNeofsTokenSignatureKey(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("X-Neofs-Token-Signature-Key", "header", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
if err := validate.RequiredString("X-Neofs-Token-Signature-Key", "header", raw); err != nil {
return err
}
o.XNeofsTokenSignatureKey = raw
return nil
}

View file

@ -0,0 +1,100 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
)
// AuthOKCode is the HTTP code returned for type AuthOK
const AuthOKCode int = 200
/*AuthOK Base64 encoded stable binary marshaled bearer token
swagger:response authOK
*/
type AuthOK struct {
/*
In: Body
*/
Payload *models.TokenResponse `json:"body,omitempty"`
}
// NewAuthOK creates AuthOK with default headers values
func NewAuthOK() *AuthOK {
return &AuthOK{}
}
// WithPayload adds the payload to the auth o k response
func (o *AuthOK) WithPayload(payload *models.TokenResponse) *AuthOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the auth o k response
func (o *AuthOK) SetPayload(payload *models.TokenResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *AuthOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// AuthBadRequestCode is the HTTP code returned for type AuthBadRequest
const AuthBadRequestCode int = 400
/*AuthBadRequest Bad request
swagger:response authBadRequest
*/
type AuthBadRequest struct {
/*
In: Body
*/
Payload models.Error `json:"body,omitempty"`
}
// NewAuthBadRequest creates AuthBadRequest with default headers values
func NewAuthBadRequest() *AuthBadRequest {
return &AuthBadRequest{}
}
// WithPayload adds the payload to the auth bad request response
func (o *AuthBadRequest) WithPayload(payload models.Error) *AuthBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the auth bad request response
func (o *AuthBadRequest) SetPayload(payload models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *AuthBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(400)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}

View file

@ -0,0 +1,56 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// GetContainerHandlerFunc turns a function with the right signature into a get container handler
type GetContainerHandlerFunc func(GetContainerParams) middleware.Responder
// Handle executing the request and returning a response
func (fn GetContainerHandlerFunc) Handle(params GetContainerParams) middleware.Responder {
return fn(params)
}
// GetContainerHandler interface for that can handle valid get container params
type GetContainerHandler interface {
Handle(GetContainerParams) middleware.Responder
}
// NewGetContainer creates a new http.Handler for the get container operation
func NewGetContainer(ctx *middleware.Context, handler GetContainerHandler) *GetContainer {
return &GetContainer{Context: ctx, Handler: handler}
}
/* GetContainer swagger:route GET /containers/{containerId} getContainer
Get container by id
*/
type GetContainer struct {
Context *middleware.Context
Handler GetContainerHandler
}
func (o *GetContainer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewGetContainerParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View file

@ -0,0 +1,71 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
)
// NewGetContainerParams creates a new GetContainerParams object
//
// There are no default values defined in the spec.
func NewGetContainerParams() GetContainerParams {
return GetContainerParams{}
}
// GetContainerParams contains all the bound params for the get container operation
// typically these are obtained from a http.Request
//
// swagger:parameters getContainer
type GetContainerParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*Base58 encoded container id
Required: true
In: path
*/
ContainerID string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewGetContainerParams() beforehand.
func (o *GetContainerParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
rContainerID, rhkContainerID, _ := route.Params.GetOK("containerId")
if err := o.bindContainerID(rContainerID, rhkContainerID, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindContainerID binds and validates parameter ContainerID from path.
func (o *GetContainerParams) bindContainerID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.ContainerID = raw
return nil
}

View file

@ -0,0 +1,100 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
)
// GetContainerOKCode is the HTTP code returned for type GetContainerOK
const GetContainerOKCode int = 200
/*GetContainerOK Container info
swagger:response getContainerOK
*/
type GetContainerOK struct {
/*
In: Body
*/
Payload *models.ContainerInfo `json:"body,omitempty"`
}
// NewGetContainerOK creates GetContainerOK with default headers values
func NewGetContainerOK() *GetContainerOK {
return &GetContainerOK{}
}
// WithPayload adds the payload to the get container o k response
func (o *GetContainerOK) WithPayload(payload *models.ContainerInfo) *GetContainerOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get container o k response
func (o *GetContainerOK) SetPayload(payload *models.ContainerInfo) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetContainerOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// GetContainerBadRequestCode is the HTTP code returned for type GetContainerBadRequest
const GetContainerBadRequestCode int = 400
/*GetContainerBadRequest Bad request
swagger:response getContainerBadRequest
*/
type GetContainerBadRequest struct {
/*
In: Body
*/
Payload models.Error `json:"body,omitempty"`
}
// NewGetContainerBadRequest creates GetContainerBadRequest with default headers values
func NewGetContainerBadRequest() *GetContainerBadRequest {
return &GetContainerBadRequest{}
}
// WithPayload adds the payload to the get container bad request response
func (o *GetContainerBadRequest) WithPayload(payload models.Error) *GetContainerBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get container bad request response
func (o *GetContainerBadRequest) SetPayload(payload models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetContainerBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(400)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}

View file

@ -0,0 +1,368 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"fmt"
"net/http"
"strings"
"github.com/go-openapi/errors"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/runtime/security"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
)
// NewNeofsRestGwAPI creates a new NeofsRestGw instance
func NewNeofsRestGwAPI(spec *loads.Document) *NeofsRestGwAPI {
return &NeofsRestGwAPI{
handlers: make(map[string]map[string]http.Handler),
formats: strfmt.Default,
defaultConsumes: "application/json",
defaultProduces: "application/json",
customConsumers: make(map[string]runtime.Consumer),
customProducers: make(map[string]runtime.Producer),
PreServerShutdown: func() {},
ServerShutdown: func() {},
spec: spec,
useSwaggerUI: false,
ServeError: errors.ServeError,
BasicAuthenticator: security.BasicAuth,
APIKeyAuthenticator: security.APIKeyAuth,
BearerAuthenticator: security.BearerAuth,
JSONConsumer: runtime.JSONConsumer(),
JSONProducer: runtime.JSONProducer(),
AuthHandler: AuthHandlerFunc(func(params AuthParams) middleware.Responder {
return middleware.NotImplemented("operation Auth has not yet been implemented")
}),
GetContainerHandler: GetContainerHandlerFunc(func(params GetContainerParams) middleware.Responder {
return middleware.NotImplemented("operation GetContainer has not yet been implemented")
}),
PutContainerHandler: PutContainerHandlerFunc(func(params PutContainerParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation PutContainer has not yet been implemented")
}),
PutObjectHandler: PutObjectHandlerFunc(func(params PutObjectParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation PutObject has not yet been implemented")
}),
// Applies when the "Authorization" header is set
BearerAuthAuth: func(token string) (*models.Principal, error) {
return nil, errors.NotImplemented("api key auth (BearerAuth) Authorization from header param [Authorization] has not yet been implemented")
},
// default authorizer is authorized meaning no requests are blocked
APIAuthorizer: security.Authorized(),
}
}
/*NeofsRestGwAPI REST API NeoFS */
type NeofsRestGwAPI struct {
spec *loads.Document
context *middleware.Context
handlers map[string]map[string]http.Handler
formats strfmt.Registry
customConsumers map[string]runtime.Consumer
customProducers map[string]runtime.Producer
defaultConsumes string
defaultProduces string
Middleware func(middleware.Builder) http.Handler
useSwaggerUI bool
// BasicAuthenticator generates a runtime.Authenticator from the supplied basic auth function.
// It has a default implementation in the security package, however you can replace it for your particular usage.
BasicAuthenticator func(security.UserPassAuthentication) runtime.Authenticator
// APIKeyAuthenticator generates a runtime.Authenticator from the supplied token auth function.
// It has a default implementation in the security package, however you can replace it for your particular usage.
APIKeyAuthenticator func(string, string, security.TokenAuthentication) runtime.Authenticator
// BearerAuthenticator generates a runtime.Authenticator from the supplied bearer token auth function.
// It has a default implementation in the security package, however you can replace it for your particular usage.
BearerAuthenticator func(string, security.ScopedTokenAuthentication) runtime.Authenticator
// JSONConsumer registers a consumer for the following mime types:
// - application/json
JSONConsumer runtime.Consumer
// JSONProducer registers a producer for the following mime types:
// - application/json
JSONProducer runtime.Producer
// BearerAuthAuth registers a function that takes a token and returns a principal
// it performs authentication based on an api key Authorization provided in the header
BearerAuthAuth func(string) (*models.Principal, error)
// APIAuthorizer provides access control (ACL/RBAC/ABAC) by providing access to the request and authenticated principal
APIAuthorizer runtime.Authorizer
// AuthHandler sets the operation handler for the auth operation
AuthHandler AuthHandler
// GetContainerHandler sets the operation handler for the get container operation
GetContainerHandler GetContainerHandler
// PutContainerHandler sets the operation handler for the put container operation
PutContainerHandler PutContainerHandler
// PutObjectHandler sets the operation handler for the put object operation
PutObjectHandler PutObjectHandler
// ServeError is called when an error is received, there is a default handler
// but you can set your own with this
ServeError func(http.ResponseWriter, *http.Request, error)
// PreServerShutdown is called before the HTTP(S) server is shutdown
// This allows for custom functions to get executed before the HTTP(S) server stops accepting traffic
PreServerShutdown func()
// ServerShutdown is called when the HTTP(S) server is shut down and done
// handling all active connections and does not accept connections any more
ServerShutdown func()
// Custom command line argument groups with their descriptions
CommandLineOptionsGroups []swag.CommandLineOptionsGroup
// User defined logger function.
Logger func(string, ...interface{})
}
// UseRedoc for documentation at /docs
func (o *NeofsRestGwAPI) UseRedoc() {
o.useSwaggerUI = false
}
// UseSwaggerUI for documentation at /docs
func (o *NeofsRestGwAPI) UseSwaggerUI() {
o.useSwaggerUI = true
}
// SetDefaultProduces sets the default produces media type
func (o *NeofsRestGwAPI) SetDefaultProduces(mediaType string) {
o.defaultProduces = mediaType
}
// SetDefaultConsumes returns the default consumes media type
func (o *NeofsRestGwAPI) SetDefaultConsumes(mediaType string) {
o.defaultConsumes = mediaType
}
// SetSpec sets a spec that will be served for the clients.
func (o *NeofsRestGwAPI) SetSpec(spec *loads.Document) {
o.spec = spec
}
// DefaultProduces returns the default produces media type
func (o *NeofsRestGwAPI) DefaultProduces() string {
return o.defaultProduces
}
// DefaultConsumes returns the default consumes media type
func (o *NeofsRestGwAPI) DefaultConsumes() string {
return o.defaultConsumes
}
// Formats returns the registered string formats
func (o *NeofsRestGwAPI) Formats() strfmt.Registry {
return o.formats
}
// RegisterFormat registers a custom format validator
func (o *NeofsRestGwAPI) RegisterFormat(name string, format strfmt.Format, validator strfmt.Validator) {
o.formats.Add(name, format, validator)
}
// Validate validates the registrations in the NeofsRestGwAPI
func (o *NeofsRestGwAPI) Validate() error {
var unregistered []string
if o.JSONConsumer == nil {
unregistered = append(unregistered, "JSONConsumer")
}
if o.JSONProducer == nil {
unregistered = append(unregistered, "JSONProducer")
}
if o.BearerAuthAuth == nil {
unregistered = append(unregistered, "AuthorizationAuth")
}
if o.AuthHandler == nil {
unregistered = append(unregistered, "AuthHandler")
}
if o.GetContainerHandler == nil {
unregistered = append(unregistered, "GetContainerHandler")
}
if o.PutContainerHandler == nil {
unregistered = append(unregistered, "PutContainerHandler")
}
if o.PutObjectHandler == nil {
unregistered = append(unregistered, "PutObjectHandler")
}
if len(unregistered) > 0 {
return fmt.Errorf("missing registration: %s", strings.Join(unregistered, ", "))
}
return nil
}
// ServeErrorFor gets a error handler for a given operation id
func (o *NeofsRestGwAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
return o.ServeError
}
// AuthenticatorsFor gets the authenticators for the specified security schemes
func (o *NeofsRestGwAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {
result := make(map[string]runtime.Authenticator)
for name := range schemes {
switch name {
case "BearerAuth":
scheme := schemes[name]
result[name] = o.APIKeyAuthenticator(scheme.Name, scheme.In, func(token string) (interface{}, error) {
return o.BearerAuthAuth(token)
})
}
}
return result
}
// Authorizer returns the registered authorizer
func (o *NeofsRestGwAPI) Authorizer() runtime.Authorizer {
return o.APIAuthorizer
}
// ConsumersFor gets the consumers for the specified media types.
// MIME type parameters are ignored here.
func (o *NeofsRestGwAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
result := make(map[string]runtime.Consumer, len(mediaTypes))
for _, mt := range mediaTypes {
switch mt {
case "application/json":
result["application/json"] = o.JSONConsumer
}
if c, ok := o.customConsumers[mt]; ok {
result[mt] = c
}
}
return result
}
// ProducersFor gets the producers for the specified media types.
// MIME type parameters are ignored here.
func (o *NeofsRestGwAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
result := make(map[string]runtime.Producer, len(mediaTypes))
for _, mt := range mediaTypes {
switch mt {
case "application/json":
result["application/json"] = o.JSONProducer
}
if p, ok := o.customProducers[mt]; ok {
result[mt] = p
}
}
return result
}
// HandlerFor gets a http.Handler for the provided operation method and path
func (o *NeofsRestGwAPI) HandlerFor(method, path string) (http.Handler, bool) {
if o.handlers == nil {
return nil, false
}
um := strings.ToUpper(method)
if _, ok := o.handlers[um]; !ok {
return nil, false
}
if path == "/" {
path = ""
}
h, ok := o.handlers[um][path]
return h, ok
}
// Context returns the middleware context for the neofs rest gw API
func (o *NeofsRestGwAPI) Context() *middleware.Context {
if o.context == nil {
o.context = middleware.NewRoutableContext(o.spec, o, nil)
}
return o.context
}
func (o *NeofsRestGwAPI) initHandlerCache() {
o.Context() // don't care about the result, just that the initialization happened
if o.handlers == nil {
o.handlers = make(map[string]map[string]http.Handler)
}
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/auth"] = NewAuth(o.context, o.AuthHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/containers/{containerId}"] = NewGetContainer(o.context, o.GetContainerHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/containers"] = NewPutContainer(o.context, o.PutContainerHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/objects"] = NewPutObject(o.context, o.PutObjectHandler)
}
// Serve creates a http handler to serve the API over HTTP
// can be used directly in http.ListenAndServe(":8000", api.Serve(nil))
func (o *NeofsRestGwAPI) Serve(builder middleware.Builder) http.Handler {
o.Init()
if o.Middleware != nil {
return o.Middleware(builder)
}
if o.useSwaggerUI {
return o.context.APIHandlerSwaggerUI(builder)
}
return o.context.APIHandler(builder)
}
// Init allows you to just initialize the handler cache, you can then recompose the middleware as you see fit
func (o *NeofsRestGwAPI) Init() {
if len(o.handlers) == 0 {
o.initHandlerCache()
}
}
// RegisterConsumer allows you to add (or override) a consumer for a media type.
func (o *NeofsRestGwAPI) RegisterConsumer(mediaType string, consumer runtime.Consumer) {
o.customConsumers[mediaType] = consumer
}
// RegisterProducer allows you to add (or override) a producer for a media type.
func (o *NeofsRestGwAPI) RegisterProducer(mediaType string, producer runtime.Producer) {
o.customProducers[mediaType] = producer
}
// AddMiddlewareFor adds a http middleware to existing handler
func (o *NeofsRestGwAPI) AddMiddlewareFor(method, path string, builder middleware.Builder) {
um := strings.ToUpper(method)
if path == "/" {
path = ""
}
o.Init()
if h, ok := o.handlers[um][path]; ok {
o.handlers[method][path] = builder(h)
}
}

View file

@ -0,0 +1,196 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"context"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
)
// PutContainerHandlerFunc turns a function with the right signature into a put container handler
type PutContainerHandlerFunc func(PutContainerParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn PutContainerHandlerFunc) Handle(params PutContainerParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// PutContainerHandler interface for that can handle valid put container params
type PutContainerHandler interface {
Handle(PutContainerParams, *models.Principal) middleware.Responder
}
// NewPutContainer creates a new http.Handler for the put container operation
func NewPutContainer(ctx *middleware.Context, handler PutContainerHandler) *PutContainer {
return &PutContainer{Context: ctx, Handler: handler}
}
/* PutContainer swagger:route PUT /containers putContainer
Create new container in NeoFS
*/
type PutContainer struct {
Context *middleware.Context
Handler PutContainerHandler
}
func (o *PutContainer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewPutContainerParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}
// PutContainerBody put container body
// Example: {"basicAcl":"public-read-write","containerId":"container","placementPolicy":"REP 3"}
//
// swagger:model PutContainerBody
type PutContainerBody struct {
// basic Acl
BasicACL string `json:"basicAcl,omitempty"`
// container name
// Required: true
ContainerName *string `json:"containerName"`
// placement policy
PlacementPolicy string `json:"placementPolicy,omitempty"`
}
// Validate validates this put container body
func (o *PutContainerBody) Validate(formats strfmt.Registry) error {
var res []error
if err := o.validateContainerName(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (o *PutContainerBody) validateContainerName(formats strfmt.Registry) error {
if err := validate.Required("container"+"."+"containerName", "body", o.ContainerName); err != nil {
return err
}
return nil
}
// ContextValidate validates this put container body based on context it is used
func (o *PutContainerBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (o *PutContainerBody) MarshalBinary() ([]byte, error) {
if o == nil {
return nil, nil
}
return swag.WriteJSON(o)
}
// UnmarshalBinary interface implementation
func (o *PutContainerBody) UnmarshalBinary(b []byte) error {
var res PutContainerBody
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*o = res
return nil
}
// PutContainerOKBody put container o k body
// Example: {"containerId":"5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv"}
//
// swagger:model PutContainerOKBody
type PutContainerOKBody struct {
// container Id
// Required: true
ContainerID *string `json:"containerId"`
}
// Validate validates this put container o k body
func (o *PutContainerOKBody) Validate(formats strfmt.Registry) error {
var res []error
if err := o.validateContainerID(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (o *PutContainerOKBody) validateContainerID(formats strfmt.Registry) error {
if err := validate.Required("putContainerOK"+"."+"containerId", "body", o.ContainerID); err != nil {
return err
}
return nil
}
// ContextValidate validates this put container o k body based on context it is used
func (o *PutContainerOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (o *PutContainerOKBody) MarshalBinary() ([]byte, error) {
if o == nil {
return nil, nil
}
return swag.WriteJSON(o)
}
// UnmarshalBinary interface implementation
func (o *PutContainerOKBody) UnmarshalBinary(b []byte) error {
var res PutContainerOKBody
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*o = res
return nil
}

View file

@ -0,0 +1,142 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// NewPutContainerParams creates a new PutContainerParams object
//
// There are no default values defined in the spec.
func NewPutContainerParams() PutContainerParams {
return PutContainerParams{}
}
// PutContainerParams contains all the bound params for the put container operation
// typically these are obtained from a http.Request
//
// swagger:parameters putContainer
type PutContainerParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*Base64 encoded signature for bearer token
Required: true
In: header
*/
XNeofsTokenSignature string
/*Hex encoded the public part of the key that signed the bearer token
Required: true
In: header
*/
XNeofsTokenSignatureKey string
/*Container info
Required: true
In: body
*/
Container PutContainerBody
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewPutContainerParams() beforehand.
func (o *PutContainerParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if err := o.bindXNeofsTokenSignature(r.Header[http.CanonicalHeaderKey("X-Neofs-Token-Signature")], true, route.Formats); err != nil {
res = append(res, err)
}
if err := o.bindXNeofsTokenSignatureKey(r.Header[http.CanonicalHeaderKey("X-Neofs-Token-signature-Key")], true, route.Formats); err != nil {
res = append(res, err)
}
if runtime.HasBody(r) {
defer r.Body.Close()
var body PutContainerBody
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("container", "body", ""))
} else {
res = append(res, errors.NewParseError("container", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(context.Background())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Container = body
}
}
} else {
res = append(res, errors.Required("container", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindXNeofsTokenSignature binds and validates parameter XNeofsTokenSignature from header.
func (o *PutContainerParams) bindXNeofsTokenSignature(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("X-Neofs-Token-Signature", "header", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
if err := validate.RequiredString("X-Neofs-Token-Signature", "header", raw); err != nil {
return err
}
o.XNeofsTokenSignature = raw
return nil
}
// bindXNeofsTokenSignatureKey binds and validates parameter XNeofsTokenSignatureKey from header.
func (o *PutContainerParams) bindXNeofsTokenSignatureKey(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("X-Neofs-Token-signature-Key", "header", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
if err := validate.RequiredString("X-Neofs-Token-signature-Key", "header", raw); err != nil {
return err
}
o.XNeofsTokenSignatureKey = raw
return nil
}

View file

@ -0,0 +1,100 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
)
// PutContainerOKCode is the HTTP code returned for type PutContainerOK
const PutContainerOKCode int = 200
/*PutContainerOK Address of uploaded objects
swagger:response putContainerOK
*/
type PutContainerOK struct {
/*
In: Body
*/
Payload *PutContainerOKBody `json:"body,omitempty"`
}
// NewPutContainerOK creates PutContainerOK with default headers values
func NewPutContainerOK() *PutContainerOK {
return &PutContainerOK{}
}
// WithPayload adds the payload to the put container o k response
func (o *PutContainerOK) WithPayload(payload *PutContainerOKBody) *PutContainerOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the put container o k response
func (o *PutContainerOK) SetPayload(payload *PutContainerOKBody) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PutContainerOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// PutContainerBadRequestCode is the HTTP code returned for type PutContainerBadRequest
const PutContainerBadRequestCode int = 400
/*PutContainerBadRequest Bad request
swagger:response putContainerBadRequest
*/
type PutContainerBadRequest struct {
/*
In: Body
*/
Payload models.Error `json:"body,omitempty"`
}
// NewPutContainerBadRequest creates PutContainerBadRequest with default headers values
func NewPutContainerBadRequest() *PutContainerBadRequest {
return &PutContainerBadRequest{}
}
// WithPayload adds the payload to the put container bad request response
func (o *PutContainerBadRequest) WithPayload(payload models.Error) *PutContainerBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the put container bad request response
func (o *PutContainerBadRequest) SetPayload(payload models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PutContainerBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(400)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}

View file

@ -0,0 +1,227 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"context"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
)
// PutObjectHandlerFunc turns a function with the right signature into a put object handler
type PutObjectHandlerFunc func(PutObjectParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn PutObjectHandlerFunc) Handle(params PutObjectParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// PutObjectHandler interface for that can handle valid put object params
type PutObjectHandler interface {
Handle(PutObjectParams, *models.Principal) middleware.Responder
}
// NewPutObject creates a new http.Handler for the put object operation
func NewPutObject(ctx *middleware.Context, handler PutObjectHandler) *PutObject {
return &PutObject{Context: ctx, Handler: handler}
}
/* PutObject swagger:route PUT /objects putObject
Upload object to NeoFS
*/
type PutObject struct {
Context *middleware.Context
Handler PutObjectHandler
}
func (o *PutObject) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewPutObjectParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}
// PutObjectBody put object body
// Example: {"containerId":"5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv","fileName":"myFile.txt","payload":"Y29udGVudCBvZiBmaWxl"}
//
// swagger:model PutObjectBody
type PutObjectBody struct {
// container Id
// Required: true
ContainerID *string `json:"containerId"`
// file name
// Required: true
FileName *string `json:"fileName"`
// payload
Payload string `json:"payload,omitempty"`
}
// Validate validates this put object body
func (o *PutObjectBody) Validate(formats strfmt.Registry) error {
var res []error
if err := o.validateContainerID(formats); err != nil {
res = append(res, err)
}
if err := o.validateFileName(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (o *PutObjectBody) validateContainerID(formats strfmt.Registry) error {
if err := validate.Required("object"+"."+"containerId", "body", o.ContainerID); err != nil {
return err
}
return nil
}
func (o *PutObjectBody) validateFileName(formats strfmt.Registry) error {
if err := validate.Required("object"+"."+"fileName", "body", o.FileName); err != nil {
return err
}
return nil
}
// ContextValidate validates this put object body based on context it is used
func (o *PutObjectBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (o *PutObjectBody) MarshalBinary() ([]byte, error) {
if o == nil {
return nil, nil
}
return swag.WriteJSON(o)
}
// UnmarshalBinary interface implementation
func (o *PutObjectBody) UnmarshalBinary(b []byte) error {
var res PutObjectBody
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*o = res
return nil
}
// PutObjectOKBody put object o k body
// Example: {"containerId":"5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv","objectId":"8N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"}
//
// swagger:model PutObjectOKBody
type PutObjectOKBody struct {
// container Id
// Required: true
ContainerID *string `json:"containerId"`
// object Id
// Required: true
ObjectID *string `json:"objectId"`
}
// Validate validates this put object o k body
func (o *PutObjectOKBody) Validate(formats strfmt.Registry) error {
var res []error
if err := o.validateContainerID(formats); err != nil {
res = append(res, err)
}
if err := o.validateObjectID(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (o *PutObjectOKBody) validateContainerID(formats strfmt.Registry) error {
if err := validate.Required("putObjectOK"+"."+"containerId", "body", o.ContainerID); err != nil {
return err
}
return nil
}
func (o *PutObjectOKBody) validateObjectID(formats strfmt.Registry) error {
if err := validate.Required("putObjectOK"+"."+"objectId", "body", o.ObjectID); err != nil {
return err
}
return nil
}
// ContextValidate validates this put object o k body based on context it is used
func (o *PutObjectOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (o *PutObjectOKBody) MarshalBinary() ([]byte, error) {
if o == nil {
return nil, nil
}
return swag.WriteJSON(o)
}
// UnmarshalBinary interface implementation
func (o *PutObjectOKBody) UnmarshalBinary(b []byte) error {
var res PutObjectOKBody
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*o = res
return nil
}

View file

@ -0,0 +1,142 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// NewPutObjectParams creates a new PutObjectParams object
//
// There are no default values defined in the spec.
func NewPutObjectParams() PutObjectParams {
return PutObjectParams{}
}
// PutObjectParams contains all the bound params for the put object operation
// typically these are obtained from a http.Request
//
// swagger:parameters putObject
type PutObjectParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*Base64 encoded signature for bearer token
Required: true
In: header
*/
XNeofsTokenSignature string
/*Hex encoded the public part of the key that signed the bearer token
Required: true
In: header
*/
XNeofsTokenSignatureKey string
/*Object info to upload
Required: true
In: body
*/
Object PutObjectBody
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewPutObjectParams() beforehand.
func (o *PutObjectParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if err := o.bindXNeofsTokenSignature(r.Header[http.CanonicalHeaderKey("X-Neofs-Token-Signature")], true, route.Formats); err != nil {
res = append(res, err)
}
if err := o.bindXNeofsTokenSignatureKey(r.Header[http.CanonicalHeaderKey("X-Neofs-Token-Signature-Key")], true, route.Formats); err != nil {
res = append(res, err)
}
if runtime.HasBody(r) {
defer r.Body.Close()
var body PutObjectBody
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("object", "body", ""))
} else {
res = append(res, errors.NewParseError("object", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(context.Background())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Object = body
}
}
} else {
res = append(res, errors.Required("object", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindXNeofsTokenSignature binds and validates parameter XNeofsTokenSignature from header.
func (o *PutObjectParams) bindXNeofsTokenSignature(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("X-Neofs-Token-Signature", "header", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
if err := validate.RequiredString("X-Neofs-Token-Signature", "header", raw); err != nil {
return err
}
o.XNeofsTokenSignature = raw
return nil
}
// bindXNeofsTokenSignatureKey binds and validates parameter XNeofsTokenSignatureKey from header.
func (o *PutObjectParams) bindXNeofsTokenSignatureKey(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("X-Neofs-Token-Signature-Key", "header", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
if err := validate.RequiredString("X-Neofs-Token-Signature-Key", "header", raw); err != nil {
return err
}
o.XNeofsTokenSignatureKey = raw
return nil
}

View file

@ -0,0 +1,100 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
)
// PutObjectOKCode is the HTTP code returned for type PutObjectOK
const PutObjectOKCode int = 200
/*PutObjectOK Address of uploaded objects
swagger:response putObjectOK
*/
type PutObjectOK struct {
/*
In: Body
*/
Payload *PutObjectOKBody `json:"body,omitempty"`
}
// NewPutObjectOK creates PutObjectOK with default headers values
func NewPutObjectOK() *PutObjectOK {
return &PutObjectOK{}
}
// WithPayload adds the payload to the put object o k response
func (o *PutObjectOK) WithPayload(payload *PutObjectOKBody) *PutObjectOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the put object o k response
func (o *PutObjectOK) SetPayload(payload *PutObjectOKBody) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PutObjectOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// PutObjectBadRequestCode is the HTTP code returned for type PutObjectBadRequest
const PutObjectBadRequestCode int = 400
/*PutObjectBadRequest Bad request
swagger:response putObjectBadRequest
*/
type PutObjectBadRequest struct {
/*
In: Body
*/
Payload models.Error `json:"body,omitempty"`
}
// NewPutObjectBadRequest creates PutObjectBadRequest with default headers values
func NewPutObjectBadRequest() *PutObjectBadRequest {
return &PutObjectBadRequest{}
}
// WithPayload adds the payload to the put object bad request response
func (o *PutObjectBadRequest) WithPayload(payload models.Error) *PutObjectBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the put object bad request response
func (o *PutObjectBadRequest) SetPayload(payload models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PutObjectBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(400)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}

495
gen/restapi/server.go Normal file
View file

@ -0,0 +1,495 @@
// Code generated by go-swagger; DO NOT EDIT.
package restapi
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"os/signal"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/go-openapi/swag"
"golang.org/x/net/netutil"
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations"
)
const (
schemeHTTP = "http"
schemeHTTPS = "https"
)
var defaultSchemes []string
func init() {
defaultSchemes = []string{
schemeHTTP,
}
}
type ServerConfig struct {
EnabledListeners []string
CleanupTimeout time.Duration
GracefulTimeout time.Duration
MaxHeaderSize int
ListenAddress string
ListenLimit int
KeepAlive time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
TLSListenAddress string
TLSListenLimit int
TLSKeepAlive time.Duration
TLSReadTimeout time.Duration
TLSWriteTimeout time.Duration
TLSCertificate string
TLSCertificateKey string
TLSCACertificate string
}
// NewServer creates a new api neofs rest gw server but does not configure it
func NewServer(api *operations.NeofsRestGwAPI, cfg *ServerConfig) *Server {
s := new(Server)
s.EnabledListeners = cfg.EnabledListeners
s.CleanupTimeout = cfg.CleanupTimeout
s.GracefulTimeout = cfg.GracefulTimeout
s.MaxHeaderSize = cfg.MaxHeaderSize
s.ListenAddress = cfg.ListenAddress
s.ListenLimit = cfg.ListenLimit
s.KeepAlive = cfg.KeepAlive
s.ReadTimeout = cfg.ReadTimeout
s.WriteTimeout = cfg.WriteTimeout
s.TLSListenAddress = cfg.TLSListenAddress
if len(s.TLSListenAddress) == 0 {
s.TLSListenAddress = s.ListenAddress
}
s.TLSCertificate = cfg.TLSCertificate
s.TLSCertificateKey = cfg.TLSCertificateKey
s.TLSCACertificate = cfg.TLSCACertificate
s.TLSListenLimit = cfg.TLSListenLimit
s.TLSKeepAlive = cfg.TLSKeepAlive
s.TLSReadTimeout = cfg.TLSReadTimeout
s.TLSWriteTimeout = cfg.TLSWriteTimeout
s.shutdown = make(chan struct{})
s.api = api
s.interrupt = make(chan os.Signal, 1)
return s
}
// ConfigureAPI configures the API and handlers.
func (s *Server) ConfigureAPI(fn func(*operations.NeofsRestGwAPI) http.Handler) {
if s.api != nil {
s.handler = fn(s.api)
}
}
// Server for the neofs rest gw API
type Server struct {
EnabledListeners []string
CleanupTimeout time.Duration
GracefulTimeout time.Duration
MaxHeaderSize int
ListenAddress string
ListenLimit int
KeepAlive time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
httpServerL net.Listener
TLSListenAddress string
TLSCertificate string
TLSCertificateKey string
TLSCACertificate string
TLSListenLimit int
TLSKeepAlive time.Duration
TLSReadTimeout time.Duration
TLSWriteTimeout time.Duration
httpsServerL net.Listener
cfgTLSFn func(tlsConfig *tls.Config)
cfgServerFn func(s *http.Server, scheme, addr string)
api *operations.NeofsRestGwAPI
handler http.Handler
hasListeners bool
shutdown chan struct{}
shuttingDown int32
interrupted bool
interrupt chan os.Signal
}
// Logf logs message either via defined user logger or via system one if no user logger is defined.
func (s *Server) Logf(f string, args ...interface{}) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
} else {
log.Printf(f, args...)
}
}
// Fatalf logs message either via defined user logger or via system one if no user logger is defined.
// Exits with non-zero status after printing
func (s *Server) Fatalf(f string, args ...interface{}) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
os.Exit(1)
} else {
log.Fatalf(f, args...)
}
}
func (s *Server) hasScheme(scheme string) bool {
schemes := s.EnabledListeners
if len(schemes) == 0 {
schemes = defaultSchemes
}
for _, v := range schemes {
if v == scheme {
return true
}
}
return false
}
// Serve the api
func (s *Server) Serve() (err error) {
if !s.hasListeners {
if err = s.Listen(); err != nil {
return err
}
}
// set default handler, if none is set
if s.handler == nil {
if s.api == nil {
return errors.New("can't create the default handler, as no api is set")
}
s.SetHandler(s.api.Serve(nil))
}
wg := new(sync.WaitGroup)
once := new(sync.Once)
signalNotify(s.interrupt)
go handleInterrupt(once, s)
servers := []*http.Server{}
if s.hasScheme(schemeHTTP) {
httpServer := new(http.Server)
httpServer.MaxHeaderBytes = s.MaxHeaderSize
httpServer.ReadTimeout = s.ReadTimeout
httpServer.WriteTimeout = s.WriteTimeout
httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0)
if s.ListenLimit > 0 {
s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit)
}
if int64(s.CleanupTimeout) > 0 {
httpServer.IdleTimeout = s.CleanupTimeout
}
httpServer.Handler = s.handler
if s.cfgServerFn != nil {
s.cfgServerFn(httpServer, "http", s.httpServerL.Addr().String())
}
servers = append(servers, httpServer)
wg.Add(1)
s.Logf("Serving neofs rest gw at http://%s", s.httpServerL.Addr())
go func(l net.Listener) {
defer wg.Done()
if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
}
s.Logf("Stopped serving neofs rest gw at http://%s", l.Addr())
}(s.httpServerL)
}
if s.hasScheme(schemeHTTPS) {
httpsServer := new(http.Server)
httpsServer.MaxHeaderBytes = s.MaxHeaderSize
httpsServer.ReadTimeout = s.TLSReadTimeout
httpsServer.WriteTimeout = s.TLSWriteTimeout
httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0)
if s.TLSListenLimit > 0 {
s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit)
}
if int64(s.CleanupTimeout) > 0 {
httpsServer.IdleTimeout = s.CleanupTimeout
}
httpsServer.Handler = s.handler
// Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
httpsServer.TLSConfig = &tls.Config{
// Causes servers to use Go's default ciphersuite preferences,
// which are tuned to avoid attacks. Does nothing on clients.
PreferServerCipherSuites: true,
// Only use curves which have assembly implementations
// https://github.com/golang/go/tree/master/src/crypto/elliptic
CurvePreferences: []tls.CurveID{tls.CurveP256},
// Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
NextProtos: []string{"h2", "http/1.1"},
// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
MinVersion: tls.VersionTLS12,
// These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
}
// build standard config from server options
if s.TLSCertificate != "" && s.TLSCertificateKey != "" {
httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(s.TLSCertificate, s.TLSCertificateKey)
if err != nil {
return err
}
}
if s.TLSCACertificate != "" {
// include specified CA certificate
caCert, caCertErr := ioutil.ReadFile(s.TLSCACertificate)
if caCertErr != nil {
return caCertErr
}
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(caCert)
if !ok {
return fmt.Errorf("cannot parse CA certificate")
}
httpsServer.TLSConfig.ClientCAs = caCertPool
httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
// call custom TLS configurator
if s.cfgTLSFn != nil {
s.cfgTLSFn(httpsServer.TLSConfig)
}
if len(httpsServer.TLSConfig.Certificates) == 0 && httpsServer.TLSConfig.GetCertificate == nil {
// after standard and custom config are passed, this ends up with no certificate
if s.TLSCertificate == "" {
if s.TLSCertificateKey == "" {
s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
}
s.Fatalf("the required flag `--tls-certificate` was not specified")
}
if s.TLSCertificateKey == "" {
s.Fatalf("the required flag `--tls-key` was not specified")
}
// this happens with a wrong custom TLS configurator
s.Fatalf("no certificate was configured for TLS")
}
if s.cfgServerFn != nil {
s.cfgServerFn(httpsServer, "https", s.httpsServerL.Addr().String())
}
servers = append(servers, httpsServer)
wg.Add(1)
s.Logf("Serving neofs rest gw at https://%s", s.httpsServerL.Addr())
go func(l net.Listener) {
defer wg.Done()
if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
}
s.Logf("Stopped serving neofs rest gw at https://%s", l.Addr())
}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))
}
wg.Add(1)
go s.handleShutdown(wg, &servers)
wg.Wait()
return nil
}
// The TLS configuration before HTTPS server starts.
func (s *Server) ConfigureTLS(cfgTLS func(tlsConfig *tls.Config)) {
s.cfgTLSFn = cfgTLS
}
// As soon as server is initialized but not run yet, this function will be called.
// If you need to modify a config, store server instance to stop it individually later, this is the place.
// This function can be called multiple times, depending on the number of serving schemes.
// scheme value will be set accordingly: "http", "https" or "unix".
func (s *Server) ConfigureServer(cfgServer func(s *http.Server, scheme, addr string)) {
s.cfgServerFn = cfgServer
}
// Listen creates the listeners for the server
func (s *Server) Listen() error {
if s.hasListeners { // already done this
return nil
}
if s.hasScheme(schemeHTTPS) {
// Use http listen limit if https listen limit wasn't defined
if s.TLSListenLimit == 0 {
s.TLSListenLimit = s.ListenLimit
}
// Use http tcp keep alive if https tcp keep alive wasn't defined
if int64(s.TLSKeepAlive) == 0 {
s.TLSKeepAlive = s.KeepAlive
}
// Use http read timeout if https read timeout wasn't defined
if int64(s.TLSReadTimeout) == 0 {
s.TLSReadTimeout = s.ReadTimeout
}
// Use http write timeout if https write timeout wasn't defined
if int64(s.TLSWriteTimeout) == 0 {
s.TLSWriteTimeout = s.WriteTimeout
}
}
if s.hasScheme(schemeHTTP) {
listener, err := net.Listen("tcp", s.ListenAddress)
if err != nil {
return err
}
_, _, err = swag.SplitHostPort(listener.Addr().String())
if err != nil {
return err
}
s.httpServerL = listener
}
if s.hasScheme(schemeHTTPS) {
tlsListener, err := net.Listen("tcp", s.TLSListenAddress)
if err != nil {
return err
}
_, _, err = swag.SplitHostPort(tlsListener.Addr().String())
if err != nil {
return err
}
s.httpsServerL = tlsListener
}
s.hasListeners = true
return nil
}
// Shutdown server and clean up resources
func (s *Server) Shutdown() error {
if atomic.CompareAndSwapInt32(&s.shuttingDown, 0, 1) {
close(s.shutdown)
}
return nil
}
func (s *Server) handleShutdown(wg *sync.WaitGroup, serversPtr *[]*http.Server) {
// wg.Done must occur last, after s.api.ServerShutdown()
// (to preserve old behaviour)
defer wg.Done()
<-s.shutdown
servers := *serversPtr
ctx, cancel := context.WithTimeout(context.TODO(), s.GracefulTimeout)
defer cancel()
// first execute the pre-shutdown hook
s.api.PreServerShutdown()
shutdownChan := make(chan bool)
for i := range servers {
server := servers[i]
go func() {
var success bool
defer func() {
shutdownChan <- success
}()
if err := server.Shutdown(ctx); err != nil {
// Error from closing listeners, or context timeout:
s.Logf("HTTP server Shutdown: %v", err)
} else {
success = true
}
}()
}
// Wait until all listeners have successfully shut down before calling ServerShutdown
success := true
for range servers {
success = success && <-shutdownChan
}
if success {
s.api.ServerShutdown()
}
}
// GetHandler returns a handler useful for testing
func (s *Server) GetHandler() http.Handler {
return s.handler
}
// SetHandler allows for setting a http handler on this server
func (s *Server) SetHandler(handler http.Handler) {
s.handler = handler
}
// HTTPListener returns the http listener
func (s *Server) HTTPListener() (net.Listener, error) {
if !s.hasListeners {
if err := s.Listen(); err != nil {
return nil, err
}
}
return s.httpServerL, nil
}
// TLSListener returns the https listener
func (s *Server) TLSListener() (net.Listener, error) {
if !s.hasListeners {
if err := s.Listen(); err != nil {
return nil, err
}
}
return s.httpsServerL, nil
}
func handleInterrupt(once *sync.Once, s *Server) {
once.Do(func() {
for range s.interrupt {
if s.interrupted {
s.Logf("Server already shutting down")
continue
}
s.interrupted = true
s.Logf("Shutting down... ")
if err := s.Shutdown(); err != nil {
s.Logf("HTTP server Shutdown: %v", err)
}
}
})
}
func signalNotify(interrupt chan<- os.Signal) {
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
}

View file

@ -0,0 +1,53 @@
// Code generated by go-swagger; DO NOT EDIT.
package restapi
import (
"time"
"github.com/spf13/pflag"
)
const (
FlagScheme = "scheme"
FlagCleanupTimeout = "cleanup-timeout"
FlagGracefulTimeout = "graceful-timeout"
FlagMaxHeaderSize = "max-header-size"
FlagListenAddress = "listen-address"
FlagListenLimit = "listen-limit"
FlagKeepAlive = "keep-alive"
FlagReadTimeout = "read-timeout"
FlagWriteTimeout = "write-timeout"
FlagTLSListenAddress = "tls-listen-address"
FlagTLSCertificate = "tls-certificate"
FlagTLSKey = "tls-key"
FlagTLSCa = "tls-ca"
FlagTLSListenLimit = "tls-listen-limit"
FlagTLSKeepAlive = "tls-keep-alive"
FlagTLSReadTimeout = "tls-read-timeout"
FlagTLSWriteTimeout = "tls-write-timeout"
)
// BindDefaultFlag init default flag.
func BindDefaultFlags(flagSet *pflag.FlagSet) {
flagSet.StringSlice(FlagScheme, defaultSchemes, "the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec")
flagSet.Duration(FlagCleanupTimeout, 10*time.Second, "grace period for which to wait before killing idle connections")
flagSet.Duration(FlagGracefulTimeout, 15*time.Second, "grace period for which to wait before shutting down the server")
flagSet.Int(FlagMaxHeaderSize, 1000000, "controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body")
flagSet.String(FlagListenAddress, "localhost:8080", "the IP and port to listen on")
flagSet.Int(FlagListenLimit, 0, "limit the number of outstanding requests")
flagSet.Duration(FlagKeepAlive, 3*time.Minute, "sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)")
flagSet.Duration(FlagReadTimeout, 30*time.Second, "maximum duration before timing out read of the request")
flagSet.Duration(FlagWriteTimeout, 30*time.Second, "maximum duration before timing out write of the response")
flagSet.String(FlagTLSListenAddress, "localhost:8081", "the IP and port to listen on")
flagSet.String(FlagTLSCertificate, "", "the certificate file to use for secure connections")
flagSet.String(FlagTLSKey, "", "the private key file to use for secure connections (without passphrase)")
flagSet.String(FlagTLSCa, "", "the certificate authority certificate file to be used with mutual tls auth")
flagSet.Int(FlagTLSListenLimit, 0, "limit the number of outstanding requests")
flagSet.Duration(FlagTLSKeepAlive, 3*time.Minute, "sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)")
flagSet.Duration(FlagTLSReadTimeout, 30*time.Second, "maximum duration before timing out read of the request")
flagSet.Duration(FlagTLSWriteTimeout, 30*time.Second, "maximum duration before timing out write of the response")
}

107
go.mod Normal file
View file

@ -0,0 +1,107 @@
module github.com/nspcc-dev/neofs-rest-gw
go 1.17
require (
github.com/go-openapi/errors v0.20.2
github.com/go-openapi/loads v0.21.1
github.com/go-openapi/runtime v0.23.3
github.com/go-openapi/spec v0.20.4
github.com/go-openapi/strfmt v0.21.2
github.com/go-openapi/swag v0.21.1
github.com/go-openapi/validate v0.21.0
github.com/google/uuid v1.3.0
github.com/nspcc-dev/neo-go v0.98.2
github.com/nspcc-dev/neofs-api-go/v2 v2.12.1
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220407103316-e50e6d28280d
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/testcontainers/testcontainers-go v0.13.0
go.uber.org/zap v1.18.1
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.4.17 // indirect
github.com/Microsoft/hcsshim v0.8.23 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521073959-f0d4d129b7f1 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.22.0-beta // indirect
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/containerd/cgroups v1.0.1 // indirect
github.com/containerd/containerd v1.5.9 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.11+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-openapi/analysis v0.21.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/moby/sys/mount v0.2.0 // indirect
github.com/moby/sys/mountinfo v0.5.0 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/nspcc-dev/hrw v1.0.9 // indirect
github.com/nspcc-dev/neofs-crypto v0.3.0 // indirect
github.com/nspcc-dev/rfc6979 v0.2.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.0.2 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.10.1
github.com/subosito/gotenv v1.2.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 // indirect
github.com/urfave/cli v1.22.5 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.mongodb.org/mongo-driver v1.8.4 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
google.golang.org/grpc v1.43.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

1628
go.sum Normal file

File diff suppressed because it is too large Load diff

87
handlers/api.go Normal file
View file

@ -0,0 +1,87 @@
package handlers
import (
"fmt"
"net/http"
"strings"
"github.com/go-openapi/errors"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations"
"github.com/nspcc-dev/neofs-sdk-go/pool"
"go.uber.org/zap"
)
// API is a REST v1 request handler.
type API struct {
log *zap.Logger
pool *pool.Pool
key *keys.PrivateKey
defaultTimestamp bool
}
// PrmAPI groups parameters to init rest API.
type PrmAPI struct {
Logger *zap.Logger
Pool *pool.Pool
Key *keys.PrivateKey
DefaultTimestamp bool
}
type BearerToken struct {
Token string
Signature string
Key string
}
// New creates a new API using specified logger, connection pool and other parameters.
func New(prm *PrmAPI) *API {
return &API{
log: prm.Logger,
pool: prm.Pool,
key: prm.Key,
defaultTimestamp: prm.DefaultTimestamp,
}
}
const (
bearerPrefix = "Bearer "
)
func (a *API) Configure(api *operations.NeofsRestGwAPI) http.Handler {
api.ServeError = errors.ServeError
api.AuthHandler = operations.AuthHandlerFunc(a.PostAuth)
api.PutObjectHandler = operations.PutObjectHandlerFunc(a.PutObjects)
api.PutContainerHandler = operations.PutContainerHandlerFunc(a.PutContainers)
api.GetContainerHandler = operations.GetContainerHandlerFunc(a.GetContainer)
api.BearerAuthAuth = func(s string) (*models.Principal, error) {
if !strings.HasPrefix(s, bearerPrefix) {
return nil, fmt.Errorf("has not bearer token")
}
if s = strings.TrimPrefix(s, bearerPrefix); len(s) == 0 {
return nil, fmt.Errorf("bearer token is empty")
}
return (*models.Principal)(&s), nil
}
api.PreServerShutdown = func() {}
api.ServerShutdown = func() {}
return setupGlobalMiddleware(api.Serve(setupMiddlewares))
}
// The middleware configuration is for the handler executors. These do not apply to the swagger.json document.
// The middleware executes after routing but before authentication, binding and validation.
func setupMiddlewares(handler http.Handler) http.Handler {
return handler
}
// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
// So this is a good place to plug in a panic handling middleware, logging and metrics.
func setupGlobalMiddleware(handler http.Handler) http.Handler {
return handler
}

130
handlers/auth.go Normal file
View file

@ -0,0 +1,130 @@
package handlers
import (
"context"
"crypto/ecdsa"
"encoding/base64"
"fmt"
"github.com/go-openapi/runtime/middleware"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations"
"github.com/nspcc-dev/neofs-sdk-go/owner"
"github.com/nspcc-dev/neofs-sdk-go/pool"
)
const defaultTokenExpDuration = 100 // in epoch
// PostAuth handler that forms bearer token to sign.
func (a *API) PostAuth(params operations.AuthParams) middleware.Responder {
var (
err error
resp *models.TokenResponse
)
if params.XNeofsTokenScope == "object" {
resp, err = prepareObjectToken(params, a.pool)
} else {
resp, err = prepareContainerTokens(params, a.pool, a.key.PublicKey())
}
if err != nil {
return operations.NewAuthBadRequest().WithPayload(models.Error(err.Error()))
}
return operations.NewAuthOK().WithPayload(resp)
}
func prepareObjectToken(params operations.AuthParams, pool *pool.Pool) (*models.TokenResponse, error) {
ctx := params.HTTPRequest.Context()
btoken, err := ToNativeObjectToken(params.Token)
if err != nil {
return nil, fmt.Errorf("couldn't transform token to native: %w", err)
}
btoken.SetOwner(pool.OwnerID())
iat, exp, err := getTokenLifetime(ctx, pool, params.XNeofsTokenLifetime)
if err != nil {
return nil, fmt.Errorf("couldn't get lifetime: %w", err)
}
btoken.SetLifetime(exp, 0, iat)
binaryBearer, err := btoken.ToV2().GetBody().StableMarshal(nil)
if err != nil {
return nil, fmt.Errorf("couldn't marshal bearer token: %w", err)
}
var resp models.TokenResponse
resp.Type = models.NewTokenType(models.TokenTypeObject)
resp.Token = NewString(base64.StdEncoding.EncodeToString(binaryBearer))
return &resp, nil
}
func prepareContainerTokens(params operations.AuthParams, pool *pool.Pool, key *keys.PublicKey) (*models.TokenResponse, error) {
ctx := params.HTTPRequest.Context()
iat, exp, err := getTokenLifetime(ctx, pool, params.XNeofsTokenLifetime)
if err != nil {
return nil, fmt.Errorf("couldn't get lifetime: %w", err)
}
ownerKey, err := keys.NewPublicKeyFromString(params.XNeofsTokenSignatureKey)
if err != nil {
return nil, fmt.Errorf("invalid singature key: %w", err)
}
var resp models.TokenResponse
resp.Type = models.NewTokenType(models.TokenTypeContainer)
stoken, err := ToNativeContainerToken(params.Token)
if err != nil {
return nil, fmt.Errorf("couldn't transform rule to native session token: %w", err)
}
uid, err := uuid.New().MarshalBinary()
if err != nil {
return nil, err
}
stoken.SetID(uid)
stoken.SetOwnerID(owner.NewIDFromPublicKey((*ecdsa.PublicKey)(ownerKey)))
stoken.SetIat(iat)
stoken.SetExp(exp)
stoken.SetSessionKey(key.Bytes())
binaryToken, err := stoken.ToV2().GetBody().StableMarshal(nil)
if err != nil {
return nil, fmt.Errorf("couldn't marshal session token: %w", err)
}
resp.Token = NewString(base64.StdEncoding.EncodeToString(binaryToken))
return &resp, nil
}
func getCurrentEpoch(ctx context.Context, p *pool.Pool) (uint64, error) {
netInfo, err := p.NetworkInfo(ctx)
if err != nil {
return 0, fmt.Errorf("couldn't get netwokr info: %w", err)
}
return netInfo.CurrentEpoch(), nil
}
func getTokenLifetime(ctx context.Context, p *pool.Pool, expDuration *int64) (uint64, uint64, error) {
currEpoch, err := getCurrentEpoch(ctx, p)
if err != nil {
return 0, 0, err
}
var lifetimeDuration uint64 = defaultTokenExpDuration
if expDuration != nil && *expDuration > 0 {
lifetimeDuration = uint64(*expDuration)
}
return currEpoch, currEpoch + lifetimeDuration, nil
}

66
handlers/auth_test.go Normal file
View file

@ -0,0 +1,66 @@
package handlers
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha512"
"encoding/base64"
"encoding/hex"
"testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
"github.com/nspcc-dev/neofs-sdk-go/owner"
"github.com/stretchr/testify/require"
)
const devenvPrivateKey = "1dd37fba80fec4e6a6f13fd708d8dcb3b29def768017052f6c930fa1c5d90bbb"
func TestSign(t *testing.T) {
key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey)
require.NoError(t, err)
pubKeyHex := hex.EncodeToString(key.PublicKey().Bytes())
b := &models.Bearer{
Object: []*models.Record{{
Operation: models.NewOperation(models.OperationPUT),
Action: models.NewAction(models.ActionALLOW),
Filters: []*models.Filter{},
Targets: []*models.Target{{
Role: models.NewRole(models.RoleOTHERS),
Keys: []string{},
}},
}},
}
btoken, err := ToNativeObjectToken(b)
require.NoError(t, err)
ownerKey, err := keys.NewPublicKeyFromString(pubKeyHex)
require.NoError(t, err)
btoken.SetOwner(owner.NewIDFromPublicKey((*ecdsa.PublicKey)(ownerKey)))
binaryBearer, err := btoken.ToV2().GetBody().StableMarshal(nil)
require.NoError(t, err)
bearerBase64 := base64.StdEncoding.EncodeToString(binaryBearer)
h := sha512.Sum512(binaryBearer)
x, y, err := ecdsa.Sign(rand.Reader, &key.PrivateKey, h[:])
if err != nil {
panic(err)
}
signatureData := elliptic.Marshal(elliptic.P256(), x, y)
bt := &BearerToken{
Token: bearerBase64,
Signature: base64.StdEncoding.EncodeToString(signatureData),
Key: pubKeyHex,
}
_, err = prepareBearerToken(bt)
require.NoError(t, err)
}

192
handlers/containers.go Normal file
View file

@ -0,0 +1,192 @@
package handlers
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/go-openapi/runtime/middleware"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations"
"github.com/nspcc-dev/neofs-sdk-go/acl"
"github.com/nspcc-dev/neofs-sdk-go/container"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/policy"
"github.com/nspcc-dev/neofs-sdk-go/pool"
"github.com/nspcc-dev/neofs-sdk-go/session"
)
const (
defaultPlacementPolicy = "REP 3"
defaultBasicACL = acl.PrivateBasicName
)
// PutContainers handler that creates container in NeoFS.
func (a *API) PutContainers(params operations.PutContainerParams, principal *models.Principal) middleware.Responder {
bt := &BearerToken{
Token: string(*principal),
Signature: params.XNeofsTokenSignature,
Key: params.XNeofsTokenSignatureKey,
}
stoken, err := prepareSessionToken(bt)
if err != nil {
return wrapError(err)
}
userAttributes := prepareUserAttributes(params.HTTPRequest.Header)
cnrID, err := createContainer(params.HTTPRequest.Context(), a.pool, stoken, &params.Container, userAttributes)
if err != nil {
return wrapError(err)
}
var resp operations.PutContainerOKBody
resp.ContainerID = NewString(cnrID.String())
return operations.NewPutContainerOK().WithPayload(&resp)
}
// GetContainer handler that returns container info.
func (a *API) GetContainer(params operations.GetContainerParams) middleware.Responder {
cnr, err := getContainer(params.HTTPRequest.Context(), a.pool, params.ContainerID)
if err != nil {
return wrapError(err)
}
attrs := make([]*models.Attribute, len(cnr.Attributes()))
for i, attr := range cnr.Attributes() {
attrs[i] = &models.Attribute{Key: attr.Key(), Value: attr.Value()}
}
resp := &models.ContainerInfo{
ContainerID: params.ContainerID,
Version: cnr.Version().String(),
OwnerID: cnr.OwnerID().String(),
BasicACL: acl.BasicACL(cnr.BasicACL()).String(),
PlacementPolicy: strings.Join(policy.Encode(cnr.PlacementPolicy()), " "),
Attributes: attrs,
}
return operations.NewGetContainerOK().WithPayload(resp)
}
func prepareUserAttributes(header http.Header) map[string]string {
filtered := filterHeaders(header)
delete(filtered, container.AttributeName)
delete(filtered, container.AttributeTimestamp)
return filtered
}
func getContainer(ctx context.Context, p *pool.Pool, containerID string) (*container.Container, error) {
var cnrID cid.ID
if err := cnrID.Parse(containerID); err != nil {
return nil, fmt.Errorf("parse container id '%s': %w", containerID, err)
}
var prm pool.PrmContainerGet
prm.SetContainerID(cnrID)
return p.GetContainer(ctx, prm)
}
func createContainer(ctx context.Context, p *pool.Pool, stoken *session.Token, request *operations.PutContainerBody, userAttrs map[string]string) (*cid.ID, error) {
if request.PlacementPolicy == "" {
request.PlacementPolicy = defaultPlacementPolicy
}
pp, err := policy.Parse(request.PlacementPolicy)
if err != nil {
return nil, fmt.Errorf("couldn't parse placement policy: %w", err)
}
if request.BasicACL == "" {
request.BasicACL = defaultBasicACL
}
basicACL, err := acl.ParseBasicACL(request.BasicACL)
if err != nil {
return nil, fmt.Errorf("couldn't parse basic acl: %w", err)
}
cnrOptions := []container.Option{
container.WithPolicy(pp),
container.WithCustomBasicACL(basicACL),
container.WithAttribute(container.AttributeName, *request.ContainerName),
container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(time.Now().Unix(), 10)),
}
for key, val := range userAttrs {
cnrOptions = append(cnrOptions, container.WithAttribute(key, val))
}
cnr := container.New(cnrOptions...)
cnr.SetOwnerID(stoken.OwnerID())
cnr.SetSessionToken(stoken)
container.SetNativeName(cnr, *request.ContainerName)
var prm pool.PrmContainerPut
prm.SetContainer(*cnr)
cnrID, err := p.PutContainer(ctx, prm)
if err != nil {
return nil, fmt.Errorf("could put object to neofs: %w", err)
}
return cnrID, nil
}
func prepareSessionToken(bt *BearerToken) (*session.Token, error) {
stoken, err := GetSessionToken(bt.Token)
if err != nil {
return nil, fmt.Errorf("could not fetch session token: %w", err)
}
signature, err := base64.StdEncoding.DecodeString(bt.Signature)
if err != nil {
return nil, fmt.Errorf("couldn't decode bearer signature: %w", err)
}
ownerKey, err := keys.NewPublicKeyFromString(bt.Key)
if err != nil {
return nil, fmt.Errorf("couldn't fetch bearer token owner key: %w", err)
}
v2signature := new(refs.Signature)
v2signature.SetScheme(refs.ECDSA_SHA512)
v2signature.SetSign(signature)
v2signature.SetKey(ownerKey.Bytes())
stoken.ToV2().SetSignature(v2signature)
if !stoken.VerifySignature() {
err = fmt.Errorf("invalid signature")
}
return stoken, err
}
func GetSessionToken(auth string) (*session.Token, error) {
data, err := base64.StdEncoding.DecodeString(auth)
if err != nil {
return nil, fmt.Errorf("can't base64-decode bearer token: %w", err)
}
body := new(sessionv2.TokenBody)
if err = body.Unmarshal(data); err != nil {
return nil, fmt.Errorf("can't unmarshal bearer token: %w", err)
}
tkn := new(session.Token)
tkn.ToV2().SetBody(body)
return tkn, nil
}
func wrapError(err error) middleware.Responder {
return operations.NewPutContainerBadRequest().WithPayload(models.Error(err.Error()))
}

115
handlers/objects.go Normal file
View file

@ -0,0 +1,115 @@
package handlers
import (
"encoding/base64"
"fmt"
"github.com/go-openapi/runtime/middleware"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-api-go/v2/acl"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/nspcc-dev/neofs-sdk-go/pool"
"github.com/nspcc-dev/neofs-sdk-go/token"
)
// PutObjects handler that uploads object to NeoFS.
func (a *API) PutObjects(params operations.PutObjectParams, principal *models.Principal) middleware.Responder {
ctx := params.HTTPRequest.Context()
bt := &BearerToken{
Token: string(*principal),
Signature: params.XNeofsTokenSignature,
Key: params.XNeofsTokenSignatureKey,
}
btoken, err := prepareBearerToken(bt)
if err != nil {
return operations.NewPutObjectBadRequest().WithPayload(models.Error(err.Error()))
}
var cnrID cid.ID
if err = cnrID.Parse(*params.Object.ContainerID); err != nil {
return operations.NewPutObjectBadRequest().WithPayload(models.Error(err.Error()))
}
payload, err := base64.StdEncoding.DecodeString(params.Object.Payload)
if err != nil {
return operations.NewPutObjectBadRequest().WithPayload(models.Error(err.Error()))
}
prm := PrmAttributes{
DefaultTimestamp: a.defaultTimestamp,
DefaultFileName: *params.Object.FileName,
}
attributes, err := GetObjectAttributes(ctx, params.HTTPRequest.Header, a.pool, prm)
if err != nil {
return operations.NewPutObjectBadRequest().WithPayload(models.Error(err.Error()))
}
obj := object.New()
obj.SetContainerID(&cnrID)
obj.SetOwnerID(btoken.OwnerID())
obj.SetPayload(payload)
obj.SetAttributes(attributes...)
var prmPut pool.PrmObjectPut
prmPut.SetHeader(*obj)
prmPut.UseBearer(btoken)
objID, err := a.pool.PutObject(ctx, prmPut)
if err != nil {
return operations.NewPutObjectBadRequest().WithPayload(models.Error(err.Error()))
}
var resp operations.PutObjectOKBody
resp.ContainerID = params.Object.ContainerID
resp.ObjectID = NewString(objID.String())
return operations.NewPutObjectOK().WithPayload(&resp)
}
func prepareBearerToken(bt *BearerToken) (*token.BearerToken, error) {
btoken, err := getBearerToken(bt.Token)
if err != nil {
return nil, fmt.Errorf("could not fetch bearer token: %w", err)
}
signature, err := base64.StdEncoding.DecodeString(bt.Signature)
if err != nil {
return nil, fmt.Errorf("couldn't decode bearer signature: %w", err)
}
ownerKey, err := keys.NewPublicKeyFromString(bt.Key)
if err != nil {
return nil, fmt.Errorf("couldn't fetch bearer token owner key: %w", err)
}
v2signature := new(refs.Signature)
v2signature.SetScheme(refs.ECDSA_SHA512)
v2signature.SetSign(signature)
v2signature.SetKey(ownerKey.Bytes())
btoken.ToV2().SetSignature(v2signature)
return btoken, btoken.VerifySignature()
}
func getBearerToken(auth string) (*token.BearerToken, error) {
data, err := base64.StdEncoding.DecodeString(auth)
if err != nil {
return nil, fmt.Errorf("can't base64-decode bearer token: %w", err)
}
body := new(acl.BearerTokenBody)
if err = body.Unmarshal(data); err != nil {
return nil, fmt.Errorf("can't unmarshal bearer token: %w", err)
}
tkn := new(token.BearerToken)
tkn.ToV2().SetBody(body)
return tkn, nil
}

245
handlers/transformers.go Normal file
View file

@ -0,0 +1,245 @@
package handlers
import (
"encoding/hex"
"fmt"
sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
"github.com/nspcc-dev/neofs-sdk-go/session"
"github.com/nspcc-dev/neofs-sdk-go/token"
)
// ToNativeAction converts models.Action to appropriate eacl.Action.
func ToNativeAction(a *models.Action) (eacl.Action, error) {
if a == nil {
return eacl.ActionUnknown, fmt.Errorf("unsupported empty action")
}
switch *a {
case models.ActionALLOW:
return eacl.ActionAllow, nil
case models.ActionDENY:
return eacl.ActionDeny, nil
default:
return eacl.ActionUnknown, fmt.Errorf("unsupported action type: '%s'", *a)
}
}
// ToNativeOperation converts models.Operation to appropriate eacl.Operation.
func ToNativeOperation(o *models.Operation) (eacl.Operation, error) {
if o == nil {
return eacl.OperationUnknown, fmt.Errorf("unsupported empty opertaion")
}
switch *o {
case models.OperationGET:
return eacl.OperationGet, nil
case models.OperationHEAD:
return eacl.OperationHead, nil
case models.OperationPUT:
return eacl.OperationPut, nil
case models.OperationDELETE:
return eacl.OperationDelete, nil
case models.OperationSEARCH:
return eacl.OperationSearch, nil
case models.OperationRANGE:
return eacl.OperationRange, nil
case models.OperationRANGEHASH:
return eacl.OperationRangeHash, nil
default:
return eacl.OperationUnknown, fmt.Errorf("unsupported operation type: '%s'", *o)
}
}
// ToNativeHeaderType converts models.HeaderType to appropriate eacl.FilterHeaderType.
func ToNativeHeaderType(h *models.HeaderType) (eacl.FilterHeaderType, error) {
if h == nil {
return eacl.HeaderTypeUnknown, fmt.Errorf("unsupported empty header type")
}
switch *h {
case models.HeaderTypeOBJECT:
return eacl.HeaderFromObject, nil
case models.HeaderTypeREQUEST:
return eacl.HeaderFromRequest, nil
case models.HeaderTypeSERVICE:
return eacl.HeaderFromService, nil
default:
return eacl.HeaderTypeUnknown, fmt.Errorf("unsupported header type: '%s'", *h)
}
}
// ToNativeMatchType converts models.MatchType to appropriate eacl.Match.
func ToNativeMatchType(t *models.MatchType) (eacl.Match, error) {
if t == nil {
return eacl.MatchUnknown, fmt.Errorf("unsupported empty match type")
}
switch *t {
case models.MatchTypeSTRINGEQUAL:
return eacl.MatchStringEqual, nil
case models.MatchTypeSTRINGNOTEQUAL:
return eacl.MatchStringNotEqual, nil
default:
return eacl.MatchUnknown, fmt.Errorf("unsupported match type: '%s'", *t)
}
}
// ToNativeRole converts models.Role to appropriate eacl.Role.
func ToNativeRole(r *models.Role) (eacl.Role, error) {
if r == nil {
return eacl.RoleUnknown, fmt.Errorf("unsupported empty role")
}
switch *r {
case models.RoleUSER:
return eacl.RoleUser, nil
case models.RoleSYSTEM:
return eacl.RoleSystem, nil
case models.RoleOTHERS:
return eacl.RoleOthers, nil
default:
return eacl.RoleUnknown, fmt.Errorf("unsupported role type: '%s'", *r)
}
}
// ToNativeVerb converts models.Verb to appropriate session.ContainerSessionVerb.
func ToNativeVerb(r *models.Verb) (sessionv2.ContainerSessionVerb, error) {
if r == nil {
return sessionv2.ContainerVerbUnknown, fmt.Errorf("unsupported empty verb type")
}
switch *r {
case models.VerbPUT:
return sessionv2.ContainerVerbPut, nil
case models.VerbDELETE:
return sessionv2.ContainerVerbDelete, nil
case models.VerbSETEACL:
return sessionv2.ContainerVerbSetEACL, nil
default:
return sessionv2.ContainerVerbUnknown, fmt.Errorf("unsupported verb type: '%s'", *r)
}
}
// ToNativeRule converts models.Rule to appropriate session.ContainerContext.
func ToNativeRule(r *models.Rule) (*session.ContainerContext, error) {
var ctx session.ContainerContext
verb, err := ToNativeVerb(r.Verb)
if err != nil {
return nil, err
}
ctx.ToV2().SetVerb(verb)
if r.ContainerID == "" {
ctx.ApplyTo(nil)
} else {
var cnrID cid.ID
if err = cnrID.Parse(r.ContainerID); err != nil {
return nil, fmt.Errorf("couldn't parse container id: %w", err)
}
ctx.ApplyTo(&cnrID)
}
return &ctx, nil
}
// ToNativeContainerToken converts models.Bearer to appropriate session.Token.
func ToNativeContainerToken(b *models.Bearer) (*session.Token, error) {
sctx, err := ToNativeRule(b.Container)
if err != nil {
return nil, fmt.Errorf("couldn't transform rule to native: %w", err)
}
tok := session.NewToken()
tok.SetContext(sctx)
return tok, nil
}
// ToNativeRecord converts models.Record to appropriate eacl.Record.
func ToNativeRecord(r *models.Record) (*eacl.Record, error) {
var record eacl.Record
action, err := ToNativeAction(r.Action)
if err != nil {
return nil, err
}
record.SetAction(action)
operation, err := ToNativeOperation(r.Operation)
if err != nil {
return nil, err
}
record.SetOperation(operation)
for _, filter := range r.Filters {
headerType, err := ToNativeHeaderType(filter.HeaderType)
if err != nil {
return nil, err
}
matchType, err := ToNativeMatchType(filter.MatchType)
if err != nil {
return nil, err
}
if filter.Key == nil || filter.Value == nil {
return nil, fmt.Errorf("invalid filter")
}
record.AddFilter(headerType, matchType, *filter.Key, *filter.Value)
}
targets := make([]eacl.Target, len(r.Targets))
for i, target := range r.Targets {
trgt, err := ToNativeTarget(target)
if err != nil {
return nil, err
}
targets[i] = *trgt
}
record.SetTargets(targets...)
return &record, nil
}
// ToNativeTarget converts models.Target to appropriate eacl.Target.
func ToNativeTarget(t *models.Target) (*eacl.Target, error) {
var target eacl.Target
role, err := ToNativeRole(t.Role)
if err != nil {
return nil, err
}
target.SetRole(role)
keys := make([][]byte, len(t.Keys))
for i, key := range t.Keys {
binaryKey, err := hex.DecodeString(key)
if err != nil {
return nil, fmt.Errorf("couldn't decode target key: %w", err)
}
keys[i] = binaryKey
}
target.SetBinaryKeys(keys)
return &target, nil
}
// ToNativeObjectToken converts Bearer to appropriate token.BearerToken.
func ToNativeObjectToken(b *models.Bearer) (*token.BearerToken, error) {
var btoken token.BearerToken
var table eacl.Table
for _, rec := range b.Object {
record, err := ToNativeRecord(rec)
if err != nil {
return nil, fmt.Errorf("couldn't transform record to native: %w", err)
}
table.AddRecord(record)
}
btoken.SetEACLTable(&table)
return &btoken, nil
}

218
handlers/util.go Normal file
View file

@ -0,0 +1,218 @@
package handlers
import (
"context"
"encoding/binary"
"fmt"
"net/http"
"strconv"
"strings"
"time"
objectv2 "github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-sdk-go/netmap"
"github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/nspcc-dev/neofs-sdk-go/pool"
)
// PrmAttributes groups parameters to form attributes from request headers.
type PrmAttributes struct {
DefaultTimestamp bool
DefaultFileName string
}
type epochDurations struct {
currentEpoch uint64
msPerBlock int64
blockPerEpoch uint64
}
const (
UserAttributeHeaderPrefix = "X-Attribute-"
SystemAttributePrefix = "__NEOFS__"
ExpirationDurationAttr = SystemAttributePrefix + "EXPIRATION_DURATION"
ExpirationTimestampAttr = SystemAttributePrefix + "EXPIRATION_TIMESTAMP"
ExpirationRFC3339Attr = SystemAttributePrefix + "EXPIRATION_RFC3339"
)
var neofsAttributeHeaderPrefixes = [...]string{"Neofs-", "NEOFS-", "neofs-"}
func systemTranslator(key, prefix string) string {
// replace specified prefix with `__NEOFS__`
key = strings.Replace(key, prefix, SystemAttributePrefix, 1)
// replace `-` with `_`
key = strings.ReplaceAll(key, "-", "_")
// replace with uppercase
return strings.ToUpper(key)
}
func filterHeaders(header http.Header) map[string]string {
result := make(map[string]string)
prefix := UserAttributeHeaderPrefix
for key, vals := range header {
if len(key) == 0 || len(vals) == 0 || len(vals[0]) == 0 {
continue
}
// checks that key has attribute prefix
if !strings.HasPrefix(key, prefix) {
continue
}
// removing attribute prefix
key = strings.TrimPrefix(key, prefix)
// checks that it's a system NeoFS header
for _, system := range neofsAttributeHeaderPrefixes {
if strings.HasPrefix(key, system) {
key = systemTranslator(key, system)
break
}
}
// checks that attribute key not empty
if len(key) == 0 {
continue
}
result[key] = vals[0]
}
return result
}
// GetObjectAttributes forms object attributes from request headers.
func GetObjectAttributes(ctx context.Context, header http.Header, pool *pool.Pool, prm PrmAttributes) ([]object.Attribute, error) {
filtered := filterHeaders(header)
if needParseExpiration(filtered) {
epochDuration, err := getEpochDurations(ctx, pool)
if err != nil {
return nil, fmt.Errorf("could not get epoch durations from network info: %w", err)
}
if err = prepareExpirationHeader(filtered, epochDuration); err != nil {
return nil, fmt.Errorf("could not prepare expiration header: %w", err)
}
}
attributes := make([]object.Attribute, 0, len(filtered))
// prepares attributes from filtered headers
for key, val := range filtered {
attribute := object.NewAttribute()
attribute.SetKey(key)
attribute.SetValue(val)
attributes = append(attributes, *attribute)
}
// sets FileName attribute if it wasn't set from header
if _, ok := filtered[object.AttributeFileName]; !ok && prm.DefaultFileName != "" {
filename := object.NewAttribute()
filename.SetKey(object.AttributeFileName)
filename.SetValue(prm.DefaultFileName)
attributes = append(attributes, *filename)
}
// sets Timestamp attribute if it wasn't set from header and enabled by settings
if _, ok := filtered[object.AttributeTimestamp]; !ok && prm.DefaultTimestamp {
timestamp := object.NewAttribute()
timestamp.SetKey(object.AttributeTimestamp)
timestamp.SetValue(strconv.FormatInt(time.Now().Unix(), 10))
attributes = append(attributes, *timestamp)
}
return attributes, nil
}
func getEpochDurations(ctx context.Context, p *pool.Pool) (*epochDurations, error) {
networkInfo, err := p.NetworkInfo(ctx)
if err != nil {
return nil, err
}
res := &epochDurations{
currentEpoch: networkInfo.CurrentEpoch(),
msPerBlock: networkInfo.MsPerBlock(),
}
networkInfo.NetworkConfig().IterateParameters(func(parameter *netmap.NetworkParameter) bool {
if string(parameter.Key()) == "EpochDuration" {
data := make([]byte, 8)
copy(data, parameter.Value())
res.blockPerEpoch = binary.LittleEndian.Uint64(data)
return true
}
return false
})
if res.blockPerEpoch == 0 {
return nil, fmt.Errorf("not found param: EpochDuration")
}
return res, nil
}
func needParseExpiration(headers map[string]string) bool {
_, ok1 := headers[ExpirationDurationAttr]
_, ok2 := headers[ExpirationRFC3339Attr]
_, ok3 := headers[ExpirationTimestampAttr]
return ok1 || ok2 || ok3
}
func prepareExpirationHeader(headers map[string]string, epochDurations *epochDurations) error {
expirationInEpoch := headers[objectv2.SysAttributeExpEpoch]
if timeRFC3339, ok := headers[ExpirationRFC3339Attr]; ok {
expTime, err := time.Parse(time.RFC3339, timeRFC3339)
if err != nil {
return fmt.Errorf("couldn't parse value %s of header %s", timeRFC3339, ExpirationRFC3339Attr)
}
now := time.Now().UTC()
if expTime.Before(now) {
return fmt.Errorf("value %s of header %s must be in the future", timeRFC3339, ExpirationRFC3339Attr)
}
updateExpirationHeader(headers, epochDurations, expTime.Sub(now))
delete(headers, ExpirationRFC3339Attr)
}
if timestamp, ok := headers[ExpirationTimestampAttr]; ok {
value, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
return fmt.Errorf("couldn't parse value %s of header %s", timestamp, ExpirationTimestampAttr)
}
expTime := time.Unix(value, 0)
now := time.Now()
if expTime.Before(now) {
return fmt.Errorf("value %s of header %s must be in the future", timestamp, ExpirationTimestampAttr)
}
updateExpirationHeader(headers, epochDurations, expTime.Sub(now))
delete(headers, ExpirationTimestampAttr)
}
if duration, ok := headers[ExpirationDurationAttr]; ok {
expDuration, err := time.ParseDuration(duration)
if err != nil {
return fmt.Errorf("couldn't parse value %s of header %s", duration, ExpirationDurationAttr)
}
if expDuration <= 0 {
return fmt.Errorf("value %s of header %s must be positive", expDuration, ExpirationDurationAttr)
}
updateExpirationHeader(headers, epochDurations, expDuration)
delete(headers, ExpirationDurationAttr)
}
if expirationInEpoch != "" {
headers[objectv2.SysAttributeExpEpoch] = expirationInEpoch
}
return nil
}
func updateExpirationHeader(headers map[string]string, durations *epochDurations, expDuration time.Duration) {
epochDuration := durations.msPerBlock * int64(durations.blockPerEpoch)
numEpoch := expDuration.Milliseconds() / epochDuration
headers[objectv2.SysAttributeExpEpoch] = strconv.FormatInt(int64(durations.currentEpoch)+numEpoch, 10)
}
func NewString(val string) *string {
return &val
}

388
spec/rest.yaml Normal file
View file

@ -0,0 +1,388 @@
swagger: "2.0"
info:
title: REST API NeoFS
description: REST API NeoFS
version: v1
host: localhost:8090
basePath: /v1
schemes:
- http
# - https
securityDefinitions:
BearerAuth:
type: apiKey
in: header
name: Authorization
security:
- BearerAuth: [ ]
paths:
/auth:
post:
operationId: auth
summary: Form bearer token to futher requests
security: [ ]
parameters:
- in: header
description: Supported operation scope for token
name: X-Neofs-Token-Scope
type: string
enum:
- object
- container
required: true
- in: header
description: Public key of user
name: X-Neofs-Token-Signature-Key
type: string
required: true
- in: header
description: Token lifetime in epoch
name: X-Neofs-Token-Lifetime
type: integer
default: 100
- in: body
name: token
required: true
description: Bearer token
schema:
$ref: '#/definitions/Bearer'
consumes:
- application/json
produces:
- application/json
responses:
200:
description: Base64 encoded stable binary marshaled bearer token
schema:
$ref: '#/definitions/TokenResponse'
400:
description: Bad request
schema:
$ref: '#/definitions/Error'
/objects:
parameters:
- in: header
name: X-Neofs-Token-Signature
description: Base64 encoded signature for bearer token
type: string
required: true
# example:
# BGtqMEpzxTabrdIIIDAnL79Cs7bm46+8lsFaMMU+LCKw/ujEjF0r5mVLKixWmxoreuj1E0BXWcqp9d3wGV6Hc9I=
- in: header
name: X-Neofs-Token-Signature-Key
description: Hex encoded the public part of the key that signed the bearer token
type: string
required: true
# example:
# 031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a
put:
operationId: putObject
summary: Upload object to NeoFS
parameters:
- in: body
required: true
name: object
description: Object info to upload
schema:
type: object
properties:
containerId:
type: string
fileName:
type: string
payload:
type: string
required:
- containerId
- fileName
example:
containerId: 5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv
fileName: myFile.txt
payload: Y29udGVudCBvZiBmaWxl
consumes:
- application/json
produces:
- application/json
responses:
200:
description: Address of uploaded objects
schema:
type: object
properties:
objectId:
type: string
containerId:
type: string
required:
- objectId
- containerId
example:
objectId: 8N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd
containerId: 5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv
400:
description: Bad request
schema:
$ref: '#/definitions/Error'
/containers:
parameters:
- in: header
name: X-Neofs-Token-Signature
description: Base64 encoded signature for bearer token
type: string
required: true
# example:
# BEvF1N0heytTXn1p2ZV3jN8YM25YkG4FxHmPeq2kWP5HeHCAN4cDjONyX6Bh30Ypw6Kfch2nYOfhiL+rClYQJ9Q=
- in: header
name: X-Neofs-Token-signature-Key
description: Hex encoded the public part of the key that signed the bearer token
type: string
required: true
# example:
# 031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a
put:
operationId: putContainer
summary: Create new container in NeoFS
parameters:
- in: body
name: container
required: true
description: Container info
schema:
type: object
properties:
containerName:
type: string
placementPolicy:
type: string
basicAcl:
type: string
required:
- containerName
example:
containerId: container
placementPolicy: "REP 3"
basicAcl: public-read-write
responses:
200:
description: Address of uploaded objects
schema:
type: object
properties:
containerId:
type: string
required:
- containerId
example:
containerId: 5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv
400:
description: Bad request
schema:
$ref: '#/definitions/Error'
/containers/{containerId}:
get:
operationId: getContainer
summary: Get container by id
security: [ ]
parameters:
- in: path
name: containerId
type: string
required: true
description: Base58 encoded container id
responses:
200:
description: Container info
schema:
$ref: '#/definitions/ContainerInfo'
400:
description: Bad request
schema:
$ref: '#/definitions/Error'
definitions:
Bearer:
type: object
properties:
object:
type: array
items:
$ref: '#/definitions/Record'
container:
$ref: '#/definitions/Rule'
Record:
type: object
properties:
action:
$ref: '#/definitions/Action'
operation:
$ref: '#/definitions/Operation'
filters:
type: array
items:
$ref: '#/definitions/Filter'
targets:
type: array
items:
$ref: '#/definitions/Target'
required:
- action
- operation
- filters
- targets
example:
operation: GET
action: ALLOW
filters: [ ]
targets:
- role: OTHERS
keys: [ ]
Action:
type: string
enum:
- ALLOW
- DENY
Operation:
type: string
enum:
- GET
- HEAD
- PUT
- DELETE
- SEARCH
- RANGE
- RANGEHASH
Filter:
type: object
properties:
headerType:
$ref: '#/definitions/HeaderType'
matchType:
$ref: '#/definitions/MatchType'
key:
type: string
value:
type: string
required:
- headerType
- matchType
- key
- value
example:
headerType: OBJECT
matchType: STRING_NOT_EQUAL
key: FileName
value: myfile
HeaderType:
type: string
enum:
- REQUEST
- OBJECT
- SERVICE
MatchType:
type: string
enum:
- STRING_EQUAL
- STRING_NOT_EQUAL
Target:
type: object
properties:
role:
$ref: '#/definitions/Role'
keys:
type: array
items:
type: string
required:
- role
- keys
example:
role: USER
keys:
- 021dc56fc6d81d581ae7605a8e00e0e0bab6cbad566a924a527339475a97a8e38e
Role:
type: string
enum:
- USER
- SYSTEM
- OTHERS
Rule:
type: object
properties:
verb:
$ref: '#/definitions/Verb'
containerId:
type: string
required:
- verb
Verb:
type: string
enum:
- PUT
- DELETE
- SETEACL
TokenResponse:
type: object
properties:
type:
$ref: '#/definitions/TokenType'
token:
type: string
required:
- type
- token
example:
- type: object
token: sometoken-todo-add
- type: container
token: ChCpanIBJCpJuJz42KOmGMSnEhsKGTWquaX2Lq6GhhO4faOYkLD0f9WkXuYJlq4aBAhnGAMiIQJgFcIEghQB5lq3AJZOVswInwc1IGhlQ7NCUh4DFO3UATIECAEQAQ==
TokenType:
type: string
enum:
- object
- container
ContainerInfo:
type: object
properties:
containerId:
type: string
version:
type: string
ownerId:
type: string
basicAcl:
type: string
placementPolicy:
type: string
attributes:
type: array
items:
$ref: '#/definitions/Attribute'
example:
containerId: 5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv
version: "2.11"
ownerId: NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM
basicAcl: "0x1fbf9fff"
placementPolicy: "REP 2"
attribute:
- key: Timestamp
value: "1648810072"
- key: Name
value: container
Attribute:
type: object
properties:
key:
type: string
value:
type: string
Principal:
type: string
Error:
type: string

View file

@ -0,0 +1,45 @@
layout:
application:
- name: main
source: asset:serverMain
target: "{{ joinFilePath .Target \"cmd\" (dasherize (pascalize .Name)) }}-server"
file_name: "main.go"
- name: embedded_spec
source: asset:swaggerJsonEmbed
target: "{{ joinFilePath .Target .ServerPackage }}"
file_name: "embedded_spec.go"
- name: server
source: serverServer
target: "{{ joinFilePath .Target .ServerPackage }}"
file_name: "server.go"
- name: server_config
source: serverConfig
target: "{{ joinFilePath .Target .ServerPackage }}"
file_name: "server_config.go"
- name: builder
source: asset:serverBuilder
target: "{{ joinFilePath .Target .ServerPackage .Package }}"
file_name: "{{ snakize (pascalize .Name) }}_api.go"
- name: doc
source: asset:serverDoc
target: "{{ joinFilePath .Target .ServerPackage }}"
file_name: "doc.go"
models:
- name: definition
source: asset:model
target: "{{ joinFilePath .Target .ModelPackage }}"
file_name: "{{ (snakize (pascalize .Name)) }}.go"
operations:
- name: parameters
source: asset:serverParameter
target: "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target .ServerPackage .APIPackage .Package }}{{ else }}{{ joinFilePath .Target .ServerPackage .Package }}{{ end }}"
file_name: "{{ (snakize (pascalize .Name)) }}_parameters.go"
- name: responses
source: asset:serverResponses
target: "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target .ServerPackage .APIPackage .Package }}{{ else }}{{ joinFilePath .Target .ServerPackage .Package }}{{ end }}"
file_name: "{{ (snakize (pascalize .Name)) }}_responses.go"
- name: handler
source: asset:serverOperation
target: "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target .ServerPackage .APIPackage .Package }}{{ else }}{{ joinFilePath .Target .ServerPackage .Package }}{{ end }}"
file_name: "{{ (snakize (pascalize .Name)) }}.go"
operation_groups:

View file

@ -0,0 +1,57 @@
// Code generated by go-swagger; DO NOT EDIT.
{{ if .Copyright -}}// {{ comment .Copyright -}}{{ end }}
package {{ .APIPackage }}
import (
"time"
"github.com/spf13/pflag"
)
const (
FlagScheme = "scheme"
FlagCleanupTimeout = "cleanup-timeout"
FlagGracefulTimeout = "graceful-timeout"
FlagMaxHeaderSize = "max-header-size"
FlagListenAddress = "listen-address"
FlagListenLimit = "listen-limit"
FlagKeepAlive = "keep-alive"
FlagReadTimeout = "read-timeout"
FlagWriteTimeout = "write-timeout"
FlagTLSListenAddress = "tls-listen-address"
FlagTLSCertificate = "tls-certificate"
FlagTLSKey = "tls-key"
FlagTLSCa = "tls-ca"
FlagTLSListenLimit = "tls-listen-limit"
FlagTLSKeepAlive = "tls-keep-alive"
FlagTLSReadTimeout = "tls-read-timeout"
FlagTLSWriteTimeout = "tls-write-timeout"
)
// BindDefaultFlag init default flag.
func BindDefaultFlags(flagSet *pflag.FlagSet) {
flagSet.StringSlice(FlagScheme, defaultSchemes, "the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec")
flagSet.Duration(FlagCleanupTimeout, 10*time.Second, "grace period for which to wait before killing idle connections")
flagSet.Duration(FlagGracefulTimeout, 15*time.Second, "grace period for which to wait before shutting down the server")
flagSet.Int(FlagMaxHeaderSize, 1000000, "controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body")
flagSet.String(FlagListenAddress, "localhost:8080", "the IP and port to listen on")
flagSet.Int(FlagListenLimit, 0, "limit the number of outstanding requests")
flagSet.Duration(FlagKeepAlive, 3*time.Minute, "sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)")
flagSet.Duration(FlagReadTimeout, 30*time.Second, "maximum duration before timing out read of the request")
flagSet.Duration(FlagWriteTimeout, 30*time.Second, "maximum duration before timing out write of the response")
flagSet.String(FlagTLSListenAddress, "localhost:8081", "the IP and port to listen on")
flagSet.String(FlagTLSCertificate, "", "the certificate file to use for secure connections")
flagSet.String(FlagTLSKey, "", "the private key file to use for secure connections (without passphrase)")
flagSet.String(FlagTLSCa, "", "the certificate authority certificate file to be used with mutual tls auth")
flagSet.Int(FlagTLSListenLimit, 0, "limit the number of outstanding requests")
flagSet.Duration(FlagTLSKeepAlive, 3*time.Minute, "sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)")
flagSet.Duration(FlagTLSReadTimeout, 30*time.Second, "maximum duration before timing out read of the request")
flagSet.Duration(FlagTLSWriteTimeout, 30*time.Second, "maximum duration before timing out write of the response")
}

View file

@ -0,0 +1,502 @@
// Code generated by go-swagger; DO NOT EDIT.
{{ if .Copyright -}}// {{ comment .Copyright -}}{{ end }}
package {{ .APIPackage }}
import (
"context"
"crypto/tls"
"errors"
"log"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/go-openapi/runtime/flagext"
"github.com/go-openapi/swag"
"golang.org/x/net/netutil"
{{ imports .DefaultImports }}
{{ imports .Imports }}
)
const (
schemeHTTP = "http"
schemeHTTPS = "https"
)
var defaultSchemes []string
func init() {
defaultSchemes = []string{ {{ if (hasInsecure .Schemes) }}
schemeHTTP,{{ end}}{{ if (hasSecure .Schemes) }}
schemeHTTPS,{{ end }}
}
}
type ServerConfig struct {
EnabledListeners []string
CleanupTimeout time.Duration
GracefulTimeout time.Duration
MaxHeaderSize int
ListenAddress string
ListenLimit int
KeepAlive time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
TLSListenAddress string
TLSListenLimit int
TLSKeepAlive time.Duration
TLSReadTimeout time.Duration
TLSWriteTimeout time.Duration
TLSCertificate string
TLSCertificateKey string
TLSCACertificate string
}
// NewServer creates a new api {{ humanize .Name }} server but does not configure it
func NewServer(api *{{ .APIPackageAlias }}.{{ pascalize .Name }}API, cfg *ServerConfig) *Server {
s := new(Server)
s.EnabledListeners = cfg.EnabledListeners
s.CleanupTimeout = cfg.CleanupTimeout
s.GracefulTimeout = cfg.GracefulTimeout
s.MaxHeaderSize = cfg.MaxHeaderSize
s.ListenAddress = cfg.ListenAddress
s.ListenLimit = cfg.ListenLimit
s.KeepAlive = cfg.KeepAlive
s.ReadTimeout = cfg.ReadTimeout
s.WriteTimeout = cfg.WriteTimeout
s.TLSListenAddress = cfg.TLSListenAddress
if len(s.TLSListenAddress) == 0 {
s.TLSListenAddress = s.ListenAddress
}
s.TLSCertificate = cfg.TLSCertificate
s.TLSCertificateKey = cfg.TLSCertificateKey
s.TLSCACertificate = cfg.TLSCACertificate
s.TLSListenLimit = cfg.TLSListenLimit
s.TLSKeepAlive = cfg.TLSKeepAlive
s.TLSReadTimeout = cfg.TLSReadTimeout
s.TLSWriteTimeout = cfg.TLSWriteTimeout
s.shutdown = make(chan struct{})
s.api = api
s.interrupt = make(chan os.Signal, 1)
return s
}
// ConfigureAPI configures the API and handlers.
func (s *Server) ConfigureAPI(fn func (*{{ .APIPackageAlias }}.{{ pascalize .Name }}API) http.Handler) {
if s.api != nil {
s.handler = fn(s.api)
}
}
// Server for the {{ humanize .Name }} API
type Server struct {
EnabledListeners []string
CleanupTimeout time.Duration
GracefulTimeout time.Duration
MaxHeaderSize int
ListenAddress string
ListenLimit int
KeepAlive time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
httpServerL net.Listener
TLSListenAddress string
TLSCertificate string
TLSCertificateKey string
TLSCACertificate string
TLSListenLimit int
TLSKeepAlive time.Duration
TLSReadTimeout time.Duration
TLSWriteTimeout time.Duration
httpsServerL net.Listener
cfgTLSFn func (tlsConfig *tls.Config)
cfgServerFn func(s *http.Server, scheme, addr string)
api *{{ .APIPackageAlias }}.{{ pascalize .Name }}API
handler http.Handler
hasListeners bool
shutdown chan struct{}
shuttingDown int32
interrupted bool
interrupt chan os.Signal
}
// Logf logs message either via defined user logger or via system one if no user logger is defined.
func (s *Server) Logf(f string, args ...interface{}) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
} else {
log.Printf(f, args...)
}
}
// Fatalf logs message either via defined user logger or via system one if no user logger is defined.
// Exits with non-zero status after printing
func (s *Server) Fatalf(f string, args ...interface{}) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
os.Exit(1)
} else {
log.Fatalf(f, args...)
}
}
func (s *Server) hasScheme(scheme string) bool {
schemes := s.EnabledListeners
if len(schemes) == 0 {
schemes = defaultSchemes
}
for _, v := range schemes {
if v == scheme {
return true
}
}
return false
}
// Serve the api
func (s *Server) Serve() (err error) {
if !s.hasListeners {
if err = s.Listen(); err != nil {
return err
}
}
// set default handler, if none is set
if s.handler == nil {
if s.api == nil {
return errors.New("can't create the default handler, as no api is set")
}
s.SetHandler(s.api.Serve(nil))
}
wg := new(sync.WaitGroup)
once := new(sync.Once)
signalNotify(s.interrupt)
go handleInterrupt(once, s)
servers := []*http.Server{}
if s.hasScheme(schemeHTTP) {
httpServer := new(http.Server)
httpServer.MaxHeaderBytes = s.MaxHeaderSize
httpServer.ReadTimeout = s.ReadTimeout
httpServer.WriteTimeout = s.WriteTimeout
httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0)
if s.ListenLimit > 0 {
s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit)
}
if int64(s.CleanupTimeout) > 0 {
httpServer.IdleTimeout = s.CleanupTimeout
}
httpServer.Handler = s.handler
if s.cfgServerFn !=nil {
s.cfgServerFn(httpServer, "http", s.httpServerL.Addr().String())
}
servers = append(servers, httpServer)
wg.Add(1)
s.Logf("Serving {{ humanize .Name }} at http://%s", s.httpServerL.Addr())
go func(l net.Listener) {
defer wg.Done()
if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
}
s.Logf("Stopped serving {{ humanize .Name }} at http://%s", l.Addr())
}(s.httpServerL)
}
if s.hasScheme(schemeHTTPS) {
httpsServer := new(http.Server)
httpsServer.MaxHeaderBytes = s.MaxHeaderSize
httpsServer.ReadTimeout = s.TLSReadTimeout
httpsServer.WriteTimeout = s.TLSWriteTimeout
httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0)
if s.TLSListenLimit > 0 {
s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit)
}
if int64(s.CleanupTimeout) > 0 {
httpsServer.IdleTimeout = s.CleanupTimeout
}
httpsServer.Handler = s.handler
// Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
httpsServer.TLSConfig = &tls.Config{
// Causes servers to use Go's default ciphersuite preferences,
// which are tuned to avoid attacks. Does nothing on clients.
PreferServerCipherSuites: true,
// Only use curves which have assembly implementations
// https://github.com/golang/go/tree/master/src/crypto/elliptic
CurvePreferences: []tls.CurveID{tls.CurveP256},
{{- if .UseModernMode }}
// Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
NextProtos: []string{"h2", "http/1.1"},
// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
MinVersion: tls.VersionTLS12,
// These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
{{- end }}
}
// build standard config from server options
if s.TLSCertificate != "" && s.TLSCertificateKey != "" {
httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(s.TLSCertificate, s.TLSCertificateKey)
if err != nil {
return err
}
}
if s.TLSCACertificate != "" {
// include specified CA certificate
caCert, caCertErr := ioutil.ReadFile(s.TLSCACertificate)
if caCertErr != nil {
return caCertErr
}
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(caCert)
if !ok {
return fmt.Errorf("cannot parse CA certificate")
}
httpsServer.TLSConfig.ClientCAs = caCertPool
httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
// call custom TLS configurator
if s.cfgTLSFn != nil {
s.cfgTLSFn(httpsServer.TLSConfig)
}
if len(httpsServer.TLSConfig.Certificates) == 0 && httpsServer.TLSConfig.GetCertificate == nil {
// after standard and custom config are passed, this ends up with no certificate
if s.TLSCertificate == "" {
if s.TLSCertificateKey == "" {
s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
}
s.Fatalf("the required flag `--tls-certificate` was not specified")
}
if s.TLSCertificateKey == "" {
s.Fatalf("the required flag `--tls-key` was not specified")
}
// this happens with a wrong custom TLS configurator
s.Fatalf("no certificate was configured for TLS")
}
if s.cfgServerFn !=nil {
s.cfgServerFn(httpsServer, "https", s.httpsServerL.Addr().String())
}
servers = append(servers, httpsServer)
wg.Add(1)
s.Logf("Serving {{ humanize .Name }} at https://%s", s.httpsServerL.Addr())
go func(l net.Listener) {
defer wg.Done()
if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
}
s.Logf("Stopped serving {{ humanize .Name }} at https://%s", l.Addr())
}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))
}
wg.Add(1)
go s.handleShutdown(wg, &servers)
wg.Wait()
return nil
}
// The TLS configuration before HTTPS server starts.
func (s *Server) ConfigureTLS(cfgTLS func (tlsConfig *tls.Config)) {
s.cfgTLSFn = cfgTLS
}
// As soon as server is initialized but not run yet, this function will be called.
// If you need to modify a config, store server instance to stop it individually later, this is the place.
// This function can be called multiple times, depending on the number of serving schemes.
// scheme value will be set accordingly: "http", "https" or "unix".
func (s *Server) ConfigureServer(cfgServer func (s *http.Server, scheme, addr string)) {
s.cfgServerFn = cfgServer
}
// Listen creates the listeners for the server
func (s *Server) Listen() error {
if s.hasListeners { // already done this
return nil
}
if s.hasScheme(schemeHTTPS) {
// Use http listen limit if https listen limit wasn't defined
if s.TLSListenLimit == 0 {
s.TLSListenLimit = s.ListenLimit
}
// Use http tcp keep alive if https tcp keep alive wasn't defined
if int64(s.TLSKeepAlive) == 0 {
s.TLSKeepAlive = s.KeepAlive
}
// Use http read timeout if https read timeout wasn't defined
if int64(s.TLSReadTimeout) == 0 {
s.TLSReadTimeout = s.ReadTimeout
}
// Use http write timeout if https write timeout wasn't defined
if int64(s.TLSWriteTimeout) == 0 {
s.TLSWriteTimeout = s.WriteTimeout
}
}
if s.hasScheme(schemeHTTP) {
listener, err := net.Listen("tcp", s.ListenAddress)
if err != nil {
return err
}
_, _, err = swag.SplitHostPort(listener.Addr().String())
if err != nil {
return err
}
s.httpServerL = listener
}
if s.hasScheme(schemeHTTPS) {
tlsListener, err := net.Listen("tcp", s.TLSListenAddress)
if err != nil {
return err
}
_, _, err = swag.SplitHostPort(tlsListener.Addr().String())
if err != nil {
return err
}
s.httpsServerL = tlsListener
}
s.hasListeners = true
return nil
}
// Shutdown server and clean up resources
func (s *Server) Shutdown() error {
if atomic.CompareAndSwapInt32(&s.shuttingDown, 0, 1) {
close(s.shutdown)
}
return nil
}
func (s *Server) handleShutdown(wg *sync.WaitGroup, serversPtr *[]*http.Server) {
// wg.Done must occur last, after s.api.ServerShutdown()
// (to preserve old behaviour)
defer wg.Done()
<-s.shutdown
servers := *serversPtr
ctx, cancel := context.WithTimeout(context.TODO(), s.GracefulTimeout)
defer cancel()
// first execute the pre-shutdown hook
s.api.PreServerShutdown()
shutdownChan := make(chan bool)
for i := range servers {
server := servers[i]
go func() {
var success bool
defer func() {
shutdownChan <- success
}()
if err := server.Shutdown(ctx); err != nil {
// Error from closing listeners, or context timeout:
s.Logf("HTTP server Shutdown: %v", err)
} else {
success = true
}
}()
}
// Wait until all listeners have successfully shut down before calling ServerShutdown
success := true
for range servers {
success = success && <-shutdownChan
}
if success {
s.api.ServerShutdown()
}
}
// GetHandler returns a handler useful for testing
func (s *Server) GetHandler() http.Handler {
return s.handler
}
// SetHandler allows for setting a http handler on this server
func (s *Server) SetHandler(handler http.Handler) {
s.handler = handler
}
// HTTPListener returns the http listener
func (s *Server) HTTPListener() (net.Listener, error) {
if !s.hasListeners {
if err := s.Listen(); err != nil {
return nil, err
}
}
return s.httpServerL, nil
}
// TLSListener returns the https listener
func (s *Server) TLSListener() (net.Listener, error) {
if !s.hasListeners {
if err := s.Listen(); err != nil {
return nil, err
}
}
return s.httpsServerL, nil
}
func handleInterrupt(once *sync.Once, s *Server) {
once.Do(func(){
for range s.interrupt {
if s.interrupted {
s.Logf("Server already shutting down")
continue
}
s.interrupted = true
s.Logf("Shutting down... ")
if err := s.Shutdown(); err != nil {
s.Logf("HTTP server Shutdown: %v", err)
}
}
})
}
func signalNotify(interrupt chan<- os.Signal) {
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
}