mirror of
https://github.com/pgsty/minio.git
synced 2026-08-10 08:13:28 +03:00
Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a8332efa94 | |||
| 3dbef72dc7 | |||
| 2d16e74f38 | |||
| de5070446d | |||
| 374abd1e7d | |||
| 0506d9e83d | |||
| bd3dfad8b9 | |||
| 9fff315555 | |||
| 07b6dce1a5 | |||
| 18fb86b7be | |||
| 58a8275e84 | |||
| 196fab6834 | |||
| 85fc7cea97 | |||
| 328d660106 | |||
| c68910005b | |||
| c79bcc8838 | |||
| 0fe58dbb34 | |||
| 6cb2f56395 | |||
| 59e33b3b21 | |||
| 0e3c92c027 | |||
| db7a9b2c37 | |||
| 44097faec1 | |||
| ff5fca76ab | |||
| bf3da5081f | |||
| 5532982857 | |||
| 783dd875f7 | |||
| 97112c69be | |||
| b0b573052a | |||
| 2939000342 | |||
| 41e1654f9a | |||
| e3cb0278ce | |||
| 0c81f1bdb3 | |||
| 6220875803 | |||
| afd4279cd8 | |||
| 0c8dd8046a | |||
| 3c4ef4338f | |||
| 64cf887b28 | |||
| 927a879052 | |||
| dfe0c96b87 | |||
| e856e10ac2 | |||
| 6d6a731d6d | |||
| 928feb0889 | |||
| b3febe2d24 | |||
| b6b26dba87 | |||
| 5c034e26bd | |||
| cef0fb1434 | |||
| 158d0e26a2 | |||
| 78385bfbeb | |||
| 2a13cc28f2 | |||
| 4d761fda81 | |||
| 4bdf41a6c7 | |||
| 3c605c93fe | |||
| 121f18a443 | |||
| 538aeef27a | |||
| be0d2537b7 | |||
| 3307aa1260 | |||
| 57cfdfd8fb | |||
| dc6733dacc | |||
| f696a221af | |||
| ed5b67720c | |||
| 2aac50571d | |||
| 45edd27ad7 | |||
| c302d1cfc8 | |||
| 6287e8c571 | |||
| f69a98ce49 | |||
| d44f3526dc | |||
| 41b633f5ea | |||
| 4f1ff9c4d9 | |||
| 86bb48792c | |||
| 94dbb4a427 | |||
| 048a46ec2a | |||
| 1480340830 | |||
| 877bd95fa3 | |||
| 8ea6fb368d | |||
| 5fd5ddea23 | |||
| b04c0697e1 | |||
| 50a8ba6a6f | |||
| 334f1ed45a | |||
| 20c89ebbb3 | |||
| 6f56ba80b3 | |||
| 6e84283c66 | |||
| 9d6fddcfdf | |||
| a83105df9d | |||
| 9528b55c25 | |||
| 749ce107ee | |||
| b2a67834ac | |||
| aec2aa3497 | |||
| c7dcbfd6c1 | |||
| ff12080ff5 | |||
| 0b6175b742 | |||
| cf49da387b | |||
| ac714e7e3d | |||
| 79fb79b71c | |||
| 98874c3baf |
@@ -28,14 +28,6 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
||||
- name: Build on ${{ matrix.os }}
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
env:
|
||||
|
||||
@@ -28,24 +28,6 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
- uses: actions/cache@v2
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
||||
- uses: actions/cache@v2
|
||||
if: matrix.os == 'windows-latest'
|
||||
with:
|
||||
path: |
|
||||
%LocalAppData%\go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
||||
- name: Build on ${{ matrix.os }}
|
||||
if: matrix.os == 'windows-latest'
|
||||
env:
|
||||
@@ -63,9 +45,6 @@ jobs:
|
||||
sudo apt install jq -y
|
||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||
nancy_version=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/sonatype-nexus-community/nancy/releases/latest | sed "s/https:\/\/github.com\/sonatype-nexus-community\/nancy\/releases\/tag\///")
|
||||
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-${nancy_version}-linux-amd64 && chmod +x nancy
|
||||
go list -deps -json ./... | jq -s 'unique_by(.Module.Path)|.[]|select(has("Module"))|.Module' | ./nancy sleuth
|
||||
make
|
||||
make test
|
||||
make test-race
|
||||
|
||||
@@ -28,23 +28,12 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
||||
- name: Build on ${{ matrix.os }}
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: on
|
||||
MINIO_KMS_KES_CERT_FILE: /home/runner/work/minio/minio/.github/workflows/root.cert
|
||||
MINIO_KMS_KES_KEY_FILE: /home/runner/work/minio/minio/.github/workflows/root.key
|
||||
MINIO_KMS_KES_ENDPOINT: "https://play.min.io:7373"
|
||||
MINIO_KMS_KES_KEY_NAME: "my-minio-key"
|
||||
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||
MINIO_KMS_AUTO_ENCRYPTION: on
|
||||
run: |
|
||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||
|
||||
@@ -80,14 +80,6 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
||||
- name: Test LDAP/OpenID/Etcd combo
|
||||
env:
|
||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||
|
||||
@@ -29,14 +29,6 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
||||
- name: Test Decom
|
||||
run: |
|
||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
linters-settings:
|
||||
golint:
|
||||
min-confidence: 0
|
||||
|
||||
gofumpt:
|
||||
lang-version: "1.18"
|
||||
|
||||
# Choose whether or not to use the extra rules that are disabled
|
||||
# by default
|
||||
extra-rules: false
|
||||
|
||||
misspell:
|
||||
locale: US
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
CVE-2020-26160
|
||||
CVE-2020-15136
|
||||
CVE-2020-15115
|
||||
CVE-2020-15114
|
||||
CVE-2022-2835
|
||||
CVE-2022-2837
|
||||
@@ -4514,213 +4514,6 @@ https://github.com/docker/go-units
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/dswarbrick/smart
|
||||
https://github.com/dswarbrick/smart
|
||||
----------------------------------------------------------------
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/dustin/go-humanize
|
||||
https://github.com/dustin/go-humanize
|
||||
----------------------------------------------------------------
|
||||
@@ -14610,6 +14403,33 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/lithammer/shortuuid/v4
|
||||
https://github.com/lithammer/shortuuid/v4
|
||||
----------------------------------------------------------------
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Peter Lithammer
|
||||
|
||||
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/lucasb-eyer/go-colorful
|
||||
https://github.com/lucasb-eyer/go-colorful
|
||||
----------------------------------------------------------------
|
||||
|
||||
@@ -14,7 +14,7 @@ Use the following commands to run a standalone MinIO server as a container.
|
||||
|
||||
Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication
|
||||
require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically,
|
||||
with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html)
|
||||
with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html)
|
||||
for more complete documentation.
|
||||
|
||||
### Stable
|
||||
@@ -32,7 +32,7 @@ root credentials. You can use the Browser to create buckets, upload objects, and
|
||||
|
||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See
|
||||
[Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers,
|
||||
see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
||||
see <https://min.io/docs/minio/linux/developers/minio-drivers.html> to view MinIO SDKs for supported languages.
|
||||
|
||||
> NOTE: To deploy MinIO on with persistent storage, you must map local persistent directories from the host OS to the container using the `podman -v` option. For example, `-v /mnt/data:/data` maps the host OS drive at `/mnt/data` to `/data` on the container.
|
||||
|
||||
@@ -40,7 +40,7 @@ see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to vi
|
||||
|
||||
Use the following commands to run a standalone MinIO server on macOS.
|
||||
|
||||
Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
||||
Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html) for more complete documentation.
|
||||
|
||||
### Homebrew (recommended)
|
||||
|
||||
@@ -60,7 +60,7 @@ brew install minio/stable/minio
|
||||
|
||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||
|
||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://min.io/docs/minio/linux/developers/minio-drivers.html/> to view MinIO SDKs for supported languages.
|
||||
|
||||
### Binary Download
|
||||
|
||||
@@ -74,7 +74,7 @@ chmod +x minio
|
||||
|
||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||
|
||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://min.io/docs/minio/linux/developers/minio-drivers.html> to view MinIO SDKs for supported languages.
|
||||
|
||||
## GNU/Linux
|
||||
|
||||
@@ -99,9 +99,9 @@ The following table lists supported architectures. Replace the `wget` URL with t
|
||||
|
||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||
|
||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://min.io/docs/minio/linux/developers/minio-drivers.html> to view MinIO SDKs for supported languages.
|
||||
|
||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html#) for more complete documentation.
|
||||
|
||||
## Microsoft Windows
|
||||
|
||||
@@ -119,23 +119,23 @@ minio.exe server D:\
|
||||
|
||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||
|
||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://min.io/docs/minio/linux/developers/minio-drivers.html> to view MinIO SDKs for supported languages.
|
||||
|
||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html#) for more complete documentation.
|
||||
|
||||
## Install from Source
|
||||
|
||||
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.18](https://golang.org/dl/#stable)
|
||||
|
||||
```sh
|
||||
GO111MODULE=on go install github.com/minio/minio@latest
|
||||
go install github.com/minio/minio@latest
|
||||
```
|
||||
|
||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||
|
||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://min.io/docs/minio/linux/developers/minio-drivers.html> to view MinIO SDKs for supported languages.
|
||||
|
||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html) for more complete documentation.
|
||||
|
||||
MinIO strongly recommends *against* using compiled-from-source MinIO servers for production environments.
|
||||
|
||||
@@ -222,7 +222,7 @@ For example: `export MINIO_SERVER_URL="https://minio.example.net"`
|
||||
|
||||
## Test using MinIO Client `mc`
|
||||
|
||||
`mc` provides a modern alternative to UNIX commands like ls, cat, cp, mirror, diff etc. It supports filesystems and Amazon S3 compatible cloud storage services. Follow the MinIO Client [Quickstart Guide](https://docs.min.io/docs/minio-client-quickstart-guide) for further instructions.
|
||||
`mc` provides a modern alternative to UNIX commands like ls, cat, cp, mirror, diff etc. It supports filesystems and Amazon S3 compatible cloud storage services. Follow the MinIO Client [Quickstart Guide](https://min.io/docs/minio/linux/reference/minio-mc.html#quickstart) for further instructions.
|
||||
|
||||
## Upgrading MinIO
|
||||
|
||||
@@ -230,7 +230,7 @@ Upgrades require zero downtime in MinIO, all upgrades are non-disruptive, all tr
|
||||
|
||||
> NOTE: requires internet access to update directly from <https://dl.min.io>, optionally you can host any mirrors at <https://my-artifactory.example.com/minio/>
|
||||
|
||||
- For deployments that installed the MinIO server binary by hand, use [`mc admin update`](https://docs.min.io/minio/baremetal/reference/minio-mc-admin/mc-admin-update.html)
|
||||
- For deployments that installed the MinIO server binary by hand, use [`mc admin update`](https://min.io/docs/minio/linux/reference/minio-mc-admin/mc-admin-update.html)
|
||||
|
||||
```sh
|
||||
mc admin update <minio alias, e.g., myminio>
|
||||
@@ -250,12 +250,10 @@ mc admin update <minio alias, e.g., myminio>
|
||||
|
||||
## Explore Further
|
||||
|
||||
- [MinIO Erasure Code QuickStart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
||||
- [Use `mc` with MinIO Server](https://docs.min.io/docs/minio-client-quickstart-guide)
|
||||
- [Use `aws-cli` with MinIO Server](https://docs.min.io/docs/aws-cli-with-minio)
|
||||
- [Use `s3cmd` with MinIO Server](https://docs.min.io/docs/s3cmd-with-minio)
|
||||
- [Use `minio-go` SDK with MinIO Server](https://docs.min.io/docs/golang-client-quickstart-guide)
|
||||
- [The MinIO documentation website](https://docs.min.io)
|
||||
- [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html)
|
||||
- [Use `mc` with MinIO Server](https://min.io/docs/minio/linux/reference/minio-mc.html)
|
||||
- [Use `minio-go` SDK with MinIO Server](https://min.io/docs/minio/linux/developers/go/minio-go.html)
|
||||
- [The MinIO documentation website](https://min.io/docs/minio/linux/index.html)
|
||||
|
||||
## Contribute to MinIO Project
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ function _init() {
|
||||
export CGO_ENABLED=0
|
||||
|
||||
## List of architectures and OS to test coss compilation.
|
||||
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips"
|
||||
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips openbsd/amd64"
|
||||
}
|
||||
|
||||
function _build() {
|
||||
|
||||
@@ -66,7 +66,7 @@ __init__() {
|
||||
mc mb minio/minio-test/
|
||||
mc cp ./minio minio/minio-test/to-read/
|
||||
mc cp /etc/hosts minio/minio-test/to-read/hosts
|
||||
mc policy set download minio/minio-test
|
||||
mc anonymous set download minio/minio-test
|
||||
|
||||
verify_checksum_mc ./minio minio/minio-test/to-read/minio
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -74,7 +73,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
||||
return
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||
return
|
||||
@@ -671,7 +670,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||
return
|
||||
@@ -871,7 +870,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
||||
continue
|
||||
}
|
||||
|
||||
bucketPolicyBytes, err := ioutil.ReadAll(io.LimitReader(reader, sz))
|
||||
bucketPolicyBytes, err := io.ReadAll(io.LimitReader(reader, sz))
|
||||
if err != nil {
|
||||
rpt.SetStatus(bucket, fileName, err)
|
||||
continue
|
||||
@@ -1032,7 +1031,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
||||
continue
|
||||
}
|
||||
case bucketQuotaConfigFile:
|
||||
data, err := ioutil.ReadAll(reader)
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
rpt.SetStatus(bucket, fileName, err)
|
||||
continue
|
||||
@@ -1084,7 +1083,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
||||
}
|
||||
|
||||
// ReplicationDiffHandler - POST returns info on unreplicated versions for a remote target ARN
|
||||
// to the connected HTTP client. This is a MinIO only extension
|
||||
// to the connected HTTP client.
|
||||
func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ReplicationDiff")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
@@ -1097,15 +1096,8 @@ func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff)
|
||||
if objectAPI == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
return
|
||||
}
|
||||
// check if user has permissions to perform this operation
|
||||
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketVersionsAction, bucket, ""); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err = validateConfig(cfg, subSys); err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||
return
|
||||
@@ -91,6 +92,12 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
// freshly retrieve the config so that default values are loaded for reset config
|
||||
if cfg, err = getValidConfig(objectAPI); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
dynamic := config.SubSystemsDynamic.Contains(subSys)
|
||||
if dynamic {
|
||||
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
||||
@@ -173,6 +180,7 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
||||
if dynamic {
|
||||
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
||||
}
|
||||
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -27,16 +28,13 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/config/identity/openid"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
"github.com/minio/pkg/ldap"
|
||||
)
|
||||
|
||||
// List of implemented ID config types.
|
||||
var idCfgTypes = set.CreateStringSet("openid")
|
||||
|
||||
// SetIdentityProviderCfg:
|
||||
//
|
||||
// PUT <admin-prefix>/id-cfg?type=openid&name=dex1
|
||||
@@ -64,18 +62,18 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
cfgType := mux.Vars(r)["type"]
|
||||
if !idCfgTypes.Contains(cfgType) {
|
||||
// TODO: change this to invalid type error when implementation
|
||||
// is complete.
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
idpCfgType := mux.Vars(r)["type"]
|
||||
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var cfgDataBuilder strings.Builder
|
||||
switch cfgType {
|
||||
case "openid":
|
||||
switch idpCfgType {
|
||||
case madmin.OpenidIDPCfg:
|
||||
fmt.Fprintf(&cfgDataBuilder, "identity_openid")
|
||||
case madmin.LDAPIDPCfg:
|
||||
fmt.Fprintf(&cfgDataBuilder, "identity_ldap")
|
||||
}
|
||||
|
||||
// Ensure body content type is opaque.
|
||||
@@ -88,6 +86,13 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
||||
// Subsystem configuration name could be empty.
|
||||
cfgName := mux.Vars(r)["name"]
|
||||
if cfgName != "" {
|
||||
if idpCfgType == madmin.LDAPIDPCfg {
|
||||
// LDAP does not support multiple configurations. So this must be
|
||||
// empty.
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(&cfgDataBuilder, "%s%s", config.SubSystemSeparator, cfgName)
|
||||
}
|
||||
|
||||
@@ -119,6 +124,16 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
||||
}
|
||||
|
||||
if err = validateConfig(cfg, subSys); err != nil {
|
||||
|
||||
var validationErr ldap.Validation
|
||||
if errors.As(err, &validationErr) {
|
||||
// If we got an LDAP validation error, we need to send appropriate
|
||||
// error message back to client (likely mc).
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigLDAPValidation),
|
||||
validationErr.FormatError(), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -156,28 +171,32 @@ func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
cfgType := mux.Vars(r)["type"]
|
||||
idpCfgType := mux.Vars(r)["type"]
|
||||
cfgName := r.Form.Get("name")
|
||||
password := cred.SecretKey
|
||||
|
||||
if !idCfgTypes.Contains(cfgType) {
|
||||
// TODO: change this to invalid type error when implementation
|
||||
// is complete.
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// If no cfgName is provided, we list.
|
||||
if cfgName == "" {
|
||||
a.listIdentityProviders(ctx, w, r, cfgType, password)
|
||||
a.listIdentityProviders(ctx, w, r, idpCfgType, password)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := globalServerConfig.Clone()
|
||||
|
||||
cfgInfos, err := globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
|
||||
var cfgInfos []madmin.IDPCfgInfo
|
||||
var err error
|
||||
switch idpCfgType {
|
||||
case madmin.OpenidIDPCfg:
|
||||
cfgInfos, err = globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
|
||||
case madmin.LDAPIDPCfg:
|
||||
cfgInfos, err = globalLDAPConfig.GetConfigInfo(cfg, cfgName)
|
||||
}
|
||||
if err != nil {
|
||||
if err == openid.ErrProviderConfigNotFound {
|
||||
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -187,7 +206,7 @@ func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
||||
}
|
||||
|
||||
res := madmin.IDPConfig{
|
||||
Type: cfgType,
|
||||
Type: idpCfgType,
|
||||
Name: cfgName,
|
||||
Info: cfgInfos,
|
||||
}
|
||||
@@ -206,18 +225,22 @@ func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
||||
writeSuccessResponseJSON(w, econfigData)
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) listIdentityProviders(ctx context.Context, w http.ResponseWriter, r *http.Request, cfgType, password string) {
|
||||
// var subSys string
|
||||
switch cfgType {
|
||||
case "openid":
|
||||
// subSys = config.IdentityOpenIDSubSys
|
||||
func (a adminAPIHandlers) listIdentityProviders(ctx context.Context, w http.ResponseWriter, r *http.Request, idpCfgType, password string) {
|
||||
var cfgList []madmin.IDPListItem
|
||||
var err error
|
||||
switch idpCfgType {
|
||||
case madmin.OpenidIDPCfg:
|
||||
cfg := globalServerConfig.Clone()
|
||||
cfgList, err = globalOpenIDConfig.GetConfigList(cfg)
|
||||
case madmin.LDAPIDPCfg:
|
||||
cfg := globalServerConfig.Clone()
|
||||
cfgList, err = globalLDAPConfig.GetConfigList(cfg)
|
||||
|
||||
default:
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := globalServerConfig.Clone()
|
||||
cfgList, err := globalOpenIDConfig.GetConfigList(cfg)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
@@ -251,57 +274,82 @@ func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
cfgType := mux.Vars(r)["type"]
|
||||
idpCfgType := mux.Vars(r)["type"]
|
||||
cfgName := mux.Vars(r)["name"]
|
||||
if !idCfgTypes.Contains(cfgType) {
|
||||
// TODO: change this to invalid type error when implementation
|
||||
// is complete.
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := globalServerConfig.Clone()
|
||||
cfgCopy := globalServerConfig.Clone()
|
||||
var subSys string
|
||||
switch idpCfgType {
|
||||
case madmin.OpenidIDPCfg:
|
||||
subSys = config.IdentityOpenIDSubSys
|
||||
cfgInfos, err := globalOpenIDConfig.GetConfigInfo(cfgCopy, cfgName)
|
||||
if err != nil {
|
||||
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
cfgInfos, err := globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
|
||||
if err != nil {
|
||||
if err == openid.ErrProviderConfigNotFound {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
hasEnv := false
|
||||
for _, ci := range cfgInfos {
|
||||
if ci.IsCfg && ci.IsEnv {
|
||||
hasEnv = true
|
||||
break
|
||||
hasEnv := false
|
||||
for _, ci := range cfgInfos {
|
||||
if ci.IsCfg && ci.IsEnv {
|
||||
hasEnv = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasEnv {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigEnvOverridden), r.URL)
|
||||
return
|
||||
}
|
||||
if hasEnv {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigEnvOverridden), r.URL)
|
||||
return
|
||||
}
|
||||
case madmin.LDAPIDPCfg:
|
||||
subSys = config.IdentityLDAPSubSys
|
||||
cfgInfos, err := globalLDAPConfig.GetConfigInfo(cfgCopy, cfgName)
|
||||
if err != nil {
|
||||
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var subSys string
|
||||
switch cfgType {
|
||||
case "openid":
|
||||
subSys = config.IdentityOpenIDSubSys
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
hasEnv := false
|
||||
for _, ci := range cfgInfos {
|
||||
if ci.IsCfg && ci.IsEnv {
|
||||
hasEnv = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasEnv {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigEnvOverridden), r.URL)
|
||||
return
|
||||
}
|
||||
default:
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err = readServerConfig(ctx, objectAPI)
|
||||
cfg, err := readServerConfig(ctx, objectAPI)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err = cfg.DelKVS(fmt.Sprintf("%s:%s", subSys, cfgName)); err != nil {
|
||||
cfgKey := fmt.Sprintf("%s:%s", subSys, cfgName)
|
||||
if cfgName == madmin.Default {
|
||||
cfgKey = subSys
|
||||
}
|
||||
if err = cfg.DelKVS(cfgKey); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -319,7 +318,7 @@ func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
func parseJSONBody(ctx context.Context, body io.Reader, v interface{}, encryptionKey string) error {
|
||||
data, err := ioutil.ReadAll(body)
|
||||
data, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
return SRError{
|
||||
Cause: err,
|
||||
|
||||
+37
-27
@@ -22,7 +22,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
@@ -136,7 +135,7 @@ func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Add ldap users which have mapped policies if in LDAP mode
|
||||
// FIXME(vadmeste): move this to policy info in the future
|
||||
ldapUsers, err := globalIAMSys.ListLDAPUsers()
|
||||
ldapUsers, err := globalIAMSys.ListLDAPUsers(ctx)
|
||||
if err != nil && err != errIAMActionNotAllowed {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
@@ -229,7 +228,7 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||
return
|
||||
@@ -658,7 +657,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
||||
// query their groups:
|
||||
if globalLDAPConfig.Enabled {
|
||||
if globalLDAPConfig.Enabled() {
|
||||
opts.claims[ldapUserN] = targetUser // simple username
|
||||
targetUser, targetGroups, err = globalLDAPConfig.LookupUserDN(targetUser)
|
||||
if err != nil {
|
||||
@@ -1190,31 +1189,42 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
||||
accountName = cred.ParentUser
|
||||
}
|
||||
|
||||
roleArn := iampolicy.Args{Claims: claims}.GetRoleArn()
|
||||
var effectivePolicy iampolicy.Policy
|
||||
|
||||
var buf []byte
|
||||
if accountName == globalActiveCred.AccessKey {
|
||||
switch {
|
||||
case accountName == globalActiveCred.AccessKey:
|
||||
for _, policy := range iampolicy.DefaultPolicies {
|
||||
if policy.Name == "consoleAdmin" {
|
||||
buf, err = json.MarshalIndent(policy.Definition, "", " ")
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
effectivePolicy = policy.Definition
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
case roleArn != "":
|
||||
_, policy, err := globalIAMSys.GetRolePolicy(roleArn)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
policySlice := newMappedPolicy(policy).toSlice()
|
||||
effectivePolicy = globalIAMSys.GetCombinedPolicy(policySlice...)
|
||||
|
||||
default:
|
||||
policies, err := globalIAMSys.PolicyDBGet(accountName, false, cred.Groups...)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
effectivePolicy = globalIAMSys.GetCombinedPolicy(policies...)
|
||||
|
||||
buf, err = json.MarshalIndent(globalIAMSys.GetCombinedPolicy(policies...), "", " ")
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
buf, err = json.MarshalIndent(effectivePolicy, "", " ")
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
acctInfo := madmin.AccountInfo{
|
||||
@@ -1464,7 +1474,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
iamPolicyBytes, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength))
|
||||
iamPolicyBytes, err := io.ReadAll(io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
@@ -1848,7 +1858,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||
return
|
||||
}
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||
return
|
||||
@@ -1871,7 +1881,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
defer f.Close()
|
||||
var allPolicies map[string]iampolicy.Policy
|
||||
data, err = ioutil.ReadAll(f)
|
||||
data, err = io.ReadAll(f)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allPoliciesFile, ""), r.URL)
|
||||
return
|
||||
@@ -1906,7 +1916,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
defer f.Close()
|
||||
var userAccts map[string]madmin.AddOrUpdateUserReq
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allUsersFile, ""), r.URL)
|
||||
return
|
||||
@@ -1983,7 +1993,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
defer f.Close()
|
||||
var grpInfos map[string]GroupInfo
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allGroupsFile, ""), r.URL)
|
||||
return
|
||||
@@ -2021,7 +2031,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
defer f.Close()
|
||||
var serviceAcctReqs map[string]madmin.SRSvcAccCreate
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allSvcAcctsFile, ""), r.URL)
|
||||
return
|
||||
@@ -2087,7 +2097,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
||||
// query their groups:
|
||||
if globalLDAPConfig.Enabled {
|
||||
if globalLDAPConfig.Enabled() {
|
||||
opts.claims[ldapUserN] = svcAcctReq.AccessKey // simple username
|
||||
targetUser, _, err := globalLDAPConfig.LookupUserDN(svcAcctReq.AccessKey)
|
||||
if err != nil {
|
||||
@@ -2117,7 +2127,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
defer f.Close()
|
||||
var userPolicyMap map[string]MappedPolicy
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, userPolicyMappingsFile, ""), r.URL)
|
||||
return
|
||||
@@ -2156,7 +2166,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
defer f.Close()
|
||||
var grpPolicyMap map[string]MappedPolicy
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, groupPolicyMappingsFile, ""), r.URL)
|
||||
return
|
||||
@@ -2185,7 +2195,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
defer f.Close()
|
||||
var userPolicyMap map[string]MappedPolicy
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, stsUserPolicyMappingsFile, ""), r.URL)
|
||||
return
|
||||
@@ -2224,7 +2234,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
defer f.Close()
|
||||
var grpPolicyMap map[string]MappedPolicy
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, stsGroupPolicyMappingsFile, ""), r.URL)
|
||||
return
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -388,7 +388,7 @@ func (s *TestSuiteIAM) TestUserPolicyEscalationBug(c *check) {
|
||||
req.ContentLength = int64(len(buf))
|
||||
sum := sha256.Sum256(buf)
|
||||
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum[:]))
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader(buf))
|
||||
req.Body = io.NopCloser(bytes.NewReader(buf))
|
||||
req = signer.SignV4(*req, accessKey, secretKey, "", "")
|
||||
|
||||
// 3.1 Execute the request.
|
||||
@@ -1203,6 +1203,47 @@ func (c *check) mustNotListObjects(ctx context.Context, client *minio.Client, bu
|
||||
}
|
||||
}
|
||||
|
||||
func (c *check) mustPutObjectWithTags(ctx context.Context, client *minio.Client, bucket, object string) {
|
||||
c.Helper()
|
||||
_, err := client.PutObject(ctx, bucket, object, bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{
|
||||
UserTags: map[string]string{
|
||||
"security": "public",
|
||||
"virus": "true",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("user was unable to upload the object: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *check) mustGetObject(ctx context.Context, client *minio.Client, bucket, object string) {
|
||||
c.Helper()
|
||||
|
||||
r, err := client.GetObject(ctx, bucket, object, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
c.Fatalf("user was unable to download the object: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
_, err = io.Copy(io.Discard, r)
|
||||
if err != nil {
|
||||
c.Fatalf("user was unable to download the object: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *check) mustHeadObject(ctx context.Context, client *minio.Client, bucket, object string, tagCount int) {
|
||||
c.Helper()
|
||||
|
||||
oinfo, err := client.StatObject(ctx, bucket, object, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
c.Fatalf("user was unable to download the object: %v", err)
|
||||
}
|
||||
|
||||
if oinfo.UserTagCount != tagCount {
|
||||
c.Fatalf("expected tagCount: %d, got %d", tagCount, oinfo.UserTagCount)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *check) mustListObjects(ctx context.Context, client *minio.Client, bucket string) {
|
||||
c.Helper()
|
||||
res := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
||||
|
||||
+21
-111
@@ -27,7 +27,6 @@ import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
@@ -412,6 +411,7 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
}
|
||||
jobID := r.Form.Get("by-jobID")
|
||||
|
||||
hosts := strings.Split(r.Form.Get("hosts"), ",")
|
||||
byHost := strings.EqualFold(r.Form.Get("by-host"), "true")
|
||||
@@ -433,12 +433,20 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
|
||||
enc := json.NewEncoder(w)
|
||||
for n > 0 {
|
||||
var m madmin.RealtimeMetrics
|
||||
mLocal := collectLocalMetrics(types, hostMap, diskMap)
|
||||
mLocal := collectLocalMetrics(types, collectMetricsOpts{
|
||||
hosts: hostMap,
|
||||
disks: diskMap,
|
||||
jobID: jobID,
|
||||
})
|
||||
m.Merge(&mLocal)
|
||||
|
||||
// Allow half the interval for collecting remote...
|
||||
cctx, cancel := context.WithTimeout(ctx, interval/2)
|
||||
mRemote := collectRemoteMetrics(cctx, types, hostMap, diskMap)
|
||||
mRemote := collectRemoteMetrics(cctx, types, collectMetricsOpts{
|
||||
hosts: hostMap,
|
||||
disks: diskMap,
|
||||
jobID: jobID,
|
||||
})
|
||||
cancel()
|
||||
m.Merge(&mRemote)
|
||||
if !byHost {
|
||||
@@ -450,7 +458,7 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
m.Final = n <= 1
|
||||
|
||||
// Marshal API response
|
||||
// Marshal API reesponse
|
||||
if err := enc.Encode(&m); err != nil {
|
||||
n = 0
|
||||
}
|
||||
@@ -758,7 +766,7 @@ func (a adminAPIHandlers) ProfileHandler(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
// read request body
|
||||
io.CopyN(ioutil.Discard, r.Body, 1)
|
||||
io.CopyN(io.Discard, r.Body, 1)
|
||||
|
||||
globalProfilerMu.Lock()
|
||||
|
||||
@@ -1790,8 +1798,8 @@ func getServerInfo(ctx context.Context, r *http.Request) madmin.InfoMessage {
|
||||
kmsStat := fetchKMSStatus()
|
||||
|
||||
ldap := madmin.LDAP{}
|
||||
if globalLDAPConfig.Enabled {
|
||||
ldapConn, err := globalLDAPConfig.Connect()
|
||||
if globalLDAPConfig.Enabled() {
|
||||
ldapConn, err := globalLDAPConfig.LDAP.Connect()
|
||||
//nolint:gocritic
|
||||
if err != nil {
|
||||
ldap.Status = string(madmin.ItemOffline)
|
||||
@@ -1960,11 +1968,6 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
deadline := 10 * time.Second // Default deadline is 10secs for health diagnostics.
|
||||
if query.Get(string(madmin.HealthDataTypePerfNet)) != "" ||
|
||||
query.Get(string(madmin.HealthDataTypePerfDrive)) != "" ||
|
||||
query.Get(string(madmin.HealthDataTypePerfObj)) != "" {
|
||||
deadline = 1 * time.Hour
|
||||
}
|
||||
if dstr := r.Form.Get("deadline"); dstr != "" {
|
||||
var err error
|
||||
deadline, err = time.ParseDuration(dstr)
|
||||
@@ -2226,87 +2229,6 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
}
|
||||
|
||||
getAndWriteDrivePerfInfo := func() {
|
||||
if query.Get(string(madmin.HealthDataTypePerfDrive)) == "true" {
|
||||
opts := madmin.DriveSpeedTestOpts{
|
||||
Serial: false,
|
||||
BlockSize: 4 * humanize.MiByte,
|
||||
FileSize: 1 * humanize.GiByte,
|
||||
}
|
||||
|
||||
localDPI := driveSpeedTest(ctx, opts)
|
||||
healthInfo.Perf.DrivePerf = append(healthInfo.Perf.DrivePerf, localDPI)
|
||||
|
||||
perfCh := globalNotificationSys.DriveSpeedTest(ctx, opts)
|
||||
for perfInfo := range perfCh {
|
||||
healthInfo.Perf.DrivePerf = append(healthInfo.Perf.DrivePerf, perfInfo)
|
||||
}
|
||||
partialWrite(healthInfo)
|
||||
}
|
||||
}
|
||||
|
||||
getAndWriteObjPerfInfo := func() {
|
||||
if query.Get(string(madmin.HealthDataTypePerfObj)) == "true" {
|
||||
concurrent := 32
|
||||
|
||||
storageInfo, _ := objectAPI.StorageInfo(ctx)
|
||||
|
||||
size := 64 * humanize.MiByte
|
||||
autotune := true
|
||||
|
||||
sufficientCapacity, canAutotune, capacityErrMsg := validateObjPerfOptions(ctx, storageInfo, concurrent, size, autotune)
|
||||
if !sufficientCapacity {
|
||||
healthInfo.Perf.Error = capacityErrMsg
|
||||
partialWrite(healthInfo)
|
||||
return
|
||||
}
|
||||
|
||||
if !canAutotune {
|
||||
autotune = false
|
||||
}
|
||||
|
||||
bucketExists, err := makeObjectPerfBucket(ctx, objectAPI, globalObjectPerfBucket)
|
||||
if err != nil {
|
||||
healthInfo.Perf.Error = "Unable to create bucket: " + err.Error()
|
||||
partialWrite(healthInfo)
|
||||
return
|
||||
}
|
||||
|
||||
if !bucketExists {
|
||||
defer deleteObjectPerfBucket(objectAPI)
|
||||
}
|
||||
|
||||
opts := speedTestOpts{
|
||||
objectSize: size,
|
||||
concurrencyStart: concurrent,
|
||||
duration: 10 * time.Second,
|
||||
autotune: autotune,
|
||||
}
|
||||
|
||||
perfCh := objectSpeedTest(ctx, opts)
|
||||
for perfInfo := range perfCh {
|
||||
healthInfo.Perf.ObjPerf = append(healthInfo.Perf.ObjPerf, perfInfo)
|
||||
}
|
||||
partialWrite(healthInfo)
|
||||
}
|
||||
}
|
||||
|
||||
getAndWriteNetPerfInfo := func() {
|
||||
if query.Get(string(madmin.HealthDataTypePerfObj)) == "true" {
|
||||
if !globalIsDistErasure {
|
||||
return
|
||||
}
|
||||
|
||||
netPerf := globalNotificationSys.Netperf(ctx, time.Second*10)
|
||||
for _, np := range netPerf {
|
||||
np.Endpoint = anonAddr(np.Endpoint)
|
||||
healthInfo.Perf.NetPerf = append(healthInfo.Perf.NetPerf, np)
|
||||
}
|
||||
|
||||
partialWrite(healthInfo)
|
||||
}
|
||||
}
|
||||
|
||||
anonymizeNetwork := func(network map[string]string) map[string]string {
|
||||
anonNetwork := map[string]string{}
|
||||
for endpoint, status := range network {
|
||||
@@ -2336,16 +2258,13 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
||||
getAndWriteMemInfo()
|
||||
getAndWriteProcInfo()
|
||||
getAndWriteMinioConfig()
|
||||
getAndWriteDrivePerfInfo()
|
||||
getAndWriteObjPerfInfo()
|
||||
getAndWriteNetPerfInfo()
|
||||
getAndWriteSysErrors()
|
||||
getAndWriteSysServices()
|
||||
getAndWriteSysConfig()
|
||||
|
||||
if query.Get("minioinfo") == "true" {
|
||||
infoMessage := getServerInfo(ctx, r)
|
||||
servers := []madmin.ServerInfo{}
|
||||
servers := make([]madmin.ServerInfo, 0, len(infoMessage.Servers))
|
||||
for _, server := range infoMessage.Servers {
|
||||
anonEndpoint := anonAddr(server.Endpoint)
|
||||
servers = append(servers, madmin.ServerInfo{
|
||||
@@ -2364,6 +2283,11 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
||||
Frees: server.MemStats.Frees,
|
||||
HeapAlloc: server.MemStats.HeapAlloc,
|
||||
},
|
||||
GoMaxProcs: server.GoMaxProcs,
|
||||
NumCPU: server.NumCPU,
|
||||
RuntimeVersion: server.RuntimeVersion,
|
||||
GCStats: server.GCStats,
|
||||
MinioEnvVars: server.MinioEnvVars,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2556,20 +2480,6 @@ func fetchLambdaInfo() []map[string][]madmin.TargetIDStatus {
|
||||
lambdaMap[targetID.Name] = list
|
||||
}
|
||||
|
||||
for _, tgt := range globalEnvTargetList.Targets() {
|
||||
targetIDStatus := make(map[string]madmin.Status)
|
||||
active, _ := tgt.IsActive()
|
||||
targetID := tgt.ID()
|
||||
if active {
|
||||
targetIDStatus[targetID.ID] = madmin.Status{Status: string(madmin.ItemOnline)}
|
||||
} else {
|
||||
targetIDStatus[targetID.ID] = madmin.Status{Status: string(madmin.ItemOffline)}
|
||||
}
|
||||
list := lambdaMap[targetID.Name]
|
||||
list = append(list, targetIDStatus)
|
||||
lambdaMap[targetID.Name] = list
|
||||
}
|
||||
|
||||
notify := make([]map[string][]madmin.TargetIDStatus, len(lambdaMap))
|
||||
counter := 0
|
||||
for key, value := range lambdaMap {
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@@ -74,7 +73,7 @@ func prepareAdminErasureTestBed(ctx context.Context) (*adminErasureTestBed, erro
|
||||
|
||||
globalEndpoints = mustGetPoolEndpoints(erasureDirs...)
|
||||
|
||||
initAllSubsystems()
|
||||
initAllSubsystems(ctx)
|
||||
|
||||
initConfigSubsystem(ctx, objLayer)
|
||||
|
||||
@@ -220,7 +219,7 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
||||
adminTestBed.router.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
resp, _ := ioutil.ReadAll(rec.Body)
|
||||
resp, _ := io.ReadAll(rec.Body)
|
||||
t.Errorf("Expected to receive %d status code but received %d. Body (%s)",
|
||||
http.StatusOK, rec.Code, string(resp))
|
||||
}
|
||||
|
||||
@@ -100,14 +100,14 @@ type allHealState struct {
|
||||
}
|
||||
|
||||
// newHealState - initialize global heal state management
|
||||
func newHealState(cleanup bool) *allHealState {
|
||||
func newHealState(ctx context.Context, cleanup bool) *allHealState {
|
||||
hstate := &allHealState{
|
||||
healSeqMap: make(map[string]*healSequence),
|
||||
healLocalDisks: make(map[Endpoint]bool),
|
||||
healStatus: make(map[string]healingTracker),
|
||||
}
|
||||
if cleanup {
|
||||
go hstate.periodicHealSeqsClean(GlobalContext)
|
||||
go hstate.periodicHealSeqsClean(ctx)
|
||||
}
|
||||
return hstate
|
||||
}
|
||||
|
||||
@@ -206,6 +206,16 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/replication/diff").HandlerFunc(
|
||||
gz(httpTraceHdrs(adminAPI.ReplicationDiffHandler))).Queries("bucket", "{bucket:.*}")
|
||||
|
||||
// Batch job operations
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/start-job").HandlerFunc(
|
||||
gz(httpTraceHdrs(adminAPI.StartBatchJob)))
|
||||
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-jobs").HandlerFunc(
|
||||
gz(httpTraceHdrs(adminAPI.ListBatchJobs)))
|
||||
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/describe-job").HandlerFunc(
|
||||
gz(httpTraceHdrs(adminAPI.DescribeBatchJob)))
|
||||
|
||||
// Bucket migration operations
|
||||
// ExportBucketMetaHandler
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-bucket-metadata").HandlerFunc(
|
||||
|
||||
@@ -20,10 +20,14 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
@@ -67,6 +71,22 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
||||
var memstats runtime.MemStats
|
||||
runtime.ReadMemStats(&memstats)
|
||||
|
||||
gcStats := debug.GCStats{
|
||||
// If stats.PauseQuantiles is non-empty, ReadGCStats fills
|
||||
// it with quantiles summarizing the distribution of pause time.
|
||||
// For example, if len(stats.PauseQuantiles) is 5, it will be
|
||||
// filled with the minimum, 25%, 50%, 75%, and maximum pause times.
|
||||
PauseQuantiles: make([]time.Duration, 5),
|
||||
}
|
||||
debug.ReadGCStats(&gcStats)
|
||||
// Truncate GC stats to max 5 entries.
|
||||
if len(gcStats.PauseEnd) > 5 {
|
||||
gcStats.PauseEnd = gcStats.PauseEnd[len(gcStats.PauseEnd)-5:]
|
||||
}
|
||||
if len(gcStats.Pause) > 5 {
|
||||
gcStats.Pause = gcStats.Pause[len(gcStats.Pause)-5:]
|
||||
}
|
||||
|
||||
props := madmin.ServerProperties{
|
||||
State: string(madmin.ItemInitializing),
|
||||
Endpoint: addr,
|
||||
@@ -81,6 +101,44 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
||||
Frees: memstats.Frees,
|
||||
HeapAlloc: memstats.HeapAlloc,
|
||||
},
|
||||
GoMaxProcs: runtime.GOMAXPROCS(0),
|
||||
NumCPU: runtime.NumCPU(),
|
||||
RuntimeVersion: runtime.Version(),
|
||||
GCStats: &madmin.GCStats{
|
||||
LastGC: gcStats.LastGC,
|
||||
NumGC: gcStats.NumGC,
|
||||
PauseTotal: gcStats.PauseTotal,
|
||||
Pause: gcStats.Pause,
|
||||
PauseEnd: gcStats.PauseEnd,
|
||||
},
|
||||
MinioEnvVars: make(map[string]string, 10),
|
||||
}
|
||||
|
||||
sensitive := map[string]struct{}{
|
||||
config.EnvAccessKey: {},
|
||||
config.EnvSecretKey: {},
|
||||
config.EnvRootUser: {},
|
||||
config.EnvRootPassword: {},
|
||||
config.EnvMinIOSubnetAPIKey: {},
|
||||
config.EnvKMSSecretKey: {},
|
||||
}
|
||||
for _, v := range os.Environ() {
|
||||
if !strings.HasPrefix(v, "MINIO") && !strings.HasPrefix(v, "_MINIO") {
|
||||
continue
|
||||
}
|
||||
split := strings.SplitN(v, "=", 2)
|
||||
key := split[0]
|
||||
value := ""
|
||||
if len(split) > 1 {
|
||||
value = split[1]
|
||||
}
|
||||
|
||||
// Do not send sensitive creds.
|
||||
if _, ok := sensitive[key]; ok || strings.Contains(strings.ToLower(key), "password") || strings.HasSuffix(strings.ToLower(key), "key") {
|
||||
props.MinioEnvVars[key] = "*** EXISTS, REDACTED ***"
|
||||
continue
|
||||
}
|
||||
props.MinioEnvVars[key] = value
|
||||
}
|
||||
|
||||
objLayer := newObjectLayerFn()
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"google.golang.org/api/googleapi"
|
||||
|
||||
"github.com/minio/madmin-go"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
@@ -263,6 +264,7 @@ const (
|
||||
ErrAdminNoSuchUser
|
||||
ErrAdminNoSuchGroup
|
||||
ErrAdminGroupNotEmpty
|
||||
ErrAdminNoSuchJob
|
||||
ErrAdminNoSuchPolicy
|
||||
ErrAdminInvalidArgument
|
||||
ErrAdminInvalidAccessKey
|
||||
@@ -273,6 +275,8 @@ const (
|
||||
ErrAdminNoSuchConfigTarget
|
||||
ErrAdminConfigEnvOverridden
|
||||
ErrAdminConfigDuplicateKeys
|
||||
ErrAdminConfigInvalidIDPType
|
||||
ErrAdminConfigLDAPValidation
|
||||
ErrAdminCredentialsMismatch
|
||||
ErrInsecureClientRequest
|
||||
ErrObjectTampered
|
||||
@@ -1226,6 +1230,11 @@ var errorCodes = errorCodeMap{
|
||||
Description: "The specified group does not exist.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrAdminNoSuchJob: {
|
||||
Code: "XMinioAdminNoSuchJob",
|
||||
Description: "The specified job does not exist.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrAdminGroupNotEmpty: {
|
||||
Code: "XMinioAdminGroupNotEmpty",
|
||||
Description: "The specified group is not empty - cannot remove it.",
|
||||
@@ -1282,6 +1291,16 @@ var errorCodes = errorCodeMap{
|
||||
Description: "JSON configuration provided has objects with duplicate keys",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrAdminConfigInvalidIDPType: {
|
||||
Code: "XMinioAdminConfigInvalidIDPType",
|
||||
Description: fmt.Sprintf("Invalid IDP configuration type - must be one of %v", madmin.ValidIDPConfigTypes),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrAdminConfigLDAPValidation: {
|
||||
Code: "XMinioAdminConfigLDAPValidation",
|
||||
Description: "LDAP Configuration validation failed",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrAdminConfigNotificationTargetsFailed: {
|
||||
Code: "XMinioAdminNotificationTargetsTestFailed",
|
||||
Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers",
|
||||
@@ -1923,6 +1942,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
apiErr = ErrAdminNoSuchGroup
|
||||
case errGroupNotEmpty:
|
||||
apiErr = ErrAdminGroupNotEmpty
|
||||
case errNoSuchJob:
|
||||
apiErr = ErrAdminNoSuchJob
|
||||
case errNoSuchPolicy:
|
||||
apiErr = ErrAdminNoSuchPolicy
|
||||
case errSignatureMismatch:
|
||||
@@ -2220,6 +2241,12 @@ func toAPIError(ctx context.Context, err error) APIError {
|
||||
// their internal error types. This code is only
|
||||
// useful with gateway implementations.
|
||||
switch e := err.(type) {
|
||||
case batchReplicationJobError:
|
||||
apiErr = APIError{
|
||||
Code: e.Code,
|
||||
Description: e.Description,
|
||||
HTTPStatusCode: e.HTTPStatusCode,
|
||||
}
|
||||
case InvalidArgument:
|
||||
apiErr = APIError{
|
||||
Code: "InvalidArgument",
|
||||
|
||||
+24
-27
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/minio/minio/internal/hash"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
xxml "github.com/minio/xxml"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -90,8 +91,7 @@ type ListVersionsResponse struct {
|
||||
IsTruncated bool
|
||||
|
||||
CommonPrefixes []CommonPrefix
|
||||
DeleteMarkers []DeleteMarkerVersion `xml:"DeleteMarker,omitempty"`
|
||||
Versions []ObjectVersion `xml:"Version,omitempty"`
|
||||
Versions []ObjectVersion
|
||||
|
||||
// Encoding type used to encode object keys in the response.
|
||||
EncodingType string `xml:"EncodingType,omitempty"`
|
||||
@@ -166,10 +166,10 @@ type Part struct {
|
||||
Size int64
|
||||
|
||||
// Checksum values
|
||||
ChecksumCRC32 string
|
||||
ChecksumCRC32C string
|
||||
ChecksumSHA1 string
|
||||
ChecksumSHA256 string
|
||||
ChecksumCRC32 string `xml:"ChecksumCRC32,omitempty"`
|
||||
ChecksumCRC32C string `xml:"ChecksumCRC32C,omitempty"`
|
||||
ChecksumSHA1 string `xml:"ChecksumSHA1,omitempty"`
|
||||
ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
||||
}
|
||||
|
||||
// ListPartsResponse - format for list parts response.
|
||||
@@ -256,6 +256,19 @@ type ObjectVersion struct {
|
||||
Object
|
||||
IsLatest bool
|
||||
VersionID string `xml:"VersionId"`
|
||||
|
||||
isDeleteMarker bool
|
||||
}
|
||||
|
||||
// MarshalXML - marshal ObjectVersion
|
||||
func (o ObjectVersion) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
|
||||
if o.isDeleteMarker {
|
||||
start.Name.Local = "DeleteMarker"
|
||||
} else {
|
||||
start.Name.Local = "Version"
|
||||
}
|
||||
type objectVersionWrapper ObjectVersion
|
||||
return e.EncodeElement(objectVersionWrapper(o), start)
|
||||
}
|
||||
|
||||
// DeleteMarkerVersion container for delete marker metadata
|
||||
@@ -390,10 +403,10 @@ type CompleteMultipartUploadResponse struct {
|
||||
Key string
|
||||
ETag string
|
||||
|
||||
ChecksumCRC32 string
|
||||
ChecksumCRC32C string
|
||||
ChecksumSHA1 string
|
||||
ChecksumSHA256 string
|
||||
ChecksumCRC32 string `xml:"ChecksumCRC32,omitempty"`
|
||||
ChecksumCRC32C string `xml:"ChecksumCRC32C,omitempty"`
|
||||
ChecksumSHA1 string `xml:"ChecksumSHA1,omitempty"`
|
||||
ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
||||
}
|
||||
|
||||
// DeleteError structure.
|
||||
@@ -482,7 +495,6 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
||||
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
||||
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
|
||||
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
||||
deleteMarkers := make([]DeleteMarkerVersion, 0, len(resp.Objects))
|
||||
|
||||
owner := Owner{
|
||||
ID: globalMinioDefaultOwnerID,
|
||||
@@ -494,21 +506,6 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
||||
if object.Name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if object.DeleteMarker {
|
||||
deleteMarker := DeleteMarkerVersion{
|
||||
Key: s3EncodeName(object.Name, encodingType),
|
||||
LastModified: object.ModTime.UTC().Format(iso8601TimeFormat),
|
||||
Owner: owner,
|
||||
VersionID: object.VersionID,
|
||||
}
|
||||
if deleteMarker.VersionID == "" {
|
||||
deleteMarker.VersionID = nullVersionID
|
||||
}
|
||||
deleteMarker.IsLatest = object.IsLatest
|
||||
deleteMarkers = append(deleteMarkers, deleteMarker)
|
||||
continue
|
||||
}
|
||||
content := ObjectVersion{}
|
||||
content.Key = s3EncodeName(object.Name, encodingType)
|
||||
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
||||
@@ -527,12 +524,12 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
||||
content.VersionID = nullVersionID
|
||||
}
|
||||
content.IsLatest = object.IsLatest
|
||||
content.isDeleteMarker = object.DeleteMarker
|
||||
versions = append(versions, content)
|
||||
}
|
||||
|
||||
data.Name = bucket
|
||||
data.Versions = versions
|
||||
data.DeleteMarkers = deleteMarkers
|
||||
data.EncodingType = encodingType
|
||||
data.Prefix = s3EncodeName(prefix, encodingType)
|
||||
data.KeyMarker = s3EncodeName(marker, encodingType)
|
||||
|
||||
+129
-126
File diff suppressed because one or more lines are too long
+91
-49
@@ -25,7 +25,6 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -85,6 +84,8 @@ func isRequestSignStreamingV4(r *http.Request) bool {
|
||||
}
|
||||
|
||||
// Authorization type.
|
||||
//
|
||||
//go:generate stringer -type=authType -trimprefix=authType $GOFILE
|
||||
type authType int
|
||||
|
||||
// List of all supported auth types.
|
||||
@@ -294,24 +295,27 @@ func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]in
|
||||
//
|
||||
// returns APIErrorCode if any to be replied to the client.
|
||||
func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (s3Err APIErrorCode) {
|
||||
_, _, s3Err = checkRequestAuthTypeCredential(ctx, r, action, bucketName, objectName)
|
||||
logger.GetReqInfo(ctx).BucketName = bucketName
|
||||
logger.GetReqInfo(ctx).ObjectName = objectName
|
||||
|
||||
_, _, s3Err = checkRequestAuthTypeCredential(ctx, r, action)
|
||||
return s3Err
|
||||
}
|
||||
|
||||
// Check request auth type verifies the incoming http request
|
||||
// - validates the request signature
|
||||
// - validates the policy action if anonymous tests bucket policies if any,
|
||||
// for authenticated requests validates IAM policies.
|
||||
//
|
||||
// returns APIErrorCode if any to be replied to the client.
|
||||
// Additionally returns the accessKey used in the request, and if this request is by an admin.
|
||||
func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (cred auth.Credentials, owner bool, s3Err APIErrorCode) {
|
||||
func authenticateRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||
if logger.GetReqInfo(ctx) == nil {
|
||||
logger.LogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.Minio)
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
||||
var cred auth.Credentials
|
||||
var owner bool
|
||||
switch getRequestAuthType(r) {
|
||||
case authTypeUnknown, authTypeStreamingSigned:
|
||||
return cred, owner, ErrSignatureVersionNotSupported
|
||||
return ErrSignatureVersionNotSupported
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone {
|
||||
return cred, owner, s3Err
|
||||
return s3Err
|
||||
}
|
||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||
case authTypeSigned, authTypePresigned:
|
||||
@@ -321,52 +325,67 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action
|
||||
region = ""
|
||||
}
|
||||
if s3Err = isReqAuthenticated(ctx, r, region, serviceS3); s3Err != ErrNone {
|
||||
return cred, owner, s3Err
|
||||
return s3Err
|
||||
}
|
||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||
}
|
||||
if s3Err != ErrNone {
|
||||
return cred, owner, s3Err
|
||||
return s3Err
|
||||
}
|
||||
|
||||
// LocationConstraint is valid only for CreateBucketAction.
|
||||
var locationConstraint string
|
||||
logger.GetReqInfo(ctx).Cred = cred
|
||||
logger.GetReqInfo(ctx).Owner = owner
|
||||
|
||||
// region is valid only for CreateBucketAction.
|
||||
var region string
|
||||
if action == policy.CreateBucketAction {
|
||||
// To extract region from XML in request body, get copy of request body.
|
||||
payload, err := ioutil.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
|
||||
payload, err := io.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
return cred, owner, ErrMalformedXML
|
||||
return ErrMalformedXML
|
||||
}
|
||||
|
||||
// Populate payload to extract location constraint.
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
|
||||
|
||||
var s3Error APIErrorCode
|
||||
locationConstraint, s3Error = parseLocationConstraint(r)
|
||||
if s3Error != ErrNone {
|
||||
return cred, owner, s3Error
|
||||
r.Body = io.NopCloser(bytes.NewReader(payload))
|
||||
region, s3Err = parseLocationConstraint(r)
|
||||
if s3Err != ErrNone {
|
||||
return s3Err
|
||||
}
|
||||
|
||||
// Populate payload again to handle it in HTTP handler.
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
|
||||
}
|
||||
if cred.AccessKey != "" {
|
||||
logger.GetReqInfo(ctx).AccessKey = cred.AccessKey
|
||||
r.Body = io.NopCloser(bytes.NewReader(payload))
|
||||
}
|
||||
|
||||
logger.GetReqInfo(ctx).Region = region
|
||||
|
||||
return s3Err
|
||||
}
|
||||
|
||||
func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||
reqInfo := logger.GetReqInfo(ctx)
|
||||
if reqInfo == nil {
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
||||
cred := reqInfo.Cred
|
||||
owner := reqInfo.Owner
|
||||
region := reqInfo.Region
|
||||
bucket := reqInfo.BucketName
|
||||
object := reqInfo.ObjectName
|
||||
|
||||
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
||||
// Anonymous checks are not meant for ListBuckets action
|
||||
// Anonymous checks are not meant for ListAllBuckets action
|
||||
if globalPolicySys.IsAllowed(policy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Action: action,
|
||||
BucketName: bucketName,
|
||||
ConditionValues: getConditionValues(r, locationConstraint, "", nil),
|
||||
BucketName: bucket,
|
||||
ConditionValues: getConditionValues(r, region, "", nil),
|
||||
IsOwner: false,
|
||||
ObjectName: objectName,
|
||||
ObjectName: object,
|
||||
}) {
|
||||
// Request is allowed return the appropriate access key.
|
||||
return cred, owner, ErrNone
|
||||
return ErrNone
|
||||
}
|
||||
|
||||
if action == policy.ListBucketVersionsAction {
|
||||
@@ -375,31 +394,31 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action
|
||||
if globalPolicySys.IsAllowed(policy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Action: policy.ListBucketAction,
|
||||
BucketName: bucketName,
|
||||
ConditionValues: getConditionValues(r, locationConstraint, "", nil),
|
||||
BucketName: bucket,
|
||||
ConditionValues: getConditionValues(r, region, "", nil),
|
||||
IsOwner: false,
|
||||
ObjectName: objectName,
|
||||
ObjectName: object,
|
||||
}) {
|
||||
// Request is allowed return the appropriate access key.
|
||||
return cred, owner, ErrNone
|
||||
return ErrNone
|
||||
}
|
||||
}
|
||||
|
||||
return cred, owner, ErrAccessDenied
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: iampolicy.Action(action),
|
||||
BucketName: bucketName,
|
||||
BucketName: bucket,
|
||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
||||
ObjectName: objectName,
|
||||
ObjectName: object,
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
}) {
|
||||
// Request is allowed return the appropriate access key.
|
||||
return cred, owner, ErrNone
|
||||
return ErrNone
|
||||
}
|
||||
|
||||
if action == policy.ListBucketVersionsAction {
|
||||
@@ -409,18 +428,41 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: iampolicy.ListBucketAction,
|
||||
BucketName: bucketName,
|
||||
BucketName: bucket,
|
||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
||||
ObjectName: objectName,
|
||||
ObjectName: object,
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
}) {
|
||||
// Request is allowed return the appropriate access key.
|
||||
return cred, owner, ErrNone
|
||||
return ErrNone
|
||||
}
|
||||
}
|
||||
|
||||
return cred, owner, ErrAccessDenied
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
||||
// Check request auth type verifies the incoming http request
|
||||
// - validates the request signature
|
||||
// - validates the policy action if anonymous tests bucket policies if any,
|
||||
// for authenticated requests validates IAM policies.
|
||||
//
|
||||
// returns APIErrorCode if any to be replied to the client.
|
||||
// Additionally returns the accessKey used in the request, and if this request is by an admin.
|
||||
func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action policy.Action) (cred auth.Credentials, owner bool, s3Err APIErrorCode) {
|
||||
s3Err = authenticateRequest(ctx, r, action)
|
||||
reqInfo := logger.GetReqInfo(ctx)
|
||||
if reqInfo == nil {
|
||||
return cred, owner, ErrAccessDenied
|
||||
}
|
||||
|
||||
cred = reqInfo.Cred
|
||||
owner = reqInfo.Owner
|
||||
if s3Err != ErrNone {
|
||||
return cred, owner, s3Err
|
||||
}
|
||||
|
||||
return cred, owner, authorizeRequest(ctx, r, action)
|
||||
}
|
||||
|
||||
// Verify if request has valid AWS Signature Version '2'.
|
||||
@@ -624,22 +666,22 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
||||
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action iampolicy.Action) (s3Err APIErrorCode) {
|
||||
var cred auth.Credentials
|
||||
var owner bool
|
||||
region := globalSite.Region
|
||||
switch atype {
|
||||
case authTypeUnknown:
|
||||
return ErrSignatureVersionNotSupported
|
||||
case authTypeSignedV2, authTypePresignedV2:
|
||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||
case authTypeStreamingSigned, authTypePresigned, authTypeSigned:
|
||||
region := globalSite.Region
|
||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||
}
|
||||
if s3Err != ErrNone {
|
||||
return s3Err
|
||||
}
|
||||
|
||||
if cred.AccessKey != "" {
|
||||
logger.GetReqInfo(ctx).AccessKey = cred.AccessKey
|
||||
}
|
||||
logger.GetReqInfo(ctx).Cred = cred
|
||||
logger.GetReqInfo(ctx).Owner = owner
|
||||
logger.GetReqInfo(ctx).Region = region
|
||||
|
||||
// Do not check for PutObjectRetentionAction permission,
|
||||
// if mode and retain until date are not set.
|
||||
|
||||
+13
-13
@@ -21,7 +21,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -44,7 +43,7 @@ func TestGetRequestAuthType(t *testing.T) {
|
||||
req *http.Request
|
||||
authT authType
|
||||
}
|
||||
nopCloser := ioutil.NopCloser(io.LimitReader(&nullReader{}, 1024))
|
||||
nopCloser := io.NopCloser(io.LimitReader(&nullReader{}, 1024))
|
||||
testCases := []testCase{
|
||||
// Test case - 1
|
||||
// Check for generic signature v4 header.
|
||||
@@ -360,7 +359,10 @@ func mustNewSignedBadMD5Request(method string, urlStr string, contentLength int6
|
||||
|
||||
// Tests is requested authenticated function, tests replies for s3 errors.
|
||||
func TestIsReqAuthenticated(t *testing.T) {
|
||||
objLayer, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(GlobalContext)
|
||||
defer cancel()
|
||||
|
||||
objLayer, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -369,10 +371,7 @@ func TestIsReqAuthenticated(t *testing.T) {
|
||||
t.Fatalf("unable initialize config file, %s", err)
|
||||
}
|
||||
|
||||
initAllSubsystems()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
initAllSubsystems(ctx)
|
||||
|
||||
initConfigSubsystem(ctx, objLayer)
|
||||
|
||||
@@ -406,7 +405,7 @@ func TestIsReqAuthenticated(t *testing.T) {
|
||||
for i, testCase := range testCases {
|
||||
s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region, serviceS3)
|
||||
if s3Error != testCase.s3Error {
|
||||
if _, err := ioutil.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
|
||||
if _, err := io.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
|
||||
t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)
|
||||
}
|
||||
}
|
||||
@@ -414,7 +413,10 @@ func TestIsReqAuthenticated(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckAdminRequestAuthType(t *testing.T) {
|
||||
objLayer, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
objLayer, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -440,7 +442,6 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
|
||||
{Request: mustNewPresignedV2Request(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},
|
||||
}
|
||||
ctx := context.Background()
|
||||
for i, testCase := range testCases {
|
||||
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
|
||||
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
||||
@@ -452,7 +453,7 @@ func TestValidateAdminSignature(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
objLayer, fsDir, err := prepareFS()
|
||||
objLayer, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -462,8 +463,7 @@ func TestValidateAdminSignature(t *testing.T) {
|
||||
t.Fatalf("unable initialize config file, %s", err)
|
||||
}
|
||||
|
||||
initAllSubsystems()
|
||||
|
||||
initAllSubsystems(ctx)
|
||||
initConfigSubsystem(ctx, objLayer)
|
||||
|
||||
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Code generated by "stringer -type=authType -trimprefix=authType auth-handler.go"; DO NOT EDIT.
|
||||
|
||||
package cmd
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[authTypeUnknown-0]
|
||||
_ = x[authTypeAnonymous-1]
|
||||
_ = x[authTypePresigned-2]
|
||||
_ = x[authTypePresignedV2-3]
|
||||
_ = x[authTypePostPolicy-4]
|
||||
_ = x[authTypeStreamingSigned-5]
|
||||
_ = x[authTypeSigned-6]
|
||||
_ = x[authTypeSignedV2-7]
|
||||
_ = x[authTypeJWT-8]
|
||||
_ = x[authTypeSTS-9]
|
||||
}
|
||||
|
||||
const _authType_name = "UnknownAnonymousPresignedPresignedV2PostPolicyStreamingSignedSignedSignedV2JWTSTS"
|
||||
|
||||
var _authType_index = [...]uint8{0, 7, 16, 25, 36, 46, 61, 67, 75, 78, 81}
|
||||
|
||||
func (i authType) String() string {
|
||||
if i < 0 || i >= authType(len(_authType_index)-1) {
|
||||
return "authType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _authType_name[_authType_index[i]:_authType_index[i+1]]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
// Code generated by "stringer -type=batchReplicationMetric -trimprefix=batchReplicationMetric batch-handlers.go"; DO NOT EDIT.
|
||||
|
||||
package cmd
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[batchReplicationMetricObject-0]
|
||||
}
|
||||
|
||||
const _batchReplicationMetric_name = "Object"
|
||||
|
||||
var _batchReplicationMetric_index = [...]uint8{0, 6}
|
||||
|
||||
func (i batchReplicationMetric) String() string {
|
||||
if i >= batchReplicationMetric(len(_batchReplicationMetric_index)-1) {
|
||||
return "batchReplicationMetric(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _batchReplicationMetric_name[_batchReplicationMetric_index[i]:_batchReplicationMetric_index[i+1]]
|
||||
}
|
||||
+17
-4
@@ -24,6 +24,7 @@ import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
@@ -146,16 +147,28 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
||||
// Can never happen unless there are programmer bugs
|
||||
return 0, errUnexpected
|
||||
}
|
||||
ignoredErrs := []error{
|
||||
errDiskNotFound,
|
||||
}
|
||||
if strings.HasPrefix(b.volume, minioMetaBucket) {
|
||||
ignoredErrs = append(ignoredErrs,
|
||||
errFileNotFound,
|
||||
errVolumeNotFound,
|
||||
errFileVersionNotFound,
|
||||
)
|
||||
}
|
||||
if b.rc == nil {
|
||||
// For the first ReadAt() call we need to open the stream for reading.
|
||||
b.currOffset = offset
|
||||
streamOffset := (offset/b.shardSize)*int64(b.h.Size()) + offset
|
||||
if len(b.data) == 0 && b.tillOffset != streamOffset {
|
||||
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
||||
if err != nil && err != errDiskNotFound {
|
||||
logger.LogIf(GlobalContext,
|
||||
fmt.Errorf("Reading erasure shards at (%s: %s/%s) returned '%w', will attempt to reconstruct if we have quorum",
|
||||
b.disk, b.volume, b.filePath, err))
|
||||
if err != nil {
|
||||
if !IsErr(err, ignoredErrs...) {
|
||||
logger.LogIf(GlobalContext,
|
||||
fmt.Errorf("Reading erasure shards at (%s: %s/%s) returned '%w', will attempt to reconstruct if we have quorum",
|
||||
b.disk, b.volume, b.filePath, err))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
|
||||
|
||||
@@ -204,7 +204,7 @@ func (api objectAPIHandlers) DeleteBucketEncryptionHandler(w http.ResponseWriter
|
||||
}
|
||||
|
||||
// Delete bucket encryption config from object layer
|
||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, nil)
|
||||
updatedAt, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketSSEConfig)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
|
||||
@@ -300,7 +300,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
||||
|
||||
listBuckets := objectAPI.ListBuckets
|
||||
|
||||
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.ListAllMyBucketsAction, "", "")
|
||||
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.ListAllMyBucketsAction)
|
||||
if s3Error != ErrNone && s3Error != ErrAccessDenied {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
@@ -731,7 +731,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
}
|
||||
|
||||
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.CreateBucketAction, bucket, "")
|
||||
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.CreateBucketAction)
|
||||
if s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
@@ -1618,7 +1618,7 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, nil)
|
||||
updatedAt, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketTaggingConfig)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
@@ -885,7 +885,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
}
|
||||
|
||||
// read the response body.
|
||||
actualContent, err = ioutil.ReadAll(rec.Body)
|
||||
actualContent, err = io.ReadAll(rec.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
bucketLifecycle, err := lifecycle.ParseLifecycleConfig(io.LimitReader(r.Body, r.ContentLength))
|
||||
bucketLifecycle, err := lifecycle.ParseLifecycleConfigWithID(io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
@@ -168,7 +168,7 @@ func (api objectAPIHandlers) DeleteBucketLifecycleHandler(w http.ResponseWriter,
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketLifecycleConfig, nil); err != nil {
|
||||
if _, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketLifecycleConfig); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -81,9 +81,7 @@ func (sys *BucketMetadataSys) Set(bucket string, meta BucketMetadata) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update update bucket metadata for the specified config file.
|
||||
// The configData data should not be modified after being sent here.
|
||||
func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configFile string, configData []byte) (updatedAt time.Time, err error) {
|
||||
func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string, configFile string, configData []byte, parse bool) (updatedAt time.Time, err error) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
return updatedAt, errServerNotInitialized
|
||||
@@ -107,7 +105,7 @@ func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configF
|
||||
return updatedAt, errInvalidArgument
|
||||
}
|
||||
|
||||
meta, err := loadBucketMetadata(ctx, objAPI, bucket)
|
||||
meta, err := loadBucketMetadataParse(ctx, objAPI, bucket, parse)
|
||||
if err != nil {
|
||||
if !globalIsErasure && !globalIsDistErasure && errors.Is(err, errVolumeNotFound) {
|
||||
// Only single drive mode needs this fallback.
|
||||
@@ -165,6 +163,18 @@ func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configF
|
||||
return updatedAt, nil
|
||||
}
|
||||
|
||||
// Delete delete the bucket metadata for the specified bucket.
|
||||
// must be used by all callers instead of using Update() with nil configData.
|
||||
func (sys *BucketMetadataSys) Delete(ctx context.Context, bucket string, configFile string) (updatedAt time.Time, err error) {
|
||||
return sys.updateAndParse(ctx, bucket, configFile, nil, false)
|
||||
}
|
||||
|
||||
// Update update bucket metadata for the specified bucket.
|
||||
// The configData data should not be modified after being sent here.
|
||||
func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configFile string, configData []byte) (updatedAt time.Time, err error) {
|
||||
return sys.updateAndParse(ctx, bucket, configFile, configData, true)
|
||||
}
|
||||
|
||||
// Get metadata for a bucket.
|
||||
// If no metadata exists errConfigNotFound is returned and a new metadata is returned.
|
||||
// Only a shallow copy is returned, so referenced data should not be modified,
|
||||
|
||||
+28
-11
@@ -162,8 +162,7 @@ func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string)
|
||||
return err
|
||||
}
|
||||
|
||||
// loadBucketMetadata loads and migrates to bucket metadata.
|
||||
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
|
||||
func loadBucketMetadataParse(ctx context.Context, objectAPI ObjectLayer, bucket string, parse bool) (BucketMetadata, error) {
|
||||
b := newBucketMetadata(bucket)
|
||||
err := b.Load(ctx, objectAPI, b.Name)
|
||||
if err != nil && !errors.Is(err, errConfigNotFound) {
|
||||
@@ -172,8 +171,22 @@ func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket strin
|
||||
if err == nil {
|
||||
b.defaultTimestamps()
|
||||
}
|
||||
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
||||
if err := b.convertLegacyConfigs(ctx, objectAPI); err != nil {
|
||||
|
||||
configs, err := b.getAllLegacyConfigs(ctx, objectAPI)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
|
||||
if len(configs) == 0 {
|
||||
if parse {
|
||||
// nothing to update, parse and proceed.
|
||||
err = b.parseAllConfigs(ctx, objectAPI)
|
||||
}
|
||||
} else {
|
||||
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
||||
err = b.convertLegacyConfigs(ctx, objectAPI, configs)
|
||||
}
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
|
||||
@@ -185,6 +198,11 @@ func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket strin
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// loadBucketMetadata loads and migrates to bucket metadata.
|
||||
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
|
||||
return loadBucketMetadataParse(ctx, objectAPI, bucket, true)
|
||||
}
|
||||
|
||||
// parseAllConfigs will parse all configs and populate the private fields.
|
||||
// The first error encountered is returned.
|
||||
func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLayer) (err error) {
|
||||
@@ -277,7 +295,7 @@ func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI ObjectLayer) error {
|
||||
func (b *BucketMetadata) getAllLegacyConfigs(ctx context.Context, objectAPI ObjectLayer) (map[string][]byte, error) {
|
||||
legacyConfigs := []string{
|
||||
legacyBucketObjectLockEnabledConfigFile,
|
||||
bucketPolicyConfig,
|
||||
@@ -291,7 +309,7 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
||||
objectLockConfig,
|
||||
}
|
||||
|
||||
configs := make(map[string][]byte)
|
||||
configs := make(map[string][]byte, len(legacyConfigs))
|
||||
|
||||
// Handle migration from lockEnabled to newer format.
|
||||
if b.LockEnabled {
|
||||
@@ -316,16 +334,15 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
configs[legacyFile] = configData
|
||||
}
|
||||
|
||||
if len(configs) == 0 {
|
||||
// nothing to update, return right away.
|
||||
return b.parseAllConfigs(ctx, objectAPI)
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI ObjectLayer, configs map[string][]byte) error {
|
||||
for legacyFile, configData := range configs {
|
||||
switch legacyFile {
|
||||
case legacyBucketObjectLockEnabledConfigFile:
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
@@ -79,7 +78,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
bucketPolicyBytes, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength))
|
||||
bucketPolicyBytes, err := io.ReadAll(io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
@@ -150,7 +149,7 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, nil)
|
||||
updatedAt, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketPolicyConfig)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
@@ -487,7 +486,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV4.Code)
|
||||
}
|
||||
// read the response body.
|
||||
bucketPolicyReadBuf, err := ioutil.ReadAll(recV4.Body)
|
||||
bucketPolicyReadBuf, err := io.ReadAll(recV4.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
@@ -525,7 +524,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV2.Code)
|
||||
}
|
||||
// read the response body.
|
||||
bucketPolicyReadBuf, err = ioutil.ReadAll(recV2.Body)
|
||||
bucketPolicyReadBuf, err = io.ReadAll(recV2.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/handlers"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@@ -125,6 +126,20 @@ func getConditionValues(r *http.Request, lc string, username string, claims map[
|
||||
|
||||
cloneHeader := r.Header.Clone()
|
||||
|
||||
if userTags := cloneHeader.Get(xhttp.AmzObjectTagging); userTags != "" {
|
||||
tag, _ := tags.ParseObjectTags(userTags)
|
||||
if tag != nil {
|
||||
tagMap := tag.ToMap()
|
||||
keys := make([]string, 0, len(tagMap))
|
||||
for k, v := range tagMap {
|
||||
args[pathJoin("ExistingObjectTag", k)] = []string{v}
|
||||
args[pathJoin("RequestObjectTag", k)] = []string{v}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
args["RequestObjectTagKeys"] = keys
|
||||
}
|
||||
}
|
||||
|
||||
for _, objLock := range []string{
|
||||
xhttp.AmzObjectLockMode,
|
||||
xhttp.AmzObjectLockLegalHold,
|
||||
@@ -137,6 +152,9 @@ func getConditionValues(r *http.Request, lc string, username string, claims map[
|
||||
}
|
||||
|
||||
for key, values := range cloneHeader {
|
||||
if strings.EqualFold(key, xhttp.AmzObjectTagging) {
|
||||
continue
|
||||
}
|
||||
if existingValues, found := args[key]; found {
|
||||
args[key] = append(existingValues, values...)
|
||||
} else {
|
||||
|
||||
@@ -165,7 +165,7 @@ func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.Respons
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationDenyEditError), r.URL)
|
||||
return
|
||||
}
|
||||
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketReplicationConfig, nil); err != nil {
|
||||
if _, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketReplicationConfig); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -246,6 +246,9 @@ outer:
|
||||
}
|
||||
|
||||
func (r *ReplicationStats) getAllCachedLatest() BucketStatsMap {
|
||||
if r == nil {
|
||||
return BucketStatsMap{}
|
||||
}
|
||||
r.dlock.RLock()
|
||||
defer r.dlock.RUnlock()
|
||||
return r.mostRecentStats
|
||||
|
||||
+190
-39
@@ -23,11 +23,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
@@ -37,6 +39,7 @@ import (
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/bucket/bandwidth"
|
||||
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||
"github.com/minio/minio/internal/bucket/replication"
|
||||
"github.com/minio/minio/internal/config/storageclass"
|
||||
"github.com/minio/minio/internal/crypto"
|
||||
@@ -115,15 +118,22 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re
|
||||
return sameTarget, toAPIError(ctx, BucketRemoteTargetNotFound{Bucket: bucket})
|
||||
}
|
||||
if checkRemote { // validate remote bucket
|
||||
if found, err := clnt.BucketExists(ctx, arn.Bucket); !found {
|
||||
found, err := clnt.BucketExists(ctx, arn.Bucket)
|
||||
if err != nil {
|
||||
return sameTarget, errorCodes.ToAPIErrWithErr(ErrRemoteDestinationNotFoundError, err)
|
||||
}
|
||||
if !found {
|
||||
return sameTarget, errorCodes.ToAPIErrWithErr(ErrRemoteDestinationNotFoundError, BucketRemoteTargetNotFound{Bucket: arn.Bucket})
|
||||
}
|
||||
if ret, err := globalBucketObjectLockSys.Get(bucket); err == nil {
|
||||
if ret.LockEnabled {
|
||||
lock, _, _, _, err := clnt.GetObjectLockConfig(ctx, arn.Bucket)
|
||||
if err != nil || lock != "Enabled" {
|
||||
if err != nil {
|
||||
return sameTarget, errorCodes.ToAPIErrWithErr(ErrReplicationDestinationMissingLock, err)
|
||||
}
|
||||
if lock != objectlock.Enabled {
|
||||
return sameTarget, errorCodes.ToAPIErrWithErr(ErrReplicationDestinationMissingLock, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -566,18 +576,26 @@ func replicateDeleteToTarget(ctx context.Context, dobj DeletedObjectReplicationI
|
||||
return
|
||||
}
|
||||
// early return if already replicated delete marker for existing object replication/ healing delete markers
|
||||
if dobj.DeleteMarkerVersionID != "" && (dobj.OpType == replication.ExistingObjectReplicationType || dobj.OpType == replication.HealReplicationType) {
|
||||
if _, err := tgt.StatObject(ctx, tgt.Bucket, dobj.ObjectName, miniogo.StatObjectOptions{
|
||||
if dobj.DeleteMarkerVersionID != "" {
|
||||
toi, err := tgt.StatObject(ctx, tgt.Bucket, dobj.ObjectName, miniogo.StatObjectOptions{
|
||||
VersionID: versionID,
|
||||
Internal: miniogo.AdvancedGetOptions{
|
||||
ReplicationProxyRequest: "false",
|
||||
ReplicationDeleteMarker: true,
|
||||
},
|
||||
}); isErrMethodNotAllowed(ErrorRespToObjectError(err, dobj.Bucket, dobj.ObjectName)) {
|
||||
})
|
||||
if isErrMethodNotAllowed(ErrorRespToObjectError(err, dobj.Bucket, dobj.ObjectName)) {
|
||||
if dobj.VersionID == "" {
|
||||
rinfo.ReplicationStatus = replication.Completed
|
||||
return
|
||||
}
|
||||
}
|
||||
// mark delete marker replication as failed if target cluster not ready to receive
|
||||
// this request yet (object version not replicated yet)
|
||||
if err != nil && !toi.ReplicationReady {
|
||||
rinfo.ReplicationStatus = replication.Failed
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rmErr := tgt.RemoveObject(ctx, tgt.Bucket, dobj.ObjectName, miniogo.RemoveObjectOptions{
|
||||
@@ -730,9 +748,12 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (put
|
||||
putOpts.Mode = rmode
|
||||
}
|
||||
if retainDateStr, ok := lkMap.Lookup(xhttp.AmzObjectLockRetainUntilDate); ok {
|
||||
rdate, err := time.Parse(time.RFC3339, retainDateStr)
|
||||
rdate, err := time.Parse(iso8601TimeFormat, retainDateStr)
|
||||
if err != nil {
|
||||
return putOpts, err
|
||||
rdate, err = time.Parse(time.RFC3339, retainDateStr)
|
||||
if err != nil {
|
||||
return putOpts, err
|
||||
}
|
||||
}
|
||||
putOpts.RetainUntilDate = rdate
|
||||
// set retention timestamp in opts
|
||||
@@ -1063,7 +1084,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
||||
VersionSuspended: versionSuspended,
|
||||
})
|
||||
if err != nil {
|
||||
if !isErrObjectNotFound(err) {
|
||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectReplicationNotTracked,
|
||||
BucketName: bucket,
|
||||
@@ -1211,7 +1232,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
VersionSuspended: versionSuspended,
|
||||
})
|
||||
if err != nil {
|
||||
if !isErrObjectNotFound(err) {
|
||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectReplicationNotTracked,
|
||||
BucketName: bucket,
|
||||
@@ -1508,23 +1529,62 @@ type ReplicationPool struct {
|
||||
mrfSaveCh chan MRFReplicateEntry
|
||||
saveStateCh chan struct{}
|
||||
|
||||
workerSize int
|
||||
mrfWorkerSize int
|
||||
resyncState replicationResyncState
|
||||
workerWg sync.WaitGroup
|
||||
mrfWorkerWg sync.WaitGroup
|
||||
once sync.Once
|
||||
mu sync.Mutex
|
||||
workerSize int
|
||||
mrfWorkerSize int
|
||||
activeWorkers int32
|
||||
activeMRFWorkers int32
|
||||
priority string
|
||||
resyncState replicationResyncState
|
||||
workerWg sync.WaitGroup
|
||||
mrfWorkerWg sync.WaitGroup
|
||||
once sync.Once
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
const (
|
||||
// WorkerMaxLimit max number of workers per node for "fast" mode
|
||||
WorkerMaxLimit = 500
|
||||
|
||||
// WorkerMinLimit min number of workers per node for "slow" mode
|
||||
WorkerMinLimit = 50
|
||||
|
||||
// WorkerAutoDefault is default number of workers for "auto" mode
|
||||
WorkerAutoDefault = 100
|
||||
|
||||
// MRFWorkerMaxLimit max number of mrf workers per node for "fast" mode
|
||||
MRFWorkerMaxLimit = 8
|
||||
|
||||
// MRFWorkerMinLimit min number of mrf workers per node for "slow" mode
|
||||
MRFWorkerMinLimit = 2
|
||||
|
||||
// MRFWorkerAutoDefault is default number of mrf workers for "auto" mode
|
||||
MRFWorkerAutoDefault = 4
|
||||
)
|
||||
|
||||
// NewReplicationPool creates a pool of replication workers of specified size
|
||||
func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPoolOpts) *ReplicationPool {
|
||||
var workers, failedWorkers int
|
||||
priority := "auto"
|
||||
if opts.Priority != "" {
|
||||
priority = opts.Priority
|
||||
}
|
||||
switch priority {
|
||||
case "fast":
|
||||
workers = WorkerMaxLimit
|
||||
failedWorkers = MRFWorkerMaxLimit
|
||||
case "slow":
|
||||
workers = WorkerMinLimit
|
||||
failedWorkers = MRFWorkerMinLimit
|
||||
default:
|
||||
workers = WorkerAutoDefault
|
||||
failedWorkers = MRFWorkerAutoDefault
|
||||
}
|
||||
pool := &ReplicationPool{
|
||||
replicaCh: make(chan ReplicateObjectInfo, 100000),
|
||||
replicaDeleteCh: make(chan DeletedObjectReplicationInfo, 100000),
|
||||
mrfReplicaCh: make(chan ReplicateObjectInfo, 100000),
|
||||
workerKillCh: make(chan struct{}, opts.Workers),
|
||||
mrfWorkerKillCh: make(chan struct{}, opts.FailedWorkers),
|
||||
workerKillCh: make(chan struct{}, workers),
|
||||
mrfWorkerKillCh: make(chan struct{}, failedWorkers),
|
||||
existingReplicaCh: make(chan ReplicateObjectInfo, 100000),
|
||||
existingReplicaDeleteCh: make(chan DeletedObjectReplicationInfo, 100000),
|
||||
resyncState: replicationResyncState{statusMap: make(map[string]BucketReplicationResyncStatus)},
|
||||
@@ -1532,10 +1592,11 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
|
||||
saveStateCh: make(chan struct{}, 1),
|
||||
ctx: ctx,
|
||||
objLayer: o,
|
||||
priority: priority,
|
||||
}
|
||||
|
||||
pool.ResizeWorkers(opts.Workers)
|
||||
pool.ResizeFailedWorkers(opts.FailedWorkers)
|
||||
pool.ResizeWorkers(workers)
|
||||
pool.ResizeFailedWorkers(failedWorkers)
|
||||
go pool.AddExistingObjectReplicateWorker()
|
||||
go pool.updateResyncStatus(ctx, o)
|
||||
go pool.processMRF()
|
||||
@@ -1556,7 +1617,9 @@ func (p *ReplicationPool) AddMRFWorker() {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
atomic.AddInt32(&p.activeMRFWorkers, 1)
|
||||
replicateObject(p.ctx, oi, p.objLayer)
|
||||
atomic.AddInt32(&p.activeMRFWorkers, -1)
|
||||
case <-p.mrfWorkerKillCh:
|
||||
return
|
||||
}
|
||||
@@ -1574,12 +1637,17 @@ func (p *ReplicationPool) AddWorker() {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
atomic.AddInt32(&p.activeWorkers, 1)
|
||||
replicateObject(p.ctx, oi, p.objLayer)
|
||||
atomic.AddInt32(&p.activeWorkers, -1)
|
||||
|
||||
case doi, ok := <-p.replicaDeleteCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
atomic.AddInt32(&p.activeWorkers, 1)
|
||||
replicateDelete(p.ctx, doi, p.objLayer)
|
||||
atomic.AddInt32(&p.activeWorkers, -1)
|
||||
case <-p.workerKillCh:
|
||||
return
|
||||
}
|
||||
@@ -1606,6 +1674,16 @@ func (p *ReplicationPool) AddExistingObjectReplicateWorker() {
|
||||
}
|
||||
}
|
||||
|
||||
// ActiveWorkers returns the number of active workers handling replication traffic.
|
||||
func (p *ReplicationPool) ActiveWorkers() int {
|
||||
return int(atomic.LoadInt32(&p.activeWorkers))
|
||||
}
|
||||
|
||||
// ActiveMRFWorkers returns the number of active workers handling replication failures.
|
||||
func (p *ReplicationPool) ActiveMRFWorkers() int {
|
||||
return int(atomic.LoadInt32(&p.activeMRFWorkers))
|
||||
}
|
||||
|
||||
// ResizeWorkers sets replication workers pool to new size
|
||||
func (p *ReplicationPool) ResizeWorkers(n int) {
|
||||
p.mu.Lock()
|
||||
@@ -1622,6 +1700,33 @@ func (p *ReplicationPool) ResizeWorkers(n int) {
|
||||
}
|
||||
}
|
||||
|
||||
// ResizeWorkerPriority sets replication failed workers pool size
|
||||
func (p *ReplicationPool) ResizeWorkerPriority(pri string) {
|
||||
var workers, mrfWorkers int
|
||||
p.mu.Lock()
|
||||
switch pri {
|
||||
case "fast":
|
||||
workers = WorkerMaxLimit
|
||||
mrfWorkers = MRFWorkerMaxLimit
|
||||
case "slow":
|
||||
workers = WorkerMinLimit
|
||||
mrfWorkers = MRFWorkerMinLimit
|
||||
default:
|
||||
workers = WorkerAutoDefault
|
||||
mrfWorkers = MRFWorkerAutoDefault
|
||||
if p.workerSize < WorkerAutoDefault {
|
||||
workers = int(math.Min(float64(p.workerSize+1), WorkerAutoDefault))
|
||||
}
|
||||
if p.mrfWorkerSize < MRFWorkerAutoDefault {
|
||||
mrfWorkers = int(math.Min(float64(p.mrfWorkerSize+1), MRFWorkerAutoDefault))
|
||||
}
|
||||
}
|
||||
p.priority = pri
|
||||
p.mu.Unlock()
|
||||
p.ResizeWorkers(workers)
|
||||
p.ResizeFailedWorkers(mrfWorkers)
|
||||
}
|
||||
|
||||
// ResizeFailedWorkers sets replication failed workers pool size
|
||||
func (p *ReplicationPool) ResizeFailedWorkers(n int) {
|
||||
p.mu.Lock()
|
||||
@@ -1638,14 +1743,6 @@ func (p *ReplicationPool) ResizeFailedWorkers(n int) {
|
||||
}
|
||||
}
|
||||
|
||||
// suggestedWorkers recommends an increase in number of workers to meet replication load.
|
||||
func (p *ReplicationPool) suggestedWorkers(failQueue bool) int {
|
||||
if failQueue {
|
||||
return int(float64(p.mrfWorkerSize) * ReplicationWorkerMultiplier)
|
||||
}
|
||||
return int(float64(p.workerSize) * ReplicationWorkerMultiplier)
|
||||
}
|
||||
|
||||
func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) {
|
||||
if p == nil {
|
||||
return
|
||||
@@ -1672,7 +1769,23 @@ func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) {
|
||||
case ch <- ri:
|
||||
default:
|
||||
globalReplicationPool.queueMRFSave(ri.ToMRFEntry())
|
||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming traffic - we recommend increasing number of replicate object workers with `mc admin config set api replication_workers=%d`", p.suggestedWorkers(false)), string(replicationSubsystem))
|
||||
p.mu.RLock()
|
||||
switch p.priority {
|
||||
case "fast":
|
||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming traffic"), string(replicationSubsystem))
|
||||
case "slow":
|
||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming traffic - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem))
|
||||
default:
|
||||
if p.ActiveWorkers() < WorkerMaxLimit {
|
||||
workers := int(math.Min(float64(p.workerSize+1), WorkerMaxLimit))
|
||||
p.ResizeWorkers(workers)
|
||||
}
|
||||
if p.ActiveMRFWorkers() < MRFWorkerMaxLimit {
|
||||
workers := int(math.Min(float64(p.mrfWorkerSize+1), MRFWorkerMaxLimit))
|
||||
p.ResizeFailedWorkers(workers)
|
||||
}
|
||||
}
|
||||
p.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1710,19 +1823,29 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
|
||||
case ch <- doi:
|
||||
default:
|
||||
globalReplicationPool.queueMRFSave(doi.ToMRFEntry())
|
||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming deletes - we recommend increasing number of replicate workers with `mc admin config set api replication_workers=%d`", p.suggestedWorkers(false)), string(replicationSubsystem))
|
||||
p.mu.RLock()
|
||||
switch p.priority {
|
||||
case "fast":
|
||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming deletes"), string(replicationSubsystem))
|
||||
case "slow":
|
||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming deletes - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem))
|
||||
default:
|
||||
if p.ActiveWorkers() < WorkerMaxLimit {
|
||||
workers := int(math.Min(float64(p.workerSize+1), WorkerMaxLimit))
|
||||
p.ResizeWorkers(workers)
|
||||
}
|
||||
}
|
||||
p.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
type replicationPoolOpts struct {
|
||||
Workers int
|
||||
FailedWorkers int
|
||||
Priority string
|
||||
}
|
||||
|
||||
func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) {
|
||||
globalReplicationPool = NewReplicationPool(ctx, objectAPI, replicationPoolOpts{
|
||||
Workers: globalAPIConfig.getReplicationWorkers(),
|
||||
FailedWorkers: globalAPIConfig.getReplicationFailedWorkers(),
|
||||
Priority: globalAPIConfig.getReplicationPriority(),
|
||||
})
|
||||
globalReplicationStats = NewReplicationStats(ctx, objectAPI)
|
||||
go globalReplicationStats.loadInitialReplicationMetrics(ctx)
|
||||
@@ -1790,7 +1913,9 @@ func getProxyTargets(ctx context.Context, bucket, object string, opts ObjectOpti
|
||||
if opts.VersionSuspended {
|
||||
return &madmin.BucketTargets{}
|
||||
}
|
||||
|
||||
if !opts.ProxyRequest {
|
||||
return &madmin.BucketTargets{}
|
||||
}
|
||||
cfg, err := getReplicationConfig(ctx, bucket)
|
||||
if err != nil || cfg == nil {
|
||||
return &madmin.BucketTargets{}
|
||||
@@ -2547,6 +2672,10 @@ func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcf
|
||||
const mrfTimeInterval = 5 * time.Minute
|
||||
|
||||
func (p *ReplicationPool) persistMRF() {
|
||||
if !p.initialized() {
|
||||
return
|
||||
}
|
||||
|
||||
var mu sync.Mutex
|
||||
entries := make(map[string]MRFReplicateEntry)
|
||||
mTimer := time.NewTimer(mrfTimeInterval)
|
||||
@@ -2599,7 +2728,7 @@ func (p *ReplicationPool) persistMRF() {
|
||||
}
|
||||
|
||||
func (p *ReplicationPool) queueMRFSave(entry MRFReplicateEntry) {
|
||||
if p == nil {
|
||||
if !p.initialized() {
|
||||
return
|
||||
}
|
||||
select {
|
||||
@@ -2611,6 +2740,10 @@ func (p *ReplicationPool) queueMRFSave(entry MRFReplicateEntry) {
|
||||
|
||||
// save mrf entries to mrf_<uuid>.bin
|
||||
func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string]MRFReplicateEntry) error {
|
||||
if !p.initialized() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -2636,6 +2769,10 @@ func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string
|
||||
|
||||
// load mrf entries from disk
|
||||
func (p *ReplicationPool) loadMRF(fileName string) (re MRFReplicateEntries, e error) {
|
||||
if !p.initialized() {
|
||||
return re, nil
|
||||
}
|
||||
|
||||
data, err := readConfig(p.ctx, p.objLayer, fileName)
|
||||
if err != nil && err != errConfigNotFound {
|
||||
return re, err
|
||||
@@ -2672,7 +2809,7 @@ func (p *ReplicationPool) loadMRF(fileName string) (re MRFReplicateEntries, e er
|
||||
}
|
||||
|
||||
func (p *ReplicationPool) processMRF() {
|
||||
if p == nil || p.objLayer == nil {
|
||||
if !p.initialized() {
|
||||
return
|
||||
}
|
||||
pTimer := time.NewTimer(mrfTimeInterval)
|
||||
@@ -2715,7 +2852,7 @@ func (p *ReplicationPool) processMRF() {
|
||||
|
||||
// process sends error logs to the heal channel for an attempt to heal replication.
|
||||
func (p *ReplicationPool) queueMRFHeal(file string) error {
|
||||
if p == nil || p.objLayer == nil {
|
||||
if !p.initialized() {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
@@ -2737,6 +2874,10 @@ func (p *ReplicationPool) queueMRFHeal(file string) error {
|
||||
|
||||
// load replication stats from disk
|
||||
func (p *ReplicationPool) loadStatsFromDisk() (rs map[string]BucketReplicationStats, e error) {
|
||||
if !p.initialized() {
|
||||
return map[string]BucketReplicationStats{}, nil
|
||||
}
|
||||
|
||||
data, err := readConfig(p.ctx, p.objLayer, getReplicationStatsPath(globalLocalNodeName))
|
||||
if err != nil {
|
||||
if !errors.Is(err, errConfigNotFound) {
|
||||
@@ -2772,8 +2913,12 @@ func (p *ReplicationPool) loadStatsFromDisk() (rs map[string]BucketReplicationSt
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (p *ReplicationPool) initialized() bool {
|
||||
return !(p == nil || p.objLayer == nil)
|
||||
}
|
||||
|
||||
func (p *ReplicationPool) saveStatsToDisk() {
|
||||
if p == nil || p.objLayer == nil {
|
||||
if !p.initialized() {
|
||||
return
|
||||
}
|
||||
sTimer := time.NewTimer(replStatsSaveInterval)
|
||||
@@ -2795,6 +2940,9 @@ func (p *ReplicationPool) saveStatsToDisk() {
|
||||
|
||||
// save replication stats to .minio.sys/buckets/replication/node-name.stats
|
||||
func (p *ReplicationPool) saveStats(ctx context.Context) error {
|
||||
if !p.initialized() {
|
||||
return nil
|
||||
}
|
||||
bsm := globalReplicationStats.getAllCachedLatest()
|
||||
if len(bsm.Stats) == 0 {
|
||||
return nil
|
||||
@@ -2813,6 +2961,9 @@ func (p *ReplicationPool) saveStats(ctx context.Context) error {
|
||||
|
||||
// SaveState saves replication stats and mrf data before server restart
|
||||
func (p *ReplicationPool) SaveState(ctx context.Context) error {
|
||||
if !p.initialized() {
|
||||
return nil
|
||||
}
|
||||
go func() {
|
||||
select {
|
||||
case p.saveStateCh <- struct{}{}:
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio/internal/bucket/replication"
|
||||
"github.com/minio/minio/internal/bucket/versioning"
|
||||
"github.com/minio/minio/internal/crypto"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@@ -108,7 +107,6 @@ func (sys *BucketTargetSys) heartBeat(ctx context.Context) {
|
||||
|
||||
if len(eps) > 0 {
|
||||
cctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
m := map[string]epHealth{}
|
||||
for result := range sys.hcClient.Alive(cctx, madmin.AliveOpts{}, eps...) {
|
||||
var online bool
|
||||
@@ -121,6 +119,7 @@ func (sys *BucketTargetSys) heartBeat(ctx context.Context) {
|
||||
Online: online,
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
sys.hMutex.Lock()
|
||||
sys.hc = m
|
||||
sys.hMutex.Unlock()
|
||||
@@ -220,12 +219,16 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||
}
|
||||
// validate if target credentials are ok
|
||||
if _, err = clnt.BucketExists(ctx, tgt.TargetBucket); err != nil {
|
||||
exists, err := clnt.BucketExists(ctx, tgt.TargetBucket)
|
||||
if err != nil {
|
||||
if minio.ToErrorResponse(err).Code == "NoSuchBucket" {
|
||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||
}
|
||||
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err}
|
||||
}
|
||||
if !exists {
|
||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||
}
|
||||
if tgt.Type == madmin.ReplicationService {
|
||||
if !globalBucketVersioningSys.Enabled(bucket) {
|
||||
return BucketReplicationSourceNotVersioned{Bucket: bucket}
|
||||
@@ -234,7 +237,7 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
||||
if err != nil {
|
||||
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err}
|
||||
}
|
||||
if vcfg.Status != string(versioning.Enabled) {
|
||||
if !vcfg.Enabled() {
|
||||
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -27,7 +27,6 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -203,7 +202,7 @@ func minioConfigToConsoleFeatures() {
|
||||
}
|
||||
}
|
||||
// Enable if LDAP is enabled.
|
||||
if globalLDAPConfig.Enabled {
|
||||
if globalLDAPConfig.Enabled() {
|
||||
os.Setenv("CONSOLE_LDAP_ENABLED", config.EnableOn)
|
||||
}
|
||||
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
|
||||
@@ -237,6 +236,7 @@ func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg {
|
||||
Userinfo: cfg.ClaimUserinfo,
|
||||
RedirectCallbackDynamic: cfg.RedirectURIDynamic,
|
||||
RedirectCallback: callback,
|
||||
RoleArn: cfg.GetRoleArn(),
|
||||
}
|
||||
}
|
||||
return m
|
||||
@@ -564,7 +564,7 @@ func readFromSecret(sp string) (string, error) {
|
||||
if isFile(pathJoin("/run/secrets/", sp)) {
|
||||
sp = pathJoin("/run/secrets/", sp)
|
||||
}
|
||||
credBuf, err := ioutil.ReadFile(sp)
|
||||
credBuf, err := os.ReadFile(sp)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) { // ignore if file doesn't exist.
|
||||
return "", nil
|
||||
|
||||
@@ -19,7 +19,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
@@ -45,7 +45,7 @@ func Test_readFromSecret(t *testing.T) {
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run("", func(t *testing.T) {
|
||||
tmpfile, err := ioutil.TempFile("", "testfile")
|
||||
tmpfile, err := os.CreateTemp("", "testfile")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -157,7 +157,7 @@ MINIO_ROOT_PASSWORD=minio123`,
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run("", func(t *testing.T) {
|
||||
tmpfile, err := ioutil.TempFile("", "testfile")
|
||||
tmpfile, err := os.CreateTemp("", "testfile")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/minio/internal/hash"
|
||||
@@ -41,7 +41,7 @@ func readConfigWithMetadata(ctx context.Context, store objectIO, configFile stri
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
buf, err := ioutil.ReadAll(r)
|
||||
buf, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, ObjectInfo{}, err
|
||||
}
|
||||
|
||||
+43
-52
@@ -25,6 +25,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/minio/madmin-go"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/config/api"
|
||||
"github.com/minio/minio/internal/config/cache"
|
||||
@@ -68,7 +69,6 @@ func initHelp() {
|
||||
config.LoggerWebhookSubSys: logger.DefaultLoggerWebhookKVS,
|
||||
config.AuditWebhookSubSys: logger.DefaultAuditWebhookKVS,
|
||||
config.AuditKafkaSubSys: logger.DefaultAuditKafkaKVS,
|
||||
config.HealSubSys: heal.DefaultKVS,
|
||||
config.ScannerSubSys: scanner.DefaultKVS,
|
||||
config.SubnetSubSys: subnet.DefaultKVS,
|
||||
config.CallhomeSubSys: callhome.DefaultKVS,
|
||||
@@ -78,6 +78,7 @@ func initHelp() {
|
||||
}
|
||||
if globalIsErasure {
|
||||
kvs[config.StorageClassSubSys] = storageclass.DefaultKVS
|
||||
kvs[config.HealSubSys] = heal.DefaultKVS
|
||||
}
|
||||
config.RegisterDefaultKVS(kvs)
|
||||
|
||||
@@ -124,10 +125,6 @@ func initHelp() {
|
||||
Key: config.APISubSys,
|
||||
Description: "manage global HTTP API call specific features, such as throttling, authentication types, etc.",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: config.HealSubSys,
|
||||
Description: "manage object healing frequency and bitrot verification checks",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: config.ScannerSubSys,
|
||||
Description: "manage namespace scanning for usage calculation, lifecycle, healing and more",
|
||||
@@ -212,12 +209,13 @@ func initHelp() {
|
||||
}
|
||||
|
||||
if globalIsErasure {
|
||||
helpSubSys = append(helpSubSys, config.HelpKV{})
|
||||
copy(helpSubSys[2:], helpSubSys[1:])
|
||||
helpSubSys[1] = config.HelpKV{
|
||||
helpSubSys = append(helpSubSys, config.HelpKV{
|
||||
Key: config.StorageClassSubSys,
|
||||
Description: "define object level redundancy",
|
||||
}
|
||||
}, config.HelpKV{
|
||||
Key: config.HealSubSys,
|
||||
Description: "manage object healing frequency and bitrot verification checks",
|
||||
})
|
||||
}
|
||||
|
||||
helpMap := map[string]config.HelpKVS{
|
||||
@@ -279,10 +277,6 @@ var (
|
||||
|
||||
func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) error {
|
||||
switch subSys {
|
||||
case config.CredentialsSubSys:
|
||||
if _, err := config.LookupCreds(s[config.CredentialsSubSys][config.Default]); err != nil {
|
||||
return err
|
||||
}
|
||||
case config.SiteSubSys:
|
||||
if _, err := config.LookupSite(s[config.SiteSubSys][config.Default], s[config.RegionSubSys][config.Default]); err != nil {
|
||||
return err
|
||||
@@ -292,14 +286,12 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
||||
return err
|
||||
}
|
||||
case config.StorageClassSubSys:
|
||||
if globalIsErasure {
|
||||
if objAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
for _, setDriveCount := range objAPI.SetDriveCounts() {
|
||||
if _, err := storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default], setDriveCount); err != nil {
|
||||
return err
|
||||
}
|
||||
if objAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
for _, setDriveCount := range objAPI.SetDriveCounts() {
|
||||
if _, err := storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default], setDriveCount); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case config.CacheSubSys:
|
||||
@@ -343,12 +335,12 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
||||
return err
|
||||
}
|
||||
case config.IdentityLDAPSubSys:
|
||||
cfg, err := xldap.Lookup(s[config.IdentityLDAPSubSys][config.Default], globalRootCAs)
|
||||
cfg, err := xldap.Lookup(s, globalRootCAs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.Enabled {
|
||||
conn, cerr := cfg.Connect()
|
||||
if cfg.Enabled() {
|
||||
conn, cerr := cfg.LDAP.Connect()
|
||||
if cerr != nil {
|
||||
return cerr
|
||||
}
|
||||
@@ -396,7 +388,7 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
||||
}
|
||||
|
||||
if config.NotifySubSystems.Contains(subSys) {
|
||||
if err := notify.TestSubSysNotificationTargets(GlobalContext, s, NewGatewayHTTPTransport(), globalEventNotifier.ConfiguredTargetIDs(), subSys); err != nil {
|
||||
if err := notify.TestSubSysNotificationTargets(GlobalContext, s, subSys, NewGatewayHTTPTransport()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -555,12 +547,7 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
|
||||
|
||||
transport := NewGatewayHTTPTransport()
|
||||
|
||||
globalConfigTargetList, err = notify.GetNotificationTargets(GlobalContext, s, transport, false)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
|
||||
}
|
||||
|
||||
globalEnvTargetList, err = notify.GetNotificationTargets(GlobalContext, newServerConfig(), transport, true)
|
||||
globalConfigTargetList, err = notify.FetchEnabledTargets(GlobalContext, s, transport)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
|
||||
}
|
||||
@@ -576,16 +563,18 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
|
||||
}
|
||||
|
||||
func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s config.Config, subSys string) error {
|
||||
if objAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
setDriveCounts := objAPI.SetDriveCounts()
|
||||
switch subSys {
|
||||
case config.APISubSys:
|
||||
apiConfig, err := api.LookupConfig(s[config.APISubSys][config.Default])
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Invalid api configuration: %w", err))
|
||||
}
|
||||
var setDriveCounts []int
|
||||
if objAPI != nil {
|
||||
setDriveCounts = objAPI.SetDriveCounts()
|
||||
}
|
||||
|
||||
globalAPIConfig.init(apiConfig, setDriveCounts)
|
||||
|
||||
// Initialize remote instance transport once.
|
||||
@@ -598,10 +587,8 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
||||
return fmt.Errorf("Unable to setup Compression: %w", err)
|
||||
}
|
||||
// Validate if the object layer supports compression.
|
||||
if objAPI != nil {
|
||||
if cmpCfg.Enabled && !objAPI.IsCompressionSupported() {
|
||||
return fmt.Errorf("Backend does not support compression")
|
||||
}
|
||||
if cmpCfg.Enabled && !objAPI.IsCompressionSupported() {
|
||||
return fmt.Errorf("Backend does not support compression")
|
||||
}
|
||||
globalCompressConfigMu.Lock()
|
||||
globalCompressConfig = cmpCfg
|
||||
@@ -670,19 +657,16 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to update audit kafka targets: %w", err))
|
||||
}
|
||||
case config.StorageClassSubSys:
|
||||
if globalIsErasure && objAPI != nil {
|
||||
setDriveCounts := objAPI.SetDriveCounts()
|
||||
for i, setDriveCount := range setDriveCounts {
|
||||
sc, err := storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default], setDriveCount)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize storage class config: %w", err))
|
||||
break
|
||||
}
|
||||
// if we validated all setDriveCounts and it was successful
|
||||
// proceed to store the correct storage class globally.
|
||||
if i == len(setDriveCounts)-1 {
|
||||
globalStorageClass.Update(sc)
|
||||
}
|
||||
for i, setDriveCount := range setDriveCounts {
|
||||
sc, err := storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default], setDriveCount)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize storage class config: %w", err))
|
||||
break
|
||||
}
|
||||
// if we validated all setDriveCounts and it was successful
|
||||
// proceed to store the correct storage class globally.
|
||||
if i == len(setDriveCounts)-1 {
|
||||
globalStorageClass.Update(sc)
|
||||
}
|
||||
}
|
||||
case config.CallhomeSubSys:
|
||||
@@ -799,6 +783,13 @@ func newSrvConfig(objAPI ObjectLayer) error {
|
||||
// Initialize server config.
|
||||
srvCfg := newServerConfig()
|
||||
|
||||
if globalActiveCred.IsValid() && !globalActiveCred.Equal(auth.DefaultCredentials) {
|
||||
kvs := srvCfg[config.CredentialsSubSys][config.Default]
|
||||
kvs.Set(config.AccessKey, globalActiveCred.AccessKey)
|
||||
kvs.Set(config.SecretKey, globalActiveCred.SecretKey)
|
||||
srvCfg[config.CredentialsSubSys][config.Default] = kvs
|
||||
}
|
||||
|
||||
// hold the mutex lock before a new config is assigned.
|
||||
globalServerConfigMu.Lock()
|
||||
globalServerConfig = srvCfg
|
||||
|
||||
@@ -26,7 +26,10 @@ import (
|
||||
)
|
||||
|
||||
func TestServerConfig(t *testing.T) {
|
||||
objLayer, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
objLayer, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@@ -28,7 +28,10 @@ import (
|
||||
|
||||
// Test if config v1 is purged
|
||||
func TestServerConfigMigrateV1(t *testing.T) {
|
||||
objLayer, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
objLayer, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -47,7 +50,7 @@ func TestServerConfigMigrateV1(t *testing.T) {
|
||||
// Create a V1 config json file and store it
|
||||
configJSON := "{ \"version\":\"1\", \"accessKeyId\":\"abcde\", \"secretAccessKey\":\"abcdefgh\"}"
|
||||
configPath := rootPath + "/fsUsers.json"
|
||||
if err := ioutil.WriteFile(configPath, []byte(configJSON), 0o644); err != nil {
|
||||
if err := os.WriteFile(configPath, []byte(configJSON), 0o644); err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
|
||||
@@ -155,10 +158,12 @@ func TestServerConfigMigrateInexistentConfig(t *testing.T) {
|
||||
// Test if a config migration from v2 to v33 is successfully done
|
||||
func TestServerConfigMigrateV2toV33(t *testing.T) {
|
||||
rootPath := t.TempDir()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
globalConfigDir = &ConfigDir{path: rootPath}
|
||||
|
||||
objLayer, fsDir, err := prepareFS()
|
||||
objLayer, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -167,7 +172,7 @@ func TestServerConfigMigrateV2toV33(t *testing.T) {
|
||||
configPath := rootPath + SlashSeparator + minioConfigFile
|
||||
|
||||
// Create a corrupted config file
|
||||
if err := ioutil.WriteFile(configPath, []byte("{ \"version\":\"2\","), 0o644); err != nil {
|
||||
if err := os.WriteFile(configPath, []byte("{ \"version\":\"2\","), 0o644); err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
// Fire a migrateConfig()
|
||||
@@ -180,7 +185,7 @@ func TestServerConfigMigrateV2toV33(t *testing.T) {
|
||||
|
||||
// Create a V2 config json file and store it
|
||||
configJSON := "{ \"version\":\"2\", \"credentials\": {\"accessKeyId\":\"" + accessKey + "\", \"secretAccessKey\":\"" + secretKey + "\", \"region\":\"us-east-1\"}, \"mongoLogger\":{\"addr\":\"127.0.0.1:3543\", \"db\":\"foodb\", \"collection\":\"foo\"}, \"syslogLogger\":{\"network\":\"127.0.0.1:543\", \"addr\":\"addr\"}, \"fileLogger\":{\"filename\":\"log.out\"}}"
|
||||
if err := ioutil.WriteFile(configPath, []byte(configJSON), 0o644); err != nil {
|
||||
if err := os.WriteFile(configPath, []byte(configJSON), 0o644); err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
|
||||
@@ -226,7 +231,7 @@ func TestServerConfigMigrateFaultyConfig(t *testing.T) {
|
||||
configPath := rootPath + SlashSeparator + minioConfigFile
|
||||
|
||||
// Create a corrupted config file
|
||||
if err := ioutil.WriteFile(configPath, []byte("{ \"version\":\"2\", \"test\":"), 0o644); err != nil {
|
||||
if err := os.WriteFile(configPath, []byte("{ \"version\":\"2\", \"test\":"), 0o644); err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
|
||||
@@ -320,7 +325,7 @@ func TestServerConfigMigrateCorruptedConfig(t *testing.T) {
|
||||
|
||||
for i := 3; i <= 17; i++ {
|
||||
// Create a corrupted config file
|
||||
if err := ioutil.WriteFile(configPath, []byte(fmt.Sprintf("{ \"version\":\"%d\", \"credential\": { \"accessKey\": 1 } }", i)),
|
||||
if err := os.WriteFile(configPath, []byte(fmt.Sprintf("{ \"version\":\"%d\", \"credential\": { \"accessKey\": 1 } }", i)),
|
||||
0o644); err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
@@ -332,7 +337,7 @@ func TestServerConfigMigrateCorruptedConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create a corrupted config file for version '2'.
|
||||
if err := ioutil.WriteFile(configPath, []byte("{ \"version\":\"2\", \"credentials\": { \"accessKeyId\": 1 } }"), 0o644); err != nil {
|
||||
if err := os.WriteFile(configPath, []byte("{ \"version\":\"2\", \"credentials\": { \"accessKeyId\": 1 } }"), 0o644); err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -855,5 +855,5 @@ type serverConfigV33 struct {
|
||||
// Add new external policy enforcements here.
|
||||
} `json:"policy"`
|
||||
|
||||
LDAPServerConfig xldap.Config `json:"ldapserverconfig"`
|
||||
LDAPServerConfig xldap.LegacyConfig `json:"ldapserverconfig"`
|
||||
}
|
||||
|
||||
+8
-5
@@ -938,7 +938,10 @@ func (i *scannerItem) applyHealing(ctx context.Context, o ObjectLayer, oi Object
|
||||
ScanMode: scanMode,
|
||||
}
|
||||
res, err := o.HealObject(ctx, i.bucket, i.objectPath(), oi.VersionID, healOpts)
|
||||
if err != nil && !errors.Is(err, NotImplemented{}) {
|
||||
if err != nil {
|
||||
if errors.Is(err, NotImplemented{}) || isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
err = nil
|
||||
}
|
||||
logger.LogIf(ctx, err)
|
||||
return 0
|
||||
}
|
||||
@@ -960,7 +963,7 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
|
||||
|
||||
versionID := oi.VersionID
|
||||
rCfg, _ := globalBucketObjectLockSys.Get(i.bucket)
|
||||
action := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, oi, false)
|
||||
action := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, oi)
|
||||
if i.debug {
|
||||
if versionID != "" {
|
||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %q (version-id=%s), Initial scan: %v\n", i.objectPath(), versionID, action)
|
||||
@@ -1106,9 +1109,9 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi Object
|
||||
return size
|
||||
}
|
||||
|
||||
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, obj ObjectInfo, debug bool) (action lifecycle.Action) {
|
||||
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, obj ObjectInfo) (action lifecycle.Action) {
|
||||
action = lc.ComputeAction(obj.ToLifecycleOpts())
|
||||
if debug {
|
||||
if serverDebugLog {
|
||||
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", action)
|
||||
}
|
||||
|
||||
@@ -1123,7 +1126,7 @@ func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr loc
|
||||
return lifecycle.NoneAction
|
||||
}
|
||||
if lr.LockEnabled && enforceRetentionForDeletion(ctx, obj) {
|
||||
if debug {
|
||||
if serverDebugLog {
|
||||
if obj.VersionID != "" {
|
||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %s v(%s) is locked, not deleting\n", obj.Name, obj.VersionID)
|
||||
} else {
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
@@ -296,7 +295,7 @@ func (d *dataUpdateTracker) startSaver(ctx context.Context, interval time.Durati
|
||||
}
|
||||
for _, drive := range drives {
|
||||
cacheFormatPath := pathJoin(drive, dataUpdateTrackerFilename)
|
||||
err := ioutil.WriteFile(cacheFormatPath, buf.Bytes(), os.ModePerm)
|
||||
err := os.WriteFile(cacheFormatPath, buf.Bytes(), os.ModePerm)
|
||||
if err != nil {
|
||||
if osIsNotExist(err) {
|
||||
continue
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -502,7 +501,7 @@ func createUsageTestFiles(t *testing.T, base, bucket string, files []usageTestFi
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(base, bucket, f.name), make([]byte, f.size), os.ModePerm)
|
||||
err = os.WriteFile(filepath.Join(base, bucket, f.name), make([]byte, f.size), os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -520,7 +519,7 @@ func generateUsageTestFiles(t *testing.T, base, bucket string, nFolders, nFiles,
|
||||
}
|
||||
for j := 0; j < nFiles; j++ {
|
||||
name := filepath.Join(base, bucket, fmt.Sprint(i), fmt.Sprint(j)+".txt")
|
||||
err = ioutil.WriteFile(name, pl, os.ModePerm)
|
||||
err = os.WriteFile(name, pl, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
@@ -707,7 +706,7 @@ func (c *diskCache) updateMetadata(ctx context.Context, bucket, object, etag str
|
||||
|
||||
if globalCacheKMS != nil {
|
||||
// Calculating object encryption key
|
||||
key, err = decryptObjectInfo(key, bucket, object, m.Meta)
|
||||
key, err = decryptObjectMeta(key, bucket, object, m.Meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -851,7 +850,7 @@ func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Read
|
||||
// Caches the object to disk
|
||||
func (c *diskCache) put(ctx context.Context, bucket, object string, data io.Reader, size int64, rs *HTTPRangeSpec, opts ObjectOptions, incHitsOnly, writeback bool) (oi ObjectInfo, err error) {
|
||||
if !c.diskSpaceAvailable(size) {
|
||||
io.Copy(ioutil.Discard, data)
|
||||
io.Copy(io.Discard, data)
|
||||
return oi, errDiskFull
|
||||
}
|
||||
cachePath := getCacheSHADir(c.dir, bucket, object)
|
||||
@@ -1311,7 +1310,7 @@ func (c *diskCache) NewMultipartUpload(ctx context.Context, bucket, object, uID
|
||||
func (c *diskCache) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, data io.Reader, size int64, opts ObjectOptions) (partInfo PartInfo, err error) {
|
||||
oi := PartInfo{}
|
||||
if !c.diskSpaceAvailable(size) {
|
||||
io.Copy(ioutil.Discard, data)
|
||||
io.Copy(io.Discard, data)
|
||||
return oi, errDiskFull
|
||||
}
|
||||
cachePath := getMultipartCacheSHADir(c.dir, bucket, object)
|
||||
@@ -1398,7 +1397,7 @@ func (c *diskCache) SavePartMetadata(ctx context.Context, bucket, object, upload
|
||||
var objectEncryptionKey crypto.ObjectKey
|
||||
if globalCacheKMS != nil {
|
||||
// Calculating object encryption key
|
||||
key, err = decryptObjectInfo(key, bucket, object, m.Meta)
|
||||
key, err = decryptObjectMeta(key, bucket, object, m.Meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1428,7 +1427,7 @@ func newCachePartEncryptReader(ctx context.Context, bucket, object string, partI
|
||||
var objectEncryptionKey, partEncryptionKey crypto.ObjectKey
|
||||
|
||||
// Calculating object encryption key
|
||||
key, err = decryptObjectInfo(key, bucket, object, metadata)
|
||||
key, err = decryptObjectMeta(key, bucket, object, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/djherbis/atime"
|
||||
@@ -30,7 +29,7 @@ import (
|
||||
|
||||
// Return error if Atime is disabled on the O/S
|
||||
func checkAtimeSupport(dir string) (err error) {
|
||||
file, err := ioutil.TempFile(dir, "prefix")
|
||||
file, err := os.CreateTemp(dir, "prefix")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ package cmd
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -32,7 +31,7 @@ import (
|
||||
|
||||
// Return error if Atime is disabled on the O/S
|
||||
func checkAtimeSupport(dir string) (err error) {
|
||||
file, err := ioutil.TempFile(dir, "prefix")
|
||||
file, err := os.CreateTemp(dir, "prefix")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -45,7 +44,7 @@ func checkAtimeSupport(dir string) (err error) {
|
||||
// add a sleep to ensure atime change is detected
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
if _, err = io.Copy(ioutil.Discard, file); err != nil {
|
||||
if _, err = io.Copy(io.Discard, file); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -46,7 +45,7 @@ type DummyDataGen struct {
|
||||
// Given the function:
|
||||
//
|
||||
// f := func(r io.Reader) string {
|
||||
// b, _ := ioutil.ReadAll(r)
|
||||
// b, _ := io.ReadAll(r)
|
||||
// return string(b)
|
||||
// }
|
||||
//
|
||||
@@ -115,7 +114,7 @@ func (d *DummyDataGen) Seek(offset int64, whence int) (int64, error) {
|
||||
|
||||
func TestDummyDataGenerator(t *testing.T) {
|
||||
readAll := func(r io.Reader) string {
|
||||
b, _ := ioutil.ReadAll(r)
|
||||
b, _ := io.ReadAll(r)
|
||||
return string(b)
|
||||
}
|
||||
checkEq := func(a, b string) {
|
||||
|
||||
@@ -130,7 +130,6 @@ func TestDynamicTimeoutConcurrent(t *testing.T) {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 100; i++ {
|
||||
timeout.LogFailure()
|
||||
for j := 0; j < 100; j++ {
|
||||
timeout.LogSuccess(time.Duration(float64(time.Second) * rng.Float64()))
|
||||
}
|
||||
|
||||
@@ -473,7 +473,7 @@ func EncryptRequest(content io.Reader, r *http.Request, bucket, object string, m
|
||||
return newEncryptReader(r.Context(), content, kind, keyID, key, bucket, object, metadata, ctx)
|
||||
}
|
||||
|
||||
func decryptObjectInfo(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) {
|
||||
func decryptObjectMeta(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) {
|
||||
switch kind, _ := crypto.IsEncrypted(metadata); kind {
|
||||
case crypto.S3:
|
||||
var KMS kms.KMS = GlobalKMS
|
||||
@@ -544,7 +544,7 @@ func DecryptCopyRequestR(client io.Reader, h http.Header, bucket, object string,
|
||||
}
|
||||
|
||||
func newDecryptReader(client io.Reader, key []byte, bucket, object string, seqNumber uint32, metadata map[string]string) (io.Reader, error) {
|
||||
objectEncryptionKey, err := decryptObjectInfo(key, bucket, object, metadata)
|
||||
objectEncryptionKey, err := decryptObjectMeta(key, bucket, object, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -656,7 +656,7 @@ func (d *DecryptBlocksReader) buildDecrypter(partID int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
objectEncryptionKey, err := decryptObjectInfo(key, d.bucket, d.object, m)
|
||||
objectEncryptionKey, err := decryptObjectMeta(key, d.bucket, d.object, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -822,7 +822,7 @@ func getDecryptedETag(headers http.Header, objInfo ObjectInfo, copySource bool)
|
||||
return objInfo.ETag[len(objInfo.ETag)-32:]
|
||||
}
|
||||
|
||||
objectEncryptionKey, err := decryptObjectInfo(key[:], objInfo.Bucket, objInfo.Name, objInfo.UserDefined)
|
||||
objectEncryptionKey, err := decryptObjectMeta(key[:], objInfo.Bucket, objInfo.Name, objInfo.UserDefined)
|
||||
if err != nil {
|
||||
return objInfo.ETag
|
||||
}
|
||||
@@ -1085,7 +1085,7 @@ func (o *ObjectInfo) metadataDecrypter() objectMetaDecryptFn {
|
||||
return input, nil
|
||||
}
|
||||
|
||||
key, err := decryptObjectInfo(nil, o.Bucket, o.Name, o.UserDefined)
|
||||
key, err := decryptObjectMeta(nil, o.Bucket, o.Name, o.UserDefined)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func TestEncryptRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var decryptObjectInfoTests = []struct {
|
||||
var decryptObjectMetaTests = []struct {
|
||||
info ObjectInfo
|
||||
request *http.Request
|
||||
expErr error
|
||||
@@ -122,7 +122,7 @@ var decryptObjectInfoTests = []struct {
|
||||
}
|
||||
|
||||
func TestDecryptObjectInfo(t *testing.T) {
|
||||
for i, test := range decryptObjectInfoTests {
|
||||
for i, test := range decryptObjectMetaTests {
|
||||
if encrypted, err := DecryptObjectInfo(&test.info, test.request); err != test.expErr {
|
||||
t.Errorf("Test %d: Decryption returned wrong error code: got %d , want %d", i, err, test.expErr)
|
||||
} else if _, enc := crypto.IsEncrypted(test.info.UserDefined); encrypted && enc != encrypted {
|
||||
|
||||
@@ -179,53 +179,6 @@ func listOnlineDisks(disks []StorageAPI, partsMetadata []FileInfo, errs []error)
|
||||
return onlineDisks, modTime
|
||||
}
|
||||
|
||||
// Returns the latest updated FileInfo files and error in case of failure.
|
||||
func getLatestFileInfo(ctx context.Context, partsMetadata []FileInfo, defaultParityCount int, errs []error) (FileInfo, error) {
|
||||
// There should be atleast half correct entries, if not return failure
|
||||
expectedRQuorum := len(partsMetadata) / 2
|
||||
if defaultParityCount == 0 {
|
||||
// if parity count is '0', we expected all entries to be present.
|
||||
expectedRQuorum = len(partsMetadata)
|
||||
}
|
||||
|
||||
reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, expectedRQuorum)
|
||||
if reducedErr != nil {
|
||||
return FileInfo{}, reducedErr
|
||||
}
|
||||
|
||||
// List all the file commit ids from parts metadata.
|
||||
modTimes := listObjectModtimes(partsMetadata, errs)
|
||||
|
||||
// Count all latest updated FileInfo values
|
||||
var count int
|
||||
var latestFileInfo FileInfo
|
||||
|
||||
// Reduce list of UUIDs to a single common value - i.e. the last updated Time
|
||||
modTime := commonTime(modTimes)
|
||||
|
||||
if modTime.IsZero() || modTime.Equal(timeSentinel) {
|
||||
return FileInfo{}, errErasureReadQuorum
|
||||
}
|
||||
|
||||
// Interate through all the modTimes and count the FileInfo(s) with latest time.
|
||||
for index, t := range modTimes {
|
||||
if partsMetadata[index].IsValid() && t.Equal(modTime) {
|
||||
latestFileInfo = partsMetadata[index]
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
if !latestFileInfo.IsValid() {
|
||||
return FileInfo{}, errErasureReadQuorum
|
||||
}
|
||||
|
||||
if count < latestFileInfo.Erasure.DataBlocks {
|
||||
return FileInfo{}, errErasureReadQuorum
|
||||
}
|
||||
|
||||
return latestFileInfo, nil
|
||||
}
|
||||
|
||||
// disksWithAllParts - This function needs to be called with
|
||||
// []StorageAPI returned by listOnlineDisks. Returns,
|
||||
//
|
||||
|
||||
@@ -30,6 +30,53 @@ import (
|
||||
"github.com/minio/madmin-go"
|
||||
)
|
||||
|
||||
// Returns the latest updated FileInfo files and error in case of failure.
|
||||
func getLatestFileInfo(ctx context.Context, partsMetadata []FileInfo, defaultParityCount int, errs []error) (FileInfo, error) {
|
||||
// There should be atleast half correct entries, if not return failure
|
||||
expectedRQuorum := len(partsMetadata) / 2
|
||||
if defaultParityCount == 0 {
|
||||
// if parity count is '0', we expected all entries to be present.
|
||||
expectedRQuorum = len(partsMetadata)
|
||||
}
|
||||
|
||||
reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, expectedRQuorum)
|
||||
if reducedErr != nil {
|
||||
return FileInfo{}, reducedErr
|
||||
}
|
||||
|
||||
// List all the file commit ids from parts metadata.
|
||||
modTimes := listObjectModtimes(partsMetadata, errs)
|
||||
|
||||
// Count all latest updated FileInfo values
|
||||
var count int
|
||||
var latestFileInfo FileInfo
|
||||
|
||||
// Reduce list of UUIDs to a single common value - i.e. the last updated Time
|
||||
modTime := commonTime(modTimes)
|
||||
|
||||
if modTime.IsZero() || modTime.Equal(timeSentinel) {
|
||||
return FileInfo{}, errErasureReadQuorum
|
||||
}
|
||||
|
||||
// Interate through all the modTimes and count the FileInfo(s) with latest time.
|
||||
for index, t := range modTimes {
|
||||
if partsMetadata[index].IsValid() && t.Equal(modTime) {
|
||||
latestFileInfo = partsMetadata[index]
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
if !latestFileInfo.IsValid() {
|
||||
return FileInfo{}, errErasureReadQuorum
|
||||
}
|
||||
|
||||
if count < latestFileInfo.Erasure.DataBlocks {
|
||||
return FileInfo{}, errErasureReadQuorum
|
||||
}
|
||||
|
||||
return latestFileInfo, nil
|
||||
}
|
||||
|
||||
// validates functionality provided to find most common
|
||||
// time occurrence from a list of time.
|
||||
func TestCommonTime(t *testing.T) {
|
||||
|
||||
@@ -230,6 +230,13 @@ func TestHealing(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer obj.Shutdown(context.Background())
|
||||
|
||||
// initialize the server and obtain the credentials and root.
|
||||
// credentials are necessary to sign the HTTP request.
|
||||
if err = newTestConfig(globalMinioDefaultRegion, obj); err != nil {
|
||||
t.Fatalf("Unable to initialize server config. %s", err)
|
||||
}
|
||||
|
||||
defer removeRoots(fsDirs)
|
||||
|
||||
z := obj.(*erasureServerPools)
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/sync/errgroup"
|
||||
@@ -165,18 +167,23 @@ func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, ve
|
||||
}, index)
|
||||
}
|
||||
|
||||
ignoredErrs := []error{
|
||||
errFileNotFound,
|
||||
errVolumeNotFound,
|
||||
errFileVersionNotFound,
|
||||
errDiskNotFound,
|
||||
errUnformattedDisk,
|
||||
}
|
||||
if strings.HasPrefix(bucket, minioMetaBucket) {
|
||||
// listing object might be truncated, ignore such errors from logging.
|
||||
ignoredErrs = append(ignoredErrs, io.ErrUnexpectedEOF)
|
||||
}
|
||||
errs := g.Wait()
|
||||
for index, err := range errs {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if !IsErr(err, []error{
|
||||
errFileNotFound,
|
||||
errVolumeNotFound,
|
||||
errFileVersionNotFound,
|
||||
errDiskNotFound,
|
||||
errUnformattedDisk,
|
||||
}...) {
|
||||
if !IsErr(err, ignoredErrs...) {
|
||||
logger.LogOnceIf(ctx, fmt.Errorf("Drive %s, path (%s/%s) returned an error (%w)",
|
||||
disks[index], bucket, object, err),
|
||||
disks[index].String())
|
||||
|
||||
+62
-26
@@ -394,21 +394,73 @@ func writeUniqueFileInfo(ctx context.Context, disks []StorageAPI, bucket, prefix
|
||||
return evalDisks(disks, mErrs), err
|
||||
}
|
||||
|
||||
func commonParity(parities []int) int {
|
||||
occMap := make(map[int]int)
|
||||
for _, p := range parities {
|
||||
occMap[p]++
|
||||
}
|
||||
|
||||
var maxOcc, commonParity int
|
||||
|
||||
for parity, occ := range occMap {
|
||||
if parity == -1 {
|
||||
// Ignore non defined parity
|
||||
continue
|
||||
}
|
||||
if occ >= maxOcc {
|
||||
maxOcc = occ
|
||||
commonParity = parity
|
||||
}
|
||||
}
|
||||
|
||||
if maxOcc == 0 {
|
||||
// Did not found anything useful
|
||||
return -1
|
||||
}
|
||||
return commonParity
|
||||
}
|
||||
|
||||
func listObjectParities(partsMetadata []FileInfo, errs []error) (parities []int) {
|
||||
parities = make([]int, len(partsMetadata))
|
||||
for index, metadata := range partsMetadata {
|
||||
if errs[index] != nil {
|
||||
parities[index] = -1
|
||||
continue
|
||||
}
|
||||
parities[index] = metadata.Erasure.ParityBlocks
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Returns per object readQuorum and writeQuorum
|
||||
// readQuorum is the min required disks to read data.
|
||||
// writeQuorum is the min required disks to write data.
|
||||
func objectQuorumFromMeta(ctx context.Context, partsMetaData []FileInfo, errs []error, defaultParityCount int) (objectReadQuorum, objectWriteQuorum int, err error) {
|
||||
// get the latest updated Metadata and a count of all the latest updated FileInfo(s)
|
||||
latestFileInfo, err := getLatestFileInfo(ctx, partsMetaData, defaultParityCount, errs)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
// There should be atleast half correct entries, if not return failure
|
||||
expectedRQuorum := len(partsMetaData) / 2
|
||||
if defaultParityCount == 0 {
|
||||
// if parity count is '0', we expected all entries to be present.
|
||||
expectedRQuorum = len(partsMetaData)
|
||||
}
|
||||
|
||||
if latestFileInfo.Deleted {
|
||||
// special case when parity is '0'
|
||||
if defaultParityCount == 0 {
|
||||
return len(partsMetaData), len(partsMetaData), nil
|
||||
}
|
||||
reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, expectedRQuorum)
|
||||
if reducedErr != nil {
|
||||
return -1, -1, reducedErr
|
||||
}
|
||||
|
||||
// special case when parity is '0'
|
||||
if defaultParityCount == 0 {
|
||||
return len(partsMetaData), len(partsMetaData), nil
|
||||
}
|
||||
|
||||
parities := listObjectParities(partsMetaData, errs)
|
||||
parityBlocks := commonParity(parities)
|
||||
|
||||
if parityBlocks < 0 {
|
||||
return -1, -1, errErasureReadQuorum
|
||||
}
|
||||
|
||||
if parityBlocks == 0 {
|
||||
// For delete markers do not use 'defaultParityCount' as it is not expected to be the case.
|
||||
// Use maximum allowed read quorum instead, writeQuorum+1 is returned for compatibility sake
|
||||
// but there are no callers that shall be using this.
|
||||
@@ -416,23 +468,7 @@ func objectQuorumFromMeta(ctx context.Context, partsMetaData []FileInfo, errs []
|
||||
return readQuorum, readQuorum + 1, nil
|
||||
}
|
||||
|
||||
parityBlocks := globalStorageClass.GetParityForSC(latestFileInfo.Metadata[xhttp.AmzStorageClass])
|
||||
if parityBlocks < 0 {
|
||||
parityBlocks = defaultParityCount
|
||||
}
|
||||
|
||||
// For erasure code upgraded objects choose the parity
|
||||
// blocks saved internally, instead of 'defaultParityCount'
|
||||
if _, ok := latestFileInfo.Metadata[minIOErasureUpgraded]; ok {
|
||||
if latestFileInfo.Erasure.ParityBlocks != 0 {
|
||||
parityBlocks = latestFileInfo.Erasure.ParityBlocks
|
||||
}
|
||||
}
|
||||
|
||||
dataBlocks := latestFileInfo.Erasure.DataBlocks
|
||||
if dataBlocks == 0 {
|
||||
dataBlocks = len(partsMetaData) - parityBlocks
|
||||
}
|
||||
dataBlocks := len(partsMetaData) - parityBlocks
|
||||
|
||||
writeQuorum := dataBlocks
|
||||
if dataBlocks == parityBlocks {
|
||||
|
||||
+34
-12
@@ -32,6 +32,7 @@ import (
|
||||
|
||||
"github.com/klauspost/readahead"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/crypto"
|
||||
"github.com/minio/minio/internal/hash"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@@ -982,8 +983,28 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var checksumCombined []byte
|
||||
|
||||
// However, in case of encryption, the persisted part ETags don't match
|
||||
// what we have sent to the client during PutObjectPart. The reason is
|
||||
// that ETags are encrypted. Hence, the client will send a list of complete
|
||||
// part ETags of which non can match the ETag of any part. For example
|
||||
// ETag (client): 30902184f4e62dd8f98f0aaff810c626
|
||||
// ETag (server-internal): 20000f00ce5dc16e3f3b124f586ae1d88e9caa1c598415c2759bbb50e84a59f630902184f4e62dd8f98f0aaff810c626
|
||||
//
|
||||
// Therefore, we adjust all ETags sent by the client to match what is stored
|
||||
// on the backend.
|
||||
kind, isEncrypted := crypto.IsEncrypted(fi.Metadata)
|
||||
|
||||
var objectEncryptionKey []byte
|
||||
if isEncrypted && kind == crypto.S3 {
|
||||
objectEncryptionKey, err = decryptObjectMeta(nil, bucket, object, fi.Metadata)
|
||||
if err != nil {
|
||||
return oi, err
|
||||
}
|
||||
}
|
||||
|
||||
for i, part := range partInfoFiles {
|
||||
partID := parts[i].PartNumber
|
||||
if part.Error != "" || !part.Exists {
|
||||
@@ -1042,21 +1063,22 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
}
|
||||
return oi, invp
|
||||
}
|
||||
gotPart := currentFI.Parts[partIdx]
|
||||
expPart := currentFI.Parts[partIdx]
|
||||
|
||||
// ensure that part ETag is canonicalized to strip off extraneous quotes
|
||||
part.ETag = canonicalizeETag(part.ETag)
|
||||
if gotPart.ETag != part.ETag {
|
||||
expETag := tryDecryptETag(objectEncryptionKey, expPart.ETag, kind != crypto.S3)
|
||||
if expETag != part.ETag {
|
||||
invp := InvalidPart{
|
||||
PartNumber: part.PartNumber,
|
||||
ExpETag: gotPart.ETag,
|
||||
ExpETag: expETag,
|
||||
GotETag: part.ETag,
|
||||
}
|
||||
return oi, invp
|
||||
}
|
||||
|
||||
if checksumType.IsSet() {
|
||||
crc := gotPart.Checksums[checksumType.String()]
|
||||
crc := expPart.Checksums[checksumType.String()]
|
||||
if crc == "" {
|
||||
return oi, InvalidPart{
|
||||
PartNumber: part.PartNumber,
|
||||
@@ -1081,31 +1103,31 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
PartNumber: part.PartNumber,
|
||||
}
|
||||
}
|
||||
checksumCombined = append(checksumCombined, cs.Raw()...)
|
||||
checksumCombined = append(checksumCombined, cs.Raw...)
|
||||
}
|
||||
|
||||
// All parts except the last part has to be at least 5MB.
|
||||
if (i < len(parts)-1) && !isMinAllowedPartSize(currentFI.Parts[partIdx].ActualSize) {
|
||||
return oi, PartTooSmall{
|
||||
PartNumber: part.PartNumber,
|
||||
PartSize: gotPart.ActualSize,
|
||||
PartSize: expPart.ActualSize,
|
||||
PartETag: part.ETag,
|
||||
}
|
||||
}
|
||||
|
||||
// Save for total object size.
|
||||
objectSize += gotPart.Size
|
||||
objectSize += expPart.Size
|
||||
|
||||
// Save the consolidated actual size.
|
||||
objectActualSize += gotPart.ActualSize
|
||||
objectActualSize += expPart.ActualSize
|
||||
|
||||
// Add incoming parts.
|
||||
fi.Parts[i] = ObjectPartInfo{
|
||||
Number: part.PartNumber,
|
||||
Size: gotPart.Size,
|
||||
ActualSize: gotPart.ActualSize,
|
||||
ModTime: gotPart.ModTime,
|
||||
Index: gotPart.Index,
|
||||
Size: expPart.Size,
|
||||
ActualSize: expPart.ActualSize,
|
||||
ModTime: expPart.ModTime,
|
||||
Index: expPart.Index,
|
||||
Checksums: nil, // Not transferred since we do not need it.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1530,7 +1530,7 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
||||
if opts.Expiration.Expire {
|
||||
goi, _, err := er.getObjectInfoAndQuorum(ctx, bucket, object, opts)
|
||||
if err == nil {
|
||||
action := evalActionFromLifecycle(ctx, *lc, rcfg, goi, false)
|
||||
action := evalActionFromLifecycle(ctx, *lc, rcfg, goi)
|
||||
var isErr bool
|
||||
switch action {
|
||||
case lifecycle.NoneAction:
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -206,7 +205,7 @@ func TestDeleteObjectsVersioned(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = ioutil.ReadFile(pathJoin(fsDirs[0], bucketName, "dir/obj1", "xl.meta")); err == nil {
|
||||
if _, err = os.ReadFile(pathJoin(fsDirs[0], bucketName, "dir/obj1", "xl.meta")); err == nil {
|
||||
t.Fatalf("xl.meta still present after removal")
|
||||
}
|
||||
}
|
||||
@@ -543,7 +542,7 @@ func TestGetObjectNoQuorum(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if gr != nil {
|
||||
_, err = io.Copy(ioutil.Discard, gr)
|
||||
_, err = io.Copy(io.Discard, gr)
|
||||
if err != toObjectErr(errErasureReadQuorum, bucket, object) {
|
||||
t.Errorf("Expected GetObject to fail with %v, but failed with %v", toObjectErr(errErasureReadQuorum, bucket, object), err)
|
||||
}
|
||||
@@ -588,7 +587,7 @@ func TestGetObjectNoQuorum(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if gr != nil {
|
||||
_, err = io.Copy(ioutil.Discard, gr)
|
||||
_, err = io.Copy(io.Discard, gr)
|
||||
if err != toObjectErr(errErasureReadQuorum, bucket, object) {
|
||||
t.Errorf("Expected GetObject to fail with %v, but failed with %v", toObjectErr(errErasureReadQuorum, bucket, object), err)
|
||||
}
|
||||
|
||||
@@ -701,7 +701,7 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
||||
}
|
||||
versioned := vc != nil && vc.Versioned(object)
|
||||
objInfo := fi.ToObjectInfo(bucket, object, versioned)
|
||||
action := evalActionFromLifecycle(ctx, *lc, lr, objInfo, false)
|
||||
action := evalActionFromLifecycle(ctx, *lc, lr, objInfo)
|
||||
switch action {
|
||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||
globalExpiryState.enqueueByDays(objInfo, false, action == lifecycle.DeleteVersionAction)
|
||||
@@ -768,6 +768,10 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
||||
DeleteMarker: true, // make sure we create a delete marker
|
||||
SkipDecommissioned: true, // make sure we skip the decommissioned pool
|
||||
})
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
// object/version already deleted by the application, nothing to do here we move on.
|
||||
continue
|
||||
}
|
||||
var failure bool
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
@@ -783,7 +787,7 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
||||
continue
|
||||
}
|
||||
|
||||
var failure bool
|
||||
var failure, ignore bool
|
||||
// gr.Close() is ensured by decommissionObject().
|
||||
for try := 0; try < 3; try++ {
|
||||
gr, err := set.GetObjectNInfo(ctx,
|
||||
@@ -796,9 +800,10 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
||||
VersionID: version.VersionID,
|
||||
NoDecryption: true,
|
||||
})
|
||||
if isErrObjectNotFound(err) {
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
// object deleted by the application, nothing to do here we move on.
|
||||
return
|
||||
ignore = true
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
failure = true
|
||||
@@ -816,6 +821,9 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
||||
failure = false
|
||||
break
|
||||
}
|
||||
if ignore {
|
||||
continue
|
||||
}
|
||||
z.poolMetaMutex.Lock()
|
||||
z.poolMeta.CountItem(idx, version.Size, failure)
|
||||
z.poolMetaMutex.Unlock()
|
||||
|
||||
@@ -1302,7 +1302,7 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
|
||||
objInfo, err := z.GetObjectInfo(ctx, bucket, prefix, ObjectOptions{NoLock: true})
|
||||
if err == nil {
|
||||
if opts.Lifecycle != nil {
|
||||
action := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, objInfo, false)
|
||||
action := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, objInfo)
|
||||
switch action {
|
||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||
fallthrough
|
||||
@@ -1588,6 +1588,8 @@ func (z *erasureServerPools) GetBucketInfo(ctx context.Context, bucket string, o
|
||||
meta, err := globalBucketMetadataSys.Get(bucket)
|
||||
if err == nil {
|
||||
bucketInfo.Created = meta.Created
|
||||
bucketInfo.Versioning = meta.LockEnabled || globalBucketVersioningSys.Enabled(bucket)
|
||||
bucketInfo.ObjectLocking = meta.LockEnabled
|
||||
}
|
||||
return bucketInfo, nil
|
||||
}
|
||||
@@ -1602,6 +1604,8 @@ func (z *erasureServerPools) GetBucketInfo(ctx context.Context, bucket string, o
|
||||
meta, err := globalBucketMetadataSys.Get(bucket)
|
||||
if err == nil {
|
||||
bucketInfo.Created = meta.Created
|
||||
bucketInfo.Versioning = meta.LockEnabled || globalBucketVersioningSys.Enabled(bucket)
|
||||
bucketInfo.ObjectLocking = meta.LockEnabled
|
||||
}
|
||||
return bucketInfo, nil
|
||||
}
|
||||
@@ -1875,12 +1879,22 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
|
||||
versionsSorter(fivs.Versions).reverse()
|
||||
|
||||
for _, version := range fivs.Versions {
|
||||
send := true
|
||||
if opts.WalkFilter != nil && !opts.WalkFilter(version) {
|
||||
send = false
|
||||
}
|
||||
|
||||
if !send {
|
||||
continue
|
||||
}
|
||||
|
||||
versioned := vcfg != nil && vcfg.Versioned(version.Name)
|
||||
objInfo := version.ToObjectInfo(bucket, version.Name, versioned)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case results <- version.ToObjectInfo(bucket, version.Name, versioned):
|
||||
case results <- objInfo:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1904,7 +1918,7 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
|
||||
path: path,
|
||||
filterPrefix: filterPrefix,
|
||||
recursive: true,
|
||||
forwardTo: "",
|
||||
forwardTo: opts.WalkMarker,
|
||||
minDisks: 1,
|
||||
reportNotFound: false,
|
||||
agreed: loadEntry,
|
||||
|
||||
@@ -357,6 +357,10 @@ func newErasureSets(ctx context.Context, endpoints PoolEndpoints, storageDisks [
|
||||
endpointStrings[i] = endpoint.String()
|
||||
}
|
||||
|
||||
if defaultParityCount == 0 {
|
||||
logger.Error("Warning: Default parity set to 0. This can lead to data loss.")
|
||||
}
|
||||
|
||||
// Initialize the erasure sets instance.
|
||||
s := &erasureSets{
|
||||
sets: make([]*erasureObjects, setCount),
|
||||
|
||||
+113
-17
@@ -43,6 +43,7 @@ import (
|
||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||
"github.com/minio/minio/internal/bucket/object/lock"
|
||||
"github.com/minio/minio/internal/bucket/replication"
|
||||
"github.com/minio/minio/internal/crypto"
|
||||
"github.com/minio/minio/internal/event"
|
||||
"github.com/minio/minio/internal/hash"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
@@ -343,7 +344,14 @@ func (es *erasureSingle) GetBucketInfo(ctx context.Context, bucket string, opts
|
||||
}
|
||||
return bi, toObjectErr(err, bucket)
|
||||
}
|
||||
return BucketInfo{Name: volInfo.Name, Created: volInfo.Created}, nil
|
||||
bi = BucketInfo{Name: volInfo.Name, Created: volInfo.Created}
|
||||
meta, err := globalBucketMetadataSys.Get(bucket)
|
||||
if err == nil {
|
||||
bi.Created = meta.Created
|
||||
bi.Versioning = meta.LockEnabled || globalBucketVersioningSys.Enabled(bucket)
|
||||
bi.ObjectLocking = meta.LockEnabled
|
||||
}
|
||||
return bi, nil
|
||||
}
|
||||
|
||||
// DeleteBucket - deletes a bucket.
|
||||
@@ -1009,6 +1017,11 @@ func (es *erasureSingle) putObject(ctx context.Context, bucket string, object st
|
||||
}
|
||||
|
||||
fi.DataDir = mustGetUUID()
|
||||
fi.Checksum = opts.WantChecksum.AppendTo(nil)
|
||||
if opts.EncryptFn != nil {
|
||||
fi.Checksum = opts.EncryptFn("object-checksum", fi.Checksum)
|
||||
}
|
||||
|
||||
uniqueID := mustGetUUID()
|
||||
tempObj := uniqueID
|
||||
|
||||
@@ -1464,7 +1477,7 @@ func (es *erasureSingle) DeleteObject(ctx context.Context, bucket, object string
|
||||
}
|
||||
|
||||
if opts.Expiration.Expire {
|
||||
action := evalActionFromLifecycle(ctx, *lc, rcfg, goi, false)
|
||||
action := evalActionFromLifecycle(ctx, *lc, rcfg, goi)
|
||||
var isErr bool
|
||||
switch action {
|
||||
case lifecycle.NoneAction:
|
||||
@@ -2585,6 +2598,7 @@ func (es *erasureSingle) ListObjectParts(ctx context.Context, bucket, object, up
|
||||
result.MaxParts = maxParts
|
||||
result.PartNumberMarker = partNumberMarker
|
||||
result.UserDefined = cloneMSS(fi.Metadata)
|
||||
result.ChecksumAlgorithm = fi.Metadata[hash.MinIOMultipartChecksum]
|
||||
|
||||
// For empty number of parts or maxParts as zero, return right here.
|
||||
if len(fi.Parts) == 0 || maxParts == 0 {
|
||||
@@ -2605,10 +2619,15 @@ func (es *erasureSingle) ListObjectParts(ctx context.Context, bucket, object, up
|
||||
count := maxParts
|
||||
for _, part := range parts {
|
||||
result.Parts = append(result.Parts, PartInfo{
|
||||
PartNumber: part.Number,
|
||||
ETag: part.ETag,
|
||||
LastModified: fi.ModTime,
|
||||
Size: part.Size,
|
||||
PartNumber: part.Number,
|
||||
ETag: part.ETag,
|
||||
LastModified: fi.ModTime,
|
||||
ActualSize: part.ActualSize,
|
||||
Size: part.Size,
|
||||
ChecksumCRC32: part.Checksums["CRC32"],
|
||||
ChecksumCRC32C: part.Checksums["CRC32C"],
|
||||
ChecksumSHA1: part.Checksums["SHA1"],
|
||||
ChecksumSHA256: part.Checksums["SHA256"],
|
||||
})
|
||||
count--
|
||||
if count == 0 {
|
||||
@@ -2677,6 +2696,40 @@ func (es *erasureSingle) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
return oi, err
|
||||
}
|
||||
|
||||
// Checksum type set when upload started.
|
||||
var checksumType hash.ChecksumType
|
||||
if cs := fi.Metadata[hash.MinIOMultipartChecksum]; cs != "" {
|
||||
checksumType = hash.NewChecksumType(cs)
|
||||
if opts.WantChecksum != nil && !opts.WantChecksum.Type.Is(checksumType) {
|
||||
return oi, InvalidArgument{
|
||||
Bucket: bucket,
|
||||
Object: fi.Name,
|
||||
Err: fmt.Errorf("checksum type mismatch"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var checksumCombined []byte
|
||||
|
||||
// However, in case of encryption, the persisted part ETags don't match
|
||||
// what we have sent to the client during PutObjectPart. The reason is
|
||||
// that ETags are encrypted. Hence, the client will send a list of complete
|
||||
// part ETags of which non can match the ETag of any part. For example
|
||||
// ETag (client): 30902184f4e62dd8f98f0aaff810c626
|
||||
// ETag (server-internal): 20000f00ce5dc16e3f3b124f586ae1d88e9caa1c598415c2759bbb50e84a59f630902184f4e62dd8f98f0aaff810c626
|
||||
//
|
||||
// Therefore, we adjust all ETags sent by the client to match what is stored
|
||||
// on the backend.
|
||||
kind, isEncrypted := crypto.IsEncrypted(fi.Metadata)
|
||||
|
||||
var objectEncryptionKey []byte
|
||||
if isEncrypted && kind == crypto.S3 {
|
||||
objectEncryptionKey, err = decryptObjectMeta(nil, bucket, object, fi.Metadata)
|
||||
if err != nil {
|
||||
return oi, err
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate full object size.
|
||||
var objectSize int64
|
||||
|
||||
@@ -2704,39 +2757,72 @@ func (es *erasureSingle) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
}
|
||||
return oi, invp
|
||||
}
|
||||
expPart := currentFI.Parts[partIdx]
|
||||
|
||||
// ensure that part ETag is canonicalized to strip off extraneous quotes
|
||||
part.ETag = canonicalizeETag(part.ETag)
|
||||
if currentFI.Parts[partIdx].ETag != part.ETag {
|
||||
expETag := tryDecryptETag(objectEncryptionKey, currentFI.Parts[partIdx].ETag, kind != crypto.S3)
|
||||
if expETag != part.ETag {
|
||||
invp := InvalidPart{
|
||||
PartNumber: part.PartNumber,
|
||||
ExpETag: currentFI.Parts[partIdx].ETag,
|
||||
ExpETag: expETag,
|
||||
GotETag: part.ETag,
|
||||
}
|
||||
return oi, invp
|
||||
}
|
||||
|
||||
if checksumType.IsSet() {
|
||||
crc := expPart.Checksums[checksumType.String()]
|
||||
if crc == "" {
|
||||
return oi, InvalidPart{
|
||||
PartNumber: part.PartNumber,
|
||||
}
|
||||
}
|
||||
wantCS := map[string]string{
|
||||
hash.ChecksumCRC32.String(): part.ChecksumCRC32,
|
||||
hash.ChecksumCRC32C.String(): part.ChecksumCRC32C,
|
||||
hash.ChecksumSHA1.String(): part.ChecksumSHA1,
|
||||
hash.ChecksumSHA256.String(): part.ChecksumSHA256,
|
||||
}
|
||||
if wantCS[checksumType.String()] != crc {
|
||||
return oi, InvalidPart{
|
||||
PartNumber: part.PartNumber,
|
||||
ExpETag: wantCS[checksumType.String()],
|
||||
GotETag: crc,
|
||||
}
|
||||
}
|
||||
cs := hash.NewChecksumString(checksumType.String(), crc)
|
||||
if !cs.Valid() {
|
||||
return oi, InvalidPart{
|
||||
PartNumber: part.PartNumber,
|
||||
}
|
||||
}
|
||||
checksumCombined = append(checksumCombined, cs.Raw...)
|
||||
}
|
||||
|
||||
// All parts except the last part has to be atleast 5MB.
|
||||
if (i < len(parts)-1) && !isMinAllowedPartSize(currentFI.Parts[partIdx].ActualSize) {
|
||||
return oi, PartTooSmall{
|
||||
PartNumber: part.PartNumber,
|
||||
PartSize: currentFI.Parts[partIdx].ActualSize,
|
||||
PartSize: expPart.ActualSize,
|
||||
PartETag: part.ETag,
|
||||
}
|
||||
}
|
||||
|
||||
// Save for total object size.
|
||||
objectSize += currentFI.Parts[partIdx].Size
|
||||
objectSize += expPart.Size
|
||||
|
||||
// Save the consolidated actual size.
|
||||
objectActualSize += currentFI.Parts[partIdx].ActualSize
|
||||
objectActualSize += expPart.ActualSize
|
||||
|
||||
// Add incoming parts.
|
||||
fi.Parts[i] = ObjectPartInfo{
|
||||
Number: part.PartNumber,
|
||||
Size: currentFI.Parts[partIdx].Size,
|
||||
ActualSize: currentFI.Parts[partIdx].ActualSize,
|
||||
Index: currentFI.Parts[partIdx].Index,
|
||||
Size: expPart.Size,
|
||||
ActualSize: expPart.ActualSize,
|
||||
ModTime: expPart.ModTime,
|
||||
Index: expPart.Index,
|
||||
Checksums: nil, // Not transferred since we do not need it.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2876,7 +2962,7 @@ func (es *erasureSingle) ListObjects(ctx context.Context, bucket, prefix, marker
|
||||
objInfo, err := es.GetObjectInfo(ctx, bucket, prefix, ObjectOptions{NoLock: true})
|
||||
if err == nil {
|
||||
if opts.Lifecycle != nil {
|
||||
action := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, objInfo, false)
|
||||
action := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, objInfo)
|
||||
switch action {
|
||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||
fallthrough
|
||||
@@ -3029,12 +3115,22 @@ func (es *erasureSingle) Walk(ctx context.Context, bucket, prefix string, result
|
||||
versionsSorter(fivs.Versions).reverse()
|
||||
|
||||
for _, version := range fivs.Versions {
|
||||
send := true
|
||||
if opts.WalkFilter != nil && !opts.WalkFilter(version) {
|
||||
send = false
|
||||
}
|
||||
|
||||
if !send {
|
||||
continue
|
||||
}
|
||||
|
||||
versioned := vcfg != nil && vcfg.Versioned(version.Name)
|
||||
objInfo := version.ToObjectInfo(bucket, version.Name, versioned)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case results <- version.ToObjectInfo(bucket, version.Name, versioned):
|
||||
case results <- objInfo:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3058,7 +3154,7 @@ func (es *erasureSingle) Walk(ctx context.Context, bucket, prefix string, result
|
||||
path: path,
|
||||
filterPrefix: filterPrefix,
|
||||
recursive: true,
|
||||
forwardTo: "",
|
||||
forwardTo: opts.WalkMarker,
|
||||
minDisks: 1,
|
||||
reportNotFound: false,
|
||||
agreed: loadEntry,
|
||||
|
||||
+7
-4
@@ -107,9 +107,8 @@ func (d byDiskTotal) Less(i, j int) bool {
|
||||
}
|
||||
|
||||
func diskErrToDriveState(err error) (state string) {
|
||||
state = madmin.DriveStateUnknown
|
||||
switch {
|
||||
case errors.Is(err, errDiskNotFound):
|
||||
case errors.Is(err, errDiskNotFound) || errors.Is(err, context.DeadlineExceeded):
|
||||
state = madmin.DriveStateOffline
|
||||
case errors.Is(err, errCorruptedFormat):
|
||||
state = madmin.DriveStateCorrupt
|
||||
@@ -121,7 +120,10 @@ func diskErrToDriveState(err error) (state string) {
|
||||
state = madmin.DriveStateFaulty
|
||||
case err == nil:
|
||||
state = madmin.DriveStateOk
|
||||
default:
|
||||
state = fmt.Sprintf("%s (cause: %s)", madmin.DriveStateUnknown, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -184,18 +186,19 @@ func getDisksInfo(disks []StorageAPI, endpoints []Endpoint) (disksInfo []madmin.
|
||||
for index := range disks {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
diskEndpoint := endpoints[index].String()
|
||||
if disks[index] == OfflineDisk {
|
||||
logger.LogIf(GlobalContext, fmt.Errorf("%s: %s", errDiskNotFound, endpoints[index]))
|
||||
disksInfo[index] = madmin.Disk{
|
||||
State: diskErrToDriveState(errDiskNotFound),
|
||||
Endpoint: endpoints[index].String(),
|
||||
Endpoint: diskEndpoint,
|
||||
}
|
||||
// Storage disk is empty, perhaps ignored disk or not available.
|
||||
return errDiskNotFound
|
||||
}
|
||||
info, err := disks[index].DiskInfo(context.TODO())
|
||||
di := madmin.Disk{
|
||||
Endpoint: info.Endpoint,
|
||||
Endpoint: diskEndpoint,
|
||||
DrivePath: info.MountPath,
|
||||
TotalSpace: info.Total,
|
||||
UsedSpace: info.Used,
|
||||
|
||||
+28
-13
@@ -19,6 +19,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -38,16 +39,18 @@ type EventNotifier struct {
|
||||
targetResCh chan event.TargetIDResult
|
||||
bucketRulesMap map[string]event.RulesMap
|
||||
bucketRemoteTargetRulesMap map[string]map[event.TargetID]event.RulesMap
|
||||
eventsQueue chan eventArgs
|
||||
}
|
||||
|
||||
// NewEventNotifier - creates new event notification object.
|
||||
func NewEventNotifier() *EventNotifier {
|
||||
// targetList/bucketRulesMap/bucketRemoteTargetRulesMap are populated by NotificationSys.Init()
|
||||
// targetList/bucketRulesMap/bucketRemoteTargetRulesMap are populated by NotificationSys.InitBucketTargets()
|
||||
return &EventNotifier{
|
||||
targetList: event.NewTargetList(),
|
||||
targetResCh: make(chan event.TargetIDResult),
|
||||
bucketRulesMap: make(map[string]event.RulesMap),
|
||||
bucketRemoteTargetRulesMap: make(map[string]map[event.TargetID]event.RulesMap),
|
||||
eventsQueue: make(chan eventArgs, 10000),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +67,7 @@ func (evnot *EventNotifier) GetARNList(onlyActive bool) []string {
|
||||
// This list is only meant for external targets, filter
|
||||
// this out pro-actively.
|
||||
if !strings.HasPrefix(targetID.ID, "httpclient+") {
|
||||
if onlyActive && !target.HasQueueStore() {
|
||||
if onlyActive {
|
||||
if _, err := target.IsActive(); err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -91,7 +94,7 @@ func (evnot *EventNotifier) set(bucket BucketInfo, meta BucketMetadata) {
|
||||
evnot.AddRulesMap(bucket.Name, config.ToRulesMap())
|
||||
}
|
||||
|
||||
// InitBucketTargets - initializes notification system from notification.xml of all buckets.
|
||||
// InitBucketTargets - initializes event notification system from notification.xml of all buckets.
|
||||
func (evnot *EventNotifier) InitBucketTargets(ctx context.Context, objAPI ObjectLayer) error {
|
||||
if objAPI == nil {
|
||||
return errServerNotInitialized
|
||||
@@ -102,7 +105,15 @@ func (evnot *EventNotifier) InitBucketTargets(ctx context.Context, objAPI Object
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.LogIf(ctx, evnot.targetList.Add(globalConfigTargetList.Targets()...))
|
||||
if err := evnot.targetList.Add(globalConfigTargetList.Targets()...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for e := range evnot.eventsQueue {
|
||||
evnot.send(e)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for res := range evnot.targetResCh {
|
||||
@@ -166,14 +177,8 @@ func (evnot *EventNotifier) ConfiguredTargetIDs() []event.TargetID {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Filter out targets configured via env
|
||||
var tIDs []event.TargetID
|
||||
for _, targetID := range targetIDs {
|
||||
if !globalEnvTargetList.Exists(targetID) {
|
||||
tIDs = append(tIDs, targetID)
|
||||
}
|
||||
}
|
||||
return tIDs
|
||||
|
||||
return targetIDs
|
||||
}
|
||||
|
||||
// RemoveNotification - removes all notification configuration for bucket name.
|
||||
@@ -207,8 +212,18 @@ func (evnot *EventNotifier) RemoveAllRemoteTargets() {
|
||||
}
|
||||
}
|
||||
|
||||
// Send - sends event data to all matching targets.
|
||||
// Send - sends the event to all registered notification targets
|
||||
func (evnot *EventNotifier) Send(args eventArgs) {
|
||||
select {
|
||||
case evnot.eventsQueue <- args:
|
||||
default:
|
||||
// A new goroutine is created for each notification job, eventsQueue is
|
||||
// drained quickly and is not expected to be filled with any scenario.
|
||||
logger.LogIf(context.Background(), errors.New("internal events queue unexpectedly full"))
|
||||
}
|
||||
}
|
||||
|
||||
func (evnot *EventNotifier) send(args eventArgs) {
|
||||
evnot.RLock()
|
||||
targetIDSet := evnot.bucketRulesMap[args.BucketName].Match(args.EventName, args.Object.Name)
|
||||
evnot.RUnlock()
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -421,7 +420,7 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
|
||||
// get old cached metadata
|
||||
oldMetaPath := pathJoin(oldCacheBucketsPath, bucket, object, cacheMetaJSONFile)
|
||||
metaPath := pathJoin(destdir, cacheMetaJSONFile)
|
||||
metaBytes, err := ioutil.ReadFile(oldMetaPath)
|
||||
metaBytes, err := os.ReadFile(oldMetaPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -459,7 +458,7 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(metaPath, jsonData, 0o644); err != nil {
|
||||
if err = os.WriteFile(metaPath, jsonData, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/minio/minio/internal/color"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/config/storageclass"
|
||||
@@ -203,7 +203,7 @@ func formatErasureMigrate(export string) ([]byte, fs.FileInfo, error) {
|
||||
}
|
||||
|
||||
migrate := func(formatPath string, formatData []byte) ([]byte, fs.FileInfo, error) {
|
||||
if err = ioutil.WriteFile(formatPath, formatData, 0o666); err != nil {
|
||||
if err = os.WriteFile(formatPath, formatData, 0o666); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formatFi, err := Lstat(formatPath)
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -122,7 +121,7 @@ func TestFormatErasureMigrate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0o644)); err != nil {
|
||||
if err = os.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0o644)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -140,7 +139,7 @@ func TestFormatErasureMigrate(t *testing.T) {
|
||||
t.Fatalf("expected version: %s, got: %s", formatErasureVersionV3, migratedVersion)
|
||||
}
|
||||
|
||||
b, err = ioutil.ReadFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile))
|
||||
b, err = os.ReadFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -170,7 +169,7 @@ func TestFormatErasureMigrate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0o644)); err != nil {
|
||||
if err = os.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0o644)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -190,7 +189,7 @@ func TestFormatErasureMigrate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0o644)); err != nil {
|
||||
if err = os.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0o644)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
@@ -267,7 +266,7 @@ func TestFSDeletes(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile(pathJoin(path, "success-vol", "not-empty", "file"), []byte("data"), 0o777)
|
||||
err = os.WriteFile(pathJoin(path, "success-vol", "not-empty", "file"), []byte("data"), 0o777)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -363,7 +362,7 @@ func BenchmarkFSDeleteFile(b *testing.B) {
|
||||
// We need to create and delete the file sequentially inside the benchmark.
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
err = ioutil.WriteFile(filename, []byte("data"), 0o777)
|
||||
err = os.WriteFile(filename, []byte("data"), 0o777)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -540,7 +539,7 @@ func TestFSRemoveMeta(t *testing.T) {
|
||||
func TestFSIsFile(t *testing.T) {
|
||||
filePath := pathJoin(t.TempDir(), "tmpfile")
|
||||
|
||||
if err := ioutil.WriteFile(filePath, nil, 0o777); err != nil {
|
||||
if err := os.WriteFile(filePath, nil, 0o777); err != nil {
|
||||
t.Fatalf("Unable to create file %s", filePath)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
pathutil "path"
|
||||
|
||||
@@ -216,7 +215,7 @@ func (m *fsMetaV1) ReadFrom(ctx context.Context, lk *lock.LockedFile) (n int64,
|
||||
return 0, err
|
||||
}
|
||||
|
||||
fsMetaBuf, err = ioutil.ReadAll(io.NewSectionReader(lk, 0, fi.Size()))
|
||||
fsMetaBuf, err = io.ReadAll(io.NewSectionReader(lk, 0, fi.Size()))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return 0, err
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -246,7 +246,7 @@ func (fs *FSObjects) NewMultipartUpload(ctx context.Context, bucket, object stri
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(pathJoin(uploadIDDir, fs.metaJSONFile), fsMetaBytes, 0o666); err != nil {
|
||||
if err = os.WriteFile(pathJoin(uploadIDDir, fs.metaJSONFile), fsMetaBytes, 0o666); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return nil, err
|
||||
}
|
||||
@@ -525,7 +525,7 @@ func (fs *FSObjects) ListObjectParts(ctx context.Context, bucket, object, upload
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
fsMetaBytes, err := ioutil.ReadAll(rc)
|
||||
fsMetaBytes, err := io.ReadAll(rc)
|
||||
if err != nil {
|
||||
return result, toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
+3
-4
@@ -22,7 +22,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/user"
|
||||
@@ -117,8 +116,7 @@ func initMetaVolumeFS(fsPath, fsUUID string) error {
|
||||
}
|
||||
|
||||
// NewFSObjectLayer - initialize new fs object layer.
|
||||
func NewFSObjectLayer(fsPath string) (ObjectLayer, error) {
|
||||
ctx := GlobalContext
|
||||
func NewFSObjectLayer(ctx context.Context, fsPath string) (ObjectLayer, error) {
|
||||
if fsPath == "" {
|
||||
return nil, errInvalidArgument
|
||||
}
|
||||
@@ -474,6 +472,7 @@ func (fs *FSObjects) MakeBucketWithLocation(ctx context.Context, bucket string,
|
||||
}
|
||||
|
||||
meta := newBucketMetadata(bucket)
|
||||
meta.SetCreatedAt(opts.CreatedAt)
|
||||
if err := meta.Save(ctx, fs); err != nil {
|
||||
return toObjectErr(err, bucket)
|
||||
}
|
||||
@@ -868,7 +867,7 @@ func (fs *FSObjects) getObjectInfoNoFSLock(ctx context.Context, bucket, object s
|
||||
|
||||
rc, _, err := fsOpenFile(ctx, fsMetaPath, 0)
|
||||
if err == nil {
|
||||
fsMetaBuf, rerr := ioutil.ReadAll(rc)
|
||||
fsMetaBuf, rerr := io.ReadAll(rc)
|
||||
rc.Close()
|
||||
if rerr == nil {
|
||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
|
||||
+5
-2
@@ -19,6 +19,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -29,17 +30,19 @@ import (
|
||||
// TestNewFS - tests initialization of all input disks
|
||||
// and constructs a valid `FS` object layer.
|
||||
func TestNewFS(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
// Do not attempt to create this path, the test validates
|
||||
// so that NewFSObjectLayer initializes non existing paths
|
||||
// and successfully returns initialized object layer.
|
||||
disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
defer os.RemoveAll(disk)
|
||||
|
||||
_, err := NewFSObjectLayer("")
|
||||
_, err := NewFSObjectLayer(ctx, "")
|
||||
if err != errInvalidArgument {
|
||||
t.Errorf("Expecting error invalid argument, got %s", err)
|
||||
}
|
||||
_, err = NewFSObjectLayer(disk)
|
||||
_, err = NewFSObjectLayer(ctx, disk)
|
||||
if err != nil {
|
||||
errMsg := "Unable to recognize backend format, Drive is not in FS format."
|
||||
if err.Error() == errMsg {
|
||||
|
||||
+15
-29
@@ -21,7 +21,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -220,17 +220,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
||||
// Set when gateway is enabled
|
||||
globalIsGateway = true
|
||||
|
||||
// Initialize server config.
|
||||
srvCfg := newServerConfig()
|
||||
|
||||
// Override any values from ENVs.
|
||||
lookupConfigs(srvCfg, nil)
|
||||
|
||||
// hold the mutex lock before a new config is assigned.
|
||||
globalServerConfigMu.Lock()
|
||||
globalServerConfig = srvCfg
|
||||
globalServerConfigMu.Unlock()
|
||||
|
||||
// Initialize router. `SkipClean(true)` stops gorilla/mux from
|
||||
// normalizing URL path minio/minio#3256
|
||||
// avoid URL path encoding minio/minio#8950
|
||||
@@ -252,11 +241,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
||||
// Add API router.
|
||||
registerAPIRouter(router)
|
||||
|
||||
// Enable bucket forwarding handler only if bucket federation is enabled.
|
||||
if globalDNSConfig != nil && globalBucketFederation {
|
||||
globalHandlers = append(globalHandlers, setBucketForwardingHandler)
|
||||
}
|
||||
|
||||
// Use all the middlewares
|
||||
router.Use(globalHandlers...)
|
||||
|
||||
@@ -265,21 +249,12 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
||||
getCert = globalTLSCerts.GetCertificate
|
||||
}
|
||||
|
||||
listeners := ctx.Int("listeners")
|
||||
if listeners == 0 {
|
||||
listeners = 1
|
||||
}
|
||||
addrs := make([]string, 0, listeners)
|
||||
for i := 0; i < listeners; i++ {
|
||||
addrs = append(addrs, globalMinioAddr)
|
||||
}
|
||||
|
||||
httpServer := xhttp.NewServer(addrs).
|
||||
httpServer := xhttp.NewServer([]string{globalMinioAddr}).
|
||||
UseHandler(setCriticalErrorHandler(corsHandler(router))).
|
||||
UseTLSConfig(newTLSConfig(getCert)).
|
||||
UseShutdownTimeout(ctx.Duration("shutdown-timeout")).
|
||||
UseBaseContext(GlobalContext).
|
||||
UseCustomLogger(log.New(ioutil.Discard, "", 0)) // Turn-off random logging by Go stdlib
|
||||
UseCustomLogger(log.New(io.Discard, "", 0)) // Turn-off random logging by Go stdlib
|
||||
|
||||
go func() {
|
||||
globalHTTPServerErrorCh <- httpServer.Start(GlobalContext)
|
||||
@@ -300,13 +275,24 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
||||
newObject = NewGatewayLayerWithLocker(newObject)
|
||||
|
||||
// Calls all New() for all sub-systems.
|
||||
initAllSubsystems()
|
||||
initAllSubsystems(GlobalContext)
|
||||
|
||||
// Once endpoints are finalized, initialize the new object api in safe mode.
|
||||
globalObjLayerMutex.Lock()
|
||||
globalObjectAPI = newObject
|
||||
globalObjLayerMutex.Unlock()
|
||||
|
||||
// Initialize server config.
|
||||
srvCfg := newServerConfig()
|
||||
|
||||
// Override any values from ENVs.
|
||||
lookupConfigs(srvCfg, newObject)
|
||||
|
||||
// hold the mutex lock before a new config is assigned.
|
||||
globalServerConfigMu.Lock()
|
||||
globalServerConfig = srvCfg
|
||||
globalServerConfigMu.Unlock()
|
||||
|
||||
go globalIAMSys.Init(GlobalContext, newObject, globalEtcdClient, globalRefreshIAMInterval)
|
||||
|
||||
if gatewayName == NASBackendGateway {
|
||||
|
||||
@@ -18,13 +18,17 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test printing Gateway common message.
|
||||
func TestPrintGatewayCommonMessage(t *testing.T) {
|
||||
obj, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
obj, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -39,7 +43,10 @@ func TestPrintGatewayCommonMessage(t *testing.T) {
|
||||
|
||||
// Test print gateway startup message.
|
||||
func TestPrintGatewayStartupMessage(t *testing.T) {
|
||||
obj, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
obj, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func (g *NAS) Name() string {
|
||||
// NewGatewayLayer returns nas gatewaylayer.
|
||||
func (g *NAS) NewGatewayLayer(creds madmin.Credentials) (minio.ObjectLayer, error) {
|
||||
var err error
|
||||
newObject, err := minio.NewFSObjectLayer(g.path)
|
||||
newObject, err := minio.NewFSObjectLayer(minio.GlobalContext, g.path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -245,6 +245,12 @@ func isAdminReq(r *http.Request) bool {
|
||||
return strings.HasPrefix(r.URL.Path, adminPathPrefix)
|
||||
}
|
||||
|
||||
// Check to allow access to the reserved "bucket" `/minio` for KMS
|
||||
// API requests.
|
||||
func isKMSReq(r *http.Request) bool {
|
||||
return strings.HasPrefix(r.URL.Path, kmsPathPrefix)
|
||||
}
|
||||
|
||||
// Supported Amz date headers.
|
||||
var amzDateHeaders = []string{
|
||||
// Do not chane this order, x-amz-date value should be
|
||||
@@ -428,12 +434,11 @@ func setRequestValidityHandler(h http.Handler) http.Handler {
|
||||
// For all other requests reject access to reserved buckets
|
||||
bucketName, _ := request2BucketObjectName(r)
|
||||
if isMinioReservedBucket(bucketName) || isMinioMetaBucket(bucketName) {
|
||||
if !guessIsRPCReq(r) && !guessIsBrowserReq(r) && !guessIsHealthCheckReq(r) && !guessIsMetricsReq(r) && !isAdminReq(r) {
|
||||
if !guessIsRPCReq(r) && !guessIsBrowserReq(r) && !guessIsHealthCheckReq(r) && !guessIsMetricsReq(r) && !isAdminReq(r) && !isKMSReq(r) {
|
||||
if ok {
|
||||
tc.funcName = "handler.ValidRequest"
|
||||
tc.responseRecorder.LogErrBody = true
|
||||
}
|
||||
|
||||
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrAllAccessDisabled), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -192,8 +192,6 @@ var (
|
||||
|
||||
globalEventNotifier *EventNotifier
|
||||
globalConfigTargetList *event.TargetList
|
||||
// globalEnvTargetList has list of targets configured via env.
|
||||
globalEnvTargetList *event.TargetList
|
||||
|
||||
globalBucketMetadataSys *BucketMetadataSys
|
||||
globalBucketMonitor *bandwidth.Monitor
|
||||
|
||||
+11
-20
@@ -18,8 +18,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -41,10 +41,9 @@ type apiConfig struct {
|
||||
listQuorum string
|
||||
corsAllowOrigins []string
|
||||
// total drives per erasure set across pools.
|
||||
totalDriveCount int
|
||||
replicationWorkers int
|
||||
replicationFailedWorkers int
|
||||
transitionWorkers int
|
||||
totalDriveCount int
|
||||
replicationPriority string
|
||||
transitionWorkers int
|
||||
|
||||
staleUploadsExpiry time.Duration
|
||||
staleUploadsCleanupInterval time.Duration
|
||||
@@ -56,7 +55,7 @@ type apiConfig struct {
|
||||
const cgroupLimitFile = "/sys/fs/cgroup/memory/memory.limit_in_bytes"
|
||||
|
||||
func cgroupLimit(limitFile string) (limit uint64) {
|
||||
buf, err := ioutil.ReadFile(limitFile)
|
||||
buf, err := os.ReadFile(limitFile)
|
||||
if err != nil {
|
||||
return 9223372036854771712
|
||||
}
|
||||
@@ -137,12 +136,11 @@ func (t *apiConfig) init(cfg api.Config, setDriveCounts []int) {
|
||||
t.requestsDeadline = cfg.RequestsDeadline
|
||||
t.listQuorum = cfg.ListQuorum
|
||||
if globalReplicationPool != nil &&
|
||||
cfg.ReplicationWorkers != t.replicationWorkers {
|
||||
globalReplicationPool.ResizeFailedWorkers(cfg.ReplicationFailedWorkers)
|
||||
globalReplicationPool.ResizeWorkers(cfg.ReplicationWorkers)
|
||||
cfg.ReplicationPriority != t.replicationPriority {
|
||||
globalReplicationPool.ResizeWorkerPriority(cfg.ReplicationPriority)
|
||||
}
|
||||
t.replicationFailedWorkers = cfg.ReplicationFailedWorkers
|
||||
t.replicationWorkers = cfg.ReplicationWorkers
|
||||
t.replicationPriority = cfg.ReplicationPriority
|
||||
|
||||
if globalTransitionState != nil && cfg.TransitionWorkers != t.transitionWorkers {
|
||||
globalTransitionState.UpdateWorkers(cfg.TransitionWorkers)
|
||||
}
|
||||
@@ -289,18 +287,11 @@ func maxClients(f http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *apiConfig) getReplicationFailedWorkers() int {
|
||||
func (t *apiConfig) getReplicationPriority() string {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
|
||||
return t.replicationFailedWorkers
|
||||
}
|
||||
|
||||
func (t *apiConfig) getReplicationWorkers() int {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
|
||||
return t.replicationWorkers
|
||||
return t.replicationPriority
|
||||
}
|
||||
|
||||
func (t *apiConfig) getTransitionWorkers() int {
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
@@ -305,7 +304,7 @@ func extractPostPolicyFormValues(ctx context.Context, form *multipart.Form) (fil
|
||||
b.WriteString(v)
|
||||
}
|
||||
fileSize = int64(b.Len())
|
||||
filePart = ioutil.NopCloser(b)
|
||||
filePart = io.NopCloser(b)
|
||||
return filePart, fileName, fileSize, formValues, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"os"
|
||||
@@ -34,7 +34,10 @@ import (
|
||||
|
||||
// Tests validate bucket LocationConstraint.
|
||||
func TestIsValidLocationContraint(t *testing.T) {
|
||||
obj, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
obj, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -45,13 +48,13 @@ func TestIsValidLocationContraint(t *testing.T) {
|
||||
|
||||
// Corrupted XML
|
||||
malformedReq := &http.Request{
|
||||
Body: ioutil.NopCloser(bytes.NewReader([]byte("<>"))),
|
||||
Body: io.NopCloser(bytes.NewReader([]byte("<>"))),
|
||||
ContentLength: int64(len("<>")),
|
||||
}
|
||||
|
||||
// Not an XML
|
||||
badRequest := &http.Request{
|
||||
Body: ioutil.NopCloser(bytes.NewReader([]byte("garbage"))),
|
||||
Body: io.NopCloser(bytes.NewReader([]byte("garbage"))),
|
||||
ContentLength: int64(len("garbage")),
|
||||
}
|
||||
|
||||
@@ -61,7 +64,7 @@ func TestIsValidLocationContraint(t *testing.T) {
|
||||
createBucketConfig.Location = location
|
||||
createBucketConfigBytes, _ := xml.Marshal(createBucketConfig)
|
||||
createBucketConfigBuffer := bytes.NewReader(createBucketConfigBytes)
|
||||
req.Body = ioutil.NopCloser(createBucketConfigBuffer)
|
||||
req.Body = io.NopCloser(createBucketConfigBuffer)
|
||||
req.ContentLength = int64(createBucketConfigBuffer.Len())
|
||||
return req
|
||||
}
|
||||
|
||||
+15
-14
@@ -163,7 +163,7 @@ func (sys *IAMSys) LoadServiceAccount(ctx context.Context, accessKey string) err
|
||||
|
||||
// initStore initializes IAM stores
|
||||
func (sys *IAMSys) initStore(objAPI ObjectLayer, etcdClient *etcd.Client) {
|
||||
if sys.ldapConfig.Enabled {
|
||||
if sys.ldapConfig.Enabled() {
|
||||
sys.SetUsersSysType(LDAPUsersSysType)
|
||||
}
|
||||
|
||||
@@ -222,8 +222,6 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
||||
s := globalServerConfig
|
||||
globalServerConfigMu.RUnlock()
|
||||
|
||||
ldapCfg := s[config.IdentityLDAPSubSys][config.Default]
|
||||
|
||||
var err error
|
||||
globalOpenIDConfig, err = openid.LookupConfig(s,
|
||||
NewGatewayHTTPTransport(), xhttp.DrainBody, globalSite.Region)
|
||||
@@ -232,7 +230,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
||||
}
|
||||
|
||||
// Initialize if LDAP is enabled
|
||||
globalLDAPConfig, err = xldap.Lookup(ldapCfg, globalRootCAs)
|
||||
globalLDAPConfig, err = xldap.Lookup(s, globalRootCAs)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to parse LDAP configuration: %w", err))
|
||||
}
|
||||
@@ -347,7 +345,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
||||
}
|
||||
}
|
||||
}()
|
||||
case sys.ldapConfig.Enabled:
|
||||
case sys.ldapConfig.Enabled():
|
||||
go func() {
|
||||
timer := time.NewTimer(refreshInterval)
|
||||
defer timer.Stop()
|
||||
@@ -774,7 +772,7 @@ func (sys *IAMSys) ListUsers(ctx context.Context) (map[string]madmin.UserInfo, e
|
||||
}
|
||||
|
||||
// ListLDAPUsers - list LDAP users which has
|
||||
func (sys *IAMSys) ListLDAPUsers() (map[string]madmin.UserInfo, error) {
|
||||
func (sys *IAMSys) ListLDAPUsers(ctx context.Context) (map[string]madmin.UserInfo, error) {
|
||||
if !sys.Initialized() {
|
||||
return nil, errServerNotInitialized
|
||||
}
|
||||
@@ -783,16 +781,19 @@ func (sys *IAMSys) ListLDAPUsers() (map[string]madmin.UserInfo, error) {
|
||||
return nil, errIAMActionNotAllowed
|
||||
}
|
||||
|
||||
<-sys.configLoaded
|
||||
|
||||
ldapUsers := make(map[string]madmin.UserInfo)
|
||||
for user, policy := range sys.store.GetUsersWithMappedPolicies() {
|
||||
ldapUsers[user] = madmin.UserInfo{
|
||||
PolicyName: policy,
|
||||
Status: madmin.AccountEnabled,
|
||||
select {
|
||||
case <-sys.configLoaded:
|
||||
ldapUsers := make(map[string]madmin.UserInfo)
|
||||
for user, policy := range sys.store.GetUsersWithMappedPolicies() {
|
||||
ldapUsers[user] = madmin.UserInfo{
|
||||
PolicyName: policy,
|
||||
Status: madmin.AccountEnabled,
|
||||
}
|
||||
}
|
||||
return ldapUsers, nil
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
return ldapUsers, nil
|
||||
}
|
||||
|
||||
// IsTempUser - returns if given key is a temporary user.
|
||||
|
||||
+25
-6
@@ -18,6 +18,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
@@ -29,7 +30,10 @@ import (
|
||||
)
|
||||
|
||||
func testAuthenticate(authType string, t *testing.T) {
|
||||
obj, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
obj, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -103,7 +107,10 @@ func getTokenString(accessKey, secretKey string) (string, error) {
|
||||
|
||||
// Tests web request authenticator.
|
||||
func TestWebRequestAuthenticate(t *testing.T) {
|
||||
obj, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
obj, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -157,7 +164,10 @@ func TestWebRequestAuthenticate(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkParseJWTStandardClaims(b *testing.B) {
|
||||
obj, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
obj, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -185,7 +195,10 @@ func BenchmarkParseJWTStandardClaims(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkParseJWTMapClaims(b *testing.B) {
|
||||
obj, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
obj, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -215,7 +228,10 @@ func BenchmarkParseJWTMapClaims(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkAuthenticateNode(b *testing.B) {
|
||||
obj, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
obj, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -244,7 +260,10 @@ func BenchmarkAuthenticateNode(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkAuthenticateWeb(b *testing.B) {
|
||||
obj, fsDir, err := prepareFS()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
obj, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,706 @@
|
||||
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/minio/kes"
|
||||
"github.com/minio/madmin-go"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
// KMSStatusHandler - GET /minio/kms/v1/status
|
||||
func (a kmsAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSStatus")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSStatusAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
stat, err := GlobalKMS.Stat(ctx)
|
||||
if err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
status := madmin.KMSStatus{
|
||||
Name: stat.Name,
|
||||
DefaultKeyID: stat.DefaultKey,
|
||||
Endpoints: make(map[string]madmin.ItemState, len(stat.Endpoints)),
|
||||
}
|
||||
for _, endpoint := range stat.Endpoints {
|
||||
status.Endpoints[endpoint] = madmin.ItemOnline // TODO(aead): Implement an online check for mTLS
|
||||
}
|
||||
|
||||
resp, err := json.Marshal(status)
|
||||
if err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseJSON(w, resp)
|
||||
}
|
||||
|
||||
// KMSMetricsHandler - POST /minio/kms/v1/metrics
|
||||
func (a kmsAPIHandlers) KMSMetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSMetrics")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSMetricsAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
metrics, err := GlobalKMS.Metrics(ctx)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if res, err := json.Marshal(metrics); err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
} else {
|
||||
writeSuccessResponseJSON(w, res)
|
||||
}
|
||||
}
|
||||
|
||||
// KMSAPIsHandler - POST /minio/kms/v1/apis
|
||||
func (a kmsAPIHandlers) KMSAPIsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSAPIs")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSAPIAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
manager, ok := GlobalKMS.(kms.StatusManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
apis, err := manager.APIs(ctx)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if res, err := json.Marshal(apis); err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
} else {
|
||||
writeSuccessResponseJSON(w, res)
|
||||
}
|
||||
}
|
||||
|
||||
type versionResponse struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// KMSVersionHandler - POST /minio/kms/v1/version
|
||||
func (a kmsAPIHandlers) KMSVersionHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSVersion")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSVersionAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
manager, ok := GlobalKMS.(kms.StatusManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
version, err := manager.Version(ctx)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
res := &versionResponse{Version: version}
|
||||
v, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseJSON(w, v)
|
||||
}
|
||||
|
||||
// KMSCreateKeyHandler - POST /minio/kms/v1/key/create?key-id=<master-key-id>
|
||||
func (a kmsAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSCreateKey")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSCreateKeyAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
manager, ok := GlobalKMS.(kms.KeyManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.CreateKey(ctx, r.Form.Get("key-id")); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
// KMSDeleteKeyHandler - DELETE /minio/kms/v1/key/delete?key-id=<master-key-id>
|
||||
func (a kmsAPIHandlers) KMSDeleteKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSDeleteKey")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeleteKeyAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.KeyManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
if err := manager.DeleteKey(ctx, r.Form.Get("key-id")); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
// KMSListKeysHandler - GET /minio/kms/v1/key/list?pattern=<pattern>
|
||||
func (a kmsAPIHandlers) KMSListKeysHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSListKeys")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListKeysAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.KeyManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
keys, err := manager.ListKeys(ctx, r.Form.Get("pattern"))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
values, err := keys.Values(0)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if res, err := json.Marshal(values); err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
} else {
|
||||
writeSuccessResponseJSON(w, res)
|
||||
}
|
||||
}
|
||||
|
||||
type importKeyRequest struct {
|
||||
Bytes string
|
||||
}
|
||||
|
||||
// KMSImportKeyHandler - POST /minio/kms/v1/key/import?key-id=<master-key-id>
|
||||
func (a kmsAPIHandlers) KMSImportKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSImportKey")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSImportKeyAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.KeyManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
var request importKeyRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if err := manager.ImportKey(ctx, r.Form.Get("key-id"), []byte(request.Bytes)); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
// KMSKeyStatusHandler - GET /minio/kms/v1/key/status?key-id=<master-key-id>
|
||||
func (a kmsAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSKeyStatus")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
stat, err := GlobalKMS.Stat(ctx)
|
||||
if err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
keyID := r.Form.Get("key-id")
|
||||
if keyID == "" {
|
||||
keyID = stat.DefaultKey
|
||||
}
|
||||
response := madmin.KMSKeyStatus{
|
||||
KeyID: keyID,
|
||||
}
|
||||
|
||||
kmsContext := kms.Context{"MinIO admin API": "KMSKeyStatusHandler"} // Context for a test key operation
|
||||
// 1. Generate a new key using the KMS.
|
||||
key, err := GlobalKMS.GenerateKey(ctx, keyID, kmsContext)
|
||||
if err != nil {
|
||||
response.EncryptionErr = err.Error()
|
||||
resp, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseJSON(w, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Verify that we can indeed decrypt the (encrypted) key
|
||||
decryptedKey, err := GlobalKMS.DecryptKey(key.KeyID, key.Ciphertext, kmsContext)
|
||||
if err != nil {
|
||||
response.DecryptionErr = err.Error()
|
||||
resp, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseJSON(w, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Compare generated key with decrypted key
|
||||
if subtle.ConstantTimeCompare(key.Plaintext, decryptedKey) != 1 {
|
||||
response.DecryptionErr = "The generated and the decrypted data key do not match"
|
||||
resp, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseJSON(w, resp)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseJSON(w, resp)
|
||||
}
|
||||
|
||||
// KMSDescribePolicyHandler - GET /minio/kms/v1/policy/describe?policy=<policy>
|
||||
func (a kmsAPIHandlers) KMSDescribePolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSDescribePolicy")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribePolicyAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
policy, err := manager.DescribePolicy(ctx, r.Form.Get("policy"))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
p, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseJSON(w, p)
|
||||
}
|
||||
|
||||
type assignPolicyRequest struct {
|
||||
Identity string
|
||||
}
|
||||
|
||||
// KMSAssignPolicyHandler - POST /minio/kms/v1/policy/assign?policy=<policy>
|
||||
func (a kmsAPIHandlers) KMSAssignPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSAssignPolicy")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSAssignPolicyAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
var request assignPolicyRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
err := manager.AssignPolicy(ctx, r.Form.Get("policy"), request.Identity)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
// KMSSetPolicyHandler - POST /minio/kms/v1/policy/policy?policy=<policy>
|
||||
func (a kmsAPIHandlers) KMSSetPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSSetPolicy")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSSetPolicyAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
var policy kes.Policy
|
||||
if err := json.NewDecoder(r.Body).Decode(&policy); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if err := manager.SetPolicy(ctx, r.Form.Get("policy"), &policy); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
// KMSDeletePolicyHandler - DELETE /minio/kms/v1/policy/delete?policy=<policy>
|
||||
func (a kmsAPIHandlers) KMSDeletePolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSDeletePolicy")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeletePolicyAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
if err := manager.DeletePolicy(ctx, r.Form.Get("policy")); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
// KMSListPoliciesHandler - GET /minio/kms/v1/policy/list?pattern=<pattern>
|
||||
func (a kmsAPIHandlers) KMSListPoliciesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSListPolicies")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListPoliciesAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
policies, err := manager.ListPolicies(ctx, r.Form.Get("pattern"))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
values, err := policies.Values(0)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if res, err := json.Marshal(values); err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
} else {
|
||||
writeSuccessResponseJSON(w, res)
|
||||
}
|
||||
}
|
||||
|
||||
// KMSGetPolicyHandler - GET /minio/kms/v1/policy/get?policy=<policy>
|
||||
func (a kmsAPIHandlers) KMSGetPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSGetPolicy")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSGetPolicyAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
policy, err := manager.GetPolicy(ctx, r.Form.Get("policy"))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if p, err := json.Marshal(policy); err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
} else {
|
||||
writeSuccessResponseJSON(w, p)
|
||||
}
|
||||
}
|
||||
|
||||
// KMSDescribeIdentityHandler - GET /minio/kms/v1/identity/describe?identity=<identity>
|
||||
func (a kmsAPIHandlers) KMSDescribeIdentityHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSDescribeIdentity")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribeIdentityAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.IdentityManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
identity, err := manager.DescribeIdentity(ctx, r.Form.Get("identity"))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
i, err := json.Marshal(identity)
|
||||
if err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseJSON(w, i)
|
||||
}
|
||||
|
||||
type describeSelfIdentityResponse struct {
|
||||
Policy *kes.Policy `json:"policy"`
|
||||
PolicyName string `json:"policyName"`
|
||||
Identity string `json:"identity"`
|
||||
IsAdmin bool `json:"isAdmin"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
}
|
||||
|
||||
// KMSDescribeSelfIdentityHandler - GET /minio/kms/v1/identity/describe-self
|
||||
func (a kmsAPIHandlers) KMSDescribeSelfIdentityHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSDescribeSelfIdentity")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribeSelfIdentityAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.IdentityManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
identity, policy, err := manager.DescribeSelfIdentity(ctx)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
res := &describeSelfIdentityResponse{
|
||||
Policy: policy,
|
||||
PolicyName: identity.Policy,
|
||||
Identity: identity.Identity.String(),
|
||||
IsAdmin: identity.IsAdmin,
|
||||
CreatedAt: identity.CreatedAt,
|
||||
CreatedBy: identity.CreatedBy.String(),
|
||||
}
|
||||
i, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseJSON(w, i)
|
||||
}
|
||||
|
||||
// KMSDeleteIdentityHandler - DELETE /minio/kms/v1/identity/delete?identity=<identity>
|
||||
func (a kmsAPIHandlers) KMSDeleteIdentityHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSDeleteIdentity")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeleteIdentityAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.IdentityManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.DeleteIdentity(ctx, r.Form.Get("policy")); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
// KMSListIdentitiesHandler - GET /minio/kms/v1/identity/list?pattern=<pattern>
|
||||
func (a kmsAPIHandlers) KMSListIdentitiesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSListIdentities")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListIdentitiesAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if GlobalKMS == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||
return
|
||||
}
|
||||
manager, ok := GlobalKMS.(kms.IdentityManager)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
identities, err := manager.ListIdentities(ctx, r.Form.Get("pattern"))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
values, err := identities.Values(0)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if res, err := json.Marshal(values); err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||
} else {
|
||||
writeSuccessResponseJSON(w, res)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
"github.com/klauspost/compress/gzip"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
kmsPathPrefix = minioReservedBucketPath + "/kms"
|
||||
kmsAPIVersion = "v1"
|
||||
kmsAPIVersionPrefix = SlashSeparator + kmsAPIVersion
|
||||
)
|
||||
|
||||
type kmsAPIHandlers struct{}
|
||||
|
||||
// registerKMSRouter - Registers KMS APIs
|
||||
func registerKMSRouter(router *mux.Router) {
|
||||
kmsAPI := kmsAPIHandlers{}
|
||||
kmsRouter := router.PathPrefix(kmsPathPrefix).Subrouter()
|
||||
|
||||
KMSVersions := []string{
|
||||
kmsAPIVersionPrefix,
|
||||
}
|
||||
|
||||
gz, err := gzhttp.NewWrapper(gzhttp.MinSize(1000), gzhttp.CompressionLevel(gzip.BestSpeed))
|
||||
if err != nil {
|
||||
// Static params, so this is very unlikely.
|
||||
logger.Fatal(err, "Unable to initialize server")
|
||||
}
|
||||
|
||||
for _, version := range KMSVersions {
|
||||
// KMS Status APIs
|
||||
kmsRouter.Methods(http.MethodGet).Path(version + "/status").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSStatusHandler)))
|
||||
kmsRouter.Methods(http.MethodGet).Path(version + "/metrics").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSMetricsHandler)))
|
||||
kmsRouter.Methods(http.MethodGet).Path(version + "/apis").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSAPIsHandler)))
|
||||
kmsRouter.Methods(http.MethodGet).Path(version + "/version").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSVersionHandler)))
|
||||
// KMS Key APIs
|
||||
kmsRouter.Methods(http.MethodPost).Path(version+"/key/create").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSCreateKeyHandler))).Queries("key-id", "{key-id:.*}")
|
||||
kmsRouter.Methods(http.MethodPost).Path(version+"/key/import").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSImportKeyHandler))).Queries("key-id", "{key-id:.*}")
|
||||
kmsRouter.Methods(http.MethodDelete).Path(version+"/key/delete").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDeleteKeyHandler))).Queries("key-id", "{key-id:.*}")
|
||||
kmsRouter.Methods(http.MethodGet).Path(version+"/key/list").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSListKeysHandler))).Queries("pattern", "{pattern:.*}")
|
||||
kmsRouter.Methods(http.MethodGet).Path(version + "/key/status").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSKeyStatusHandler)))
|
||||
|
||||
// KMS Policy APIs
|
||||
kmsRouter.Methods(http.MethodPost).Path(version+"/policy/set").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSSetPolicyHandler))).Queries("policy", "{policy:.*}")
|
||||
kmsRouter.Methods(http.MethodPost).Path(version+"/policy/assign").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSAssignPolicyHandler))).Queries("policy", "{policy:.*}")
|
||||
kmsRouter.Methods(http.MethodGet).Path(version+"/policy/describe").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDescribePolicyHandler))).Queries("policy", "{policy:.*}")
|
||||
kmsRouter.Methods(http.MethodGet).Path(version+"/policy/get").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSGetPolicyHandler))).Queries("policy", "{policy:.*}")
|
||||
kmsRouter.Methods(http.MethodDelete).Path(version+"/policy/delete").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDeletePolicyHandler))).Queries("policy", "{policy:.*}")
|
||||
kmsRouter.Methods(http.MethodGet).Path(version+"/policy/list").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSListPoliciesHandler))).Queries("pattern", "{pattern:.*}")
|
||||
|
||||
// KMS Identity APIs
|
||||
kmsRouter.Methods(http.MethodGet).Path(version+"/identity/describe").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDescribeIdentityHandler))).Queries("identity", "{identity:.*}")
|
||||
kmsRouter.Methods(http.MethodGet).Path(version + "/identity/describe-self").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDescribeSelfIdentityHandler)))
|
||||
kmsRouter.Methods(http.MethodDelete).Path(version+"/identity/delete").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDeleteIdentityHandler))).Queries("identity", "{identity:.*}")
|
||||
kmsRouter.Methods(http.MethodGet).Path(version+"/identity/list").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSListIdentitiesHandler))).Queries("pattern", "{pattern:.*}")
|
||||
}
|
||||
|
||||
// If none of the routes match add default error handler routes
|
||||
kmsRouter.NotFoundHandler = httpTraceAll(errorResponseHandler)
|
||||
kmsRouter.MethodNotAllowedHandler = httpTraceAll(methodNotAllowedHandler("KMS"))
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
@@ -27,8 +28,8 @@ import (
|
||||
)
|
||||
|
||||
// Helper function to create a lock server for testing
|
||||
func createLockTestServer(t *testing.T) (string, *lockRESTServer, string) {
|
||||
obj, fsDir, err := prepareFS()
|
||||
func createLockTestServer(ctx context.Context, t *testing.T) (string, *lockRESTServer, string) {
|
||||
obj, fsDir, err := prepareFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -52,7 +53,10 @@ func createLockTestServer(t *testing.T) (string, *lockRESTServer, string) {
|
||||
|
||||
// Test function to remove lock entries from map based on name & uid combination
|
||||
func TestLockRpcServerRemoveEntry(t *testing.T) {
|
||||
testPath, locker, _ := createLockTestServer(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
testPath, locker, _ := createLockTestServer(ctx, t)
|
||||
defer os.RemoveAll(testPath)
|
||||
|
||||
lockRequesterInfo1 := lockRequesterInfo{
|
||||
|
||||
@@ -20,7 +20,7 @@ package cmd
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
@@ -49,7 +49,7 @@ func BenchmarkLockArgs(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader(argBytes))
|
||||
req.Body = io.NopCloser(bytes.NewReader(argBytes))
|
||||
getLockArgs(req)
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func BenchmarkLockArgsOld(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader([]byte(`obj.txt`)))
|
||||
req.Body = io.NopCloser(bytes.NewReader([]byte(`obj.txt`)))
|
||||
getLockArgsOld(req)
|
||||
}
|
||||
}
|
||||
|
||||
+22
-1
@@ -23,11 +23,13 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio/internal/color"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/console"
|
||||
"github.com/minio/pkg/trie"
|
||||
"github.com/minio/pkg/words"
|
||||
@@ -189,8 +191,27 @@ func Main(args []string) {
|
||||
// Set the minio app name.
|
||||
appName := filepath.Base(args[0])
|
||||
|
||||
if os.Getenv("_MINIO_DEBUG_NO_EXIT") != "" {
|
||||
freeze := func(_ int) {
|
||||
// Infinite blocking op
|
||||
<-make(chan struct{})
|
||||
}
|
||||
|
||||
// Override the logger os.Exit()
|
||||
logger.ExitFunc = freeze
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Println("panic:", err)
|
||||
fmt.Println("")
|
||||
fmt.Println(string(debug.Stack()))
|
||||
}
|
||||
freeze(-1)
|
||||
}()
|
||||
}
|
||||
|
||||
// Run the app - exit on error.
|
||||
if err := newApp(appName).Run(args); err != nil {
|
||||
os.Exit(1)
|
||||
os.Exit(1) //nolint:gocritic
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user