mirror of
https://github.com/pgsty/minio.git
synced 2026-08-14 10:43:15 +03:00
Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 22d2dbc4e6 | |||
| 2b5d9428b1 | |||
| d6446cb096 | |||
| c34bdc33fb | |||
| dd8547e51c | |||
| aec023f537 | |||
| e101eeeda9 | |||
| f29522269d | |||
| 3c470a6b8b | |||
| 6bc7d711b3 | |||
| 10d5dd3a67 | |||
| d9f1df01eb | |||
| cdeab19673 | |||
| 22ee678136 | |||
| 50a8f13e85 | |||
| 6dec60b6e6 | |||
| ac3a19138a | |||
| 21e8e071d7 | |||
| 57f84a8b4c | |||
| 8a672e70a7 | |||
| 22041bbcc4 | |||
| 5afb459113 | |||
| 91ebac0a00 | |||
| 5fcb1cfd31 | |||
| 3a90fb108c | |||
| 4eeb48f8e0 | |||
| 373d48c8a3 | |||
| 1472875670 | |||
| 74cfb207c1 | |||
| 6a096e7dc7 | |||
| 9788d85ea3 | |||
| 69c0e18685 | |||
| 3cac927348 | |||
| 9081346c40 | |||
| fcfadb0e51 | |||
| 2add57cfed | |||
| c5279ec630 | |||
| b73699fad8 | |||
| b8ebe54e53 | |||
| c3d70e0795 | |||
| 8c4561b8da | |||
| fd421ddd6f | |||
| 9947c01c8e | |||
| a00db4267c | |||
| 36385010f5 | |||
| fa6d082bfd | |||
| 9fab91852a | |||
| b733e6e83c | |||
| ce05bb69dc | |||
| 37aa5934a1 | |||
| 1647fc7edc | |||
| 7b92687397 | |||
| 419e5baf16 | |||
| dc48cd841a | |||
| b0e1776d6d | |||
| 7a7068ee47 | |||
| cbc0ef459b | |||
| a2aabfabd9 | |||
| 822cbd4b43 | |||
| 3c19a9308d | |||
| 32890342ce | |||
| ed2c2a285f | |||
| 18e23bafd9 | |||
| 8b8be2695f | |||
| 96fbf18201 | |||
| c8a57a8fa2 | |||
| b1c2dacab3 | |||
| 65939913b4 | |||
| 08b3a466e8 | |||
| 5aa7c38035 | |||
| 1df5e31706 | |||
| 9f7044aed0 | |||
| 41de53996b | |||
| 9878031cfd | |||
| e3fbcaeb72 | |||
| ca6dd8be5e | |||
| fba0924b1d | |||
| 703ed46d79 | |||
| f7ca6c63c2 | |||
| ad69b9907f | |||
| b9269151a4 | |||
| bfddbb8b40 | |||
| 13a2dc8485 | |||
| 1e51424e8a | |||
| 5b114b43f7 | |||
| 812f5a02d7 | |||
| 19f70dbfbf | |||
| 1c99fb106c | |||
| 71c32e9b48 | |||
| 380a59520b | |||
| 3995355150 | |||
| 8208bcb896 | |||
| d665e855de | |||
| 18b3655c99 | |||
| 6a8d8f34a5 | |||
| b1c1f02132 | |||
| ea93643e6a |
+9
-2
@@ -1,10 +1,17 @@
|
|||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
docs
|
|
||||||
default.etcd
|
default.etcd
|
||||||
*.gz
|
*.gz
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.bzip2
|
*.bzip2
|
||||||
*.zip
|
*.zip
|
||||||
browser/node_modules
|
browser/node_modules
|
||||||
node_modules
|
node_modules
|
||||||
|
docs/debugging/s3-verify/s3-verify
|
||||||
|
docs/debugging/xl-meta/xl-meta
|
||||||
|
docs/debugging/s3-check-md5/s3-check-md5
|
||||||
|
docs/debugging/hash-set/hash-set
|
||||||
|
docs/debugging/healing-bin/healing-bin
|
||||||
|
docs/debugging/inspect/inspect
|
||||||
|
docs/debugging/pprofgoparser/pprofgoparser
|
||||||
|
docs/debugging/reorder-disks/reorder-disks
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.21.0
|
go-version: 1.21.1
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Get official govulncheck
|
- name: Get official govulncheck
|
||||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
|||||||
+3
-2
@@ -32,6 +32,8 @@ minio.RELEASE*
|
|||||||
mc
|
mc
|
||||||
nancy
|
nancy
|
||||||
inspects/*
|
inspects/*
|
||||||
|
.bin/
|
||||||
|
*.gz
|
||||||
docs/debugging/s3-verify/s3-verify
|
docs/debugging/s3-verify/s3-verify
|
||||||
docs/debugging/xl-meta/xl-meta
|
docs/debugging/xl-meta/xl-meta
|
||||||
docs/debugging/s3-check-md5/s3-check-md5
|
docs/debugging/s3-check-md5/s3-check-md5
|
||||||
@@ -39,5 +41,4 @@ docs/debugging/hash-set/hash-set
|
|||||||
docs/debugging/healing-bin/healing-bin
|
docs/debugging/healing-bin/healing-bin
|
||||||
docs/debugging/inspect/inspect
|
docs/debugging/inspect/inspect
|
||||||
docs/debugging/pprofgoparser/pprofgoparser
|
docs/debugging/pprofgoparser/pprofgoparser
|
||||||
.bin/
|
docs/debugging/reorder-disks/reorder-disks
|
||||||
*.gz
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ linters:
|
|||||||
enable:
|
enable:
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofmt
|
|
||||||
- gofumpt
|
- gofumpt
|
||||||
- goimports
|
- goimports
|
||||||
- gomodguard
|
- gomodguard
|
||||||
|
|||||||
@@ -2392,6 +2392,7 @@ https://github.com/IBM/sarama
|
|||||||
# MIT License
|
# MIT License
|
||||||
|
|
||||||
Copyright (c) 2013 Shopify
|
Copyright (c) 2013 Shopify
|
||||||
|
|
||||||
Copyright (c) 2023 IBM Corporation
|
Copyright (c) 2023 IBM Corporation
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
@@ -2922,33 +2923,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
|
|
||||||
================================================================
|
================================================================
|
||||||
|
|
||||||
github.com/benbjohnson/clock
|
|
||||||
https://github.com/benbjohnson/clock
|
|
||||||
----------------------------------------------------------------
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Ben Johnson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
github.com/beorn7/perks
|
github.com/beorn7/perks
|
||||||
https://github.com/beorn7/perks
|
https://github.com/beorn7/perks
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
@@ -3089,7 +3063,7 @@ https://github.com/charmbracelet/lipgloss
|
|||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2021 Charmbracelet, Inc
|
Copyright (c) 2021-2023 Charmbracelet, Inc
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -11869,6 +11843,8 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
|||||||
github.com/hashicorp/golang-lru
|
github.com/hashicorp/golang-lru
|
||||||
https://github.com/hashicorp/golang-lru
|
https://github.com/hashicorp/golang-lru
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
Copyright (c) 2014 HashiCorp, Inc.
|
||||||
|
|
||||||
Mozilla Public License, version 2.0
|
Mozilla Public License, version 2.0
|
||||||
|
|
||||||
1. Definitions
|
1. Definitions
|
||||||
@@ -20017,6 +19993,673 @@ For more information on this, and how to apply and follow the GNU AGPL, see
|
|||||||
|
|
||||||
================================================================
|
================================================================
|
||||||
|
|
||||||
|
github.com/minio/pkg/v2
|
||||||
|
https://github.com/minio/pkg/v2
|
||||||
|
----------------------------------------------------------------
|
||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 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 Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
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 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 work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero 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 your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
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 AGPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
================================================================
|
||||||
|
|
||||||
github.com/minio/selfupdate
|
github.com/minio/selfupdate
|
||||||
https://github.com/minio/selfupdate
|
https://github.com/minio/selfupdate
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|||||||
@@ -45,22 +45,22 @@ lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
|
|||||||
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
|
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
|
||||||
|
|
||||||
check: test
|
check: test
|
||||||
test: verifiers build ## builds minio, runs linters, tests
|
test: verifiers build build-debugging ## builds minio, runs linters, tests
|
||||||
@echo "Running unit tests"
|
@echo "Running unit tests"
|
||||||
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue ./...
|
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue ./...
|
||||||
|
|
||||||
test-root-disable: install
|
test-root-disable: install-race
|
||||||
@echo "Running minio root lockdown tests"
|
@echo "Running minio root lockdown tests"
|
||||||
@env bash $(PWD)/buildscripts/disable-root.sh
|
@env bash $(PWD)/buildscripts/disable-root.sh
|
||||||
|
|
||||||
test-decom: install
|
test-decom: install-race
|
||||||
@echo "Running minio decom tests"
|
@echo "Running minio decom tests"
|
||||||
@env bash $(PWD)/docs/distributed/decom.sh
|
@env bash $(PWD)/docs/distributed/decom.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-encrypted.sh
|
@env bash $(PWD)/docs/distributed/decom-encrypted.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
||||||
|
|
||||||
test-upgrade: build
|
test-upgrade: install-race
|
||||||
@echo "Running minio upgrade tests"
|
@echo "Running minio upgrade tests"
|
||||||
@(env bash $(PWD)/buildscripts/minio-upgrade.sh)
|
@(env bash $(PWD)/buildscripts/minio-upgrade.sh)
|
||||||
|
|
||||||
@@ -86,18 +86,18 @@ test-replication-3site:
|
|||||||
test-delete-replication:
|
test-delete-replication:
|
||||||
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
|
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
|
||||||
|
|
||||||
test-replication: install test-replication-2site test-replication-3site test-delete-replication test-sio-error ## verify multi site replication
|
test-replication: install-race test-replication-2site test-replication-3site test-delete-replication test-sio-error ## verify multi site replication
|
||||||
@echo "Running tests for replicating three sites"
|
@echo "Running tests for replicating three sites"
|
||||||
|
|
||||||
test-site-replication-ldap: install ## verify automatic site replication
|
test-site-replication-ldap: install-race ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
|
||||||
|
|
||||||
test-site-replication-oidc: install ## verify automatic site replication
|
test-site-replication-oidc: install-race ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with OIDC)"
|
@echo "Running tests for automatic site replication of IAM (with OIDC)"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-oidc.sh)
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-oidc.sh)
|
||||||
|
|
||||||
test-site-replication-minio: install ## verify automatic site replication
|
test-site-replication-minio: install-race ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with MinIO IDP)"
|
@echo "Running tests for automatic site replication of IAM (with MinIO IDP)"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh)
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh)
|
||||||
|
|
||||||
@@ -128,6 +128,9 @@ verify-healing-inconsistent-versions: ## verify resolving inconsistent versions
|
|||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
|
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
|
||||||
|
|
||||||
|
build-debugging:
|
||||||
|
@(env bash $(PWD)/docs/debugging/build.sh)
|
||||||
|
|
||||||
build: checks ## builds minio to $(PWD)
|
build: checks ## builds minio to $(PWD)
|
||||||
@echo "Building minio binary to './minio'"
|
@echo "Building minio binary to './minio'"
|
||||||
@CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
@@ -159,6 +162,12 @@ docker: build ## builds minio docker container
|
|||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
|
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
|
||||||
|
|
||||||
|
install-race: checks ## builds minio to $(PWD)
|
||||||
|
@echo "Building minio binary to './minio'"
|
||||||
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
||||||
|
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
|
||||||
|
|
||||||
install: build ## builds minio and installs it to $GOPATH/bin.
|
install: build ## builds minio and installs it to $GOPATH/bin.
|
||||||
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
||||||
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
|
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ done
|
|||||||
|
|
||||||
set +e
|
set +e
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
|
||||||
./mc ls minioadm/
|
./mc ls minioadm/
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "listing failed, 'minioadmin' should be enabled"
|
echo "listing failed, 'minioadmin' should be enabled"
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data types used for returning dummy access control
|
// Data types used for returning dummy access control
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -58,7 +57,7 @@ const (
|
|||||||
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketQuotaAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -95,7 +94,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
Quota: data,
|
Quota: data,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}
|
}
|
||||||
if quotaConfig.Quota == 0 {
|
if quotaConfig.Size == 0 || quotaConfig.Quota == 0 {
|
||||||
bucketMeta.Quota = nil
|
bucketMeta.Quota = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +109,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketQuotaAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetBucketQuotaAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -148,7 +147,7 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
update := r.Form.Get("update") == "true"
|
update := r.Form.Get("update") == "true"
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -291,7 +290,7 @@ func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *htt
|
|||||||
arnType := vars["type"]
|
arnType := vars["type"]
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -325,7 +324,7 @@ func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *ht
|
|||||||
arn := vars["arn"]
|
arn := vars["arn"]
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -365,7 +364,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
bucket := pathClean(r.Form.Get("bucket"))
|
bucket := pathClean(r.Form.Get("bucket"))
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportBucketMetadataAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportBucketMetadataAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -647,7 +646,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ImportBucketMetadataAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ImportBucketMetadataAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -853,7 +852,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
bucketPolicy, err := policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
@@ -1021,7 +1020,7 @@ func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ReplicationDiff)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1083,7 +1082,7 @@ func (a adminAPIHandlers) ReplicationMRFHandler(w http.ResponseWriter, r *http.R
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ReplicationDiff)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// validateAdminReq will validate request against and return whether it is allowed.
|
// validateAdminReq will validate request against and return whether it is allowed.
|
||||||
// If any of the supplied actions are allowed it will be successful.
|
// If any of the supplied actions are allowed it will be successful.
|
||||||
// If nil ObjectLayer is returned, the operation is not permitted.
|
// If nil ObjectLayer is returned, the operation is not permitted.
|
||||||
// When nil ObjectLayer has been returned an error has always been sent to w.
|
// When nil ObjectLayer has been returned an error has always been sent to w.
|
||||||
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, actions ...iampolicy.AdminAction) (ObjectLayer, auth.Credentials) {
|
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, actions ...policy.AdminAction) (ObjectLayer, auth.Credentials) {
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil || globalNotificationSys == nil {
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
@@ -78,7 +78,7 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
|
|
||||||
var apiErr APIError
|
var apiErr APIError
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case iampolicy.Error:
|
case policy.Error:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioMalformedIAMPolicy",
|
Code: "XMinioMalformedIAMPolicy",
|
||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/subnet"
|
"github.com/minio/minio/internal/config/subnet"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
||||||
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ type setConfigResult struct {
|
|||||||
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -242,7 +242,7 @@ func setConfigKV(ctx context.Context, objectAPI ObjectLayer, kvBytes []byte) (re
|
|||||||
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -288,7 +288,7 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -321,7 +321,7 @@ func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *
|
|||||||
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -367,7 +367,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
|||||||
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -405,7 +405,7 @@ func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *h
|
|||||||
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -430,7 +430,7 @@ func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Req
|
|||||||
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -482,7 +482,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/ldap"
|
||||||
"github.com/minio/pkg/ldap"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) {
|
func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) {
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -220,7 +220,7 @@ func (a adminAPIHandlers) UpdateIdentityProviderCfg(w http.ResponseWriter, r *ht
|
|||||||
func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -273,7 +273,7 @@ func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http
|
|||||||
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -332,7 +332,7 @@ func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
|
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
|
||||||
@@ -50,7 +50,7 @@ func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r
|
|||||||
// Check authorization.
|
// Check authorization.
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r,
|
objectAPI, cred := validateAdminReq(ctx, w, r,
|
||||||
iampolicy.ListGroupsAdminAction, iampolicy.ListUsersAdminAction, iampolicy.ListUserPoliciesAdminAction)
|
policy.ListGroupsAdminAction, policy.ListUsersAdminAction, policy.ListUserPoliciesAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
// Check authorization.
|
// Check authorization.
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.UpdatePolicyAssociationAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.UpdatePolicyAssociationAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -37,7 +37,7 @@ var (
|
|||||||
func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DecommissionAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DecommissionAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
|||||||
func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DecommissionAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DecommissionAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction, iampolicy.DecommissionAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction, policy.DecommissionAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -200,7 +200,7 @@ func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction, iampolicy.DecommissionAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction, policy.DecommissionAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -233,7 +233,7 @@ func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -304,7 +304,7 @@ func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request)
|
|||||||
func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -344,7 +344,7 @@ func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request
|
|||||||
func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,15 +33,14 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
|
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
|
||||||
func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -75,7 +74,7 @@ func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -97,7 +96,7 @@ func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -144,7 +143,7 @@ func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request
|
|||||||
func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -163,7 +162,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
|||||||
if item.Policy == nil {
|
if item.Policy == nil {
|
||||||
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt)
|
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt)
|
||||||
} else {
|
} else {
|
||||||
policy, perr := iampolicy.ParseConfig(bytes.NewReader(item.Policy))
|
policy, perr := policy.ParseConfig(bytes.NewReader(item.Policy))
|
||||||
if perr != nil {
|
if perr != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, perr), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, perr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -196,7 +195,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
|||||||
func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -220,7 +219,7 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
if item.Policy == nil {
|
if item.Policy == nil {
|
||||||
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil, item.UpdatedAt)
|
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil, item.UpdatedAt)
|
||||||
} else {
|
} else {
|
||||||
bktPolicy, berr := policy.ParseConfig(bytes.NewReader(item.Policy), item.Bucket)
|
bktPolicy, berr := policy.ParseBucketPolicyConfig(bytes.NewReader(item.Policy), item.Bucket)
|
||||||
if berr != nil {
|
if berr != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, berr), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, berr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -265,7 +264,7 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -285,7 +284,7 @@ func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Req
|
|||||||
func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -322,7 +321,7 @@ func parseJSONBody(ctx context.Context, body io.Reader, v interface{}, encryptio
|
|||||||
func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -351,7 +350,7 @@ func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.R
|
|||||||
func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -373,7 +372,7 @@ func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http
|
|||||||
func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -404,7 +403,7 @@ func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Req
|
|||||||
func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -439,7 +438,7 @@ func getSRStatusOptions(r *http.Request) (opts madmin.SRStatusOptions) {
|
|||||||
func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationRemoveAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationRemoveAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -470,7 +469,7 @@ func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.R
|
|||||||
func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationRemoveAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationRemoveAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -492,7 +491,7 @@ func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationResyncAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationResyncAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
minio "github.com/minio/minio-go/v7"
|
minio "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
|
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
|
||||||
|
|||||||
+96
-77
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -34,14 +35,14 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/dns"
|
"github.com/minio/minio/internal/config/dns"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
|
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
|
||||||
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.DeleteUserAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.DeleteUserAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -85,7 +86,7 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ListUsersAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -119,7 +120,7 @@ func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request
|
|||||||
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ListUsersAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -186,10 +187,10 @@ func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
checkDenyOnly = true
|
checkDenyOnly = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.GetUserAdminAction,
|
Action: policy.GetUserAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -218,7 +219,7 @@ func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AddUserToGroupAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.AddUserToGroupAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -288,7 +289,7 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetGroupAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetGroupAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -315,7 +316,7 @@ func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListGroupsAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListGroupsAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -339,7 +340,7 @@ func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.EnableGroupAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.EnableGroupAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -382,7 +383,7 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request)
|
|||||||
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, creds := validateAdminReq(ctx, w, r, iampolicy.EnableUserAdminAction)
|
objectAPI, creds := validateAdminReq(ctx, w, r, policy.EnableUserAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -470,10 +471,10 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
checkDenyOnly = true
|
checkDenyOnly = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.CreateUserAdminAction,
|
Action: policy.CreateUserAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -544,10 +545,10 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListTemporaryAccountsAdminAction,
|
Action: policy.ListTemporaryAccountsAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -556,16 +557,16 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stsAccount, policy, err := globalIAMSys.GetTemporaryAccount(ctx, accessKey)
|
stsAccount, sessionPolicy, err := globalIAMSys.GetTemporaryAccount(ctx, accessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var stsAccountPolicy iampolicy.Policy
|
var stsAccountPolicy policy.Policy
|
||||||
|
|
||||||
if policy != nil {
|
if sessionPolicy != nil {
|
||||||
stsAccountPolicy = *policy
|
stsAccountPolicy = *sessionPolicy
|
||||||
} else {
|
} else {
|
||||||
policiesNames, err := globalIAMSys.PolicyDBGet(stsAccount.ParentUser, false)
|
policiesNames, err := globalIAMSys.PolicyDBGet(stsAccount.ParentUser, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -584,7 +585,7 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
|||||||
infoResp := madmin.TemporaryAccountInfoResp{
|
infoResp := madmin.TemporaryAccountInfoResp{
|
||||||
ParentUser: stsAccount.ParentUser,
|
ParentUser: stsAccount.ParentUser,
|
||||||
AccountStatus: stsAccount.Status,
|
AccountStatus: stsAccount.Status,
|
||||||
ImpliedPolicy: policy == nil,
|
ImpliedPolicy: sessionPolicy == nil,
|
||||||
Policy: string(policyJSON),
|
Policy: string(policyJSON),
|
||||||
Expiration: &stsAccount.Expiration,
|
Expiration: &stsAccount.Expiration,
|
||||||
}
|
}
|
||||||
@@ -709,10 +710,10 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
//
|
//
|
||||||
// This allows turning off service accounts for request sender,
|
// This allows turning off service accounts for request sender,
|
||||||
// if there is no deny statement this call is implicitly enabled.
|
// if there is no deny statement this call is implicitly enabled.
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: requestorUser,
|
AccountName: requestorUser,
|
||||||
Groups: requestorGroups,
|
Groups: requestorGroups,
|
||||||
Action: iampolicy.CreateServiceAccountAdminAction,
|
Action: policy.CreateServiceAccountAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -743,10 +744,10 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
} else {
|
} else {
|
||||||
// Need permission if we are creating a service account for a
|
// Need permission if we are creating a service account for a
|
||||||
// user <> to the request sender
|
// user <> to the request sender
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: requestorUser,
|
AccountName: requestorUser,
|
||||||
Groups: requestorGroups,
|
Groups: requestorGroups,
|
||||||
Action: iampolicy.CreateServiceAccountAdminAction,
|
Action: policy.CreateServiceAccountAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -773,9 +774,9 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
// latter, a group notion is not supported.
|
// latter, a group notion is not supported.
|
||||||
}
|
}
|
||||||
|
|
||||||
var sp *iampolicy.Policy
|
var sp *policy.Policy
|
||||||
if len(createReq.Policy) > 0 {
|
if len(createReq.Policy) > 0 {
|
||||||
sp, err = iampolicy.ParseConfig(bytes.NewReader(createReq.Policy))
|
sp, err = policy.ParseConfig(bytes.NewReader(createReq.Policy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -864,10 +865,10 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.UpdateServiceAccountAdminAction,
|
Action: policy.UpdateServiceAccountAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -903,9 +904,9 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var sp *iampolicy.Policy
|
var sp *policy.Policy
|
||||||
if len(updateReq.NewPolicy) > 0 {
|
if len(updateReq.NewPolicy) > 0 {
|
||||||
sp, err = iampolicy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
|
sp, err = policy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -970,16 +971,16 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
svcAccount, policy, err := globalIAMSys.GetServiceAccount(ctx, accessKey)
|
svcAccount, sessionPolicy, err := globalIAMSys.GetServiceAccount(ctx, accessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListServiceAccountsAdminAction,
|
Action: policy.ListServiceAccountsAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -995,10 +996,10 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var svcAccountPolicy iampolicy.Policy
|
var svcAccountPolicy policy.Policy
|
||||||
|
|
||||||
if policy != nil {
|
if sessionPolicy != nil {
|
||||||
svcAccountPolicy = *policy
|
svcAccountPolicy = *sessionPolicy
|
||||||
} else {
|
} else {
|
||||||
policiesNames, err := globalIAMSys.PolicyDBGet(svcAccount.ParentUser, false)
|
policiesNames, err := globalIAMSys.PolicyDBGet(svcAccount.ParentUser, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1024,7 +1025,7 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
|||||||
Name: svcAccount.Name,
|
Name: svcAccount.Name,
|
||||||
Description: svcAccount.Description,
|
Description: svcAccount.Description,
|
||||||
AccountStatus: svcAccount.Status,
|
AccountStatus: svcAccount.Status,
|
||||||
ImpliedPolicy: policy == nil,
|
ImpliedPolicy: sessionPolicy == nil,
|
||||||
Policy: string(policyJSON),
|
Policy: string(policyJSON),
|
||||||
Expiration: expiration,
|
Expiration: expiration,
|
||||||
}
|
}
|
||||||
@@ -1067,10 +1068,10 @@ func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Req
|
|||||||
// sender), check that the user has permissions.
|
// sender), check that the user has permissions.
|
||||||
user := r.Form.Get("user")
|
user := r.Form.Get("user")
|
||||||
if user != "" && user != cred.AccessKey {
|
if user != "" && user != cred.AccessKey {
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListServiceAccountsAdminAction,
|
Action: policy.ListServiceAccountsAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -1148,10 +1149,10 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
// since this is a delete call we shall allow it to be deleted if possible.
|
// since this is a delete call we shall allow it to be deleted if possible.
|
||||||
svcAccount, _, _ := globalIAMSys.GetServiceAccount(ctx, serviceAccount)
|
svcAccount, _, _ := globalIAMSys.GetServiceAccount(ctx, serviceAccount)
|
||||||
|
|
||||||
adminPrivilege := globalIAMSys.IsAllowed(iampolicy.Args{
|
adminPrivilege := globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.RemoveServiceAccountAdminAction,
|
Action: policy.RemoveServiceAccountAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -1192,7 +1193,7 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountInfoHandler returns usage
|
// AccountInfoHandler returns usage, permissions and other bucket metadata for incoming us
|
||||||
func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
@@ -1219,10 +1220,10 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
enablePrefixUsage := r.Form.Get("prefix-usage") == "true"
|
enablePrefixUsage := r.Form.Get("prefix-usage") == "true"
|
||||||
|
|
||||||
isAllowedAccess := func(bucketName string) (rd, wr bool) {
|
isAllowedAccess := func(bucketName string) (rd, wr bool) {
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
@@ -1232,10 +1233,10 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
rd = true
|
rd = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.GetBucketLocationAction,
|
Action: policy.GetBucketLocationAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
@@ -1245,10 +1246,10 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
rd = true
|
rd = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.PutObjectAction,
|
Action: policy.PutObjectAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
@@ -1261,12 +1262,30 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return rd, wr
|
return rd, wr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the latest calculated data usage
|
bucketStorageCache.Once.Do(func() {
|
||||||
dataUsageInfo, _ := loadDataUsageFromBackend(ctx, objectAPI)
|
// Set this to 10 secs since its enough, as scanner
|
||||||
|
// does not update the bucket usage values frequently.
|
||||||
|
bucketStorageCache.TTL = 10 * time.Second
|
||||||
|
|
||||||
|
// Rely on older value if usage loading fails from disk.
|
||||||
|
bucketStorageCache.Relax = true
|
||||||
|
bucketStorageCache.Update = func() (interface{}, error) {
|
||||||
|
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
return loadDataUsageFromBackend(ctx, objectAPI)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var dataUsageInfo DataUsageInfo
|
||||||
|
v, _ := bucketStorageCache.Get()
|
||||||
|
if v != nil {
|
||||||
|
dataUsageInfo, _ = v.(DataUsageInfo)
|
||||||
|
}
|
||||||
|
|
||||||
// If etcd, dns federation configured list buckets from etcd.
|
// If etcd, dns federation configured list buckets from etcd.
|
||||||
var buckets []BucketInfo
|
|
||||||
var err error
|
var err error
|
||||||
|
var buckets []BucketInfo
|
||||||
if globalDNSConfig != nil && globalBucketFederation {
|
if globalDNSConfig != nil && globalBucketFederation {
|
||||||
dnsBuckets, err := globalDNSConfig.List()
|
dnsBuckets, err := globalDNSConfig.List()
|
||||||
if err != nil && !IsErrIgnored(err,
|
if err != nil && !IsErrIgnored(err,
|
||||||
@@ -1285,7 +1304,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return buckets[i].Name < buckets[j].Name
|
return buckets[i].Name < buckets[j].Name
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
buckets, err = objectAPI.ListBuckets(ctx, BucketOptions{})
|
buckets, err = objectAPI.ListBuckets(ctx, BucketOptions{Cached: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1298,14 +1317,14 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
accountName = cred.ParentUser
|
accountName = cred.ParentUser
|
||||||
}
|
}
|
||||||
|
|
||||||
roleArn := iampolicy.Args{Claims: cred.Claims}.GetRoleArn()
|
roleArn := policy.Args{Claims: cred.Claims}.GetRoleArn()
|
||||||
policySetFromClaims, hasPolicyClaim := iampolicy.GetPoliciesFromClaims(cred.Claims, iamPolicyClaimNameOpenID())
|
policySetFromClaims, hasPolicyClaim := policy.GetPoliciesFromClaims(cred.Claims, iamPolicyClaimNameOpenID())
|
||||||
var effectivePolicy iampolicy.Policy
|
var effectivePolicy policy.Policy
|
||||||
|
|
||||||
var buf []byte
|
var buf []byte
|
||||||
switch {
|
switch {
|
||||||
case accountName == globalActiveCred.AccessKey:
|
case accountName == globalActiveCred.AccessKey:
|
||||||
for _, policy := range iampolicy.DefaultPolicies {
|
for _, policy := range policy.DefaultPolicies {
|
||||||
if policy.Name == "consoleAdmin" {
|
if policy.Name == "consoleAdmin" {
|
||||||
effectivePolicy = policy.Definition
|
effectivePolicy = policy.Definition
|
||||||
break
|
break
|
||||||
@@ -1417,7 +1436,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetPolicyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1463,7 +1482,7 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques
|
|||||||
func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListUserPoliciesAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1475,7 +1494,7 @@ func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newPolicies := make(map[string]iampolicy.Policy)
|
newPolicies := make(map[string]policy.Policy)
|
||||||
for name, p := range policies {
|
for name, p := range policies {
|
||||||
_, err = json.Marshal(p)
|
_, err = json.Marshal(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1494,7 +1513,7 @@ func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListUserPoliciesAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1505,7 +1524,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newPolicies := make(map[string]iampolicy.Policy)
|
newPolicies := make(map[string]policy.Policy)
|
||||||
for name, p := range policies {
|
for name, p := range policies {
|
||||||
_, err = json.Marshal(p)
|
_, err = json.Marshal(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1524,7 +1543,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DeletePolicyAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DeletePolicyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1550,7 +1569,7 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.CreatePolicyAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.CreatePolicyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1582,7 +1601,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iamPolicy, err := iampolicy.ParseConfig(bytes.NewReader(iamPolicyBytes))
|
iamPolicy, err := policy.ParseConfig(bytes.NewReader(iamPolicyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1614,7 +1633,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
|
|||||||
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.AttachPolicyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1688,7 +1707,7 @@ func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *ht
|
|||||||
|
|
||||||
// Check authorization.
|
// Check authorization.
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r,
|
objectAPI, cred := validateAdminReq(ctx, w, r,
|
||||||
iampolicy.ListGroupsAdminAction, iampolicy.ListUsersAdminAction, iampolicy.ListUserPoliciesAdminAction)
|
policy.ListGroupsAdminAction, policy.ListUsersAdminAction, policy.ListUserPoliciesAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1726,8 +1745,8 @@ func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *ht
|
|||||||
func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.UpdatePolicyAssociationAction,
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.UpdatePolicyAssociationAction,
|
||||||
iampolicy.AttachPolicyAdminAction)
|
policy.AttachPolicyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1826,7 +1845,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportIAMAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportIAMAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2096,7 +2115,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
var allPolicies map[string]iampolicy.Policy
|
var allPolicies map[string]policy.Policy
|
||||||
data, err = io.ReadAll(f)
|
data, err = io.ReadAll(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allPoliciesFile, ""), r.URL)
|
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allPoliciesFile, ""), r.URL)
|
||||||
@@ -2177,10 +2196,10 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
checkDenyOnly = true
|
checkDenyOnly = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.CreateUserAdminAction,
|
Action: policy.CreateUserAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -2257,10 +2276,10 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for user, svcAcctReq := range serviceAcctReqs {
|
for user, svcAcctReq := range serviceAcctReqs {
|
||||||
var sp *iampolicy.Policy
|
var sp *policy.Policy
|
||||||
var err error
|
var err error
|
||||||
if len(svcAcctReq.SessionPolicy) > 0 {
|
if len(svcAcctReq.SessionPolicy) > 0 {
|
||||||
sp, err = iampolicy.ParseConfig(bytes.NewReader(svcAcctReq.SessionPolicy))
|
sp, err = policy.ParseConfig(bytes.NewReader(svcAcctReq.SessionPolicy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
|
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
|
||||||
return
|
return
|
||||||
@@ -2271,10 +2290,10 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.CreateServiceAccountAdminAction,
|
Action: policy.CreateServiceAccountAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio-go/v7/pkg/signer"
|
"github.com/minio/minio-go/v7/pkg/signer"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+45
-41
@@ -55,9 +55,9 @@ import (
|
|||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/logger/message/log"
|
||||||
"github.com/minio/pkg/logger/message/log"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
xnet "github.com/minio/pkg/net"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/secure-io/sio-go"
|
"github.com/secure-io/sio-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ const (
|
|||||||
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerUpdateAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -252,11 +252,11 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
var objectAPI ObjectLayer
|
var objectAPI ObjectLayer
|
||||||
switch serviceSig {
|
switch serviceSig {
|
||||||
case serviceRestart:
|
case serviceRestart:
|
||||||
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceRestartAdminAction)
|
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceRestartAdminAction)
|
||||||
case serviceStop:
|
case serviceStop:
|
||||||
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceStopAdminAction)
|
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceStopAdminAction)
|
||||||
case serviceFreeze, serviceUnFreeze:
|
case serviceFreeze, serviceUnFreeze:
|
||||||
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceFreezeAdminAction)
|
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceFreezeAdminAction)
|
||||||
}
|
}
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
@@ -331,7 +331,7 @@ type ServerHTTPStats struct {
|
|||||||
func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.StorageInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.StorageInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -370,7 +370,7 @@ func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -479,7 +479,7 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DataUsageInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DataUsageInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -562,7 +562,7 @@ type PeerLocks struct {
|
|||||||
func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ForceUnlockAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ForceUnlockAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -597,7 +597,7 @@ func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.TopLocksAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.TopLocksAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -648,7 +648,7 @@ func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.R
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ProfilingAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -733,7 +733,7 @@ func (a adminAPIHandlers) ProfileHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ProfilingAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -828,7 +828,7 @@ func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *htt
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ProfilingAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -926,7 +926,7 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
|
|||||||
func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1110,7 +1110,7 @@ func getAggregatedBackgroundHealState(ctx context.Context, o ObjectLayer) (madmi
|
|||||||
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1131,7 +1131,7 @@ func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *
|
|||||||
func (a adminAPIHandlers) SitePerfHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SitePerfHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1179,7 +1179,7 @@ func (a adminAPIHandlers) SitePerfHandler(w http.ResponseWriter, r *http.Request
|
|||||||
func (a adminAPIHandlers) ClientDevNullExtraTime(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ClientDevNullExtraTime(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.BandwidthMonitorAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.BandwidthMonitorAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1196,7 +1196,7 @@ func (a adminAPIHandlers) ClientDevNull(w http.ResponseWriter, r *http.Request)
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
timeStart := time.Now()
|
timeStart := time.Now()
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.BandwidthMonitorAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.BandwidthMonitorAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1238,7 +1238,7 @@ func (a adminAPIHandlers) ClientDevNull(w http.ResponseWriter, r *http.Request)
|
|||||||
func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1284,7 +1284,7 @@ func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1445,7 +1445,7 @@ func validateObjPerfOptions(ctx context.Context, storageInfo madmin.StorageInfo,
|
|||||||
func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1566,7 +1566,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.TraceAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.TraceAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1637,7 +1637,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConsoleLogAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConsoleLogAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1718,7 +1718,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSCreateKeyAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSCreateKeyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1739,7 +1739,7 @@ func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Req
|
|||||||
func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSKeyStatusAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1776,7 +1776,7 @@ func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSKeyStatusAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2376,7 +2376,7 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
|
|||||||
func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2483,7 +2483,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ServerInfoAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ServerInfoAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -2627,12 +2627,12 @@ func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
|
|||||||
return loggerInfo, auditloggerInfo
|
return loggerInfo, auditloggerInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func embedFileInZip(zipWriter *zip.Writer, name string, data []byte) error {
|
func embedFileInZip(zipWriter *zip.Writer, name string, data []byte, fileMode os.FileMode) error {
|
||||||
// Send profiling data to zip as file
|
// Send profiling data to zip as file
|
||||||
header, zerr := zip.FileInfoHeader(dummyFileInfo{
|
header, zerr := zip.FileInfoHeader(dummyFileInfo{
|
||||||
name: name,
|
name: name,
|
||||||
size: int64(len(data)),
|
size: int64(len(data)),
|
||||||
mode: 0o600,
|
mode: fileMode,
|
||||||
modTime: UTCNow(),
|
modTime: UTCNow(),
|
||||||
isDir: false,
|
isDir: false,
|
||||||
sys: nil,
|
sys: nil,
|
||||||
@@ -2735,7 +2735,7 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.InspectDataAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.InspectDataAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -2863,7 +2863,7 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
defer inspectZipW.Close()
|
defer inspectZipW.Close()
|
||||||
|
|
||||||
if b := getClusterMetaInfo(ctx); len(b) > 0 {
|
if b := getClusterMetaInfo(ctx); len(b) > 0 {
|
||||||
logger.LogIf(ctx, embedFileInZip(inspectZipW, "cluster.info", b))
|
logger.LogIf(ctx, embedFileInZip(inspectZipW, "cluster.info", b, 0o600))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2927,11 +2927,16 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
sb.WriteString(pool.CmdLine)
|
sb.WriteString(pool.CmdLine)
|
||||||
}
|
}
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
logger.LogIf(ctx, embedFileInZip(inspectZipW, "inspect-input.txt", sb.Bytes()))
|
logger.LogIf(ctx, embedFileInZip(inspectZipW, "inspect-input.txt", sb.Bytes(), 0o600))
|
||||||
|
|
||||||
|
scheme := "https"
|
||||||
|
if !globalIsTLS {
|
||||||
|
scheme = "http"
|
||||||
|
}
|
||||||
|
|
||||||
// save MinIO start script to inspect command
|
// save MinIO start script to inspect command
|
||||||
var scrb bytes.Buffer
|
var scrb bytes.Buffer
|
||||||
scrb.WriteString(`#!/usr/bin/env bash
|
fmt.Fprintf(&scrb, `#!/usr/bin/env bash
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
for file in $(ls -1); do
|
for file in $(ls -1); do
|
||||||
@@ -2940,10 +2945,10 @@ function main() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Read content of inspect-input.txt
|
# Read content of inspect-input.txt
|
||||||
MINIO_OPTS=$(grep "Server command line args" <./inspect-input.txt | sed "s/Server command line args: //g" | sed -r "s#https:\/\/#\.\/#g")
|
MINIO_OPTS=$(grep "Server command line args" <./inspect-input.txt | sed "s/Server command line args: //g" | sed -r "s#%s:\/\/#\.\/#g")
|
||||||
|
|
||||||
# Start MinIO instance using the options
|
# Start MinIO instance using the options
|
||||||
START_CMD="CI=on MINIO_ROOT_USER=minio MINIO_ROOT_PASSWORD=minio123 minio server ${MINIO_OPTS} &"
|
START_CMD="CI=on _MINIO_AUTO_DRIVE_HEALING=off minio server ${MINIO_OPTS} &"
|
||||||
echo
|
echo
|
||||||
echo "Starting MinIO instance: ${START_CMD}"
|
echo "Starting MinIO instance: ${START_CMD}"
|
||||||
echo
|
echo
|
||||||
@@ -2955,9 +2960,8 @@ function main() {
|
|||||||
sleep 10
|
sleep 10
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"`,
|
main "$@"`, scheme)
|
||||||
)
|
logger.LogIf(ctx, embedFileInZip(inspectZipW, "start-minio.sh", scrb.Bytes(), 0o755))
|
||||||
logger.LogIf(ctx, embedFileInZip(inspectZipW, "start-minio.sh", scrb.Bytes()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSubnetAdminPublicKey() []byte {
|
func getSubnetAdminPublicKey() []byte {
|
||||||
|
|||||||
@@ -683,13 +683,6 @@ func (h *healSequence) healSequenceStart(objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) logHeal(healType madmin.HealItemType) {
|
|
||||||
h.mutex.Lock()
|
|
||||||
h.scannedItemsMap[healType]++
|
|
||||||
h.lastHealActivity = UTCNow()
|
|
||||||
h.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
||||||
// Send heal request
|
// Send heal request
|
||||||
task := healTask{
|
task := healTask{
|
||||||
|
|||||||
+1
-3
@@ -47,7 +47,7 @@ import (
|
|||||||
levent "github.com/minio/minio/internal/config/lambda/event"
|
levent "github.com/minio/minio/internal/config/lambda/event"
|
||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIError structure
|
// APIError structure
|
||||||
@@ -2236,8 +2236,6 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrSlowDownWrite
|
apiErr = ErrSlowDownWrite
|
||||||
case InsufficientReadQuorum:
|
case InsufficientReadQuorum:
|
||||||
apiErr = ErrSlowDownRead
|
apiErr = ErrSlowDownRead
|
||||||
case InvalidMarkerPrefixCombination:
|
|
||||||
apiErr = ErrNotImplemented
|
|
||||||
case InvalidUploadIDKeyCombination:
|
case InvalidUploadIDKeyCombination:
|
||||||
apiErr = ErrNotImplemented
|
apiErr = ErrNotImplemented
|
||||||
case MalformedUploadID:
|
case MalformedUploadID:
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ var toAPIErrorTests = []struct {
|
|||||||
{err: InvalidPart{}, errCode: ErrInvalidPart},
|
{err: InvalidPart{}, errCode: ErrInvalidPart},
|
||||||
{err: InsufficientReadQuorum{}, errCode: ErrSlowDownRead},
|
{err: InsufficientReadQuorum{}, errCode: ErrSlowDownRead},
|
||||||
{err: InsufficientWriteQuorum{}, errCode: ErrSlowDownWrite},
|
{err: InsufficientWriteQuorum{}, errCode: ErrSlowDownWrite},
|
||||||
{err: InvalidMarkerPrefixCombination{}, errCode: ErrNotImplemented},
|
|
||||||
{err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented},
|
{err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented},
|
||||||
{err: MalformedUploadID{}, errCode: ErrNoSuchUpload},
|
{err: MalformedUploadID{}, errCode: ErrNoSuchUpload},
|
||||||
{err: PartTooSmall{}, errCode: ErrEntityTooSmall},
|
{err: PartTooSmall{}, errCode: ErrEntityTooSmall},
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
xxml "github.com/minio/xxml"
|
xxml "github.com/minio/xxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+23
-24
@@ -41,8 +41,7 @@ import (
|
|||||||
xjwt "github.com/minio/minio/internal/jwt"
|
xjwt "github.com/minio/minio/internal/jwt"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/mcontext"
|
"github.com/minio/minio/internal/mcontext"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify if request has JWT.
|
// Verify if request has JWT.
|
||||||
@@ -186,15 +185,15 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
|
|||||||
// checkAdminRequestAuth checks for authentication and authorization for the incoming
|
// checkAdminRequestAuth checks for authentication and authorization for the incoming
|
||||||
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
|
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
|
||||||
// are automatically rejected.
|
// are automatically rejected.
|
||||||
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action policy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
||||||
cred, owner, s3Err := validateAdminSignature(ctx, r, region)
|
cred, owner, s3Err := validateAdminSignature(ctx, r, region)
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, s3Err
|
return cred, s3Err
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(action),
|
Action: policy.Action(action),
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -248,7 +247,7 @@ func getClaimsFromTokenWithSecret(token, secret string) (map[string]interface{},
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if a session policy is set. If so, decode it here.
|
// Check if a session policy is set. If so, decode it here.
|
||||||
sp, spok := claims.Lookup(iampolicy.SessionPolicyName)
|
sp, spok := claims.Lookup(policy.SessionPolicyName)
|
||||||
if spok {
|
if spok {
|
||||||
// Looks like subpolicy is set and is a string, if set then its
|
// Looks like subpolicy is set and is a string, if set then its
|
||||||
// base64 encoded, decode it. Decoding fails reject such
|
// base64 encoded, decode it. Decoding fails reject such
|
||||||
@@ -413,7 +412,7 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
|
|
||||||
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
||||||
// Anonymous checks are not meant for ListAllBuckets action
|
// Anonymous checks are not meant for ListAllBuckets action
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: action,
|
Action: action,
|
||||||
@@ -429,7 +428,7 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
if action == policy.ListBucketVersionsAction {
|
if action == policy.ListBucketVersionsAction {
|
||||||
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
||||||
// verify as a fallback.
|
// verify as a fallback.
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: policy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
@@ -446,10 +445,10 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
if action == policy.DeleteObjectAction && versionID != "" {
|
if action == policy.DeleteObjectAction && versionID != "" {
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(policy.DeleteObjectVersionAction),
|
Action: policy.Action(policy.DeleteObjectVersionAction),
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
@@ -460,10 +459,10 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(action),
|
Action: action,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
@@ -477,10 +476,10 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
if action == policy.ListBucketVersionsAction {
|
if action == policy.ListBucketVersionsAction {
|
||||||
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
||||||
// verify as a fallback.
|
// verify as a fallback.
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
@@ -568,7 +567,7 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
|||||||
|
|
||||||
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
||||||
// The verification happens implicit during reading.
|
// The verification happens implicit during reading.
|
||||||
reader, err := hash.NewReader(r.Body, -1, clientETag.String(), hex.EncodeToString(contentSHA256), -1)
|
reader, err := hash.NewReader(ctx, r.Body, -1, clientETag.String(), hex.EncodeToString(contentSHA256), -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toAPIErrorCode(ctx, err)
|
return toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
@@ -696,10 +695,10 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
|
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
|
||||||
}
|
}
|
||||||
if retMode == objectlock.RetGovernance && byPassSet {
|
if retMode == objectlock.RetGovernance && byPassSet {
|
||||||
byPassSet = globalIAMSys.IsAllowed(iampolicy.Args{
|
byPassSet = globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.BypassGovernanceRetentionAction,
|
Action: policy.BypassGovernanceRetentionAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
ConditionValues: conditions,
|
ConditionValues: conditions,
|
||||||
@@ -707,10 +706,10 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.PutObjectRetentionAction,
|
Action: policy.PutObjectRetentionAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: conditions,
|
ConditionValues: conditions,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
@@ -728,7 +727,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
// isPutActionAllowed - check if PUT operation is allowed on the resource, this
|
// isPutActionAllowed - check if PUT operation is allowed on the resource, this
|
||||||
// call verifies bucket policies and IAM policies, supports multi user
|
// call verifies bucket policies and IAM policies, supports multi user
|
||||||
// checks etc.
|
// checks etc.
|
||||||
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action iampolicy.Action) (s3Err APIErrorCode) {
|
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||||
var cred auth.Credentials
|
var cred auth.Credentials
|
||||||
var owner bool
|
var owner bool
|
||||||
region := globalSite.Region
|
region := globalSite.Region
|
||||||
@@ -751,17 +750,17 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
|
|||||||
// Do not check for PutObjectRetentionAction permission,
|
// Do not check for PutObjectRetentionAction permission,
|
||||||
// if mode and retain until date are not set.
|
// if mode and retain until date are not set.
|
||||||
// Can happen when bucket has default lock config set
|
// Can happen when bucket has default lock config set
|
||||||
if action == iampolicy.PutObjectRetentionAction &&
|
if action == policy.PutObjectRetentionAction &&
|
||||||
r.Header.Get(xhttp.AmzObjectLockMode) == "" &&
|
r.Header.Get(xhttp.AmzObjectLockMode) == "" &&
|
||||||
r.Header.Get(xhttp.AmzObjectLockRetainUntilDate) == "" {
|
r.Header.Get(xhttp.AmzObjectLockRetainUntilDate) == "" {
|
||||||
return ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
if cred.AccessKey == "" {
|
if cred.AccessKey == "" {
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: policy.Action(action),
|
Action: action,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
@@ -772,7 +771,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
|
|||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: action,
|
Action: action,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nullReader struct{}
|
type nullReader struct{}
|
||||||
@@ -443,7 +443,7 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
|
|||||||
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||||
}
|
}
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
|
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, policy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
|
||||||
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// healTask represents what to heal along with options
|
// healTask represents what to heal along with options
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -347,7 +347,7 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
||||||
|
|
||||||
if env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn {
|
if env.Get("_MINIO_AUTO_DRIVE_HEALING", config.EnableOn) == config.EnableOn || env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn {
|
||||||
go monitorLocalDisksAndHeal(ctx, z)
|
go monitorLocalDisksAndHeal(ctx, z)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-38
@@ -28,7 +28,6 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -38,6 +37,7 @@ import (
|
|||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/lithammer/shortuuid/v4"
|
"github.com/lithammer/shortuuid/v4"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
miniogo "github.com/minio/minio-go/v7"
|
miniogo "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||||
@@ -47,10 +47,10 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/ioutil"
|
"github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/v2/console"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/minio/pkg/workers"
|
"github.com/minio/pkg/v2/workers"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
|||||||
srcObject := srcObjInfo.Name
|
srcObject := srcObjInfo.Name
|
||||||
tgtObject := srcObjInfo.Name
|
tgtObject := srcObjInfo.Name
|
||||||
if r.Target.Prefix != "" {
|
if r.Target.Prefix != "" {
|
||||||
tgtObject = path.Join(r.Target.Prefix, srcObjInfo.Name)
|
tgtObject = pathJoin(r.Target.Prefix, srcObjInfo.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
versionID := srcObjInfo.VersionID
|
versionID := srcObjInfo.VersionID
|
||||||
@@ -167,7 +167,7 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
|||||||
}
|
}
|
||||||
defer rd.Close()
|
defer rd.Close()
|
||||||
|
|
||||||
hr, err := hash.NewReader(rd, objInfo.Size, "", "", objInfo.Size)
|
hr, err := hash.NewReader(ctx, rd, objInfo.Size, "", "", objInfo.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,7 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
|
|||||||
srcObject := srcObjInfo.Name
|
srcObject := srcObjInfo.Name
|
||||||
tgtObject := srcObjInfo.Name
|
tgtObject := srcObjInfo.Name
|
||||||
if r.Target.Prefix != "" {
|
if r.Target.Prefix != "" {
|
||||||
tgtObject = path.Join(r.Target.Prefix, srcObjInfo.Name)
|
tgtObject = pathJoin(r.Target.Prefix, srcObjInfo.Name)
|
||||||
}
|
}
|
||||||
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
|
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
|
||||||
opts.VersionID = ""
|
opts.VersionID = ""
|
||||||
@@ -230,7 +230,7 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
|
|||||||
}
|
}
|
||||||
defer rd.Close()
|
defer rd.Close()
|
||||||
|
|
||||||
hr, err = hash.NewReader(io.LimitReader(rd, objInfo.Size), objInfo.Size, "", "", objInfo.Size)
|
hr, err = hash.NewReader(ctx, io.LimitReader(rd, objInfo.Size), objInfo.Size, "", "", objInfo.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -270,6 +270,10 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
}
|
}
|
||||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
|
isTags := len(r.Flags.Filter.Tags) != 0
|
||||||
|
isMetadata := len(r.Flags.Filter.Metadata) != 0
|
||||||
|
isStorageClassOnly := len(r.Flags.Filter.Metadata) == 1 && strings.EqualFold(r.Flags.Filter.Metadata[0].Key, xhttp.AmzStorageClass)
|
||||||
|
|
||||||
skip := func(oi ObjectInfo) (ok bool) {
|
skip := func(oi ObjectInfo) (ok bool) {
|
||||||
if r.Flags.Filter.OlderThan > 0 && time.Since(oi.ModTime) < r.Flags.Filter.OlderThan {
|
if r.Flags.Filter.OlderThan > 0 && time.Since(oi.ModTime) < r.Flags.Filter.OlderThan {
|
||||||
// skip all objects that are newer than specified older duration
|
// skip all objects that are newer than specified older duration
|
||||||
@@ -290,7 +294,8 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
// skip all objects that are created after the specified time.
|
// skip all objects that are created after the specified time.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if len(r.Flags.Filter.Tags) > 0 {
|
|
||||||
|
if isTags {
|
||||||
// Only parse object tags if tags filter is specified.
|
// Only parse object tags if tags filter is specified.
|
||||||
tagMap := map[string]string{}
|
tagMap := map[string]string{}
|
||||||
tagStr := oi.UserTags
|
tagStr := oi.UserTags
|
||||||
@@ -313,23 +318,19 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.Flags.Filter.Metadata) > 0 {
|
for _, kv := range r.Flags.Filter.Metadata {
|
||||||
for _, kv := range r.Flags.Filter.Metadata {
|
for k, v := range oi.UserDefined {
|
||||||
for k, v := range oi.UserDefined {
|
if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) {
|
||||||
if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) {
|
continue
|
||||||
continue
|
}
|
||||||
}
|
// We only need to match x-amz-meta or standardHeaders
|
||||||
// We only need to match x-amz-meta or standardHeaders
|
if kv.Match(BatchJobKV{Key: k, Value: v}) {
|
||||||
if kv.Match(BatchJobKV{Key: k, Value: v}) {
|
return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// None of the provided metadata filters match skip the object.
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// None of the provided filters match
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,17 +385,32 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
for obj := range objInfoCh {
|
for obj := range objInfoCh {
|
||||||
oi := toObjectInfo(r.Source.Bucket, obj.Key, obj)
|
oi := toObjectInfo(r.Source.Bucket, obj.Key, obj)
|
||||||
if !minioSrc {
|
if !minioSrc {
|
||||||
oi2, err := c.StatObject(ctx, r.Source.Bucket, obj.Key, miniogo.StatObjectOptions{})
|
// Check if metadata filter was requested and it is expected to have
|
||||||
if err == nil {
|
// all user metadata or just storageClass. If its only storageClass
|
||||||
oi = toObjectInfo(r.Source.Bucket, obj.Key, oi2)
|
// List() already returns relevant information for filter to be applied.
|
||||||
} else {
|
if isMetadata && !isStorageClassOnly {
|
||||||
if isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) ||
|
oi2, err := c.StatObject(ctx, r.Source.Bucket, obj.Key, miniogo.StatObjectOptions{})
|
||||||
isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) {
|
if err == nil {
|
||||||
|
oi = toObjectInfo(r.Source.Bucket, obj.Key, oi2)
|
||||||
|
} else {
|
||||||
|
if !isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) &&
|
||||||
|
!isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isTags {
|
||||||
|
tags, err := c.GetObjectTagging(ctx, r.Source.Bucket, obj.Key, minio.GetObjectTaggingOptions{})
|
||||||
|
if err == nil {
|
||||||
|
oi.UserTags = tags.String()
|
||||||
|
} else {
|
||||||
|
if !isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) &&
|
||||||
|
!isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
cancel()
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if skip(oi) {
|
if skip(oi) {
|
||||||
@@ -482,10 +498,12 @@ func toObjectInfo(bucket, object string, objInfo miniogo.ObjectInfo) ObjectInfo
|
|||||||
ReplicationStatusInternal: objInfo.ReplicationStatus,
|
ReplicationStatusInternal: objInfo.ReplicationStatus,
|
||||||
UserTags: tags.String(),
|
UserTags: tags.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
oi.UserDefined = make(map[string]string, len(objInfo.Metadata))
|
oi.UserDefined = make(map[string]string, len(objInfo.Metadata))
|
||||||
for k, v := range objInfo.Metadata {
|
for k, v := range objInfo.Metadata {
|
||||||
oi.UserDefined[k] = v[0]
|
oi.UserDefined[k] = v[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
ce, ok := oi.UserDefined[xhttp.ContentEncoding]
|
ce, ok := oi.UserDefined[xhttp.ContentEncoding]
|
||||||
if !ok {
|
if !ok {
|
||||||
ce, ok = oi.UserDefined[strings.ToLower(xhttp.ContentEncoding)]
|
ce, ok = oi.UserDefined[strings.ToLower(xhttp.ContentEncoding)]
|
||||||
@@ -493,6 +511,12 @@ func toObjectInfo(bucket, object string, objInfo miniogo.ObjectInfo) ObjectInfo
|
|||||||
if ok {
|
if ok {
|
||||||
oi.ContentEncoding = ce
|
oi.ContentEncoding = ce
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, ok = oi.UserDefined[xhttp.AmzStorageClass]
|
||||||
|
if !ok {
|
||||||
|
oi.UserDefined[xhttp.AmzStorageClass] = objInfo.StorageClass
|
||||||
|
}
|
||||||
|
|
||||||
return oi
|
return oi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -822,7 +846,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
}
|
}
|
||||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
skip := func(info FileInfo) (ok bool) {
|
selectObj := func(info FileInfo) (ok bool) {
|
||||||
if r.Flags.Filter.OlderThan > 0 && time.Since(info.ModTime) < r.Flags.Filter.OlderThan {
|
if r.Flags.Filter.OlderThan > 0 && time.Since(info.ModTime) < r.Flags.Filter.OlderThan {
|
||||||
// skip all objects that are newer than specified older duration
|
// skip all objects that are newer than specified older duration
|
||||||
return false
|
return false
|
||||||
@@ -932,7 +956,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
results := make(chan ObjectInfo, 100)
|
results := make(chan ObjectInfo, 100)
|
||||||
if err := api.Walk(ctx, r.Source.Bucket, r.Source.Prefix, results, ObjectOptions{
|
if err := api.Walk(ctx, r.Source.Bucket, r.Source.Prefix, results, ObjectOptions{
|
||||||
WalkMarker: lastObject,
|
WalkMarker: lastObject,
|
||||||
WalkFilter: skip,
|
WalkFilter: selectObj,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
// Do not need to retry if we can't list objects on source.
|
// Do not need to retry if we can't list objects on source.
|
||||||
@@ -1258,7 +1282,7 @@ func batchReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (p
|
|||||||
func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListBatchJobsAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListBatchJobsAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1308,7 +1332,7 @@ var errNoSuchJob = errors.New("no such job")
|
|||||||
func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DescribeBatchJobAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DescribeBatchJobAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1343,7 +1367,7 @@ func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Reques
|
|||||||
func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, creds := validateAdminReq(ctx, w, r, iampolicy.StartBatchJobAction)
|
objectAPI, creds := validateAdminReq(ctx, w, r, policy.StartBatchJobAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1397,7 +1421,7 @@ func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request)
|
|||||||
func (a adminAPIHandlers) CancelBatchJob(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) CancelBatchJob(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.CancelBatchJobAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.CancelBatchJobAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate msgp -file $GOFILE
|
//go:generate msgp -file $GOFILE
|
||||||
|
|||||||
+2
-2
@@ -34,8 +34,8 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
"github.com/minio/pkg/workers"
|
"github.com/minio/pkg/v2/workers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// keyrotate:
|
// keyrotate:
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/rest"
|
"github.com/minio/minio/internal/rest"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+20
-18
@@ -60,9 +60,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/ioutil"
|
"github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -367,10 +366,10 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
// Use the following trick to filter in place
|
// Use the following trick to filter in place
|
||||||
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
|
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
|
||||||
for _, bucketInfo := range bucketsInfo {
|
for _, bucketInfo := range bucketsInfo {
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucketInfo.Name,
|
BucketName: bucketInfo.Name,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
@@ -379,10 +378,10 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
}) {
|
}) {
|
||||||
bucketsInfo[n] = bucketInfo
|
bucketsInfo[n] = bucketInfo
|
||||||
n++
|
n++
|
||||||
} else if globalIAMSys.IsAllowed(iampolicy.Args{
|
} else if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.GetBucketLocationAction,
|
Action: policy.GetBucketLocationAction,
|
||||||
BucketName: bucketInfo.Name,
|
BucketName: bucketInfo.Name,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
@@ -770,8 +769,8 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
if objectLockEnabled {
|
if objectLockEnabled {
|
||||||
// Creating a bucket with locking requires the user having more permissions
|
// Creating a bucket with locking requires the user having more permissions
|
||||||
for _, action := range []iampolicy.Action{iampolicy.PutBucketObjectLockConfigurationAction, iampolicy.PutBucketVersioningAction} {
|
for _, action := range []policy.Action{policy.PutBucketObjectLockConfigurationAction, policy.PutBucketVersioningAction} {
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: action,
|
Action: action,
|
||||||
@@ -1103,10 +1102,10 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
if len(fanOutEntries) > 0 {
|
if len(fanOutEntries) > 0 {
|
||||||
// Once signature is validated, check if the user has
|
// Once signature is validated, check if the user has
|
||||||
// explicit permissions for the user.
|
// explicit permissions for the user.
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.PutObjectFanOutAction,
|
Action: policy.PutObjectFanOutAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
@@ -1119,10 +1118,10 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
} else {
|
} else {
|
||||||
// Once signature is validated, check if the user has
|
// Once signature is validated, check if the user has
|
||||||
// explicit permissions for the user.
|
// explicit permissions for the user.
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.PutObjectAction,
|
Action: policy.PutObjectAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
@@ -1140,7 +1139,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hashReader, err := hash.NewReader(reader, fileSize, "", "", fileSize)
|
hashReader, err := hash.NewReader(ctx, reader, fileSize, "", "", fileSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
@@ -1255,7 +1254,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// do not try to verify encrypted content/
|
// do not try to verify encrypted content/
|
||||||
hashReader, err = hash.NewReader(reader, -1, "", "", -1)
|
hashReader, err = hash.NewReader(ctx, reader, -1, "", "", -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1290,7 +1289,10 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
// instead of "copying" from source, we need the stream to be seekable
|
// instead of "copying" from source, we need the stream to be seekable
|
||||||
// to ensure that we can make fan-out calls concurrently.
|
// to ensure that we can make fan-out calls concurrently.
|
||||||
buf := bytebufferpool.Get()
|
buf := bytebufferpool.Get()
|
||||||
defer bytebufferpool.Put(buf)
|
defer func() {
|
||||||
|
buf.Reset()
|
||||||
|
bytebufferpool.Put(buf)
|
||||||
|
}()
|
||||||
|
|
||||||
md5w := md5.New()
|
md5w := md5.New()
|
||||||
|
|
||||||
@@ -1498,7 +1500,7 @@ func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if anonymous (non-owner) has access to list objects.
|
// Check if anonymous (non-owner) has access to list objects.
|
||||||
readable := globalPolicySys.IsAllowed(policy.Args{
|
readable := globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
Action: policy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
||||||
@@ -1506,7 +1508,7 @@ func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter,
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Check if anonymous (non-owner) has access to upload objects.
|
// Check if anonymous (non-owner) has access to upload objects.
|
||||||
writable := globalPolicySys.IsAllowed(policy.Args{
|
writable := globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
Action: policy.PutObjectAction,
|
Action: policy.PutObjectAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/s3select"
|
"github.com/minio/minio/internal/s3select"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
"github.com/minio/pkg/workers"
|
"github.com/minio/pkg/v2/workers"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
|
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
||||||
@@ -310,7 +310,7 @@ func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) {
|
|||||||
|
|
||||||
// GetPolicyConfig returns configured bucket policy
|
// GetPolicyConfig returns configured bucket policy
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, time.Time, error) {
|
func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.BucketPolicy, time.Time, error) {
|
||||||
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/fips"
|
"github.com/minio/minio/internal/fips"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/minio/sio"
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ type BucketMetadata struct {
|
|||||||
LifecycleConfigUpdatedAt time.Time
|
LifecycleConfigUpdatedAt time.Time
|
||||||
|
|
||||||
// Unexported fields. Must be updated atomically.
|
// Unexported fields. Must be updated atomically.
|
||||||
policyConfig *policy.Policy
|
policyConfig *policy.BucketPolicy
|
||||||
notificationConfig *event.Config
|
notificationConfig *event.Config
|
||||||
lifecycleConfig *lifecycle.Lifecycle
|
lifecycleConfig *lifecycle.Lifecycle
|
||||||
objectLockConfig *objectlock.Config
|
objectLockConfig *objectlock.Config
|
||||||
@@ -217,7 +217,7 @@ func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket strin
|
|||||||
// The first error encountered is returned.
|
// The first error encountered is returned.
|
||||||
func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLayer) (err error) {
|
func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLayer) (err error) {
|
||||||
if len(b.PolicyConfigJSON) != 0 {
|
if len(b.PolicyConfigJSON) != 0 {
|
||||||
b.policyConfig, err = policy.ParseConfig(bytes.NewReader(b.PolicyConfigJSON), b.Name)
|
b.policyConfig, err = policy.ParseBucketPolicyConfig(bytes.NewReader(b.PolicyConfigJSON), b.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketObjectLockSys - map of bucket and retention configuration.
|
// BucketObjectLockSys - map of bucket and retention configuration.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -84,7 +84,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
bucketPolicy, err := policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, APIError{
|
writeErrorResponse(ctx, w, APIError{
|
||||||
Code: "MalformedPolicy",
|
Code: "MalformedPolicy",
|
||||||
|
|||||||
@@ -29,31 +29,31 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/minio/pkg/bucket/policy/condition"
|
"github.com/minio/pkg/v2/policy/condition"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getAnonReadOnlyBucketPolicy(bucketName string) *policy.Policy {
|
func getAnonReadOnlyBucketPolicy(bucketName string) *policy.BucketPolicy {
|
||||||
return &policy.Policy{
|
return &policy.BucketPolicy{
|
||||||
Version: policy.DefaultVersion,
|
Version: policy.DefaultVersion,
|
||||||
Statements: []policy.Statement{
|
Statements: []policy.BPStatement{
|
||||||
policy.NewStatement(
|
policy.NewBPStatement(
|
||||||
"",
|
"",
|
||||||
policy.Allow,
|
policy.Allow,
|
||||||
policy.NewPrincipal("*"),
|
policy.NewPrincipal("*"),
|
||||||
policy.NewActionSet(policy.GetBucketLocationAction, policy.ListBucketAction),
|
policy.NewActionSet(policy.GetBucketLocationAction, policy.ListBucketAction),
|
||||||
policy.NewResourceSet(policy.NewResource(bucketName, "")),
|
policy.NewResourceSet(policy.NewResource(bucketName)),
|
||||||
condition.NewFunctions(),
|
condition.NewFunctions(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAnonWriteOnlyBucketPolicy(bucketName string) *policy.Policy {
|
func getAnonWriteOnlyBucketPolicy(bucketName string) *policy.BucketPolicy {
|
||||||
return &policy.Policy{
|
return &policy.BucketPolicy{
|
||||||
Version: policy.DefaultVersion,
|
Version: policy.DefaultVersion,
|
||||||
Statements: []policy.Statement{
|
Statements: []policy.BPStatement{
|
||||||
policy.NewStatement(
|
policy.NewBPStatement(
|
||||||
"",
|
"",
|
||||||
policy.Allow,
|
policy.Allow,
|
||||||
policy.NewPrincipal("*"),
|
policy.NewPrincipal("*"),
|
||||||
@@ -61,34 +61,34 @@ func getAnonWriteOnlyBucketPolicy(bucketName string) *policy.Policy {
|
|||||||
policy.GetBucketLocationAction,
|
policy.GetBucketLocationAction,
|
||||||
policy.ListBucketMultipartUploadsAction,
|
policy.ListBucketMultipartUploadsAction,
|
||||||
),
|
),
|
||||||
policy.NewResourceSet(policy.NewResource(bucketName, "")),
|
policy.NewResourceSet(policy.NewResource(bucketName)),
|
||||||
condition.NewFunctions(),
|
condition.NewFunctions(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAnonReadOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
|
func getAnonReadOnlyObjectPolicy(bucketName, prefix string) *policy.BucketPolicy {
|
||||||
return &policy.Policy{
|
return &policy.BucketPolicy{
|
||||||
Version: policy.DefaultVersion,
|
Version: policy.DefaultVersion,
|
||||||
Statements: []policy.Statement{
|
Statements: []policy.BPStatement{
|
||||||
policy.NewStatement(
|
policy.NewBPStatement(
|
||||||
"",
|
"",
|
||||||
policy.Allow,
|
policy.Allow,
|
||||||
policy.NewPrincipal("*"),
|
policy.NewPrincipal("*"),
|
||||||
policy.NewActionSet(policy.GetObjectAction),
|
policy.NewActionSet(policy.GetObjectAction),
|
||||||
policy.NewResourceSet(policy.NewResource(bucketName, prefix)),
|
policy.NewResourceSet(policy.NewResource(bucketName+"/"+prefix)),
|
||||||
condition.NewFunctions(),
|
condition.NewFunctions(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
|
func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.BucketPolicy {
|
||||||
return &policy.Policy{
|
return &policy.BucketPolicy{
|
||||||
Version: policy.DefaultVersion,
|
Version: policy.DefaultVersion,
|
||||||
Statements: []policy.Statement{
|
Statements: []policy.BPStatement{
|
||||||
policy.NewStatement(
|
policy.NewBPStatement(
|
||||||
"",
|
"",
|
||||||
policy.Allow,
|
policy.Allow,
|
||||||
policy.NewPrincipal("*"),
|
policy.NewPrincipal("*"),
|
||||||
@@ -98,7 +98,7 @@ func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
|
|||||||
policy.ListMultipartUploadPartsAction,
|
policy.ListMultipartUploadPartsAction,
|
||||||
policy.PutObjectAction,
|
policy.PutObjectAction,
|
||||||
),
|
),
|
||||||
policy.NewResourceSet(policy.NewResource(bucketName, prefix)),
|
policy.NewResourceSet(policy.NewResource(bucketName+"/"+prefix)),
|
||||||
condition.NewFunctions(),
|
condition.NewFunctions(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -493,13 +493,13 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
|
|
||||||
if recV4.Code != testCase.expectedRespStatus {
|
if recV4.Code != testCase.expectedRespStatus {
|
||||||
// Verify whether the bucket policy fetched is same as the one inserted.
|
// Verify whether the bucket policy fetched is same as the one inserted.
|
||||||
var expectedPolicy *policy.Policy
|
var expectedPolicy *policy.BucketPolicy
|
||||||
expectedPolicy, err = policy.ParseConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
|
expectedPolicy, err = policy.ParseBucketPolicyConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error. %v", err)
|
t.Fatalf("unexpected error. %v", err)
|
||||||
}
|
}
|
||||||
var gotPolicy *policy.Policy
|
var gotPolicy *policy.BucketPolicy
|
||||||
gotPolicy, err = policy.ParseConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
|
gotPolicy, err = policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error. %v", err)
|
t.Fatalf("unexpected error. %v", err)
|
||||||
}
|
}
|
||||||
@@ -530,11 +530,11 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
}
|
}
|
||||||
if recV2.Code == http.StatusOK {
|
if recV2.Code == http.StatusOK {
|
||||||
// Verify whether the bucket policy fetched is same as the one inserted.
|
// Verify whether the bucket policy fetched is same as the one inserted.
|
||||||
expectedPolicy, err := policy.ParseConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
|
expectedPolicy, err := policy.ParseBucketPolicyConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error. %v", err)
|
t.Fatalf("unexpected error. %v", err)
|
||||||
}
|
}
|
||||||
gotPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
|
gotPolicy, err := policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error. %v", err)
|
t.Fatalf("unexpected error. %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,20 +32,20 @@ import (
|
|||||||
"github.com/minio/minio/internal/handlers"
|
"github.com/minio/minio/internal/handlers"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PolicySys - policy subsystem.
|
// PolicySys - policy subsystem.
|
||||||
type PolicySys struct{}
|
type PolicySys struct{}
|
||||||
|
|
||||||
// Get returns stored bucket policy
|
// Get returns stored bucket policy
|
||||||
func (sys *PolicySys) Get(bucket string) (*policy.Policy, error) {
|
func (sys *PolicySys) Get(bucket string) (*policy.BucketPolicy, error) {
|
||||||
policy, _, err := globalBucketMetadataSys.GetPolicyConfig(bucket)
|
policy, _, err := globalBucketMetadataSys.GetPolicyConfig(bucket)
|
||||||
return policy, err
|
return policy, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
||||||
func (sys *PolicySys) IsAllowed(args policy.Args) bool {
|
func (sys *PolicySys) IsAllowed(args policy.BucketPolicyArgs) bool {
|
||||||
p, err := sys.Get(args.BucketName)
|
p, err := sys.Get(args.BucketName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return p.IsAllowed(args)
|
return p.IsAllowed(args)
|
||||||
@@ -236,7 +236,7 @@ func getConditionValues(r *http.Request, lc string, cred auth.Credentials) map[s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PolicyToBucketAccessPolicy converts a MinIO policy into a minio-go policy data structure.
|
// PolicyToBucketAccessPolicy converts a MinIO policy into a minio-go policy data structure.
|
||||||
func PolicyToBucketAccessPolicy(bucketPolicy *policy.Policy) (*miniogopolicy.BucketAccessPolicy, error) {
|
func PolicyToBucketAccessPolicy(bucketPolicy *policy.BucketPolicy) (*miniogopolicy.BucketAccessPolicy, error) {
|
||||||
// Return empty BucketAccessPolicy for empty bucket policy.
|
// Return empty BucketAccessPolicy for empty bucket policy.
|
||||||
if bucketPolicy == nil {
|
if bucketPolicy == nil {
|
||||||
return &miniogopolicy.BucketAccessPolicy{Version: policy.DefaultVersion}, nil
|
return &miniogopolicy.BucketAccessPolicy{Version: policy.DefaultVersion}, nil
|
||||||
@@ -258,15 +258,15 @@ func PolicyToBucketAccessPolicy(bucketPolicy *policy.Policy) (*miniogopolicy.Buc
|
|||||||
return &policyInfo, nil
|
return &policyInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BucketAccessPolicyToPolicy - converts minio-go/policy.BucketAccessPolicy to policy.Policy.
|
// BucketAccessPolicyToPolicy - converts minio-go/policy.BucketAccessPolicy to policy.BucketPolicy.
|
||||||
func BucketAccessPolicyToPolicy(policyInfo *miniogopolicy.BucketAccessPolicy) (*policy.Policy, error) {
|
func BucketAccessPolicyToPolicy(policyInfo *miniogopolicy.BucketAccessPolicy) (*policy.BucketPolicy, error) {
|
||||||
data, err := json.Marshal(policyInfo)
|
data, err := json.Marshal(policyInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// This should not happen because policyInfo is valid to convert to JSON data.
|
// This should not happen because policyInfo is valid to convert to JSON data.
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var bucketPolicy policy.Policy
|
var bucketPolicy policy.BucketPolicy
|
||||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
if err = json.Unmarshal(data, &bucketPolicy); err != nil {
|
if err = json.Unmarshal(data, &bucketPolicy); err != nil {
|
||||||
// This should not happen because data is valid to JSON data.
|
// This should not happen because data is valid to JSON data.
|
||||||
|
|||||||
+29
-20
@@ -29,14 +29,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// BucketQuotaSys - map of bucket and quota configuration.
|
// BucketQuotaSys - map of bucket and quota configuration.
|
||||||
type BucketQuotaSys struct {
|
type BucketQuotaSys struct{}
|
||||||
bucketStorageCache timedValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get - Get quota configuration.
|
// Get - Get quota configuration.
|
||||||
func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.BucketQuota, error) {
|
func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.BucketQuota, error) {
|
||||||
qCfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName)
|
cfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName)
|
||||||
return qCfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBucketQuotaSys returns initialized BucketQuotaSys
|
// NewBucketQuotaSys returns initialized BucketQuotaSys
|
||||||
@@ -44,16 +42,18 @@ func NewBucketQuotaSys() *BucketQuotaSys {
|
|||||||
return &BucketQuotaSys{}
|
return &BucketQuotaSys{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bucketStorageCache timedValue
|
||||||
|
|
||||||
// Init initialize bucket quota.
|
// Init initialize bucket quota.
|
||||||
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
||||||
sys.bucketStorageCache.Once.Do(func() {
|
bucketStorageCache.Once.Do(func() {
|
||||||
// Set this to 10 secs since its enough, as scanner
|
// Set this to 10 secs since its enough, as scanner
|
||||||
// does not update the bucket usage values frequently.
|
// does not update the bucket usage values frequently.
|
||||||
sys.bucketStorageCache.TTL = 10 * time.Second
|
bucketStorageCache.TTL = 10 * time.Second
|
||||||
// Rely on older value if usage loading fails from disk.
|
// Rely on older value if usage loading fails from disk.
|
||||||
sys.bucketStorageCache.Relax = true
|
bucketStorageCache.Relax = true
|
||||||
sys.bucketStorageCache.Update = func() (interface{}, error) {
|
bucketStorageCache.Update = func() (interface{}, error) {
|
||||||
ctx, done := context.WithTimeout(context.Background(), 1*time.Second)
|
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
return loadDataUsageFromBackend(ctx, objAPI)
|
return loadDataUsageFromBackend(ctx, objAPI)
|
||||||
@@ -63,16 +63,17 @@ func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
|||||||
|
|
||||||
// GetBucketUsageInfo return bucket usage info for a given bucket
|
// GetBucketUsageInfo return bucket usage info for a given bucket
|
||||||
func (sys *BucketQuotaSys) GetBucketUsageInfo(bucket string) (BucketUsageInfo, error) {
|
func (sys *BucketQuotaSys) GetBucketUsageInfo(bucket string) (BucketUsageInfo, error) {
|
||||||
v, err := sys.bucketStorageCache.Get()
|
v, err := bucketStorageCache.Get()
|
||||||
if err != nil && v != nil {
|
timedout := OperationTimedOut{}
|
||||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to retrieve usage information for bucket: %s, relying on older value cached in-memory: err(%v)", bucket, err), "bucket-usage-cache-"+bucket)
|
if err != nil && !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &timedout) {
|
||||||
}
|
if v != nil {
|
||||||
if v == nil {
|
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to retrieve usage information for bucket: %s, relying on older value cached in-memory: err(%v)", bucket, err), "bucket-usage-cache-"+bucket)
|
||||||
logger.LogOnceIf(GlobalContext, errors.New("unable to retrieve usage information for bucket: %s, no reliable usage value available - quota will not be enforced"), "bucket-usage-empty-"+bucket)
|
} else {
|
||||||
|
logger.LogOnceIf(GlobalContext, errors.New("unable to retrieve usage information for bucket: %s, no reliable usage value available - quota will not be enforced"), "bucket-usage-empty-"+bucket)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var bui BucketUsageInfo
|
var bui BucketUsageInfo
|
||||||
|
|
||||||
dui, ok := v.(DataUsageInfo)
|
dui, ok := v.(DataUsageInfo)
|
||||||
if ok {
|
if ok {
|
||||||
bui = dui.BucketsUsage[bucket]
|
bui = dui.BucketsUsage[bucket]
|
||||||
@@ -107,8 +108,16 @@ func (sys *BucketQuotaSys) enforceQuotaHard(ctx context.Context, bucket string,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if q != nil && q.Type == madmin.HardQuota && q.Quota > 0 {
|
var quotaSize uint64
|
||||||
if uint64(size) >= q.Quota { // check if file size already exceeds the quota
|
if q != nil && q.Type == madmin.HardQuota {
|
||||||
|
if q.Size > 0 {
|
||||||
|
quotaSize = q.Size
|
||||||
|
} else if q.Quota > 0 {
|
||||||
|
quotaSize = q.Quota
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if quotaSize > 0 {
|
||||||
|
if uint64(size) >= quotaSize { // check if file size already exceeds the quota
|
||||||
return BucketQuotaExceeded{Bucket: bucket}
|
return BucketQuotaExceeded{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +126,7 @@ func (sys *BucketQuotaSys) enforceQuotaHard(ctx context.Context, bucket string,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if bui.Size > 0 && ((bui.Size + uint64(size)) >= q.Quota) {
|
if bui.Size > 0 && ((bui.Size + uint64(size)) >= quotaSize) {
|
||||||
return BucketQuotaExceeded{Bucket: bucket}
|
return BucketQuotaExceeded{Bucket: bucket}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
|
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
|
||||||
@@ -232,10 +232,10 @@ func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseW
|
|||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
stats := globalReplicationStats.getLatestReplicationStats(bucket)
|
stats := globalReplicationStats.getLatestReplicationStats(bucket)
|
||||||
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
|
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
|
||||||
bwMap := bwRpt.BucketStats[bucket]
|
bwMap := bwRpt.BucketStats
|
||||||
for arn, st := range stats.ReplicationStats.Stats {
|
for arn, st := range stats.ReplicationStats.Stats {
|
||||||
if bwMap != nil {
|
for opts, bw := range bwMap {
|
||||||
if bw, ok := bwMap[arn]; ok {
|
if opts.ReplicationARN != "" && opts.ReplicationARN == arn {
|
||||||
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
|
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
|
||||||
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
|
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
|
||||||
stats.ReplicationStats.Stats[arn] = st
|
stats.ReplicationStats.Stats[arn] = st
|
||||||
@@ -288,10 +288,10 @@ func (api objectAPIHandlers) GetBucketReplicationMetricsV2Handler(w http.Respons
|
|||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
stats := globalReplicationStats.getLatestReplicationStats(bucket)
|
stats := globalReplicationStats.getLatestReplicationStats(bucket)
|
||||||
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
|
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
|
||||||
bwMap := bwRpt.BucketStats[bucket]
|
bwMap := bwRpt.BucketStats
|
||||||
for arn, st := range stats.ReplicationStats.Stats {
|
for arn, st := range stats.ReplicationStats.Stats {
|
||||||
if bwMap != nil {
|
for opts, bw := range bwMap {
|
||||||
if bw, ok := bwMap[arn]; ok {
|
if opts.ReplicationARN != "" && opts.ReplicationARN == arn {
|
||||||
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
|
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
|
||||||
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
|
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
|
||||||
stats.ReplicationStats.Stats[arn] = st
|
stats.ReplicationStats.Stats[arn] = st
|
||||||
@@ -476,27 +476,17 @@ func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.Response
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var tgtStats map[string]TargetReplicationResyncStatus
|
brs, err := loadBucketResyncMetadata(ctx, bucket, objectAPI)
|
||||||
globalReplicationPool.resyncer.RLock()
|
if err != nil {
|
||||||
brs, ok := globalReplicationPool.resyncer.statusMap[bucket]
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
||||||
if ok {
|
Bucket: bucket,
|
||||||
tgtStats = brs.cloneTgtStats()
|
Err: fmt.Errorf("replication resync status not available for %s (%s)", arn, err.Error()),
|
||||||
}
|
}), r.URL)
|
||||||
globalReplicationPool.resyncer.RUnlock()
|
return
|
||||||
if !ok {
|
|
||||||
brs, err = loadBucketResyncMetadata(ctx, bucket, objectAPI)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
|
||||||
Bucket: bucket,
|
|
||||||
Err: fmt.Errorf("No replication resync status available for %s", arn),
|
|
||||||
}), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tgtStats = brs.cloneTgtStats()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rinfo ResyncTargetsInfo
|
var rinfo ResyncTargetsInfo
|
||||||
for tarn, st := range tgtStats {
|
for tarn, st := range brs.TargetsMap {
|
||||||
if arn != "" && tarn != arn {
|
if arn != "" && tarn != arn {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
|
"github.com/minio/minio/internal/crypto"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate msgp -file=$GOFILE
|
//go:generate msgp -file=$GOFILE
|
||||||
@@ -167,7 +167,21 @@ func (ri replicatedInfos) Action() replicationAction {
|
|||||||
var replStatusRegex = regexp.MustCompile(`([^=].*?)=([^,].*?);`)
|
var replStatusRegex = regexp.MustCompile(`([^=].*?)=([^,].*?);`)
|
||||||
|
|
||||||
// TargetReplicationStatus - returns replication status of a target
|
// TargetReplicationStatus - returns replication status of a target
|
||||||
func (o *ObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) {
|
func (ri ReplicateObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) {
|
||||||
|
repStatMatches := replStatusRegex.FindAllStringSubmatch(ri.ReplicationStatusInternal, -1)
|
||||||
|
for _, repStatMatch := range repStatMatches {
|
||||||
|
if len(repStatMatch) != 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if repStatMatch[1] == arn {
|
||||||
|
return replication.StatusType(repStatMatch[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetReplicationStatus - returns replication status of a target
|
||||||
|
func (o ObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) {
|
||||||
repStatMatches := replStatusRegex.FindAllStringSubmatch(o.ReplicationStatusInternal, -1)
|
repStatMatches := replStatusRegex.FindAllStringSubmatch(o.ReplicationStatusInternal, -1)
|
||||||
for _, repStatMatch := range repStatMatches {
|
for _, repStatMatch := range repStatMatches {
|
||||||
if len(repStatMatch) != 3 {
|
if len(repStatMatch) != 3 {
|
||||||
@@ -185,7 +199,6 @@ type replicateTargetDecision struct {
|
|||||||
Synchronous bool // Synchronous replication configured.
|
Synchronous bool // Synchronous replication configured.
|
||||||
Arn string // ARN of replication target
|
Arn string // ARN of replication target
|
||||||
ID string
|
ID string
|
||||||
Tgt *TargetClient
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *replicateTargetDecision) String() string {
|
func (t *replicateTargetDecision) String() string {
|
||||||
@@ -207,7 +220,7 @@ type ReplicateDecision struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReplicateAny returns true if atleast one target qualifies for replication
|
// ReplicateAny returns true if atleast one target qualifies for replication
|
||||||
func (d *ReplicateDecision) ReplicateAny() bool {
|
func (d ReplicateDecision) ReplicateAny() bool {
|
||||||
for _, t := range d.targetsMap {
|
for _, t := range d.targetsMap {
|
||||||
if t.Replicate {
|
if t.Replicate {
|
||||||
return true
|
return true
|
||||||
@@ -217,7 +230,7 @@ func (d *ReplicateDecision) ReplicateAny() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Synchronous returns true if atleast one target qualifies for synchronous replication
|
// Synchronous returns true if atleast one target qualifies for synchronous replication
|
||||||
func (d *ReplicateDecision) Synchronous() bool {
|
func (d ReplicateDecision) Synchronous() bool {
|
||||||
for _, t := range d.targetsMap {
|
for _, t := range d.targetsMap {
|
||||||
if t.Synchronous {
|
if t.Synchronous {
|
||||||
return true
|
return true
|
||||||
@@ -226,7 +239,7 @@ func (d *ReplicateDecision) Synchronous() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ReplicateDecision) String() string {
|
func (d ReplicateDecision) String() string {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
for key, value := range d.targetsMap {
|
for key, value := range d.targetsMap {
|
||||||
fmt.Fprintf(b, "%s=%s,", key, value.String())
|
fmt.Fprintf(b, "%s=%s,", key, value.String())
|
||||||
@@ -243,7 +256,7 @@ func (d *ReplicateDecision) Set(t replicateTargetDecision) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PendingStatus returns a stringified representation of internal replication status with all targets marked as `PENDING`
|
// PendingStatus returns a stringified representation of internal replication status with all targets marked as `PENDING`
|
||||||
func (d *ReplicateDecision) PendingStatus() string {
|
func (d ReplicateDecision) PendingStatus() string {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
for _, k := range d.targetsMap {
|
for _, k := range d.targetsMap {
|
||||||
if k.Replicate {
|
if k.Replicate {
|
||||||
@@ -259,11 +272,11 @@ type ResyncDecision struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Empty returns true if no targets with resync decision present
|
// Empty returns true if no targets with resync decision present
|
||||||
func (r *ResyncDecision) Empty() bool {
|
func (r ResyncDecision) Empty() bool {
|
||||||
return r.targets == nil
|
return r.targets == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ResyncDecision) mustResync() bool {
|
func (r ResyncDecision) mustResync() bool {
|
||||||
for _, v := range r.targets {
|
for _, v := range r.targets {
|
||||||
if v.Replicate {
|
if v.Replicate {
|
||||||
return true
|
return true
|
||||||
@@ -272,15 +285,12 @@ func (r *ResyncDecision) mustResync() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ResyncDecision) mustResyncTarget(tgtArn string) bool {
|
func (r ResyncDecision) mustResyncTarget(tgtArn string) bool {
|
||||||
if r.targets == nil {
|
if r.targets == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
v, ok := r.targets[tgtArn]
|
v, ok := r.targets[tgtArn]
|
||||||
if ok && v.Replicate {
|
return ok && v.Replicate
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResyncTargetDecision is struct that represents resync decision for this target
|
// ResyncTargetDecision is struct that represents resync decision for this target
|
||||||
@@ -301,35 +311,20 @@ func parseReplicateDecision(ctx context.Context, bucket, s string) (r ReplicateD
|
|||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pairs := strings.Split(s, ",")
|
for _, p := range strings.Split(s, ",") {
|
||||||
for _, p := range pairs {
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
slc := strings.Split(p, "=")
|
slc := strings.Split(p, "=")
|
||||||
if len(slc) != 2 {
|
if len(slc) != 2 {
|
||||||
return r, errInvalidReplicateDecisionFormat
|
return r, errInvalidReplicateDecisionFormat
|
||||||
}
|
}
|
||||||
tgtStr := strings.TrimPrefix(slc[1], "\"")
|
tgtStr := strings.TrimSuffix(strings.TrimPrefix(slc[1], `"`), `"`)
|
||||||
tgtStr = strings.TrimSuffix(tgtStr, "\"")
|
|
||||||
tgt := strings.Split(tgtStr, ";")
|
tgt := strings.Split(tgtStr, ";")
|
||||||
if len(tgt) != 4 {
|
if len(tgt) != 4 {
|
||||||
return r, errInvalidReplicateDecisionFormat
|
return r, errInvalidReplicateDecisionFormat
|
||||||
}
|
}
|
||||||
var replicate, sync bool
|
r.targetsMap[slc[0]] = replicateTargetDecision{Replicate: tgt[0] == "true", Synchronous: tgt[1] == "true", Arn: tgt[2], ID: tgt[3]}
|
||||||
var err error
|
|
||||||
replicate, err = strconv.ParseBool(tgt[0])
|
|
||||||
if err != nil {
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
sync, err = strconv.ParseBool(tgt[1])
|
|
||||||
if err != nil {
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
tgtClnt := globalBucketTargetSys.GetRemoteTargetClient(slc[0])
|
|
||||||
if tgtClnt == nil {
|
|
||||||
// Skip stale targets if any and log them to be missing atleast once.
|
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, slc[0]), slc[0])
|
|
||||||
// We save the targetDecision even when its not configured or stale.
|
|
||||||
}
|
|
||||||
r.targetsMap[slc[0]] = replicateTargetDecision{Replicate: replicate, Synchronous: sync, Arn: tgt[2], ID: tgt[3], Tgt: tgtClnt}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -496,8 +491,8 @@ func getCompositeVersionPurgeStatus(m map[string]VersionPurgeStatusType) Version
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getHealReplicateObjectInfo returns info needed by heal replication in ReplicateObjectInfo
|
// getHealReplicateObjectInfo returns info needed by heal replication in ReplicateObjectInfo
|
||||||
func getHealReplicateObjectInfo(objInfo ObjectInfo, rcfg replicationConfig) ReplicateObjectInfo {
|
func getHealReplicateObjectInfo(oi ObjectInfo, rcfg replicationConfig) ReplicateObjectInfo {
|
||||||
oi := objInfo.Clone()
|
userDefined := cloneMSS(oi.UserDefined)
|
||||||
if rcfg.Config != nil && rcfg.Config.RoleArn != "" {
|
if rcfg.Config != nil && rcfg.Config.RoleArn != "" {
|
||||||
// For backward compatibility of objects pending/failed replication.
|
// For backward compatibility of objects pending/failed replication.
|
||||||
// Save replication related statuses in the new internal representation for
|
// Save replication related statuses in the new internal representation for
|
||||||
@@ -508,17 +503,15 @@ func getHealReplicateObjectInfo(objInfo ObjectInfo, rcfg replicationConfig) Repl
|
|||||||
if !oi.VersionPurgeStatus.Empty() {
|
if !oi.VersionPurgeStatus.Empty() {
|
||||||
oi.VersionPurgeStatusInternal = fmt.Sprintf("%s=%s;", rcfg.Config.RoleArn, oi.VersionPurgeStatus)
|
oi.VersionPurgeStatusInternal = fmt.Sprintf("%s=%s;", rcfg.Config.RoleArn, oi.VersionPurgeStatus)
|
||||||
}
|
}
|
||||||
for k, v := range oi.UserDefined {
|
for k, v := range userDefined {
|
||||||
if strings.EqualFold(k, ReservedMetadataPrefixLower+ReplicationReset) {
|
if strings.EqualFold(k, ReservedMetadataPrefixLower+ReplicationReset) {
|
||||||
delete(oi.UserDefined, k)
|
delete(userDefined, k)
|
||||||
oi.UserDefined[targetResetHeader(rcfg.Config.RoleArn)] = v
|
userDefined[targetResetHeader(rcfg.Config.RoleArn)] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var dsc ReplicateDecision
|
|
||||||
var tgtStatuses map[string]replication.StatusType
|
|
||||||
var purgeStatuses map[string]VersionPurgeStatusType
|
|
||||||
|
|
||||||
|
var dsc ReplicateDecision
|
||||||
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
||||||
dsc = checkReplicateDelete(GlobalContext, oi.Bucket, ObjectToDelete{
|
dsc = checkReplicateDelete(GlobalContext, oi.Bucket, ObjectToDelete{
|
||||||
ObjectV: ObjectV{
|
ObjectV: ObjectV{
|
||||||
@@ -530,33 +523,45 @@ func getHealReplicateObjectInfo(objInfo ObjectInfo, rcfg replicationConfig) Repl
|
|||||||
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
|
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
|
||||||
}, nil)
|
}, nil)
|
||||||
} else {
|
} else {
|
||||||
dsc = mustReplicate(GlobalContext, oi.Bucket, oi.Name, getMustReplicateOptions(ObjectInfo{
|
dsc = mustReplicate(GlobalContext, oi.Bucket, oi.Name, getMustReplicateOptions(userDefined, oi.UserTags, "", replication.HealReplicationType, ObjectOptions{}))
|
||||||
UserDefined: oi.UserDefined,
|
|
||||||
}, replication.HealReplicationType, ObjectOptions{}))
|
|
||||||
}
|
}
|
||||||
tgtStatuses = replicationStatusesMap(oi.ReplicationStatusInternal)
|
|
||||||
purgeStatuses = versionPurgeStatusesMap(oi.VersionPurgeStatusInternal)
|
tgtStatuses := replicationStatusesMap(oi.ReplicationStatusInternal)
|
||||||
existingObjResync := rcfg.Resync(GlobalContext, oi, &dsc, tgtStatuses)
|
purgeStatuses := versionPurgeStatusesMap(oi.VersionPurgeStatusInternal)
|
||||||
tm, _ := time.Parse(time.RFC3339Nano, oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp])
|
existingObjResync := rcfg.Resync(GlobalContext, oi, dsc, tgtStatuses)
|
||||||
|
tm, _ := time.Parse(time.RFC3339Nano, userDefined[ReservedMetadataPrefixLower+ReplicationTimestamp])
|
||||||
|
rstate := oi.ReplicationState()
|
||||||
|
rstate.ReplicateDecisionStr = dsc.String()
|
||||||
|
asz, _ := oi.GetActualSize()
|
||||||
|
|
||||||
return ReplicateObjectInfo{
|
return ReplicateObjectInfo{
|
||||||
ObjectInfo: oi,
|
Name: oi.Name,
|
||||||
|
Size: oi.Size,
|
||||||
|
ActualSize: asz,
|
||||||
|
Bucket: oi.Bucket,
|
||||||
|
VersionID: oi.VersionID,
|
||||||
|
ETag: oi.ETag,
|
||||||
|
ModTime: oi.ModTime,
|
||||||
|
ReplicationStatus: oi.ReplicationStatus,
|
||||||
|
ReplicationStatusInternal: oi.ReplicationStatusInternal,
|
||||||
|
DeleteMarker: oi.DeleteMarker,
|
||||||
|
VersionPurgeStatusInternal: oi.VersionPurgeStatusInternal,
|
||||||
|
VersionPurgeStatus: oi.VersionPurgeStatus,
|
||||||
|
|
||||||
|
ReplicationState: rstate,
|
||||||
OpType: replication.HealReplicationType,
|
OpType: replication.HealReplicationType,
|
||||||
Dsc: dsc,
|
Dsc: dsc,
|
||||||
ExistingObjResync: existingObjResync,
|
ExistingObjResync: existingObjResync,
|
||||||
TargetStatuses: tgtStatuses,
|
TargetStatuses: tgtStatuses,
|
||||||
TargetPurgeStatuses: purgeStatuses,
|
TargetPurgeStatuses: purgeStatuses,
|
||||||
ReplicationTimestamp: tm,
|
ReplicationTimestamp: tm,
|
||||||
|
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
||||||
|
UserTags: oi.UserTags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ri *ReplicateObjectInfo) getReplicationState() ReplicationState {
|
// ReplicationState - returns replication state using other internal replication metadata in ObjectInfo
|
||||||
rs := ri.ObjectInfo.getReplicationState()
|
func (o ObjectInfo) ReplicationState() ReplicationState {
|
||||||
rs.ReplicateDecisionStr = ri.Dsc.String()
|
|
||||||
return rs
|
|
||||||
}
|
|
||||||
|
|
||||||
// vID here represents the versionID client specified in request - need to distinguish between delete marker and delete marker deletion
|
|
||||||
func (o *ObjectInfo) getReplicationState() ReplicationState {
|
|
||||||
rs := ReplicationState{
|
rs := ReplicationState{
|
||||||
ReplicationStatusInternal: o.ReplicationStatusInternal,
|
ReplicationStatusInternal: o.ReplicationStatusInternal,
|
||||||
VersionPurgeStatusInternal: o.VersionPurgeStatusInternal,
|
VersionPurgeStatusInternal: o.VersionPurgeStatusInternal,
|
||||||
@@ -577,7 +582,7 @@ func (o *ObjectInfo) getReplicationState() ReplicationState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReplicationState returns replication state using other internal replication metadata in ObjectToDelete
|
// ReplicationState returns replication state using other internal replication metadata in ObjectToDelete
|
||||||
func (o *ObjectToDelete) ReplicationState() ReplicationState {
|
func (o ObjectToDelete) ReplicationState() ReplicationState {
|
||||||
r := ReplicationState{
|
r := ReplicationState{
|
||||||
ReplicationStatusInternal: o.DeleteMarkerReplicationStatus,
|
ReplicationStatusInternal: o.DeleteMarkerReplicationStatus,
|
||||||
VersionPurgeStatusInternal: o.VersionPurgeStatuses,
|
VersionPurgeStatusInternal: o.VersionPurgeStatuses,
|
||||||
|
|||||||
+328
-259
@@ -49,6 +49,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
"github.com/zeebo/xxh3"
|
"github.com/zeebo/xxh3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -223,21 +224,19 @@ func (o mustReplicateOptions) isMetadataReplication() bool {
|
|||||||
return o.opType == replication.MetadataReplicationType
|
return o.opType == replication.MetadataReplicationType
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMustReplicateOptions(o ObjectInfo, op replication.Type, opts ObjectOptions) mustReplicateOptions {
|
func (o ObjectInfo) getMustReplicateOptions(op replication.Type, opts ObjectOptions) mustReplicateOptions {
|
||||||
if !op.Valid() {
|
return getMustReplicateOptions(o.UserDefined, o.UserTags, o.ReplicationStatus, op, opts)
|
||||||
op = replication.ObjectReplicationType
|
}
|
||||||
if o.metadataOnly {
|
|
||||||
op = replication.MetadataReplicationType
|
func getMustReplicateOptions(userDefined map[string]string, userTags string, status replication.StatusType, op replication.Type, opts ObjectOptions) mustReplicateOptions {
|
||||||
}
|
meta := cloneMSS(userDefined)
|
||||||
}
|
if userTags != "" {
|
||||||
meta := cloneMSS(o.UserDefined)
|
meta[xhttp.AmzObjectTagging] = userTags
|
||||||
if o.UserTags != "" {
|
|
||||||
meta[xhttp.AmzObjectTagging] = o.UserTags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mustReplicateOptions{
|
return mustReplicateOptions{
|
||||||
meta: meta,
|
meta: meta,
|
||||||
status: o.ReplicationStatus,
|
status: status,
|
||||||
opType: op,
|
opType: op,
|
||||||
replicationRequest: opts.ReplicationRequest,
|
replicationRequest: opts.ReplicationRequest,
|
||||||
}
|
}
|
||||||
@@ -355,40 +354,41 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
|
|||||||
OpType: replication.DeleteReplicationType,
|
OpType: replication.DeleteReplicationType,
|
||||||
}
|
}
|
||||||
tgtArns := rcfg.FilterTargetArns(opts)
|
tgtArns := rcfg.FilterTargetArns(opts)
|
||||||
if len(tgtArns) > 0 {
|
dsc.targetsMap = make(map[string]replicateTargetDecision, len(tgtArns))
|
||||||
dsc.targetsMap = make(map[string]replicateTargetDecision, len(tgtArns))
|
if len(tgtArns) == 0 {
|
||||||
var sync, replicate bool
|
return dsc
|
||||||
for _, tgtArn := range tgtArns {
|
}
|
||||||
opts.TargetArn = tgtArn
|
var sync, replicate bool
|
||||||
replicate = rcfg.Replicate(opts)
|
for _, tgtArn := range tgtArns {
|
||||||
// when incoming delete is removal of a delete marker(a.k.a versioned delete),
|
opts.TargetArn = tgtArn
|
||||||
// GetObjectInfo returns extra information even though it returns errFileNotFound
|
replicate = rcfg.Replicate(opts)
|
||||||
if gerr != nil {
|
// when incoming delete is removal of a delete marker(a.k.a versioned delete),
|
||||||
validReplStatus := false
|
// GetObjectInfo returns extra information even though it returns errFileNotFound
|
||||||
switch oi.TargetReplicationStatus(tgtArn) {
|
if gerr != nil {
|
||||||
case replication.Pending, replication.Completed, replication.Failed:
|
validReplStatus := false
|
||||||
validReplStatus = true
|
switch oi.TargetReplicationStatus(tgtArn) {
|
||||||
}
|
case replication.Pending, replication.Completed, replication.Failed:
|
||||||
if oi.DeleteMarker && (validReplStatus || replicate) {
|
validReplStatus = true
|
||||||
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
// can be the case that other cluster is down and duplicate `mc rm --vid`
|
|
||||||
// is issued - this still needs to be replicated back to the other target
|
|
||||||
replicate = oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed
|
|
||||||
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(tgtArn)
|
if oi.DeleteMarker && (validReplStatus || replicate) {
|
||||||
// the target online status should not be used here while deciding
|
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
|
||||||
// whether to replicate deletes as the target could be temporarily down
|
continue
|
||||||
tgtDsc := newReplicateTargetDecision(tgtArn, false, false)
|
} else {
|
||||||
if tgt != nil {
|
// can be the case that other cluster is down and duplicate `mc rm --vid`
|
||||||
tgtDsc = newReplicateTargetDecision(tgtArn, replicate, tgt.replicateSync)
|
// is issued - this still needs to be replicated back to the other target
|
||||||
|
replicate = oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed
|
||||||
|
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
dsc.Set(tgtDsc)
|
|
||||||
}
|
}
|
||||||
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(tgtArn)
|
||||||
|
// the target online status should not be used here while deciding
|
||||||
|
// whether to replicate deletes as the target could be temporarily down
|
||||||
|
tgtDsc := newReplicateTargetDecision(tgtArn, false, false)
|
||||||
|
if tgt != nil {
|
||||||
|
tgtDsc = newReplicateTargetDecision(tgtArn, replicate, tgt.replicateSync)
|
||||||
|
}
|
||||||
|
dsc.Set(tgtDsc)
|
||||||
}
|
}
|
||||||
return dsc
|
return dsc
|
||||||
}
|
}
|
||||||
@@ -482,15 +482,10 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, obj
|
|||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer lk.Unlock(lkctx)
|
defer lk.Unlock(lkctx)
|
||||||
|
|
||||||
|
rinfos := replicatedInfos{Targets: make([]replicatedTargetInfo, 0, len(dsc.targetsMap))}
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var rinfos replicatedInfos
|
var mu sync.Mutex
|
||||||
rinfos.Targets = make([]replicatedTargetInfo, len(dsc.targetsMap))
|
|
||||||
idx := -1
|
|
||||||
for _, tgtEntry := range dsc.targetsMap {
|
for _, tgtEntry := range dsc.targetsMap {
|
||||||
idx++
|
|
||||||
if tgtEntry.Tgt == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !tgtEntry.Replicate {
|
if !tgtEntry.Replicate {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -498,11 +493,33 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, obj
|
|||||||
if dobj.TargetArn != "" && dobj.TargetArn != tgtEntry.Arn {
|
if dobj.TargetArn != "" && dobj.TargetArn != tgtEntry.Arn {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
tgtClnt := globalBucketTargetSys.GetRemoteTargetClient(tgtEntry.Arn)
|
||||||
|
if tgtClnt == nil {
|
||||||
|
// Skip stale targets if any and log them to be missing atleast once.
|
||||||
|
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtEntry.Arn), tgtEntry.Arn)
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: ObjectInfo{
|
||||||
|
Bucket: bucket,
|
||||||
|
Name: dobj.ObjectName,
|
||||||
|
VersionID: versionID,
|
||||||
|
DeleteMarker: dobj.DeleteMarker,
|
||||||
|
},
|
||||||
|
UserAgent: "Internal: [Replication]",
|
||||||
|
Host: globalLocalNodeName,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(index int, tgt *TargetClient) {
|
go func(tgt *TargetClient) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
rinfos.Targets[index] = replicateDeleteToTarget(ctx, dobj, tgt)
|
tgtInfo := replicateDeleteToTarget(ctx, dobj, tgt)
|
||||||
}(idx, tgtEntry.Tgt)
|
|
||||||
|
mu.Lock()
|
||||||
|
rinfos.Targets = append(rinfos.Targets, tgtInfo)
|
||||||
|
mu.Unlock()
|
||||||
|
}(tgtClnt)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
@@ -702,10 +719,8 @@ func getCopyObjMetadata(oi ObjectInfo, sc string) map[string]string {
|
|||||||
meta[xhttp.ContentType] = oi.ContentType
|
meta[xhttp.ContentType] = oi.ContentType
|
||||||
}
|
}
|
||||||
|
|
||||||
if oi.UserTags != "" {
|
meta[xhttp.AmzObjectTagging] = oi.UserTags
|
||||||
meta[xhttp.AmzObjectTagging] = oi.UserTags
|
meta[xhttp.AmzTagDirective] = "REPLACE"
|
||||||
meta[xhttp.AmzTagDirective] = "REPLACE"
|
|
||||||
}
|
|
||||||
|
|
||||||
if sc == "" {
|
if sc == "" {
|
||||||
sc = oi.StorageClass
|
sc = oi.StorageClass
|
||||||
@@ -887,7 +902,7 @@ func getReplicationAction(oi1 ObjectInfo, oi2 minio.ObjectInfo, opType replicati
|
|||||||
}
|
}
|
||||||
|
|
||||||
t, _ := tags.ParseObjectTags(oi1.UserTags)
|
t, _ := tags.ParseObjectTags(oi1.UserTags)
|
||||||
if !reflect.DeepEqual(oi2.UserTags, t.ToMap()) || (oi2.UserTagCount != len(t.ToMap())) {
|
if (oi2.UserTagCount > 0 && !reflect.DeepEqual(oi2.UserTags, t.ToMap())) || (oi2.UserTagCount != len(t.ToMap())) {
|
||||||
return replicateMetadata
|
return replicateMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -964,9 +979,8 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
objInfo := ri.ObjectInfo
|
bucket := ri.Bucket
|
||||||
bucket := objInfo.Bucket
|
object := ri.Name
|
||||||
object := objInfo.Name
|
|
||||||
|
|
||||||
cfg, err := getReplicationConfig(ctx, bucket)
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -974,7 +988,7 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
Object: objInfo,
|
Object: ri.ToObjectInfo(),
|
||||||
UserAgent: "Internal: [Replication]",
|
UserAgent: "Internal: [Replication]",
|
||||||
Host: globalLocalNodeName,
|
Host: globalLocalNodeName,
|
||||||
})
|
})
|
||||||
@@ -982,8 +996,8 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
}
|
}
|
||||||
tgtArns := cfg.FilterTargetArns(replication.ObjectOpts{
|
tgtArns := cfg.FilterTargetArns(replication.ObjectOpts{
|
||||||
Name: object,
|
Name: object,
|
||||||
SSEC: crypto.SSEC.IsEncrypted(objInfo.UserDefined),
|
SSEC: ri.SSEC,
|
||||||
UserTags: objInfo.UserTags,
|
UserTags: ri.UserTags,
|
||||||
})
|
})
|
||||||
// Lock the object name before starting replication.
|
// Lock the object name before starting replication.
|
||||||
// Use separate lock that doesn't collide with regular objects.
|
// Use separate lock that doesn't collide with regular objects.
|
||||||
@@ -993,7 +1007,7 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
Object: objInfo,
|
Object: ri.ToObjectInfo(),
|
||||||
UserAgent: "Internal: [Replication]",
|
UserAgent: "Internal: [Replication]",
|
||||||
Host: globalLocalNodeName,
|
Host: globalLocalNodeName,
|
||||||
})
|
})
|
||||||
@@ -1003,32 +1017,38 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer lk.Unlock(lkctx)
|
defer lk.Unlock(lkctx)
|
||||||
|
|
||||||
|
rinfos := replicatedInfos{Targets: make([]replicatedTargetInfo, 0, len(tgtArns))}
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var rinfos replicatedInfos
|
var mu sync.Mutex
|
||||||
rinfos.Targets = make([]replicatedTargetInfo, len(tgtArns))
|
for _, tgtArn := range tgtArns {
|
||||||
for i, tgtArn := range tgtArns {
|
|
||||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(tgtArn)
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(tgtArn)
|
||||||
if tgt == nil {
|
if tgt == nil {
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtArn), tgtArn)
|
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtArn), tgtArn)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
Object: objInfo,
|
Object: ri.ToObjectInfo(),
|
||||||
UserAgent: "Internal: [Replication]",
|
UserAgent: "Internal: [Replication]",
|
||||||
Host: globalLocalNodeName,
|
Host: globalLocalNodeName,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(index int, tgt *TargetClient) {
|
go func(tgt *TargetClient) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
|
var tgtInfo replicatedTargetInfo
|
||||||
if ri.OpType == replication.ObjectReplicationType {
|
if ri.OpType == replication.ObjectReplicationType {
|
||||||
// all incoming calls go through optimized path.
|
// all incoming calls go through optimized path.
|
||||||
rinfos.Targets[index] = ri.replicateObject(ctx, objectAPI, tgt)
|
tgtInfo = ri.replicateObject(ctx, objectAPI, tgt)
|
||||||
} else {
|
} else {
|
||||||
rinfos.Targets[index] = ri.replicateAll(ctx, objectAPI, tgt)
|
tgtInfo = ri.replicateAll(ctx, objectAPI, tgt)
|
||||||
}
|
}
|
||||||
}(i, tgt)
|
|
||||||
|
mu.Lock()
|
||||||
|
rinfos.Targets = append(rinfos.Targets, tgtInfo)
|
||||||
|
mu.Unlock()
|
||||||
|
}(tgt)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
@@ -1043,10 +1063,11 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
newReplStatusInternal := rinfos.ReplicationStatusInternal()
|
newReplStatusInternal := rinfos.ReplicationStatusInternal()
|
||||||
// Note that internal replication status(es) may match for previously replicated objects - in such cases
|
// Note that internal replication status(es) may match for previously replicated objects - in such cases
|
||||||
// metadata should be updated with last resync timestamp.
|
// metadata should be updated with last resync timestamp.
|
||||||
if objInfo.ReplicationStatusInternal != newReplStatusInternal || rinfos.ReplicationResynced() {
|
objInfo := ri.ToObjectInfo()
|
||||||
|
if ri.ReplicationStatusInternal != newReplStatusInternal || rinfos.ReplicationResynced() {
|
||||||
popts := ObjectOptions{
|
popts := ObjectOptions{
|
||||||
MTime: objInfo.ModTime,
|
MTime: ri.ModTime,
|
||||||
VersionID: objInfo.VersionID,
|
VersionID: ri.VersionID,
|
||||||
EvalMetadataFn: func(oi *ObjectInfo, gerr error) (dsc ReplicateDecision, err error) {
|
EvalMetadataFn: func(oi *ObjectInfo, gerr error) (dsc ReplicateDecision, err error) {
|
||||||
oi.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = newReplStatusInternal
|
oi.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = newReplStatusInternal
|
||||||
oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
|
oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
|
||||||
@@ -1056,14 +1077,18 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
oi.UserDefined[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp
|
oi.UserDefined[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if objInfo.UserTags != "" {
|
if ri.UserTags != "" {
|
||||||
oi.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
|
oi.UserDefined[xhttp.AmzObjectTagging] = ri.UserTags
|
||||||
}
|
}
|
||||||
return dsc, nil
|
return dsc, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = objectAPI.PutObjectMetadata(ctx, bucket, object, popts)
|
uobjInfo, _ := objectAPI.PutObjectMetadata(ctx, bucket, object, popts)
|
||||||
|
if uobjInfo.Name != "" {
|
||||||
|
objInfo = uobjInfo
|
||||||
|
}
|
||||||
|
|
||||||
opType := replication.MetadataReplicationType
|
opType := replication.MetadataReplicationType
|
||||||
if rinfos.Action() == replicateAll {
|
if rinfos.Action() == replicateAll {
|
||||||
opType = replication.ObjectReplicationType
|
opType = replication.ObjectReplicationType
|
||||||
@@ -1099,23 +1124,21 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
// The source object is then updated to reflect the replication status.
|
// The source object is then updated to reflect the replication status.
|
||||||
func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI ObjectLayer, tgt *TargetClient) (rinfo replicatedTargetInfo) {
|
func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI ObjectLayer, tgt *TargetClient) (rinfo replicatedTargetInfo) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
objInfo := ri.ObjectInfo.Clone()
|
bucket := ri.Bucket
|
||||||
bucket := objInfo.Bucket
|
object := ri.Name
|
||||||
object := objInfo.Name
|
|
||||||
sz, _ := objInfo.GetActualSize()
|
|
||||||
|
|
||||||
rAction := replicateAll
|
rAction := replicateAll
|
||||||
rinfo = replicatedTargetInfo{
|
rinfo = replicatedTargetInfo{
|
||||||
Size: sz,
|
Size: ri.ActualSize,
|
||||||
Arn: tgt.ARN,
|
Arn: tgt.ARN,
|
||||||
PrevReplicationStatus: objInfo.TargetReplicationStatus(tgt.ARN),
|
PrevReplicationStatus: ri.TargetReplicationStatus(tgt.ARN),
|
||||||
ReplicationStatus: replication.Failed,
|
ReplicationStatus: replication.Failed,
|
||||||
OpType: ri.OpType,
|
OpType: ri.OpType,
|
||||||
ReplicationAction: rAction,
|
ReplicationAction: rAction,
|
||||||
endpoint: tgt.EndpointURL().Host,
|
endpoint: tgt.EndpointURL().Host,
|
||||||
secure: tgt.EndpointURL().Scheme == "https",
|
secure: tgt.EndpointURL().Scheme == "https",
|
||||||
}
|
}
|
||||||
if ri.ObjectInfo.TargetReplicationStatus(tgt.ARN) == replication.Completed && !ri.ExistingObjResync.Empty() && !ri.ExistingObjResync.mustResyncTarget(tgt.ARN) {
|
if ri.TargetReplicationStatus(tgt.ARN) == replication.Completed && !ri.ExistingObjResync.Empty() && !ri.ExistingObjResync.mustResyncTarget(tgt.ARN) {
|
||||||
rinfo.ReplicationStatus = replication.Completed
|
rinfo.ReplicationStatus = replication.Completed
|
||||||
rinfo.ReplicationResynced = true
|
rinfo.ReplicationResynced = true
|
||||||
return
|
return
|
||||||
@@ -1126,7 +1149,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
Object: objInfo,
|
Object: ri.ToObjectInfo(),
|
||||||
UserAgent: "Internal: [Replication]",
|
UserAgent: "Internal: [Replication]",
|
||||||
Host: globalLocalNodeName,
|
Host: globalLocalNodeName,
|
||||||
})
|
})
|
||||||
@@ -1137,12 +1160,13 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||||
|
|
||||||
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
||||||
VersionID: objInfo.VersionID,
|
VersionID: ri.VersionID,
|
||||||
Versioned: versioned,
|
Versioned: versioned,
|
||||||
VersionSuspended: versionSuspended,
|
VersionSuspended: versionSuspended,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||||
|
objInfo := ri.ToObjectInfo()
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1156,7 +1180,8 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
}
|
}
|
||||||
defer gr.Close()
|
defer gr.Close()
|
||||||
|
|
||||||
objInfo = gr.ObjInfo
|
objInfo := gr.ObjInfo
|
||||||
|
|
||||||
// make sure we have the latest metadata for metrics calculation
|
// make sure we have the latest metadata for metrics calculation
|
||||||
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
|
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
|
||||||
|
|
||||||
@@ -1217,8 +1242,10 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts := &bandwidth.MonitorReaderOptions{
|
opts := &bandwidth.MonitorReaderOptions{
|
||||||
Bucket: objInfo.Bucket,
|
BucketOptions: bandwidth.BucketOptions{
|
||||||
TargetARN: tgt.ARN,
|
Name: ri.Bucket,
|
||||||
|
ReplicationARN: tgt.ARN,
|
||||||
|
},
|
||||||
HeaderSize: headerSize,
|
HeaderSize: headerSize,
|
||||||
}
|
}
|
||||||
newCtx := ctx
|
newCtx := ctx
|
||||||
@@ -1255,10 +1282,8 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
// The source object is then updated to reflect the replication status.
|
// The source object is then updated to reflect the replication status.
|
||||||
func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI ObjectLayer, tgt *TargetClient) (rinfo replicatedTargetInfo) {
|
func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI ObjectLayer, tgt *TargetClient) (rinfo replicatedTargetInfo) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
objInfo := ri.ObjectInfo.Clone()
|
bucket := ri.Bucket
|
||||||
bucket := objInfo.Bucket
|
object := ri.Name
|
||||||
object := objInfo.Name
|
|
||||||
sz, _ := objInfo.GetActualSize()
|
|
||||||
|
|
||||||
// set defaults for replication action based on operation being performed - actual
|
// set defaults for replication action based on operation being performed - actual
|
||||||
// replication action can only be determined after stat on remote. This default is
|
// replication action can only be determined after stat on remote. This default is
|
||||||
@@ -1266,9 +1291,9 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
rAction := replicateMetadata
|
rAction := replicateMetadata
|
||||||
|
|
||||||
rinfo = replicatedTargetInfo{
|
rinfo = replicatedTargetInfo{
|
||||||
Size: sz,
|
Size: ri.ActualSize,
|
||||||
Arn: tgt.ARN,
|
Arn: tgt.ARN,
|
||||||
PrevReplicationStatus: objInfo.TargetReplicationStatus(tgt.ARN),
|
PrevReplicationStatus: ri.TargetReplicationStatus(tgt.ARN),
|
||||||
ReplicationStatus: replication.Failed,
|
ReplicationStatus: replication.Failed,
|
||||||
OpType: ri.OpType,
|
OpType: ri.OpType,
|
||||||
ReplicationAction: rAction,
|
ReplicationAction: rAction,
|
||||||
@@ -1281,7 +1306,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
Object: objInfo,
|
Object: ri.ToObjectInfo(),
|
||||||
UserAgent: "Internal: [Replication]",
|
UserAgent: "Internal: [Replication]",
|
||||||
Host: globalLocalNodeName,
|
Host: globalLocalNodeName,
|
||||||
})
|
})
|
||||||
@@ -1292,12 +1317,13 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||||
|
|
||||||
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
||||||
VersionID: objInfo.VersionID,
|
VersionID: ri.VersionID,
|
||||||
Versioned: versioned,
|
Versioned: versioned,
|
||||||
VersionSuspended: versionSuspended,
|
VersionSuspended: versionSuspended,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||||
|
objInfo := ri.ToObjectInfo()
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1311,7 +1337,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
}
|
}
|
||||||
defer gr.Close()
|
defer gr.Close()
|
||||||
|
|
||||||
objInfo = gr.ObjInfo
|
objInfo := gr.ObjInfo
|
||||||
// make sure we have the latest metadata for metrics calculation
|
// make sure we have the latest metadata for metrics calculation
|
||||||
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
|
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
|
||||||
|
|
||||||
@@ -1378,7 +1404,9 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
}
|
}
|
||||||
// object with same VersionID already exists, replication kicked off by
|
// object with same VersionID already exists, replication kicked off by
|
||||||
// PutObject might have completed
|
// PutObject might have completed
|
||||||
if objInfo.TargetReplicationStatus(tgt.ARN) == replication.Pending || objInfo.TargetReplicationStatus(tgt.ARN) == replication.Failed || ri.OpType == replication.ExistingObjectReplicationType {
|
if objInfo.TargetReplicationStatus(tgt.ARN) == replication.Pending ||
|
||||||
|
objInfo.TargetReplicationStatus(tgt.ARN) == replication.Failed ||
|
||||||
|
ri.OpType == replication.ExistingObjectReplicationType {
|
||||||
// if metadata is not updated for some reason after replication, such as
|
// if metadata is not updated for some reason after replication, such as
|
||||||
// 503 encountered while updating metadata - make sure to set ReplicationStatus
|
// 503 encountered while updating metadata - make sure to set ReplicationStatus
|
||||||
// as Completed.
|
// as Completed.
|
||||||
@@ -1456,8 +1484,10 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts := &bandwidth.MonitorReaderOptions{
|
opts := &bandwidth.MonitorReaderOptions{
|
||||||
Bucket: objInfo.Bucket,
|
BucketOptions: bandwidth.BucketOptions{
|
||||||
TargetARN: tgt.ARN,
|
Name: objInfo.Bucket,
|
||||||
|
ReplicationARN: tgt.ARN,
|
||||||
|
},
|
||||||
HeaderSize: headerSize,
|
HeaderSize: headerSize,
|
||||||
}
|
}
|
||||||
newCtx := ctx
|
newCtx := ctx
|
||||||
@@ -1498,9 +1528,18 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
var uploadedParts []minio.CompletePart
|
var uploadedParts []minio.CompletePart
|
||||||
// new multipart must not set mtime as it may lead to erroneous cleanups at various intervals.
|
// new multipart must not set mtime as it may lead to erroneous cleanups at various intervals.
|
||||||
opts.Internal.SourceMTime = time.Time{} // this value is saved properly in CompleteMultipartUpload()
|
opts.Internal.SourceMTime = time.Time{} // this value is saved properly in CompleteMultipartUpload()
|
||||||
nctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
var uploadID string
|
||||||
defer cancel()
|
attempts := 1
|
||||||
uploadID, err := c.NewMultipartUpload(nctx, bucket, object, opts)
|
for attempts <= 3 {
|
||||||
|
nctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||||
|
uploadID, err = c.NewMultipartUpload(nctx, bucket, object, opts)
|
||||||
|
cancel()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
attempts++
|
||||||
|
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1521,7 +1560,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
fmt.Errorf("trying %s: Unable to cleanup failed multipart replication %s on remote %s/%s: %w - this may consume space on remote cluster",
|
fmt.Errorf("trying %s: Unable to cleanup failed multipart replication %s on remote %s/%s: %w - this may consume space on remote cluster",
|
||||||
humanize.Ordinal(attempts), uploadID, bucket, object, aerr))
|
humanize.Ordinal(attempts), uploadID, bucket, object, aerr))
|
||||||
attempts++
|
attempts++
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -1532,7 +1571,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
)
|
)
|
||||||
|
|
||||||
for _, partInfo := range objInfo.Parts {
|
for _, partInfo := range objInfo.Parts {
|
||||||
hr, err = hash.NewReader(io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
|
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1658,9 +1697,8 @@ type ReplicationPool struct {
|
|||||||
resyncer *replicationResyncer
|
resyncer *replicationResyncer
|
||||||
|
|
||||||
// workers:
|
// workers:
|
||||||
workers []chan ReplicationWorkerOperation
|
workers []chan ReplicationWorkerOperation
|
||||||
lrgworkers []chan ReplicationWorkerOperation
|
lrgworkers []chan ReplicationWorkerOperation
|
||||||
existingWorkers chan ReplicationWorkerOperation
|
|
||||||
|
|
||||||
// mrf:
|
// mrf:
|
||||||
mrfWorkerKillCh chan struct{}
|
mrfWorkerKillCh chan struct{}
|
||||||
@@ -1720,8 +1758,6 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
|
|||||||
pool := &ReplicationPool{
|
pool := &ReplicationPool{
|
||||||
workers: make([]chan ReplicationWorkerOperation, 0, workers),
|
workers: make([]chan ReplicationWorkerOperation, 0, workers),
|
||||||
lrgworkers: make([]chan ReplicationWorkerOperation, 0, LargeWorkerCount),
|
lrgworkers: make([]chan ReplicationWorkerOperation, 0, LargeWorkerCount),
|
||||||
existingWorkers: make(chan ReplicationWorkerOperation, 100000),
|
|
||||||
|
|
||||||
mrfReplicaCh: make(chan ReplicationWorkerOperation, 100000),
|
mrfReplicaCh: make(chan ReplicationWorkerOperation, 100000),
|
||||||
mrfWorkerKillCh: make(chan struct{}, failedWorkers),
|
mrfWorkerKillCh: make(chan struct{}, failedWorkers),
|
||||||
resyncer: newresyncer(),
|
resyncer: newresyncer(),
|
||||||
@@ -1735,7 +1771,6 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
|
|||||||
pool.AddLargeWorkers()
|
pool.AddLargeWorkers()
|
||||||
pool.ResizeWorkers(workers, 0)
|
pool.ResizeWorkers(workers, 0)
|
||||||
pool.ResizeFailedWorkers(failedWorkers)
|
pool.ResizeFailedWorkers(failedWorkers)
|
||||||
go pool.AddWorker(pool.existingWorkers, nil)
|
|
||||||
go pool.resyncer.PersistToDisk(ctx, o)
|
go pool.resyncer.PersistToDisk(ctx, o)
|
||||||
go pool.processMRF()
|
go pool.processMRF()
|
||||||
go pool.persistMRF()
|
go pool.persistMRF()
|
||||||
@@ -1962,9 +1997,7 @@ func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) {
|
|||||||
|
|
||||||
var ch, healCh chan<- ReplicationWorkerOperation
|
var ch, healCh chan<- ReplicationWorkerOperation
|
||||||
switch ri.OpType {
|
switch ri.OpType {
|
||||||
case replication.ExistingObjectReplicationType:
|
case replication.HealReplicationType, replication.ExistingObjectReplicationType:
|
||||||
ch = p.existingWorkers
|
|
||||||
case replication.HealReplicationType:
|
|
||||||
ch = p.mrfReplicaCh
|
ch = p.mrfReplicaCh
|
||||||
healCh = p.getWorkerCh(ri.Name, ri.Bucket, ri.Size)
|
healCh = p.getWorkerCh(ri.Name, ri.Bucket, ri.Size)
|
||||||
default:
|
default:
|
||||||
@@ -2023,9 +2056,7 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
|
|||||||
}
|
}
|
||||||
var ch chan<- ReplicationWorkerOperation
|
var ch chan<- ReplicationWorkerOperation
|
||||||
switch doi.OpType {
|
switch doi.OpType {
|
||||||
case replication.ExistingObjectReplicationType:
|
case replication.HealReplicationType, replication.ExistingObjectReplicationType:
|
||||||
ch = p.existingWorkers
|
|
||||||
case replication.HealReplicationType:
|
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
ch = p.getWorkerCh(doi.Bucket, doi.ObjectName, 0)
|
ch = p.getWorkerCh(doi.Bucket, doi.ObjectName, 0)
|
||||||
@@ -2229,8 +2260,38 @@ func proxyHeadToReplicationTarget(ctx context.Context, bucket, object string, rs
|
|||||||
return oi, proxy
|
return oi, proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func scheduleReplication(ctx context.Context, objInfo ObjectInfo, o ObjectLayer, dsc ReplicateDecision, opType replication.Type) {
|
func scheduleReplication(ctx context.Context, oi ObjectInfo, o ObjectLayer, dsc ReplicateDecision, opType replication.Type) {
|
||||||
ri := ReplicateObjectInfo{ObjectInfo: objInfo, OpType: opType, Dsc: dsc, EventType: ReplicateIncoming}
|
tgtStatuses := replicationStatusesMap(oi.ReplicationStatusInternal)
|
||||||
|
purgeStatuses := versionPurgeStatusesMap(oi.VersionPurgeStatusInternal)
|
||||||
|
tm, _ := time.Parse(time.RFC3339Nano, oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp])
|
||||||
|
rstate := oi.ReplicationState()
|
||||||
|
rstate.ReplicateDecisionStr = dsc.String()
|
||||||
|
asz, _ := oi.GetActualSize()
|
||||||
|
|
||||||
|
ri := ReplicateObjectInfo{
|
||||||
|
Name: oi.Name,
|
||||||
|
Size: oi.Size,
|
||||||
|
ActualSize: asz,
|
||||||
|
Bucket: oi.Bucket,
|
||||||
|
VersionID: oi.VersionID,
|
||||||
|
ETag: oi.ETag,
|
||||||
|
ModTime: oi.ModTime,
|
||||||
|
ReplicationStatus: oi.ReplicationStatus,
|
||||||
|
ReplicationStatusInternal: oi.ReplicationStatusInternal,
|
||||||
|
DeleteMarker: oi.DeleteMarker,
|
||||||
|
VersionPurgeStatusInternal: oi.VersionPurgeStatusInternal,
|
||||||
|
VersionPurgeStatus: oi.VersionPurgeStatus,
|
||||||
|
|
||||||
|
ReplicationState: rstate,
|
||||||
|
OpType: opType,
|
||||||
|
Dsc: dsc,
|
||||||
|
TargetStatuses: tgtStatuses,
|
||||||
|
TargetPurgeStatuses: purgeStatuses,
|
||||||
|
ReplicationTimestamp: tm,
|
||||||
|
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
||||||
|
UserTags: oi.UserTags,
|
||||||
|
}
|
||||||
|
|
||||||
if dsc.Synchronous() {
|
if dsc.Synchronous() {
|
||||||
replicateObject(ctx, ri, o)
|
replicateObject(ctx, ri, o)
|
||||||
} else {
|
} else {
|
||||||
@@ -2259,7 +2320,7 @@ func (c replicationConfig) Replicate(opts replication.ObjectOpts) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resync returns true if replication reset is requested
|
// Resync returns true if replication reset is requested
|
||||||
func (c replicationConfig) Resync(ctx context.Context, oi ObjectInfo, dsc *ReplicateDecision, tgtStatuses map[string]replication.StatusType) (r ResyncDecision) {
|
func (c replicationConfig) Resync(ctx context.Context, oi ObjectInfo, dsc ReplicateDecision, tgtStatuses map[string]replication.StatusType) (r ResyncDecision) {
|
||||||
if c.Empty() {
|
if c.Empty() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2268,8 +2329,6 @@ func (c replicationConfig) Resync(ctx context.Context, oi ObjectInfo, dsc *Repli
|
|||||||
if oi.DeleteMarker {
|
if oi.DeleteMarker {
|
||||||
opts := replication.ObjectOpts{
|
opts := replication.ObjectOpts{
|
||||||
Name: oi.Name,
|
Name: oi.Name,
|
||||||
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
|
||||||
UserTags: oi.UserTags,
|
|
||||||
DeleteMarker: oi.DeleteMarker,
|
DeleteMarker: oi.DeleteMarker,
|
||||||
VersionID: oi.VersionID,
|
VersionID: oi.VersionID,
|
||||||
OpType: replication.DeleteReplicationType,
|
OpType: replication.DeleteReplicationType,
|
||||||
@@ -2290,23 +2349,19 @@ func (c replicationConfig) Resync(ctx context.Context, oi ObjectInfo, dsc *Repli
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ignore previous replication status when deciding if object can be re-replicated
|
// Ignore previous replication status when deciding if object can be re-replicated
|
||||||
objInfo := oi.Clone()
|
userDefined := cloneMSS(oi.UserDefined)
|
||||||
objInfo.ReplicationStatusInternal = ""
|
delete(userDefined, xhttp.AmzBucketReplicationStatus)
|
||||||
objInfo.VersionPurgeStatusInternal = ""
|
|
||||||
objInfo.ReplicationStatus = ""
|
rdsc := mustReplicate(ctx, oi.Bucket, oi.Name, getMustReplicateOptions(userDefined, oi.UserTags, "", replication.ExistingObjectReplicationType, ObjectOptions{}))
|
||||||
objInfo.VersionPurgeStatus = ""
|
return c.resync(oi, rdsc, tgtStatuses)
|
||||||
delete(objInfo.UserDefined, xhttp.AmzBucketReplicationStatus)
|
|
||||||
resyncdsc := mustReplicate(ctx, oi.Bucket, oi.Name, getMustReplicateOptions(objInfo, replication.ExistingObjectReplicationType, ObjectOptions{}))
|
|
||||||
dsc = &resyncdsc
|
|
||||||
return c.resync(oi, dsc, tgtStatuses)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapper function for testability. Returns true if a new reset is requested on
|
// wrapper function for testability. Returns true if a new reset is requested on
|
||||||
// already replicated objects OR object qualifies for existing object replication
|
// already replicated objects OR object qualifies for existing object replication
|
||||||
// and no reset requested.
|
// and no reset requested.
|
||||||
func (c replicationConfig) resync(oi ObjectInfo, dsc *ReplicateDecision, tgtStatuses map[string]replication.StatusType) (r ResyncDecision) {
|
func (c replicationConfig) resync(oi ObjectInfo, dsc ReplicateDecision, tgtStatuses map[string]replication.StatusType) (r ResyncDecision) {
|
||||||
r = ResyncDecision{
|
r = ResyncDecision{
|
||||||
targets: make(map[string]ResyncTargetDecision),
|
targets: make(map[string]ResyncTargetDecision, len(dsc.targetsMap)),
|
||||||
}
|
}
|
||||||
if c.remotes == nil {
|
if c.remotes == nil {
|
||||||
return
|
return
|
||||||
@@ -2527,6 +2582,13 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
workers := make([]chan ReplicateObjectInfo, resyncParallelRoutines)
|
workers := make([]chan ReplicateObjectInfo, resyncParallelRoutines)
|
||||||
resultCh := make(chan TargetReplicationResyncStatus, 1)
|
resultCh := make(chan TargetReplicationResyncStatus, 1)
|
||||||
defer close(resultCh)
|
defer close(resultCh)
|
||||||
|
go func() {
|
||||||
|
for r := range resultCh {
|
||||||
|
s.incStats(r, opts)
|
||||||
|
globalSiteResyncMetrics.updateMetric(r, opts.resyncID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i := 0; i < resyncParallelRoutines; i++ {
|
for i := 0; i < resyncParallelRoutines; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
@@ -2556,7 +2618,7 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
ObjectName: roi.Name,
|
ObjectName: roi.Name,
|
||||||
DeleteMarkerVersionID: dmVersionID,
|
DeleteMarkerVersionID: dmVersionID,
|
||||||
VersionID: versionID,
|
VersionID: versionID,
|
||||||
ReplicationState: roi.getReplicationState(),
|
ReplicationState: roi.ReplicationState,
|
||||||
DeleteMarkerMTime: DeleteMarkerMTime{roi.ModTime},
|
DeleteMarkerMTime: DeleteMarkerMTime{roi.ModTime},
|
||||||
DeleteMarker: roi.DeleteMarker,
|
DeleteMarker: roi.DeleteMarker,
|
||||||
},
|
},
|
||||||
@@ -2631,12 +2693,6 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
for i := 0; i < resyncParallelRoutines; i++ {
|
for i := 0; i < resyncParallelRoutines; i++ {
|
||||||
close(workers[i])
|
close(workers[i])
|
||||||
}
|
}
|
||||||
go func() {
|
|
||||||
for r := range resultCh {
|
|
||||||
s.incStats(r, opts)
|
|
||||||
globalSiteResyncMetrics.updateMetric(r, opts.resyncID)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
resyncStatus = ResyncCompleted
|
resyncStatus = ResyncCompleted
|
||||||
}
|
}
|
||||||
@@ -2960,8 +3016,8 @@ func getReplicationDiff(ctx context.Context, objAPI ObjectLayer, bucket string,
|
|||||||
|
|
||||||
// QueueReplicationHeal is a wrapper for queueReplicationHeal
|
// QueueReplicationHeal is a wrapper for queueReplicationHeal
|
||||||
func QueueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, retryCount int) {
|
func QueueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, retryCount int) {
|
||||||
// un-versioned or a prefix
|
// ignore modtime zero objects
|
||||||
if oi.VersionID == "" || oi.ModTime.IsZero() {
|
if oi.ModTime.IsZero() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rcfg, _ := getReplicationConfig(ctx, bucket)
|
rcfg, _ := getReplicationConfig(ctx, bucket)
|
||||||
@@ -2975,8 +3031,8 @@ func QueueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, ret
|
|||||||
// queueReplicationHeal enqueues objects that failed replication OR eligible for resyncing through
|
// queueReplicationHeal enqueues objects that failed replication OR eligible for resyncing through
|
||||||
// an ongoing resync operation or via existing objects replication configuration setting.
|
// an ongoing resync operation or via existing objects replication configuration setting.
|
||||||
func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcfg replicationConfig, retryCount int) (roi ReplicateObjectInfo) {
|
func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcfg replicationConfig, retryCount int) (roi ReplicateObjectInfo) {
|
||||||
// un-versioned or a prefix
|
// ignore modtime zero objects
|
||||||
if oi.VersionID == "" || oi.ModTime.IsZero() {
|
if oi.ModTime.IsZero() {
|
||||||
return roi
|
return roi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3008,7 +3064,7 @@ func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcf
|
|||||||
ObjectName: roi.Name,
|
ObjectName: roi.Name,
|
||||||
DeleteMarkerVersionID: dmVersionID,
|
DeleteMarkerVersionID: dmVersionID,
|
||||||
VersionID: versionID,
|
VersionID: versionID,
|
||||||
ReplicationState: roi.getReplicationState(),
|
ReplicationState: roi.ReplicationState,
|
||||||
DeleteMarkerMTime: DeleteMarkerMTime{roi.ModTime},
|
DeleteMarkerMTime: DeleteMarkerMTime{roi.ModTime},
|
||||||
DeleteMarker: roi.DeleteMarker,
|
DeleteMarker: roi.DeleteMarker,
|
||||||
},
|
},
|
||||||
@@ -3048,7 +3104,7 @@ func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcf
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
mrfSaveInterval = 5 * time.Minute
|
mrfSaveInterval = 5 * time.Minute
|
||||||
mrfQueueInterval = 6 * time.Minute
|
mrfQueueInterval = mrfSaveInterval + time.Minute // A minute higher than save interval
|
||||||
|
|
||||||
mrfRetryLimit = 3 // max number of retries before letting scanner catch up on this object version
|
mrfRetryLimit = 3 // max number of retries before letting scanner catch up on this object version
|
||||||
mrfMaxEntries = 1000000
|
mrfMaxEntries = 1000000
|
||||||
@@ -3063,46 +3119,37 @@ func (p *ReplicationPool) persistMRF() {
|
|||||||
mTimer := time.NewTimer(mrfSaveInterval)
|
mTimer := time.NewTimer(mrfSaveInterval)
|
||||||
defer mTimer.Stop()
|
defer mTimer.Stop()
|
||||||
|
|
||||||
saveMRFToDisk := func(drain bool) {
|
saveMRFToDisk := func() {
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cctx := p.ctx
|
|
||||||
if drain {
|
|
||||||
cctx = context.Background()
|
|
||||||
// drain all mrf entries and save to disk
|
|
||||||
for e := range p.mrfSaveCh {
|
|
||||||
entries[e.versionID] = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// queue all entries for healing before overwriting the node mrf file
|
// queue all entries for healing before overwriting the node mrf file
|
||||||
if !contextCanceled(p.ctx) {
|
if !contextCanceled(p.ctx) {
|
||||||
p.queueMRFHeal()
|
p.queueMRFHeal()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.saveMRFEntries(cctx, entries); err != nil {
|
p.saveMRFEntries(p.ctx, entries)
|
||||||
logger.LogOnceIf(p.ctx, fmt.Errorf("unable to persist replication failures to disk:%w", err), string(replicationSubsystem))
|
|
||||||
}
|
|
||||||
|
|
||||||
entries = make(map[string]MRFReplicateEntry)
|
entries = make(map[string]MRFReplicateEntry)
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-mTimer.C:
|
case <-mTimer.C:
|
||||||
saveMRFToDisk(false)
|
saveMRFToDisk()
|
||||||
mTimer.Reset(mrfSaveInterval)
|
mTimer.Reset(mrfSaveInterval)
|
||||||
case <-p.ctx.Done():
|
case <-p.ctx.Done():
|
||||||
p.mrfStopCh <- struct{}{}
|
p.mrfStopCh <- struct{}{}
|
||||||
close(p.mrfSaveCh)
|
close(p.mrfSaveCh)
|
||||||
saveMRFToDisk(true)
|
// We try to save if possible, but we don't care beyond that.
|
||||||
|
saveMRFToDisk()
|
||||||
return
|
return
|
||||||
case e, ok := <-p.mrfSaveCh:
|
case e, ok := <-p.mrfSaveCh:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(entries) >= mrfMaxEntries {
|
if len(entries) >= mrfMaxEntries {
|
||||||
saveMRFToDisk(true)
|
saveMRFToDisk()
|
||||||
}
|
}
|
||||||
entries[e.versionID] = e
|
entries[e.versionID] = e
|
||||||
}
|
}
|
||||||
@@ -3132,14 +3179,45 @@ func (p *ReplicationPool) queueMRFSave(entry MRFReplicateEntry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntries, data []byte) {
|
||||||
|
newReader := func() io.ReadCloser {
|
||||||
|
r, w := io.Pipe()
|
||||||
|
go func() {
|
||||||
|
mw := msgp.NewWriter(w)
|
||||||
|
n, err := mw.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
w.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n != len(data) {
|
||||||
|
w.CloseWithError(io.ErrShortWrite)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = v.EncodeMsg(mw)
|
||||||
|
mw.Flush()
|
||||||
|
w.CloseWithError(err)
|
||||||
|
}()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, localDrive := range globalLocalDrives {
|
||||||
|
r := newReader()
|
||||||
|
err := localDrive.CreateFile(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), -1, r)
|
||||||
|
r.Close()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// save mrf entries to nodenamehex.bin
|
// save mrf entries to nodenamehex.bin
|
||||||
func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string]MRFReplicateEntry) error {
|
func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string]MRFReplicateEntry) {
|
||||||
if !p.initialized() {
|
if !p.initialized() {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
atomic.StoreUint64(&globalReplicationStats.mrfStats.LastFailedCount, uint64(len(entries)))
|
atomic.StoreUint64(&globalReplicationStats.mrfStats.LastFailedCount, uint64(len(entries)))
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v := MRFReplicateEntries{
|
v := MRFReplicateEntries{
|
||||||
@@ -3152,53 +3230,60 @@ func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string
|
|||||||
binary.LittleEndian.PutUint16(data[0:2], mrfMetaFormat)
|
binary.LittleEndian.PutUint16(data[0:2], mrfMetaFormat)
|
||||||
binary.LittleEndian.PutUint16(data[2:4], mrfMetaVersion)
|
binary.LittleEndian.PutUint16(data[2:4], mrfMetaVersion)
|
||||||
|
|
||||||
buf, err := v.MarshalMsg(data)
|
p.persistToDrive(ctx, v, data)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, localDrive := range globalLocalDrives {
|
|
||||||
if err := localDrive.WriteAll(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), buf); err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// load mrf entries from disk
|
// load mrf entries from disk
|
||||||
func (p *ReplicationPool) loadMRF(data []byte) (re MRFReplicateEntries, e error) {
|
func (p *ReplicationPool) loadMRF() (mrfRec MRFReplicateEntries, err error) {
|
||||||
if !p.initialized() {
|
loadMRF := func(rc io.ReadCloser) (re MRFReplicateEntries, err error) {
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
if !p.initialized() {
|
||||||
|
return re, nil
|
||||||
|
}
|
||||||
|
data := make([]byte, 4)
|
||||||
|
n, err := rc.Read(data)
|
||||||
|
if err != nil {
|
||||||
|
return re, err
|
||||||
|
}
|
||||||
|
if n != len(data) {
|
||||||
|
return re, errors.New("replication mrf: no data")
|
||||||
|
}
|
||||||
|
// Read resync meta header
|
||||||
|
switch binary.LittleEndian.Uint16(data[0:2]) {
|
||||||
|
case mrfMetaFormat:
|
||||||
|
default:
|
||||||
|
return re, fmt.Errorf("replication mrf: unknown format: %d", binary.LittleEndian.Uint16(data[0:2]))
|
||||||
|
}
|
||||||
|
switch binary.LittleEndian.Uint16(data[2:4]) {
|
||||||
|
case mrfMetaVersion:
|
||||||
|
default:
|
||||||
|
return re, fmt.Errorf("replication mrf: unknown version: %d", binary.LittleEndian.Uint16(data[2:4]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK, parse data.
|
||||||
|
// ignore any parsing errors, we do not care this file is generated again anyways.
|
||||||
|
re.DecodeMsg(msgp.NewReader(rc))
|
||||||
|
|
||||||
return re, nil
|
return re, nil
|
||||||
}
|
}
|
||||||
if len(data) == 0 {
|
for _, localDrive := range globalLocalDrives {
|
||||||
// Seems to be empty.
|
rc, err := localDrive.ReadFileStream(p.ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), 0, -1)
|
||||||
return re, nil
|
if err != nil {
|
||||||
}
|
continue
|
||||||
if len(data) <= 4 {
|
}
|
||||||
return re, fmt.Errorf("replication mrf: no data")
|
|
||||||
}
|
mrfRec, err = loadMRF(rc)
|
||||||
// Read resync meta header
|
if err != nil {
|
||||||
switch binary.LittleEndian.Uint16(data[0:2]) {
|
continue
|
||||||
case mrfMetaFormat:
|
}
|
||||||
default:
|
|
||||||
return re, fmt.Errorf("replication mrf: unknown format: %d", binary.LittleEndian.Uint16(data[0:2]))
|
// finally delete the file after processing mrf entries
|
||||||
}
|
localDrive.Delete(p.ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), DeleteOptions{})
|
||||||
switch binary.LittleEndian.Uint16(data[2:4]) {
|
break
|
||||||
case mrfMetaVersion:
|
|
||||||
default:
|
|
||||||
return re, fmt.Errorf("replication mrf: unknown version: %d", binary.LittleEndian.Uint16(data[2:4]))
|
|
||||||
}
|
|
||||||
// OK, parse data.
|
|
||||||
if _, err := re.UnmarshalMsg(data[4:]); err != nil {
|
|
||||||
return re, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch re.Version {
|
return mrfRec, nil
|
||||||
case mrfMetaVersionV1:
|
|
||||||
default:
|
|
||||||
return re, fmt.Errorf("unexpected mrf meta version: %d", re.Version)
|
|
||||||
}
|
|
||||||
return re, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReplicationPool) processMRF() {
|
func (p *ReplicationPool) processMRF() {
|
||||||
@@ -3241,39 +3326,28 @@ func (p *ReplicationPool) queueMRFHeal() error {
|
|||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, localDrive := range globalLocalDrives {
|
mrfRec, err := p.loadMRF()
|
||||||
buf, err := localDrive.ReadAll(p.ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"))
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mrfRec, err := p.loadMRF(buf)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally delete the file after processing mrf entries
|
|
||||||
localDrive.Delete(p.ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), DeleteOptions{})
|
|
||||||
|
|
||||||
// queue replication heal in a goroutine to avoid holding up mrf save routine
|
|
||||||
go func(mrfRec MRFReplicateEntries) {
|
|
||||||
for vID, e := range mrfRec.Entries {
|
|
||||||
ctx, cancel := context.WithTimeout(p.ctx, time.Second) // Do not waste more than a second on this.
|
|
||||||
|
|
||||||
oi, err := p.objLayer.GetObjectInfo(ctx, e.Bucket, e.Object, ObjectOptions{
|
|
||||||
VersionID: vID,
|
|
||||||
})
|
|
||||||
cancel()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
QueueReplicationHeal(p.ctx, e.Bucket, oi, e.RetryCount)
|
|
||||||
}
|
|
||||||
}(mrfRec)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// queue replication heal in a goroutine to avoid holding up mrf save routine
|
||||||
|
go func() {
|
||||||
|
for vID, e := range mrfRec.Entries {
|
||||||
|
ctx, cancel := context.WithTimeout(p.ctx, time.Second) // Do not waste more than a second on this.
|
||||||
|
|
||||||
|
oi, err := p.objLayer.GetObjectInfo(ctx, e.Bucket, e.Object, ObjectOptions{
|
||||||
|
VersionID: vID,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueReplicationHeal(p.ctx, e.Bucket, oi, e.RetryCount)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3283,33 +3357,28 @@ func (p *ReplicationPool) initialized() bool {
|
|||||||
|
|
||||||
// getMRF returns MRF entries for this node.
|
// getMRF returns MRF entries for this node.
|
||||||
func (p *ReplicationPool) getMRF(ctx context.Context, bucket string) (ch chan madmin.ReplicationMRF, err error) {
|
func (p *ReplicationPool) getMRF(ctx context.Context, bucket string) (ch chan madmin.ReplicationMRF, err error) {
|
||||||
|
mrfRec, err := p.loadMRF()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
mrfCh := make(chan madmin.ReplicationMRF, 100)
|
mrfCh := make(chan madmin.ReplicationMRF, 100)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(mrfCh)
|
defer close(mrfCh)
|
||||||
for _, localDrive := range globalLocalDrives {
|
for vID, e := range mrfRec.Entries {
|
||||||
buf, err := localDrive.ReadAll(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"))
|
if e.Bucket != bucket && bucket != "" {
|
||||||
if err != nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mrfRec, err := p.loadMRF(buf)
|
select {
|
||||||
if err != nil {
|
case mrfCh <- madmin.ReplicationMRF{
|
||||||
continue
|
NodeName: globalLocalNodeName,
|
||||||
}
|
Object: e.Object,
|
||||||
for vID, e := range mrfRec.Entries {
|
VersionID: vID,
|
||||||
if e.Bucket != bucket && bucket != "" {
|
Bucket: e.Bucket,
|
||||||
continue
|
RetryCount: e.RetryCount,
|
||||||
}
|
}:
|
||||||
select {
|
case <-ctx.Done():
|
||||||
case mrfCh <- madmin.ReplicationMRF{
|
return
|
||||||
NodeName: globalLocalNodeName,
|
|
||||||
Object: e.Object,
|
|
||||||
VersionID: vID,
|
|
||||||
Bucket: e.Bucket,
|
|
||||||
RetryCount: e.RetryCount,
|
|
||||||
}:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ var replicationConfigTests = []struct {
|
|||||||
func TestReplicationResync(t *testing.T) {
|
func TestReplicationResync(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
for i, test := range replicationConfigTests {
|
for i, test := range replicationConfigTests {
|
||||||
if sync := test.rcfg.Resync(ctx, test.info, &test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync {
|
if sync := test.rcfg.Resync(ctx, test.info, test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync {
|
||||||
t.Errorf("Test%d (%s): Resync got %t , want %t", i+1, test.name, sync.mustResync(), test.expectedSync)
|
t.Errorf("Test%d (%s): Resync got %t , want %t", i+1, test.name, sync.mustResync(), test.expectedSync)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +283,7 @@ var (
|
|||||||
|
|
||||||
func TestReplicationResyncwrapper(t *testing.T) {
|
func TestReplicationResyncwrapper(t *testing.T) {
|
||||||
for i, test := range replicationConfigTests2 {
|
for i, test := range replicationConfigTests2 {
|
||||||
if sync := test.rcfg.resync(test.info, &test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync {
|
if sync := test.rcfg.resync(test.info, test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync {
|
||||||
t.Errorf("%s (%s): Replicationresync got %t , want %t", fmt.Sprintf("Test%d - %s", i+1, time.Now().Format(http.TimeFormat)), test.name, sync.mustResync(), test.expectedSync)
|
t.Errorf("%s (%s): Replicationresync got %t , want %t", fmt.Sprintf("Test%d - %s", i+1, time.Now().Format(http.TimeFormat)), test.name, sync.mustResync(), test.expectedSync)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-21
@@ -489,31 +489,27 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
|
|||||||
defer sys.Unlock()
|
defer sys.Unlock()
|
||||||
|
|
||||||
// Remove existingtarget and arn association
|
// Remove existingtarget and arn association
|
||||||
if tgts, ok := sys.targetsMap[bucket]; ok {
|
if stgts, ok := sys.targetsMap[bucket]; ok {
|
||||||
for _, t := range tgts {
|
for _, t := range stgts {
|
||||||
delete(sys.arnRemotesMap, t.Arn)
|
delete(sys.arnRemotesMap, t.Arn)
|
||||||
}
|
}
|
||||||
delete(sys.targetsMap, bucket)
|
delete(sys.targetsMap, bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need for more if not adding anything
|
if tgts != nil {
|
||||||
if tgts == nil || tgts.Empty() {
|
for _, tgt := range tgts.Targets {
|
||||||
globalBucketMonitor.DeleteBucket(bucket)
|
tgtClient, err := sys.getRemoteTargetClient(&tgt)
|
||||||
return
|
if err != nil {
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
if len(tgts.Targets) > 0 {
|
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
||||||
sys.targetsMap[bucket] = tgts.Targets
|
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
||||||
}
|
}
|
||||||
for _, tgt := range tgts.Targets {
|
|
||||||
tgtClient, err := sys.getRemoteTargetClient(&tgt)
|
if !tgts.Empty() {
|
||||||
if err != nil {
|
sys.targetsMap[bucket] = tgts.Targets
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
|
||||||
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
|
||||||
}
|
}
|
||||||
sys.targetsMap[bucket] = tgts.Targets
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create minio-go clients for buckets having remote targets
|
// create minio-go clients for buckets having remote targets
|
||||||
@@ -524,9 +520,6 @@ func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) {
|
|||||||
}
|
}
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
defer sys.Unlock()
|
defer sys.Unlock()
|
||||||
if len(cfg.Targets) > 0 {
|
|
||||||
sys.targetsMap[bucket.Name] = cfg.Targets
|
|
||||||
}
|
|
||||||
for _, tgt := range cfg.Targets {
|
for _, tgt := range cfg.Targets {
|
||||||
tgtClient, err := sys.getRemoteTargetClient(&tgt)
|
tgtClient, err := sys.getRemoteTargetClient(&tgt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/bucket/versioning"
|
"github.com/minio/minio/internal/bucket/versioning"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+38
-29
@@ -29,7 +29,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -54,27 +53,23 @@ import (
|
|||||||
"github.com/minio/kes-go"
|
"github.com/minio/kes-go"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/color"
|
"github.com/minio/minio/internal/color"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/certs"
|
"github.com/minio/pkg/v2/certs"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/v2/console"
|
||||||
"github.com/minio/pkg/ellipses"
|
"github.com/minio/pkg/v2/ellipses"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
xnet "github.com/minio/pkg/net"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// serverDebugLog will enable debug printing
|
// serverDebugLog will enable debug printing
|
||||||
var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
|
var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
|
||||||
|
|
||||||
var (
|
var shardDiskTimeDelta time.Duration
|
||||||
shardDiskTimeDelta time.Duration
|
|
||||||
defaultAWSCredProvider []credentials.Provider
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
@@ -112,23 +107,16 @@ func init() {
|
|||||||
gob.Register(madmin.XFSErrorConfigs{})
|
gob.Register(madmin.XFSErrorConfigs{})
|
||||||
gob.Register(map[string]interface{}{})
|
gob.Register(map[string]interface{}{})
|
||||||
|
|
||||||
defaultAWSCredProvider = []credentials.Provider{
|
|
||||||
&credentials.IAM{
|
|
||||||
Client: &http.Client{
|
|
||||||
Transport: NewHTTPTransport(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
shardDiskTimeDelta, err = time.ParseDuration(env.Get("_MINIO_SHARD_DISKTIME_DELTA", "1m"))
|
shardDiskTimeDelta, err = time.ParseDuration(env.Get("_MINIO_SHARD_DISKTIME_DELTA", "1m"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
shardDiskTimeDelta = 1 * time.Minute
|
shardDiskTimeDelta = 1 * time.Minute
|
||||||
}
|
}
|
||||||
|
|
||||||
// All minio-go API operations shall be performed only once,
|
// All minio-go and madmin-go API operations shall be performed only once,
|
||||||
// another way to look at this is we are turning off retries.
|
// another way to look at this is we are turning off retries.
|
||||||
minio.MaxRetry = 1
|
minio.MaxRetry = 1
|
||||||
|
madmin.MaxRetry = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const consolePrefix = "CONSOLE_"
|
const consolePrefix = "CONSOLE_"
|
||||||
@@ -143,29 +131,33 @@ func minioConfigToConsoleFeatures() {
|
|||||||
// This will save users from providing a certificate with IP or FQDN SAN that points to the local host.
|
// This will save users from providing a certificate with IP or FQDN SAN that points to the local host.
|
||||||
os.Setenv("CONSOLE_MINIO_SERVER", fmt.Sprintf("%s://127.0.0.1:%s", getURLScheme(globalIsTLS), globalMinioPort))
|
os.Setenv("CONSOLE_MINIO_SERVER", fmt.Sprintf("%s://127.0.0.1:%s", getURLScheme(globalIsTLS), globalMinioPort))
|
||||||
}
|
}
|
||||||
if value := env.Get("MINIO_LOG_QUERY_URL", ""); value != "" {
|
if value := env.Get(config.EnvMinIOLogQueryURL, ""); value != "" {
|
||||||
os.Setenv("CONSOLE_LOG_QUERY_URL", value)
|
os.Setenv("CONSOLE_LOG_QUERY_URL", value)
|
||||||
if value := env.Get("MINIO_LOG_QUERY_AUTH_TOKEN", ""); value != "" {
|
if value := env.Get(config.EnvMinIOLogQueryAuthToken, ""); value != "" {
|
||||||
os.Setenv("CONSOLE_LOG_QUERY_AUTH_TOKEN", value)
|
os.Setenv("CONSOLE_LOG_QUERY_AUTH_TOKEN", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// pass the console subpath configuration
|
// pass the console subpath configuration
|
||||||
if value := env.Get(config.EnvBrowserRedirectURL, ""); value != "" {
|
if globalBrowserRedirectURL != nil {
|
||||||
subPath := path.Clean(pathJoin(strings.TrimSpace(globalBrowserRedirectURL.Path), SlashSeparator))
|
subPath := path.Clean(pathJoin(strings.TrimSpace(globalBrowserRedirectURL.Path), SlashSeparator))
|
||||||
if subPath != SlashSeparator {
|
if subPath != SlashSeparator {
|
||||||
os.Setenv("CONSOLE_SUBPATH", subPath)
|
os.Setenv("CONSOLE_SUBPATH", subPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Enable if prometheus URL is set.
|
// Enable if prometheus URL is set.
|
||||||
if value := env.Get("MINIO_PROMETHEUS_URL", ""); value != "" {
|
if value := env.Get(config.EnvMinIOPrometheusURL, ""); value != "" {
|
||||||
os.Setenv("CONSOLE_PROMETHEUS_URL", value)
|
os.Setenv("CONSOLE_PROMETHEUS_URL", value)
|
||||||
if value := env.Get("MINIO_PROMETHEUS_JOB_ID", "minio-job"); value != "" {
|
if value := env.Get(config.EnvMinIOPrometheusJobID, "minio-job"); value != "" {
|
||||||
os.Setenv("CONSOLE_PROMETHEUS_JOB_ID", value)
|
os.Setenv("CONSOLE_PROMETHEUS_JOB_ID", value)
|
||||||
// Support additional labels for more granular filtering.
|
// Support additional labels for more granular filtering.
|
||||||
if value := env.Get("MINIO_PROMETHEUS_EXTRA_LABELS", ""); value != "" {
|
if value := env.Get(config.EnvMinIOPrometheusExtraLabels, ""); value != "" {
|
||||||
os.Setenv("CONSOLE_PROMETHEUS_EXTRA_LABELS", value)
|
os.Setenv("CONSOLE_PROMETHEUS_EXTRA_LABELS", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Support Prometheus Auth Token
|
||||||
|
if value := env.Get(config.EnvMinIOPrometheusAuthToken, ""); value != "" {
|
||||||
|
os.Setenv("CONSOLE_PROMETHEUS_AUTH_TOKEN", value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Enable if LDAP is enabled.
|
// Enable if LDAP is enabled.
|
||||||
if globalIAMSys.LDAPConfig.Enabled() {
|
if globalIAMSys.LDAPConfig.Enabled() {
|
||||||
@@ -391,7 +383,7 @@ func handleCommonCmdArgs(ctx *cli.Context) {
|
|||||||
if consoleAddr == "" {
|
if consoleAddr == "" {
|
||||||
p, err := xnet.GetFreePort()
|
p, err := xnet.GetFreePort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.FatalIf(err, "Unable to get free port for console on the host")
|
logger.FatalIf(err, "Unable to get free port for Console UI on the host")
|
||||||
}
|
}
|
||||||
consoleAddr = net.JoinHostPort("", p.String())
|
consoleAddr = net.JoinHostPort("", p.String())
|
||||||
}
|
}
|
||||||
@@ -405,6 +397,15 @@ func handleCommonCmdArgs(ctx *cli.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
globalMinioHost, globalMinioPort = mustSplitHostPort(addr)
|
globalMinioHost, globalMinioPort = mustSplitHostPort(addr)
|
||||||
|
if globalMinioPort == "0" {
|
||||||
|
p, err := xnet.GetFreePort()
|
||||||
|
if err != nil {
|
||||||
|
logger.FatalIf(err, "Unable to get free port for S3 API on the host")
|
||||||
|
}
|
||||||
|
globalMinioPort = p.String()
|
||||||
|
globalDynamicAPIPort = true
|
||||||
|
}
|
||||||
|
|
||||||
globalMinioConsoleHost, globalMinioConsolePort = mustSplitHostPort(consoleAddr)
|
globalMinioConsoleHost, globalMinioConsolePort = mustSplitHostPort(consoleAddr)
|
||||||
|
|
||||||
if globalMinioPort == globalMinioConsolePort {
|
if globalMinioPort == globalMinioConsolePort {
|
||||||
@@ -640,6 +641,7 @@ func handleCommonEnvVars() {
|
|||||||
}
|
}
|
||||||
globalBrowserRedirectURL = u
|
globalBrowserRedirectURL = u
|
||||||
}
|
}
|
||||||
|
globalBrowserRedirect = env.Get(config.EnvBrowserRedirect, config.EnableOn) == config.EnableOn
|
||||||
}
|
}
|
||||||
|
|
||||||
if serverURL := env.Get(config.EnvMinIOServerURL, ""); serverURL != "" {
|
if serverURL := env.Get(config.EnvMinIOServerURL, ""); serverURL != "" {
|
||||||
@@ -664,10 +666,14 @@ func handleCommonEnvVars() {
|
|||||||
logger.Fatal(config.ErrInvalidFSOSyncValue(err), "Invalid MINIO_FS_OSYNC value in environment variable")
|
logger.Fatal(config.ErrInvalidFSOSyncValue(err), "Invalid MINIO_FS_OSYNC value in environment variable")
|
||||||
}
|
}
|
||||||
|
|
||||||
if rootDiskSize := env.Get(config.EnvRootDiskThresholdSize, ""); rootDiskSize != "" {
|
rootDiskSize := env.Get(config.EnvRootDriveThresholdSize, "")
|
||||||
|
if rootDiskSize == "" {
|
||||||
|
rootDiskSize = env.Get(config.EnvRootDiskThresholdSize, "")
|
||||||
|
}
|
||||||
|
if rootDiskSize != "" {
|
||||||
size, err := humanize.ParseBytes(rootDiskSize)
|
size, err := humanize.ParseBytes(rootDiskSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal(err, fmt.Sprintf("Invalid %s value in environment variable", config.EnvRootDiskThresholdSize))
|
logger.Fatal(err, fmt.Sprintf("Invalid %s value in root drive threshold environment variable", rootDiskSize))
|
||||||
}
|
}
|
||||||
globalRootDiskThreshold = size
|
globalRootDiskThreshold = size
|
||||||
}
|
}
|
||||||
@@ -802,6 +808,9 @@ func handleKMSConfig() {
|
|||||||
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientCert))
|
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientCert))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !env.IsSet(kms.EnvKESKeyName) {
|
||||||
|
logger.Fatal(errors.New("Invalid KES configuration"), fmt.Sprintf("The mandatory environment variable %q not set", kms.EnvKESKeyName))
|
||||||
|
}
|
||||||
|
|
||||||
var endpoints []string
|
var endpoints []string
|
||||||
for _, endpoint := range strings.Split(env.Get(kms.EnvKESEndpoint, ""), ",") {
|
for _, endpoint := range strings.Split(env.Get(kms.EnvKESEndpoint, ""), ",") {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ func deleteConfig(ctx context.Context, objAPI objectDeleter, configFile string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func saveConfigWithOpts(ctx context.Context, store objectIO, configFile string, data []byte, opts ObjectOptions) error {
|
func saveConfigWithOpts(ctx context.Context, store objectIO, configFile string, data []byte, opts ObjectOptions) error {
|
||||||
hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data)))
|
hashReader, err := hash.NewReader(ctx, bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initHelp() {
|
func initHelp() {
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/event/target"
|
"github.com/minio/minio/internal/event/target"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/logger/target/http"
|
"github.com/minio/minio/internal/logger/target/http"
|
||||||
xnet "github.com/minio/pkg/net"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
"github.com/minio/pkg/quick"
|
"github.com/minio/pkg/v2/quick"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Do not edit following message template, please open a GitHub issue to discuss instead.
|
// Do not edit following message template, please open a GitHub issue to discuss instead.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/storageclass"
|
"github.com/minio/minio/internal/config/storageclass"
|
||||||
"github.com/minio/minio/internal/event/target"
|
"github.com/minio/minio/internal/event/target"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/quick"
|
"github.com/minio/pkg/v2/quick"
|
||||||
)
|
)
|
||||||
|
|
||||||
type configV1 struct {
|
type configV1 struct {
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger/target/console"
|
"github.com/minio/minio/internal/logger/target/console"
|
||||||
"github.com/minio/minio/internal/logger/target/types"
|
"github.com/minio/minio/internal/logger/target/types"
|
||||||
"github.com/minio/minio/internal/pubsub"
|
"github.com/minio/minio/internal/pubsub"
|
||||||
"github.com/minio/pkg/logger/message/log"
|
"github.com/minio/pkg/v2/logger/message/log"
|
||||||
xnet "github.com/minio/pkg/net"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// number of log messages to buffer
|
// number of log messages to buffer
|
||||||
|
|||||||
@@ -66,10 +66,12 @@ const (
|
|||||||
scannerMetricYield
|
scannerMetricYield
|
||||||
scannerMetricCleanAbandoned
|
scannerMetricCleanAbandoned
|
||||||
scannerMetricApplyNonCurrent
|
scannerMetricApplyNonCurrent
|
||||||
|
scannerMetricHealAbandonedVersion
|
||||||
|
|
||||||
// START Trace metrics:
|
// START Trace metrics:
|
||||||
scannerMetricStartTrace
|
scannerMetricStartTrace
|
||||||
scannerMetricScanObject // Scan object. All operations included.
|
scannerMetricScanObject // Scan object. All operations included.
|
||||||
|
scannerMetricHealAbandonedObject
|
||||||
|
|
||||||
// END realtime metrics:
|
// END realtime metrics:
|
||||||
scannerMetricLastRealtime
|
scannerMetricLastRealtime
|
||||||
|
|||||||
+40
-32
@@ -41,7 +41,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/heal"
|
"github.com/minio/minio/internal/config/heal"
|
||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/v2/console"
|
||||||
uatomic "go.uber.org/atomic"
|
uatomic "go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,11 +54,10 @@ const (
|
|||||||
dataScannerForceCompactAtFolders = 1_000_000 // Compact when this many subfolders in a single folder (even top level).
|
dataScannerForceCompactAtFolders = 1_000_000 // Compact when this many subfolders in a single folder (even top level).
|
||||||
dataScannerStartDelay = 1 * time.Minute // Time to wait on startup and between cycles.
|
dataScannerStartDelay = 1 * time.Minute // Time to wait on startup and between cycles.
|
||||||
|
|
||||||
healDeleteDangling = true
|
healDeleteDangling = true
|
||||||
healFolderIncludeProb = 32 // Include a clean folder one in n cycles.
|
healObjectSelectProb = 1024 // Overall probability of a file being scanned; one in n.
|
||||||
healObjectSelectProb = 512 // Overall probability of a file being scanned; one in n.
|
|
||||||
|
|
||||||
dataScannerExcessiveVersionsThreshold = 1000 // Issue a warning when a single object has more versions than this
|
dataScannerExcessiveVersionsThreshold = 100 // Issue a warning when a single object has more versions than this
|
||||||
dataScannerExcessiveFoldersThreshold = 50000 // Issue a warning when a folder has more subfolders than this in a *set*
|
dataScannerExcessiveFoldersThreshold = 50000 // Issue a warning when a folder has more subfolders than this in a *set*
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,7 +65,7 @@ var (
|
|||||||
globalHealConfig heal.Config
|
globalHealConfig heal.Config
|
||||||
|
|
||||||
// Sleeper values are updated when config is loaded.
|
// Sleeper values are updated when config is loaded.
|
||||||
scannerSleeper = newDynamicSleeper(10, 10*time.Second, true)
|
scannerSleeper = newDynamicSleeper(2, time.Second, true) // Keep defaults same as config defaults
|
||||||
scannerCycle = uatomic.NewDuration(dataScannerStartDelay)
|
scannerCycle = uatomic.NewDuration(dataScannerStartDelay)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -274,7 +273,6 @@ type folderScanner struct {
|
|||||||
// rarer if the bloom filter for the path is clean and no lifecycles are applied.
|
// rarer if the bloom filter for the path is clean and no lifecycles are applied.
|
||||||
// Skipped leaves have their totals transferred from the previous cycle.
|
// Skipped leaves have their totals transferred from the previous cycle.
|
||||||
//
|
//
|
||||||
// A clean leaf will be included once every healFolderIncludeProb for partial heal scans.
|
|
||||||
// When selected there is a one in healObjectSelectProb that any object will be chosen for heal scan.
|
// When selected there is a one in healObjectSelectProb that any object will be chosen for heal scan.
|
||||||
//
|
//
|
||||||
// Compaction happens when either:
|
// Compaction happens when either:
|
||||||
@@ -409,9 +407,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
|
|
||||||
var existingFolders, newFolders []cachedFolder
|
var existingFolders, newFolders []cachedFolder
|
||||||
var foundObjects bool
|
var foundObjects bool
|
||||||
err := readDirFn(path.Join(f.root, folder.name), func(entName string, typ os.FileMode) error {
|
err := readDirFn(pathJoin(f.root, folder.name), func(entName string, typ os.FileMode) error {
|
||||||
// Parse
|
// Parse
|
||||||
entName = pathClean(path.Join(folder.name, entName))
|
entName = pathClean(pathJoin(folder.name, entName))
|
||||||
if entName == "" || entName == folder.name {
|
if entName == "" || entName == folder.name {
|
||||||
if f.dataUsageScannerDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(scannerLogPrefix+" no entity (%s,%s)\n", f.root, entName)
|
console.Debugf(scannerLogPrefix+" no entity (%s,%s)\n", f.root, entName)
|
||||||
@@ -461,7 +459,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
|
|
||||||
// Get file size, ignore errors.
|
// Get file size, ignore errors.
|
||||||
item := scannerItem{
|
item := scannerItem{
|
||||||
Path: path.Join(f.root, entName),
|
Path: pathJoin(f.root, entName),
|
||||||
Typ: typ,
|
Typ: typ,
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
prefix: path.Dir(prefix),
|
prefix: path.Dir(prefix),
|
||||||
@@ -496,7 +494,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
// Object already accounted for, remove from heal map,
|
// Object already accounted for, remove from heal map,
|
||||||
// simply because getSize() function already heals the
|
// simply because getSize() function already heals the
|
||||||
// object.
|
// object.
|
||||||
delete(abandonedChildren, path.Join(item.bucket, item.objectPath()))
|
delete(abandonedChildren, pathJoin(item.bucket, item.objectPath()))
|
||||||
|
|
||||||
into.addSizes(sz)
|
into.addSizes(sz)
|
||||||
into.Objects++
|
into.Objects++
|
||||||
@@ -691,6 +689,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
reportNotFound: true,
|
reportNotFound: true,
|
||||||
minDisks: f.disksQuorum,
|
minDisks: f.disksQuorum,
|
||||||
agreed: func(entry metaCacheEntry) {
|
agreed: func(entry metaCacheEntry) {
|
||||||
|
f.updateCurrentPath(entry.name)
|
||||||
if f.dataUsageScannerDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(healObjectsPrefix+" got agreement: %v\n", entry.name)
|
console.Debugf(healObjectsPrefix+" got agreement: %v\n", entry.name)
|
||||||
}
|
}
|
||||||
@@ -704,6 +703,13 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
// this object might be dangling.
|
// this object might be dangling.
|
||||||
entry, _ = entries.firstFound()
|
entry, _ = entries.firstFound()
|
||||||
}
|
}
|
||||||
|
// wait timer per object.
|
||||||
|
wait := scannerSleeper.Timer(ctx)
|
||||||
|
defer wait()
|
||||||
|
f.updateCurrentPath(entry.name)
|
||||||
|
stopFn := globalScannerMetrics.log(scannerMetricHealAbandonedObject, f.root, entry.name)
|
||||||
|
custom := make(map[string]string)
|
||||||
|
defer stopFn(custom)
|
||||||
|
|
||||||
if f.dataUsageScannerDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(healObjectsPrefix+" resolved to: %v, dir: %v\n", entry.name, entry.isDir())
|
console.Debugf(healObjectsPrefix+" resolved to: %v, dir: %v\n", entry.name, entry.isDir())
|
||||||
@@ -713,13 +719,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait on timer per object.
|
|
||||||
wait := scannerSleeper.Timer(ctx)
|
|
||||||
|
|
||||||
// We got an entry which we should be able to heal.
|
// We got an entry which we should be able to heal.
|
||||||
fiv, err := entry.fileInfoVersions(bucket)
|
fiv, err := entry.fileInfoVersions(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wait()
|
|
||||||
err := bgSeq.queueHealTask(healSource{
|
err := bgSeq.queueHealTask(healSource{
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: entry.name,
|
object: entry.name,
|
||||||
@@ -732,21 +734,28 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
custom["versions"] = fmt.Sprint(len(fiv.Versions))
|
||||||
|
var successVersions, failVersions int
|
||||||
for _, ver := range fiv.Versions {
|
for _, ver := range fiv.Versions {
|
||||||
// Sleep and reset.
|
stopFn := globalScannerMetrics.timeSize(scannerMetricHealAbandonedVersion)
|
||||||
wait()
|
|
||||||
wait = scannerSleeper.Timer(ctx)
|
|
||||||
|
|
||||||
err := bgSeq.queueHealTask(healSource{
|
err := bgSeq.queueHealTask(healSource{
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: fiv.Name,
|
object: fiv.Name,
|
||||||
versionID: ver.VersionID,
|
versionID: ver.VersionID,
|
||||||
}, madmin.HealItemObject)
|
}, madmin.HealItemObject)
|
||||||
|
stopFn(int(ver.Size))
|
||||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
successVersions++
|
||||||
|
} else {
|
||||||
|
failVersions++
|
||||||
|
}
|
||||||
foundObjs = foundObjs || err == nil
|
foundObjs = foundObjs || err == nil
|
||||||
}
|
}
|
||||||
|
custom["success_versions"] = fmt.Sprint(successVersions)
|
||||||
|
custom["failed_versions"] = fmt.Sprint(failVersions)
|
||||||
},
|
},
|
||||||
// Too many disks failed.
|
// Too many disks failed.
|
||||||
finished: func(errs []error) {
|
finished: func(errs []error) {
|
||||||
@@ -873,7 +882,7 @@ type getSizeFn func(item scannerItem) (sizeSummary, error)
|
|||||||
func (i *scannerItem) transformMetaDir() {
|
func (i *scannerItem) transformMetaDir() {
|
||||||
split := strings.Split(i.prefix, SlashSeparator)
|
split := strings.Split(i.prefix, SlashSeparator)
|
||||||
if len(split) > 1 {
|
if len(split) > 1 {
|
||||||
i.prefix = path.Join(split[:len(split)-1]...)
|
i.prefix = pathJoin(split[:len(split)-1]...)
|
||||||
} else {
|
} else {
|
||||||
i.prefix = ""
|
i.prefix = ""
|
||||||
}
|
}
|
||||||
@@ -1061,17 +1070,6 @@ func (i *scannerItem) applyNewerNoncurrentVersionLimit(ctx context.Context, _ Ob
|
|||||||
// applyVersionActions will apply lifecycle checks on all versions of a scanned item. Returns versions that remain
|
// applyVersionActions will apply lifecycle checks on all versions of a scanned item. Returns versions that remain
|
||||||
// after applying lifecycle checks configured.
|
// after applying lifecycle checks configured.
|
||||||
func (i *scannerItem) applyVersionActions(ctx context.Context, o ObjectLayer, fivs []FileInfo) ([]ObjectInfo, error) {
|
func (i *scannerItem) applyVersionActions(ctx context.Context, o ObjectLayer, fivs []FileInfo) ([]ObjectInfo, error) {
|
||||||
if i.heal.enabled {
|
|
||||||
if healDeleteDangling {
|
|
||||||
done := globalScannerMetrics.time(scannerMetricCleanAbandoned)
|
|
||||||
err := o.CheckAbandonedParts(ctx, i.bucket, i.objectPath(), madmin.HealOpts{Remove: healDeleteDangling})
|
|
||||||
done()
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to check object %s/%s for abandoned data: %w", i.bucket, i.objectPath(), err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
objInfos, err := i.applyNewerNoncurrentVersionLimit(ctx, o, fivs)
|
objInfos, err := i.applyNewerNoncurrentVersionLimit(ctx, o, fivs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1112,7 +1110,17 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi Object
|
|||||||
done := globalScannerMetrics.time(scannerMetricHealCheck)
|
done := globalScannerMetrics.time(scannerMetricHealCheck)
|
||||||
size = i.applyHealing(ctx, o, oi)
|
size = i.applyHealing(ctx, o, oi)
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
if healDeleteDangling {
|
||||||
|
done := globalScannerMetrics.time(scannerMetricCleanAbandoned)
|
||||||
|
err := o.CheckAbandonedParts(ctx, i.bucket, i.objectPath(), madmin.HealOpts{Remove: healDeleteDangling})
|
||||||
|
done()
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("unable to check object %s/%s for abandoned data: %w", i.bucket, i.objectPath(), err))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// replicate only if lifecycle rules are not applied.
|
// replicate only if lifecycle rules are not applied.
|
||||||
done := globalScannerMetrics.time(scannerMetricCheckReplication)
|
done := globalScannerMetrics.time(scannerMetricCheckReplication)
|
||||||
i.healReplication(ctx, o, oi.Clone(), sizeS)
|
i.healReplication(ctx, o, oi.Clone(), sizeS)
|
||||||
@@ -1247,7 +1255,7 @@ func applyLifecycleAction(event lifecycle.Event, src lcEventSrc, obj ObjectInfo)
|
|||||||
|
|
||||||
// objectPath returns the prefix and object name.
|
// objectPath returns the prefix and object name.
|
||||||
func (i *scannerItem) objectPath() string {
|
func (i *scannerItem) objectPath() string {
|
||||||
return path.Join(i.prefix, i.objectName)
|
return pathJoin(i.prefix, i.objectName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// healReplication will heal a scanned item that has failed replication.
|
// healReplication will heal a scanned item that has failed replication.
|
||||||
|
|||||||
+80
-66
@@ -38,6 +38,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/tinylib/msgp/msgp"
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
"github.com/valyala/bytebufferpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate msgp -file $GOFILE -unexported
|
//go:generate msgp -file $GOFILE -unexported
|
||||||
@@ -346,20 +347,18 @@ func (e *dataUsageEntry) addSizes(summary sizeSummary) {
|
|||||||
e.ReplicationStats.ReplicaSize += uint64(summary.replicaSize)
|
e.ReplicationStats.ReplicaSize += uint64(summary.replicaSize)
|
||||||
e.ReplicationStats.ReplicaCount += uint64(summary.replicaCount)
|
e.ReplicationStats.ReplicaCount += uint64(summary.replicaCount)
|
||||||
|
|
||||||
if summary.replTargetStats != nil {
|
for arn, st := range summary.replTargetStats {
|
||||||
for arn, st := range summary.replTargetStats {
|
tgtStat, ok := e.ReplicationStats.Targets[arn]
|
||||||
tgtStat, ok := e.ReplicationStats.Targets[arn]
|
if !ok {
|
||||||
if !ok {
|
tgtStat = replicationStats{}
|
||||||
tgtStat = replicationStats{}
|
|
||||||
}
|
|
||||||
tgtStat.PendingSize += uint64(st.pendingSize)
|
|
||||||
tgtStat.FailedSize += uint64(st.failedSize)
|
|
||||||
tgtStat.ReplicatedSize += uint64(st.replicatedSize)
|
|
||||||
tgtStat.ReplicatedCount += uint64(st.replicatedCount)
|
|
||||||
tgtStat.FailedCount += st.failedCount
|
|
||||||
tgtStat.PendingCount += st.pendingCount
|
|
||||||
e.ReplicationStats.Targets[arn] = tgtStat
|
|
||||||
}
|
}
|
||||||
|
tgtStat.PendingSize += uint64(st.pendingSize)
|
||||||
|
tgtStat.FailedSize += uint64(st.failedSize)
|
||||||
|
tgtStat.ReplicatedSize += uint64(st.replicatedSize)
|
||||||
|
tgtStat.ReplicatedCount += uint64(st.replicatedCount)
|
||||||
|
tgtStat.FailedCount += st.failedCount
|
||||||
|
tgtStat.PendingCount += st.pendingCount
|
||||||
|
e.ReplicationStats.Targets[arn] = tgtStat
|
||||||
}
|
}
|
||||||
if summary.tiers != nil {
|
if summary.tiers != nil {
|
||||||
if e.AllTierStats == nil {
|
if e.AllTierStats == nil {
|
||||||
@@ -927,37 +926,47 @@ type objectIO interface {
|
|||||||
// The loader is optimistic and has no locking, but tries 5 times before giving up.
|
// The loader is optimistic and has no locking, but tries 5 times before giving up.
|
||||||
// If the object is not found or unable to deserialize d is cleared and nil error is returned.
|
// If the object is not found or unable to deserialize d is cleared and nil error is returned.
|
||||||
func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string) error {
|
func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string) error {
|
||||||
// Abandon if more than 5 minutes, so we don't hold up scanner.
|
// By defaut, empty data usage cache
|
||||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
*d = dataUsageCache{}
|
||||||
defer cancel()
|
|
||||||
|
load := func(name string, timeout time.Duration) (bool, error) {
|
||||||
|
// Abandon if more than time.Minute, so we don't hold up scanner.
|
||||||
|
// drive timeout by default is 2 minutes, we do not need to wait longer.
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
// Caches are read+written without locks,
|
|
||||||
retries := 0
|
|
||||||
for retries < 5 {
|
|
||||||
r, err := store.GetObjectNInfo(ctx, dataUsageBucket, name, nil, http.Header{}, ObjectOptions{NoLock: true})
|
r, err := store.GetObjectNInfo(ctx, dataUsageBucket, name, nil, http.Header{}, ObjectOptions{NoLock: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case ObjectNotFound, BucketNotFound:
|
case ObjectNotFound, BucketNotFound:
|
||||||
|
return false, nil
|
||||||
case InsufficientReadQuorum, StorageErr:
|
case InsufficientReadQuorum, StorageErr:
|
||||||
retries++
|
return true, nil
|
||||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
return toObjectErr(err, dataUsageBucket, name)
|
|
||||||
}
|
}
|
||||||
*d = dataUsageCache{}
|
return false, err
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := d.deserialize(r); err != nil {
|
|
||||||
r.Close()
|
|
||||||
retries++
|
|
||||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
err = d.deserialize(r)
|
||||||
r.Close()
|
r.Close()
|
||||||
return nil
|
return err != nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caches are read+written without locks,
|
||||||
|
retries := 0
|
||||||
|
for retries < 5 {
|
||||||
|
retry, err := load(name, time.Minute)
|
||||||
|
if err != nil {
|
||||||
|
return toObjectErr(err, dataUsageBucket, name)
|
||||||
|
}
|
||||||
|
if !retry {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
retry, _ = load(name+".bkp", 30*time.Second)
|
||||||
|
if !retry {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
retries++
|
||||||
|
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
||||||
}
|
}
|
||||||
*d = dataUsageCache{}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,47 +976,52 @@ var maxConcurrentScannerSaves = make(chan struct{}, 4)
|
|||||||
// save the content of the cache to minioMetaBackgroundOpsBucket with the provided name.
|
// save the content of the cache to minioMetaBackgroundOpsBucket with the provided name.
|
||||||
// Note that no locking is done when saving.
|
// Note that no locking is done when saving.
|
||||||
func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error {
|
func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error {
|
||||||
var r io.Reader
|
select {
|
||||||
maxConcurrentScannerSaves <- struct{}{}
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case maxConcurrentScannerSaves <- struct{}{}:
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
<-maxConcurrentScannerSaves
|
select {
|
||||||
}()
|
case <-ctx.Done():
|
||||||
// If big, do streaming...
|
case <-maxConcurrentScannerSaves:
|
||||||
size := int64(-1)
|
|
||||||
if len(d.Cache) > 10000 {
|
|
||||||
pr, pw := io.Pipe()
|
|
||||||
go func() {
|
|
||||||
pw.CloseWithError(d.serializeTo(pw))
|
|
||||||
}()
|
|
||||||
defer pr.Close()
|
|
||||||
r = pr
|
|
||||||
} else {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
err := d.serializeTo(&buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
r = &buf
|
}()
|
||||||
size = int64(buf.Len())
|
|
||||||
|
buf := bytebufferpool.Get()
|
||||||
|
defer func() {
|
||||||
|
buf.Reset()
|
||||||
|
bytebufferpool.Put(buf)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := d.serializeTo(buf); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hr, err := hash.NewReader(r, size, "", "", size)
|
hr, err := hash.NewReader(ctx, bytes.NewReader(buf.Bytes()), int64(buf.Len()), "", "", int64(buf.Len()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abandon if more than 5 minutes, so we don't hold up scanner.
|
save := func(name string, timeout time.Duration) error {
|
||||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
// Abandon if more than a minute, so we don't hold up scanner.
|
||||||
defer cancel()
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
_, err = store.PutObject(ctx,
|
defer cancel()
|
||||||
dataUsageBucket,
|
|
||||||
name,
|
_, err = store.PutObject(ctx,
|
||||||
NewPutObjReader(hr),
|
dataUsageBucket,
|
||||||
ObjectOptions{NoLock: true})
|
name,
|
||||||
if isErrBucketNotFound(err) {
|
NewPutObjReader(hr),
|
||||||
return nil
|
ObjectOptions{NoLock: true})
|
||||||
|
if isErrBucketNotFound(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return err
|
defer save(name+".bkp", 30*time.Second) // Keep a backup as well
|
||||||
|
|
||||||
|
// drive timeout by default is 2 minutes, we do not need to wait longer.
|
||||||
|
return save(name, time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dataUsageCacheVer indicates the cache version.
|
// dataUsageCacheVer indicates the cache version.
|
||||||
|
|||||||
+48
-19
@@ -21,6 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -42,6 +43,7 @@ const (
|
|||||||
|
|
||||||
// storeDataUsageInBackend will store all objects sent on the gui channel until closed.
|
// storeDataUsageInBackend will store all objects sent on the gui channel until closed.
|
||||||
func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan DataUsageInfo) {
|
func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan DataUsageInfo) {
|
||||||
|
attempts := 1
|
||||||
for dataUsageInfo := range dui {
|
for dataUsageInfo := range dui {
|
||||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
dataUsageJSON, err := json.Marshal(dataUsageInfo)
|
dataUsageJSON, err := json.Marshal(dataUsageInfo)
|
||||||
@@ -49,12 +51,19 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan
|
|||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if attempts > 10 {
|
||||||
|
saveConfig(ctx, objAPI, dataUsageObjNamePath+".bkp", dataUsageJSON) // Save a backup every 10th update.
|
||||||
|
attempts = 1
|
||||||
|
}
|
||||||
if err = saveConfig(ctx, objAPI, dataUsageObjNamePath, dataUsageJSON); err != nil {
|
if err = saveConfig(ctx, objAPI, dataUsageObjNamePath, dataUsageJSON); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
|
attempts++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var prefixUsageCache timedValue
|
||||||
|
|
||||||
// loadPrefixUsageFromBackend returns prefix usages found in passed buckets
|
// loadPrefixUsageFromBackend returns prefix usages found in passed buckets
|
||||||
//
|
//
|
||||||
// e.g.: /testbucket/prefix => 355601334
|
// e.g.: /testbucket/prefix => 355601334
|
||||||
@@ -67,37 +76,57 @@ func loadPrefixUsageFromBackend(ctx context.Context, objAPI ObjectLayer, bucket
|
|||||||
|
|
||||||
cache := dataUsageCache{}
|
cache := dataUsageCache{}
|
||||||
|
|
||||||
m := make(map[string]uint64)
|
prefixUsageCache.Once.Do(func() {
|
||||||
for _, pool := range z.serverPools {
|
prefixUsageCache.TTL = 30 * time.Second
|
||||||
for _, er := range pool.sets {
|
|
||||||
// Load bucket usage prefixes
|
|
||||||
if err := cache.load(ctx, er, bucket+slashSeparator+dataUsageCacheName); err == nil {
|
|
||||||
root := cache.find(bucket)
|
|
||||||
if root == nil {
|
|
||||||
// We dont have usage information for this bucket in this
|
|
||||||
// set, go to the next set
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for id, usageInfo := range cache.flattenChildrens(*root) {
|
// No need to fail upon Update() error, fallback to old value.
|
||||||
prefix := decodeDirObject(strings.TrimPrefix(id, bucket+slashSeparator))
|
prefixUsageCache.Relax = true
|
||||||
// decodeDirObject to avoid any __XLDIR__ objects
|
prefixUsageCache.Update = func() (interface{}, error) {
|
||||||
m[prefix] += uint64(usageInfo.Size)
|
m := make(map[string]uint64)
|
||||||
|
for _, pool := range z.serverPools {
|
||||||
|
for _, er := range pool.sets {
|
||||||
|
// Load bucket usage prefixes
|
||||||
|
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
ok := cache.load(ctx, er, bucket+slashSeparator+dataUsageCacheName) == nil
|
||||||
|
done()
|
||||||
|
if ok {
|
||||||
|
root := cache.find(bucket)
|
||||||
|
if root == nil {
|
||||||
|
// We dont have usage information for this bucket in this
|
||||||
|
// set, go to the next set
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, usageInfo := range cache.flattenChildrens(*root) {
|
||||||
|
prefix := decodeDirObject(strings.TrimPrefix(id, bucket+slashSeparator))
|
||||||
|
// decodeDirObject to avoid any __XLDIR__ objects
|
||||||
|
m[prefix] += uint64(usageInfo.Size)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return m, nil
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
v, _ := prefixUsageCache.Get()
|
||||||
|
if v != nil {
|
||||||
|
return v.(map[string]uint64), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, nil
|
return map[string]uint64{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsageInfo, error) {
|
func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsageInfo, error) {
|
||||||
buf, err := readConfig(ctx, objAPI, dataUsageObjNamePath)
|
buf, err := readConfig(ctx, objAPI, dataUsageObjNamePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
buf, err = readConfig(ctx, objAPI, dataUsageObjNamePath+".bkp")
|
||||||
return DataUsageInfo{}, nil
|
if err != nil {
|
||||||
|
if errors.Is(err, errConfigNotFound) {
|
||||||
|
return DataUsageInfo{}, nil
|
||||||
|
}
|
||||||
|
return DataUsageInfo{}, toObjectErr(err, minioMetaBucket, dataUsageObjNamePath)
|
||||||
}
|
}
|
||||||
return DataUsageInfo{}, toObjectErr(err, minioMetaBucket, dataUsageObjNamePath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataUsageInfo DataUsageInfo
|
var dataUsageInfo DataUsageInfo
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ func (c *diskCache) SaveMetadata(ctx context.Context, bucket, object string, met
|
|||||||
// move part saved in writeback directory and cache.json atomically
|
// move part saved in writeback directory and cache.json atomically
|
||||||
if finalizeWB {
|
if finalizeWB {
|
||||||
wbdir := getCacheWriteBackSHADir(c.dir, bucket, object)
|
wbdir := getCacheWriteBackSHADir(c.dir, bucket, object)
|
||||||
if err = renameAll(pathJoin(wbdir, cacheDataFile), pathJoin(cachedPath, cacheDataFile)); err != nil {
|
if err = renameAll(pathJoin(wbdir, cacheDataFile), pathJoin(cachedPath, cacheDataFile), c.dir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
removeAll(wbdir) // cleanup writeback/shadir
|
removeAll(wbdir) // cleanup writeback/shadir
|
||||||
@@ -1273,7 +1273,7 @@ func (c *diskCache) NewMultipartUpload(ctx context.Context, bucket, object, uID
|
|||||||
|
|
||||||
cachePath := getMultipartCacheSHADir(c.dir, bucket, object)
|
cachePath := getMultipartCacheSHADir(c.dir, bucket, object)
|
||||||
uploadIDDir := path.Join(cachePath, uploadID)
|
uploadIDDir := path.Join(cachePath, uploadID)
|
||||||
if err := mkdirAll(uploadIDDir, 0o777); err != nil {
|
if err := mkdirAll(uploadIDDir, 0o777, c.dir); err != nil {
|
||||||
return uploadID, err
|
return uploadID, err
|
||||||
}
|
}
|
||||||
metaPath := pathJoin(uploadIDDir, cacheMetaJSONFile)
|
metaPath := pathJoin(uploadIDDir, cacheMetaJSONFile)
|
||||||
@@ -1441,7 +1441,7 @@ func newCachePartEncryptReader(ctx context.Context, bucket, object string, partI
|
|||||||
info := ObjectInfo{Size: size}
|
info := ObjectInfo{Size: size}
|
||||||
wantSize = info.EncryptedSize()
|
wantSize = info.EncryptedSize()
|
||||||
}
|
}
|
||||||
hReader, err := hash.NewReader(content, wantSize, "", "", size)
|
hReader, err := hash.NewReader(ctx, content, wantSize, "", "", size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1570,9 +1570,9 @@ func (c *diskCache) CompleteMultipartUpload(ctx context.Context, bucket, object,
|
|||||||
jsonSave(f, uploadMeta)
|
jsonSave(f, uploadMeta)
|
||||||
for _, pi := range uploadedParts {
|
for _, pi := range uploadedParts {
|
||||||
part := fmt.Sprintf("part.%d", pi.PartNumber)
|
part := fmt.Sprintf("part.%d", pi.PartNumber)
|
||||||
renameAll(pathJoin(uploadIDDir, part), pathJoin(cachePath, part))
|
renameAll(pathJoin(uploadIDDir, part), pathJoin(cachePath, part), c.dir)
|
||||||
}
|
}
|
||||||
renameAll(pathJoin(uploadIDDir, cacheMetaJSONFile), pathJoin(cachePath, cacheMetaJSONFile))
|
renameAll(pathJoin(uploadIDDir, cacheMetaJSONFile), pathJoin(cachePath, cacheMetaJSONFile), c.dir)
|
||||||
removeAll(uploadIDDir) // clean up any unused parts in the uploadIDDir
|
removeAll(uploadIDDir) // clean up any unused parts in the uploadIDDir
|
||||||
return uploadMeta.ToObjectInfo(), nil
|
return uploadMeta.ToObjectInfo(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -33,9 +33,9 @@ import (
|
|||||||
"github.com/minio/minio/internal/disk"
|
"github.com/minio/minio/internal/disk"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
xnet "github.com/minio/pkg/net"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -734,7 +734,7 @@ func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *
|
|||||||
defer cLock.Unlock(lkctx)
|
defer cLock.Unlock(lkctx)
|
||||||
// Initialize pipe to stream data to backend
|
// Initialize pipe to stream data to backend
|
||||||
pipeReader, pipeWriter := io.Pipe()
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
hashReader, err := hash.NewReader(pipeReader, size, "", "", r.ActualSize())
|
hashReader, err := hash.NewReader(ctx, pipeReader, size, "", "", r.ActualSize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -795,7 +795,7 @@ func (c *cacheObjects) uploadObject(ctx context.Context, oi ObjectInfo) {
|
|||||||
if st == CommitComplete || st.String() == "" {
|
if st == CommitComplete || st.String() == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hashReader, err := hash.NewReader(cReader, oi.Size, "", "", oi.Size)
|
hashReader, err := hash.NewReader(ctx, cReader, oi.Size, "", "", oi.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1059,7 +1059,7 @@ func (c *cacheObjects) PutObjectPart(ctx context.Context, bucket, object, upload
|
|||||||
info = PartInfo{}
|
info = PartInfo{}
|
||||||
// Initialize pipe to stream data to backend
|
// Initialize pipe to stream data to backend
|
||||||
pipeReader, pipeWriter := io.Pipe()
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
hashReader, err := hash.NewReader(pipeReader, size, "", "", data.ActualSize())
|
hashReader, err := hash.NewReader(ctx, pipeReader, size, "", "", data.ActualSize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data types used for returning dummy tagging XML.
|
// Data types used for returning dummy tagging XML.
|
||||||
|
|||||||
@@ -728,7 +728,7 @@ func (d *DecryptBlocksReader) Read(p []byte) (int, error) {
|
|||||||
// DecryptedSize returns the size of the object after decryption in bytes.
|
// DecryptedSize returns the size of the object after decryption in bytes.
|
||||||
// It returns an error if the object is not encrypted or marked as encrypted
|
// It returns an error if the object is not encrypted or marked as encrypted
|
||||||
// but has an invalid size.
|
// but has an invalid size.
|
||||||
func (o *ObjectInfo) DecryptedSize() (int64, error) {
|
func (o ObjectInfo) DecryptedSize() (int64, error) {
|
||||||
if _, ok := crypto.IsEncrypted(o.UserDefined); !ok {
|
if _, ok := crypto.IsEncrypted(o.UserDefined); !ok {
|
||||||
return 0, errors.New("Cannot compute decrypted size of an unencrypted object")
|
return 0, errors.New("Cannot compute decrypted size of an unencrypted object")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/pkg/ellipses"
|
"github.com/minio/pkg/v2/ellipses"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file implements and supports ellipses pattern for
|
// This file implements and supports ellipses pattern for
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/pkg/ellipses"
|
"github.com/minio/pkg/v2/ellipses"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests create endpoints with ellipses and without.
|
// Tests create endpoints with ellipses and without.
|
||||||
|
|||||||
+2
-2
@@ -36,8 +36,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/mountinfo"
|
"github.com/minio/minio/internal/mountinfo"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
xnet "github.com/minio/pkg/net"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+1
-57
@@ -26,7 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (er erasureObjects) getOnlineDisks() (newDisks []StorageAPI) {
|
func (er erasureObjects) getOnlineDisks() (newDisks []StorageAPI) {
|
||||||
@@ -74,62 +74,6 @@ func (er erasureObjects) getLoadBalancedLocalDisks() (newDisks []StorageAPI) {
|
|||||||
return newDisks
|
return newDisks
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLoadBalancedDisks - fetches load balanced (sufficiently randomized) disk slice.
|
|
||||||
// ensures to skip disks if they are not healing and online.
|
|
||||||
func (er erasureObjects) getLoadBalancedDisks(optimized bool) []StorageAPI {
|
|
||||||
disks := er.getDisks()
|
|
||||||
|
|
||||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
||||||
if !optimized {
|
|
||||||
var newDisks []StorageAPI
|
|
||||||
for _, i := range r.Perm(len(disks)) {
|
|
||||||
newDisks = append(newDisks, disks[i])
|
|
||||||
}
|
|
||||||
return newDisks
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var mu sync.Mutex
|
|
||||||
newDisks := map[uint64][]StorageAPI{}
|
|
||||||
// Based on the random shuffling return back randomized disks.
|
|
||||||
for _, i := range r.Perm(len(disks)) {
|
|
||||||
i := i
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if disks[i] == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
di, err := disks[i].DiskInfo(context.Background(), false)
|
|
||||||
if err != nil || di.Healing {
|
|
||||||
// - Do not consume disks which are not reachable
|
|
||||||
// unformatted or simply not accessible for some reason.
|
|
||||||
//
|
|
||||||
// - Do not consume disks which are being healed
|
|
||||||
//
|
|
||||||
// - Future: skip busy disks
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mu.Lock()
|
|
||||||
// Capture disks usage wise upto resolution of MiB
|
|
||||||
newDisks[di.Used/1024/1024] = append(newDisks[di.Used/1024/1024], disks[i])
|
|
||||||
mu.Unlock()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
var max uint64
|
|
||||||
for k := range newDisks {
|
|
||||||
if k > max {
|
|
||||||
max = k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return disks which have maximum disk usage common.
|
|
||||||
return newDisks[max]
|
|
||||||
}
|
|
||||||
|
|
||||||
// readMultipleFiles Reads raw data from all specified files from all disks.
|
// readMultipleFiles Reads raw data from all specified files from all disks.
|
||||||
func readMultipleFiles(ctx context.Context, disks []StorageAPI, req ReadMultipleReq, readQuorum int) ([]ReadMultipleResp, error) {
|
func readMultipleFiles(ctx context.Context, disks []StorageAPI, req ReadMultipleReq, readQuorum int) ([]ReadMultipleResp, error) {
|
||||||
resps := make([]chan ReadMultipleResp, len(disks))
|
resps := make([]chan ReadMultipleResp, len(disks))
|
||||||
|
|||||||
@@ -620,7 +620,7 @@ func TestDisksWithAllParts(t *testing.T) {
|
|||||||
diskFailures[15] = "part.1"
|
diskFailures[15] = "part.1"
|
||||||
|
|
||||||
for diskIndex, partName := range diskFailures {
|
for diskIndex, partName := range diskFailures {
|
||||||
for i := range partsMetadata[diskIndex].Erasure.Checksums {
|
for i := range partsMetadata[diskIndex].Parts {
|
||||||
if fmt.Sprintf("part.%d", i+1) == partName {
|
if fmt.Sprintf("part.%d", i+1) == partName {
|
||||||
filePath := pathJoin(erasureDisks[diskIndex].String(), bucket, object, partsMetadata[diskIndex].DataDir, partName)
|
filePath := pathJoin(erasureDisks[diskIndex].String(), bucket, object, partsMetadata[diskIndex].DataDir, partName)
|
||||||
f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_SYNC, 0)
|
f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_SYNC, 0)
|
||||||
|
|||||||
+37
-11
@@ -29,7 +29,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
const reservedMetadataPrefixLowerDataShardFix = ReservedMetadataPrefixLower + "data-shard-fix"
|
const reservedMetadataPrefixLowerDataShardFix = ReservedMetadataPrefixLower + "data-shard-fix"
|
||||||
@@ -563,11 +563,26 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !latestMeta.XLV1 && !latestMeta.Deleted && !recreate && disksToHealCount > latestMeta.Erasure.ParityBlocks {
|
if !latestMeta.XLV1 && !latestMeta.Deleted && !recreate && disksToHealCount > latestMeta.Erasure.ParityBlocks {
|
||||||
// When disk to heal count is greater than parity blocks we should simply error out.
|
// Allow for dangling deletes, on versions that have DataDir missing etc.
|
||||||
err := fmt.Errorf("(%d > %d) more drives are expected to heal than parity, returned errors: %v (dataErrs %v) -> %s/%s(%s)", disksToHealCount, latestMeta.Erasure.ParityBlocks, errs, dataErrs, bucket, object, versionID)
|
// this would end up restoring the correct readable versions.
|
||||||
logger.LogOnceIf(ctx, err, "heal-object-count-gt-parity")
|
m, err := er.deleteIfDangling(ctx, bucket, object, partsMetadata, errs, dataErrs, ObjectOptions{
|
||||||
return er.defaultHealResult(latestMeta, storageDisks, storageEndpoints, errs,
|
VersionID: versionID,
|
||||||
bucket, object, versionID), err
|
})
|
||||||
|
errs = make([]error, len(errs))
|
||||||
|
for i := range errs {
|
||||||
|
errs[i] = err
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
// Dangling object successfully purged, size is '0'
|
||||||
|
m.Size = 0
|
||||||
|
}
|
||||||
|
// Generate file/version not found with default heal result
|
||||||
|
err = errFileNotFound
|
||||||
|
if versionID != "" {
|
||||||
|
err = errFileVersionNotFound
|
||||||
|
}
|
||||||
|
return er.defaultHealResult(m, storageDisks, storageEndpoints,
|
||||||
|
errs, bucket, object, versionID), err
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanFileInfo := func(fi FileInfo) FileInfo {
|
cleanFileInfo := func(fi FileInfo) FileInfo {
|
||||||
@@ -714,11 +729,6 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
|||||||
|
|
||||||
partsMetadata[i].DataDir = dstDataDir
|
partsMetadata[i].DataDir = dstDataDir
|
||||||
partsMetadata[i].AddObjectPart(partNumber, "", partSize, partActualSize, partModTime, partIdx, partChecksums)
|
partsMetadata[i].AddObjectPart(partNumber, "", partSize, partActualSize, partModTime, partIdx, partChecksums)
|
||||||
partsMetadata[i].Erasure.AddChecksumInfo(ChecksumInfo{
|
|
||||||
PartNumber: partNumber,
|
|
||||||
Algorithm: checksumAlgo,
|
|
||||||
Hash: bitrotWriterSum(writers[i]),
|
|
||||||
})
|
|
||||||
if len(inlineBuffers) > 0 && inlineBuffers[i] != nil {
|
if len(inlineBuffers) > 0 && inlineBuffers[i] != nil {
|
||||||
partsMetadata[i].Data = inlineBuffers[i].Bytes()
|
partsMetadata[i].Data = inlineBuffers[i].Bytes()
|
||||||
partsMetadata[i].SetInlineData()
|
partsMetadata[i].SetInlineData()
|
||||||
@@ -1082,6 +1092,22 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !validMeta.IsValid() {
|
if !validMeta.IsValid() {
|
||||||
|
// validMeta is invalid because notFoundPartsErrs is
|
||||||
|
// greater than parity blocks, thus invalidating the FileInfo{}
|
||||||
|
// every dataErrs[i], metaArr[i] is an empty FileInfo{}
|
||||||
|
dataBlocks := (len(ndataErrs) + 1) / 2
|
||||||
|
if notFoundPartsErrs > dataBlocks {
|
||||||
|
// Not using parity to ensure that we do not delete
|
||||||
|
// any valid content, if any is recoverable. But if
|
||||||
|
// notFoundDataDirs are already greater than the data
|
||||||
|
// blocks all bets are off and it is safe to purge.
|
||||||
|
//
|
||||||
|
// This is purely a defensive code, ideally parityBlocks
|
||||||
|
// is sufficient, however we can't know that since we
|
||||||
|
// do have the FileInfo{}.
|
||||||
|
return validMeta, true
|
||||||
|
}
|
||||||
|
|
||||||
// We have no idea what this file is, leave it as is.
|
// We have no idea what this file is, leave it as is.
|
||||||
return validMeta, false
|
return validMeta, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// figure out the most commonVersions across disk that satisfies
|
// figure out the most commonVersions across disk that satisfies
|
||||||
|
|||||||
+28
-14
@@ -32,7 +32,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/hash/sha256"
|
"github.com/minio/minio/internal/hash/sha256"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
"github.com/minio/sio"
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,17 +41,6 @@ const minIOErasureUpgraded = "x-minio-internal-erasure-upgraded"
|
|||||||
|
|
||||||
const erasureAlgorithm = "rs-vandermonde"
|
const erasureAlgorithm = "rs-vandermonde"
|
||||||
|
|
||||||
// AddChecksumInfo adds a checksum of a part.
|
|
||||||
func (e *ErasureInfo) AddChecksumInfo(ckSumInfo ChecksumInfo) {
|
|
||||||
for i, sum := range e.Checksums {
|
|
||||||
if sum.PartNumber == ckSumInfo.PartNumber {
|
|
||||||
e.Checksums[i] = ckSumInfo
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e.Checksums = append(e.Checksums, ckSumInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetChecksumInfo - get checksum of a part.
|
// GetChecksumInfo - get checksum of a part.
|
||||||
func (e ErasureInfo) GetChecksumInfo(partNumber int) (ckSum ChecksumInfo) {
|
func (e ErasureInfo) GetChecksumInfo(partNumber int) (ckSum ChecksumInfo) {
|
||||||
for _, sum := range e.Checksums {
|
for _, sum := range e.Checksums {
|
||||||
@@ -60,7 +49,7 @@ func (e ErasureInfo) GetChecksumInfo(partNumber int) (ckSum ChecksumInfo) {
|
|||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ChecksumInfo{}
|
return ChecksumInfo{Algorithm: DefaultBitrotAlgorithm}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShardFileSize - returns final erasure size from original size.
|
// ShardFileSize - returns final erasure size from original size.
|
||||||
@@ -393,14 +382,39 @@ func findFileInfoInQuorum(ctx context.Context, metaArr []FileInfo, modTime time.
|
|||||||
return FileInfo{}, errErasureReadQuorum
|
return FileInfo{}, errErasureReadQuorum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the successor mod time in quorum, otherwise leave the
|
||||||
|
// candidate's successor modTime as found
|
||||||
|
succModTimeMap := make(map[time.Time]int)
|
||||||
|
var candidate FileInfo
|
||||||
|
var found bool
|
||||||
for i, hash := range metaHashes {
|
for i, hash := range metaHashes {
|
||||||
if hash == maxHash {
|
if hash == maxHash {
|
||||||
if metaArr[i].IsValid() {
|
if metaArr[i].IsValid() {
|
||||||
return metaArr[i], nil
|
if !found {
|
||||||
|
candidate = metaArr[i]
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
succModTimeMap[metaArr[i].SuccessorModTime]++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var succModTime time.Time
|
||||||
|
var smodTimeQuorum bool
|
||||||
|
for smodTime, count := range succModTimeMap {
|
||||||
|
if count >= quorum {
|
||||||
|
smodTimeQuorum = true
|
||||||
|
succModTime = smodTime
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
if smodTimeQuorum {
|
||||||
|
candidate.SuccessorModTime = succModTime
|
||||||
|
candidate.IsLatest = succModTime.IsZero()
|
||||||
|
}
|
||||||
|
return candidate, nil
|
||||||
|
}
|
||||||
return FileInfo{}, errErasureReadQuorum
|
return FileInfo{}, errErasureReadQuorum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func TestObjectToPartOffset(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFindFileInfoInQuorum(t *testing.T) {
|
func TestFindFileInfoInQuorum(t *testing.T) {
|
||||||
getNFInfo := func(n int, quorum int, t int64, dataDir string) []FileInfo {
|
getNFInfo := func(n int, quorum int, t int64, dataDir string, succModTimes []time.Time) []FileInfo {
|
||||||
fi := newFileInfo("test", 8, 8)
|
fi := newFileInfo("test", 8, 8)
|
||||||
fi.AddObjectPart(1, "etag", 100, 100, UTCNow(), nil, nil)
|
fi.AddObjectPart(1, "etag", 100, 100, UTCNow(), nil, nil)
|
||||||
fi.ModTime = time.Unix(t, 0)
|
fi.ModTime = time.Unix(t, 0)
|
||||||
@@ -167,6 +167,10 @@ func TestFindFileInfoInQuorum(t *testing.T) {
|
|||||||
for i := range fis {
|
for i := range fis {
|
||||||
fis[i] = fi
|
fis[i] = fi
|
||||||
fis[i].Erasure.Index = i + 1
|
fis[i].Erasure.Index = i + 1
|
||||||
|
if succModTimes != nil {
|
||||||
|
fis[i].SuccessorModTime = succModTimes[i]
|
||||||
|
fis[i].IsLatest = succModTimes[i].IsZero()
|
||||||
|
}
|
||||||
quorum--
|
quorum--
|
||||||
if quorum == 0 {
|
if quorum == 0 {
|
||||||
break
|
break
|
||||||
@@ -175,39 +179,79 @@ func TestFindFileInfoInQuorum(t *testing.T) {
|
|||||||
return fis
|
return fis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commonSuccModTime := time.Date(2023, time.August, 25, 0, 0, 0, 0, time.UTC)
|
||||||
|
succModTimesInQuorum := make([]time.Time, 16)
|
||||||
|
succModTimesNoQuorum := make([]time.Time, 16)
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
if i < 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
succModTimesInQuorum[i] = commonSuccModTime
|
||||||
|
if i < 9 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
succModTimesNoQuorum[i] = commonSuccModTime
|
||||||
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
fis []FileInfo
|
fis []FileInfo
|
||||||
modTime time.Time
|
modTime time.Time
|
||||||
expectedErr error
|
succmodTimes []time.Time
|
||||||
expectedQuorum int
|
expectedErr error
|
||||||
|
expectedQuorum int
|
||||||
|
expectedSuccModTime time.Time
|
||||||
|
expectedIsLatest bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21"),
|
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil),
|
||||||
modTime: time.Unix(1603863445, 0),
|
modTime: time.Unix(1603863445, 0),
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
expectedQuorum: 8,
|
expectedQuorum: 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fis: getNFInfo(16, 7, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21"),
|
fis: getNFInfo(16, 7, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil),
|
||||||
modTime: time.Unix(1603863445, 0),
|
modTime: time.Unix(1603863445, 0),
|
||||||
expectedErr: errErasureReadQuorum,
|
expectedErr: errErasureReadQuorum,
|
||||||
expectedQuorum: 8,
|
expectedQuorum: 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21"),
|
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil),
|
||||||
modTime: time.Unix(1603863445, 0),
|
modTime: time.Unix(1603863445, 0),
|
||||||
expectedErr: errErasureReadQuorum,
|
expectedErr: errErasureReadQuorum,
|
||||||
expectedQuorum: 0,
|
expectedQuorum: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", succModTimesInQuorum),
|
||||||
|
modTime: time.Unix(1603863445, 0),
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedQuorum: 12,
|
||||||
|
expectedSuccModTime: commonSuccModTime,
|
||||||
|
expectedIsLatest: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", succModTimesNoQuorum),
|
||||||
|
modTime: time.Unix(1603863445, 0),
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedQuorum: 12,
|
||||||
|
expectedSuccModTime: time.Time{},
|
||||||
|
expectedIsLatest: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test := test
|
test := test
|
||||||
t.Run("", func(t *testing.T) {
|
t.Run("", func(t *testing.T) {
|
||||||
_, err := findFileInfoInQuorum(context.Background(), test.fis, test.modTime, "", test.expectedQuorum)
|
fi, err := findFileInfoInQuorum(context.Background(), test.fis, test.modTime, "", test.expectedQuorum)
|
||||||
if err != test.expectedErr {
|
if err != test.expectedErr {
|
||||||
t.Errorf("Expected %s, got %s", test.expectedErr, err)
|
t.Errorf("Expected %s, got %s", test.expectedErr, err)
|
||||||
}
|
}
|
||||||
|
if test.succmodTimes != nil {
|
||||||
|
if !test.expectedSuccModTime.Equal(fi.SuccessorModTime) {
|
||||||
|
t.Errorf("Expected successor mod time to be %v but got %v", test.expectedSuccModTime, fi.SuccessorModTime)
|
||||||
|
}
|
||||||
|
if test.expectedIsLatest != fi.IsLatest {
|
||||||
|
t.Errorf("Expected IsLatest to be %v but got %v", test.expectedIsLatest, fi.IsLatest)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/mimedb"
|
"github.com/minio/pkg/v2/mimedb"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
uatomic "go.uber.org/atomic"
|
uatomic "go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -269,7 +269,13 @@ func (er erasureObjects) ListMultipartUploads(ctx context.Context, bucket, objec
|
|||||||
|
|
||||||
var uploadIDs []string
|
var uploadIDs []string
|
||||||
var disk StorageAPI
|
var disk StorageAPI
|
||||||
for _, disk = range er.getLoadBalancedDisks(true) {
|
disks := er.getLoadBalancedLocalDisks()
|
||||||
|
if len(disks) == 0 {
|
||||||
|
// using er.getLoadBalancedLocalDisks() has one side-affect where
|
||||||
|
// on a pooled setup all disks are remote, add a fallback
|
||||||
|
disks = er.getOnlineDisks()
|
||||||
|
}
|
||||||
|
for _, disk = range disks {
|
||||||
uploadIDs, err = disk.ListDir(ctx, minioMetaMultipartBucket, er.getMultipartSHADir(bucket, object), -1)
|
uploadIDs, err = disk.ListDir(ctx, minioMetaMultipartBucket, er.getMultipartSHADir(bucket, object), -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errDiskNotFound) {
|
if errors.Is(err, errDiskNotFound) {
|
||||||
@@ -1242,7 +1248,11 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer er.deleteAll(context.Background(), minioMetaMultipartBucket, uploadIDPath)
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
er.deleteAll(context.Background(), minioMetaMultipartBucket, uploadIDPath)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Rename the multipart object to final location.
|
// Rename the multipart object to final location.
|
||||||
onlineDisks, versionsDisparity, err := renameData(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath,
|
onlineDisks, versionsDisparity, err := renameData(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath,
|
||||||
|
|||||||
+24
-23
@@ -43,9 +43,10 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/mimedb"
|
"github.com/minio/pkg/v2/mimedb"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
uatomic "go.uber.org/atomic"
|
uatomic "go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
|
|||||||
if srcOpts.VersionID != "" {
|
if srcOpts.VersionID != "" {
|
||||||
metaArr, errs = readAllFileInfo(ctx, storageDisks, srcBucket, srcObject, srcOpts.VersionID, true)
|
metaArr, errs = readAllFileInfo(ctx, storageDisks, srcBucket, srcObject, srcOpts.VersionID, true)
|
||||||
} else {
|
} else {
|
||||||
metaArr, errs = readAllXL(ctx, storageDisks, srcBucket, srcObject, true, false)
|
metaArr, errs = readAllXL(ctx, storageDisks, srcBucket, srcObject, true, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
||||||
@@ -497,7 +498,14 @@ func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object st
|
|||||||
tags := make(map[string]interface{}, 4)
|
tags := make(map[string]interface{}, 4)
|
||||||
tags["set"] = er.setIndex
|
tags["set"] = er.setIndex
|
||||||
tags["pool"] = er.poolIndex
|
tags["pool"] = er.poolIndex
|
||||||
tags["parity"] = m.Erasure.ParityBlocks
|
tags["merrs"] = errors.Join(errs...)
|
||||||
|
tags["derrs"] = errors.Join(dataErrs...)
|
||||||
|
if m.IsValid() {
|
||||||
|
tags["size"] = m.Size
|
||||||
|
tags["mtime"] = m.ModTime.Format(http.TimeFormat)
|
||||||
|
tags["parity"] = m.Erasure.ParityBlocks
|
||||||
|
}
|
||||||
|
|
||||||
if cok {
|
if cok {
|
||||||
tags["caller"] = fmt.Sprintf("%s:%d", file, line)
|
tags["caller"] = fmt.Sprintf("%s:%d", file, line)
|
||||||
}
|
}
|
||||||
@@ -533,7 +541,7 @@ func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object st
|
|||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, readData, inclFreeVers bool) ([]FileInfo, []error) {
|
func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, readData, inclFreeVers, allParts bool) ([]FileInfo, []error) {
|
||||||
metadataArray := make([]*xlMetaV2, len(disks))
|
metadataArray := make([]*xlMetaV2, len(disks))
|
||||||
metaFileInfos := make([]FileInfo, len(metadataArray))
|
metaFileInfos := make([]FileInfo, len(metadataArray))
|
||||||
metadataShallowVersions := make([][]xlMetaV2ShallowVersion, len(disks))
|
metadataShallowVersions := make([][]xlMetaV2ShallowVersion, len(disks))
|
||||||
@@ -578,6 +586,9 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
|
|||||||
errFileVersionNotFound,
|
errFileVersionNotFound,
|
||||||
io.ErrUnexpectedEOF, // some times we would read without locks, ignore these errors
|
io.ErrUnexpectedEOF, // some times we would read without locks, ignore these errors
|
||||||
io.EOF, // some times we would read without locks, ignore these errors
|
io.EOF, // some times we would read without locks, ignore these errors
|
||||||
|
msgp.ErrShortBytes,
|
||||||
|
context.DeadlineExceeded,
|
||||||
|
context.Canceled,
|
||||||
}
|
}
|
||||||
ignoredErrs = append(ignoredErrs, objectOpIgnoredErrs...)
|
ignoredErrs = append(ignoredErrs, objectOpIgnoredErrs...)
|
||||||
|
|
||||||
@@ -601,7 +612,7 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
|
|||||||
|
|
||||||
readQuorum := (len(disks) + 1) / 2
|
readQuorum := (len(disks) + 1) / 2
|
||||||
meta := &xlMetaV2{versions: mergeXLV2Versions(readQuorum, false, 1, metadataShallowVersions...)}
|
meta := &xlMetaV2{versions: mergeXLV2Versions(readQuorum, false, 1, metadataShallowVersions...)}
|
||||||
lfi, err := meta.ToFileInfo(bucket, object, "", inclFreeVers)
|
lfi, err := meta.ToFileInfo(bucket, object, "", inclFreeVers, allParts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for i := range errs {
|
for i := range errs {
|
||||||
if errs[i] == nil {
|
if errs[i] == nil {
|
||||||
@@ -631,7 +642,7 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
|
|||||||
|
|
||||||
// make sure to preserve this for diskmtime based healing bugfix.
|
// make sure to preserve this for diskmtime based healing bugfix.
|
||||||
diskMTime := metaFileInfos[index].DiskMTime
|
diskMTime := metaFileInfos[index].DiskMTime
|
||||||
metaFileInfos[index], errs[index] = metadataArray[index].ToFileInfo(bucket, object, versionID, inclFreeVers)
|
metaFileInfos[index], errs[index] = metadataArray[index].ToFileInfo(bucket, object, versionID, inclFreeVers, allParts)
|
||||||
if errs[index] != nil {
|
if errs[index] != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -660,7 +671,7 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
|||||||
if opts.VersionID != "" {
|
if opts.VersionID != "" {
|
||||||
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, readData)
|
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, readData)
|
||||||
} else {
|
} else {
|
||||||
metaArr, errs = readAllXL(ctx, disks, bucket, object, readData, opts.InclFreeVersions)
|
metaArr, errs = readAllXL(ctx, disks, bucket, object, readData, opts.InclFreeVersions, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
||||||
@@ -960,11 +971,6 @@ func (er erasureObjects) putMetacacheObject(ctx context.Context, key string, r *
|
|||||||
}
|
}
|
||||||
partsMetadata[i].Data = inlineBuffers[i].Bytes()
|
partsMetadata[i].Data = inlineBuffers[i].Bytes()
|
||||||
partsMetadata[i].AddObjectPart(1, "", n, data.ActualSize(), modTime, index, nil)
|
partsMetadata[i].AddObjectPart(1, "", n, data.ActualSize(), modTime, index, nil)
|
||||||
partsMetadata[i].Erasure.AddChecksumInfo(ChecksumInfo{
|
|
||||||
PartNumber: 1,
|
|
||||||
Algorithm: DefaultBitrotAlgorithm,
|
|
||||||
Hash: bitrotWriterSum(w),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill all the necessary metadata.
|
// Fill all the necessary metadata.
|
||||||
@@ -1296,11 +1302,6 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
}
|
}
|
||||||
// No need to add checksum to part. We already have it on the object.
|
// No need to add checksum to part. We already have it on the object.
|
||||||
partsMetadata[i].AddObjectPart(1, "", n, data.ActualSize(), modTime, compIndex, nil)
|
partsMetadata[i].AddObjectPart(1, "", n, data.ActualSize(), modTime, compIndex, nil)
|
||||||
partsMetadata[i].Erasure.AddChecksumInfo(ChecksumInfo{
|
|
||||||
PartNumber: 1,
|
|
||||||
Algorithm: DefaultBitrotAlgorithm,
|
|
||||||
Hash: bitrotWriterSum(w),
|
|
||||||
})
|
|
||||||
partsMetadata[i].Versioned = opts.Versioned || opts.VersionSuspended
|
partsMetadata[i].Versioned = opts.Versioned || opts.VersionSuspended
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1844,7 +1845,7 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s
|
|||||||
if opts.VersionID != "" {
|
if opts.VersionID != "" {
|
||||||
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
|
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
|
||||||
} else {
|
} else {
|
||||||
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false)
|
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
||||||
@@ -1917,7 +1918,7 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
|
|||||||
if opts.VersionID != "" {
|
if opts.VersionID != "" {
|
||||||
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
|
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
|
||||||
} else {
|
} else {
|
||||||
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false)
|
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
||||||
@@ -2161,7 +2162,7 @@ func (er erasureObjects) restoreTransitionedObject(ctx context.Context, bucket s
|
|||||||
return setRestoreHeaderFn(oi, toObjectErr(err, bucket, object))
|
return setRestoreHeaderFn(oi, toObjectErr(err, bucket, object))
|
||||||
}
|
}
|
||||||
defer gr.Close()
|
defer gr.Close()
|
||||||
hashReader, err := hash.NewReader(gr, gr.ObjInfo.Size, "", "", gr.ObjInfo.Size)
|
hashReader, err := hash.NewReader(ctx, gr, gr.ObjInfo.Size, "", "", gr.ObjInfo.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return setRestoreHeaderFn(oi, toObjectErr(err, bucket, object))
|
return setRestoreHeaderFn(oi, toObjectErr(err, bucket, object))
|
||||||
}
|
}
|
||||||
@@ -2186,7 +2187,7 @@ func (er erasureObjects) restoreTransitionedObject(ctx context.Context, bucket s
|
|||||||
|
|
||||||
// rehydrate the parts back on disk as per the original xl.meta prior to transition
|
// rehydrate the parts back on disk as per the original xl.meta prior to transition
|
||||||
for _, partInfo := range oi.Parts {
|
for _, partInfo := range oi.Parts {
|
||||||
hr, err := hash.NewReader(io.LimitReader(gr, partInfo.Size), partInfo.Size, "", "", partInfo.Size)
|
hr, err := hash.NewReader(ctx, io.LimitReader(gr, partInfo.Size), partInfo.Size, "", "", partInfo.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return setRestoreHeaderFn(oi, err)
|
return setRestoreHeaderFn(oi, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/v2/console"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
"github.com/minio/pkg/workers"
|
"github.com/minio/pkg/v2/workers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PoolDecommissionInfo currently decommissioning information
|
// PoolDecommissionInfo currently decommissioning information
|
||||||
@@ -68,6 +68,34 @@ type PoolDecommissionInfo struct {
|
|||||||
BytesFailed int64 `json:"bytesDecommissionedFailed" msg:"bf"`
|
BytesFailed int64 `json:"bytesDecommissionedFailed" msg:"bf"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone make a copy of PoolDecommissionInfo
|
||||||
|
func (pd *PoolDecommissionInfo) Clone() *PoolDecommissionInfo {
|
||||||
|
if pd == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if pd.StartTime.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &PoolDecommissionInfo{
|
||||||
|
StartTime: pd.StartTime,
|
||||||
|
StartSize: pd.StartSize,
|
||||||
|
TotalSize: pd.TotalSize,
|
||||||
|
CurrentSize: pd.CurrentSize,
|
||||||
|
Complete: pd.Complete,
|
||||||
|
Failed: pd.Failed,
|
||||||
|
Canceled: pd.Canceled,
|
||||||
|
QueuedBuckets: pd.QueuedBuckets,
|
||||||
|
DecommissionedBuckets: pd.DecommissionedBuckets,
|
||||||
|
Bucket: pd.Bucket,
|
||||||
|
Prefix: pd.Prefix,
|
||||||
|
Object: pd.Object,
|
||||||
|
ItemsDecommissioned: pd.ItemsDecommissioned,
|
||||||
|
ItemsDecommissionFailed: pd.ItemsDecommissionFailed,
|
||||||
|
BytesDone: pd.BytesDone,
|
||||||
|
BytesFailed: pd.BytesFailed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// bucketPop should be called when a bucket is done decommissioning.
|
// bucketPop should be called when a bucket is done decommissioning.
|
||||||
// Adds the bucket to the list of decommissioned buckets and updates resume numbers.
|
// Adds the bucket to the list of decommissioned buckets and updates resume numbers.
|
||||||
func (pd *PoolDecommissionInfo) bucketPop(bucket string) {
|
func (pd *PoolDecommissionInfo) bucketPop(bucket string) {
|
||||||
@@ -118,6 +146,16 @@ type PoolStatus struct {
|
|||||||
Decommission *PoolDecommissionInfo `json:"decommissionInfo,omitempty" msg:"dec"`
|
Decommission *PoolDecommissionInfo `json:"decommissionInfo,omitempty" msg:"dec"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone returns a copy of PoolStatus
|
||||||
|
func (ps PoolStatus) Clone() PoolStatus {
|
||||||
|
return PoolStatus{
|
||||||
|
ID: ps.ID,
|
||||||
|
CmdLine: ps.CmdLine,
|
||||||
|
LastUpdate: ps.LastUpdate,
|
||||||
|
Decommission: ps.Decommission.Clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//go:generate msgp -file $GOFILE -unexported
|
//go:generate msgp -file $GOFILE -unexported
|
||||||
type poolMeta struct {
|
type poolMeta struct {
|
||||||
Version int `msg:"v"`
|
Version int `msg:"v"`
|
||||||
@@ -375,16 +413,17 @@ func (p *poolMeta) load(ctx context.Context, pool *erasureSets, pools []*erasure
|
|||||||
|
|
||||||
func (p *poolMeta) CountItem(idx int, size int64, failed bool) {
|
func (p *poolMeta) CountItem(idx int, size int64, failed bool) {
|
||||||
pd := p.Pools[idx].Decommission
|
pd := p.Pools[idx].Decommission
|
||||||
if pd != nil {
|
if pd == nil {
|
||||||
if failed {
|
return
|
||||||
pd.ItemsDecommissionFailed++
|
|
||||||
pd.BytesFailed += size
|
|
||||||
} else {
|
|
||||||
pd.ItemsDecommissioned++
|
|
||||||
pd.BytesDone += size
|
|
||||||
}
|
|
||||||
p.Pools[idx].Decommission = pd
|
|
||||||
}
|
}
|
||||||
|
if failed {
|
||||||
|
pd.ItemsDecommissionFailed++
|
||||||
|
pd.BytesFailed += size
|
||||||
|
} else {
|
||||||
|
pd.ItemsDecommissioned++
|
||||||
|
pd.BytesDone += size
|
||||||
|
}
|
||||||
|
p.Pools[idx].Decommission = pd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *poolMeta) updateAfter(ctx context.Context, idx int, pools []*erasureSets, duration time.Duration) (bool, error) {
|
func (p *poolMeta) updateAfter(ctx context.Context, idx int, pools []*erasureSets, duration time.Duration) (bool, error) {
|
||||||
@@ -569,7 +608,7 @@ func (z *erasureServerPools) decommissionObject(ctx context.Context, bucket stri
|
|||||||
defer z.AbortMultipartUpload(ctx, bucket, objInfo.Name, res.UploadID, ObjectOptions{})
|
defer z.AbortMultipartUpload(ctx, bucket, objInfo.Name, res.UploadID, ObjectOptions{})
|
||||||
parts := make([]CompletePart, len(objInfo.Parts))
|
parts := make([]CompletePart, len(objInfo.Parts))
|
||||||
for i, part := range objInfo.Parts {
|
for i, part := range objInfo.Parts {
|
||||||
hr, err := hash.NewReader(io.LimitReader(gr, part.Size), part.Size, "", "", part.ActualSize)
|
hr, err := hash.NewReader(ctx, io.LimitReader(gr, part.Size), part.Size, "", "", part.ActualSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("decommissionObject: hash.NewReader() %w", err)
|
return fmt.Errorf("decommissionObject: hash.NewReader() %w", err)
|
||||||
}
|
}
|
||||||
@@ -603,7 +642,7 @@ func (z *erasureServerPools) decommissionObject(ctx context.Context, bucket stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hr, err := hash.NewReader(io.LimitReader(gr, objInfo.Size), objInfo.Size, "", "", actualSize)
|
hr, err := hash.NewReader(ctx, io.LimitReader(gr, objInfo.Size), objInfo.Size, "", "", actualSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("decommissionObject: hash.NewReader() %w", err)
|
return fmt.Errorf("decommissionObject: hash.NewReader() %w", err)
|
||||||
}
|
}
|
||||||
@@ -696,8 +735,6 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
|||||||
// Check if bucket is object locked.
|
// Check if bucket is object locked.
|
||||||
lr, _ := globalBucketObjectLockSys.Get(bi.Name)
|
lr, _ := globalBucketObjectLockSys.Get(bi.Name)
|
||||||
|
|
||||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
||||||
|
|
||||||
for setIdx, set := range pool.sets {
|
for setIdx, set := range pool.sets {
|
||||||
set := set
|
set := set
|
||||||
|
|
||||||
@@ -908,11 +945,11 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
|||||||
go decommissionEntry(entry)
|
go decommissionEntry(entry)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil || errors.Is(err, context.Canceled) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
setN := humanize.Ordinal(setIdx + 1)
|
setN := humanize.Ordinal(setIdx + 1)
|
||||||
retryDur := time.Duration(r.Float64() * float64(5*time.Second))
|
retryDur := time.Duration(rand.Float64() * float64(5*time.Second))
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("listing objects from %s set failed with %v, retrying in %v", setN, err, retryDur), "decom-listing-failed"+setN)
|
logger.LogOnceIf(ctx, fmt.Errorf("listing objects from %s set failed with %v, retrying in %v", setN, err, retryDur), "decom-listing-failed"+setN)
|
||||||
time.Sleep(retryDur)
|
time.Sleep(retryDur)
|
||||||
}
|
}
|
||||||
@@ -1185,15 +1222,15 @@ func (z *erasureServerPools) Status(ctx context.Context, idx int) (PoolStatus, e
|
|||||||
return PoolStatus{}, errInvalidArgument
|
return PoolStatus{}, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
z.poolMetaMutex.RLock()
|
|
||||||
defer z.poolMetaMutex.RUnlock()
|
|
||||||
|
|
||||||
pi, err := z.getDecommissionPoolSpaceInfo(idx)
|
pi, err := z.getDecommissionPoolSpaceInfo(idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PoolStatus{}, err
|
return PoolStatus{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
poolInfo := z.poolMeta.Pools[idx]
|
z.poolMetaMutex.RLock()
|
||||||
|
defer z.poolMetaMutex.RUnlock()
|
||||||
|
|
||||||
|
poolInfo := z.poolMeta.Pools[idx].Clone()
|
||||||
if poolInfo.Decommission != nil {
|
if poolInfo.Decommission != nil {
|
||||||
poolInfo.Decommission.TotalSize = pi.Total
|
poolInfo.Decommission.TotalSize = pi.Total
|
||||||
if poolInfo.Decommission.Failed || poolInfo.Decommission.Canceled {
|
if poolInfo.Decommission.Failed || poolInfo.Decommission.Canceled {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate msgp -file $GOFILE -unexported
|
//go:generate msgp -file $GOFILE -unexported
|
||||||
@@ -736,7 +736,7 @@ func (z *erasureServerPools) rebalanceObject(ctx context.Context, bucket string,
|
|||||||
|
|
||||||
parts := make([]CompletePart, len(oi.Parts))
|
parts := make([]CompletePart, len(oi.Parts))
|
||||||
for i, part := range oi.Parts {
|
for i, part := range oi.Parts {
|
||||||
hr, err := hash.NewReader(io.LimitReader(gr, part.Size), part.Size, "", "", part.ActualSize)
|
hr, err := hash.NewReader(ctx, io.LimitReader(gr, part.Size), part.Size, "", "", part.ActualSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rebalanceObject: hash.NewReader() %w", err)
|
return fmt.Errorf("rebalanceObject: hash.NewReader() %w", err)
|
||||||
}
|
}
|
||||||
@@ -766,7 +766,7 @@ func (z *erasureServerPools) rebalanceObject(ctx context.Context, bucket string,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hr, err := hash.NewReader(gr, oi.Size, "", "", actualSize)
|
hr, err := hash.NewReader(ctx, gr, oi.Size, "", "", actualSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rebalanceObject: hash.NewReader() %w", err)
|
return fmt.Errorf("rebalanceObject: hash.NewReader() %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/config/storageclass"
|
"github.com/minio/minio/internal/config/storageclass"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
)
|
)
|
||||||
|
|
||||||
type erasureServerPools struct {
|
type erasureServerPools struct {
|
||||||
@@ -1716,10 +1716,42 @@ func (z *erasureServerPools) deleteAll(ctx context.Context, bucket, prefix strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var listBucketsCache timedValue
|
||||||
|
|
||||||
// List all buckets from one of the serverPools, we are not doing merge
|
// List all buckets from one of the serverPools, we are not doing merge
|
||||||
// sort here just for simplification. As per design it is assumed
|
// sort here just for simplification. As per design it is assumed
|
||||||
// that all buckets are present on all serverPools.
|
// that all buckets are present on all serverPools.
|
||||||
func (z *erasureServerPools) ListBuckets(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
|
func (z *erasureServerPools) ListBuckets(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
|
||||||
|
if opts.Cached {
|
||||||
|
listBucketsCache.Once.Do(func() {
|
||||||
|
listBucketsCache.TTL = time.Second
|
||||||
|
|
||||||
|
listBucketsCache.Relax = true
|
||||||
|
listBucketsCache.Update = func() (interface{}, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
buckets, err = z.s3Peer.ListBuckets(ctx, opts)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range buckets {
|
||||||
|
createdAt, err := globalBucketMetadataSys.CreatedAt(buckets[i].Name)
|
||||||
|
if err == nil {
|
||||||
|
buckets[i].Created = createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buckets, nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
v, _ := listBucketsCache.Get()
|
||||||
|
if v != nil {
|
||||||
|
return v.([]BucketInfo), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return buckets, nil
|
||||||
|
}
|
||||||
|
|
||||||
buckets, err = z.s3Peer.ListBuckets(ctx, opts)
|
buckets, err = z.s3Peer.ListBuckets(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+2
-2
@@ -39,8 +39,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/bpool"
|
"github.com/minio/minio/internal/bpool"
|
||||||
"github.com/minio/minio/internal/dsync"
|
"github.com/minio/minio/internal/dsync"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/v2/console"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// setsDsyncLockers is encapsulated type for Close()
|
// setsDsyncLockers is encapsulated type for Close()
|
||||||
|
|||||||
+1
-2
@@ -33,7 +33,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/dsync"
|
"github.com/minio/minio/internal/dsync"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// list all errors that can be ignore in a bucket operation.
|
// list all errors that can be ignore in a bucket operation.
|
||||||
@@ -182,7 +182,6 @@ func getDisksInfo(disks []StorageAPI, endpoints []Endpoint) (disksInfo []madmin.
|
|||||||
DiskIndex: endpoints[index].DiskIdx,
|
DiskIndex: endpoints[index].DiskIdx,
|
||||||
}
|
}
|
||||||
if disks[index] == OfflineDisk {
|
if disks[index] == OfflineDisk {
|
||||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("%s: %s", errDiskNotFound, endpoints[index]), "get-disks-info-offline-"+di.Endpoint)
|
|
||||||
di.State = diskErrToDriveState(errDiskNotFound)
|
di.State = diskErrToDriveState(errDiskNotFound)
|
||||||
disksInfo[index] = di
|
disksInfo[index] = di
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/pubsub"
|
"github.com/minio/minio/internal/pubsub"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EventNotifier - notifies external systems about events in MinIO.
|
// EventNotifier - notifies external systems about events in MinIO.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/storageclass"
|
"github.com/minio/minio/internal/config/storageclass"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -275,7 +275,7 @@ func formatErasureMigrateV2ToV3(data []byte, export, version string) ([]byte, er
|
|||||||
|
|
||||||
tmpOld := pathJoin(export, minioMetaTmpDeletedBucket, mustGetUUID())
|
tmpOld := pathJoin(export, minioMetaTmpDeletedBucket, mustGetUUID())
|
||||||
if err := renameAll(pathJoin(export, minioMetaMultipartBucket),
|
if err := renameAll(pathJoin(export, minioMetaMultipartBucket),
|
||||||
tmpOld); err != nil && err != errFileNotFound {
|
tmpOld, export); err != nil && err != errFileNotFound {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("unable to rename (%s -> %s) %w, drive may be faulty please investigate",
|
logger.LogIf(GlobalContext, fmt.Errorf("unable to rename (%s -> %s) %w, drive may be faulty please investigate",
|
||||||
pathJoin(export, minioMetaMultipartBucket),
|
pathJoin(export, minioMetaMultipartBucket),
|
||||||
tmpOld,
|
tmpOld,
|
||||||
|
|||||||
+82
-44
@@ -21,11 +21,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -75,6 +75,69 @@ func (log *minioLogger) PrintResponse(sessionID string, code int, message string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func acceptSFTPConnection(conn net.Conn, config *ssh.ServerConfig) {
|
||||||
|
// For SSH handshake keep a 2 minute deadline, as OpensSSH default.
|
||||||
|
conn.SetDeadline(time.Now().Add(2 * time.Minute))
|
||||||
|
|
||||||
|
// Before use, a handshake must be performed on the incoming net.Conn.
|
||||||
|
sconn, chans, reqs, err := ssh.NewServerConn(conn, config)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once we are done with SSH handshake, remove deadline.
|
||||||
|
conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
|
// The incoming Request channel must be serviced.
|
||||||
|
go ssh.DiscardRequests(reqs)
|
||||||
|
|
||||||
|
// Service the incoming Channel channel.
|
||||||
|
for newChannel := range chans {
|
||||||
|
// Channels have a type, depending on the application level
|
||||||
|
// protocol intended. In the case of an SFTP session, this is "subsystem"
|
||||||
|
// with a payload string of "<length=4>sftp"
|
||||||
|
if newChannel.ChannelType() != "session" {
|
||||||
|
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, requests, err := newChannel.Accept()
|
||||||
|
if err != nil {
|
||||||
|
logger.LogOnceIf(context.Background(), fmt.Errorf("unable to accept the connection requests channel: %w", err), "accept-channel-sftp")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sessions have out-of-band requests such as "shell",
|
||||||
|
// "pty-req" and "env". Here we handle only the
|
||||||
|
// "subsystem" request.
|
||||||
|
go func(in <-chan *ssh.Request) {
|
||||||
|
for req := range in {
|
||||||
|
ok := false
|
||||||
|
if req.Type == "subsystem" {
|
||||||
|
if len(req.Payload) > 4 && string(req.Payload[4:]) == "sftp" {
|
||||||
|
ok = true
|
||||||
|
go handleSFTPConnection(channel, sconn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.WantReply {
|
||||||
|
// We only reply to SSH packets that have `sftp` payload, all other
|
||||||
|
// packets are rejected
|
||||||
|
req.Reply(ok, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(requests)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSFTPConnection(channel ssh.Channel, sconn *ssh.ServerConn) {
|
||||||
|
// Create the server instance for the channel using the handler we created above.
|
||||||
|
server := sftp.NewRequestServer(channel, NewSFTPDriver(sconn.Permissions), sftp.WithRSAllocator())
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
server.Serve()
|
||||||
|
}
|
||||||
|
|
||||||
func startSFTPServer(c *cli.Context) {
|
func startSFTPServer(c *cli.Context) {
|
||||||
args := c.StringSlice("sftp")
|
args := c.StringSlice("sftp")
|
||||||
|
|
||||||
@@ -176,54 +239,29 @@ func startSFTPServer(c *cli.Context) {
|
|||||||
|
|
||||||
logger.Info(fmt.Sprintf("MinIO SFTP Server listening on %s", net.JoinHostPort(publicIP, strconv.Itoa(port))))
|
logger.Info(fmt.Sprintf("MinIO SFTP Server listening on %s", net.JoinHostPort(publicIP, strconv.Itoa(port))))
|
||||||
|
|
||||||
|
var tempDelay time.Duration // how long to sleep on accept failure
|
||||||
|
|
||||||
for {
|
for {
|
||||||
nConn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(context.Background(), err)
|
// From https://github.com/golang/go/blob/4aa1efed4853ea067d665a952eee77c52faac774/src/net/http/server.go#L3046
|
||||||
continue
|
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||||||
}
|
if tempDelay == 0 {
|
||||||
|
tempDelay = 5 * time.Millisecond
|
||||||
// Before use, a handshake must be performed on the incoming net.Conn.
|
} else {
|
||||||
sconn, chans, reqs, err := ssh.NewServerConn(nConn, config)
|
tempDelay *= 2
|
||||||
if err != nil {
|
}
|
||||||
logger.LogIf(context.Background(), err)
|
if max := 1 * time.Second; tempDelay > max {
|
||||||
continue
|
tempDelay = max
|
||||||
}
|
}
|
||||||
|
logger.LogOnceIf(context.Background(), fmt.Errorf("error while accepting connections: %w, retrying in %s", err, tempDelay), "accept-limit-sftp")
|
||||||
// The incoming Request channel must be serviced.
|
time.Sleep(tempDelay)
|
||||||
go ssh.DiscardRequests(reqs)
|
|
||||||
|
|
||||||
// Service the incoming Channel channel.
|
|
||||||
for newChannel := range chans {
|
|
||||||
// Channels have a type, depending on the application level
|
|
||||||
// protocol intended. In the case of an SFTP session, this is "subsystem"
|
|
||||||
// with a payload string of "<length=4>sftp"
|
|
||||||
if newChannel.ChannelType() != "session" {
|
|
||||||
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
channel, requests, err := newChannel.Accept()
|
logger.Fatal(err, "unrecoverable error while accepting new connections")
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err, "unable to accept the connection requests channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sessions have out-of-band requests such as "shell",
|
|
||||||
// "pty-req" and "env". Here we handle only the
|
|
||||||
// "subsystem" request.
|
|
||||||
go func(in <-chan *ssh.Request) {
|
|
||||||
for req := range in {
|
|
||||||
// We only reply to SSH packets that have `sftp` payload.
|
|
||||||
req.Reply(req.Type == "subsystem" && string(req.Payload[4:]) == "sftp", nil)
|
|
||||||
}
|
|
||||||
}(requests)
|
|
||||||
|
|
||||||
server := sftp.NewRequestServer(channel, NewSFTPDriver(sconn.Permissions))
|
|
||||||
if err := server.Serve(); err == io.EOF {
|
|
||||||
server.Close()
|
|
||||||
} else if err != nil {
|
|
||||||
logger.Fatal(err, "unable to start SFTP server")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go acceptSFTPConnection(conn, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import (
|
|||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
xnet "github.com/minio/pkg/net"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/amztime"
|
"github.com/minio/minio/internal/amztime"
|
||||||
"github.com/minio/minio/internal/config/dns"
|
"github.com/minio/minio/internal/config/dns"
|
||||||
@@ -152,7 +152,7 @@ func setBrowserRedirectMiddleware(h http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
read := r.Method == http.MethodGet || r.Method == http.MethodHead
|
read := r.Method == http.MethodGet || r.Method == http.MethodHead
|
||||||
// Re-direction is handled specifically for browser requests.
|
// Re-direction is handled specifically for browser requests.
|
||||||
if guessIsBrowserReq(r) && read {
|
if guessIsBrowserReq(r) && read && globalBrowserRedirect {
|
||||||
// Fetch the redirect location if any.
|
// Fetch the redirect location if any.
|
||||||
if u := getRedirectLocation(r); u != nil {
|
if u := getRedirectLocation(r); u != nil {
|
||||||
// Employ a temporary re-direct.
|
// Employ a temporary re-direct.
|
||||||
|
|||||||
+48
-37
@@ -20,6 +20,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -28,8 +29,9 @@ import (
|
|||||||
"github.com/minio/minio/internal/color"
|
"github.com/minio/minio/internal/color"
|
||||||
"github.com/minio/minio/internal/config/storageclass"
|
"github.com/minio/minio/internal/config/storageclass"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/v2/console"
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
|
"github.com/minio/pkg/v2/workers"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -132,30 +134,8 @@ func getLocalBackgroundHealStatus(ctx context.Context, o ObjectLayer) (madmin.Bg
|
|||||||
return status, true
|
return status, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetHealSequence(ctx context.Context) *healSequence {
|
|
||||||
// Get background heal sequence to send elements to heal
|
|
||||||
for {
|
|
||||||
globalHealStateLK.RLock()
|
|
||||||
hstate := globalBackgroundHealState
|
|
||||||
globalHealStateLK.RUnlock()
|
|
||||||
|
|
||||||
if hstate == nil {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
bgSeq, ok := hstate.getHealSequenceByToken(bgHealingUUID)
|
|
||||||
if !ok {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return bgSeq
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// healErasureSet lists and heals all objects in a specific erasure set
|
// healErasureSet lists and heals all objects in a specific erasure set
|
||||||
func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string, tracker *healingTracker) error {
|
func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string, tracker *healingTracker) error {
|
||||||
bgSeq := mustGetHealSequence(ctx)
|
|
||||||
scanMode := madmin.HealNormalScan
|
scanMode := madmin.HealNormalScan
|
||||||
|
|
||||||
// Make sure to copy since `buckets slice`
|
// Make sure to copy since `buckets slice`
|
||||||
@@ -173,6 +153,30 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info, err := tracker.disk.DiskInfo(ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get disk information before healing it: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var numHealers uint64
|
||||||
|
|
||||||
|
if numCores := uint64(runtime.GOMAXPROCS(0)); info.NRRequests > numCores {
|
||||||
|
numHealers = numCores / 4
|
||||||
|
} else {
|
||||||
|
numHealers = info.NRRequests / 4
|
||||||
|
}
|
||||||
|
if numHealers < 4 {
|
||||||
|
numHealers = 4
|
||||||
|
}
|
||||||
|
// allow overriding this value as well..
|
||||||
|
if v := globalHealConfig.GetWorkers(); v > 0 {
|
||||||
|
numHealers = uint64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(fmt.Sprintf("Healing drive '%s' - use %d parallel workers.", tracker.disk.String(), numHealers))
|
||||||
|
|
||||||
|
jt, _ := workers.New(int(numHealers))
|
||||||
|
|
||||||
var retErr error
|
var retErr error
|
||||||
// Heal all buckets with all objects
|
// Heal all buckets with all objects
|
||||||
for _, bucket := range healBuckets {
|
for _, bucket := range healBuckets {
|
||||||
@@ -267,6 +271,8 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
|
|
||||||
// Note: updates from healEntry to tracker must be sent on results channel.
|
// Note: updates from healEntry to tracker must be sent on results channel.
|
||||||
healEntry := func(bucket string, entry metaCacheEntry) {
|
healEntry := func(bucket string, entry metaCacheEntry) {
|
||||||
|
defer jt.Give()
|
||||||
|
|
||||||
if entry.name == "" && len(entry.metadata) == 0 {
|
if entry.name == "" && len(entry.metadata) == 0 {
|
||||||
// ignore entries that don't have metadata.
|
// ignore entries that don't have metadata.
|
||||||
return
|
return
|
||||||
@@ -291,14 +297,17 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// erasureObjects layer needs object names to be encoded
|
||||||
|
encodedEntryName := encodeDirObject(entry.name)
|
||||||
|
|
||||||
var result healEntryResult
|
var result healEntryResult
|
||||||
fivs, err := entry.fileInfoVersions(bucket)
|
fivs, err := entry.fileInfoVersions(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := bgSeq.queueHealTask(healSource{
|
_, err := er.HealObject(ctx, bucket, encodedEntryName, "",
|
||||||
bucket: bucket,
|
madmin.HealOpts{
|
||||||
object: entry.name,
|
ScanMode: scanMode,
|
||||||
versionID: "",
|
Remove: healDeleteDangling,
|
||||||
}, madmin.HealItemObject)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isErrObjectNotFound(err) {
|
if isErrObjectNotFound(err) {
|
||||||
// queueing happens across namespace, ignore
|
// queueing happens across namespace, ignore
|
||||||
@@ -321,11 +330,11 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
if version.ModTime.After(tracker.Started) {
|
if version.ModTime.After(tracker.Started) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := bgSeq.queueHealTask(healSource{
|
if _, err := er.HealObject(ctx, bucket, encodedEntryName,
|
||||||
bucket: bucket,
|
version.VersionID, madmin.HealOpts{
|
||||||
object: version.Name,
|
ScanMode: scanMode,
|
||||||
versionID: version.VersionID,
|
Remove: healDeleteDangling,
|
||||||
}, madmin.HealItemObject); err != nil {
|
}); err != nil {
|
||||||
if isErrObjectNotFound(err) {
|
if isErrObjectNotFound(err) {
|
||||||
// queueing happens across namespace, ignore
|
// queueing happens across namespace, ignore
|
||||||
// objects that are not found.
|
// objects that are not found.
|
||||||
@@ -344,7 +353,6 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
} else {
|
} else {
|
||||||
result = healEntrySuccess(uint64(version.Size))
|
result = healEntrySuccess(uint64(version.Size))
|
||||||
}
|
}
|
||||||
bgSeq.logHeal(madmin.HealItemObject)
|
|
||||||
|
|
||||||
if !send(result) {
|
if !send(result) {
|
||||||
return
|
return
|
||||||
@@ -382,7 +390,8 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
minDisks: 1,
|
minDisks: 1,
|
||||||
reportNotFound: false,
|
reportNotFound: false,
|
||||||
agreed: func(entry metaCacheEntry) {
|
agreed: func(entry metaCacheEntry) {
|
||||||
healEntry(actualBucket, entry)
|
jt.Take()
|
||||||
|
go healEntry(actualBucket, entry)
|
||||||
},
|
},
|
||||||
partial: func(entries metaCacheEntries, _ []error) {
|
partial: func(entries metaCacheEntries, _ []error) {
|
||||||
entry, ok := entries.resolve(&resolver)
|
entry, ok := entries.resolve(&resolver)
|
||||||
@@ -391,10 +400,12 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
// proceed to heal nonetheless.
|
// proceed to heal nonetheless.
|
||||||
entry, _ = entries.firstFound()
|
entry, _ = entries.firstFound()
|
||||||
}
|
}
|
||||||
healEntry(actualBucket, *entry)
|
jt.Take()
|
||||||
|
go healEntry(actualBucket, *entry)
|
||||||
},
|
},
|
||||||
finished: nil,
|
finished: nil,
|
||||||
})
|
})
|
||||||
|
jt.Wait() // synchronize all the concurrent heal jobs
|
||||||
close(results)
|
close(results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Set this such that when we return this function
|
// Set this such that when we return this function
|
||||||
|
|||||||
+8
-2
@@ -52,8 +52,8 @@ import (
|
|||||||
levent "github.com/minio/minio/internal/config/lambda/event"
|
levent "github.com/minio/minio/internal/config/lambda/event"
|
||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/pubsub"
|
"github.com/minio/minio/internal/pubsub"
|
||||||
"github.com/minio/pkg/certs"
|
"github.com/minio/pkg/v2/certs"
|
||||||
xnet "github.com/minio/pkg/net"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// minio configuration related constants.
|
// minio configuration related constants.
|
||||||
@@ -152,6 +152,9 @@ var (
|
|||||||
// and it is automatically deduced.
|
// and it is automatically deduced.
|
||||||
globalBrowserRedirectURL *xnet.URL
|
globalBrowserRedirectURL *xnet.URL
|
||||||
|
|
||||||
|
// Disable redirect, default is enabled.
|
||||||
|
globalBrowserRedirect bool
|
||||||
|
|
||||||
// This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false.
|
// This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false.
|
||||||
globalInplaceUpdateDisabled = false
|
globalInplaceUpdateDisabled = false
|
||||||
|
|
||||||
@@ -402,6 +405,9 @@ var (
|
|||||||
|
|
||||||
// Captures all batch jobs metrics globally
|
// Captures all batch jobs metrics globally
|
||||||
globalBatchJobsMetrics batchJobMetrics
|
globalBatchJobsMetrics batchJobMetrics
|
||||||
|
|
||||||
|
// Indicates if server was started as `--address ":0"`
|
||||||
|
globalDynamicAPIPort bool
|
||||||
// Add new variable global values here.
|
// Add new variable global values here.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/mcontext"
|
"github.com/minio/minio/internal/mcontext"
|
||||||
xnet "github.com/minio/pkg/net"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -300,7 +300,8 @@ func collectAPIStats(api string, f http.HandlerFunc) http.HandlerFunc {
|
|||||||
globalHTTPStats.currentS3Requests.Inc(api)
|
globalHTTPStats.currentS3Requests.Inc(api)
|
||||||
defer globalHTTPStats.currentS3Requests.Dec(api)
|
defer globalHTTPStats.currentS3Requests.Dec(api)
|
||||||
|
|
||||||
if bucket != "" && bucket != minioReservedBucket {
|
_, err = globalBucketMetadataSys.Get(bucket) // check if this bucket exists.
|
||||||
|
if bucket != "" && bucket != minioReservedBucket && err == nil {
|
||||||
globalBucketHTTPStats.updateHTTPStats(bucket, api, nil)
|
globalBucketHTTPStats.updateHTTPStats(bucket, api, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +317,7 @@ func collectAPIStats(api string, f http.HandlerFunc) http.HandlerFunc {
|
|||||||
globalConnStats.incS3InputBytes(int64(tc.RequestRecorder.Size()))
|
globalConnStats.incS3InputBytes(int64(tc.RequestRecorder.Size()))
|
||||||
globalConnStats.incS3OutputBytes(int64(tc.ResponseRecorder.Size()))
|
globalConnStats.incS3OutputBytes(int64(tc.ResponseRecorder.Size()))
|
||||||
|
|
||||||
if bucket != "" && bucket != minioReservedBucket {
|
if bucket != "" && bucket != minioReservedBucket && err == nil {
|
||||||
globalBucketConnStats.incS3InputBytes(bucket, int64(tc.RequestRecorder.Size()))
|
globalBucketConnStats.incS3InputBytes(bucket, int64(tc.RequestRecorder.Size()))
|
||||||
globalBucketConnStats.incS3OutputBytes(bucket, int64(tc.ResponseRecorder.Size()))
|
globalBucketConnStats.incS3OutputBytes(bucket, int64(tc.ResponseRecorder.Size()))
|
||||||
globalBucketHTTPStats.updateHTTPStats(bucket, api, tc.ResponseRecorder)
|
globalBucketHTTPStats.updateHTTPStats(bucket, api, tc.ResponseRecorder)
|
||||||
|
|||||||
+67
-31
@@ -29,6 +29,7 @@ import (
|
|||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -343,6 +344,7 @@ var (
|
|||||||
policyDBServiceAccountsListKey = "policydb/service-accounts/"
|
policyDBServiceAccountsListKey = "policydb/service-accounts/"
|
||||||
policyDBGroupsListKey = "policydb/groups/"
|
policyDBGroupsListKey = "policydb/groups/"
|
||||||
|
|
||||||
|
// List of directories from which to read iam data into memory.
|
||||||
allListKeys = []string{
|
allListKeys = []string{
|
||||||
usersListKey,
|
usersListKey,
|
||||||
svcAccListKey,
|
svcAccListKey,
|
||||||
@@ -354,34 +356,60 @@ var (
|
|||||||
policyDBServiceAccountsListKey,
|
policyDBServiceAccountsListKey,
|
||||||
policyDBGroupsListKey,
|
policyDBGroupsListKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List of directories to skip: we do not read STS directories for better
|
||||||
|
// performance. STS credentials would be stored in memory when they are
|
||||||
|
// first used.
|
||||||
|
iamLoadSkipListKeySet = set.CreateStringSet(
|
||||||
|
stsListKey,
|
||||||
|
policyDBSTSUsersListKey,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (iamOS *IAMObjectStore) listAllIAMConfigItems(ctx context.Context) (map[string][]string, error) {
|
func (iamOS *IAMObjectStore) listAllIAMConfigItems(ctx context.Context) (map[string][]string, error) {
|
||||||
res := make(map[string][]string)
|
res := make(map[string][]string)
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator) {
|
for _, listKey := range allListKeys {
|
||||||
if item.Err != nil {
|
if iamLoadSkipListKeySet.Contains(listKey) {
|
||||||
return nil, item.Err
|
continue
|
||||||
}
|
}
|
||||||
|
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator+listKey) {
|
||||||
found := false
|
if item.Err != nil {
|
||||||
for _, listKey := range allListKeys {
|
return nil, item.Err
|
||||||
if strings.HasPrefix(item.Item, listKey) {
|
|
||||||
found = true
|
|
||||||
name := strings.TrimPrefix(item.Item, listKey)
|
|
||||||
res[listKey] = append(res[listKey], name)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
res[listKey] = append(res[listKey], item.Item)
|
||||||
|
|
||||||
if !found && (item.Item != "format.json") {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("unknown type of IAM file listed: %v", item.Item))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PurgeExpiredSTS - purge expired STS credentials from object store.
|
||||||
|
func (iamOS *IAMObjectStore) PurgeExpiredSTS(ctx context.Context) error {
|
||||||
|
if iamOS.objAPI == nil {
|
||||||
|
return errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrapTraceMsg("purging expired STS credentials")
|
||||||
|
// Scan STS users on disk and purge expired ones. We do not need to hold a
|
||||||
|
// lock with store.lock() here.
|
||||||
|
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator+stsListKey) {
|
||||||
|
if item.Err != nil {
|
||||||
|
return item.Err
|
||||||
|
}
|
||||||
|
userName := path.Dir(item.Item)
|
||||||
|
// loadUser() will delete expired user during the load - we do not need
|
||||||
|
// to keep the loaded user around in memory, so we reinitialize the map
|
||||||
|
// each time.
|
||||||
|
m := map[string]UserIdentity{}
|
||||||
|
if err := iamOS.loadUser(ctx, userName, stsUser, m); err != nil && err != errNoSuchUser {
|
||||||
|
logger.LogIf(GlobalContext, fmt.Errorf("unable to load user during STS purge: %w (%s)", err, item.Item))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Assumes cache is locked by caller.
|
// Assumes cache is locked by caller.
|
||||||
func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iamCache) error {
|
func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iamCache) error {
|
||||||
if iamOS.objAPI == nil {
|
if iamOS.objAPI == nil {
|
||||||
@@ -448,29 +476,37 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
|||||||
|
|
||||||
bootstrapTraceMsg("loading service accounts")
|
bootstrapTraceMsg("loading service accounts")
|
||||||
svcAccList := listedConfigItems[svcAccListKey]
|
svcAccList := listedConfigItems[svcAccListKey]
|
||||||
|
svcUsersMap := make(map[string]UserIdentity, len(svcAccList))
|
||||||
for _, item := range svcAccList {
|
for _, item := range svcAccList {
|
||||||
userName := path.Dir(item)
|
userName := path.Dir(item)
|
||||||
if err := iamOS.loadUser(ctx, userName, svcUser, cache.iamUsersMap); err != nil && err != errNoSuchUser {
|
if err := iamOS.loadUser(ctx, userName, svcUser, svcUsersMap); err != nil && err != errNoSuchUser {
|
||||||
return fmt.Errorf("unable to load the service account `%s`: %w", userName, err)
|
return fmt.Errorf("unable to load the service account `%s`: %w", userName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, svcAcc := range svcUsersMap {
|
||||||
bootstrapTraceMsg("loading STS users")
|
svcParent := svcAcc.Credentials.ParentUser
|
||||||
stsUsersList := listedConfigItems[stsListKey]
|
if _, ok := cache.iamUsersMap[svcParent]; !ok {
|
||||||
for _, item := range stsUsersList {
|
// If a service account's parent user is not in iamUsersMap, the
|
||||||
userName := path.Dir(item)
|
// parent is an STS account. Such accounts may have a policy mapped
|
||||||
if err := iamOS.loadUser(ctx, userName, stsUser, cache.iamUsersMap); err != nil && err != errNoSuchUser {
|
// on the parent user, so we load them. This is not needed for the
|
||||||
return fmt.Errorf("unable to load the STS user `%s`: %w", userName, err)
|
// initial server startup, however, it is needed for the case where
|
||||||
|
// the STS account's policy mapping (for example in LDAP mode) may
|
||||||
|
// be changed and the user's policy mapping in memory is stale
|
||||||
|
// (because the policy change notification was missed by the current
|
||||||
|
// server).
|
||||||
|
//
|
||||||
|
// The "policy not found" error is ignored because the STS account may
|
||||||
|
// not have a policy mapped via its parent (for e.g. in
|
||||||
|
// OIDC/AssumeRoleWithCustomToken/AssumeRoleWithCertificate).
|
||||||
|
err := iamOS.loadMappedPolicy(ctx, svcParent, stsUser, false, cache.iamSTSPolicyMap)
|
||||||
|
if err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||||
|
return fmt.Errorf("unable to load the policy mapping for the STS user `%s`: %w", svcParent, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Copy svcUsersMap to cache.iamUsersMap
|
||||||
bootstrapTraceMsg("loading STS policy mapping")
|
for k, v := range svcUsersMap {
|
||||||
stsPolicyMappingsList := listedConfigItems[policyDBSTSUsersListKey]
|
cache.iamUsersMap[k] = v
|
||||||
for _, item := range stsPolicyMappingsList {
|
|
||||||
stsName := strings.TrimSuffix(item, ".json")
|
|
||||||
if err := iamOS.loadMappedPolicy(ctx, stsName, stsUser, false, cache.iamUserPolicyMap); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
|
||||||
return fmt.Errorf("unable to load the policy mapping for the STS user `%s`: %w", stsName, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.buildUserGroupMemberships()
|
cache.buildUserGroupMemberships()
|
||||||
|
|||||||
+292
-124
@@ -34,7 +34,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
"github.com/minio/minio/internal/jwt"
|
"github.com/minio/minio/internal/jwt"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -212,12 +212,12 @@ func newMappedPolicy(policy string) MappedPolicy {
|
|||||||
// PolicyDoc represents an IAM policy with some metadata.
|
// PolicyDoc represents an IAM policy with some metadata.
|
||||||
type PolicyDoc struct {
|
type PolicyDoc struct {
|
||||||
Version int `json:",omitempty"`
|
Version int `json:",omitempty"`
|
||||||
Policy iampolicy.Policy
|
Policy policy.Policy
|
||||||
CreateDate time.Time `json:",omitempty"`
|
CreateDate time.Time `json:",omitempty"`
|
||||||
UpdateDate time.Time `json:",omitempty"`
|
UpdateDate time.Time `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPolicyDoc(p iampolicy.Policy) PolicyDoc {
|
func newPolicyDoc(p policy.Policy) PolicyDoc {
|
||||||
now := UTCNow().Round(time.Millisecond)
|
now := UTCNow().Round(time.Millisecond)
|
||||||
return PolicyDoc{
|
return PolicyDoc{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
@@ -228,14 +228,14 @@ func newPolicyDoc(p iampolicy.Policy) PolicyDoc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// defaultPolicyDoc - used to wrap a default policy as PolicyDoc.
|
// defaultPolicyDoc - used to wrap a default policy as PolicyDoc.
|
||||||
func defaultPolicyDoc(p iampolicy.Policy) PolicyDoc {
|
func defaultPolicyDoc(p policy.Policy) PolicyDoc {
|
||||||
return PolicyDoc{
|
return PolicyDoc{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Policy: p,
|
Policy: p,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *PolicyDoc) update(p iampolicy.Policy) {
|
func (d *PolicyDoc) update(p policy.Policy) {
|
||||||
now := UTCNow().Round(time.Millisecond)
|
now := UTCNow().Round(time.Millisecond)
|
||||||
d.UpdateDate = now
|
d.UpdateDate = now
|
||||||
if d.CreateDate.IsZero() {
|
if d.CreateDate.IsZero() {
|
||||||
@@ -248,7 +248,7 @@ func (d *PolicyDoc) update(p iampolicy.Policy) {
|
|||||||
// definitions.
|
// definitions.
|
||||||
//
|
//
|
||||||
// The on-disk format of policy definitions has changed (around early 12/2021)
|
// The on-disk format of policy definitions has changed (around early 12/2021)
|
||||||
// from iampolicy.Policy to PolicyDoc. To avoid a migration, loading supports
|
// from policy.Policy to PolicyDoc. To avoid a migration, loading supports
|
||||||
// both the old and the new formats.
|
// both the old and the new formats.
|
||||||
func (d *PolicyDoc) parseJSON(data []byte) error {
|
func (d *PolicyDoc) parseJSON(data []byte) error {
|
||||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
@@ -283,14 +283,22 @@ type iamCache struct {
|
|||||||
|
|
||||||
// map of policy names to policy definitions
|
// map of policy names to policy definitions
|
||||||
iamPolicyDocsMap map[string]PolicyDoc
|
iamPolicyDocsMap map[string]PolicyDoc
|
||||||
// map of usernames to credentials
|
|
||||||
|
// map of regular username to credentials
|
||||||
iamUsersMap map[string]UserIdentity
|
iamUsersMap map[string]UserIdentity
|
||||||
|
// map of regular username to policy names
|
||||||
|
iamUserPolicyMap map[string]MappedPolicy
|
||||||
|
|
||||||
|
// STS accounts are loaded on demand and not via the periodic IAM reload.
|
||||||
|
// map of STS access key to credentials
|
||||||
|
iamSTSAccountsMap map[string]UserIdentity
|
||||||
|
// map of STS access key to policy names
|
||||||
|
iamSTSPolicyMap map[string]MappedPolicy
|
||||||
|
|
||||||
// map of group names to group info
|
// map of group names to group info
|
||||||
iamGroupsMap map[string]GroupInfo
|
iamGroupsMap map[string]GroupInfo
|
||||||
// map of user names to groups they are a member of
|
// map of user names to groups they are a member of
|
||||||
iamUserGroupMemberships map[string]set.StringSet
|
iamUserGroupMemberships map[string]set.StringSet
|
||||||
// map of usernames/temporary access keys to policy names
|
|
||||||
iamUserPolicyMap map[string]MappedPolicy
|
|
||||||
// map of group names to policy names
|
// map of group names to policy names
|
||||||
iamGroupPolicyMap map[string]MappedPolicy
|
iamGroupPolicyMap map[string]MappedPolicy
|
||||||
}
|
}
|
||||||
@@ -299,9 +307,11 @@ func newIamCache() *iamCache {
|
|||||||
return &iamCache{
|
return &iamCache{
|
||||||
iamPolicyDocsMap: map[string]PolicyDoc{},
|
iamPolicyDocsMap: map[string]PolicyDoc{},
|
||||||
iamUsersMap: map[string]UserIdentity{},
|
iamUsersMap: map[string]UserIdentity{},
|
||||||
|
iamUserPolicyMap: map[string]MappedPolicy{},
|
||||||
|
iamSTSAccountsMap: map[string]UserIdentity{},
|
||||||
|
iamSTSPolicyMap: map[string]MappedPolicy{},
|
||||||
iamGroupsMap: map[string]GroupInfo{},
|
iamGroupsMap: map[string]GroupInfo{},
|
||||||
iamUserGroupMemberships: map[string]set.StringSet{},
|
iamUserGroupMemberships: map[string]set.StringSet{},
|
||||||
iamUserPolicyMap: map[string]MappedPolicy{},
|
|
||||||
iamGroupPolicyMap: map[string]MappedPolicy{},
|
iamGroupPolicyMap: map[string]MappedPolicy{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,9 +364,9 @@ func (c *iamCache) removeGroupFromMembershipsMap(group string) {
|
|||||||
// information in IAM (i.e sys.iam*Map) - this info is stored only in the STS
|
// information in IAM (i.e sys.iam*Map) - this info is stored only in the STS
|
||||||
// generated credentials. Thus we skip looking up group memberships, user map,
|
// generated credentials. Thus we skip looking up group memberships, user map,
|
||||||
// and group map and check the appropriate policy maps directly.
|
// and group map and check the appropriate policy maps directly.
|
||||||
func (c *iamCache) policyDBGet(mode UsersSysType, name string, isGroup bool) ([]string, time.Time, error) {
|
func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([]string, time.Time, error) {
|
||||||
if isGroup {
|
if isGroup {
|
||||||
if mode == MinIOUsersSysType {
|
if store.getUsersSysType() == MinIOUsersSysType {
|
||||||
g, ok := c.iamGroupsMap[name]
|
g, ok := c.iamGroupsMap[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, time.Time{}, errNoSuchGroup
|
return nil, time.Time{}, errNoSuchGroup
|
||||||
@@ -381,7 +391,20 @@ func (c *iamCache) policyDBGet(mode UsersSysType, name string, isGroup bool) ([]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mp := c.iamUserPolicyMap[name]
|
// For internal IDP regular/service account user accounts, the policy
|
||||||
|
// mapping is iamUserPolicyMap. For STS accounts, the parent user would be
|
||||||
|
// passed here and we lookup the mapping in iamSTSPolicyMap.
|
||||||
|
mp, ok := c.iamUserPolicyMap[name]
|
||||||
|
if !ok {
|
||||||
|
// Since user "name" could be a parent user of an STS account, we lookup
|
||||||
|
// mappings for those too.
|
||||||
|
mp, ok = c.iamSTSPolicyMap[name]
|
||||||
|
if !ok {
|
||||||
|
// Attempt to load parent user mapping for STS accounts
|
||||||
|
store.loadMappedPolicy(context.TODO(), name, stsUser, false, c.iamSTSPolicyMap)
|
||||||
|
mp = c.iamSTSPolicyMap[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// returned policy could be empty
|
// returned policy could be empty
|
||||||
policies := mp.toSlice()
|
policies := mp.toSlice()
|
||||||
@@ -407,7 +430,11 @@ func (c *iamCache) updateUserWithClaims(key string, u UserIdentity) error {
|
|||||||
}
|
}
|
||||||
u.Credentials.Claims = jwtClaims.Map()
|
u.Credentials.Claims = jwtClaims.Map()
|
||||||
}
|
}
|
||||||
c.iamUsersMap[key] = u
|
if !u.Credentials.IsTemp() {
|
||||||
|
c.iamUsersMap[key] = u
|
||||||
|
} else {
|
||||||
|
c.iamSTSAccountsMap[key] = u
|
||||||
|
}
|
||||||
c.updatedAt = time.Now()
|
c.updatedAt = time.Now()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -452,13 +479,23 @@ type iamStorageWatcher interface {
|
|||||||
|
|
||||||
// Set default canned policies only if not already overridden by users.
|
// Set default canned policies only if not already overridden by users.
|
||||||
func setDefaultCannedPolicies(policies map[string]PolicyDoc) {
|
func setDefaultCannedPolicies(policies map[string]PolicyDoc) {
|
||||||
for _, v := range iampolicy.DefaultPolicies {
|
for _, v := range policy.DefaultPolicies {
|
||||||
if _, ok := policies[v.Name]; !ok {
|
if _, ok := policies[v.Name]; !ok {
|
||||||
policies[v.Name] = defaultPolicyDoc(v.Definition)
|
policies[v.Name] = defaultPolicyDoc(v.Definition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PurgeExpiredSTS - purges expired STS credentials.
|
||||||
|
func (store *IAMStoreSys) PurgeExpiredSTS(ctx context.Context) error {
|
||||||
|
iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore)
|
||||||
|
if !ok {
|
||||||
|
// No purging is done for non-object storage.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return iamOS.PurgeExpiredSTS(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// LoadIAMCache reads all IAM items and populates a new iamCache object and
|
// LoadIAMCache reads all IAM items and populates a new iamCache object and
|
||||||
// replaces the in-memory cache object.
|
// replaces the in-memory cache object.
|
||||||
func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
|
func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
|
||||||
@@ -512,18 +549,6 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrapTraceMsg("loading STS users")
|
|
||||||
// load STS temp users
|
|
||||||
if err := store.loadUsers(ctx, stsUser, newCache.iamUsersMap); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
bootstrapTraceMsg("loading STS policy mapping")
|
|
||||||
// load STS policy mappings
|
|
||||||
if err := store.loadMappedPolicies(ctx, stsUser, false, newCache.iamUserPolicyMap); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newCache.buildUserGroupMemberships()
|
newCache.buildUserGroupMemberships()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,9 +560,9 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
|
|||||||
// were changes to the in-memory cache we should wait for the next
|
// were changes to the in-memory cache we should wait for the next
|
||||||
// cycle until we can safely update the in-memory cache.
|
// cycle until we can safely update the in-memory cache.
|
||||||
//
|
//
|
||||||
// An in-memory cache must be replaced only if we know for sure that
|
// An in-memory cache must be replaced only if we know for sure that the
|
||||||
// the values loaded from disk are not stale. They might be stale
|
// values loaded from disk are not stale. They might be stale if the
|
||||||
// if the cached.updatedAt is recent than the refresh cycle began.
|
// cached.updatedAt is more recent than the refresh cycle began.
|
||||||
if cache.updatedAt.Before(loadedAt) {
|
if cache.updatedAt.Before(loadedAt) {
|
||||||
// No one has updated anything since the config was loaded,
|
// No one has updated anything since the config was loaded,
|
||||||
// so we just replace whatever is on the disk into memory.
|
// so we just replace whatever is on the disk into memory.
|
||||||
@@ -547,6 +572,15 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
|
|||||||
cache.iamUserGroupMemberships = newCache.iamUserGroupMemberships
|
cache.iamUserGroupMemberships = newCache.iamUserGroupMemberships
|
||||||
cache.iamUserPolicyMap = newCache.iamUserPolicyMap
|
cache.iamUserPolicyMap = newCache.iamUserPolicyMap
|
||||||
cache.iamUsersMap = newCache.iamUsersMap
|
cache.iamUsersMap = newCache.iamUsersMap
|
||||||
|
// For STS policy map, we need to merge the new cache with the existing
|
||||||
|
// cache because the periodic IAM reload is partial. The periodic load
|
||||||
|
// here is to account for STS policy mapping changes that should apply
|
||||||
|
// for service accounts derived from such STS accounts (i.e. LDAP STS
|
||||||
|
// accounts).
|
||||||
|
for k, v := range newCache.iamSTSPolicyMap {
|
||||||
|
cache.iamSTSPolicyMap[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
cache.updatedAt = time.Now()
|
cache.updatedAt = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,6 +605,10 @@ func (store *IAMStoreSys) GetUser(user string) (UserIdentity, bool) {
|
|||||||
defer store.runlock()
|
defer store.runlock()
|
||||||
|
|
||||||
u, ok := cache.iamUsersMap[user]
|
u, ok := cache.iamUsersMap[user]
|
||||||
|
if !ok {
|
||||||
|
// Check the sts map
|
||||||
|
u, ok = cache.iamSTSAccountsMap[user]
|
||||||
|
}
|
||||||
return u, ok
|
return u, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,14 +673,14 @@ func (store *IAMStoreSys) PolicyDBGet(name string, isGroup bool, groups ...strin
|
|||||||
cache := store.rlock()
|
cache := store.rlock()
|
||||||
defer store.runlock()
|
defer store.runlock()
|
||||||
|
|
||||||
policies, _, err := cache.policyDBGet(store.getUsersSysType(), name, isGroup)
|
policies, _, err := cache.policyDBGet(store, name, isGroup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isGroup {
|
if !isGroup {
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
ps, _, err := cache.policyDBGet(store.getUsersSysType(), group, true)
|
ps, _, err := cache.policyDBGet(store, group, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -828,7 +866,7 @@ func (store *IAMStoreSys) GetGroupDescription(group string) (gd madmin.GroupDesc
|
|||||||
cache := store.rlock()
|
cache := store.rlock()
|
||||||
defer store.runlock()
|
defer store.runlock()
|
||||||
|
|
||||||
ps, updatedAt, err := cache.policyDBGet(store.getUsersSysType(), group, true)
|
ps, updatedAt, err := cache.policyDBGet(store, group, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gd, err
|
return gd, err
|
||||||
}
|
}
|
||||||
@@ -928,7 +966,16 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
|
|||||||
// Load existing policy mapping
|
// Load existing policy mapping
|
||||||
var mp MappedPolicy
|
var mp MappedPolicy
|
||||||
if !isGroup {
|
if !isGroup {
|
||||||
mp = cache.iamUserPolicyMap[name]
|
if userType == stsUser {
|
||||||
|
stsMap := map[string]MappedPolicy{}
|
||||||
|
|
||||||
|
// Attempt to load parent user mapping for STS accounts
|
||||||
|
store.loadMappedPolicy(context.TODO(), name, stsUser, false, stsMap)
|
||||||
|
|
||||||
|
mp = stsMap[name]
|
||||||
|
} else {
|
||||||
|
mp = cache.iamUserPolicyMap[name]
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if store.getUsersSysType() == MinIOUsersSysType {
|
if store.getUsersSysType() == MinIOUsersSysType {
|
||||||
g, ok := cache.iamGroupsMap[name]
|
g, ok := cache.iamGroupsMap[name]
|
||||||
@@ -981,7 +1028,11 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !isGroup {
|
if !isGroup {
|
||||||
cache.iamUserPolicyMap[name] = newPolicyMapping
|
if userType == stsUser {
|
||||||
|
cache.iamSTSPolicyMap[name] = newPolicyMapping
|
||||||
|
} else {
|
||||||
|
cache.iamUserPolicyMap[name] = newPolicyMapping
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
cache.iamGroupPolicyMap[name] = newPolicyMapping
|
cache.iamGroupPolicyMap[name] = newPolicyMapping
|
||||||
}
|
}
|
||||||
@@ -1015,7 +1066,11 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
|
|||||||
return updatedAt, err
|
return updatedAt, err
|
||||||
}
|
}
|
||||||
if !isGroup {
|
if !isGroup {
|
||||||
delete(cache.iamUserPolicyMap, name)
|
if userType == stsUser {
|
||||||
|
delete(cache.iamSTSPolicyMap, name)
|
||||||
|
} else {
|
||||||
|
delete(cache.iamUserPolicyMap, name)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
delete(cache.iamGroupPolicyMap, name)
|
delete(cache.iamGroupPolicyMap, name)
|
||||||
}
|
}
|
||||||
@@ -1035,7 +1090,11 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
|
|||||||
return updatedAt, err
|
return updatedAt, err
|
||||||
}
|
}
|
||||||
if !isGroup {
|
if !isGroup {
|
||||||
cache.iamUserPolicyMap[name] = mp
|
if userType == stsUser {
|
||||||
|
cache.iamSTSPolicyMap[name] = mp
|
||||||
|
} else {
|
||||||
|
cache.iamUserPolicyMap[name] = mp
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
cache.iamGroupPolicyMap[name] = mp
|
cache.iamGroupPolicyMap[name] = mp
|
||||||
}
|
}
|
||||||
@@ -1104,7 +1163,9 @@ func (store *IAMStoreSys) DeletePolicy(ctx context.Context, policy string) error
|
|||||||
cache := store.lock()
|
cache := store.lock()
|
||||||
defer store.unlock()
|
defer store.unlock()
|
||||||
|
|
||||||
// Check if policy is mapped to any existing user or group.
|
// Check if policy is mapped to any existing user or group. If so, we do not
|
||||||
|
// allow deletion of the policy. If the policy is mapped to an STS account,
|
||||||
|
// we do allow deletion.
|
||||||
users := []string{}
|
users := []string{}
|
||||||
groups := []string{}
|
groups := []string{}
|
||||||
for u, mp := range cache.iamUserPolicyMap {
|
for u, mp := range cache.iamUserPolicyMap {
|
||||||
@@ -1148,16 +1209,16 @@ func (store *IAMStoreSys) DeletePolicy(ctx context.Context, policy string) error
|
|||||||
|
|
||||||
// GetPolicy - gets the policy definition. Allows specifying multiple comma
|
// GetPolicy - gets the policy definition. Allows specifying multiple comma
|
||||||
// separated policies - returns a combined policy.
|
// separated policies - returns a combined policy.
|
||||||
func (store *IAMStoreSys) GetPolicy(name string) (iampolicy.Policy, error) {
|
func (store *IAMStoreSys) GetPolicy(name string) (policy.Policy, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return iampolicy.Policy{}, errInvalidArgument
|
return policy.Policy{}, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
cache := store.rlock()
|
cache := store.rlock()
|
||||||
defer store.runlock()
|
defer store.runlock()
|
||||||
|
|
||||||
policies := newMappedPolicy(name).toSlice()
|
policies := newMappedPolicy(name).toSlice()
|
||||||
var combinedPolicy iampolicy.Policy
|
var toMerge []policy.Policy
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
if policy == "" {
|
if policy == "" {
|
||||||
continue
|
continue
|
||||||
@@ -1166,9 +1227,9 @@ func (store *IAMStoreSys) GetPolicy(name string) (iampolicy.Policy, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return v.Policy, errNoSuchPolicy
|
return v.Policy, errNoSuchPolicy
|
||||||
}
|
}
|
||||||
combinedPolicy = combinedPolicy.Merge(v.Policy)
|
toMerge = append(toMerge, v.Policy)
|
||||||
}
|
}
|
||||||
return combinedPolicy, nil
|
return policy.MergePolicies(toMerge...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPolicyDoc - gets the policy doc which has the policy and some metadata.
|
// GetPolicyDoc - gets the policy doc which has the policy and some metadata.
|
||||||
@@ -1190,7 +1251,7 @@ func (store *IAMStoreSys) GetPolicyDoc(name string) (r PolicyDoc, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetPolicy - creates a policy with name.
|
// SetPolicy - creates a policy with name.
|
||||||
func (store *IAMStoreSys) SetPolicy(ctx context.Context, name string, policy iampolicy.Policy) (time.Time, error) {
|
func (store *IAMStoreSys) SetPolicy(ctx context.Context, name string, policy policy.Policy) (time.Time, error) {
|
||||||
if policy.IsEmpty() || name == "" {
|
if policy.IsEmpty() || name == "" {
|
||||||
return time.Time{}, errInvalidArgument
|
return time.Time{}, errInvalidArgument
|
||||||
}
|
}
|
||||||
@@ -1220,7 +1281,7 @@ func (store *IAMStoreSys) SetPolicy(ctx context.Context, name string, policy iam
|
|||||||
|
|
||||||
// ListPolicies - fetches all policies from storage and updates cache as well.
|
// ListPolicies - fetches all policies from storage and updates cache as well.
|
||||||
// If bucketName is non-empty, returns policies matching the bucket.
|
// If bucketName is non-empty, returns policies matching the bucket.
|
||||||
func (store *IAMStoreSys) ListPolicies(ctx context.Context, bucketName string) (map[string]iampolicy.Policy, error) {
|
func (store *IAMStoreSys) ListPolicies(ctx context.Context, bucketName string) (map[string]policy.Policy, error) {
|
||||||
cache := store.lock()
|
cache := store.lock()
|
||||||
defer store.unlock()
|
defer store.unlock()
|
||||||
|
|
||||||
@@ -1236,7 +1297,7 @@ func (store *IAMStoreSys) ListPolicies(ctx context.Context, bucketName string) (
|
|||||||
cache.iamPolicyDocsMap = m
|
cache.iamPolicyDocsMap = m
|
||||||
cache.updatedAt = time.Now()
|
cache.updatedAt = time.Now()
|
||||||
|
|
||||||
ret := map[string]iampolicy.Policy{}
|
ret := map[string]policy.Policy{}
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
if bucketName == "" || v.Policy.MatchResource(bucketName) {
|
if bucketName == "" || v.Policy.MatchResource(bucketName) {
|
||||||
ret[k] = v.Policy
|
ret[k] = v.Policy
|
||||||
@@ -1289,10 +1350,10 @@ func (store *IAMStoreSys) listPolicyDocs(ctx context.Context, bucketName string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// helper function - does not take locks.
|
// helper function - does not take locks.
|
||||||
func filterPolicies(cache *iamCache, policyName string, bucketName string) (string, iampolicy.Policy) {
|
func filterPolicies(cache *iamCache, policyName string, bucketName string) (string, policy.Policy) {
|
||||||
var policies []string
|
var policies []string
|
||||||
mp := newMappedPolicy(policyName)
|
mp := newMappedPolicy(policyName)
|
||||||
combinedPolicy := iampolicy.Policy{}
|
var toMerge []policy.Policy
|
||||||
for _, policy := range mp.toSlice() {
|
for _, policy := range mp.toSlice() {
|
||||||
if policy == "" {
|
if policy == "" {
|
||||||
continue
|
continue
|
||||||
@@ -1303,10 +1364,10 @@ func filterPolicies(cache *iamCache, policyName string, bucketName string) (stri
|
|||||||
}
|
}
|
||||||
if bucketName == "" || p.Policy.MatchResource(bucketName) {
|
if bucketName == "" || p.Policy.MatchResource(bucketName) {
|
||||||
policies = append(policies, policy)
|
policies = append(policies, policy)
|
||||||
combinedPolicy = combinedPolicy.Merge(p.Policy)
|
toMerge = append(toMerge, p.Policy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return strings.Join(policies, ","), combinedPolicy
|
return strings.Join(policies, ","), policy.MergePolicies(toMerge...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterPolicies - accepts a comma separated list of policy names as a string
|
// FilterPolicies - accepts a comma separated list of policy names as a string
|
||||||
@@ -1314,7 +1375,7 @@ func filterPolicies(cache *iamCache, policyName string, bucketName string) (stri
|
|||||||
// bucketName is non-empty, additionally filters policies matching the bucket.
|
// bucketName is non-empty, additionally filters policies matching the bucket.
|
||||||
// The first returned value is the list of currently existing policies, and the
|
// The first returned value is the list of currently existing policies, and the
|
||||||
// second is their combined policy definition.
|
// second is their combined policy definition.
|
||||||
func (store *IAMStoreSys) FilterPolicies(policyName string, bucketName string) (string, iampolicy.Policy) {
|
func (store *IAMStoreSys) FilterPolicies(policyName string, bucketName string) (string, policy.Policy) {
|
||||||
cache := store.rlock()
|
cache := store.rlock()
|
||||||
defer store.runlock()
|
defer store.runlock()
|
||||||
|
|
||||||
@@ -1403,6 +1464,9 @@ func (store *IAMStoreSys) GetUsersWithMappedPolicies() map[string]string {
|
|||||||
for k, v := range cache.iamUserPolicyMap {
|
for k, v := range cache.iamUserPolicyMap {
|
||||||
result[k] = v.Policies
|
result[k] = v.Policies
|
||||||
}
|
}
|
||||||
|
for k, v := range cache.iamSTSPolicyMap {
|
||||||
|
result[k] = v.Policies
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1425,10 +1489,25 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, v := range cache.iamSTSAccountsMap {
|
||||||
|
if v.Credentials.ParentUser == name {
|
||||||
|
groups = v.Credentials.Groups
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
mappedPolicy, ok := cache.iamUserPolicyMap[name]
|
mappedPolicy, ok := cache.iamUserPolicyMap[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return u, errNoSuchUser
|
mappedPolicy, ok = cache.iamSTSPolicyMap[name]
|
||||||
}
|
}
|
||||||
|
if !ok {
|
||||||
|
// Attempt to load parent user mapping for STS accounts
|
||||||
|
store.loadMappedPolicy(context.TODO(), name, stsUser, false, cache.iamSTSPolicyMap)
|
||||||
|
mappedPolicy, ok = cache.iamSTSPolicyMap[name]
|
||||||
|
if !ok {
|
||||||
|
return u, errNoSuchUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return madmin.UserInfo{
|
return madmin.UserInfo{
|
||||||
PolicyName: mappedPolicy.Policies,
|
PolicyName: mappedPolicy.Policies,
|
||||||
MemberOf: groups,
|
MemberOf: groups,
|
||||||
@@ -1467,8 +1546,11 @@ func (store *IAMStoreSys) PolicyMappingNotificationHandler(ctx context.Context,
|
|||||||
cache := store.lock()
|
cache := store.lock()
|
||||||
defer store.unlock()
|
defer store.unlock()
|
||||||
|
|
||||||
m := cache.iamGroupPolicyMap
|
var m map[string]MappedPolicy
|
||||||
if !isGroup {
|
switch {
|
||||||
|
case isGroup:
|
||||||
|
m = cache.iamGroupPolicyMap
|
||||||
|
default:
|
||||||
m = cache.iamUserPolicyMap
|
m = cache.iamUserPolicyMap
|
||||||
}
|
}
|
||||||
err := store.loadMappedPolicy(ctx, userOrGroup, userType, isGroup, m)
|
err := store.loadMappedPolicy(ctx, userOrGroup, userType, isGroup, m)
|
||||||
@@ -1493,10 +1575,23 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
|
|||||||
cache := store.lock()
|
cache := store.lock()
|
||||||
defer store.unlock()
|
defer store.unlock()
|
||||||
|
|
||||||
err := store.loadUser(ctx, accessKey, userType, cache.iamUsersMap)
|
var m map[string]UserIdentity
|
||||||
|
switch userType {
|
||||||
|
case stsUser:
|
||||||
|
m = cache.iamSTSAccountsMap
|
||||||
|
default:
|
||||||
|
m = cache.iamUsersMap
|
||||||
|
}
|
||||||
|
err := store.loadUser(ctx, accessKey, userType, m)
|
||||||
|
|
||||||
if err == errNoSuchUser {
|
if err == errNoSuchUser {
|
||||||
// User was deleted - we update the cache.
|
// User was deleted - we update the cache.
|
||||||
delete(cache.iamUsersMap, accessKey)
|
delete(m, accessKey)
|
||||||
|
|
||||||
|
// Since cache was updated, we update the timestamp.
|
||||||
|
defer func() {
|
||||||
|
cache.updatedAt = time.Now()
|
||||||
|
}()
|
||||||
|
|
||||||
// 1. Start with updating user-group memberships
|
// 1. Start with updating user-group memberships
|
||||||
if store.getUsersSysType() == MinIOUsersSysType {
|
if store.getUsersSysType() == MinIOUsersSysType {
|
||||||
@@ -1514,12 +1609,14 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
|
|||||||
|
|
||||||
// 2. Remove any derived credentials from memory
|
// 2. Remove any derived credentials from memory
|
||||||
if userType == regUser {
|
if userType == regUser {
|
||||||
for _, u := range cache.iamUsersMap {
|
for k, u := range cache.iamUsersMap {
|
||||||
if u.Credentials.IsServiceAccount() && u.Credentials.ParentUser == accessKey {
|
if u.Credentials.IsServiceAccount() && u.Credentials.ParentUser == accessKey {
|
||||||
delete(cache.iamUsersMap, u.Credentials.AccessKey)
|
delete(cache.iamUsersMap, k)
|
||||||
}
|
}
|
||||||
if u.Credentials.IsTemp() && u.Credentials.ParentUser == accessKey {
|
}
|
||||||
delete(cache.iamUsersMap, u.Credentials.AccessKey)
|
for k, u := range cache.iamSTSAccountsMap {
|
||||||
|
if u.Credentials.ParentUser == accessKey {
|
||||||
|
delete(cache.iamSTSAccountsMap, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1527,7 +1624,6 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
|
|||||||
// 3. Delete any mapped policy
|
// 3. Delete any mapped policy
|
||||||
delete(cache.iamUserPolicyMap, accessKey)
|
delete(cache.iamUserPolicyMap, accessKey)
|
||||||
|
|
||||||
cache.updatedAt = time.Now()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1535,31 +1631,43 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if userType != svcUser {
|
// Since cache was updated, we update the timestamp.
|
||||||
err = store.loadMappedPolicy(ctx, accessKey, userType, false, cache.iamUserPolicyMap)
|
defer func() {
|
||||||
// Ignore policy not mapped error
|
cache.updatedAt = time.Now()
|
||||||
if err != nil && !errors.Is(err, errNoSuchPolicy) {
|
}()
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are on purpose not persisting the policy map for parent
|
cred := m[accessKey].Credentials
|
||||||
// user, although this is a hack, it is a good enough hack
|
switch userType {
|
||||||
// at this point in time - we need to overhaul our OIDC
|
case stsUser:
|
||||||
// usage with service accounts with a more cleaner implementation
|
// For STS accounts a policy is mapped to the parent user (if a mapping exists).
|
||||||
//
|
err = store.loadMappedPolicy(ctx, cred.ParentUser, userType, false, cache.iamSTSPolicyMap)
|
||||||
// This mapping is necessary to ensure that valid credentials
|
case svcUser:
|
||||||
// have necessary ParentUser present - this is mainly for only
|
// For service accounts, the parent may be a regular (internal) IDP
|
||||||
// webIdentity based STS tokens.
|
// user or a "virtual" user (parent of an STS account).
|
||||||
u, ok := cache.iamUsersMap[accessKey]
|
//
|
||||||
if ok {
|
// If parent is a regular user => policy mapping is done on that parent itself.
|
||||||
cred := u.Credentials
|
//
|
||||||
if cred.IsTemp() && cred.ParentUser != "" && cred.ParentUser != globalActiveCred.AccessKey {
|
// If parent is "virtual" => policy mapping is done on the virtual
|
||||||
if _, ok := cache.iamUserPolicyMap[cred.ParentUser]; !ok {
|
// parent and that virtual parent is an stsUser.
|
||||||
cache.iamUserPolicyMap[cred.ParentUser] = cache.iamUserPolicyMap[accessKey]
|
//
|
||||||
cache.updatedAt = time.Now()
|
// To load the appropriate mapping, we check the parent user type.
|
||||||
}
|
_, parentIsRegularUser := cache.iamUsersMap[cred.ParentUser]
|
||||||
|
if parentIsRegularUser {
|
||||||
|
err = store.loadMappedPolicy(ctx, cred.ParentUser, regUser, false, cache.iamUserPolicyMap)
|
||||||
|
} else {
|
||||||
|
err = store.loadMappedPolicy(ctx, cred.ParentUser, stsUser, false, cache.iamSTSPolicyMap)
|
||||||
}
|
}
|
||||||
|
case regUser:
|
||||||
|
// For regular users, we load the mapped policy.
|
||||||
|
err = store.loadMappedPolicy(ctx, accessKey, userType, false, cache.iamUserPolicyMap)
|
||||||
|
default:
|
||||||
|
// This is just to ensure that we have covered all cases for new
|
||||||
|
// code in future.
|
||||||
|
panic("unknown user type")
|
||||||
|
}
|
||||||
|
// Ignore policy not mapped error
|
||||||
|
if err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -1648,7 +1756,7 @@ func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cre
|
|||||||
return time.Time{}, err
|
return time.Time{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.iamUserPolicyMap[cred.ParentUser] = mp
|
cache.iamSTSPolicyMap[cred.ParentUser] = mp
|
||||||
}
|
}
|
||||||
|
|
||||||
u := newUserIdentity(cred)
|
u := newUserIdentity(cred)
|
||||||
@@ -1657,8 +1765,7 @@ func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cre
|
|||||||
return time.Time{}, err
|
return time.Time{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.iamUsersMap[accessKey] = u
|
cache.iamSTSAccountsMap[accessKey] = u
|
||||||
|
|
||||||
cache.updatedAt = time.Now()
|
cache.updatedAt = time.Now()
|
||||||
|
|
||||||
return u.UpdatedAt, nil
|
return u.UpdatedAt, nil
|
||||||
@@ -1800,6 +1907,25 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stsMap := map[string]MappedPolicy{}
|
||||||
|
for _, user := range users {
|
||||||
|
// Attempt to load parent user mapping for STS accounts
|
||||||
|
store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
for user, mappedPolicy := range stsMap {
|
||||||
|
if userPredicate != nil && !userPredicate(user) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := mappedPolicy.toSlice()
|
||||||
|
sort.Strings(ps)
|
||||||
|
r = append(r, madmin.UserPolicyEntities{
|
||||||
|
User: user,
|
||||||
|
Policies: ps,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
sort.Slice(r, func(i, j int) bool {
|
sort.Slice(r, func(i, j int) bool {
|
||||||
return r[i].User < r[j].User
|
return r[i].User < r[j].User
|
||||||
})
|
})
|
||||||
@@ -1864,6 +1990,32 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore); ok {
|
||||||
|
for item := range listIAMConfigItems(context.Background(), iamOS.objAPI, iamConfigPrefix+SlashSeparator+policyDBSTSUsersListKey) {
|
||||||
|
user := strings.TrimSuffix(item.Item, ".json")
|
||||||
|
if userPredicate != nil && !userPredicate(user) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var mappedPolicy MappedPolicy
|
||||||
|
store.loadIAMConfig(context.Background(), &mappedPolicy, getMappedPolicyPath(user, stsUser, false))
|
||||||
|
|
||||||
|
commonPolicySet := mappedPolicy.policySet()
|
||||||
|
if !queryPolSet.IsEmpty() {
|
||||||
|
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
||||||
|
}
|
||||||
|
for _, policy := range commonPolicySet.ToSlice() {
|
||||||
|
s, ok := policyToUsersMap[policy]
|
||||||
|
if !ok {
|
||||||
|
policyToUsersMap[policy] = set.CreateStringSet(user)
|
||||||
|
} else {
|
||||||
|
s.Add(user)
|
||||||
|
policyToUsersMap[policy] = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
policyToGroupsMap := make(map[string]set.StringSet)
|
policyToGroupsMap := make(map[string]set.StringSet)
|
||||||
for group, mappedPolicy := range cache.iamGroupPolicyMap {
|
for group, mappedPolicy := range cache.iamGroupPolicyMap {
|
||||||
if groupPredicate != nil && !groupPredicate(group) {
|
if groupPredicate != nil && !groupPredicate(group) {
|
||||||
@@ -2076,8 +2228,8 @@ func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey st
|
|||||||
|
|
||||||
// sessionPolicy is nil and there is embedded policy attached we remove
|
// sessionPolicy is nil and there is embedded policy attached we remove
|
||||||
// embedded policy at that point.
|
// embedded policy at that point.
|
||||||
if _, ok := m[iampolicy.SessionPolicyName]; ok && opts.sessionPolicy == nil {
|
if _, ok := m[policy.SessionPolicyName]; ok && opts.sessionPolicy == nil {
|
||||||
delete(m, iampolicy.SessionPolicyName)
|
delete(m, policy.SessionPolicyName)
|
||||||
m[iamPolicyClaimNameSA()] = inheritedPolicyType
|
m[iamPolicyClaimNameSA()] = inheritedPolicyType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2096,7 +2248,7 @@ func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Overwrite session policy claims.
|
// Overwrite session policy claims.
|
||||||
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString(policyBuf)
|
m[policy.SessionPolicyName] = base64.StdEncoding.EncodeToString(policyBuf)
|
||||||
m[iamPolicyClaimNameSA()] = embeddedPolicyType
|
m[iamPolicyClaimNameSA()] = embeddedPolicyType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2155,19 +2307,10 @@ func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey str
|
|||||||
cache := store.rlock()
|
cache := store.rlock()
|
||||||
defer store.runlock()
|
defer store.runlock()
|
||||||
|
|
||||||
userExists := false
|
|
||||||
var serviceAccounts []auth.Credentials
|
var serviceAccounts []auth.Credentials
|
||||||
for _, u := range cache.iamUsersMap {
|
for _, u := range cache.iamUsersMap {
|
||||||
isDerived := false
|
|
||||||
v := u.Credentials
|
v := u.Credentials
|
||||||
if v.IsServiceAccount() || v.IsTemp() {
|
if accessKey != "" && v.ParentUser == accessKey {
|
||||||
isDerived = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isDerived && v.AccessKey == accessKey {
|
|
||||||
userExists = true
|
|
||||||
} else if isDerived && v.ParentUser == accessKey {
|
|
||||||
userExists = true
|
|
||||||
if v.IsServiceAccount() {
|
if v.IsServiceAccount() {
|
||||||
// Hide secret key & session key here
|
// Hide secret key & session key here
|
||||||
v.SecretKey = ""
|
v.SecretKey = ""
|
||||||
@@ -2177,12 +2320,6 @@ func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If root user has no STS/Service Accounts, userExists would be false here,
|
|
||||||
// so we handle this exception.
|
|
||||||
if !userExists && globalActiveCred.AccessKey != accessKey {
|
|
||||||
return nil, errNoSuchUser
|
|
||||||
}
|
|
||||||
|
|
||||||
return serviceAccounts, nil
|
return serviceAccounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2251,10 +2388,20 @@ func (store *IAMStoreSys) GetSTSAndServiceAccounts() []auth.Credentials {
|
|||||||
var res []auth.Credentials
|
var res []auth.Credentials
|
||||||
for _, u := range cache.iamUsersMap {
|
for _, u := range cache.iamUsersMap {
|
||||||
cred := u.Credentials
|
cred := u.Credentials
|
||||||
if cred.IsTemp() || cred.IsServiceAccount() {
|
if cred.IsTemp() {
|
||||||
|
panic("unexpected STS credential found in iamUsersMap")
|
||||||
|
}
|
||||||
|
if cred.IsServiceAccount() {
|
||||||
res = append(res, cred)
|
res = append(res, cred)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, u := range cache.iamSTSAccountsMap {
|
||||||
|
if !u.Credentials.IsTemp() {
|
||||||
|
panic("unexpected non STS credential found in iamSTSAccountsMap")
|
||||||
|
}
|
||||||
|
res = append(res, u.Credentials)
|
||||||
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2289,35 +2436,56 @@ func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) {
|
|||||||
cache.updatedAt = time.Now()
|
cache.updatedAt = time.Now()
|
||||||
|
|
||||||
_, found := cache.iamUsersMap[accessKey]
|
_, found := cache.iamUsersMap[accessKey]
|
||||||
|
|
||||||
|
// Check for regular user access key
|
||||||
if !found {
|
if !found {
|
||||||
store.loadUser(ctx, accessKey, regUser, cache.iamUsersMap)
|
store.loadUser(ctx, accessKey, regUser, cache.iamUsersMap)
|
||||||
if _, found = cache.iamUsersMap[accessKey]; found {
|
if _, found = cache.iamUsersMap[accessKey]; found {
|
||||||
// load mapped policies
|
// load mapped policies
|
||||||
store.loadMappedPolicy(ctx, accessKey, regUser, false, cache.iamUserPolicyMap)
|
store.loadMappedPolicy(ctx, accessKey, regUser, false, cache.iamUserPolicyMap)
|
||||||
} else {
|
}
|
||||||
// check for service account
|
}
|
||||||
store.loadUser(ctx, accessKey, svcUser, cache.iamUsersMap)
|
|
||||||
if svc, found := cache.iamUsersMap[accessKey]; found {
|
// Check for service account
|
||||||
// Load parent user and mapped policies.
|
if !found {
|
||||||
if store.getUsersSysType() == MinIOUsersSysType {
|
store.loadUser(ctx, accessKey, svcUser, cache.iamUsersMap)
|
||||||
store.loadUser(ctx, svc.Credentials.ParentUser, regUser, cache.iamUsersMap)
|
if svc, found := cache.iamUsersMap[accessKey]; found {
|
||||||
}
|
// Load parent user and mapped policies.
|
||||||
|
if store.getUsersSysType() == MinIOUsersSysType {
|
||||||
|
store.loadUser(ctx, svc.Credentials.ParentUser, regUser, cache.iamUsersMap)
|
||||||
store.loadMappedPolicy(ctx, svc.Credentials.ParentUser, regUser, false, cache.iamUserPolicyMap)
|
store.loadMappedPolicy(ctx, svc.Credentials.ParentUser, regUser, false, cache.iamUserPolicyMap)
|
||||||
} else {
|
} else {
|
||||||
// check for STS account
|
// In case of LDAP the parent user's policy mapping needs to be
|
||||||
store.loadUser(ctx, accessKey, stsUser, cache.iamUsersMap)
|
// loaded into sts map
|
||||||
if _, found = cache.iamUsersMap[accessKey]; found {
|
store.loadMappedPolicy(ctx, svc.Credentials.ParentUser, stsUser, false, cache.iamSTSPolicyMap)
|
||||||
// Load mapped policy
|
|
||||||
store.loadMappedPolicy(ctx, accessKey, stsUser, false, cache.iamUserPolicyMap)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for STS account
|
||||||
|
stsAccountFound := false
|
||||||
|
var stsUserCred UserIdentity
|
||||||
|
if !found {
|
||||||
|
store.loadUser(ctx, accessKey, stsUser, cache.iamSTSAccountsMap)
|
||||||
|
if stsUserCred, found = cache.iamSTSAccountsMap[accessKey]; found {
|
||||||
|
// Load mapped policy
|
||||||
|
store.loadMappedPolicy(ctx, stsUserCred.Credentials.ParentUser, stsUser, false, cache.iamSTSPolicyMap)
|
||||||
|
stsAccountFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load any associated policy definitions
|
// Load any associated policy definitions
|
||||||
for _, policy := range cache.iamUserPolicyMap[accessKey].toSlice() {
|
if !stsAccountFound {
|
||||||
if _, found = cache.iamPolicyDocsMap[policy]; !found {
|
for _, policy := range cache.iamUserPolicyMap[accessKey].toSlice() {
|
||||||
store.loadPolicyDoc(ctx, policy, cache.iamPolicyDocsMap)
|
if _, found = cache.iamPolicyDocsMap[policy]; !found {
|
||||||
|
store.loadPolicyDoc(ctx, policy, cache.iamPolicyDocsMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, policy := range cache.iamSTSPolicyMap[stsUserCred.Credentials.AccessKey].toSlice() {
|
||||||
|
if _, found = cache.iamPolicyDocsMap[policy]; !found {
|
||||||
|
store.loadPolicyDoc(ctx, policy, cache.iamPolicyDocsMap)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+117
-120
@@ -47,7 +47,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/jwt"
|
"github.com/minio/minio/internal/jwt"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
etcd "go.etcd.io/etcd/client/v3"
|
etcd "go.etcd.io/etcd/client/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -319,44 +319,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
|||||||
|
|
||||||
refreshInterval := sys.iamRefreshInterval
|
refreshInterval := sys.iamRefreshInterval
|
||||||
|
|
||||||
// Set up polling for expired accounts and credentials purging.
|
go sys.periodicRoutines(ctx, refreshInterval)
|
||||||
switch {
|
|
||||||
case sys.OpenIDConfig.ProviderEnabled():
|
|
||||||
go func() {
|
|
||||||
timer := time.NewTimer(refreshInterval)
|
|
||||||
defer timer.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-timer.C:
|
|
||||||
sys.purgeExpiredCredentialsForExternalSSO(ctx)
|
|
||||||
|
|
||||||
timer.Reset(refreshInterval)
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
case sys.LDAPConfig.Enabled():
|
|
||||||
go func() {
|
|
||||||
timer := time.NewTimer(refreshInterval)
|
|
||||||
defer timer.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-timer.C:
|
|
||||||
sys.purgeExpiredCredentialsForLDAP(ctx)
|
|
||||||
sys.updateGroupMembershipsForLDAP(ctx)
|
|
||||||
|
|
||||||
timer.Reset(refreshInterval)
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start watching changes to storage.
|
|
||||||
go sys.watch(ctx)
|
|
||||||
|
|
||||||
// Load RoleARNs
|
// Load RoleARNs
|
||||||
sys.rolesMap = make(map[arn.ARN]string)
|
sys.rolesMap = make(map[arn.ARN]string)
|
||||||
@@ -377,6 +340,79 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
|||||||
bootstrapTraceMsg("finishing IAM loading")
|
bootstrapTraceMsg("finishing IAM loading")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sys *IAMSys) periodicRoutines(ctx context.Context, baseInterval time.Duration) {
|
||||||
|
// Watch for IAM config changes for iamStorageWatcher.
|
||||||
|
watcher, isWatcher := sys.store.IAMStorageAPI.(iamStorageWatcher)
|
||||||
|
if isWatcher {
|
||||||
|
go func() {
|
||||||
|
ch := watcher.watch(ctx, iamConfigPrefix)
|
||||||
|
for event := range ch {
|
||||||
|
if err := sys.loadWatchedEvent(ctx, event); err != nil {
|
||||||
|
// we simply log errors
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Failure in loading watch event: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
// Add a random interval of up to 20% of the base interval.
|
||||||
|
randInterval := func() time.Duration {
|
||||||
|
return time.Duration(r.Float64() * float64(baseInterval) * 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxDurationSecondsForLog float64 = 5
|
||||||
|
timer := time.NewTimer(baseInterval + randInterval())
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
// Load all IAM items (except STS creds) periodically.
|
||||||
|
refreshStart := time.Now()
|
||||||
|
if err := sys.Load(ctx, false); err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Failure in periodic refresh for IAM (took %.2fs): %v", time.Since(refreshStart).Seconds(), err))
|
||||||
|
} else {
|
||||||
|
took := time.Since(refreshStart).Seconds()
|
||||||
|
if took > maxDurationSecondsForLog {
|
||||||
|
// Log if we took a lot of time to load.
|
||||||
|
logger.Info("IAM refresh took %.2fs", took)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following actions are performed about once in 4 times that
|
||||||
|
// IAM is refreshed:
|
||||||
|
if r.Intn(4) == 0 {
|
||||||
|
// Purge expired STS credentials.
|
||||||
|
purgeStart := time.Now()
|
||||||
|
if err := sys.store.PurgeExpiredSTS(ctx); err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Failure in periodic STS purge for IAM (took %.2fs): %v", time.Since(purgeStart).Seconds(), err))
|
||||||
|
} else {
|
||||||
|
took := time.Since(purgeStart).Seconds()
|
||||||
|
if took > maxDurationSecondsForLog {
|
||||||
|
// Log if we took a lot of time to load.
|
||||||
|
logger.Info("IAM expired STS purge took %.2fs", took)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll and remove accounts for those users who were removed
|
||||||
|
// from LDAP/OpenID.
|
||||||
|
if sys.LDAPConfig.Enabled() {
|
||||||
|
sys.purgeExpiredCredentialsForLDAP(ctx)
|
||||||
|
sys.updateGroupMembershipsForLDAP(ctx)
|
||||||
|
}
|
||||||
|
if sys.OpenIDConfig.ProviderEnabled() {
|
||||||
|
sys.purgeExpiredCredentialsForExternalSSO(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.Reset(baseInterval + randInterval())
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (sys *IAMSys) validateAndAddRolePolicyMappings(ctx context.Context, m map[arn.ARN]string) {
|
func (sys *IAMSys) validateAndAddRolePolicyMappings(ctx context.Context, m map[arn.ARN]string) {
|
||||||
// Validate that policies associated with roles are defined. If
|
// Validate that policies associated with roles are defined. If
|
||||||
// authZ plugin is set, role policies are just claims sent to
|
// authZ plugin is set, role policies are just claims sent to
|
||||||
@@ -428,45 +464,6 @@ func (sys *IAMSys) HasWatcher() bool {
|
|||||||
return sys.store.HasWatcher()
|
return sys.store.HasWatcher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *IAMSys) watch(ctx context.Context) {
|
|
||||||
watcher, ok := sys.store.IAMStorageAPI.(iamStorageWatcher)
|
|
||||||
if ok {
|
|
||||||
ch := watcher.watch(ctx, iamConfigPrefix)
|
|
||||||
for event := range ch {
|
|
||||||
if err := sys.loadWatchedEvent(ctx, event); err != nil {
|
|
||||||
// we simply log errors
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Failure in loading watch event: %v", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxRefreshDurationSecondsForLog float64 = 10
|
|
||||||
|
|
||||||
// Load all items periodically
|
|
||||||
timer := time.NewTimer(sys.iamRefreshInterval)
|
|
||||||
defer timer.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-timer.C:
|
|
||||||
refreshStart := time.Now()
|
|
||||||
if err := sys.Load(ctx, false); err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Failure in periodic refresh for IAM (took %.2fs): %v", time.Since(refreshStart).Seconds(), err))
|
|
||||||
} else {
|
|
||||||
took := time.Since(refreshStart).Seconds()
|
|
||||||
if took > maxRefreshDurationSecondsForLog {
|
|
||||||
// Log if we took a lot of time to load.
|
|
||||||
logger.Info("IAM refresh took %.2fs", took)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timer.Reset(sys.iamRefreshInterval)
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sys *IAMSys) loadWatchedEvent(ctx context.Context, event iamWatchEvent) (err error) {
|
func (sys *IAMSys) loadWatchedEvent(ctx context.Context, event iamWatchEvent) (err error) {
|
||||||
usersPrefix := strings.HasPrefix(event.keyPath, iamConfigUsersPrefix)
|
usersPrefix := strings.HasPrefix(event.keyPath, iamConfigUsersPrefix)
|
||||||
groupsPrefix := strings.HasPrefix(event.keyPath, iamConfigGroupsPrefix)
|
groupsPrefix := strings.HasPrefix(event.keyPath, iamConfigGroupsPrefix)
|
||||||
@@ -536,7 +533,7 @@ func (sys *IAMSys) DeletePolicy(ctx context.Context, policyName string, notifyPe
|
|||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range iampolicy.DefaultPolicies {
|
for _, v := range policy.DefaultPolicies {
|
||||||
if v.Name == policyName {
|
if v.Name == policyName {
|
||||||
if err := checkConfig(ctx, globalObjectAPI, getPolicyDocPath(policyName)); err != nil && err == errConfigNotFound {
|
if err := checkConfig(ctx, globalObjectAPI, getPolicyDocPath(policyName)); err != nil && err == errConfigNotFound {
|
||||||
return fmt.Errorf("inbuilt policy `%s` not allowed to be deleted", policyName)
|
return fmt.Errorf("inbuilt policy `%s` not allowed to be deleted", policyName)
|
||||||
@@ -589,7 +586,7 @@ func (sys *IAMSys) InfoPolicy(policyName string) (*madmin.PolicyInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListPolicies - lists all canned policies.
|
// ListPolicies - lists all canned policies.
|
||||||
func (sys *IAMSys) ListPolicies(ctx context.Context, bucketName string) (map[string]iampolicy.Policy, error) {
|
func (sys *IAMSys) ListPolicies(ctx context.Context, bucketName string) (map[string]policy.Policy, error) {
|
||||||
if !sys.Initialized() {
|
if !sys.Initialized() {
|
||||||
return nil, errServerNotInitialized
|
return nil, errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -607,7 +604,7 @@ func (sys *IAMSys) ListPolicyDocs(ctx context.Context, bucketName string) (map[s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetPolicy - sets a new named policy.
|
// SetPolicy - sets a new named policy.
|
||||||
func (sys *IAMSys) SetPolicy(ctx context.Context, policyName string, p iampolicy.Policy) (time.Time, error) {
|
func (sys *IAMSys) SetPolicy(ctx context.Context, policyName string, p policy.Policy) (time.Time, error) {
|
||||||
if !sys.Initialized() {
|
if !sys.Initialized() {
|
||||||
return time.Time{}, errServerNotInitialized
|
return time.Time{}, errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -849,13 +846,20 @@ func (sys *IAMSys) GetUserInfo(ctx context.Context, name string) (u madmin.UserI
|
|||||||
return u, errServerNotInitialized
|
return u, errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadUserCalled := false
|
||||||
select {
|
select {
|
||||||
case <-sys.configLoaded:
|
case <-sys.configLoaded:
|
||||||
default:
|
default:
|
||||||
sys.store.LoadUser(ctx, name)
|
sys.store.LoadUser(ctx, name)
|
||||||
|
loadUserCalled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return sys.store.GetUserInfo(name)
|
userInfo, err := sys.store.GetUserInfo(name)
|
||||||
|
if err == errNoSuchUser && !loadUserCalled {
|
||||||
|
sys.store.LoadUser(ctx, name)
|
||||||
|
userInfo, err = sys.store.GetUserInfo(name)
|
||||||
|
}
|
||||||
|
return userInfo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryPolicyEntities - queries policy associations for builtin users/groups/policies.
|
// QueryPolicyEntities - queries policy associations for builtin users/groups/policies.
|
||||||
@@ -915,7 +919,7 @@ func (sys *IAMSys) notifyForServiceAccount(ctx context.Context, accessKey string
|
|||||||
}
|
}
|
||||||
|
|
||||||
type newServiceAccountOpts struct {
|
type newServiceAccountOpts struct {
|
||||||
sessionPolicy *iampolicy.Policy
|
sessionPolicy *policy.Policy
|
||||||
accessKey string
|
accessKey string
|
||||||
secretKey string
|
secretKey string
|
||||||
name, description string
|
name, description string
|
||||||
@@ -962,7 +966,7 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
|
|||||||
m[parentClaim] = parentUser
|
m[parentClaim] = parentUser
|
||||||
|
|
||||||
if len(policyBuf) > 0 {
|
if len(policyBuf) > 0 {
|
||||||
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString(policyBuf)
|
m[policy.SessionPolicyName] = base64.StdEncoding.EncodeToString(policyBuf)
|
||||||
m[iamPolicyClaimNameSA()] = embeddedPolicyType
|
m[iamPolicyClaimNameSA()] = embeddedPolicyType
|
||||||
} else {
|
} else {
|
||||||
m[iamPolicyClaimNameSA()] = inheritedPolicyType
|
m[iamPolicyClaimNameSA()] = inheritedPolicyType
|
||||||
@@ -1014,7 +1018,7 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
|
|||||||
}
|
}
|
||||||
|
|
||||||
type updateServiceAccountOpts struct {
|
type updateServiceAccountOpts struct {
|
||||||
sessionPolicy *iampolicy.Policy
|
sessionPolicy *policy.Policy
|
||||||
secretKey string
|
secretKey string
|
||||||
status string
|
status string
|
||||||
name, description string
|
name, description string
|
||||||
@@ -1065,7 +1069,7 @@ func (sys *IAMSys) ListTempAccounts(ctx context.Context, accessKey string) ([]Us
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetServiceAccount - wrapper method to get information about a service account
|
// GetServiceAccount - wrapper method to get information about a service account
|
||||||
func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (auth.Credentials, *iampolicy.Policy, error) {
|
func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (auth.Credentials, *policy.Policy, error) {
|
||||||
sa, embeddedPolicy, err := sys.getServiceAccount(ctx, accessKey)
|
sa, embeddedPolicy, err := sys.getServiceAccount(ctx, accessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return auth.Credentials{}, nil, err
|
return auth.Credentials{}, nil, err
|
||||||
@@ -1076,7 +1080,7 @@ func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (aut
|
|||||||
return sa.Credentials, embeddedPolicy, nil
|
return sa.Credentials, embeddedPolicy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (UserIdentity, *iampolicy.Policy, error) {
|
func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (UserIdentity, *policy.Policy, error) {
|
||||||
sa, jwtClaims, err := sys.getAccountWithClaims(ctx, accessKey)
|
sa, jwtClaims, err := sys.getAccountWithClaims(ctx, accessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == errNoSuchAccount {
|
if err == errNoSuchAccount {
|
||||||
@@ -1088,16 +1092,16 @@ func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (Use
|
|||||||
return UserIdentity{}, nil, errNoSuchServiceAccount
|
return UserIdentity{}, nil, errNoSuchServiceAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
var embeddedPolicy *iampolicy.Policy
|
var embeddedPolicy *policy.Policy
|
||||||
|
|
||||||
pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA())
|
pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA())
|
||||||
sp, spok := jwtClaims.Lookup(iampolicy.SessionPolicyName)
|
sp, spok := jwtClaims.Lookup(policy.SessionPolicyName)
|
||||||
if ptok && spok && pt == embeddedPolicyType {
|
if ptok && spok && pt == embeddedPolicyType {
|
||||||
policyBytes, err := base64.StdEncoding.DecodeString(sp)
|
policyBytes, err := base64.StdEncoding.DecodeString(sp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UserIdentity{}, nil, err
|
return UserIdentity{}, nil, err
|
||||||
}
|
}
|
||||||
embeddedPolicy, err = iampolicy.ParseConfig(bytes.NewReader(policyBytes))
|
embeddedPolicy, err = policy.ParseConfig(bytes.NewReader(policyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UserIdentity{}, nil, err
|
return UserIdentity{}, nil, err
|
||||||
}
|
}
|
||||||
@@ -1107,7 +1111,7 @@ func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (Use
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetTemporaryAccount - wrapper method to get information about a temporary account
|
// GetTemporaryAccount - wrapper method to get information about a temporary account
|
||||||
func (sys *IAMSys) GetTemporaryAccount(ctx context.Context, accessKey string) (auth.Credentials, *iampolicy.Policy, error) {
|
func (sys *IAMSys) GetTemporaryAccount(ctx context.Context, accessKey string) (auth.Credentials, *policy.Policy, error) {
|
||||||
tmpAcc, embeddedPolicy, err := sys.getTempAccount(ctx, accessKey)
|
tmpAcc, embeddedPolicy, err := sys.getTempAccount(ctx, accessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return auth.Credentials{}, nil, err
|
return auth.Credentials{}, nil, err
|
||||||
@@ -1118,7 +1122,7 @@ func (sys *IAMSys) GetTemporaryAccount(ctx context.Context, accessKey string) (a
|
|||||||
return tmpAcc.Credentials, embeddedPolicy, nil
|
return tmpAcc.Credentials, embeddedPolicy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *IAMSys) getTempAccount(ctx context.Context, accessKey string) (UserIdentity, *iampolicy.Policy, error) {
|
func (sys *IAMSys) getTempAccount(ctx context.Context, accessKey string) (UserIdentity, *policy.Policy, error) {
|
||||||
tmpAcc, claims, err := sys.getAccountWithClaims(ctx, accessKey)
|
tmpAcc, claims, err := sys.getAccountWithClaims(ctx, accessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == errNoSuchAccount {
|
if err == errNoSuchAccount {
|
||||||
@@ -1130,15 +1134,15 @@ func (sys *IAMSys) getTempAccount(ctx context.Context, accessKey string) (UserId
|
|||||||
return UserIdentity{}, nil, errNoSuchTempAccount
|
return UserIdentity{}, nil, errNoSuchTempAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
var embeddedPolicy *iampolicy.Policy
|
var embeddedPolicy *policy.Policy
|
||||||
|
|
||||||
sp, spok := claims.Lookup(iampolicy.SessionPolicyName)
|
sp, spok := claims.Lookup(policy.SessionPolicyName)
|
||||||
if spok {
|
if spok {
|
||||||
policyBytes, err := base64.StdEncoding.DecodeString(sp)
|
policyBytes, err := base64.StdEncoding.DecodeString(sp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UserIdentity{}, nil, err
|
return UserIdentity{}, nil, err
|
||||||
}
|
}
|
||||||
embeddedPolicy, err = iampolicy.ParseConfig(bytes.NewReader(policyBytes))
|
embeddedPolicy, err = policy.ParseConfig(bytes.NewReader(policyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UserIdentity{}, nil, err
|
return UserIdentity{}, nil, err
|
||||||
}
|
}
|
||||||
@@ -1421,31 +1425,24 @@ func (sys *IAMSys) GetUser(ctx context.Context, accessKey string) (u UserIdentit
|
|||||||
return u, false
|
return u, false
|
||||||
}
|
}
|
||||||
|
|
||||||
fallback := false
|
if accessKey == globalActiveCred.AccessKey {
|
||||||
|
return newUserIdentity(globalActiveCred), true
|
||||||
|
}
|
||||||
|
|
||||||
|
loadUserCalled := false
|
||||||
select {
|
select {
|
||||||
case <-sys.configLoaded:
|
case <-sys.configLoaded:
|
||||||
default:
|
default:
|
||||||
sys.store.LoadUser(ctx, accessKey)
|
sys.store.LoadUser(ctx, accessKey)
|
||||||
fallback = true
|
loadUserCalled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
u, ok = sys.store.GetUser(accessKey)
|
u, ok = sys.store.GetUser(accessKey)
|
||||||
if !ok && !fallback {
|
if !ok && !loadUserCalled {
|
||||||
// accessKey not found, also
|
|
||||||
// IAM store is not in fallback mode
|
|
||||||
// we can try to reload again from
|
|
||||||
// the IAM store and see if credential
|
|
||||||
// exists now. If it doesn't proceed to
|
|
||||||
// fail.
|
|
||||||
sys.store.LoadUser(ctx, accessKey)
|
sys.store.LoadUser(ctx, accessKey)
|
||||||
u, ok = sys.store.GetUser(accessKey)
|
u, ok = sys.store.GetUser(accessKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
|
||||||
if accessKey == globalActiveCred.AccessKey {
|
|
||||||
return newUserIdentity(globalActiveCred), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return u, ok && u.Credentials.IsValid()
|
return u, ok && u.Credentials.IsValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1723,11 +1720,11 @@ func (sys *IAMSys) PolicyDBGet(name string, isGroup bool, groups ...string) ([]s
|
|||||||
return sys.store.PolicyDBGet(name, isGroup, groups...)
|
return sys.store.PolicyDBGet(name, isGroup, groups...)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionPolicyNameExtracted = iampolicy.SessionPolicyName + "-extracted"
|
const sessionPolicyNameExtracted = policy.SessionPolicyName + "-extracted"
|
||||||
|
|
||||||
// IsAllowedServiceAccount - checks if the given service account is allowed to perform
|
// IsAllowedServiceAccount - checks if the given service account is allowed to perform
|
||||||
// actions. The permission of the parent user is checked first
|
// actions. The permission of the parent user is checked first
|
||||||
func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser string) bool {
|
func (sys *IAMSys) IsAllowedServiceAccount(args policy.Args, parentUser string) bool {
|
||||||
// Verify if the parent claim matches the parentUser.
|
// Verify if the parent claim matches the parentUser.
|
||||||
p, ok := args.Claims[parentClaim]
|
p, ok := args.Claims[parentClaim]
|
||||||
if ok {
|
if ok {
|
||||||
@@ -1778,7 +1775,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin
|
|||||||
// Finally, if there is no parent policy, check if a policy claim is
|
// Finally, if there is no parent policy, check if a policy claim is
|
||||||
// present.
|
// present.
|
||||||
if len(svcPolicies) == 0 {
|
if len(svcPolicies) == 0 {
|
||||||
policySet, _ := iampolicy.GetPoliciesFromClaims(args.Claims, iamPolicyClaimNameOpenID())
|
policySet, _ := policy.GetPoliciesFromClaims(args.Claims, iamPolicyClaimNameOpenID())
|
||||||
svcPolicies = policySet.ToSlice()
|
svcPolicies = policySet.ToSlice()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1788,7 +1785,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var combinedPolicy iampolicy.Policy
|
var combinedPolicy policy.Policy
|
||||||
// Policies were found, evaluate all of them.
|
// Policies were found, evaluate all of them.
|
||||||
if !isOwnerDerived {
|
if !isOwnerDerived {
|
||||||
availablePoliciesStr, c := sys.store.FilterPolicies(strings.Join(svcPolicies, ","), "")
|
availablePoliciesStr, c := sys.store.FilterPolicies(strings.Join(svcPolicies, ","), "")
|
||||||
@@ -1831,7 +1828,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if policy is parseable.
|
// Check if policy is parseable.
|
||||||
subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
|
subPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log any error in input session policy config.
|
// Log any error in input session policy config.
|
||||||
logger.LogIf(GlobalContext, err)
|
logger.LogIf(GlobalContext, err)
|
||||||
@@ -1853,7 +1850,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin
|
|||||||
// IsAllowedSTS is meant for STS based temporary credentials,
|
// IsAllowedSTS is meant for STS based temporary credentials,
|
||||||
// which implements claims validation and verification other than
|
// which implements claims validation and verification other than
|
||||||
// applying policies.
|
// applying policies.
|
||||||
func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args, parentUser string) bool {
|
func (sys *IAMSys) IsAllowedSTS(args policy.Args, parentUser string) bool {
|
||||||
// 1. Determine mapped policies
|
// 1. Determine mapped policies
|
||||||
|
|
||||||
isOwnerDerived := parentUser == globalActiveCred.AccessKey
|
isOwnerDerived := parentUser == globalActiveCred.AccessKey
|
||||||
@@ -1905,7 +1902,7 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args, parentUser string) bool {
|
|||||||
|
|
||||||
// 2. Combine the mapped policies into a single combined policy.
|
// 2. Combine the mapped policies into a single combined policy.
|
||||||
|
|
||||||
var combinedPolicy iampolicy.Policy
|
var combinedPolicy policy.Policy
|
||||||
if !isOwnerDerived {
|
if !isOwnerDerived {
|
||||||
var err error
|
var err error
|
||||||
combinedPolicy, err = sys.store.GetPolicy(strings.Join(policies, ","))
|
combinedPolicy, err = sys.store.GetPolicy(strings.Join(policies, ","))
|
||||||
@@ -1937,7 +1934,7 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args, parentUser string) bool {
|
|||||||
return isOwnerDerived || combinedPolicy.IsAllowed(args)
|
return isOwnerDerived || combinedPolicy.IsAllowed(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAllowedBySessionPolicy(args iampolicy.Args) (hasSessionPolicy bool, isAllowed bool) {
|
func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowed bool) {
|
||||||
hasSessionPolicy = false
|
hasSessionPolicy = false
|
||||||
isAllowed = false
|
isAllowed = false
|
||||||
|
|
||||||
@@ -1957,7 +1954,7 @@ func isAllowedBySessionPolicy(args iampolicy.Args) (hasSessionPolicy bool, isAll
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if policy is parseable.
|
// Check if policy is parseable.
|
||||||
subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
|
subPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log any error in input session policy config.
|
// Log any error in input session policy config.
|
||||||
logger.LogIf(GlobalContext, err)
|
logger.LogIf(GlobalContext, err)
|
||||||
@@ -1974,13 +1971,13 @@ func isAllowedBySessionPolicy(args iampolicy.Args) (hasSessionPolicy bool, isAll
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetCombinedPolicy returns a combined policy combining all policies
|
// GetCombinedPolicy returns a combined policy combining all policies
|
||||||
func (sys *IAMSys) GetCombinedPolicy(policies ...string) iampolicy.Policy {
|
func (sys *IAMSys) GetCombinedPolicy(policies ...string) policy.Policy {
|
||||||
_, policy := sys.store.FilterPolicies(strings.Join(policies, ","), "")
|
_, policy := sys.store.FilterPolicies(strings.Join(policies, ","), "")
|
||||||
return policy
|
return policy
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
||||||
func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool {
|
func (sys *IAMSys) IsAllowed(args policy.Args) bool {
|
||||||
// If opa is configured, use OPA always.
|
// If opa is configured, use OPA always.
|
||||||
if authz := newGlobalAuthZPluginFn(); authz != nil {
|
if authz := newGlobalAuthZPluginFn(); authz != nil {
|
||||||
ok, err := authz.IsAllowed(args)
|
ok, err := authz.IsAllowed(args)
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
xjwt "github.com/minio/minio/internal/jwt"
|
xjwt "github.com/minio/minio/internal/jwt"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -148,7 +148,7 @@ func metricsRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, []string, b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now check if we have a sessionPolicy.
|
// Now check if we have a sessionPolicy.
|
||||||
if _, ok = eclaims[iampolicy.SessionPolicyName]; ok {
|
if _, ok = eclaims[policy.SessionPolicyName]; ok {
|
||||||
owner = false
|
owner = false
|
||||||
} else {
|
} else {
|
||||||
owner = globalActiveCred.AccessKey == ucred.ParentUser
|
owner = globalActiveCred.AccessKey == ucred.ParentUser
|
||||||
|
|||||||
+19
-19
@@ -29,14 +29,14 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KMSStatusHandler - GET /minio/kms/v1/status
|
// KMSStatusHandler - GET /minio/kms/v1/status
|
||||||
func (a kmsAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (a kmsAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "KMSStatus")
|
ctx := newContext(r, w, "KMSStatus")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSStatusAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSStatusAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ func (a kmsAPIHandlers) KMSMetricsHandler(w http.ResponseWriter, r *http.Request
|
|||||||
ctx := newContext(r, w, "KMSMetrics")
|
ctx := newContext(r, w, "KMSMetrics")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSMetricsAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSMetricsAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ func (a kmsAPIHandlers) KMSAPIsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
ctx := newContext(r, w, "KMSAPIs")
|
ctx := newContext(r, w, "KMSAPIs")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSAPIAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSAPIAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ func (a kmsAPIHandlers) KMSVersionHandler(w http.ResponseWriter, r *http.Request
|
|||||||
ctx := newContext(r, w, "KMSVersion")
|
ctx := newContext(r, w, "KMSVersion")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSVersionAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSVersionAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ func (a kmsAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSCreateKeyAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSCreateKeyAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -211,7 +211,7 @@ func (a kmsAPIHandlers) KMSDeleteKeyHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
ctx := newContext(r, w, "KMSDeleteKey")
|
ctx := newContext(r, w, "KMSDeleteKey")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeleteKeyAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDeleteKeyAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -246,7 +246,7 @@ func (a kmsAPIHandlers) KMSListKeysHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListKeysAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSListKeysAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -297,7 +297,7 @@ func (a kmsAPIHandlers) KMSImportKeyHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
ctx := newContext(r, w, "KMSImportKey")
|
ctx := newContext(r, w, "KMSImportKey")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSImportKeyAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSImportKeyAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -328,7 +328,7 @@ func (a kmsAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
ctx := newContext(r, w, "KMSKeyStatus")
|
ctx := newContext(r, w, "KMSKeyStatus")
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSKeyStatusAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -404,7 +404,7 @@ func (a kmsAPIHandlers) KMSDescribePolicyHandler(w http.ResponseWriter, r *http.
|
|||||||
ctx := newContext(r, w, "KMSDescribePolicy")
|
ctx := newContext(r, w, "KMSDescribePolicy")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribePolicyAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDescribePolicyAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -440,7 +440,7 @@ func (a kmsAPIHandlers) KMSAssignPolicyHandler(w http.ResponseWriter, r *http.Re
|
|||||||
ctx := newContext(r, w, "KMSAssignPolicy")
|
ctx := newContext(r, w, "KMSAssignPolicy")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSAssignPolicyAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSAssignPolicyAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -472,7 +472,7 @@ func (a kmsAPIHandlers) KMSDeletePolicyHandler(w http.ResponseWriter, r *http.Re
|
|||||||
ctx := newContext(r, w, "KMSDeletePolicy")
|
ctx := newContext(r, w, "KMSDeletePolicy")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeletePolicyAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDeletePolicyAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -499,7 +499,7 @@ func (a kmsAPIHandlers) KMSListPoliciesHandler(w http.ResponseWriter, r *http.Re
|
|||||||
ctx := newContext(r, w, "KMSListPolicies")
|
ctx := newContext(r, w, "KMSListPolicies")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListPoliciesAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSListPoliciesAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -546,7 +546,7 @@ func (a kmsAPIHandlers) KMSGetPolicyHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
ctx := newContext(r, w, "KMSGetPolicy")
|
ctx := newContext(r, w, "KMSGetPolicy")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSGetPolicyAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSGetPolicyAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -578,7 +578,7 @@ func (a kmsAPIHandlers) KMSDescribeIdentityHandler(w http.ResponseWriter, r *htt
|
|||||||
ctx := newContext(r, w, "KMSDescribeIdentity")
|
ctx := newContext(r, w, "KMSDescribeIdentity")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribeIdentityAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDescribeIdentityAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -619,7 +619,7 @@ func (a kmsAPIHandlers) KMSDescribeSelfIdentityHandler(w http.ResponseWriter, r
|
|||||||
ctx := newContext(r, w, "KMSDescribeSelfIdentity")
|
ctx := newContext(r, w, "KMSDescribeSelfIdentity")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribeSelfIdentityAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDescribeSelfIdentityAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -659,7 +659,7 @@ func (a kmsAPIHandlers) KMSDeleteIdentityHandler(w http.ResponseWriter, r *http.
|
|||||||
ctx := newContext(r, w, "KMSDeleteIdentity")
|
ctx := newContext(r, w, "KMSDeleteIdentity")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeleteIdentityAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDeleteIdentityAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -686,7 +686,7 @@ func (a kmsAPIHandlers) KMSListIdentitiesHandler(w http.ResponseWriter, r *http.
|
|||||||
ctx := newContext(r, w, "KMSListIdentities")
|
ctx := newContext(r, w, "KMSListIdentities")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListIdentitiesAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSListIdentitiesAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/pubsub"
|
"github.com/minio/minio/internal/pubsub"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
+4
-4
@@ -30,10 +30,10 @@ import (
|
|||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
"github.com/minio/minio/internal/color"
|
"github.com/minio/minio/internal/color"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/v2/console"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
"github.com/minio/pkg/trie"
|
"github.com/minio/pkg/v2/trie"
|
||||||
"github.com/minio/pkg/words"
|
"github.com/minio/pkg/v2/words"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GlobalFlags - global flags for minio.
|
// GlobalFlags - global flags for minio.
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/v2/console"
|
||||||
)
|
)
|
||||||
|
|
||||||
// a bucketMetacache keeps track of all caches generated
|
// a bucketMetacache keeps track of all caches generated
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/v2/console"
|
||||||
)
|
)
|
||||||
|
|
||||||
// metaCacheEntry is an object or a directory within an unknown bucket.
|
// metaCacheEntry is an object or a directory within an unknown bucket.
|
||||||
@@ -248,9 +248,9 @@ func (e *metaCacheEntry) fileInfo(bucket string) (FileInfo, error) {
|
|||||||
ModTime: timeSentinel1970,
|
ModTime: timeSentinel1970,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return e.cached.ToFileInfo(bucket, e.name, "", false)
|
return e.cached.ToFileInfo(bucket, e.name, "", false, false)
|
||||||
}
|
}
|
||||||
return getFileInfo(e.metadata, bucket, e.name, "", false)
|
return getFileInfo(e.metadata, bucket, e.name, "", false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// xlmeta returns the decoded metadata.
|
// xlmeta returns the decoded metadata.
|
||||||
@@ -291,7 +291,7 @@ func (e *metaCacheEntry) fileInfoVersions(bucket string) (FileInfoVersions, erro
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
// Too small gains to reuse cache here.
|
// Too small gains to reuse cache here.
|
||||||
return getFileInfoVersions(e.metadata, bucket, e.name)
|
return getFileInfoVersions(e.metadata, bucket, e.name, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// metaCacheEntries is a slice of metacache entries.
|
// metaCacheEntries is a slice of metacache entries.
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func renameAllBucketMetacache(epPath string) error {
|
|||||||
if typ == os.ModeDir {
|
if typ == os.ModeDir {
|
||||||
tmpMetacacheOld := pathutil.Join(epPath, minioMetaTmpDeletedBucket, mustGetUUID())
|
tmpMetacacheOld := pathutil.Join(epPath, minioMetaTmpDeletedBucket, mustGetUUID())
|
||||||
if err := renameAll(pathJoin(epPath, minioMetaBucket, metacachePrefixForID(name, slashSeparator)),
|
if err := renameAll(pathJoin(epPath, minioMetaBucket, metacachePrefixForID(name, slashSeparator)),
|
||||||
tmpMetacacheOld); err != nil && err != errFileNotFound {
|
tmpMetacacheOld, epPath); err != nil && err != errFileNotFound {
|
||||||
return fmt.Errorf("unable to rename (%s -> %s) %w",
|
return fmt.Errorf("unable to rename (%s -> %s) %w",
|
||||||
pathJoin(epPath, minioMetaBucket+metacachePrefixForID(minioMetaBucket, slashSeparator)),
|
pathJoin(epPath, minioMetaBucket+metacachePrefixForID(minioMetaBucket, slashSeparator)),
|
||||||
tmpMetacacheOld,
|
tmpMetacacheOld,
|
||||||
@@ -60,6 +60,10 @@ func (z *erasureServerPools) listPath(ctx context.Context, o *listPathOptions) (
|
|||||||
if err := checkListObjsArgs(ctx, o.Bucket, o.Prefix, o.Marker, z); err != nil {
|
if err := checkListObjsArgs(ctx, o.Bucket, o.Prefix, o.Marker, z); err != nil {
|
||||||
return entries, err
|
return entries, err
|
||||||
}
|
}
|
||||||
|
// Marker points to before the prefix, just ignore it.
|
||||||
|
if o.Marker < o.Prefix {
|
||||||
|
o.Marker = ""
|
||||||
|
}
|
||||||
|
|
||||||
// Marker is set validate pre-condition.
|
// Marker is set validate pre-condition.
|
||||||
if o.Marker != "" && o.Prefix != "" {
|
if o.Marker != "" && o.Prefix != "" {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/color"
|
"github.com/minio/minio/internal/color"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/v2/console"
|
||||||
)
|
)
|
||||||
|
|
||||||
type listPathOptions struct {
|
type listPathOptions struct {
|
||||||
@@ -761,7 +761,7 @@ func (er *erasureObjects) saveMetaCacheStream(ctx context.Context, mc *metaCache
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
o.debugln(color.Green("saveMetaCacheStream:")+" saving block", b.n, "to", o.objectPath(b.n))
|
o.debugln(color.Green("saveMetaCacheStream:")+" saving block", b.n, "to", o.objectPath(b.n))
|
||||||
r, err := hash.NewReader(bytes.NewReader(b.data), int64(len(b.data)), "", "", int64(len(b.data)))
|
r, err := hash.NewReader(ctx, bytes.NewReader(b.data), int64(len(b.data)), "", "", int64(len(b.data)))
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
custom := b.headerKV()
|
custom := b.headerKV()
|
||||||
_, err = er.putMetacacheObject(ctx, o.objectPath(b.n), NewPutObjReader(r), ObjectOptions{
|
_, err = er.putMetacacheObject(ctx, o.objectPath(b.n), NewPutObjReader(r), ObjectOptions{
|
||||||
|
|||||||
+15
-10
@@ -18,7 +18,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -32,6 +31,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/valyala/bytebufferpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WalkDirOptions provides options for WalkDir operations.
|
// WalkDirOptions provides options for WalkDir operations.
|
||||||
@@ -144,7 +144,12 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
|
|
||||||
scanDir = func(current string) error {
|
scanDir = func(current string) error {
|
||||||
// Skip forward, if requested...
|
// Skip forward, if requested...
|
||||||
var sb bytes.Buffer
|
sb := bytebufferpool.Get()
|
||||||
|
defer func() {
|
||||||
|
sb.Reset()
|
||||||
|
bytebufferpool.Put(sb)
|
||||||
|
}()
|
||||||
|
|
||||||
forward := ""
|
forward := ""
|
||||||
if len(opts.ForwardTo) > 0 && strings.HasPrefix(opts.ForwardTo, current) {
|
if len(opts.ForwardTo) > 0 && strings.HasPrefix(opts.ForwardTo, current) {
|
||||||
forward = strings.TrimPrefix(opts.ForwardTo, current)
|
forward = strings.TrimPrefix(opts.ForwardTo, current)
|
||||||
@@ -228,7 +233,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
if s.walkReadMu != nil {
|
if s.walkReadMu != nil {
|
||||||
s.walkReadMu.Lock()
|
s.walkReadMu.Lock()
|
||||||
}
|
}
|
||||||
meta.metadata, err = s.readMetadata(ctx, pathJoinBuf(&sb, volumeDir, current, entry))
|
meta.metadata, err = s.readMetadata(ctx, pathJoinBuf(sb, volumeDir, current, entry))
|
||||||
if s.walkReadMu != nil {
|
if s.walkReadMu != nil {
|
||||||
s.walkReadMu.Unlock()
|
s.walkReadMu.Unlock()
|
||||||
}
|
}
|
||||||
@@ -244,7 +249,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
}
|
}
|
||||||
meta.name = strings.TrimSuffix(entry, xlStorageFormatFile)
|
meta.name = strings.TrimSuffix(entry, xlStorageFormatFile)
|
||||||
meta.name = strings.TrimSuffix(meta.name, SlashSeparator)
|
meta.name = strings.TrimSuffix(meta.name, SlashSeparator)
|
||||||
meta.name = pathJoinBuf(&sb, current, meta.name)
|
meta.name = pathJoinBuf(sb, current, meta.name)
|
||||||
meta.name = decodeDirObject(meta.name)
|
meta.name = decodeDirObject(meta.name)
|
||||||
if err := send(meta); err != nil {
|
if err := send(meta); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -254,7 +259,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
// Check legacy.
|
// Check legacy.
|
||||||
if HasSuffix(entry, xlStorageFormatFileV1) && legacy {
|
if HasSuffix(entry, xlStorageFormatFileV1) && legacy {
|
||||||
var meta metaCacheEntry
|
var meta metaCacheEntry
|
||||||
meta.metadata, err = xioutil.ReadFile(pathJoinBuf(&sb, volumeDir, current, entry))
|
meta.metadata, err = xioutil.ReadFile(pathJoinBuf(sb, volumeDir, current, entry))
|
||||||
diskHealthCheckOK(ctx, err)
|
diskHealthCheckOK(ctx, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !IsErrIgnored(err, io.EOF, io.ErrUnexpectedEOF) {
|
if !IsErrIgnored(err, io.EOF, io.ErrUnexpectedEOF) {
|
||||||
@@ -264,7 +269,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
}
|
}
|
||||||
meta.name = strings.TrimSuffix(entry, xlStorageFormatFileV1)
|
meta.name = strings.TrimSuffix(entry, xlStorageFormatFileV1)
|
||||||
meta.name = strings.TrimSuffix(meta.name, SlashSeparator)
|
meta.name = strings.TrimSuffix(meta.name, SlashSeparator)
|
||||||
meta.name = pathJoinBuf(&sb, current, meta.name)
|
meta.name = pathJoinBuf(sb, current, meta.name)
|
||||||
if err := send(meta); err != nil {
|
if err := send(meta); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -297,7 +302,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
if contextCanceled(ctx) {
|
if contextCanceled(ctx) {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
meta := metaCacheEntry{name: pathJoinBuf(&sb, current, entry)}
|
meta := metaCacheEntry{name: pathJoinBuf(sb, current, entry)}
|
||||||
|
|
||||||
// If directory entry on stack before this, pop it now.
|
// If directory entry on stack before this, pop it now.
|
||||||
for len(dirStack) > 0 && dirStack[len(dirStack)-1] < meta.name {
|
for len(dirStack) > 0 && dirStack[len(dirStack)-1] < meta.name {
|
||||||
@@ -323,7 +328,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
if s.walkReadMu != nil {
|
if s.walkReadMu != nil {
|
||||||
s.walkReadMu.Lock()
|
s.walkReadMu.Lock()
|
||||||
}
|
}
|
||||||
meta.metadata, err = s.readMetadata(ctx, pathJoinBuf(&sb, volumeDir, meta.name, xlStorageFormatFile))
|
meta.metadata, err = s.readMetadata(ctx, pathJoinBuf(sb, volumeDir, meta.name, xlStorageFormatFile))
|
||||||
if s.walkReadMu != nil {
|
if s.walkReadMu != nil {
|
||||||
s.walkReadMu.Unlock()
|
s.walkReadMu.Unlock()
|
||||||
}
|
}
|
||||||
@@ -339,7 +344,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
}
|
}
|
||||||
case osIsNotExist(err), isSysErrIsDir(err):
|
case osIsNotExist(err), isSysErrIsDir(err):
|
||||||
if legacy {
|
if legacy {
|
||||||
meta.metadata, err = xioutil.ReadFile(pathJoinBuf(&sb, volumeDir, meta.name, xlStorageFormatFileV1))
|
meta.metadata, err = xioutil.ReadFile(pathJoinBuf(sb, volumeDir, meta.name, xlStorageFormatFileV1))
|
||||||
diskHealthCheckOK(ctx, err)
|
diskHealthCheckOK(ctx, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// It was an object
|
// It was an object
|
||||||
@@ -353,7 +358,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
// NOT an object, append to stack (with slash)
|
// NOT an object, append to stack (with slash)
|
||||||
// If dirObject, but no metadata (which is unexpected) we skip it.
|
// If dirObject, but no metadata (which is unexpected) we skip it.
|
||||||
if !isDirObj {
|
if !isDirObj {
|
||||||
if !isDirEmpty(pathJoinBuf(&sb, volumeDir, meta.name)) {
|
if !isDirEmpty(pathJoinBuf(sb, volumeDir, meta.name)) {
|
||||||
dirStack = append(dirStack, meta.name+slashSeparator)
|
dirStack = append(dirStack, meta.name+slashSeparator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user