mirror of
https://github.com/pgsty/minio.git
synced 2026-08-01 16:40:43 +03:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c6f0987dc | |||
| 5f64658faa | |||
| ce183cb2b4 | |||
| b3bac73c0f | |||
| e726d8ff0f | |||
| f4230777b3 | |||
| 380233d646 | |||
| d592bc0c1c | |||
| 0d0b0aa599 | |||
| b433bf14ba | |||
| cf371da346 | |||
| 107d951893 | |||
| 22c53b1c70 | |||
| 88926ad8e9 | |||
| 32d04091a2 | |||
| b6d4a77b94 | |||
| be84a4fd68 | |||
| 2ec1f404ac | |||
| 2040559f71 | |||
| ca0ce4c6ef | |||
| 757cf413cb | |||
| b35acb3dbc | |||
| e404abf103 | |||
| f7ff19cb18 | |||
| f736702da8 | |||
| 91faaa1387 | |||
| 68a9f521d5 | |||
| f365a98029 | |||
| 47bbc272df | |||
| aebac90013 | |||
| 7ca4ba77c4 | |||
| 13512170b5 | |||
| 154fcaeb56 | |||
| 722118386d | |||
| 709612cb37 | |||
| b35d083872 | |||
| 5e7b243bde | |||
| f8f9fc77ac | |||
| 499531f0b5 | |||
| 3c2141513f | |||
| 602f6a9ad0 | |||
| 22c5a5b91b | |||
| 41f508765d | |||
| 7dccd1f589 | |||
| 55ff598b23 | |||
| a22ce4550c | |||
| 168ae81b1f | |||
| 5f6a25cdd0 |
@@ -21,8 +21,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.4
|
||||
check-latest: true
|
||||
go-version: 1.22.5
|
||||
- name: Get official govulncheck
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
shell: bash
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
extend-exclude = [
|
||||
".git/",
|
||||
"docs/",
|
||||
"CREDITS",
|
||||
]
|
||||
ignore-hidden = false
|
||||
|
||||
|
||||
@@ -6334,6 +6334,203 @@ SOFTWARE.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/go-ini/ini
|
||||
https://github.com/go-ini/ini
|
||||
----------------------------------------------------------------
|
||||
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:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
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
|
||||
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 2014 Unknwon
|
||||
|
||||
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/go-jose/go-jose/v4
|
||||
https://github.com/go-jose/go-jose/v4
|
||||
----------------------------------------------------------------
|
||||
@@ -11223,33 +11420,29 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
github.com/gorilla/websocket
|
||||
https://github.com/gorilla/websocket
|
||||
----------------------------------------------------------------
|
||||
Copyright (c) 2023 The Gorilla Authors. All rights reserved.
|
||||
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/hashicorp/errwrap
|
||||
@@ -16306,6 +16499,12 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/mattn/go-localereader
|
||||
https://github.com/mattn/go-localereader
|
||||
----------------------------------------------------------------
|
||||
All rights reserved proprietary
|
||||
================================================================
|
||||
|
||||
github.com/mattn/go-runewidth
|
||||
https://github.com/mattn/go-runewidth
|
||||
----------------------------------------------------------------
|
||||
@@ -16602,6 +16801,12 @@ SOFTWARE.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/minio/colorjson
|
||||
https://github.com/minio/colorjson
|
||||
----------------------------------------------------------------
|
||||
All rights reserved proprietary
|
||||
================================================================
|
||||
|
||||
github.com/minio/console
|
||||
https://github.com/minio/console
|
||||
----------------------------------------------------------------
|
||||
@@ -17269,6 +17474,12 @@ For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/minio/csvparser
|
||||
https://github.com/minio/csvparser
|
||||
----------------------------------------------------------------
|
||||
All rights reserved proprietary
|
||||
================================================================
|
||||
|
||||
github.com/minio/dnscache
|
||||
https://github.com/minio/dnscache
|
||||
----------------------------------------------------------------
|
||||
@@ -17964,6 +18175,12 @@ For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/minio/filepath
|
||||
https://github.com/minio/filepath
|
||||
----------------------------------------------------------------
|
||||
All rights reserved proprietary
|
||||
================================================================
|
||||
|
||||
github.com/minio/highwayhash
|
||||
https://github.com/minio/highwayhash
|
||||
----------------------------------------------------------------
|
||||
@@ -24119,6 +24336,43 @@ SOFTWARE.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/munnerz/goautoneg
|
||||
https://github.com/munnerz/goautoneg
|
||||
----------------------------------------------------------------
|
||||
Copyright (c) 2011, Open Knowledge Foundation Ltd.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
Neither the name of the Open Knowledge Foundation Ltd. nor the
|
||||
names of its contributors may be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/nats-io/jwt/v2
|
||||
https://github.com/nats-io/jwt/v2
|
||||
----------------------------------------------------------------
|
||||
@@ -33171,203 +33425,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================
|
||||
|
||||
gopkg.in/ini.v1
|
||||
https://gopkg.in/ini.v1
|
||||
----------------------------------------------------------------
|
||||
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:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
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
|
||||
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 2014 Unknwon
|
||||
|
||||
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.
|
||||
|
||||
================================================================
|
||||
|
||||
gopkg.in/yaml.v2
|
||||
https://gopkg.in/yaml.v2
|
||||
----------------------------------------------------------------
|
||||
|
||||
@@ -34,11 +34,14 @@ verifiers: lint check-gen
|
||||
|
||||
check-gen: ## check for updated autogenerated files
|
||||
@go generate ./... >/dev/null
|
||||
@go mod tidy -compat=1.21
|
||||
@(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false)
|
||||
@(! git diff --name-only | grep 'go.sum') || (echo "Non-committed changes in auto-generated go.sum is detected, please commit them to proceed." && false)
|
||||
|
||||
lint: getdeps ## runs golangci-lint suite of linters
|
||||
@echo "Running $@ check"
|
||||
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml
|
||||
@command typos && typos ./ || echo "typos binary is not found.. skipping.."
|
||||
|
||||
lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
|
||||
@echo "Running $@ check"
|
||||
@@ -86,9 +89,9 @@ test-race: verifiers build ## builds minio, runs linters, tests (race)
|
||||
|
||||
test-iam: install-race ## verify IAM (external IDP, etcd backends)
|
||||
@echo "Running tests for IAM (external IDP, etcd backends)"
|
||||
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue,dev -v -run TestIAM* ./cmd
|
||||
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -timeout 15m -tags kqueue,dev -v -run TestIAM* ./cmd
|
||||
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
||||
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue,dev -v -run TestIAM* ./cmd
|
||||
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -timeout 15m -race -tags kqueue,dev -v -run TestIAM* ./cmd
|
||||
|
||||
test-iam-ldap-upgrade-import: install-race ## verify IAM (external LDAP IDP)
|
||||
@echo "Running upgrade tests for IAM (LDAP backend)"
|
||||
|
||||
@@ -216,6 +216,12 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
||||
Description: err.Error(),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
case errors.Is(err, errTierInvalidConfig):
|
||||
apiErr = APIError{
|
||||
Code: "XMinioAdminTierInvalidConfig",
|
||||
Description: err.Error(),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
default:
|
||||
apiErr = errorCodes.ToAPIErrWithErr(toAdminAPIErrCode(ctx, err), err)
|
||||
}
|
||||
|
||||
@@ -479,3 +479,179 @@ func (a adminAPIHandlers) ListAccessKeysLDAP(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
writeSuccessResponseJSON(w, encryptedData)
|
||||
}
|
||||
|
||||
// ListAccessKeysLDAPBulk - GET /minio/admin/v3/idp/ldap/list-access-keys-bulk
|
||||
func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil || globalNotificationSys == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||
if s3Err != ErrNone {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
dnList := r.Form["userDNs"]
|
||||
isAll := r.Form.Get("all") == "true"
|
||||
onlySelf := !isAll && len(dnList) == 0
|
||||
|
||||
if isAll && len(dnList) > 0 {
|
||||
// This should be checked on client side, so return generic error
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Empty DN list and not self, list access keys for all users
|
||||
if isAll {
|
||||
if !globalIAMSys.IsAllowed(policy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: policy.ListUsersAdminAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
}) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
} else if len(dnList) == 1 {
|
||||
var dn string
|
||||
foundResult, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(dnList[0])
|
||||
if err == nil {
|
||||
dn = foundResult.NormDN
|
||||
}
|
||||
if dn == cred.ParentUser || dnList[0] == cred.ParentUser {
|
||||
onlySelf = true
|
||||
}
|
||||
}
|
||||
|
||||
if !globalIAMSys.IsAllowed(policy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: policy.ListServiceAccountsAdminAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
DenyOnly: onlySelf,
|
||||
}) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if onlySelf && len(dnList) == 0 {
|
||||
selfDN := cred.AccessKey
|
||||
if cred.ParentUser != "" {
|
||||
selfDN = cred.ParentUser
|
||||
}
|
||||
dnList = append(dnList, selfDN)
|
||||
}
|
||||
|
||||
accessKeyMap := make(map[string]madmin.ListAccessKeysLDAPResp)
|
||||
if isAll {
|
||||
ldapUsers, err := globalIAMSys.ListLDAPUsers(ctx)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
for user := range ldapUsers {
|
||||
accessKeyMap[user] = madmin.ListAccessKeysLDAPResp{}
|
||||
}
|
||||
} else {
|
||||
for _, userDN := range dnList {
|
||||
// Validate the userDN
|
||||
foundResult, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(userDN)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if foundResult == nil {
|
||||
continue
|
||||
}
|
||||
accessKeyMap[foundResult.NormDN] = madmin.ListAccessKeysLDAPResp{}
|
||||
}
|
||||
}
|
||||
|
||||
listType := r.Form.Get("listType")
|
||||
var listSTSKeys, listServiceAccounts bool
|
||||
switch listType {
|
||||
case madmin.AccessKeyListUsersOnly:
|
||||
listSTSKeys = false
|
||||
listServiceAccounts = false
|
||||
case madmin.AccessKeyListSTSOnly:
|
||||
listSTSKeys = true
|
||||
listServiceAccounts = false
|
||||
case madmin.AccessKeyListSvcaccOnly:
|
||||
listSTSKeys = false
|
||||
listServiceAccounts = true
|
||||
case madmin.AccessKeyListAll:
|
||||
listSTSKeys = true
|
||||
listServiceAccounts = true
|
||||
default:
|
||||
err := errors.New("invalid list type")
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrInvalidRequest, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
for dn, accessKeys := range accessKeyMap {
|
||||
if listSTSKeys {
|
||||
stsKeys, err := globalIAMSys.ListSTSAccounts(ctx, dn)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
for _, sts := range stsKeys {
|
||||
expiryTime := sts.Expiration
|
||||
accessKeys.STSKeys = append(accessKeys.STSKeys, madmin.ServiceAccountInfo{
|
||||
AccessKey: sts.AccessKey,
|
||||
Expiration: &expiryTime,
|
||||
})
|
||||
}
|
||||
// if only STS keys, skip if user has no STS keys
|
||||
if !listServiceAccounts && len(stsKeys) == 0 {
|
||||
delete(accessKeyMap, dn)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if listServiceAccounts {
|
||||
serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, dn)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
for _, svc := range serviceAccounts {
|
||||
expiryTime := svc.Expiration
|
||||
accessKeys.ServiceAccounts = append(accessKeys.ServiceAccounts, madmin.ServiceAccountInfo{
|
||||
AccessKey: svc.AccessKey,
|
||||
Expiration: &expiryTime,
|
||||
})
|
||||
}
|
||||
// if only service accounts, skip if user has no service accounts
|
||||
if !listSTSKeys && len(serviceAccounts) == 0 {
|
||||
delete(accessKeyMap, dn)
|
||||
continue
|
||||
}
|
||||
}
|
||||
accessKeyMap[dn] = accessKeys
|
||||
}
|
||||
|
||||
data, err := json.Marshal(accessKeyMap)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessResponseJSON(w, encryptedData)
|
||||
}
|
||||
|
||||
@@ -374,6 +374,7 @@ func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request)
|
||||
globalNotificationSys.StopRebalance(r.Context())
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
adminLogIf(ctx, pools.saveRebalanceStats(GlobalContext, 0, rebalSaveStoppedAt))
|
||||
globalNotificationSys.LoadRebalanceMeta(ctx, false)
|
||||
}
|
||||
|
||||
func proxyDecommissionRequest(ctx context.Context, defaultEndPoint Endpoint, w http.ResponseWriter, r *http.Request) (proxy bool) {
|
||||
|
||||
@@ -120,9 +120,12 @@ func (s *TestSuiteIAM) TestDeleteUserRace(c *check) {
|
||||
c.Fatalf("Unable to set user: %v", err)
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: accessKey,
|
||||
}
|
||||
if _, err := s.adm.AttachPolicy(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
accessKeys[i] = accessKey
|
||||
|
||||
@@ -239,9 +239,12 @@ func (s *TestSuiteIAM) TestUserCreate(c *check) {
|
||||
c.Assert(v.Status, madmin.AccountEnabled)
|
||||
|
||||
// 3. Associate policy and check that user can access
|
||||
err = s.adm.SetPolicy(ctx, "readwrite", accessKey, false)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{"readwrite"},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("unable to set policy: %v", err)
|
||||
c.Fatalf("unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
client := s.getUserClient(c, accessKey, secretKey, "")
|
||||
@@ -348,9 +351,12 @@ func (s *TestSuiteIAM) TestUserPolicyEscalationBug(c *check) {
|
||||
if err != nil {
|
||||
c.Fatalf("policy add error: %v", err)
|
||||
}
|
||||
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("unable to attach policy: %v", err)
|
||||
}
|
||||
// 2.3 check user has access to bucket
|
||||
c.mustListObjects(ctx, uClient, bucket)
|
||||
@@ -470,9 +476,12 @@ func (s *TestSuiteIAM) TestAddServiceAccountPerms(c *check) {
|
||||
c.mustNotListObjects(ctx, uClient, "testbucket")
|
||||
|
||||
// 3.2 associate policy to user
|
||||
err = s.adm.SetPolicy(ctx, policy1, accessKey, false)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy1},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
admClnt := s.getAdminClient(c, accessKey, secretKey, "")
|
||||
@@ -490,10 +499,22 @@ func (s *TestSuiteIAM) TestAddServiceAccountPerms(c *check) {
|
||||
c.Fatalf("policy was missing!")
|
||||
}
|
||||
|
||||
// 3.2 associate policy to user
|
||||
err = s.adm.SetPolicy(ctx, policy2, accessKey, false)
|
||||
// Detach policy1 to set up for policy2
|
||||
_, err = s.adm.DetachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy1},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("unable to detach policy: %v", err)
|
||||
}
|
||||
|
||||
// 3.2 associate policy to user
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy2},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
// 3.3 check user can create service account implicitly.
|
||||
@@ -571,9 +592,12 @@ func (s *TestSuiteIAM) TestPolicyCreate(c *check) {
|
||||
c.mustNotListObjects(ctx, uClient, bucket)
|
||||
|
||||
// 3.2 associate policy to user
|
||||
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("unable to attach policy: %v", err)
|
||||
}
|
||||
// 3.3 check user has access to bucket
|
||||
c.mustListObjects(ctx, uClient, bucket)
|
||||
@@ -726,9 +750,12 @@ func (s *TestSuiteIAM) TestGroupAddRemove(c *check) {
|
||||
c.mustNotListObjects(ctx, uClient, bucket)
|
||||
|
||||
// 3. Associate policy to group and check user got access.
|
||||
err = s.adm.SetPolicy(ctx, policy, group, true)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
Group: group,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("unable to attach policy: %v", err)
|
||||
}
|
||||
// 3.1 check user has access to bucket
|
||||
c.mustListObjects(ctx, uClient, bucket)
|
||||
@@ -871,9 +898,12 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByUser(c *check) {
|
||||
c.Fatalf("Unable to set user: %v", err)
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
// Create an madmin client with user creds
|
||||
@@ -952,9 +982,12 @@ func (s *TestSuiteIAM) TestServiceAccountDurationSecondsCondition(c *check) {
|
||||
c.Fatalf("Unable to set user: %v", err)
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
// Create an madmin client with user creds
|
||||
@@ -1031,9 +1064,12 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
|
||||
c.Fatalf("Unable to set user: %v", err)
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
// 1. Create a service account for the user
|
||||
|
||||
+6
-2
@@ -301,8 +301,9 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
||||
// LDAP specific service accounts ops
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp/ldap/add-service-account").HandlerFunc(adminMiddleware(adminAPI.AddServiceAccountLDAP))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp/ldap/list-access-keys").
|
||||
HandlerFunc(adminMiddleware(adminAPI.ListAccessKeysLDAP)).
|
||||
Queries("userDN", "{userDN:.*}", "listType", "{listType:.*}")
|
||||
HandlerFunc(adminMiddleware(adminAPI.ListAccessKeysLDAP)).Queries("userDN", "{userDN:.*}", "listType", "{listType:.*}")
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp/ldap/list-access-keys-bulk").
|
||||
HandlerFunc(adminMiddleware(adminAPI.ListAccessKeysLDAPBulk)).Queries("listType", "{listType:.*}")
|
||||
|
||||
// LDAP IAM operations
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListLDAPPolicyMappingEntities))
|
||||
@@ -340,6 +341,9 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-jobs").HandlerFunc(
|
||||
adminMiddleware(adminAPI.ListBatchJobs))
|
||||
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/status-job").HandlerFunc(
|
||||
adminMiddleware(adminAPI.BatchJobStatus))
|
||||
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/describe-job").HandlerFunc(
|
||||
adminMiddleware(adminAPI.DescribeBatchJob))
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/cancel-job").HandlerFunc(
|
||||
|
||||
+13
-3
@@ -946,10 +946,20 @@ func writeSuccessResponseHeadersOnly(w http.ResponseWriter) {
|
||||
|
||||
// writeErrorResponse writes error headers
|
||||
func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
|
||||
if err.HTTPStatusCode == http.StatusServiceUnavailable {
|
||||
// Set retry-after header to indicate user-agents to retry request after 120secs.
|
||||
switch err.HTTPStatusCode {
|
||||
case http.StatusServiceUnavailable:
|
||||
// Set retry-after header to indicate user-agents to retry request after 60 seconds.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||
w.Header().Set(xhttp.RetryAfter, "120")
|
||||
w.Header().Set(xhttp.RetryAfter, "60")
|
||||
case http.StatusTooManyRequests:
|
||||
_, deadline := globalAPIConfig.getRequestsPool()
|
||||
if deadline <= 0 {
|
||||
// Set retry-after header to indicate user-agents to retry request after 10 seconds.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||
w.Header().Set(xhttp.RetryAfter, "10")
|
||||
} else {
|
||||
w.Header().Set(xhttp.RetryAfter, strconv.Itoa(int(deadline.Seconds())))
|
||||
}
|
||||
}
|
||||
|
||||
switch err.Code {
|
||||
|
||||
@@ -88,6 +88,8 @@ type healingTracker struct {
|
||||
|
||||
ItemsSkipped uint64
|
||||
BytesSkipped uint64
|
||||
|
||||
RetryAttempts uint64
|
||||
// Add future tracking capabilities
|
||||
// Be sure that they are included in toHealingDisk
|
||||
}
|
||||
@@ -363,7 +365,7 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
||||
localDrives := cloneDrives(globalLocalDrives)
|
||||
globalLocalDrivesMu.RUnlock()
|
||||
for _, disk := range localDrives {
|
||||
_, err := disk.GetDiskID()
|
||||
_, err := disk.DiskInfo(context.Background(), DiskInfoOptions{})
|
||||
if errors.Is(err, errUnformattedDisk) {
|
||||
disksToHeal = append(disksToHeal, disk.Endpoint())
|
||||
continue
|
||||
@@ -382,6 +384,8 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
||||
|
||||
var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
||||
|
||||
var errRetryHealing = errors.New("some items failed to heal, we will retry healing this drive again")
|
||||
|
||||
func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error {
|
||||
poolIdx, setIdx := endpoint.PoolIdx, endpoint.SetIdx
|
||||
disk := getStorageViaEndpoint(endpoint)
|
||||
@@ -389,6 +393,17 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
||||
return fmt.Errorf("Unexpected error disk must be initialized by now after formatting: %s", endpoint)
|
||||
}
|
||||
|
||||
_, err := disk.DiskInfo(ctx, DiskInfoOptions{})
|
||||
if err != nil {
|
||||
if errors.Is(err, errDriveIsRoot) {
|
||||
// This is a root drive, ignore and move on
|
||||
return nil
|
||||
}
|
||||
if !errors.Is(err, errUnformattedDisk) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent parallel erasure set healing
|
||||
locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%d/%d", poolIdx, setIdx))
|
||||
lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout)
|
||||
@@ -451,8 +466,27 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
||||
return err
|
||||
}
|
||||
|
||||
healingLogEvent(ctx, "Healing of drive '%s' is finished (healed: %d, skipped: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsSkipped, tracker.ItemsFailed)
|
||||
// if objects have failed healing, we attempt a retry to heal the drive upto 3 times before giving up.
|
||||
if tracker.ItemsFailed > 0 && tracker.RetryAttempts < 4 {
|
||||
tracker.RetryAttempts++
|
||||
bugLogIf(ctx, tracker.update(ctx))
|
||||
|
||||
healingLogEvent(ctx, "Healing of drive '%s' is incomplete, retrying %s time (healed: %d, skipped: %d, failed: %d).", disk,
|
||||
humanize.Ordinal(int(tracker.RetryAttempts)), tracker.ItemsHealed, tracker.ItemsSkipped, tracker.ItemsFailed)
|
||||
return errRetryHealing
|
||||
}
|
||||
|
||||
if tracker.ItemsFailed > 0 {
|
||||
healingLogEvent(ctx, "Healing of drive '%s' is incomplete, retried %d times (healed: %d, skipped: %d, failed: %d).", disk,
|
||||
tracker.RetryAttempts-1, tracker.ItemsHealed, tracker.ItemsSkipped, tracker.ItemsFailed)
|
||||
} else {
|
||||
if tracker.RetryAttempts > 0 {
|
||||
healingLogEvent(ctx, "Healing of drive '%s' is complete, retried %d times (healed: %d, skipped: %d).", disk,
|
||||
tracker.RetryAttempts-1, tracker.ItemsHealed, tracker.ItemsSkipped)
|
||||
} else {
|
||||
healingLogEvent(ctx, "Healing of drive '%s' is finished (healed: %d, skipped: %d).", disk, tracker.ItemsHealed, tracker.ItemsSkipped)
|
||||
}
|
||||
}
|
||||
if serverDebugLog {
|
||||
tracker.printTo(os.Stdout)
|
||||
fmt.Printf("\n")
|
||||
@@ -524,7 +558,7 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools) {
|
||||
if err := healFreshDisk(ctx, z, disk); err != nil {
|
||||
globalBackgroundHealState.setDiskHealingStatus(disk, false)
|
||||
timedout := OperationTimedOut{}
|
||||
if !errors.Is(err, context.Canceled) && !errors.As(err, &timedout) {
|
||||
if !errors.Is(err, context.Canceled) && !errors.As(err, &timedout) && !errors.Is(err, errRetryHealing) {
|
||||
printEndpointError(disk, err, false)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -200,6 +200,12 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "BytesSkipped")
|
||||
return
|
||||
}
|
||||
case "RetryAttempts":
|
||||
z.RetryAttempts, err = dc.ReadUint64()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "RetryAttempts")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
@@ -213,9 +219,9 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 25
|
||||
// map header, size 26
|
||||
// write "ID"
|
||||
err = en.Append(0xde, 0x0, 0x19, 0xa2, 0x49, 0x44)
|
||||
err = en.Append(0xde, 0x0, 0x1a, 0xa2, 0x49, 0x44)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -478,15 +484,25 @@ func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "BytesSkipped")
|
||||
return
|
||||
}
|
||||
// write "RetryAttempts"
|
||||
err = en.Append(0xad, 0x52, 0x65, 0x74, 0x72, 0x79, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteUint64(z.RetryAttempts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "RetryAttempts")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 25
|
||||
// map header, size 26
|
||||
// string "ID"
|
||||
o = append(o, 0xde, 0x0, 0x19, 0xa2, 0x49, 0x44)
|
||||
o = append(o, 0xde, 0x0, 0x1a, 0xa2, 0x49, 0x44)
|
||||
o = msgp.AppendString(o, z.ID)
|
||||
// string "PoolIndex"
|
||||
o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
||||
@@ -566,6 +582,9 @@ func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "BytesSkipped"
|
||||
o = append(o, 0xac, 0x42, 0x79, 0x74, 0x65, 0x73, 0x53, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64)
|
||||
o = msgp.AppendUint64(o, z.BytesSkipped)
|
||||
// string "RetryAttempts"
|
||||
o = append(o, 0xad, 0x52, 0x65, 0x74, 0x72, 0x79, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
|
||||
o = msgp.AppendUint64(o, z.RetryAttempts)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -763,6 +782,12 @@ func (z *healingTracker) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "BytesSkipped")
|
||||
return
|
||||
}
|
||||
case "RetryAttempts":
|
||||
z.RetryAttempts, bts, err = msgp.ReadUint64Bytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "RetryAttempts")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
@@ -785,6 +810,6 @@ func (z *healingTracker) Msgsize() (s int) {
|
||||
for za0002 := range z.HealedBuckets {
|
||||
s += msgp.StringPrefixSize + len(z.HealedBuckets[za0002])
|
||||
}
|
||||
s += 7 + msgp.StringPrefixSize + len(z.HealID) + 13 + msgp.Uint64Size + 13 + msgp.Uint64Size
|
||||
s += 7 + msgp.StringPrefixSize + len(z.HealID) + 13 + msgp.Uint64Size + 13 + msgp.Uint64Size + 14 + msgp.Uint64Size
|
||||
return
|
||||
}
|
||||
|
||||
+1
-1
@@ -514,7 +514,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
||||
JobType: string(job.Type()),
|
||||
StartTime: job.Started,
|
||||
}
|
||||
if err := ri.load(ctx, api, job); err != nil {
|
||||
if err := ri.loadOrInit(ctx, api, job); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+267
-122
@@ -28,6 +28,7 @@ import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -57,6 +58,11 @@ import (
|
||||
|
||||
var globalBatchConfig batch.Config
|
||||
|
||||
const (
|
||||
// Keep the completed/failed job stats 3 days before removing it
|
||||
oldJobsExpiration = 3 * 24 * time.Hour
|
||||
)
|
||||
|
||||
// BatchJobRequest this is an internal data structure not for external consumption.
|
||||
type BatchJobRequest struct {
|
||||
ID string `yaml:"-" json:"name"`
|
||||
@@ -262,7 +268,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
||||
JobType: string(job.Type()),
|
||||
StartTime: job.Started,
|
||||
}
|
||||
if err := ri.load(ctx, api, job); err != nil {
|
||||
if err := ri.loadOrInit(ctx, api, job); err != nil {
|
||||
return err
|
||||
}
|
||||
if ri.Complete {
|
||||
@@ -722,60 +728,82 @@ const (
|
||||
batchReplJobDefaultRetryDelay = 250 * time.Millisecond
|
||||
)
|
||||
|
||||
func getJobReportPath(job BatchJobRequest) string {
|
||||
var fileName string
|
||||
switch {
|
||||
case job.Replicate != nil:
|
||||
fileName = batchReplName
|
||||
case job.KeyRotate != nil:
|
||||
fileName = batchKeyRotationName
|
||||
case job.Expire != nil:
|
||||
fileName = batchExpireName
|
||||
}
|
||||
return pathJoin(batchJobReportsPrefix, job.ID, fileName)
|
||||
}
|
||||
|
||||
func getJobPath(job BatchJobRequest) string {
|
||||
return pathJoin(batchJobPrefix, job.ID)
|
||||
}
|
||||
|
||||
func (ri *batchJobInfo) getJobReportPath() (string, error) {
|
||||
var fileName string
|
||||
switch madmin.BatchJobType(ri.JobType) {
|
||||
case madmin.BatchJobReplicate:
|
||||
fileName = batchReplName
|
||||
case madmin.BatchJobKeyRotate:
|
||||
fileName = batchKeyRotationName
|
||||
case madmin.BatchJobExpire:
|
||||
fileName = batchExpireName
|
||||
default:
|
||||
return "", fmt.Errorf("unknown job type: %v", ri.JobType)
|
||||
}
|
||||
return pathJoin(batchJobReportsPrefix, ri.JobID, fileName), nil
|
||||
}
|
||||
|
||||
func (ri *batchJobInfo) loadOrInit(ctx context.Context, api ObjectLayer, job BatchJobRequest) error {
|
||||
err := ri.load(ctx, api, job)
|
||||
if errors.Is(err, errNoSuchJob) {
|
||||
switch {
|
||||
case job.Replicate != nil:
|
||||
ri.Version = batchReplVersionV1
|
||||
ri.RetryAttempts = batchReplJobDefaultRetries
|
||||
if job.Replicate.Flags.Retry.Attempts > 0 {
|
||||
ri.RetryAttempts = job.Replicate.Flags.Retry.Attempts
|
||||
}
|
||||
case job.KeyRotate != nil:
|
||||
ri.Version = batchKeyRotateVersionV1
|
||||
ri.RetryAttempts = batchKeyRotateJobDefaultRetries
|
||||
if job.KeyRotate.Flags.Retry.Attempts > 0 {
|
||||
ri.RetryAttempts = job.KeyRotate.Flags.Retry.Attempts
|
||||
}
|
||||
case job.Expire != nil:
|
||||
ri.Version = batchExpireVersionV1
|
||||
ri.RetryAttempts = batchExpireJobDefaultRetries
|
||||
if job.Expire.Retry.Attempts > 0 {
|
||||
ri.RetryAttempts = job.Expire.Retry.Attempts
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobRequest) error {
|
||||
path, err := job.getJobReportPath()
|
||||
if err != nil {
|
||||
batchLogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
return ri.loadByPath(ctx, api, path)
|
||||
}
|
||||
|
||||
func (ri *batchJobInfo) loadByPath(ctx context.Context, api ObjectLayer, path string) error {
|
||||
var format, version uint16
|
||||
switch {
|
||||
case job.Replicate != nil:
|
||||
switch filepath.Base(path) {
|
||||
case batchReplName:
|
||||
version = batchReplVersionV1
|
||||
format = batchReplFormat
|
||||
case job.KeyRotate != nil:
|
||||
case batchKeyRotationName:
|
||||
version = batchKeyRotateVersionV1
|
||||
format = batchKeyRotationFormat
|
||||
case job.Expire != nil:
|
||||
case batchExpireName:
|
||||
version = batchExpireVersionV1
|
||||
format = batchExpireFormat
|
||||
default:
|
||||
return errors.New("no supported batch job request specified")
|
||||
}
|
||||
data, err := readConfig(ctx, api, getJobReportPath(job))
|
||||
|
||||
data, err := readConfig(ctx, api, path)
|
||||
if err != nil {
|
||||
if errors.Is(err, errConfigNotFound) || isErrObjectNotFound(err) {
|
||||
ri.Version = int(version)
|
||||
switch {
|
||||
case job.Replicate != nil:
|
||||
ri.RetryAttempts = batchReplJobDefaultRetries
|
||||
if job.Replicate.Flags.Retry.Attempts > 0 {
|
||||
ri.RetryAttempts = job.Replicate.Flags.Retry.Attempts
|
||||
}
|
||||
case job.KeyRotate != nil:
|
||||
ri.RetryAttempts = batchKeyRotateJobDefaultRetries
|
||||
if job.KeyRotate.Flags.Retry.Attempts > 0 {
|
||||
ri.RetryAttempts = job.KeyRotate.Flags.Retry.Attempts
|
||||
}
|
||||
case job.Expire != nil:
|
||||
ri.RetryAttempts = batchExpireJobDefaultRetries
|
||||
if job.Expire.Retry.Attempts > 0 {
|
||||
ri.RetryAttempts = job.Expire.Retry.Attempts
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return errNoSuchJob
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -919,7 +947,12 @@ func (ri *batchJobInfo) updateAfter(ctx context.Context, api ObjectLayer, durati
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return saveConfig(ctx, api, getJobReportPath(job), buf)
|
||||
path, err := ri.getJobReportPath()
|
||||
if err != nil {
|
||||
batchLogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
return saveConfig(ctx, api, path, buf)
|
||||
}
|
||||
ri.mu.Unlock()
|
||||
return nil
|
||||
@@ -944,8 +977,10 @@ func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo,
|
||||
ri.mu.Lock()
|
||||
defer ri.mu.Unlock()
|
||||
|
||||
ri.Bucket = bucket
|
||||
ri.Object = info.Name
|
||||
if success {
|
||||
ri.Bucket = bucket
|
||||
ri.Object = info.Name
|
||||
}
|
||||
ri.countItem(info.Size, info.DeleteMarker, success, attempt)
|
||||
}
|
||||
|
||||
@@ -971,7 +1006,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
JobType: string(job.Type()),
|
||||
StartTime: job.Started,
|
||||
}
|
||||
if err := ri.load(ctx, api, job); err != nil {
|
||||
if err := ri.loadOrInit(ctx, api, job); err != nil {
|
||||
return err
|
||||
}
|
||||
if ri.Complete {
|
||||
@@ -1071,86 +1106,84 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
|
||||
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
|
||||
|
||||
var (
|
||||
walkCh = make(chan itemOrErr[ObjectInfo], 100)
|
||||
slowCh = make(chan itemOrErr[ObjectInfo], 100)
|
||||
)
|
||||
|
||||
if !*r.Source.Snowball.Disable && r.Source.Type.isMinio() && r.Target.Type.isMinio() {
|
||||
go func() {
|
||||
// Snowball currently needs the high level minio-go Client, not the Core one
|
||||
cl, err := miniogo.New(u.Host, &miniogo.Options{
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||
Secure: u.Scheme == "https",
|
||||
Transport: getRemoteInstanceTransport(),
|
||||
BucketLookup: lookupStyle(r.Target.Path),
|
||||
})
|
||||
if err != nil {
|
||||
batchLogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Already validated before arriving here
|
||||
smallerThan, _ := humanize.ParseBytes(*r.Source.Snowball.SmallerThan)
|
||||
|
||||
batch := make([]ObjectInfo, 0, *r.Source.Snowball.Batch)
|
||||
writeFn := func(batch []ObjectInfo) {
|
||||
if len(batch) > 0 {
|
||||
if err := r.writeAsArchive(ctx, api, cl, batch); err != nil {
|
||||
batchLogIf(ctx, err)
|
||||
for _, b := range batch {
|
||||
slowCh <- itemOrErr[ObjectInfo]{Item: b}
|
||||
}
|
||||
} else {
|
||||
ri.trackCurrentBucketBatch(r.Source.Bucket, batch)
|
||||
globalBatchJobsMetrics.save(job.ID, ri)
|
||||
// persist in-memory state to disk after every 10secs.
|
||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||
}
|
||||
}
|
||||
}
|
||||
for obj := range walkCh {
|
||||
if obj.Item.DeleteMarker || !obj.Item.VersionPurgeStatus.Empty() || obj.Item.Size >= int64(smallerThan) {
|
||||
slowCh <- obj
|
||||
continue
|
||||
}
|
||||
|
||||
batch = append(batch, obj.Item)
|
||||
|
||||
if len(batch) < *r.Source.Snowball.Batch {
|
||||
continue
|
||||
}
|
||||
writeFn(batch)
|
||||
batch = batch[:0]
|
||||
}
|
||||
writeFn(batch)
|
||||
xioutil.SafeClose(slowCh)
|
||||
}()
|
||||
} else {
|
||||
slowCh = walkCh
|
||||
}
|
||||
|
||||
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_REPLICATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wk, err := workers.New(workerSize)
|
||||
if err != nil {
|
||||
// invalid worker size.
|
||||
return err
|
||||
}
|
||||
|
||||
walkQuorum := env.Get("_MINIO_BATCH_REPLICATION_WALK_QUORUM", "strict")
|
||||
if walkQuorum == "" {
|
||||
walkQuorum = "strict"
|
||||
}
|
||||
|
||||
retryAttempts := ri.RetryAttempts
|
||||
retry := false
|
||||
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
||||
attempts := attempts
|
||||
var (
|
||||
walkCh = make(chan itemOrErr[ObjectInfo], 100)
|
||||
slowCh = make(chan itemOrErr[ObjectInfo], 100)
|
||||
)
|
||||
|
||||
if !*r.Source.Snowball.Disable && r.Source.Type.isMinio() && r.Target.Type.isMinio() {
|
||||
go func() {
|
||||
// Snowball currently needs the high level minio-go Client, not the Core one
|
||||
cl, err := miniogo.New(u.Host, &miniogo.Options{
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||
Secure: u.Scheme == "https",
|
||||
Transport: getRemoteInstanceTransport(),
|
||||
BucketLookup: lookupStyle(r.Target.Path),
|
||||
})
|
||||
if err != nil {
|
||||
batchLogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Already validated before arriving here
|
||||
smallerThan, _ := humanize.ParseBytes(*r.Source.Snowball.SmallerThan)
|
||||
|
||||
batch := make([]ObjectInfo, 0, *r.Source.Snowball.Batch)
|
||||
writeFn := func(batch []ObjectInfo) {
|
||||
if len(batch) > 0 {
|
||||
if err := r.writeAsArchive(ctx, api, cl, batch); err != nil {
|
||||
batchLogIf(ctx, err)
|
||||
for _, b := range batch {
|
||||
slowCh <- itemOrErr[ObjectInfo]{Item: b}
|
||||
}
|
||||
} else {
|
||||
ri.trackCurrentBucketBatch(r.Source.Bucket, batch)
|
||||
globalBatchJobsMetrics.save(job.ID, ri)
|
||||
// persist in-memory state to disk after every 10secs.
|
||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||
}
|
||||
}
|
||||
}
|
||||
for obj := range walkCh {
|
||||
if obj.Item.DeleteMarker || !obj.Item.VersionPurgeStatus.Empty() || obj.Item.Size >= int64(smallerThan) {
|
||||
slowCh <- obj
|
||||
continue
|
||||
}
|
||||
|
||||
batch = append(batch, obj.Item)
|
||||
|
||||
if len(batch) < *r.Source.Snowball.Batch {
|
||||
continue
|
||||
}
|
||||
writeFn(batch)
|
||||
batch = batch[:0]
|
||||
}
|
||||
writeFn(batch)
|
||||
xioutil.SafeClose(slowCh)
|
||||
}()
|
||||
} else {
|
||||
slowCh = walkCh
|
||||
}
|
||||
|
||||
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_REPLICATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wk, err := workers.New(workerSize)
|
||||
if err != nil {
|
||||
// invalid worker size.
|
||||
return err
|
||||
}
|
||||
|
||||
walkQuorum := env.Get("_MINIO_BATCH_REPLICATION_WALK_QUORUM", "strict")
|
||||
if walkQuorum == "" {
|
||||
walkQuorum = "strict"
|
||||
}
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
// one of source/target is s3, skip delete marker and all versions under the same object name.
|
||||
s3Type := r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3
|
||||
@@ -1436,10 +1469,24 @@ func (j BatchJobRequest) Validate(ctx context.Context, o ObjectLayer) error {
|
||||
}
|
||||
|
||||
func (j BatchJobRequest) delete(ctx context.Context, api ObjectLayer) {
|
||||
deleteConfig(ctx, api, getJobReportPath(j))
|
||||
deleteConfig(ctx, api, getJobPath(j))
|
||||
}
|
||||
|
||||
func (j BatchJobRequest) getJobReportPath() (string, error) {
|
||||
var fileName string
|
||||
switch {
|
||||
case j.Replicate != nil:
|
||||
fileName = batchReplName
|
||||
case j.KeyRotate != nil:
|
||||
fileName = batchKeyRotationName
|
||||
case j.Expire != nil:
|
||||
fileName = batchExpireName
|
||||
default:
|
||||
return "", errors.New("unknown job type")
|
||||
}
|
||||
return pathJoin(batchJobReportsPrefix, j.ID, fileName), nil
|
||||
}
|
||||
|
||||
func (j *BatchJobRequest) save(ctx context.Context, api ObjectLayer) error {
|
||||
if j.Replicate == nil && j.KeyRotate == nil && j.Expire == nil {
|
||||
return errInvalidArgument
|
||||
@@ -1542,6 +1589,55 @@ func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request)
|
||||
batchLogIf(ctx, json.NewEncoder(w).Encode(&listResult))
|
||||
}
|
||||
|
||||
// BatchJobStatus - returns the status of a batch job saved in the disk
|
||||
func (a adminAPIHandlers) BatchJobStatus(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListBatchJobsAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
jobID := r.Form.Get("jobId")
|
||||
if jobID == "" {
|
||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, errInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
req := BatchJobRequest{ID: jobID}
|
||||
if i := strings.Index(jobID, "-"); i > 0 {
|
||||
switch madmin.BatchJobType(jobID[:i]) {
|
||||
case madmin.BatchJobReplicate:
|
||||
req.Replicate = &BatchJobReplicateV1{}
|
||||
case madmin.BatchJobKeyRotate:
|
||||
req.KeyRotate = &BatchJobKeyRotateV1{}
|
||||
case madmin.BatchJobExpire:
|
||||
req.Expire = &BatchJobExpire{}
|
||||
default:
|
||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, errors.New("job ID format unrecognized")), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ri := &batchJobInfo{}
|
||||
if err := ri.load(ctx, objectAPI, req); err != nil {
|
||||
if !errors.Is(err, errNoSuchJob) {
|
||||
batchLogIf(ctx, err)
|
||||
}
|
||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(madmin.BatchJobStatus{LastMetric: ri.metric()})
|
||||
if err != nil {
|
||||
batchLogIf(ctx, err)
|
||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
var errNoSuchJob = errors.New("no such job")
|
||||
|
||||
// DescribeBatchJob returns the currently active batch job definition
|
||||
@@ -1633,7 +1729,7 @@ func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
job.ID = fmt.Sprintf("%s%s%d", shortuuid.New(), getKeySeparator(), GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
||||
job.ID = fmt.Sprintf("%s-%s%s%d", job.Type(), shortuuid.New(), getKeySeparator(), GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
||||
job.User = user
|
||||
job.Started = time.Now()
|
||||
|
||||
@@ -1721,11 +1817,60 @@ func newBatchJobPool(ctx context.Context, o ObjectLayer, workers int) *BatchJobP
|
||||
jobCancelers: make(map[string]context.CancelFunc),
|
||||
}
|
||||
jpool.ResizeWorkers(workers)
|
||||
jpool.resume()
|
||||
|
||||
randomWait := func() time.Duration {
|
||||
// randomWait depends on the number of nodes to avoid triggering resume and cleanups at the same time.
|
||||
return time.Duration(rand.Float64() * float64(time.Duration(globalEndpoints.NEndpoints())*time.Hour))
|
||||
}
|
||||
|
||||
go func() {
|
||||
jpool.resume(randomWait)
|
||||
jpool.cleanupReports(randomWait)
|
||||
}()
|
||||
|
||||
return jpool
|
||||
}
|
||||
|
||||
func (j *BatchJobPool) resume() {
|
||||
func (j *BatchJobPool) cleanupReports(randomWait func() time.Duration) {
|
||||
t := time.NewTimer(randomWait())
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-GlobalContext.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
results := make(chan itemOrErr[ObjectInfo], 100)
|
||||
ctx, cancel := context.WithCancel(j.ctx)
|
||||
defer cancel()
|
||||
if err := j.objLayer.Walk(ctx, minioMetaBucket, batchJobReportsPrefix, results, WalkOptions{}); err != nil {
|
||||
batchLogIf(j.ctx, err)
|
||||
t.Reset(randomWait())
|
||||
continue
|
||||
}
|
||||
for result := range results {
|
||||
if result.Err != nil {
|
||||
batchLogIf(j.ctx, result.Err)
|
||||
continue
|
||||
}
|
||||
ri := &batchJobInfo{}
|
||||
if err := ri.loadByPath(ctx, j.objLayer, result.Item.Name); err != nil {
|
||||
batchLogIf(ctx, err)
|
||||
continue
|
||||
}
|
||||
if (ri.Complete || ri.Failed) && time.Since(ri.LastUpdate) > oldJobsExpiration {
|
||||
deleteConfig(ctx, j.objLayer, result.Item.Name)
|
||||
}
|
||||
}
|
||||
|
||||
t.Reset(randomWait())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (j *BatchJobPool) resume(randomWait func() time.Duration) {
|
||||
time.Sleep(randomWait())
|
||||
|
||||
results := make(chan itemOrErr[ObjectInfo], 100)
|
||||
ctx, cancel := context.WithCancel(j.ctx)
|
||||
defer cancel()
|
||||
@@ -1988,7 +2133,7 @@ func (m *batchJobMetrics) purgeJobMetrics() {
|
||||
var toDeleteJobMetrics []string
|
||||
m.RLock()
|
||||
for id, metrics := range m.metrics {
|
||||
if time.Since(metrics.LastUpdate) > 24*time.Hour && (metrics.Complete || metrics.Failed) {
|
||||
if time.Since(metrics.LastUpdate) > oldJobsExpiration && (metrics.Complete || metrics.Failed) {
|
||||
toDeleteJobMetrics = append(toDeleteJobMetrics, id)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -257,7 +257,7 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
JobType: string(job.Type()),
|
||||
StartTime: job.Started,
|
||||
}
|
||||
if err := ri.load(ctx, api, job); err != nil {
|
||||
if err := ri.loadOrInit(ctx, api, job); err != nil {
|
||||
return err
|
||||
}
|
||||
if ri.Complete {
|
||||
|
||||
@@ -106,14 +106,17 @@ func (s1 *ServerSystemConfig) Diff(s2 *ServerSystemConfig) error {
|
||||
}
|
||||
|
||||
var skipEnvs = map[string]struct{}{
|
||||
"MINIO_OPTS": {},
|
||||
"MINIO_CERT_PASSWD": {},
|
||||
"MINIO_SERVER_DEBUG": {},
|
||||
"MINIO_DSYNC_TRACE": {},
|
||||
"MINIO_ROOT_USER": {},
|
||||
"MINIO_ROOT_PASSWORD": {},
|
||||
"MINIO_ACCESS_KEY": {},
|
||||
"MINIO_SECRET_KEY": {},
|
||||
"MINIO_OPTS": {},
|
||||
"MINIO_CERT_PASSWD": {},
|
||||
"MINIO_SERVER_DEBUG": {},
|
||||
"MINIO_DSYNC_TRACE": {},
|
||||
"MINIO_ROOT_USER": {},
|
||||
"MINIO_ROOT_PASSWORD": {},
|
||||
"MINIO_ACCESS_KEY": {},
|
||||
"MINIO_SECRET_KEY": {},
|
||||
"MINIO_OPERATOR_VERSION": {},
|
||||
"MINIO_VSPHERE_PLUGIN_VERSION": {},
|
||||
"MINIO_CI_CD": {},
|
||||
}
|
||||
|
||||
func getServerSystemCfg() *ServerSystemConfig {
|
||||
|
||||
+10
-3
@@ -834,7 +834,7 @@ func serverHandleEnvVars() {
|
||||
}
|
||||
}
|
||||
|
||||
globalDisableFreezeOnBoot = env.Get("_MINIO_DISABLE_API_FREEZE_ON_BOOT", "") == "true" || serverDebugLog
|
||||
globalEnableSyncBoot = env.Get("MINIO_SYNC_BOOT", config.EnableOff) == config.EnableOn
|
||||
}
|
||||
|
||||
func loadRootCredentials() {
|
||||
@@ -843,6 +843,7 @@ func loadRootCredentials() {
|
||||
// Check both cases and authenticate them if correctly defined
|
||||
var user, password string
|
||||
var hasCredentials bool
|
||||
var legacyCredentials bool
|
||||
//nolint:gocritic
|
||||
if env.IsSet(config.EnvRootUser) && env.IsSet(config.EnvRootPassword) {
|
||||
user = env.Get(config.EnvRootUser, "")
|
||||
@@ -851,6 +852,7 @@ func loadRootCredentials() {
|
||||
} else if env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) {
|
||||
user = env.Get(config.EnvAccessKey, "")
|
||||
password = env.Get(config.EnvSecretKey, "")
|
||||
legacyCredentials = true
|
||||
hasCredentials = true
|
||||
} else if globalServerCtxt.RootUser != "" && globalServerCtxt.RootPwd != "" {
|
||||
user, password = globalServerCtxt.RootUser, globalServerCtxt.RootPwd
|
||||
@@ -859,8 +861,13 @@ func loadRootCredentials() {
|
||||
if hasCredentials {
|
||||
cred, err := auth.CreateCredentials(user, password)
|
||||
if err != nil {
|
||||
logger.Fatal(config.ErrInvalidCredentials(err),
|
||||
"Unable to validate credentials inherited from the shell environment")
|
||||
if legacyCredentials {
|
||||
logger.Fatal(config.ErrInvalidCredentials(err),
|
||||
"Unable to validate credentials inherited from the shell environment")
|
||||
} else {
|
||||
logger.Fatal(config.ErrInvalidRootUserCredentials(err),
|
||||
"Unable to validate credentials inherited from the shell environment")
|
||||
}
|
||||
}
|
||||
if env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) {
|
||||
msg := fmt.Sprintf("WARNING: %s and %s are deprecated.\n"+
|
||||
|
||||
+8
-4
@@ -227,7 +227,9 @@ func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
||||
binary.LittleEndian.PutUint64(tmp, cycleInfo.next)
|
||||
tmp, _ = cycleInfo.MarshalMsg(tmp)
|
||||
err = saveConfig(ctx, objAPI, dataUsageBloomNamePath, tmp)
|
||||
scannerLogIf(ctx, err, dataUsageBloomNamePath)
|
||||
if err != nil {
|
||||
scannerLogIf(ctx, fmt.Errorf("%w, Object %s", err, dataUsageBloomNamePath))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -797,7 +799,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
}, madmin.HealItemObject)
|
||||
stopFn(int(ver.Size))
|
||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||
scannerLogIf(ctx, err, fiv.Name)
|
||||
if err != nil {
|
||||
scannerLogIf(ctx, fmt.Errorf("%w, Object %s/%s/%s", err, bucket, fiv.Name, ver.VersionID))
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
successVersions++
|
||||
@@ -1271,7 +1275,7 @@ func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer,
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
return false
|
||||
}
|
||||
ilmLogIf(ctx, err)
|
||||
ilmLogIf(ctx, fmt.Errorf("expireTransitionedObject(%s, %s): %w", obj.Bucket, obj.Name, err))
|
||||
return false
|
||||
}
|
||||
timeILM(1)
|
||||
@@ -1324,7 +1328,7 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
|
||||
return false
|
||||
}
|
||||
// Assume it is still there.
|
||||
ilmLogOnceIf(ctx, err, "non-transition-expiry")
|
||||
ilmLogOnceIf(ctx, fmt.Errorf("DeleteObject(%s, %s): %w", obj.Bucket, obj.Name, err), "non-transition-expiry"+obj.Name)
|
||||
return false
|
||||
}
|
||||
if dobj.Name == "" {
|
||||
|
||||
@@ -61,10 +61,9 @@ func NewDummyDataGen(totalLength, skipOffset int64) io.ReadSeeker {
|
||||
}
|
||||
|
||||
skipOffset %= int64(len(alphabets))
|
||||
as := make([]byte, 2*len(alphabets))
|
||||
copy(as, alphabets)
|
||||
copy(as[len(alphabets):], alphabets)
|
||||
b := as[skipOffset : skipOffset+int64(len(alphabets))]
|
||||
const multiply = 100
|
||||
as := bytes.Repeat(alphabets, multiply)
|
||||
b := as[skipOffset : skipOffset+int64(len(alphabets)*(multiply-1))]
|
||||
return &DummyDataGen{
|
||||
length: totalLength,
|
||||
b: b,
|
||||
|
||||
+11
-6
@@ -134,11 +134,16 @@ func DecryptETags(ctx context.Context, k *kms.KMS, objects []ObjectInfo) error {
|
||||
SSES3SinglePartObjects := make(map[int]bool)
|
||||
for i, object := range batch {
|
||||
if kind, ok := crypto.IsEncrypted(object.UserDefined); ok && kind == crypto.S3 && !crypto.IsMultiPart(object.UserDefined) {
|
||||
SSES3SinglePartObjects[i] = true
|
||||
|
||||
metadata = append(metadata, object.UserDefined)
|
||||
buckets = append(buckets, object.Bucket)
|
||||
names = append(names, object.Name)
|
||||
ETag, err := etag.Parse(object.ETag)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ETag.IsEncrypted() {
|
||||
SSES3SinglePartObjects[i] = true
|
||||
metadata = append(metadata, object.UserDefined)
|
||||
buckets = append(buckets, object.Bucket)
|
||||
names = append(names, object.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +195,7 @@ func DecryptETags(ctx context.Context, k *kms.KMS, objects []ObjectInfo) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if SSES3SinglePartObjects[i] && ETag.IsEncrypted() {
|
||||
if SSES3SinglePartObjects[i] {
|
||||
ETag, err = etag.Decrypt(keys[0][:], ETag)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -629,7 +629,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
||||
}
|
||||
|
||||
for i, v := range result.Before.Drives {
|
||||
if v.Endpoint == disk.String() {
|
||||
if v.Endpoint == disk.Endpoint().String() {
|
||||
result.After.Drives[i].State = madmin.DriveStateOk
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ func (p *poolMeta) validate(pools []*erasureSets) (bool, error) {
|
||||
update = true
|
||||
}
|
||||
if ok && pi.completed {
|
||||
return false, fmt.Errorf("pool(%s) = %s is decommissioned, please remove from server command line", humanize.Ordinal(pi.position+1), k)
|
||||
logger.LogIf(GlobalContext, "decommission", fmt.Errorf("pool(%s) = %s is decommissioned, please remove from server command line", humanize.Ordinal(pi.position+1), k))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ func TestPoolMetaValidate(t *testing.T) {
|
||||
meta: nmeta1,
|
||||
pools: pools,
|
||||
name: "Invalid-Completed-Pool-Not-Removed",
|
||||
expectedErr: true,
|
||||
expectedErr: false,
|
||||
expectedUpdate: false,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -119,11 +119,8 @@ func (z *erasureServerPools) loadRebalanceMeta(ctx context.Context) error {
|
||||
}
|
||||
|
||||
z.rebalMu.Lock()
|
||||
if len(r.PoolStats) == len(z.serverPools) {
|
||||
z.rebalMeta = r
|
||||
} else {
|
||||
z.updateRebalanceStats(ctx)
|
||||
}
|
||||
z.rebalMeta = r
|
||||
z.updateRebalanceStats(ctx)
|
||||
z.rebalMu.Unlock()
|
||||
|
||||
return nil
|
||||
@@ -147,24 +144,16 @@ func (z *erasureServerPools) updateRebalanceStats(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
lock := z.serverPools[0].NewNSLock(minioMetaBucket, rebalMetaName)
|
||||
lkCtx, err := lock.GetLock(ctx, globalOperationTimeout)
|
||||
if err != nil {
|
||||
rebalanceLogIf(ctx, fmt.Errorf("failed to acquire write lock on %s/%s: %w", minioMetaBucket, rebalMetaName, err))
|
||||
return err
|
||||
}
|
||||
defer lock.Unlock(lkCtx)
|
||||
|
||||
ctx = lkCtx.Context()
|
||||
|
||||
noLockOpts := ObjectOptions{NoLock: true}
|
||||
return z.rebalMeta.saveWithOpts(ctx, z.serverPools[0], noLockOpts)
|
||||
return z.rebalMeta.save(ctx, z.serverPools[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) findIndex(index int) int {
|
||||
if z.rebalMeta == nil {
|
||||
return 0
|
||||
}
|
||||
for i := 0; i < len(z.rebalMeta.PoolStats); i++ {
|
||||
if i == index {
|
||||
return index
|
||||
@@ -277,6 +266,10 @@ func (z *erasureServerPools) bucketRebalanceDone(bucket string, poolIdx int) {
|
||||
z.rebalMu.Lock()
|
||||
defer z.rebalMu.Unlock()
|
||||
|
||||
if z.rebalMeta == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ps := z.rebalMeta.PoolStats[poolIdx]
|
||||
if ps == nil {
|
||||
return
|
||||
@@ -331,6 +324,10 @@ func (r *rebalanceMeta) loadWithOpts(ctx context.Context, store objectIO, opts O
|
||||
}
|
||||
|
||||
func (r *rebalanceMeta) saveWithOpts(ctx context.Context, store objectIO, opts ObjectOptions) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := make([]byte, 4, r.Msgsize()+4)
|
||||
|
||||
// Initialize the header.
|
||||
@@ -353,8 +350,15 @@ func (z *erasureServerPools) IsRebalanceStarted() bool {
|
||||
z.rebalMu.RLock()
|
||||
defer z.rebalMu.RUnlock()
|
||||
|
||||
if r := z.rebalMeta; r != nil {
|
||||
if r.StoppedAt.IsZero() {
|
||||
r := z.rebalMeta
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
if !r.StoppedAt.IsZero() {
|
||||
return false
|
||||
}
|
||||
for _, ps := range r.PoolStats {
|
||||
if ps.Participating && ps.Info.Status != rebalCompleted {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -369,7 +373,7 @@ func (z *erasureServerPools) IsPoolRebalancing(poolIndex int) bool {
|
||||
if !r.StoppedAt.IsZero() {
|
||||
return false
|
||||
}
|
||||
ps := z.rebalMeta.PoolStats[poolIndex]
|
||||
ps := r.PoolStats[poolIndex]
|
||||
return ps.Participating && ps.Info.Status == rebalStarted
|
||||
}
|
||||
return false
|
||||
@@ -794,7 +798,9 @@ func (z *erasureServerPools) saveRebalanceStats(ctx context.Context, poolIdx int
|
||||
case rebalSaveStoppedAt:
|
||||
r.StoppedAt = time.Now()
|
||||
case rebalSaveStats:
|
||||
r.PoolStats[poolIdx] = z.rebalMeta.PoolStats[poolIdx]
|
||||
if z.rebalMeta != nil {
|
||||
r.PoolStats[poolIdx] = z.rebalMeta.PoolStats[poolIdx]
|
||||
}
|
||||
}
|
||||
z.rebalMeta = r
|
||||
|
||||
|
||||
@@ -2343,12 +2343,18 @@ func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix str
|
||||
|
||||
var poolErrs [][]error
|
||||
for idx, erasureSet := range z.serverPools {
|
||||
if opts.Pool != nil && *opts.Pool != idx {
|
||||
continue
|
||||
}
|
||||
if z.IsSuspended(idx) {
|
||||
continue
|
||||
}
|
||||
errs := make([]error, len(erasureSet.sets))
|
||||
var wg sync.WaitGroup
|
||||
for idx, set := range erasureSet.sets {
|
||||
if opts.Set != nil && *opts.Set != idx {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(idx int, set *erasureObjects) {
|
||||
defer wg.Done()
|
||||
|
||||
+1
-8
@@ -120,13 +120,6 @@ func connectEndpoint(endpoint Endpoint) (StorageAPI, *formatErasureV3, error) {
|
||||
|
||||
format, err := loadFormatErasure(disk, false)
|
||||
if err != nil {
|
||||
if errors.Is(err, errUnformattedDisk) {
|
||||
info, derr := disk.DiskInfo(context.TODO(), DiskInfoOptions{})
|
||||
if derr != nil && info.RootDisk {
|
||||
disk.Close()
|
||||
return nil, nil, fmt.Errorf("Drive: %s is a root drive", disk)
|
||||
}
|
||||
}
|
||||
disk.Close()
|
||||
return nil, nil, fmt.Errorf("Drive: %s returned %w", disk, err) // make sure to '%w' to wrap the error
|
||||
}
|
||||
@@ -230,7 +223,7 @@ func (s *erasureSets) connectDisks() {
|
||||
if err != nil {
|
||||
if endpoint.IsLocal && errors.Is(err, errUnformattedDisk) {
|
||||
globalBackgroundHealState.pushHealLocalDisks(endpoint)
|
||||
} else {
|
||||
} else if !errors.Is(err, errDriveIsRoot) {
|
||||
printEndpointError(endpoint, err, true)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -102,6 +102,8 @@ func diskErrToDriveState(err error) (state string) {
|
||||
state = madmin.DriveStatePermission
|
||||
case errors.Is(err, errFaultyDisk):
|
||||
state = madmin.DriveStateFaulty
|
||||
case errors.Is(err, errDriveIsRoot):
|
||||
state = madmin.DriveStateRootMount
|
||||
case err == nil:
|
||||
state = madmin.DriveStateOk
|
||||
default:
|
||||
|
||||
+21
-5
@@ -441,6 +441,8 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
continue
|
||||
}
|
||||
|
||||
var versionHealed bool
|
||||
|
||||
res, err := er.HealObject(ctx, bucket, encodedEntryName,
|
||||
version.VersionID, madmin.HealOpts{
|
||||
ScanMode: scanMode,
|
||||
@@ -453,15 +455,22 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
versionNotFound++
|
||||
continue
|
||||
}
|
||||
// If not deleted, assume they failed.
|
||||
} else {
|
||||
// Look for the healing results
|
||||
if res.After.Drives[tracker.DiskIndex].State == madmin.DriveStateOk {
|
||||
versionHealed = true
|
||||
}
|
||||
}
|
||||
|
||||
if versionHealed {
|
||||
result = healEntrySuccess(uint64(version.Size))
|
||||
} else {
|
||||
result = healEntryFailure(uint64(version.Size))
|
||||
if version.VersionID != "" {
|
||||
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s-v(%s): %w", bucket, version.Name, version.VersionID, err))
|
||||
} else {
|
||||
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s: %w", bucket, version.Name, err))
|
||||
}
|
||||
} else {
|
||||
result = healEntrySuccess(uint64(res.ObjectSize))
|
||||
}
|
||||
|
||||
if !send(result) {
|
||||
@@ -509,7 +518,11 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
jt.Take()
|
||||
go healEntry(bucket, *entry)
|
||||
},
|
||||
finished: nil,
|
||||
finished: func(errs []error) {
|
||||
if countErrs(errs, nil) != len(errs) {
|
||||
retErr = fmt.Errorf("one or more errors reported during listing: %v", errors.Join(errs...))
|
||||
}
|
||||
},
|
||||
})
|
||||
jt.Wait() // synchronize all the concurrent heal jobs
|
||||
if err != nil {
|
||||
@@ -517,7 +530,10 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
// we let the caller retry this disk again for the
|
||||
// buckets it failed to list.
|
||||
retErr = err
|
||||
healingLogIf(ctx, fmt.Errorf("listing failed with: %v on bucket: %v", err, bucket))
|
||||
}
|
||||
|
||||
if retErr != nil {
|
||||
healingLogIf(ctx, fmt.Errorf("listing failed with: %v on bucket: %v", retErr, bucket))
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -449,8 +449,8 @@ var (
|
||||
// dynamic sleeper for multipart expiration routine
|
||||
deleteMultipartCleanupSleeper = newDynamicSleeper(5, 25*time.Millisecond, false)
|
||||
|
||||
// Is _MINIO_DISABLE_API_FREEZE_ON_BOOT set?
|
||||
globalDisableFreezeOnBoot bool
|
||||
// Is MINIO_SYNC_BOOT set?
|
||||
globalEnableSyncBoot bool
|
||||
|
||||
// Contains NIC interface name used for internode communication
|
||||
globalInternodeInterface string
|
||||
|
||||
+10
-8
@@ -41,17 +41,19 @@ func initGlobalGrid(ctx context.Context, eps EndpointServerPools) error {
|
||||
// Pass Dialer for websocket grid, make sure we do not
|
||||
// provide any DriveOPTimeout() function, as that is not
|
||||
// useful over persistent connections.
|
||||
Dialer: grid.ContextDialer(xhttp.DialContextWithLookupHost(lookupHost, xhttp.NewInternodeDialContext(rest.DefaultTimeout, globalTCPOptions.ForWebsocket()))),
|
||||
Dialer: grid.ConnectWS(
|
||||
grid.ContextDialer(xhttp.DialContextWithLookupHost(lookupHost, xhttp.NewInternodeDialContext(rest.DefaultTimeout, globalTCPOptions.ForWebsocket()))),
|
||||
newCachedAuthToken(),
|
||||
&tls.Config{
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: fips.TLSCiphers(),
|
||||
CurvePreferences: fips.TLSCurveIDs(),
|
||||
}),
|
||||
Local: local,
|
||||
Hosts: hosts,
|
||||
AddAuth: newCachedAuthToken(),
|
||||
AuthRequest: storageServerRequestValidate,
|
||||
AuthToken: validateStorageRequestToken,
|
||||
AuthFn: newCachedAuthToken(),
|
||||
BlockConnect: globalGridStart,
|
||||
TLSConfig: &tls.Config{
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: fips.TLSCiphers(),
|
||||
CurvePreferences: fips.TLSCurveIDs(),
|
||||
},
|
||||
// Record incoming and outgoing bytes.
|
||||
Incoming: globalConnStats.incInternodeInputBytes,
|
||||
Outgoing: globalConnStats.incInternodeOutputBytes,
|
||||
|
||||
@@ -29,14 +29,35 @@ import (
|
||||
|
||||
const unavailable = "offline"
|
||||
|
||||
// ClusterCheckHandler returns if the server is ready for requests.
|
||||
func ClusterCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ClusterCheckHandler")
|
||||
|
||||
func checkHealth(w http.ResponseWriter) ObjectLayer {
|
||||
objLayer := newObjectLayerFn()
|
||||
if objLayer == nil {
|
||||
w.Header().Set(xhttp.MinIOServerStatus, unavailable)
|
||||
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !globalBucketMetadataSys.Initialized() {
|
||||
w.Header().Set(xhttp.MinIOServerStatus, "bucket-metadata-offline")
|
||||
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !globalIAMSys.Initialized() {
|
||||
w.Header().Set(xhttp.MinIOServerStatus, "iam-offline")
|
||||
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
|
||||
return nil
|
||||
}
|
||||
|
||||
return objLayer
|
||||
}
|
||||
|
||||
// ClusterCheckHandler returns if the server is ready for requests.
|
||||
func ClusterCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ClusterCheckHandler")
|
||||
|
||||
objLayer := checkHealth(w)
|
||||
if objLayer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -72,10 +93,8 @@ func ClusterCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func ClusterReadCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ClusterReadCheckHandler")
|
||||
|
||||
objLayer := newObjectLayerFn()
|
||||
objLayer := checkHealth(w)
|
||||
if objLayer == nil {
|
||||
w.Header().Set(xhttp.MinIOServerStatus, unavailable)
|
||||
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+64
-9
@@ -439,23 +439,44 @@ func (iamOS *IAMObjectStore) listAllIAMConfigItems(ctx context.Context) (res map
|
||||
return res, nil
|
||||
}
|
||||
|
||||
const (
|
||||
maxIAMLoadOpTime = 5 * time.Second
|
||||
)
|
||||
|
||||
// Assumes cache is locked by caller.
|
||||
func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iamCache) error {
|
||||
func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iamCache, firstTime bool) error {
|
||||
bootstrapTraceMsgFirstTime := func(s string) {
|
||||
if firstTime {
|
||||
bootstrapTraceMsg(s)
|
||||
}
|
||||
}
|
||||
|
||||
if iamOS.objAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
bootstrapTraceMsg("loading all IAM items")
|
||||
bootstrapTraceMsgFirstTime("loading all IAM items")
|
||||
|
||||
setDefaultCannedPolicies(cache.iamPolicyDocsMap)
|
||||
|
||||
listStartTime := UTCNow()
|
||||
listedConfigItems, err := iamOS.listAllIAMConfigItems(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to list IAM data: %w", err)
|
||||
}
|
||||
if took := time.Since(listStartTime); took > maxIAMLoadOpTime {
|
||||
var s strings.Builder
|
||||
for k, v := range listedConfigItems {
|
||||
s.WriteString(fmt.Sprintf(" %s: %d items\n", k, len(v)))
|
||||
}
|
||||
logger.Info("listAllIAMConfigItems took %.2fs with contents:\n%s", took.Seconds(), s.String())
|
||||
}
|
||||
|
||||
// Loads things in the same order as `LoadIAMCache()`
|
||||
|
||||
bootstrapTraceMsg("loading policy documents")
|
||||
bootstrapTraceMsgFirstTime("loading policy documents")
|
||||
|
||||
policyLoadStartTime := UTCNow()
|
||||
policiesList := listedConfigItems[policiesListKey]
|
||||
for _, item := range policiesList {
|
||||
policyName := path.Dir(item)
|
||||
@@ -463,10 +484,13 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
||||
return fmt.Errorf("unable to load the policy doc `%s`: %w", policyName, err)
|
||||
}
|
||||
}
|
||||
setDefaultCannedPolicies(cache.iamPolicyDocsMap)
|
||||
if took := time.Since(policyLoadStartTime); took > maxIAMLoadOpTime {
|
||||
logger.Info("Policy docs load took %.2fs (for %d items)", took.Seconds(), len(policiesList))
|
||||
}
|
||||
|
||||
if iamOS.usersSysType == MinIOUsersSysType {
|
||||
bootstrapTraceMsg("loading regular IAM users")
|
||||
bootstrapTraceMsgFirstTime("loading regular IAM users")
|
||||
regUsersLoadStartTime := UTCNow()
|
||||
regUsersList := listedConfigItems[usersListKey]
|
||||
for _, item := range regUsersList {
|
||||
userName := path.Dir(item)
|
||||
@@ -474,8 +498,14 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
||||
return fmt.Errorf("unable to load the user `%s`: %w", userName, err)
|
||||
}
|
||||
}
|
||||
if took := time.Since(regUsersLoadStartTime); took > maxIAMLoadOpTime {
|
||||
actualLoaded := len(cache.iamUsersMap)
|
||||
logger.Info("Reg. users load took %.2fs (for %d items with %d expired items)", took.Seconds(),
|
||||
len(regUsersList), len(regUsersList)-actualLoaded)
|
||||
}
|
||||
|
||||
bootstrapTraceMsg("loading regular IAM groups")
|
||||
bootstrapTraceMsgFirstTime("loading regular IAM groups")
|
||||
groupsLoadStartTime := UTCNow()
|
||||
groupsList := listedConfigItems[groupsListKey]
|
||||
for _, item := range groupsList {
|
||||
group := path.Dir(item)
|
||||
@@ -483,9 +513,13 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
||||
return fmt.Errorf("unable to load the group `%s`: %w", group, err)
|
||||
}
|
||||
}
|
||||
if took := time.Since(groupsLoadStartTime); took > maxIAMLoadOpTime {
|
||||
logger.Info("Groups load took %.2fs (for %d items)", took.Seconds(), len(groupsList))
|
||||
}
|
||||
}
|
||||
|
||||
bootstrapTraceMsg("loading user policy mapping")
|
||||
bootstrapTraceMsgFirstTime("loading user policy mapping")
|
||||
userPolicyMappingLoadStartTime := UTCNow()
|
||||
userPolicyMappingsList := listedConfigItems[policyDBUsersListKey]
|
||||
for _, item := range userPolicyMappingsList {
|
||||
userName := strings.TrimSuffix(item, ".json")
|
||||
@@ -493,8 +527,12 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
||||
return fmt.Errorf("unable to load the policy mapping for the user `%s`: %w", userName, err)
|
||||
}
|
||||
}
|
||||
if took := time.Since(userPolicyMappingLoadStartTime); took > maxIAMLoadOpTime {
|
||||
logger.Info("User policy mappings load took %.2fs (for %d items)", took.Seconds(), len(userPolicyMappingsList))
|
||||
}
|
||||
|
||||
bootstrapTraceMsg("loading group policy mapping")
|
||||
bootstrapTraceMsgFirstTime("loading group policy mapping")
|
||||
groupPolicyMappingLoadStartTime := UTCNow()
|
||||
groupPolicyMappingsList := listedConfigItems[policyDBGroupsListKey]
|
||||
for _, item := range groupPolicyMappingsList {
|
||||
groupName := strings.TrimSuffix(item, ".json")
|
||||
@@ -502,8 +540,12 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
||||
return fmt.Errorf("unable to load the policy mapping for the group `%s`: %w", groupName, err)
|
||||
}
|
||||
}
|
||||
if took := time.Since(groupPolicyMappingLoadStartTime); took > maxIAMLoadOpTime {
|
||||
logger.Info("Group policy mappings load took %.2fs (for %d items)", took.Seconds(), len(groupPolicyMappingsList))
|
||||
}
|
||||
|
||||
bootstrapTraceMsg("loading service accounts")
|
||||
bootstrapTraceMsgFirstTime("loading service accounts")
|
||||
svcAccLoadStartTime := UTCNow()
|
||||
svcAccList := listedConfigItems[svcAccListKey]
|
||||
svcUsersMap := make(map[string]UserIdentity, len(svcAccList))
|
||||
for _, item := range svcAccList {
|
||||
@@ -512,9 +554,18 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
||||
return fmt.Errorf("unable to load the service account `%s`: %w", userName, err)
|
||||
}
|
||||
}
|
||||
if took := time.Since(svcAccLoadStartTime); took > maxIAMLoadOpTime {
|
||||
logger.Info("Service accounts load took %.2fs (for %d items with %d expired items)", took.Seconds(),
|
||||
len(svcAccList), len(svcAccList)-len(svcUsersMap))
|
||||
}
|
||||
|
||||
bootstrapTraceMsg("loading STS account policy mapping")
|
||||
stsPolicyMappingLoadStartTime := UTCNow()
|
||||
var stsPolicyMappingsCount int
|
||||
for _, svcAcc := range svcUsersMap {
|
||||
svcParent := svcAcc.Credentials.ParentUser
|
||||
if _, ok := cache.iamUsersMap[svcParent]; !ok {
|
||||
stsPolicyMappingsCount++
|
||||
// If a service account's parent user is not in iamUsersMap, the
|
||||
// parent is an STS account. Such accounts may have a policy mapped
|
||||
// on the parent user, so we load them. This is not needed for the
|
||||
@@ -533,6 +584,10 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
||||
}
|
||||
}
|
||||
}
|
||||
if took := time.Since(stsPolicyMappingLoadStartTime); took > maxIAMLoadOpTime {
|
||||
logger.Info("STS policy mappings load took %.2fs (for %d items)", took.Seconds(), stsPolicyMappingsCount)
|
||||
}
|
||||
|
||||
// Copy svcUsersMap to cache.iamUsersMap
|
||||
for k, v := range svcUsersMap {
|
||||
cache.iamUsersMap[k] = v
|
||||
|
||||
+193
-58
@@ -431,8 +431,41 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([
|
||||
}
|
||||
}
|
||||
|
||||
// returned policy could be empty
|
||||
policies := mp.toSlice()
|
||||
// returned policy could be empty, we use set to de-duplicate.
|
||||
policies := set.CreateStringSet(mp.toSlice()...)
|
||||
|
||||
for _, group := range u.Credentials.Groups {
|
||||
if store.getUsersSysType() == MinIOUsersSysType {
|
||||
g, ok := c.iamGroupsMap[group]
|
||||
if !ok {
|
||||
if err := store.loadGroup(context.Background(), group, c.iamGroupsMap); err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
g, ok = c.iamGroupsMap[group]
|
||||
if !ok {
|
||||
return nil, time.Time{}, errNoSuchGroup
|
||||
}
|
||||
}
|
||||
|
||||
// Group is disabled, so we return no policy - this
|
||||
// ensures the request is denied.
|
||||
if g.Status == statusDisabled {
|
||||
return nil, time.Time{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
policy, ok := c.iamGroupPolicyMap.Load(group)
|
||||
if !ok {
|
||||
if err := store.loadMappedPolicyWithRetry(context.TODO(), group, regUser, true, c.iamGroupPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
policy, _ = c.iamGroupPolicyMap.Load(group)
|
||||
}
|
||||
|
||||
for _, p := range policy.toSlice() {
|
||||
policies.Add(p)
|
||||
}
|
||||
}
|
||||
|
||||
for _, group := range c.iamUserGroupMemberships[name].ToSlice() {
|
||||
if store.getUsersSysType() == MinIOUsersSysType {
|
||||
@@ -462,10 +495,12 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([
|
||||
policy, _ = c.iamGroupPolicyMap.Load(group)
|
||||
}
|
||||
|
||||
policies = append(policies, policy.toSlice()...)
|
||||
for _, p := range policy.toSlice() {
|
||||
policies.Add(p)
|
||||
}
|
||||
}
|
||||
|
||||
return policies, mp.UpdatedAt, nil
|
||||
return policies.ToSlice(), mp.UpdatedAt, nil
|
||||
}
|
||||
|
||||
func (c *iamCache) updateUserWithClaims(key string, u UserIdentity) error {
|
||||
@@ -537,25 +572,25 @@ func setDefaultCannedPolicies(policies map[string]PolicyDoc) {
|
||||
// LoadIAMCache reads all IAM items and populates a new iamCache object and
|
||||
// replaces the in-memory cache object.
|
||||
func (store *IAMStoreSys) LoadIAMCache(ctx context.Context, firstTime bool) error {
|
||||
bootstrapTraceMsg := func(s string) {
|
||||
bootstrapTraceMsgFirstTime := func(s string) {
|
||||
if firstTime {
|
||||
bootstrapTraceMsg(s)
|
||||
}
|
||||
}
|
||||
bootstrapTraceMsg("loading IAM data")
|
||||
bootstrapTraceMsgFirstTime("loading IAM data")
|
||||
|
||||
newCache := newIamCache()
|
||||
|
||||
loadedAt := time.Now()
|
||||
|
||||
if iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore); ok {
|
||||
err := iamOS.loadAllFromObjStore(ctx, newCache)
|
||||
err := iamOS.loadAllFromObjStore(ctx, newCache, firstTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
bootstrapTraceMsg("loading policy documents")
|
||||
// Only non-object IAM store (i.e. only etcd backend).
|
||||
bootstrapTraceMsgFirstTime("loading policy documents")
|
||||
if err := store.loadPolicyDocs(ctx, newCache.iamPolicyDocsMap); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -564,29 +599,29 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context, firstTime bool) erro
|
||||
setDefaultCannedPolicies(newCache.iamPolicyDocsMap)
|
||||
|
||||
if store.getUsersSysType() == MinIOUsersSysType {
|
||||
bootstrapTraceMsg("loading regular users")
|
||||
bootstrapTraceMsgFirstTime("loading regular users")
|
||||
if err := store.loadUsers(ctx, regUser, newCache.iamUsersMap); err != nil {
|
||||
return err
|
||||
}
|
||||
bootstrapTraceMsg("loading regular groups")
|
||||
bootstrapTraceMsgFirstTime("loading regular groups")
|
||||
if err := store.loadGroups(ctx, newCache.iamGroupsMap); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bootstrapTraceMsg("loading user policy mapping")
|
||||
bootstrapTraceMsgFirstTime("loading user policy mapping")
|
||||
// load polices mapped to users
|
||||
if err := store.loadMappedPolicies(ctx, regUser, false, newCache.iamUserPolicyMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bootstrapTraceMsg("loading group policy mapping")
|
||||
bootstrapTraceMsgFirstTime("loading group policy mapping")
|
||||
// load policies mapped to groups
|
||||
if err := store.loadMappedPolicies(ctx, regUser, true, newCache.iamGroupPolicyMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bootstrapTraceMsg("loading service accounts")
|
||||
bootstrapTraceMsgFirstTime("loading service accounts")
|
||||
// load service accounts
|
||||
if err := store.loadUsers(ctx, svcUser, newCache.iamUsersMap); err != nil {
|
||||
return err
|
||||
@@ -937,12 +972,7 @@ func (store *IAMStoreSys) GetGroupDescription(group string) (gd madmin.GroupDesc
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListGroups - lists groups. Since this is not going to be a frequent
|
||||
// operation, we fetch this info from storage, and refresh the cache as well.
|
||||
func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err error) {
|
||||
cache := store.lock()
|
||||
defer store.unlock()
|
||||
|
||||
func (store *IAMStoreSys) updateGroups(ctx context.Context, cache *iamCache) (res []string, err error) {
|
||||
if store.getUsersSysType() == MinIOUsersSysType {
|
||||
m := map[string]GroupInfo{}
|
||||
err = store.loadGroups(ctx, m)
|
||||
@@ -970,7 +1000,16 @@ func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err err
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ListGroups - lists groups. Since this is not going to be a frequent
|
||||
// operation, we fetch this info from storage, and refresh the cache as well.
|
||||
func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err error) {
|
||||
cache := store.lock()
|
||||
defer store.unlock()
|
||||
|
||||
return store.updateGroups(ctx, cache)
|
||||
}
|
||||
|
||||
// listGroups - lists groups - fetch groups from cache
|
||||
@@ -1445,16 +1484,51 @@ func filterPolicies(cache *iamCache, policyName string, bucketName string) (stri
|
||||
return strings.Join(policies, ","), policy.MergePolicies(toMerge...)
|
||||
}
|
||||
|
||||
// FilterPolicies - accepts a comma separated list of policy names as a string
|
||||
// and bucket and returns only policies that currently exist in MinIO. If
|
||||
// bucketName is non-empty, additionally filters policies matching the bucket.
|
||||
// The first returned value is the list of currently existing policies, and the
|
||||
// second is their combined policy definition.
|
||||
func (store *IAMStoreSys) FilterPolicies(policyName string, bucketName string) (string, policy.Policy) {
|
||||
cache := store.rlock()
|
||||
defer store.runlock()
|
||||
// MergePolicies - accepts a comma separated list of policy names as a string
|
||||
// and returns only policies that currently exist in MinIO. It includes hot loading
|
||||
// of policies if not in the memory
|
||||
func (store *IAMStoreSys) MergePolicies(policyName string) (string, policy.Policy) {
|
||||
var policies []string
|
||||
var missingPolicies []string
|
||||
var toMerge []policy.Policy
|
||||
|
||||
return filterPolicies(cache, policyName, bucketName)
|
||||
cache := store.rlock()
|
||||
for _, policy := range newMappedPolicy(policyName).toSlice() {
|
||||
if policy == "" {
|
||||
continue
|
||||
}
|
||||
p, found := cache.iamPolicyDocsMap[policy]
|
||||
if !found {
|
||||
missingPolicies = append(missingPolicies, policy)
|
||||
continue
|
||||
}
|
||||
policies = append(policies, policy)
|
||||
toMerge = append(toMerge, p.Policy)
|
||||
}
|
||||
store.runlock()
|
||||
|
||||
if len(missingPolicies) > 0 {
|
||||
m := make(map[string]PolicyDoc)
|
||||
for _, policy := range missingPolicies {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
_ = store.loadPolicyDoc(ctx, policy, m)
|
||||
cancel()
|
||||
}
|
||||
|
||||
cache := store.lock()
|
||||
for policy, p := range m {
|
||||
cache.iamPolicyDocsMap[policy] = p
|
||||
}
|
||||
store.unlock()
|
||||
|
||||
for policy, p := range m {
|
||||
policies = append(policies, policy)
|
||||
toMerge = append(toMerge, p.Policy)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return strings.Join(policies, ","), policy.MergePolicies(toMerge...)
|
||||
}
|
||||
|
||||
// GetBucketUsers - returns users (not STS or service accounts) that have access
|
||||
@@ -1907,6 +1981,11 @@ func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo {
|
||||
cache := store.rlock()
|
||||
defer store.runlock()
|
||||
|
||||
return store.getParentUsers(cache)
|
||||
}
|
||||
|
||||
// assumes store is locked by caller.
|
||||
func (store *IAMStoreSys) getParentUsers(cache *iamCache) map[string]ParentUserInfo {
|
||||
res := map[string]ParentUserInfo{}
|
||||
for _, ui := range cache.iamUsersMap {
|
||||
cred := ui.Credentials
|
||||
@@ -1977,50 +2056,96 @@ func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo {
|
||||
return res
|
||||
}
|
||||
|
||||
// Assumes store is locked by caller. If users is empty, returns all user mappings.
|
||||
func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string,
|
||||
// GetAllSTSUserMappings - Loads all STS user policy mappings from storage and
|
||||
// returns them. Also gets any STS users that do not have policy mappings but have
|
||||
// Service Accounts or STS keys (This is useful if the user is part of a group)
|
||||
func (store *IAMStoreSys) GetAllSTSUserMappings(userPredicate func(string) bool) (map[string]string, error) {
|
||||
cache := store.rlock()
|
||||
defer store.runlock()
|
||||
|
||||
stsMap := make(map[string]string)
|
||||
m := xsync.NewMapOf[string, MappedPolicy]()
|
||||
if err := store.loadMappedPolicies(context.Background(), stsUser, false, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
||||
if userPredicate != nil && !userPredicate(user) {
|
||||
return true
|
||||
}
|
||||
stsMap[user] = mappedPolicy.Policies
|
||||
return true
|
||||
})
|
||||
|
||||
for user := range store.getParentUsers(cache) {
|
||||
if _, ok := stsMap[user]; !ok {
|
||||
if userPredicate != nil && !userPredicate(user) {
|
||||
continue
|
||||
}
|
||||
stsMap[user] = ""
|
||||
}
|
||||
}
|
||||
return stsMap, nil
|
||||
}
|
||||
|
||||
// Assumes store is locked by caller. If userMap is empty, returns all user mappings.
|
||||
func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[string]set.StringSet,
|
||||
userPredicate func(string) bool,
|
||||
) []madmin.UserPolicyEntities {
|
||||
stsMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
resMap := make(map[string]madmin.UserPolicyEntities, len(userMap))
|
||||
|
||||
for user, groupSet := range userMap {
|
||||
// Attempt to load parent user mapping for STS accounts
|
||||
store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap)
|
||||
blankEntities := madmin.UserPolicyEntities{User: user}
|
||||
if !groupSet.IsEmpty() {
|
||||
blankEntities.MemberOfMappings = store.listGroupPolicyMappings(cache, groupSet, nil)
|
||||
}
|
||||
resMap[user] = blankEntities
|
||||
}
|
||||
|
||||
var r []madmin.UserPolicyEntities
|
||||
usersSet := set.CreateStringSet(users...)
|
||||
cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
||||
if userPredicate != nil && !userPredicate(user) {
|
||||
return true
|
||||
}
|
||||
|
||||
if !usersSet.IsEmpty() && !usersSet.Contains(user) {
|
||||
return true
|
||||
entitiesWithMemberOf, ok := resMap[user]
|
||||
if !ok {
|
||||
if len(userMap) > 0 {
|
||||
return true
|
||||
}
|
||||
entitiesWithMemberOf = madmin.UserPolicyEntities{User: user}
|
||||
}
|
||||
|
||||
ps := mappedPolicy.toSlice()
|
||||
sort.Strings(ps)
|
||||
r = append(r, madmin.UserPolicyEntities{
|
||||
User: user,
|
||||
Policies: ps,
|
||||
})
|
||||
entitiesWithMemberOf.Policies = ps
|
||||
resMap[user] = entitiesWithMemberOf
|
||||
return true
|
||||
})
|
||||
|
||||
stsMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
for _, user := range users {
|
||||
// Attempt to load parent user mapping for STS accounts
|
||||
store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap)
|
||||
}
|
||||
|
||||
stsMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
||||
if userPredicate != nil && !userPredicate(user) {
|
||||
return true
|
||||
}
|
||||
|
||||
entitiesWithMemberOf := resMap[user]
|
||||
|
||||
ps := mappedPolicy.toSlice()
|
||||
sort.Strings(ps)
|
||||
r = append(r, madmin.UserPolicyEntities{
|
||||
User: user,
|
||||
Policies: ps,
|
||||
})
|
||||
entitiesWithMemberOf.Policies = ps
|
||||
resMap[user] = entitiesWithMemberOf
|
||||
return true
|
||||
})
|
||||
|
||||
for _, v := range resMap {
|
||||
if v.Policies != nil || v.MemberOfMappings != nil {
|
||||
r = append(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(r, func(i, j int) bool {
|
||||
return r[i].User < r[j].User
|
||||
})
|
||||
@@ -2029,11 +2154,11 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string
|
||||
}
|
||||
|
||||
// Assumes store is locked by caller. If groups is empty, returns all group mappings.
|
||||
func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []string,
|
||||
func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set.StringSet,
|
||||
groupPredicate func(string) bool,
|
||||
) []madmin.GroupPolicyEntities {
|
||||
var r []madmin.GroupPolicyEntities
|
||||
groupsSet := set.CreateStringSet(groups...)
|
||||
|
||||
cache.iamGroupPolicyMap.Range(func(group string, mappedPolicy MappedPolicy) bool {
|
||||
if groupPredicate != nil && !groupPredicate(group) {
|
||||
return true
|
||||
@@ -2060,11 +2185,9 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []stri
|
||||
}
|
||||
|
||||
// Assumes store is locked by caller. If policies is empty, returns all policy mappings.
|
||||
func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.StringSet,
|
||||
userPredicate, groupPredicate func(string) bool,
|
||||
) []madmin.PolicyEntities {
|
||||
queryPolSet := set.CreateStringSet(policies...)
|
||||
|
||||
policyToUsersMap := make(map[string]set.StringSet)
|
||||
cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
||||
if userPredicate != nil && !userPredicate(user) {
|
||||
@@ -2194,7 +2317,7 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
}
|
||||
|
||||
// ListPolicyMappings - return users/groups mapped to policies.
|
||||
func (store *IAMStoreSys) ListPolicyMappings(q madmin.PolicyEntitiesQuery,
|
||||
func (store *IAMStoreSys) ListPolicyMappings(q cleanEntitiesQuery,
|
||||
userPredicate, groupPredicate func(string) bool,
|
||||
) madmin.PolicyEntitiesResult {
|
||||
cache := store.rlock()
|
||||
@@ -2202,7 +2325,7 @@ func (store *IAMStoreSys) ListPolicyMappings(q madmin.PolicyEntitiesQuery,
|
||||
|
||||
var result madmin.PolicyEntitiesResult
|
||||
|
||||
isAllPoliciesQuery := len(q.Users) == 0 && len(q.Groups) == 0 && len(q.Policy) == 0
|
||||
isAllPoliciesQuery := len(q.Users) == 0 && len(q.Groups) == 0 && len(q.Policies) == 0
|
||||
|
||||
if len(q.Users) > 0 {
|
||||
result.UserMappings = store.listUserPolicyMappings(cache, q.Users, userPredicate)
|
||||
@@ -2210,8 +2333,8 @@ func (store *IAMStoreSys) ListPolicyMappings(q madmin.PolicyEntitiesQuery,
|
||||
if len(q.Groups) > 0 {
|
||||
result.GroupMappings = store.listGroupPolicyMappings(cache, q.Groups, groupPredicate)
|
||||
}
|
||||
if len(q.Policy) > 0 || isAllPoliciesQuery {
|
||||
result.PolicyMappings = store.listPolicyMappings(cache, q.Policy, userPredicate, groupPredicate)
|
||||
if len(q.Policies) > 0 || isAllPoliciesQuery {
|
||||
result.PolicyMappings = store.listPolicyMappings(cache, q.Policies, userPredicate, groupPredicate)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -2638,6 +2761,18 @@ func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) error
|
||||
}
|
||||
}
|
||||
|
||||
load := len(cache.iamGroupsMap) == 0
|
||||
if store.getUsersSysType() == LDAPUsersSysType && cache.iamGroupPolicyMap.Size() == 0 {
|
||||
load = true
|
||||
}
|
||||
if load {
|
||||
if _, err = store.updateGroups(ctx, cache); err != nil {
|
||||
return "done", err
|
||||
}
|
||||
}
|
||||
|
||||
cache.buildUserGroupMemberships()
|
||||
|
||||
return "done", err
|
||||
})
|
||||
|
||||
|
||||
+91
-36
@@ -315,6 +315,24 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
||||
break
|
||||
}
|
||||
|
||||
cache := sys.store.lock()
|
||||
setDefaultCannedPolicies(cache.iamPolicyDocsMap)
|
||||
sys.store.unlock()
|
||||
|
||||
// Load RoleARNs
|
||||
sys.rolesMap = make(map[arn.ARN]string)
|
||||
|
||||
// From OpenID
|
||||
if riMap := sys.OpenIDConfig.GetRoleInfo(); riMap != nil {
|
||||
sys.validateAndAddRolePolicyMappings(ctx, riMap)
|
||||
}
|
||||
|
||||
// From AuthN plugin if enabled.
|
||||
if authn := newGlobalAuthNPluginFn(); authn != nil {
|
||||
riMap := authn.GetRoleInfo()
|
||||
sys.validateAndAddRolePolicyMappings(ctx, riMap)
|
||||
}
|
||||
|
||||
// Load IAM data from storage.
|
||||
for {
|
||||
if err := sys.Load(retryCtx, true); err != nil {
|
||||
@@ -334,20 +352,6 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
||||
|
||||
go sys.periodicRoutines(ctx, refreshInterval)
|
||||
|
||||
// Load RoleARNs
|
||||
sys.rolesMap = make(map[arn.ARN]string)
|
||||
|
||||
// From OpenID
|
||||
if riMap := sys.OpenIDConfig.GetRoleInfo(); riMap != nil {
|
||||
sys.validateAndAddRolePolicyMappings(ctx, riMap)
|
||||
}
|
||||
|
||||
// From AuthN plugin if enabled.
|
||||
if authn := newGlobalAuthNPluginFn(); authn != nil {
|
||||
riMap := authn.GetRoleInfo()
|
||||
sys.validateAndAddRolePolicyMappings(ctx, riMap)
|
||||
}
|
||||
|
||||
sys.printIAMRoles()
|
||||
|
||||
bootstrapTraceMsg("finishing IAM loading")
|
||||
@@ -436,7 +440,7 @@ func (sys *IAMSys) validateAndAddRolePolicyMappings(ctx context.Context, m map[a
|
||||
// running server by creating the policies after start up.
|
||||
for arn, rolePolicies := range m {
|
||||
specifiedPoliciesSet := newMappedPolicy(rolePolicies).policySet()
|
||||
validPolicies, _ := sys.store.FilterPolicies(rolePolicies, "")
|
||||
validPolicies, _ := sys.store.MergePolicies(rolePolicies)
|
||||
knownPoliciesSet := newMappedPolicy(validPolicies).policySet()
|
||||
unknownPoliciesSet := specifiedPoliciesSet.Difference(knownPoliciesSet)
|
||||
if len(unknownPoliciesSet) > 0 {
|
||||
@@ -672,7 +676,7 @@ func (sys *IAMSys) CurrentPolicies(policyName string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
policies, _ := sys.store.FilterPolicies(policyName, "")
|
||||
policies, _ := sys.store.MergePolicies(policyName)
|
||||
return policies
|
||||
}
|
||||
|
||||
@@ -786,11 +790,15 @@ func (sys *IAMSys) ListLDAPUsers(ctx context.Context) (map[string]madmin.UserInf
|
||||
|
||||
select {
|
||||
case <-sys.configLoaded:
|
||||
ldapUsers := make(map[string]madmin.UserInfo)
|
||||
for user, policy := range sys.store.GetUsersWithMappedPolicies() {
|
||||
stsMap, err := sys.store.GetAllSTSUserMappings(sys.LDAPConfig.IsLDAPUserDN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ldapUsers := make(map[string]madmin.UserInfo, len(stsMap))
|
||||
for user, policy := range stsMap {
|
||||
ldapUsers[user] = madmin.UserInfo{
|
||||
PolicyName: policy,
|
||||
Status: madmin.AccountEnabled,
|
||||
Status: statusEnabled,
|
||||
}
|
||||
}
|
||||
return ldapUsers, nil
|
||||
@@ -799,6 +807,57 @@ func (sys *IAMSys) ListLDAPUsers(ctx context.Context) (map[string]madmin.UserInf
|
||||
}
|
||||
}
|
||||
|
||||
type cleanEntitiesQuery struct {
|
||||
Users map[string]set.StringSet
|
||||
Groups set.StringSet
|
||||
Policies set.StringSet
|
||||
}
|
||||
|
||||
// createCleanEntitiesQuery - maps users to their groups and normalizes user or group DNs if ldap.
|
||||
func (sys *IAMSys) createCleanEntitiesQuery(q madmin.PolicyEntitiesQuery, ldap bool) cleanEntitiesQuery {
|
||||
cleanQ := cleanEntitiesQuery{
|
||||
Users: make(map[string]set.StringSet),
|
||||
Groups: set.CreateStringSet(q.Groups...),
|
||||
Policies: set.CreateStringSet(q.Policy...),
|
||||
}
|
||||
|
||||
if ldap {
|
||||
// Validate and normalize users, then fetch and normalize their groups
|
||||
// Also include unvalidated users for backward compatibility.
|
||||
for _, user := range q.Users {
|
||||
lookupRes, actualGroups, _ := sys.LDAPConfig.GetValidatedDNWithGroups(user)
|
||||
if lookupRes != nil {
|
||||
groupSet := set.CreateStringSet(actualGroups...)
|
||||
|
||||
// duplicates can be overwritten, fetched groups should be identical.
|
||||
cleanQ.Users[lookupRes.NormDN] = groupSet
|
||||
}
|
||||
// Search for non-normalized DN as well for backward compatibility.
|
||||
if _, ok := cleanQ.Users[user]; !ok {
|
||||
cleanQ.Users[user] = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Validate and normalize groups.
|
||||
for _, group := range q.Groups {
|
||||
lookupRes, underDN, _ := sys.LDAPConfig.GetValidatedGroupDN(nil, group)
|
||||
if lookupRes != nil && !underDN {
|
||||
cleanQ.Groups.Add(lookupRes.NormDN)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, user := range q.Users {
|
||||
info, err := sys.store.GetUserInfo(user)
|
||||
var groupSet set.StringSet
|
||||
if err == nil {
|
||||
groupSet = set.CreateStringSet(info.MemberOf...)
|
||||
}
|
||||
cleanQ.Users[user] = groupSet
|
||||
}
|
||||
}
|
||||
return cleanQ
|
||||
}
|
||||
|
||||
// QueryLDAPPolicyEntities - queries policy associations for LDAP users/groups/policies.
|
||||
func (sys *IAMSys) QueryLDAPPolicyEntities(ctx context.Context, q madmin.PolicyEntitiesQuery) (*madmin.PolicyEntitiesResult, error) {
|
||||
if !sys.Initialized() {
|
||||
@@ -811,7 +870,8 @@ func (sys *IAMSys) QueryLDAPPolicyEntities(ctx context.Context, q madmin.PolicyE
|
||||
|
||||
select {
|
||||
case <-sys.configLoaded:
|
||||
pe := sys.store.ListPolicyMappings(q, sys.LDAPConfig.IsLDAPUserDN, sys.LDAPConfig.IsLDAPGroupDN)
|
||||
cleanQuery := sys.createCleanEntitiesQuery(q, true)
|
||||
pe := sys.store.ListPolicyMappings(cleanQuery, sys.LDAPConfig.IsLDAPUserDN, sys.LDAPConfig.IsLDAPGroupDN)
|
||||
pe.Timestamp = UTCNow()
|
||||
return &pe, nil
|
||||
case <-ctx.Done():
|
||||
@@ -885,6 +945,7 @@ func (sys *IAMSys) QueryPolicyEntities(ctx context.Context, q madmin.PolicyEntit
|
||||
|
||||
select {
|
||||
case <-sys.configLoaded:
|
||||
cleanQuery := sys.createCleanEntitiesQuery(q, false)
|
||||
var userPredicate, groupPredicate func(string) bool
|
||||
if sys.LDAPConfig.Enabled() {
|
||||
userPredicate = func(s string) bool {
|
||||
@@ -894,7 +955,7 @@ func (sys *IAMSys) QueryPolicyEntities(ctx context.Context, q madmin.PolicyEntit
|
||||
return !sys.LDAPConfig.IsLDAPGroupDN(s)
|
||||
}
|
||||
}
|
||||
pe := sys.store.ListPolicyMappings(q, userPredicate, groupPredicate)
|
||||
pe := sys.store.ListPolicyMappings(cleanQuery, userPredicate, groupPredicate)
|
||||
pe.Timestamp = UTCNow()
|
||||
return &pe, nil
|
||||
case <-ctx.Done():
|
||||
@@ -2118,7 +2179,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args policy.Args, parentUser string)
|
||||
var combinedPolicy policy.Policy
|
||||
// Policies were found, evaluate all of them.
|
||||
if !isOwnerDerived {
|
||||
availablePoliciesStr, c := sys.store.FilterPolicies(strings.Join(svcPolicies, ","), "")
|
||||
availablePoliciesStr, c := sys.store.MergePolicies(strings.Join(svcPolicies, ","))
|
||||
if availablePoliciesStr == "" {
|
||||
return false
|
||||
}
|
||||
@@ -2210,22 +2271,16 @@ func (sys *IAMSys) IsAllowedSTS(args policy.Args, parentUser string) bool {
|
||||
// 2. Combine the mapped policies into a single combined policy.
|
||||
|
||||
var combinedPolicy policy.Policy
|
||||
// Policies were found, evaluate all of them.
|
||||
if !isOwnerDerived {
|
||||
var err error
|
||||
combinedPolicy, err = sys.store.GetPolicy(strings.Join(policies, ","))
|
||||
if errors.Is(err, errNoSuchPolicy) {
|
||||
for _, pname := range policies {
|
||||
_, err := sys.store.GetPolicy(pname)
|
||||
if errors.Is(err, errNoSuchPolicy) {
|
||||
// all policies presented in the claim should exist
|
||||
iamLogIf(GlobalContext, fmt.Errorf("expected policy (%s) missing from the JWT claim %s, rejecting the request", pname, iamPolicyClaimNameOpenID()))
|
||||
return false
|
||||
}
|
||||
}
|
||||
iamLogIf(GlobalContext, fmt.Errorf("all policies were unexpectedly present!"))
|
||||
availablePoliciesStr, c := sys.store.MergePolicies(strings.Join(policies, ","))
|
||||
if availablePoliciesStr == "" {
|
||||
// all policies presented in the claim should exist
|
||||
iamLogIf(GlobalContext, fmt.Errorf("expected policy (%s) missing from the JWT claim %s, rejecting the request", policies, iamPolicyClaimNameOpenID()))
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
combinedPolicy = c
|
||||
}
|
||||
|
||||
// 3. If an inline session-policy is present, evaluate it.
|
||||
@@ -2346,7 +2401,7 @@ func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowe
|
||||
|
||||
// GetCombinedPolicy returns a combined policy combining all policies
|
||||
func (sys *IAMSys) GetCombinedPolicy(policies ...string) policy.Policy {
|
||||
_, policy := sys.store.FilterPolicies(strings.Join(policies, ","), "")
|
||||
_, policy := sys.store.MergePolicies(strings.Join(policies, ","))
|
||||
return policy
|
||||
}
|
||||
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
// Copyright (c) 2015-2023 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
const (
|
||||
licUpdateCycle = 24 * time.Hour * 30
|
||||
licRenewPath = "/api/cluster/renew-license"
|
||||
)
|
||||
|
||||
// initlicenseUpdateJob start the periodic license update job in the background.
|
||||
func initLicenseUpdateJob(ctx context.Context, objAPI ObjectLayer) {
|
||||
go func() {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
// Leader node (that successfully acquires the lock inside licenceUpdaterLoop)
|
||||
// will keep performing the license update. If the leader goes down for some
|
||||
// reason, the lock will be released and another node will acquire it and
|
||||
// take over because of this loop.
|
||||
for {
|
||||
licenceUpdaterLoop(ctx, objAPI)
|
||||
|
||||
// license update stopped for some reason.
|
||||
// sleep for some time and try again.
|
||||
duration := time.Duration(r.Float64() * float64(time.Hour))
|
||||
if duration < time.Second {
|
||||
// Make sure to sleep at least a second to avoid high CPU ticks.
|
||||
duration = time.Second
|
||||
}
|
||||
time.Sleep(duration)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func licenceUpdaterLoop(ctx context.Context, objAPI ObjectLayer) {
|
||||
ctx, cancel := globalLeaderLock.GetLock(ctx)
|
||||
defer cancel()
|
||||
|
||||
licenseUpdateTimer := time.NewTimer(licUpdateCycle)
|
||||
defer licenseUpdateTimer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-licenseUpdateTimer.C:
|
||||
|
||||
if globalSubnetConfig.Registered() {
|
||||
performLicenseUpdate(ctx, objAPI)
|
||||
}
|
||||
|
||||
// Reset the timer for next cycle.
|
||||
licenseUpdateTimer.Reset(licUpdateCycle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func performLicenseUpdate(ctx context.Context, objectAPI ObjectLayer) {
|
||||
// the subnet license renewal api renews the license only
|
||||
// if required e.g. when it is expiring soon
|
||||
url := globalSubnetConfig.BaseURL + licRenewPath
|
||||
|
||||
resp, err := globalSubnetConfig.Post(url, nil)
|
||||
if err != nil {
|
||||
subnetLogIf(ctx, fmt.Errorf("error from %s: %w", url, err))
|
||||
return
|
||||
}
|
||||
|
||||
r := gjson.Parse(resp).Get("license_v2")
|
||||
if r.Index == 0 {
|
||||
internalLogIf(ctx, fmt.Errorf("license not found in response from %s", url))
|
||||
return
|
||||
}
|
||||
|
||||
lic := r.String()
|
||||
if lic == globalSubnetConfig.License {
|
||||
// license hasn't changed.
|
||||
return
|
||||
}
|
||||
|
||||
kv := "subnet license=" + lic
|
||||
result, err := setConfigKV(ctx, objectAPI, []byte(kv))
|
||||
if err != nil {
|
||||
internalLogIf(ctx, fmt.Errorf("error setting subnet license config: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if result.Dynamic {
|
||||
if err := applyDynamicConfigForSubSys(GlobalContext, objectAPI, result.Cfg, result.SubSys); err != nil {
|
||||
subnetLogIf(ctx, fmt.Errorf("error applying subnet dynamic config: %w", err))
|
||||
return
|
||||
}
|
||||
globalNotificationSys.SignalConfigReload(result.SubSys)
|
||||
}
|
||||
}
|
||||
@@ -184,10 +184,6 @@ func etcdLogOnceIf(ctx context.Context, err error, id string, errKind ...interfa
|
||||
logger.LogOnceIf(ctx, "etcd", err, id, errKind...)
|
||||
}
|
||||
|
||||
func subnetLogIf(ctx context.Context, err error, errKind ...interface{}) {
|
||||
logger.LogIf(ctx, "subnet", err, errKind...)
|
||||
}
|
||||
|
||||
func metricsLogIf(ctx context.Context, err error, errKind ...interface{}) {
|
||||
logger.LogIf(ctx, "metrics", err, errKind...)
|
||||
}
|
||||
|
||||
@@ -532,6 +532,9 @@ func (m *metaCacheEntriesSorted) fileInfoVersions(bucket, prefix, delimiter, aft
|
||||
}
|
||||
|
||||
for _, version := range fiVersions {
|
||||
if !version.VersionPurgeStatus().Empty() {
|
||||
continue
|
||||
}
|
||||
versioned := vcfg != nil && vcfg.Versioned(entry.name)
|
||||
versions = append(versions, version.ToObjectInfo(bucket, entry.name, versioned))
|
||||
}
|
||||
@@ -593,7 +596,7 @@ func (m *metaCacheEntriesSorted) fileInfos(bucket, prefix, delimiter string) (ob
|
||||
}
|
||||
|
||||
fi, err := entry.fileInfo(bucket)
|
||||
if err == nil {
|
||||
if err == nil && fi.VersionPurgeStatus().Empty() {
|
||||
versioned := vcfg != nil && vcfg.Versioned(entry.name)
|
||||
objects = append(objects, fi.ToObjectInfo(bucket, entry.name, versioned))
|
||||
}
|
||||
|
||||
@@ -751,6 +751,12 @@ func isErrSignatureDoesNotMatch(err error) bool {
|
||||
return errors.As(err, &signatureDoesNotMatch)
|
||||
}
|
||||
|
||||
// isErrObjectNameInvalid - Check if error type is ObjectNameInvalid.
|
||||
func isErrObjectNameInvalid(err error) bool {
|
||||
var invalidObject ObjectNameInvalid
|
||||
return errors.As(err, &invalidObject)
|
||||
}
|
||||
|
||||
// PreConditionFailed - Check if copy precondition failed
|
||||
type PreConditionFailed struct{}
|
||||
|
||||
|
||||
@@ -502,7 +502,7 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
||||
perr error
|
||||
)
|
||||
// avoid proxying if version is a delete marker
|
||||
if !isErrMethodNotAllowed(err) && !(gr != nil && gr.ObjInfo.DeleteMarker) {
|
||||
if !isErrObjectNameInvalid(err) && !isErrMethodNotAllowed(err) && !(gr != nil && gr.ObjInfo.DeleteMarker) {
|
||||
proxytgts := getProxyTargets(ctx, bucket, object, opts)
|
||||
if !proxytgts.Empty() {
|
||||
globalReplicationStats.incProxy(bucket, getObjectAPI, false)
|
||||
@@ -1028,7 +1028,7 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
|
||||
|
||||
objInfo, err := getObjectInfo(ctx, bucket, object, opts)
|
||||
var proxy proxyResult
|
||||
if err != nil && !objInfo.DeleteMarker && !isErrMethodNotAllowed(err) {
|
||||
if err != nil && !objInfo.DeleteMarker && !isErrMethodNotAllowed(err) && !isErrObjectNameInvalid(err) {
|
||||
// proxy HEAD to replication target if active-active replication configured on bucket
|
||||
proxytgts := getProxyTargets(ctx, bucket, object, opts)
|
||||
if !proxytgts.Empty() {
|
||||
|
||||
@@ -692,8 +692,8 @@ func testAPIGetObjectWithMPHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
{"small-1", []int64{509}, make(map[string]string)},
|
||||
{"small-2", []int64{5 * oneMiB}, make(map[string]string)},
|
||||
// // // cases 4-7: multipart part objects
|
||||
{"mp-0", []int64{5 * oneMiB, 1}, make(map[string]string)},
|
||||
{"mp-1", []int64{5*oneMiB + 1, 1}, make(map[string]string)},
|
||||
{"mp-0", []int64{5 * oneMiB, 10}, make(map[string]string)},
|
||||
{"mp-1", []int64{5*oneMiB + 1, 10}, make(map[string]string)},
|
||||
{"mp-2", []int64{5487701, 5487799, 3}, make(map[string]string)},
|
||||
{"mp-3", []int64{10499807, 10499963, 7}, make(map[string]string)},
|
||||
// cases 8-11: small single part objects with encryption
|
||||
@@ -702,17 +702,13 @@ func testAPIGetObjectWithMPHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
{"enc-small-1", []int64{509}, mapCopy(metaWithSSEC)},
|
||||
{"enc-small-2", []int64{5 * oneMiB}, mapCopy(metaWithSSEC)},
|
||||
// cases 12-15: multipart part objects with encryption
|
||||
{"enc-mp-0", []int64{5 * oneMiB, 1}, mapCopy(metaWithSSEC)},
|
||||
{"enc-mp-1", []int64{5*oneMiB + 1, 1}, mapCopy(metaWithSSEC)},
|
||||
{"enc-mp-0", []int64{5 * oneMiB, 10}, mapCopy(metaWithSSEC)},
|
||||
{"enc-mp-1", []int64{5*oneMiB + 1, 10}, mapCopy(metaWithSSEC)},
|
||||
{"enc-mp-2", []int64{5487701, 5487799, 3}, mapCopy(metaWithSSEC)},
|
||||
{"enc-mp-3", []int64{10499807, 10499963, 7}, mapCopy(metaWithSSEC)},
|
||||
}
|
||||
// SSEC can't be used with compression
|
||||
globalCompressConfigMu.Lock()
|
||||
globalCompressEnabled := globalCompressConfig.Enabled
|
||||
globalCompressConfigMu.Unlock()
|
||||
if globalCompressEnabled {
|
||||
objectInputs = objectInputs[0:8]
|
||||
if testing.Short() {
|
||||
objectInputs = append(objectInputs[0:5], objectInputs[8:11]...)
|
||||
}
|
||||
// iterate through the above set of inputs and upload the object.
|
||||
for _, input := range objectInputs {
|
||||
@@ -768,6 +764,7 @@ func testAPIGetObjectWithMPHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
readers = append(readers, NewDummyDataGen(p, cumulativeSum))
|
||||
cumulativeSum += p
|
||||
}
|
||||
|
||||
refReader := io.LimitReader(ioutilx.NewSkipReader(io.MultiReader(readers...), off), length)
|
||||
if ok, msg := cmpReaders(refReader, rec.Body); !ok {
|
||||
t.Fatalf("(%s) Object: %s Case %d ByteRange: %s --> data mismatch! (msg: %s)", instanceType, oi.objectName, i+1, byteRange, msg)
|
||||
|
||||
@@ -199,7 +199,7 @@ func fwdStatusToAPIError(resp *http.Response) *APIError {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetObjectLamdbaHandler - GET Object with transformed data via lambda functions
|
||||
// GetObjectLambdaHandler - GET Object with transformed data via lambda functions
|
||||
// ----------
|
||||
// This implementation of the GET operation applies lambda functions and returns the
|
||||
// response generated via the lambda functions. To use this API, you must have READ access
|
||||
|
||||
@@ -559,21 +559,17 @@ func execExtended(t *testing.T, fn func(t *testing.T, init func(), bucketOptions
|
||||
t.Run("default", func(t *testing.T) {
|
||||
fn(t, nil, MakeBucketOptions{})
|
||||
})
|
||||
t.Run("defaultVerioned", func(t *testing.T) {
|
||||
t.Run("default+versioned", func(t *testing.T) {
|
||||
fn(t, nil, MakeBucketOptions{VersioningEnabled: true})
|
||||
})
|
||||
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("compressed", func(t *testing.T) {
|
||||
fn(t, func() {
|
||||
resetCompressEncryption()
|
||||
enableCompression(t, false, []string{"*"}, []string{"*"})
|
||||
}, MakeBucketOptions{})
|
||||
})
|
||||
t.Run("compressedVerioned", func(t *testing.T) {
|
||||
t.Run("compressed+versioned", func(t *testing.T) {
|
||||
fn(t, func() {
|
||||
resetCompressEncryption()
|
||||
enableCompression(t, false, []string{"*"}, []string{"*"})
|
||||
@@ -588,7 +584,7 @@ func execExtended(t *testing.T, fn func(t *testing.T, init func(), bucketOptions
|
||||
enableEncryption(t)
|
||||
}, MakeBucketOptions{})
|
||||
})
|
||||
t.Run("encryptedVerioned", func(t *testing.T) {
|
||||
t.Run("encrypted+versioned", func(t *testing.T) {
|
||||
fn(t, func() {
|
||||
resetCompressEncryption()
|
||||
enableEncryption(t)
|
||||
@@ -603,7 +599,7 @@ func execExtended(t *testing.T, fn func(t *testing.T, init func(), bucketOptions
|
||||
enableCompression(t, true, []string{"*"}, []string{"*"})
|
||||
}, MakeBucketOptions{})
|
||||
})
|
||||
t.Run("compressed+encryptedVerioned", func(t *testing.T) {
|
||||
t.Run("compressed+encrypted+versioned", func(t *testing.T) {
|
||||
fn(t, func() {
|
||||
resetCompressEncryption()
|
||||
enableCompression(t, true, []string{"*"}, []string{"*"})
|
||||
|
||||
@@ -364,7 +364,7 @@ func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) erro
|
||||
for key := range checkHeader {
|
||||
logKeys = append(logKeys, key)
|
||||
}
|
||||
return fmt.Errorf("Each form field that you specify in a form (except %s) must appear in the list of conditions.", strings.Join(logKeys, ", "))
|
||||
return fmt.Errorf("Each form field that you specify in a form must appear in the list of policy conditions. %q not specified in the policy.", strings.Join(logKeys, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ func registerDistErasureRouters(router *mux.Router, endpointServerPools Endpoint
|
||||
registerLockRESTHandlers()
|
||||
|
||||
// Add grid to router
|
||||
router.Handle(grid.RoutePath, adminMiddleware(globalGrid.Load().Handler(), noGZFlag, noObjLayerFlag))
|
||||
router.Handle(grid.RoutePath, adminMiddleware(globalGrid.Load().Handler(storageServerRequestValidate), noGZFlag, noObjLayerFlag))
|
||||
}
|
||||
|
||||
// List of some generic middlewares which are applied for all incoming requests.
|
||||
|
||||
+9
-13
@@ -897,7 +897,7 @@ func serverMain(ctx *cli.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
if !globalDisableFreezeOnBoot {
|
||||
if globalEnableSyncBoot {
|
||||
// Freeze the services until the bucket notification subsystem gets initialized.
|
||||
bootstrapTrace("freezeServices", freezeServices)
|
||||
}
|
||||
@@ -1000,10 +1000,11 @@ func serverMain(ctx *cli.Context) {
|
||||
}()
|
||||
|
||||
go func() {
|
||||
if !globalDisableFreezeOnBoot {
|
||||
if globalEnableSyncBoot {
|
||||
defer bootstrapTrace("unfreezeServices", unfreezeServices)
|
||||
t := time.AfterFunc(5*time.Minute, func() {
|
||||
warnings = append(warnings, color.YellowBold("- Initializing the config subsystem is taking longer than 5 minutes. Please set '_MINIO_DISABLE_API_FREEZE_ON_BOOT=true' to not freeze the APIs"))
|
||||
warnings = append(warnings,
|
||||
color.YellowBold("- Initializing the config subsystem is taking longer than 5 minutes. Please remove 'MINIO_SYNC_BOOT=on' to not freeze the APIs"))
|
||||
})
|
||||
defer t.Stop()
|
||||
}
|
||||
@@ -1029,16 +1030,6 @@ func serverMain(ctx *cli.Context) {
|
||||
globalTransitionState.Init(newObject)
|
||||
})
|
||||
|
||||
// Initialize batch job pool.
|
||||
bootstrapTrace("newBatchJobPool", func() {
|
||||
globalBatchJobPool = newBatchJobPool(GlobalContext, newObject, 100)
|
||||
})
|
||||
|
||||
// Initialize the license update job
|
||||
bootstrapTrace("initLicenseUpdateJob", func() {
|
||||
initLicenseUpdateJob(GlobalContext, newObject)
|
||||
})
|
||||
|
||||
go func() {
|
||||
// Initialize transition tier configuration manager
|
||||
bootstrapTrace("globalTierConfigMgr.Init", func() {
|
||||
@@ -1103,6 +1094,11 @@ func serverMain(ctx *cli.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// Initialize batch job pool.
|
||||
bootstrapTrace("newBatchJobPool", func() {
|
||||
globalBatchJobPool = newBatchJobPool(GlobalContext, newObject, 100)
|
||||
})
|
||||
|
||||
// Prints the formatted startup message, if err is not nil then it prints additional information as well.
|
||||
printStartupMessage(getAPIEndpoints(), err)
|
||||
|
||||
|
||||
@@ -82,11 +82,7 @@ func setMaxResources(ctx serverCtxt) (err error) {
|
||||
}
|
||||
|
||||
if ctx.MemLimit > 0 {
|
||||
maxLimit = ctx.MemLimit
|
||||
}
|
||||
|
||||
if maxLimit > 0 {
|
||||
debug.SetMemoryLimit(int64(maxLimit))
|
||||
debug.SetMemoryLimit(int64(ctx.MemLimit))
|
||||
}
|
||||
|
||||
// Do not use RLIMIT_AS as that is not useful and at times on systems < 4Gi
|
||||
|
||||
+3
-1
@@ -161,11 +161,13 @@ internalAuth:
|
||||
return nil, errNoSuchUser
|
||||
}
|
||||
|
||||
if caPublicKey != nil {
|
||||
if caPublicKey != nil && pass == nil {
|
||||
|
||||
err := validateKey(c, key)
|
||||
if err != nil {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Temporary credentials are not allowed.
|
||||
|
||||
+18
-9
@@ -194,9 +194,12 @@ func (s *TestSuiteIAM) SFTPInvalidServiceAccountPassword(c *check) {
|
||||
c.Fatalf("Unable to set user: %v", err)
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, "readwrite", accessKey, false)
|
||||
if err != nil {
|
||||
c.Fatalf("unable to set policy: %v", err)
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{"readwrite"},
|
||||
User: accessKey,
|
||||
}
|
||||
if _, err := s.adm.AttachPolicy(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
newSSHCon := newSSHConnMock(accessKey + "=svc")
|
||||
@@ -222,9 +225,12 @@ func (s *TestSuiteIAM) SFTPServiceAccountLogin(c *check) {
|
||||
c.Fatalf("Unable to set user: %v", err)
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, "readwrite", accessKey, false)
|
||||
if err != nil {
|
||||
c.Fatalf("unable to set policy: %v", err)
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{"readwrite"},
|
||||
User: accessKey,
|
||||
}
|
||||
if _, err := s.adm.AttachPolicy(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
newSSHCon := newSSHConnMock(accessKey + "=svc")
|
||||
@@ -270,9 +276,12 @@ func (s *TestSuiteIAM) SFTPValidLDAPLoginWithPassword(c *check) {
|
||||
}
|
||||
|
||||
userDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io"
|
||||
err = s.adm.SetPolicy(ctx, policy, userDN, false)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: userDN,
|
||||
}
|
||||
if _, err := s.adm.AttachPolicy(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
newSSHCon := newSSHConnMock("dillon=ldap")
|
||||
|
||||
+44
-14
@@ -2250,10 +2250,18 @@ func (c *SiteReplicationSys) toErrorFromErrMap(errMap map[string]error, actionNa
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get ordered list of keys of errMap
|
||||
keys := []string{}
|
||||
for d := range errMap {
|
||||
keys = append(keys, d)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var success int
|
||||
msgs := []string{}
|
||||
for d, err := range errMap {
|
||||
for _, d := range keys {
|
||||
name := c.state.Peers[d].Name
|
||||
err := errMap[d]
|
||||
if err == nil {
|
||||
msgs = append(msgs, fmt.Sprintf("'%s' on site %s (%s): succeeded", actionName, name, d))
|
||||
success++
|
||||
@@ -2261,7 +2269,7 @@ func (c *SiteReplicationSys) toErrorFromErrMap(errMap map[string]error, actionNa
|
||||
msgs = append(msgs, fmt.Sprintf("'%s' on site %s (%s): failed(%v)", actionName, name, d, err))
|
||||
}
|
||||
}
|
||||
if success == len(errMap) {
|
||||
if success == len(keys) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Site replication error(s): \n%s", strings.Join(msgs, "\n"))
|
||||
@@ -5225,7 +5233,7 @@ func (c *SiteReplicationSys) healBucketReplicationConfig(ctx context.Context, ob
|
||||
}
|
||||
|
||||
if replMismatch {
|
||||
replLogIf(ctx, c.annotateErr(configureReplication, c.PeerBucketConfigureReplHandler(ctx, bucket)))
|
||||
replLogOnceIf(ctx, c.annotateErr(configureReplication, c.PeerBucketConfigureReplHandler(ctx, bucket)), "heal-bucket-relication-config")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -5318,7 +5326,10 @@ func (c *SiteReplicationSys) healPolicies(ctx context.Context, objAPI ObjectLaye
|
||||
UpdatedAt: lastUpdate,
|
||||
})
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal IAM policy %s from peer site %s -> site %s : %w", policy, latestPeerName, peerName, err))
|
||||
replLogOnceIf(
|
||||
ctx,
|
||||
fmt.Errorf("Unable to heal IAM policy %s from peer site %s -> site %s : %w", policy, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-policy-%s", policy))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -5379,7 +5390,8 @@ func (c *SiteReplicationSys) healUserPolicies(ctx context.Context, objAPI Object
|
||||
UpdatedAt: lastUpdate,
|
||||
})
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal IAM user policy mapping for %s from peer site %s -> site %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx, fmt.Errorf("Unable to heal IAM user policy mapping for %s from peer site %s -> site %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-policy-%s", user))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -5442,7 +5454,9 @@ func (c *SiteReplicationSys) healGroupPolicies(ctx context.Context, objAPI Objec
|
||||
UpdatedAt: lastUpdate,
|
||||
})
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal IAM group policy mapping for %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal IAM group policy mapping for %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-group-policy-%s", group))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -5503,13 +5517,17 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
if creds.IsServiceAccount() {
|
||||
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, creds.AccessKey)
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
continue
|
||||
}
|
||||
|
||||
_, policy, err := globalIAMSys.GetServiceAccount(ctx, creds.AccessKey)
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -5517,7 +5535,9 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
if policy != nil {
|
||||
policyJSON, err = json.Marshal(policy)
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -5540,7 +5560,9 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -5553,7 +5575,9 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
// policy. The session token will contain info about policy to
|
||||
// be applied.
|
||||
if !errors.Is(err, errNoSuchUser) {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
@@ -5571,7 +5595,9 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -5587,7 +5613,9 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal user %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal user %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -5651,7 +5679,9 @@ func (c *SiteReplicationSys) healGroups(ctx context.Context, objAPI ObjectLayer,
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal group %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal group %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-group-%s", group))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
+20
-13
@@ -109,6 +109,24 @@ func (s *storageRESTServer) writeErrorResponse(w http.ResponseWriter, err error)
|
||||
// DefaultSkewTime - skew time is 15 minutes between minio peers.
|
||||
const DefaultSkewTime = 15 * time.Minute
|
||||
|
||||
// validateStorageRequestToken will validate the token against the provided audience.
|
||||
func validateStorageRequestToken(token, audience string) error {
|
||||
claims := xjwt.NewStandardClaims()
|
||||
if err := xjwt.ParseWithStandardClaims(token, claims, []byte(globalActiveCred.SecretKey)); err != nil {
|
||||
return errAuthentication
|
||||
}
|
||||
|
||||
owner := claims.AccessKey == globalActiveCred.AccessKey || claims.Subject == globalActiveCred.AccessKey
|
||||
if !owner {
|
||||
return errAuthentication
|
||||
}
|
||||
|
||||
if claims.Audience != audience {
|
||||
return errAuthentication
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authenticates storage client's requests and validates for skewed time.
|
||||
func storageServerRequestValidate(r *http.Request) error {
|
||||
token, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(r)
|
||||
@@ -118,19 +136,8 @@ func storageServerRequestValidate(r *http.Request) error {
|
||||
}
|
||||
return errMalformedAuth
|
||||
}
|
||||
|
||||
claims := xjwt.NewStandardClaims()
|
||||
if err = xjwt.ParseWithStandardClaims(token, claims, []byte(globalActiveCred.SecretKey)); err != nil {
|
||||
return errAuthentication
|
||||
}
|
||||
|
||||
owner := claims.AccessKey == globalActiveCred.AccessKey || claims.Subject == globalActiveCred.AccessKey
|
||||
if !owner {
|
||||
return errAuthentication
|
||||
}
|
||||
|
||||
if claims.Audience != r.URL.RawQuery {
|
||||
return errAuthentication
|
||||
if err = validateStorageRequestToken(token, r.URL.RawQuery); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requestTimeStr := r.Header.Get("X-Minio-Time")
|
||||
|
||||
+207
-56
@@ -116,9 +116,12 @@ func (s *TestSuiteIAM) TestSTSServiceAccountsWithUsername(c *check) {
|
||||
c.Fatalf("policy add error: %v", err)
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, policy, "dillon", false)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: "dillon",
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("Unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
assumeRole := cr.STSAssumeRole{
|
||||
@@ -231,9 +234,12 @@ func (s *TestSuiteIAM) TestSTSWithDenyDeleteVersion(c *check) {
|
||||
c.Fatalf("Unable to set user: %v", err)
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("Unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
// confirm that the user is able to access the bucket
|
||||
@@ -332,9 +338,12 @@ func (s *TestSuiteIAM) TestSTSWithTags(c *check) {
|
||||
c.Fatalf("Unable to set user: %v", err)
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("Unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
// confirm that the user is able to access the bucket
|
||||
@@ -420,9 +429,12 @@ func (s *TestSuiteIAM) TestSTS(c *check) {
|
||||
c.Fatalf("Unable to set user: %v", err)
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("Unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
// confirm that the user is able to access the bucket
|
||||
@@ -515,9 +527,12 @@ func (s *TestSuiteIAM) TestSTSWithGroupPolicy(c *check) {
|
||||
c.Fatalf("unable to add user to group: %v", err)
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, policy, "test-group", true)
|
||||
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
Group: "test-group",
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
c.Fatalf("Unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
// confirm that the user is able to access the bucket - permission comes
|
||||
@@ -718,6 +733,7 @@ func TestIAMWithLDAPServerSuite(t *testing.T) {
|
||||
suite.SetUpSuite(c)
|
||||
suite.SetUpLDAP(c, ldapServer)
|
||||
suite.TestLDAPSTS(c)
|
||||
suite.TestLDAPPolicyEntitiesLookup(c)
|
||||
suite.TestLDAPUnicodeVariations(c)
|
||||
suite.TestLDAPSTSServiceAccounts(c)
|
||||
suite.TestLDAPSTSServiceAccountsWithUsername(c)
|
||||
@@ -749,6 +765,7 @@ func TestIAMWithLDAPNonNormalizedBaseDNConfigServerSuite(t *testing.T) {
|
||||
suite.SetUpSuite(c)
|
||||
suite.SetUpLDAPWithNonNormalizedBaseDN(c, ldapServer)
|
||||
suite.TestLDAPSTS(c)
|
||||
suite.TestLDAPPolicyEntitiesLookup(c)
|
||||
suite.TestLDAPUnicodeVariations(c)
|
||||
suite.TestLDAPSTSServiceAccounts(c)
|
||||
suite.TestLDAPSTSServiceAccountsWithUsername(c)
|
||||
@@ -984,6 +1001,7 @@ func (s *TestSuiteIAM) TestIAMExport(c *check, caseNum int, content iamTestConte
|
||||
}
|
||||
|
||||
for userDN, policies := range content.ldapUserPolicyMappings {
|
||||
// No need to detach, we are starting from a clean slate after exporting.
|
||||
_, err := s.adm.AttachPolicyLDAP(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: policies,
|
||||
User: userDN,
|
||||
@@ -1194,14 +1212,21 @@ func (s *TestSuiteIAM) TestLDAPSTS(c *check) {
|
||||
|
||||
// Attempting to set a non-existent policy should fail.
|
||||
userDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io"
|
||||
err = s.adm.SetPolicy(ctx, policy+"x", userDN, false)
|
||||
_, err = s.adm.AttachPolicyLDAP(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy + "x"},
|
||||
User: userDN,
|
||||
})
|
||||
if err == nil {
|
||||
c.Fatalf("should not be able to set non-existent policy")
|
||||
c.Fatalf("should not be able to attach non-existent policy")
|
||||
}
|
||||
|
||||
err = s.adm.SetPolicy(ctx, policy, userDN, false)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: userDN,
|
||||
}
|
||||
|
||||
if _, err = s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to attach user policy: %v", err)
|
||||
}
|
||||
|
||||
value, err := ldapID.Retrieve()
|
||||
@@ -1240,10 +1265,8 @@ func (s *TestSuiteIAM) TestLDAPSTS(c *check) {
|
||||
c.Fatalf("unexpected non-access-denied err: %v", err)
|
||||
}
|
||||
|
||||
// Remove the policy assignment on the user DN:
|
||||
err = s.adm.SetPolicy(ctx, "", userDN, false)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to remove policy setting: %v", err)
|
||||
if _, err = s.adm.DetachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to detach user policy: %v", err)
|
||||
}
|
||||
|
||||
_, err = ldapID.Retrieve()
|
||||
@@ -1253,9 +1276,13 @@ func (s *TestSuiteIAM) TestLDAPSTS(c *check) {
|
||||
|
||||
// Set policy via group and validate policy assignment.
|
||||
groupDN := "cn=projectb,ou=groups,ou=swengg,dc=min,dc=io"
|
||||
err = s.adm.SetPolicy(ctx, policy, groupDN, true)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set group policy: %v", err)
|
||||
groupReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
Group: groupDN,
|
||||
}
|
||||
|
||||
if _, err = s.adm.AttachPolicyLDAP(ctx, groupReq); err != nil {
|
||||
c.Fatalf("Unable to attach group policy: %v", err)
|
||||
}
|
||||
|
||||
value, err = ldapID.Retrieve()
|
||||
@@ -1278,6 +1305,10 @@ func (s *TestSuiteIAM) TestLDAPSTS(c *check) {
|
||||
// Validate that the client cannot remove any objects
|
||||
err = minioClient.RemoveObject(ctx, bucket, "someobject", minio.RemoveObjectOptions{})
|
||||
c.Assert(err.Error(), "Access Denied.")
|
||||
|
||||
if _, err = s.adm.DetachPolicyLDAP(ctx, groupReq); err != nil {
|
||||
c.Fatalf("Unable to detach group policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestLDAPUnicodeVariationsLegacyAPI(c *check) {
|
||||
@@ -1490,12 +1521,13 @@ func (s *TestSuiteIAM) TestLDAPUnicodeVariations(c *check) {
|
||||
// \uFE52 is the unicode dot SMALL FULL STOP used below:
|
||||
userDNWithUnicodeDot := "uid=svc﹒algorithm,OU=swengg,DC=min,DC=io"
|
||||
|
||||
_, err = s.adm.AttachPolicyLDAP(ctx, madmin.PolicyAssociationReq{
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: userDNWithUnicodeDot,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
}
|
||||
|
||||
if _, err = s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to attach user policy: %v", err)
|
||||
}
|
||||
|
||||
value, err := ldapID.Retrieve()
|
||||
@@ -1534,12 +1566,9 @@ func (s *TestSuiteIAM) TestLDAPUnicodeVariations(c *check) {
|
||||
}
|
||||
|
||||
// Remove the policy assignment on the user DN:
|
||||
_, err = s.adm.DetachPolicyLDAP(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: userDNWithUnicodeDot,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to remove policy setting: %v", err)
|
||||
|
||||
if _, err = s.adm.DetachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to detach user policy: %v", err)
|
||||
}
|
||||
|
||||
_, err = ldapID.Retrieve()
|
||||
@@ -1550,11 +1579,12 @@ func (s *TestSuiteIAM) TestLDAPUnicodeVariations(c *check) {
|
||||
// Set policy via group and validate policy assignment.
|
||||
actualGroupDN := mustNormalizeDN("cn=project.c,ou=groups,ou=swengg,dc=min,dc=io")
|
||||
groupDNWithUnicodeDot := "cn=project﹒c,ou=groups,ou=swengg,dc=min,dc=io"
|
||||
_, err = s.adm.AttachPolicyLDAP(ctx, madmin.PolicyAssociationReq{
|
||||
groupReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
Group: groupDNWithUnicodeDot,
|
||||
})
|
||||
if err != nil {
|
||||
}
|
||||
|
||||
if _, err = s.adm.AttachPolicyLDAP(ctx, groupReq); err != nil {
|
||||
c.Fatalf("Unable to attach group policy: %v", err)
|
||||
}
|
||||
|
||||
@@ -1594,6 +1624,10 @@ func (s *TestSuiteIAM) TestLDAPUnicodeVariations(c *check) {
|
||||
// Validate that the client cannot remove any objects
|
||||
err = minioClient.RemoveObject(ctx, bucket, "someobject", minio.RemoveObjectOptions{})
|
||||
c.Assert(err.Error(), "Access Denied.")
|
||||
|
||||
if _, err = s.adm.DetachPolicyLDAP(ctx, groupReq); err != nil {
|
||||
c.Fatalf("Unable to detach group policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestLDAPSTSServiceAccounts(c *check) {
|
||||
@@ -1630,9 +1664,13 @@ func (s *TestSuiteIAM) TestLDAPSTSServiceAccounts(c *check) {
|
||||
}
|
||||
|
||||
userDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io"
|
||||
err = s.adm.SetPolicy(ctx, policy, userDN, false)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: userDN,
|
||||
}
|
||||
|
||||
if _, err = s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to attach user policy: %v", err)
|
||||
}
|
||||
|
||||
ldapID := cr.LDAPIdentity{
|
||||
@@ -1687,6 +1725,11 @@ func (s *TestSuiteIAM) TestLDAPSTSServiceAccounts(c *check) {
|
||||
|
||||
// 6. Check that service account cannot be created for some other user.
|
||||
c.mustNotCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
|
||||
|
||||
// Detach the policy from the user
|
||||
if _, err = s.adm.DetachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to detach user policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestLDAPSTSServiceAccountsWithUsername(c *check) {
|
||||
@@ -1707,12 +1750,12 @@ func (s *TestSuiteIAM) TestLDAPSTSServiceAccountsWithUsername(c *check) {
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:PutObject",
|
||||
"s3:GetObject",
|
||||
"s3:ListBucket"
|
||||
"s3:PutObject",
|
||||
"s3:GetObject",
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::${ldap:username}/*"
|
||||
"arn:aws:s3:::${ldap:username}/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1723,9 +1766,14 @@ func (s *TestSuiteIAM) TestLDAPSTSServiceAccountsWithUsername(c *check) {
|
||||
}
|
||||
|
||||
userDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io"
|
||||
err = s.adm.SetPolicy(ctx, policy, userDN, false)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: userDN,
|
||||
}
|
||||
|
||||
if _, err = s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to attach user policy: %v", err)
|
||||
}
|
||||
|
||||
ldapID := cr.LDAPIdentity{
|
||||
@@ -1776,6 +1824,10 @@ func (s *TestSuiteIAM) TestLDAPSTSServiceAccountsWithUsername(c *check) {
|
||||
|
||||
// 3. Check S3 access for download
|
||||
c.mustDownload(ctx, svcClient, bucket)
|
||||
|
||||
if _, err = s.adm.DetachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to detach user policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// In this test, the parent users gets their permissions from a group, rather
|
||||
@@ -1814,9 +1866,13 @@ func (s *TestSuiteIAM) TestLDAPSTSServiceAccountsWithGroups(c *check) {
|
||||
}
|
||||
|
||||
groupDN := "cn=projecta,ou=groups,ou=swengg,dc=min,dc=io"
|
||||
err = s.adm.SetPolicy(ctx, policy, groupDN, true)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
Group: groupDN,
|
||||
}
|
||||
|
||||
if _, err = s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to attach user policy: %v", err)
|
||||
}
|
||||
|
||||
ldapID := cr.LDAPIdentity{
|
||||
@@ -1871,18 +1927,24 @@ func (s *TestSuiteIAM) TestLDAPSTSServiceAccountsWithGroups(c *check) {
|
||||
|
||||
// 6. Check that service account cannot be created for some other user.
|
||||
c.mustNotCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
|
||||
|
||||
// Detach the user policy
|
||||
if _, err = s.adm.DetachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to detach user policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestLDAPCyrillicUser(c *check) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := s.adm.AttachPolicyLDAP(ctx, madmin.PolicyAssociationReq{
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{"readwrite"},
|
||||
User: "uid=Пользователь,ou=people,ou=swengg,dc=min,dc=io",
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
}
|
||||
|
||||
if _, err := s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to attach user policy: %v", err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
@@ -1940,6 +2002,10 @@ func (s *TestSuiteIAM) TestLDAPCyrillicUser(c *check) {
|
||||
c.Fatalf("Test %d: unexpected dn claim: %s", i+1, dnClaim)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = s.adm.DetachPolicyLDAP(ctx, userReq); err != nil {
|
||||
c.Fatalf("Unable to detach user policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestLDAPAttributesLookup(c *check) {
|
||||
@@ -1947,12 +2013,13 @@ func (s *TestSuiteIAM) TestLDAPAttributesLookup(c *check) {
|
||||
defer cancel()
|
||||
|
||||
groupDN := "cn=projectb,ou=groups,ou=swengg,dc=min,dc=io"
|
||||
_, err := s.adm.AttachPolicyLDAP(ctx, madmin.PolicyAssociationReq{
|
||||
groupReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{"readwrite"},
|
||||
Group: groupDN,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
}
|
||||
|
||||
if _, err := s.adm.AttachPolicyLDAP(ctx, groupReq); err != nil {
|
||||
c.Fatalf("Unable to attach user policy: %v", err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
@@ -2025,6 +2092,90 @@ func (s *TestSuiteIAM) TestLDAPAttributesLookup(c *check) {
|
||||
c.Fatalf("Test %d: unexpected sshPublicKey type: %s", i+1, parts[0])
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = s.adm.DetachPolicyLDAP(ctx, groupReq); err != nil {
|
||||
c.Fatalf("Unable to detach group policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestLDAPPolicyEntitiesLookup(c *check) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
groupDN := "cn=projectb,ou=groups,ou=swengg,dc=min,dc=io"
|
||||
groupPolicy := "readwrite"
|
||||
groupReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{groupPolicy},
|
||||
Group: groupDN,
|
||||
}
|
||||
_, err := s.adm.AttachPolicyLDAP(ctx, groupReq)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to attach group policy: %v", err)
|
||||
}
|
||||
type caseTemplate struct {
|
||||
inDN string
|
||||
expectedOutDN string
|
||||
expectedGroupDN string
|
||||
expectedGroupPolicy string
|
||||
}
|
||||
cases := []caseTemplate{
|
||||
{
|
||||
inDN: "uid=dillon,ou=people,ou=swengg,dc=min,dc=io",
|
||||
expectedOutDN: "uid=dillon,ou=people,ou=swengg,dc=min,dc=io",
|
||||
expectedGroupDN: groupDN,
|
||||
expectedGroupPolicy: groupPolicy,
|
||||
},
|
||||
}
|
||||
|
||||
policy := "readonly"
|
||||
for _, testCase := range cases {
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: testCase.inDN,
|
||||
}
|
||||
_, err := s.adm.AttachPolicyLDAP(ctx, userReq)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
entities, err := s.adm.GetLDAPPolicyEntities(ctx, madmin.PolicyEntitiesQuery{
|
||||
Users: []string{testCase.inDN},
|
||||
Policy: []string{policy},
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to fetch policy entities: %v", err)
|
||||
}
|
||||
|
||||
// switch statement to check all the conditions
|
||||
switch {
|
||||
case len(entities.UserMappings) != 1:
|
||||
c.Fatalf("Expected to find exactly one user mapping")
|
||||
case entities.UserMappings[0].User != testCase.expectedOutDN:
|
||||
c.Fatalf("Expected user DN `%s`, found `%s`", testCase.expectedOutDN, entities.UserMappings[0].User)
|
||||
case len(entities.UserMappings[0].Policies) != 1:
|
||||
c.Fatalf("Expected exactly one policy attached to user")
|
||||
case entities.UserMappings[0].Policies[0] != policy:
|
||||
c.Fatalf("Expected attached policy `%s`, found `%s`", policy, entities.UserMappings[0].Policies[0])
|
||||
case len(entities.UserMappings[0].MemberOfMappings) != 1:
|
||||
c.Fatalf("Expected exactly one group attached to user")
|
||||
case entities.UserMappings[0].MemberOfMappings[0].Group != testCase.expectedGroupDN:
|
||||
c.Fatalf("Expected attached group `%s`, found `%s`", testCase.expectedGroupDN, entities.UserMappings[0].MemberOfMappings[0].Group)
|
||||
case len(entities.UserMappings[0].MemberOfMappings[0].Policies) != 1:
|
||||
c.Fatalf("Expected exactly one policy attached to group")
|
||||
case entities.UserMappings[0].MemberOfMappings[0].Policies[0] != testCase.expectedGroupPolicy:
|
||||
c.Fatalf("Expected attached policy `%s`, found `%s`", testCase.expectedGroupPolicy, entities.UserMappings[0].MemberOfMappings[0].Policies[0])
|
||||
}
|
||||
|
||||
_, err = s.adm.DetachPolicyLDAP(ctx, userReq)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to detach policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = s.adm.DetachPolicyLDAP(ctx, groupReq)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to detach group policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestOpenIDSTS(c *check) {
|
||||
|
||||
@@ -64,6 +64,12 @@ var (
|
||||
Message: "Specified remote backend is not empty",
|
||||
StatusCode: http.StatusBadRequest,
|
||||
}
|
||||
|
||||
errTierInvalidConfig = AdminError{
|
||||
Code: "XMinioAdminTierInvalidConfig",
|
||||
Message: "Unable to setup remote tier, check tier configuration",
|
||||
StatusCode: http.StatusBadRequest,
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -98,12 +98,6 @@ func TestReleaseTagToNFromTimeConversion(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDownloadURL(t *testing.T) {
|
||||
sci := globalIsCICD
|
||||
globalIsCICD = false
|
||||
defer func() {
|
||||
globalIsCICD = sci
|
||||
}()
|
||||
|
||||
minioVersion1 := releaseTimeToReleaseTag(UTCNow())
|
||||
durl := getDownloadURL(minioVersion1)
|
||||
if IsDocker() {
|
||||
@@ -164,9 +158,6 @@ func TestUserAgent(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
sci := globalIsCICD
|
||||
globalIsCICD = false
|
||||
|
||||
if testCase.envName != "" {
|
||||
t.Setenv(testCase.envName, testCase.envValue)
|
||||
if testCase.envName == "MESOS_CONTAINER_NAME" {
|
||||
@@ -182,7 +173,6 @@ func TestUserAgent(t *testing.T) {
|
||||
if !strings.Contains(str, expectedStr) {
|
||||
t.Errorf("Test %d: expected: %s, got: %s", i+1, expectedStr, str)
|
||||
}
|
||||
globalIsCICD = sci
|
||||
os.Unsetenv("MARATHON_APP_LABEL_DCOS_PACKAGE_VERSION")
|
||||
os.Unsetenv(testCase.envName)
|
||||
}
|
||||
@@ -190,12 +180,6 @@ func TestUserAgent(t *testing.T) {
|
||||
|
||||
// Tests if the environment we are running is in DCOS.
|
||||
func TestIsDCOS(t *testing.T) {
|
||||
sci := globalIsCICD
|
||||
globalIsCICD = false
|
||||
defer func() {
|
||||
globalIsCICD = sci
|
||||
}()
|
||||
|
||||
t.Setenv("MESOS_CONTAINER_NAME", "mesos-1111")
|
||||
dcos := IsDCOS()
|
||||
if !dcos {
|
||||
@@ -210,12 +194,6 @@ func TestIsDCOS(t *testing.T) {
|
||||
|
||||
// Tests if the environment we are running is in kubernetes.
|
||||
func TestIsKubernetes(t *testing.T) {
|
||||
sci := globalIsCICD
|
||||
globalIsCICD = false
|
||||
defer func() {
|
||||
globalIsCICD = sci
|
||||
}()
|
||||
|
||||
t.Setenv("KUBERNETES_SERVICE_HOST", "10.11.148.5")
|
||||
kubernetes := IsKubernetes()
|
||||
if !kubernetes {
|
||||
|
||||
+2
-1
@@ -144,7 +144,8 @@ func newWarmBackend(ctx context.Context, tier madmin.TierConfig, probe bool) (d
|
||||
return nil, errTierTypeUnsupported
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errTierTypeUnsupported
|
||||
tierLogIf(ctx, err)
|
||||
return nil, errTierInvalidConfig
|
||||
}
|
||||
|
||||
if probe {
|
||||
|
||||
+24
-20
@@ -236,30 +236,17 @@ func newXLStorage(ep Endpoint, cleanUp bool) (s *xlStorage, err error) {
|
||||
return s, err
|
||||
}
|
||||
|
||||
info, err := disk.GetInfo(s.drivePath, true)
|
||||
info, rootDrive, err := getDiskInfo(s.drivePath)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
s.major = info.Major
|
||||
s.minor = info.Minor
|
||||
s.fsType = info.FSType
|
||||
|
||||
if !globalIsCICD && !globalIsErasureSD {
|
||||
var rootDrive bool
|
||||
if globalRootDiskThreshold > 0 {
|
||||
// Use MINIO_ROOTDISK_THRESHOLD_SIZE to figure out if
|
||||
// this disk is a root disk. treat those disks with
|
||||
// size less than or equal to the threshold as rootDrives.
|
||||
rootDrive = info.Total <= globalRootDiskThreshold
|
||||
} else {
|
||||
rootDrive, err = disk.IsRootDisk(s.drivePath, SlashSeparator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if rootDrive {
|
||||
return s, errDriveIsRoot
|
||||
}
|
||||
if rootDrive {
|
||||
return s, errDriveIsRoot
|
||||
}
|
||||
|
||||
// Sanitize before setting it
|
||||
@@ -333,10 +320,11 @@ func newXLStorage(ep Endpoint, cleanUp bool) (s *xlStorage, err error) {
|
||||
s.diskInfoCache.InitOnce(time.Second, cachevalue.Opts{},
|
||||
func(ctx context.Context) (DiskInfo, error) {
|
||||
dcinfo := DiskInfo{}
|
||||
di, err := getDiskInfo(s.drivePath)
|
||||
di, root, err := getDiskInfo(s.drivePath)
|
||||
if err != nil {
|
||||
return dcinfo, err
|
||||
}
|
||||
dcinfo.RootDisk = root
|
||||
dcinfo.Major = di.Major
|
||||
dcinfo.Minor = di.Minor
|
||||
dcinfo.Total = di.Total
|
||||
@@ -345,6 +333,10 @@ func newXLStorage(ep Endpoint, cleanUp bool) (s *xlStorage, err error) {
|
||||
dcinfo.UsedInodes = di.Files - di.Ffree
|
||||
dcinfo.FreeInodes = di.Ffree
|
||||
dcinfo.FSType = di.FSType
|
||||
if root {
|
||||
return dcinfo, errDriveIsRoot
|
||||
}
|
||||
|
||||
diskID, err := s.GetDiskID()
|
||||
// Healing is 'true' when
|
||||
// - if we found an unformatted disk (no 'format.json')
|
||||
@@ -360,10 +352,22 @@ func newXLStorage(ep Endpoint, cleanUp bool) (s *xlStorage, err error) {
|
||||
}
|
||||
|
||||
// getDiskInfo returns given disk information.
|
||||
func getDiskInfo(drivePath string) (di disk.Info, err error) {
|
||||
func getDiskInfo(drivePath string) (di disk.Info, rootDrive bool, err error) {
|
||||
if err = checkPathLength(drivePath); err == nil {
|
||||
di, err = disk.GetInfo(drivePath, false)
|
||||
|
||||
if !globalIsCICD && !globalIsErasureSD {
|
||||
if globalRootDiskThreshold > 0 {
|
||||
// Use MINIO_ROOTDISK_THRESHOLD_SIZE to figure out if
|
||||
// this disk is a root disk. treat those disks with
|
||||
// size less than or equal to the threshold as rootDrives.
|
||||
rootDrive = di.Total <= globalRootDiskThreshold
|
||||
} else {
|
||||
rootDrive, err = disk.IsRootDisk(drivePath, SlashSeparator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case osIsNotExist(err):
|
||||
err = errDiskNotFound
|
||||
@@ -373,7 +377,7 @@ func getDiskInfo(drivePath string) (di disk.Info, err error) {
|
||||
err = errFaultyDisk
|
||||
}
|
||||
|
||||
return di, err
|
||||
return
|
||||
}
|
||||
|
||||
// Implements stringer compatible interface.
|
||||
|
||||
@@ -196,7 +196,7 @@ func TestXLStorageGetDiskInfo(t *testing.T) {
|
||||
|
||||
// Check test cases.
|
||||
for _, testCase := range testCases {
|
||||
if _, err := getDiskInfo(testCase.diskPath); err != testCase.expectedErr {
|
||||
if _, _, err := getDiskInfo(testCase.diskPath); err != testCase.expectedErr {
|
||||
t.Fatalf("expected: %s, got: %s", testCase.expectedErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ When an object has only one version as a delete marker, the latter can be automa
|
||||
{
|
||||
"ID": "Removing all delete markers",
|
||||
"Expiration": {
|
||||
"DeleteMarker": true
|
||||
"ExpiredObjectDeleteMarker": true
|
||||
},
|
||||
"Status": "Enabled"
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@ export MINIO_ROOT_USER="minioadmin"
|
||||
export MINIO_ROOT_PASSWORD="minioadmin"
|
||||
|
||||
./minio server --address ":9001" /tmp/xl/1/{1...4}/ 2>&1 >/tmp/dc1.log &
|
||||
pid1=$!
|
||||
./minio server --address ":9002" /tmp/xl/2/{1...4}/ 2>&1 >/tmp/dc2.log &
|
||||
pid2=$!
|
||||
|
||||
sleep 3
|
||||
|
||||
@@ -69,6 +71,8 @@ export MC_HOST_myminio2=http://minioadmin:minioadmin@localhost:9002
|
||||
|
||||
./mc replicate add myminio1/testbucket --remote-bucket http://minioadmin:minioadmin@localhost:9002/testbucket/ --priority 1
|
||||
|
||||
# Test replication of delete markers and permanent deletes
|
||||
|
||||
./mc cp README.md myminio1/testbucket/dir/file
|
||||
./mc cp README.md myminio1/testbucket/dir/file
|
||||
|
||||
@@ -111,5 +115,33 @@ if [ $ret -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test listing of non replicated permanent deletes
|
||||
|
||||
set -x
|
||||
|
||||
./mc mb myminio1/foobucket/ myminio2/foobucket/ --with-versioning
|
||||
./mc replicate add myminio1/foobucket --remote-bucket http://minioadmin:minioadmin@localhost:9002/foobucket/ --priority 1
|
||||
./mc cp README.md myminio1/foobucket/dir/file
|
||||
|
||||
versionId="$(./mc ls --json --versions myminio1/foobucket/dir/ | jq -r .versionId)"
|
||||
|
||||
kill ${pid2} && wait ${pid2} || true
|
||||
|
||||
aws s3api --endpoint-url http://localhost:9001 --profile minioadmin delete-object --bucket foobucket --key dir/file --version-id "$versionId"
|
||||
|
||||
out="$(./mc ls myminio1/foobucket/dir/)"
|
||||
if [ "$out" != "" ]; then
|
||||
echo "BUG: non versioned listing should not show pending/failed replicated delete:"
|
||||
echo "$out"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
out="$(./mc ls --versions myminio1/foobucket/dir/)"
|
||||
if [ "$out" != "" ]; then
|
||||
echo "BUG: versioned listing should not show pending/failed replicated deletes:"
|
||||
echo "$out"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Success"
|
||||
catch
|
||||
|
||||
@@ -43,8 +43,8 @@ unset MINIO_KMS_KES_KEY_FILE
|
||||
unset MINIO_KMS_KES_ENDPOINT
|
||||
unset MINIO_KMS_KES_KEY_NAME
|
||||
|
||||
wget -q -O mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
|
||||
chmod +x mc
|
||||
go install -v github.com/minio/mc@master
|
||||
cp -a $(go env GOPATH)/bin/mc ./mc
|
||||
|
||||
if [ ! -f mc.RELEASE.2021-03-12T03-36-59Z ]; then
|
||||
wget -q -O mc.RELEASE.2021-03-12T03-36-59Z https://dl.minio.io/client/mc/release/linux-amd64/archive/mc.RELEASE.2021-03-12T03-36-59Z &&
|
||||
|
||||
@@ -74,7 +74,7 @@ pools:
|
||||
- Each pool expects a minimum of 2 nodes per pool, and unique non-repeating hosts for each argument.
|
||||
- Each pool expects each host in this pool has the same number of drives specified as any other host.
|
||||
- Mixing `local-path` and `distributed-path` is not allowed, doing so would cause MinIO to refuse starting the server.
|
||||
- Ellipses notation (e.g. `{1...10}`) or bracket notations are fully allowed (e.g. `{a,c,f}`) to have multiple entries in one line.
|
||||
- Ellipses and bracket notation (e.g. `{1...10}`) are allowed.
|
||||
|
||||
> NOTE: MinIO environmental variables still take precedence over the `config.yaml` file, however `config.yaml` is preferred over MinIO internal config KV settings via `mc admin config set alias/ <sub-system>`.
|
||||
|
||||
@@ -88,3 +88,4 @@ In subsequent releases we are planning to extend this to provide things like
|
||||
and decommissioning to provide a functionality that smaller deployments
|
||||
care about.
|
||||
|
||||
- Fully allow bracket notation (e.g. `{a,c,f}`) to have multiple entries on one line.
|
||||
@@ -22,6 +22,12 @@ export MINIO_CI_CD=1
|
||||
if [ ! -f ./mc ]; then
|
||||
os="$(uname -s)"
|
||||
arch="$(uname -m)"
|
||||
case "${arch}" in
|
||||
"x86_64")
|
||||
arch="amd64"
|
||||
;;
|
||||
esac
|
||||
|
||||
wget -O mc https://dl.minio.io/client/mc/release/${os,,}-${arch,,}/mc &&
|
||||
chmod +x mc
|
||||
fi
|
||||
|
||||
@@ -8,10 +8,8 @@ pkill minio
|
||||
pkill kes
|
||||
rm -rf /tmp/xl
|
||||
|
||||
if [ ! -f ./mc ]; then
|
||||
wget --quiet -O mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
|
||||
chmod +x mc
|
||||
fi
|
||||
go install -v github.com/minio/mc@master
|
||||
cp -a $(go env GOPATH)/bin/mc ./mc
|
||||
|
||||
if [ ! -f ./kes ]; then
|
||||
wget --quiet -O kes https://github.com/minio/kes/releases/latest/download/kes-linux-amd64 &&
|
||||
@@ -39,37 +37,37 @@ export MC_HOST_myminio="http://minioadmin:minioadmin@localhost:9000/"
|
||||
(minio server http://localhost:9000/tmp/xl/{1...10}/disk{0...1} 2>&1 >/dev/null) &
|
||||
pid=$!
|
||||
|
||||
./mc ready myminio
|
||||
mc ready myminio
|
||||
|
||||
./mc admin user add myminio/ minio123 minio123
|
||||
mc admin user add myminio/ minio123 minio123
|
||||
|
||||
./mc admin policy create myminio/ deny-non-sse-kms-pol ./docs/iam/policies/deny-non-sse-kms-objects.json
|
||||
./mc admin policy create myminio/ deny-invalid-sse-kms-pol ./docs/iam/policies/deny-objects-with-invalid-sse-kms-key-id.json
|
||||
mc admin policy create myminio/ deny-non-sse-kms-pol ./docs/iam/policies/deny-non-sse-kms-objects.json
|
||||
mc admin policy create myminio/ deny-invalid-sse-kms-pol ./docs/iam/policies/deny-objects-with-invalid-sse-kms-key-id.json
|
||||
|
||||
./mc admin policy attach myminio deny-non-sse-kms-pol --user minio123
|
||||
./mc admin policy attach myminio deny-invalid-sse-kms-pol --user minio123
|
||||
./mc admin policy attach myminio consoleAdmin --user minio123
|
||||
mc admin policy attach myminio deny-non-sse-kms-pol --user minio123
|
||||
mc admin policy attach myminio deny-invalid-sse-kms-pol --user minio123
|
||||
mc admin policy attach myminio consoleAdmin --user minio123
|
||||
|
||||
./mc mb -l myminio/test-bucket
|
||||
./mc mb -l myminio/multi-key-poc
|
||||
mc mb -l myminio/test-bucket
|
||||
mc mb -l myminio/multi-key-poc
|
||||
|
||||
export MC_HOST_myminio1="http://minio123:minio123@localhost:9000/"
|
||||
|
||||
./mc cp /etc/issue myminio1/test-bucket
|
||||
mc cp /etc/issue myminio1/test-bucket
|
||||
ret=$?
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo "BUG: PutObject to bucket: test-bucket should succeed. Failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./mc cp /etc/issue myminio1/multi-key-poc | grep -q "Insufficient permissions to access this path"
|
||||
mc cp /etc/issue myminio1/multi-key-poc | grep -q "Insufficient permissions to access this path"
|
||||
ret=$?
|
||||
if [ $ret -eq 0 ]; then
|
||||
echo "BUG: PutObject to bucket: multi-key-poc without sse-kms should fail. Succedded"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./mc cp /etc/hosts myminio1/multi-key-poc/hosts --enc-kms "myminio1/multi-key-poc/hosts=minio-default-key"
|
||||
mc cp /etc/hosts myminio1/multi-key-poc/hosts --enc-kms "myminio1/multi-key-poc/hosts=minio-default-key"
|
||||
ret=$?
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo "BUG: PutObject to bucket: multi-key-poc with valid sse-kms should succeed. Failed"
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
# Settings and configurations that are common for all containers
|
||||
x-minio-common: &minio-common
|
||||
image: quay.io/minio/minio:RELEASE.2024-06-13T22-53-53Z
|
||||
image: quay.io/minio/minio:RELEASE.2024-07-04T14-25-45Z
|
||||
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
||||
expose:
|
||||
- "9000"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# How to enable 'minio' performance profile with tuned?
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Please make sure the following packages are already installed via `dnf` or `apt`:
|
||||
|
||||
- `tuned`
|
||||
- `curl`
|
||||
|
||||
### Install `tuned.conf` performance profile
|
||||
|
||||
#### Step 1 - download `tuned.conf` from the referenced link
|
||||
```
|
||||
wget https://raw.githubusercontent.com/minio/minio/master/docs/tuning/tuned.conf
|
||||
```
|
||||
|
||||
#### Step 2 - install tuned.conf as supported performance profile on all nodes
|
||||
```
|
||||
sudo mkdir -p /usr/lib/tuned/minio/
|
||||
sudo mv tuned.conf /usr/lib/tuned/minio
|
||||
```
|
||||
|
||||
#### Step 3 - to enable minio performance profile on all the nodes
|
||||
```
|
||||
sudo tuned-admin profile minio
|
||||
```
|
||||
@@ -0,0 +1,81 @@
|
||||
[main]
|
||||
summary=Maximum server performance for MinIO
|
||||
|
||||
[vm]
|
||||
transparent_hugepage=madvise
|
||||
transparent_hugepage.defrag=defer+madvise
|
||||
transparent_hugepage.khugepaged.max_ptes_none=0
|
||||
|
||||
[cpu]
|
||||
force_latency=1
|
||||
governor=performance
|
||||
energy_perf_bias=performance
|
||||
min_perf_pct=100
|
||||
|
||||
[sysctl]
|
||||
fs.xfs.xfssyncd_centisecs=72000
|
||||
net.core.busy_read=50
|
||||
net.core.busy_poll=50
|
||||
kernel.numa_balancing=1
|
||||
|
||||
# Do not use swap at all
|
||||
vm.swappiness=0
|
||||
vm.vfs_cache_pressure=50
|
||||
|
||||
# Start writeback at 3% memory
|
||||
vm.dirty_background_ratio=3
|
||||
# Force writeback at 10% memory
|
||||
vm.dirty_ratio=10
|
||||
|
||||
# Quite a few memory map
|
||||
# areas may be consumed
|
||||
vm.max_map_count=524288
|
||||
|
||||
# Default is 500000 = 0.5ms
|
||||
kernel.sched_migration_cost_ns=5000000
|
||||
|
||||
# stalled hdd io threads
|
||||
kernel.hung_task_timeout_secs=85
|
||||
|
||||
# network tuning for bigger throughput
|
||||
net.core.netdev_max_backlog=250000
|
||||
net.core.somaxconn=16384
|
||||
net.ipv4.tcp_syncookies=0
|
||||
net.ipv4.tcp_max_syn_backlog=16384
|
||||
net.core.wmem_max=4194304
|
||||
net.core.rmem_max=4194304
|
||||
net.core.rmem_default=4194304
|
||||
net.core.wmem_default=4194304
|
||||
net.ipv4.tcp_rmem="4096 87380 4194304"
|
||||
net.ipv4.tcp_wmem="4096 65536 4194304"
|
||||
|
||||
# Reduce CPU utilization
|
||||
net.ipv4.tcp_timestamps=0
|
||||
|
||||
# Increase throughput
|
||||
net.ipv4.tcp_sack=1
|
||||
|
||||
# Low latency mode for TCP
|
||||
net.ipv4.tcp_low_latency=1
|
||||
|
||||
# The following variable is used to tell the kernel how
|
||||
# much of the socket buffer space should be used for TCP
|
||||
# window size, and how much to save for an application buffer.
|
||||
net.ipv4.tcp_adv_win_scale=1
|
||||
|
||||
# disable RFC2861 behavior
|
||||
net.ipv4.tcp_slow_start_after_idle = 0
|
||||
|
||||
# Fix faulty network setups
|
||||
net.ipv4.tcp_mtu_probing=1
|
||||
net.ipv4.tcp_base_mss=1280
|
||||
|
||||
# Disable ipv6
|
||||
net.ipv6.conf.all.disable_ipv6=1
|
||||
net.ipv6.conf.default.disable_ipv6=1
|
||||
net.ipv6.conf.lo.disable_ipv6=1
|
||||
|
||||
[bootloader]
|
||||
# Avoid firing timers for all CPUs at the same time. This is irrelevant for
|
||||
# full nohz systems
|
||||
cmdline=skew_tick=1
|
||||
@@ -43,19 +43,19 @@ require (
|
||||
github.com/klauspost/reedsolomon v1.12.1
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/miekg/dns v1.1.61
|
||||
github.com/minio/cli v1.24.2
|
||||
github.com/minio/console v1.6.0
|
||||
github.com/minio/console v1.6.1
|
||||
github.com/minio/csvparser v1.0.0
|
||||
github.com/minio/dnscache v0.1.1
|
||||
github.com/minio/dperf v0.5.3
|
||||
github.com/minio/highwayhash v1.0.2
|
||||
github.com/minio/kms-go/kes v0.3.0
|
||||
github.com/minio/kms-go/kms v0.4.0
|
||||
github.com/minio/madmin-go/v3 v3.0.55
|
||||
github.com/minio/minio-go/v7 v7.0.72-0.20240610154810-fa174cbf14b0
|
||||
github.com/minio/madmin-go/v3 v3.0.58
|
||||
github.com/minio/minio-go/v7 v7.0.73
|
||||
github.com/minio/mux v1.9.0
|
||||
github.com/minio/pkg/v3 v3.0.2
|
||||
github.com/minio/pkg/v3 v3.0.3
|
||||
github.com/minio/selfupdate v0.6.0
|
||||
github.com/minio/simdjson-go v0.4.5
|
||||
github.com/minio/sio v0.4.0
|
||||
@@ -63,7 +63,7 @@ require (
|
||||
github.com/minio/zipindex v0.3.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/nats-io/nats-server/v2 v2.9.23
|
||||
github.com/nats-io/nats.go v1.35.0
|
||||
github.com/nats-io/nats.go v1.36.0
|
||||
github.com/nats-io/stan.go v0.10.4
|
||||
github.com/ncw/directio v1.0.5
|
||||
github.com/nsqio/go-nsq v1.1.0
|
||||
@@ -74,15 +74,14 @@ require (
|
||||
github.com/pkg/xattr v0.4.9
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/prometheus/client_model v0.6.1
|
||||
github.com/prometheus/common v0.54.0
|
||||
github.com/prometheus/common v0.55.0
|
||||
github.com/prometheus/procfs v0.15.1
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0
|
||||
github.com/puzpuzpuz/xsync/v3 v3.2.0
|
||||
github.com/rabbitmq/amqp091-go v1.10.0
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
|
||||
github.com/rs/cors v1.11.0
|
||||
github.com/secure-io/sio-go v0.3.1
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/tidwall/gjson v1.17.1
|
||||
github.com/tinylib/msgp v1.1.9
|
||||
github.com/valyala/bytebufferpool v1.0.0
|
||||
github.com/xdg/scram v1.0.5
|
||||
@@ -93,13 +92,13 @@ require (
|
||||
go.uber.org/zap v1.27.0
|
||||
goftp.io/server/v2 v2.0.1
|
||||
golang.org/x/crypto v0.24.0
|
||||
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||
golang.org/x/oauth2 v0.21.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/sys v0.21.0
|
||||
golang.org/x/term v0.21.0
|
||||
golang.org/x/time v0.5.0
|
||||
google.golang.org/api v0.184.0
|
||||
google.golang.org/api v0.187.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -108,10 +107,10 @@ require (
|
||||
aead.dev/mem v0.2.0 // indirect
|
||||
aead.dev/minisign v0.3.0 // indirect
|
||||
cloud.google.com/go v0.115.0 // indirect
|
||||
cloud.google.com/go/auth v0.5.1 // indirect
|
||||
cloud.google.com/go/auth v0.6.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.4.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.10 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
@@ -127,7 +126,7 @@ require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/charmbracelet/bubbles v0.18.0 // indirect
|
||||
github.com/charmbracelet/bubbletea v0.26.4 // indirect
|
||||
github.com/charmbracelet/bubbletea v0.26.6 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.11.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.1.2 // indirect
|
||||
github.com/charmbracelet/x/input v0.1.2 // indirect
|
||||
@@ -145,6 +144,7 @@ require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/frankban/quicktest v1.14.4 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
@@ -165,12 +165,12 @@ require (
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
|
||||
github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
|
||||
github.com/gorilla/websocket v1.5.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-hclog v1.2.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
@@ -183,7 +183,7 @@ require (
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9 // indirect
|
||||
github.com/jessevdk/go-flags v1.5.0 // indirect
|
||||
github.com/jessevdk/go-flags v1.6.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/juju/ratelimit v1.0.2 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
@@ -204,7 +204,7 @@ require (
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/minio/colorjson v1.0.8 // indirect
|
||||
github.com/minio/filepath v1.0.0 // indirect
|
||||
github.com/minio/mc v0.0.0-20240612143403-e7c9a733c680 // indirect
|
||||
github.com/minio/mc v0.0.0-20240702213905-74032bc16a3f // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/pkg/v2 v2.0.19 // indirect
|
||||
github.com/minio/websocket v1.6.0 // indirect
|
||||
@@ -216,6 +216,7 @@ require (
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/jwt/v2 v2.5.0 // indirect
|
||||
github.com/nats-io/nats-streaming-server v0.24.3 // indirect
|
||||
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||
@@ -229,34 +230,34 @@ require (
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rjeczalik/notify v0.9.3 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/safchain/ethtool v0.3.0 // indirect
|
||||
github.com/safchain/ethtool v0.4.1 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tidwall/gjson v1.17.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/unrolled/secure v1.14.0 // indirect
|
||||
github.com/unrolled/secure v1.15.0 // indirect
|
||||
github.com/vbauerster/mpb/v8 v8.7.3 // indirect
|
||||
github.com/xdg/stringprep v1.0.3 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect
|
||||
go.mongodb.org/mongo-driver v1.15.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.16.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
|
||||
go.opentelemetry.io/otel v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240610135401-a8a62080eff3 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
google.golang.org/grpc v1.65.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
|
||||
@@ -6,16 +6,16 @@ aead.dev/minisign v0.3.0/go.mod h1:NLvG3Uoq3skkRMDuc3YHpWUTMTrSExqm+Ij73W13F6Y=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
|
||||
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
|
||||
cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw=
|
||||
cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s=
|
||||
cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38=
|
||||
cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
|
||||
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
|
||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||
cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD3EhhSux05c=
|
||||
cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M=
|
||||
cloud.google.com/go/iam v1.1.10 h1:ZSAr64oEhQSClwBL670MsJAW5/RLiC6kfw3Bqmd5ZDI=
|
||||
cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps=
|
||||
cloud.google.com/go/longrunning v0.5.8 h1:QThI5BFSlYlS7K0wnABCdmKsXbG/htLc3nTPzrfOgeU=
|
||||
cloud.google.com/go/longrunning v0.5.8/go.mod h1:oJDErR/mm5h44gzsfjQlxd6jyjFvuBPOxR1TLy2+cQk=
|
||||
cloud.google.com/go/storage v1.42.0 h1:4QtGpplCVt1wz6g5o1ifXd656P5z+yNgzdw1tVfp0cU=
|
||||
cloud.google.com/go/storage v1.42.0/go.mod h1:HjMXRFq65pGKFn6hxj6x3HCyR41uSB72Z0SO/Vn6JFQ=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
@@ -89,8 +89,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.26.4 h1:2gDkkzLZaTjMl/dQBpNVtnvcCxsh/FCkimep7FC9c40=
|
||||
github.com/charmbracelet/bubbletea v0.26.4/go.mod h1:P+r+RRA5qtI1DOHNFn0otoNwB4rn+zNAzSj/EXz6xU0=
|
||||
github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s=
|
||||
github.com/charmbracelet/bubbletea v0.26.6/go.mod h1:dz8CWPlfCCGLFbBlTY4N7bjLiyOGDJEnd2Muu7pOWhk=
|
||||
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
|
||||
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
|
||||
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
|
||||
@@ -180,6 +180,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
@@ -275,8 +277,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
||||
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
|
||||
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
|
||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0 h1:e+8XbKB6IMn8A4OAyZccO4pYfB3s7bt6azNIPE7AnPg=
|
||||
github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
@@ -288,13 +290,13 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
|
||||
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.5.2 h1:qoW6V1GT3aZxybsbC6oLnailWnB+qTMVwMreOso9XUw=
|
||||
github.com/gorilla/websocket v1.5.2/go.mod h1:0n9H61RBAcf5/38py2MCYbxzPIY9rOkpvvMT24Rqs30=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -343,8 +345,8 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
|
||||
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
|
||||
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
@@ -434,14 +436,14 @@ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
||||
github.com/minio/cli v1.24.2 h1:J+fCUh9mhPLjN3Lj/YhklXvxj8mnyE/D6FpFduXJ2jg=
|
||||
github.com/minio/cli v1.24.2/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
|
||||
github.com/minio/colorjson v1.0.8 h1:AS6gEQ1dTRYHmC4xuoodPDRILHP/9Wz5wYUGDQfPLpg=
|
||||
github.com/minio/colorjson v1.0.8/go.mod h1:wrs39G/4kqNlGjwqHvPlAnXuc2tlPszo6JKdSBCLN8w=
|
||||
github.com/minio/console v1.6.0 h1:G3mjhGV2Pox1Sqjwp/jRbRY7WiKsVyCLaZkxoIOaMCU=
|
||||
github.com/minio/console v1.6.0/go.mod h1:XJ3HKHmigs1MgjaNjUwpyuOAJnwqlSMB+QnZCZ+BROY=
|
||||
github.com/minio/console v1.6.1 h1:/rlXITBdZeDcX33PCjhEdA2vufeMMFMufj1XVukbu+0=
|
||||
github.com/minio/console v1.6.1/go.mod h1:XJ3HKHmigs1MgjaNjUwpyuOAJnwqlSMB+QnZCZ+BROY=
|
||||
github.com/minio/csvparser v1.0.0 h1:xJEHcYK8ZAjeW4hNV9Zu30u+/2o4UyPnYgyjWp8b7ZU=
|
||||
github.com/minio/csvparser v1.0.0/go.mod h1:lKXskSLzPgC5WQyzP7maKH7Sl1cqvANXo9YCto8zbtM=
|
||||
github.com/minio/dnscache v0.1.1 h1:AMYLqomzskpORiUA1ciN9k7bZT1oB3YZN4cEIi88W5o=
|
||||
@@ -456,21 +458,21 @@ github.com/minio/kms-go/kes v0.3.0 h1:SU8VGVM/Hk9w1OiSby3OatkcojooUqIdDHl6dtM6Nk
|
||||
github.com/minio/kms-go/kes v0.3.0/go.mod h1:w6DeVT878qEOU3nUrYVy1WOT5H1Ig9hbDIh698NYJKY=
|
||||
github.com/minio/kms-go/kms v0.4.0 h1:cLPZceEp+05xHotVBaeFJrgL7JcXM4lBy6PU0idkE7I=
|
||||
github.com/minio/kms-go/kms v0.4.0/go.mod h1:q12CehiIy2qgBnDKq6Q7wmPi2PHSyRVug5DKp0HAVeE=
|
||||
github.com/minio/madmin-go/v3 v3.0.55 h1:Vm5AWS0kFoWwoJX4epskjVwmmS64xMNORMZaGR3cbK8=
|
||||
github.com/minio/madmin-go/v3 v3.0.55/go.mod h1:IFAwr0XMrdsLovxAdCcuq/eoL4nRuMVQQv0iubJANQw=
|
||||
github.com/minio/mc v0.0.0-20240612143403-e7c9a733c680 h1:Ns5mhSm86qJx6a9GJ1kzHkZMjRMZrQGsptakVRmq4QA=
|
||||
github.com/minio/mc v0.0.0-20240612143403-e7c9a733c680/go.mod h1:21/cb+wUd+lLRsdX7ACqyO8DzPNSpXftp1bOkQlIbh8=
|
||||
github.com/minio/madmin-go/v3 v3.0.58 h1:CUhb6FsBvgPfP1iOWvMGqlrB1epYpJw0i/yGXPH12WQ=
|
||||
github.com/minio/madmin-go/v3 v3.0.58/go.mod h1:IFAwr0XMrdsLovxAdCcuq/eoL4nRuMVQQv0iubJANQw=
|
||||
github.com/minio/mc v0.0.0-20240702213905-74032bc16a3f h1:UN7hxbfLhBssFfoqS4zNIBDMC57qgLpbym6v0XYLe2s=
|
||||
github.com/minio/mc v0.0.0-20240702213905-74032bc16a3f/go.mod h1:kJaOnJZfmThdTEUR/9GlLbKYiqx+a5oFQac8wWaDogA=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
|
||||
github.com/minio/minio-go/v7 v7.0.72-0.20240610154810-fa174cbf14b0 h1:7e4w0tbj1NpxxyiGB7CutxpKBnXus/RU1CwN3Sm4gDY=
|
||||
github.com/minio/minio-go/v7 v7.0.72-0.20240610154810-fa174cbf14b0/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=
|
||||
github.com/minio/minio-go/v7 v7.0.73 h1:qr2vi96Qm7kZ4v7LLebjte+MQh621fFWnv93p12htEo=
|
||||
github.com/minio/minio-go/v7 v7.0.73/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8=
|
||||
github.com/minio/mux v1.9.0 h1:dWafQFyEfGhJvK6AwLOt83bIG5bxKxKJnKMCi0XAaoA=
|
||||
github.com/minio/mux v1.9.0/go.mod h1:1pAare17ZRL5GpmNL+9YmqHoWnLmMZF9C/ioUCfy0BQ=
|
||||
github.com/minio/pkg/v2 v2.0.19 h1:r187/k/oVH9H0DDwvLY5WipkJaZ4CLd4KI3KgIUExR0=
|
||||
github.com/minio/pkg/v2 v2.0.19/go.mod h1:luK9LAhQlAPzSuF6F326XSCKjMc1G3Tbh+a9JYwqh8M=
|
||||
github.com/minio/pkg/v3 v3.0.2 h1:PX0HhnCdndHxCJ2rF2Cy3HocAyQR97fj9CRMixh5n8M=
|
||||
github.com/minio/pkg/v3 v3.0.2/go.mod h1:53gkSUVHcfYoskOs5YAJ3D99nsd2SKru90rdE9whlXU=
|
||||
github.com/minio/pkg/v3 v3.0.3 h1:PUJVi5a6Hdn5mIhffC24koFMQwucvTyBHsIOjsisI+U=
|
||||
github.com/minio/pkg/v3 v3.0.3/go.mod h1:53gkSUVHcfYoskOs5YAJ3D99nsd2SKru90rdE9whlXU=
|
||||
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
|
||||
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
@@ -506,6 +508,8 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
|
||||
github.com/nats-io/jwt/v2 v2.5.0 h1:WQQ40AAlqqfx+f6ku+i0pOVm+ASirD4fUh+oQsiE9Ak=
|
||||
@@ -518,8 +522,8 @@ github.com/nats-io/nats-streaming-server v0.24.3/go.mod h1:rqWfyCbxlhKj//fAp8POd
|
||||
github.com/nats-io/nats.go v1.13.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||
github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||
github.com/nats-io/nats.go v1.22.1/go.mod h1:tLqubohF7t4z3du1QDPYJIQQyhb4wl6DhjxEajSI7UA=
|
||||
github.com/nats-io/nats.go v1.35.0 h1:XFNqNM7v5B+MQMKqVGAyHwYhyKb48jrenXNxIU20ULk=
|
||||
github.com/nats-io/nats.go v1.35.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||
github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU=
|
||||
github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
|
||||
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
|
||||
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
|
||||
@@ -577,8 +581,8 @@ github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQy
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8=
|
||||
github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
@@ -588,8 +592,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/prometheus/prom2json v1.3.3 h1:IYfSMiZ7sSOfliBoo89PcufjWO4eAR0gznGcETyaUgo=
|
||||
github.com/prometheus/prom2json v1.3.3/go.mod h1:Pv4yIPktEkK7btWsrUTWDDDrnpUrAELaOCj+oFwlgmc=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.2.0 h1:9AzuUeF88YC5bK8u2vEG1Fpvu4wgpM1wfPIExfaaDxQ=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.2.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
|
||||
@@ -608,8 +612,8 @@ github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||
github.com/safchain/ethtool v0.4.1 h1:S6mEleTADqgynileXoiapt/nKnatyR6bmIHoF+h2ADo=
|
||||
github.com/safchain/ethtool v0.4.1/go.mod h1:XLLnZmy4OCRTkksP/UiMjij96YmIsBfmBQcs7H6tA48=
|
||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
|
||||
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
|
||||
@@ -663,8 +667,8 @@ github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYg
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/unrolled/secure v1.14.0 h1:u9vJTU/pR4Bny0ntLUMxdfLtmIRGvQf2sEFuA0TG9AE=
|
||||
github.com/unrolled/secure v1.14.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/unrolled/secure v1.15.0 h1:q7x+pdp8jAHnbzxu6UheP8fRlG/rwYTb8TPuQ3rn9Og=
|
||||
github.com/unrolled/secure v1.15.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/vbauerster/mpb/v8 v8.7.3 h1:n/mKPBav4FFWp5fH4U0lPpXfiOmCEgl5Yx/NM3tKJA0=
|
||||
@@ -693,22 +697,22 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxv
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI=
|
||||
go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg=
|
||||
go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk=
|
||||
go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
|
||||
go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4=
|
||||
go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
|
||||
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
|
||||
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
|
||||
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
|
||||
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
||||
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
|
||||
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
|
||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -745,8 +749,8 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
|
||||
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -822,7 +826,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -891,26 +894,26 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.184.0 h1:dmEdk6ZkJNXy1JcDhn/ou0ZUq7n9zropG2/tR4z+RDg=
|
||||
google.golang.org/api v0.184.0/go.mod h1:CeDTtUEiYENAf8PPG5VZW2yNp2VM3VWbCeTioAZBTBA=
|
||||
google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo=
|
||||
google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20240610135401-a8a62080eff3 h1:8RTI1cmuvdY9J7q/jpJWEj5UfgWjhV5MCoXaYmwLBYQ=
|
||||
google.golang.org/genproto v0.0.0-20240610135401-a8a62080eff3/go.mod h1:qb66gsewNb7Ghv1enkhJiRfYGWUklv3n6G8UvprOhzA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 h1:QW9+G6Fir4VcRXVH8x3LilNAb6cxBGLa6+GM4hRwexE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3/go.mod h1:kdrSS/OiLkPrNUpzD4aHgCq2rVuC/YRxok32HXZ4vRE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 h1:9Xyg6I9IWQZhRVfCWjKK+l6kI0jHcPesVlMnT//aHNo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/genproto v0.0.0-20240701130421-f6361c86f094 h1:6whtk83KtD3FkGrVb2hFXuQ+ZMbCNdakARIn/aHMmG8=
|
||||
google.golang.org/genproto v0.0.0-20240701130421-f6361c86f094/go.mod h1:Zs4wYw8z1zr6RNF4cwYb31mvN/EGaKAdQjNCF3DW6K4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -928,8 +931,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -572,6 +572,7 @@ func FilterObjectLockMetadata(metadata map[string]string, filterRetention, filte
|
||||
dst := metadata
|
||||
var copied bool
|
||||
delKey := func(key string) {
|
||||
key = strings.ToLower(key)
|
||||
if _, ok := metadata[key]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -606,7 +606,7 @@ func TestFilterObjectLockMetadata(t *testing.T) {
|
||||
|
||||
for i, tt := range tests {
|
||||
o := FilterObjectLockMetadata(tt.metadata, tt.filterRetention, tt.filterLegalHold)
|
||||
if !reflect.DeepEqual(o, tt.metadata) {
|
||||
if !reflect.DeepEqual(o, tt.expected) {
|
||||
t.Fatalf("Case %d expected %v, got %v", i, tt.metadata, o)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,12 @@ var (
|
||||
`Access key length should be at least 3, and secret key length at least 8 characters`,
|
||||
)
|
||||
|
||||
ErrInvalidRootUserCredentials = newErrFn(
|
||||
"Invalid credentials",
|
||||
"Please provide correct credentials",
|
||||
EnvRootUser+` length should be at least 3, and `+EnvRootPassword+` length at least 8 characters`,
|
||||
)
|
||||
|
||||
ErrMissingEnvCredentialRootUser = newErrFn(
|
||||
"Missing credential environment variable, \""+EnvRootUser+"\"",
|
||||
"Environment variable \""+EnvRootUser+"\" is missing",
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/fips"
|
||||
"github.com/minio/pkg/v3/env"
|
||||
xnet "github.com/minio/pkg/v3/net"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
@@ -159,7 +160,13 @@ func LookupConfig(kvs config.KVS, rootCAs *x509.CertPool) (Config, error) {
|
||||
cfg.PathPrefix = env.Get(EnvEtcdPathPrefix, kvs.Get(PathPrefix))
|
||||
if etcdSecure {
|
||||
cfg.TLS = &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
RootCAs: rootCAs,
|
||||
PreferServerCipherSuites: true,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"http/1.1", "h2"},
|
||||
ClientSessionCache: tls.NewLRUClientSessionCache(64),
|
||||
CipherSuites: fips.TLSCiphersBackwardCompatible(),
|
||||
CurvePreferences: fips.TLSCurveIDs(),
|
||||
}
|
||||
// This is only to support client side certificate authentication
|
||||
// https://coreos.com/etcd/docs/latest/op-guide/security.html
|
||||
|
||||
@@ -98,7 +98,8 @@ func (l *Config) GetValidatedDNForUsername(username string) (*xldap.DNSearchResu
|
||||
// under a configured base DN in the LDAP directory.
|
||||
validDN, isUnderBaseDN, err := l.GetValidatedUserDN(conn, username)
|
||||
if err == nil && !isUnderBaseDN {
|
||||
return nil, fmt.Errorf("Unable to find user DN: %w", err)
|
||||
// Not under any configured base DN, so treat as not found.
|
||||
return nil, nil
|
||||
}
|
||||
return validDN, err
|
||||
}
|
||||
@@ -178,6 +179,54 @@ func (l *Config) GetValidatedDNUnderBaseDN(conn *ldap.Conn, dn string, baseDNLis
|
||||
return searchRes, false, nil
|
||||
}
|
||||
|
||||
// GetValidatedDNWithGroups - Gets validated DN from given DN or short username
|
||||
// and returns the DN and the groups the user is a member of.
|
||||
//
|
||||
// If username is required in group search but a DN is passed, no groups are
|
||||
// returned.
|
||||
func (l *Config) GetValidatedDNWithGroups(username string) (*xldap.DNSearchResult, []string, error) {
|
||||
conn, err := l.LDAP.Connect()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Bind to the lookup user account
|
||||
if err = l.LDAP.LookupBind(conn); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var lookupRes *xldap.DNSearchResult
|
||||
shortUsername := ""
|
||||
// Check if the passed in username is a valid DN.
|
||||
if !l.ParsesAsDN(username) {
|
||||
// We consider it as a login username and attempt to check it exists in
|
||||
// the directory.
|
||||
lookupRes, err = l.LDAP.LookupUsername(conn, username)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "User DN not found for") {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return nil, nil, fmt.Errorf("Unable to find user DN: %w", err)
|
||||
}
|
||||
shortUsername = username
|
||||
} else {
|
||||
// Since the username parses as a valid DN, check that it exists and is
|
||||
// under a configured base DN in the LDAP directory.
|
||||
var isUnderBaseDN bool
|
||||
lookupRes, isUnderBaseDN, err = l.GetValidatedUserDN(conn, username)
|
||||
if err == nil && !isUnderBaseDN {
|
||||
return nil, nil, fmt.Errorf("Unable to find user DN: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
groups, err := l.LDAP.SearchForUserGroups(conn, shortUsername, lookupRes.ActualDN)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return lookupRes, groups, nil
|
||||
}
|
||||
|
||||
// Bind - binds to ldap, searches LDAP and returns the distinguished name of the
|
||||
// user and the list of groups.
|
||||
func (l *Config) Bind(username, password string) (*xldap.DNSearchResult, []string, error) {
|
||||
|
||||
@@ -138,7 +138,7 @@ func (target *WebhookTarget) isActive() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Stat - returns lamdba webhook target statistics such as
|
||||
// Stat - returns lambda webhook target statistics such as
|
||||
// current calls in progress, successfully completed functions
|
||||
// failed functions.
|
||||
func (target *WebhookTarget) Stat() event.TargetStat {
|
||||
|
||||
+13
-42
@@ -20,7 +20,6 @@ package grid
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -28,7 +27,6 @@ import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -100,9 +98,9 @@ type Connection struct {
|
||||
// Client or serverside.
|
||||
side ws.State
|
||||
|
||||
// Transport for outgoing connections.
|
||||
dialer ContextDialer
|
||||
header http.Header
|
||||
// Dialer for outgoing connections.
|
||||
dial ConnDialer
|
||||
authFn AuthFn
|
||||
|
||||
handleMsgWg sync.WaitGroup
|
||||
|
||||
@@ -112,10 +110,8 @@ type Connection struct {
|
||||
handlers *handlers
|
||||
|
||||
remote *RemoteClient
|
||||
auth AuthFn
|
||||
clientPingInterval time.Duration
|
||||
connPingInterval time.Duration
|
||||
tlsConfig *tls.Config
|
||||
blockConnect chan struct{}
|
||||
|
||||
incomingBytes func(n int64) // Record incoming bytes.
|
||||
@@ -205,13 +201,12 @@ type connectionParams struct {
|
||||
ctx context.Context
|
||||
id uuid.UUID
|
||||
local, remote string
|
||||
dial ContextDialer
|
||||
handlers *handlers
|
||||
auth AuthFn
|
||||
tlsConfig *tls.Config
|
||||
incomingBytes func(n int64) // Record incoming bytes.
|
||||
outgoingBytes func(n int64) // Record outgoing bytes.
|
||||
publisher *pubsub.PubSub[madmin.TraceInfo, madmin.TraceType]
|
||||
dialer ConnDialer
|
||||
authFn AuthFn
|
||||
|
||||
blockConnect chan struct{}
|
||||
}
|
||||
@@ -227,16 +222,14 @@ func newConnection(o connectionParams) *Connection {
|
||||
outgoing: xsync.NewMapOfPresized[uint64, *muxClient](1000),
|
||||
inStream: xsync.NewMapOfPresized[uint64, *muxServer](1000),
|
||||
outQueue: make(chan []byte, defaultOutQueue),
|
||||
dialer: o.dial,
|
||||
side: ws.StateServerSide,
|
||||
connChange: &sync.Cond{L: &sync.Mutex{}},
|
||||
handlers: o.handlers,
|
||||
auth: o.auth,
|
||||
header: make(http.Header, 1),
|
||||
remote: &RemoteClient{Name: o.remote},
|
||||
clientPingInterval: clientPingInterval,
|
||||
connPingInterval: connPingInterval,
|
||||
tlsConfig: o.tlsConfig,
|
||||
dial: o.dialer,
|
||||
authFn: o.authFn,
|
||||
}
|
||||
if debugPrint {
|
||||
// Random Mux ID
|
||||
@@ -648,41 +641,17 @@ func (c *Connection) connect() {
|
||||
if c.State() == StateShutdown {
|
||||
return
|
||||
}
|
||||
toDial := strings.Replace(c.Remote, "http://", "ws://", 1)
|
||||
toDial = strings.Replace(toDial, "https://", "wss://", 1)
|
||||
toDial += RoutePath
|
||||
|
||||
dialer := ws.DefaultDialer
|
||||
dialer.ReadBufferSize = readBufferSize
|
||||
dialer.WriteBufferSize = writeBufferSize
|
||||
dialer.Timeout = defaultDialTimeout
|
||||
if c.dialer != nil {
|
||||
dialer.NetDial = c.dialer.DialContext
|
||||
}
|
||||
if c.header == nil {
|
||||
c.header = make(http.Header, 2)
|
||||
}
|
||||
c.header.Set("Authorization", "Bearer "+c.auth(""))
|
||||
c.header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339))
|
||||
|
||||
if len(c.header) > 0 {
|
||||
dialer.Header = ws.HandshakeHeaderHTTP(c.header)
|
||||
}
|
||||
dialer.TLSConfig = c.tlsConfig
|
||||
dialStarted := time.Now()
|
||||
if debugPrint {
|
||||
fmt.Println(c.Local, "Connecting to ", toDial)
|
||||
}
|
||||
conn, br, _, err := dialer.Dial(c.ctx, toDial)
|
||||
if br != nil {
|
||||
ws.PutReader(br)
|
||||
fmt.Println(c.Local, "Connecting to ", c.Remote)
|
||||
}
|
||||
conn, err := c.dial(c.ctx, c.Remote)
|
||||
c.connMu.Lock()
|
||||
c.debugOutConn = conn
|
||||
c.connMu.Unlock()
|
||||
retry := func(err error) {
|
||||
if debugPrint {
|
||||
fmt.Printf("%v Connecting to %v: %v. Retrying.\n", c.Local, toDial, err)
|
||||
fmt.Printf("%v Connecting to %v: %v. Retrying.\n", c.Local, c.Remote, err)
|
||||
}
|
||||
sleep := defaultDialTimeout + time.Duration(rng.Int63n(int64(defaultDialTimeout)))
|
||||
next := dialStarted.Add(sleep / 2)
|
||||
@@ -696,7 +665,7 @@ func (c *Connection) connect() {
|
||||
}
|
||||
if gotState != StateConnecting {
|
||||
// Don't print error on first attempt, and after that only once per hour.
|
||||
gridLogOnceIf(c.ctx, fmt.Errorf("grid: %s re-connecting to %s: %w (%T) Sleeping %v (%v)", c.Local, toDial, err, err, sleep, gotState), toDial)
|
||||
gridLogOnceIf(c.ctx, fmt.Errorf("grid: %s re-connecting to %s: %w (%T) Sleeping %v (%v)", c.Local, c.Remote, err, err, sleep, gotState), c.Remote)
|
||||
}
|
||||
c.updateState(StateConnectionError)
|
||||
time.Sleep(sleep)
|
||||
@@ -712,7 +681,9 @@ func (c *Connection) connect() {
|
||||
req := connectReq{
|
||||
Host: c.Local,
|
||||
ID: c.id,
|
||||
Time: time.Now(),
|
||||
}
|
||||
req.addToken(c.authFn)
|
||||
err = c.sendMsg(conn, m, &req)
|
||||
if err != nil {
|
||||
retry(err)
|
||||
|
||||
@@ -52,11 +52,13 @@ func TestDisconnect(t *testing.T) {
|
||||
localHost := hosts[0]
|
||||
remoteHost := hosts[1]
|
||||
local, err := NewManager(context.Background(), ManagerOptions{
|
||||
Dialer: dialer.DialContext,
|
||||
Dialer: ConnectWS(dialer.DialContext,
|
||||
dummyNewToken,
|
||||
nil),
|
||||
Local: localHost,
|
||||
Hosts: hosts,
|
||||
AddAuth: func(aud string) string { return aud },
|
||||
AuthRequest: dummyRequestValidate,
|
||||
AuthFn: dummyNewToken,
|
||||
AuthToken: dummyTokenValidate,
|
||||
BlockConnect: connReady,
|
||||
})
|
||||
errFatal(err)
|
||||
@@ -74,17 +76,19 @@ func TestDisconnect(t *testing.T) {
|
||||
}))
|
||||
|
||||
remote, err := NewManager(context.Background(), ManagerOptions{
|
||||
Dialer: dialer.DialContext,
|
||||
Dialer: ConnectWS(dialer.DialContext,
|
||||
dummyNewToken,
|
||||
nil),
|
||||
Local: remoteHost,
|
||||
Hosts: hosts,
|
||||
AddAuth: func(aud string) string { return aud },
|
||||
AuthRequest: dummyRequestValidate,
|
||||
AuthFn: dummyNewToken,
|
||||
AuthToken: dummyTokenValidate,
|
||||
BlockConnect: connReady,
|
||||
})
|
||||
errFatal(err)
|
||||
|
||||
localServer := startServer(t, listeners[0], wrapServer(local.Handler()))
|
||||
remoteServer := startServer(t, listeners[1], wrapServer(remote.Handler()))
|
||||
localServer := startServer(t, listeners[0], wrapServer(local.Handler(dummyRequestValidate)))
|
||||
remoteServer := startServer(t, listeners[1], wrapServer(remote.Handler(dummyRequestValidate)))
|
||||
close(connReady)
|
||||
|
||||
defer func() {
|
||||
@@ -165,10 +169,6 @@ func TestDisconnect(t *testing.T) {
|
||||
<-gotCall
|
||||
}
|
||||
|
||||
func dummyRequestValidate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestShouldConnect(t *testing.T) {
|
||||
var c Connection
|
||||
var cReverse Connection
|
||||
|
||||
+23
-8
@@ -82,20 +82,20 @@ func SetupTestGrid(n int) (*TestGrid, error) {
|
||||
res.cancel = cancel
|
||||
for i, host := range hosts {
|
||||
manager, err := NewManager(ctx, ManagerOptions{
|
||||
Dialer: dialer.DialContext,
|
||||
Local: host,
|
||||
Hosts: hosts,
|
||||
AuthRequest: func(r *http.Request) error {
|
||||
return nil
|
||||
},
|
||||
AddAuth: func(aud string) string { return aud },
|
||||
Dialer: ConnectWS(dialer.DialContext,
|
||||
dummyNewToken,
|
||||
nil),
|
||||
Local: host,
|
||||
Hosts: hosts,
|
||||
AuthFn: dummyNewToken,
|
||||
AuthToken: dummyTokenValidate,
|
||||
BlockConnect: ready,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := mux.NewRouter()
|
||||
m.Handle(RoutePath, manager.Handler())
|
||||
m.Handle(RoutePath, manager.Handler(dummyRequestValidate))
|
||||
res.Managers = append(res.Managers, manager)
|
||||
res.Servers = append(res.Servers, startHTTPServer(listeners[i], m))
|
||||
res.Listeners = append(res.Listeners, listeners[i])
|
||||
@@ -164,3 +164,18 @@ func startHTTPServer(listener net.Listener, handler http.Handler) (server *httpt
|
||||
server.Start()
|
||||
return server
|
||||
}
|
||||
|
||||
func dummyRequestValidate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func dummyTokenValidate(token, audience string) error {
|
||||
if token == audience {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid token. want %s, got %s", audience, token)
|
||||
}
|
||||
|
||||
func dummyNewToken(audience string) string {
|
||||
return audience
|
||||
}
|
||||
|
||||
@@ -20,12 +20,17 @@ package grid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
)
|
||||
|
||||
@@ -179,3 +184,45 @@ func bytesOrLength(b []byte) string {
|
||||
}
|
||||
return fmt.Sprint(b)
|
||||
}
|
||||
|
||||
// ConnDialer is a function that dials a connection to the given address.
|
||||
// There should be no retries in this function,
|
||||
// and should have a timeout of something like 2 seconds.
|
||||
// The returned net.Conn should also have quick disconnect on errors.
|
||||
// The net.Conn must support all features as described by the net.Conn interface.
|
||||
type ConnDialer func(ctx context.Context, address string) (net.Conn, error)
|
||||
|
||||
// ConnectWS returns a function that dials a websocket connection to the given address.
|
||||
// Route and auth are added to the connection.
|
||||
func ConnectWS(dial ContextDialer, auth AuthFn, tls *tls.Config) func(ctx context.Context, remote string) (net.Conn, error) {
|
||||
return func(ctx context.Context, remote string) (net.Conn, error) {
|
||||
toDial := strings.Replace(remote, "http://", "ws://", 1)
|
||||
toDial = strings.Replace(toDial, "https://", "wss://", 1)
|
||||
toDial += RoutePath
|
||||
|
||||
dialer := ws.DefaultDialer
|
||||
dialer.ReadBufferSize = readBufferSize
|
||||
dialer.WriteBufferSize = writeBufferSize
|
||||
dialer.Timeout = defaultDialTimeout
|
||||
if dial != nil {
|
||||
dialer.NetDial = dial
|
||||
}
|
||||
header := make(http.Header, 2)
|
||||
header.Set("Authorization", "Bearer "+auth(""))
|
||||
header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339))
|
||||
|
||||
if len(header) > 0 {
|
||||
dialer.Header = ws.HandshakeHeaderHTTP(header)
|
||||
}
|
||||
dialer.TLSConfig = tls
|
||||
|
||||
conn, br, _, err := dialer.Dial(ctx, toDial)
|
||||
if br != nil {
|
||||
ws.PutReader(br)
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateTokenFn must validate the token and return an error if it is invalid.
|
||||
type ValidateTokenFn func(token, audience string) error
|
||||
|
||||
+117
-89
@@ -19,13 +19,14 @@ package grid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
@@ -62,40 +63,48 @@ type Manager struct {
|
||||
// local host name.
|
||||
local string
|
||||
|
||||
// Validate incoming requests.
|
||||
authRequest func(r *http.Request) error
|
||||
// authToken is a function that will validate a token.
|
||||
authToken ValidateTokenFn
|
||||
}
|
||||
|
||||
// ManagerOptions are options for creating a new grid manager.
|
||||
type ManagerOptions struct {
|
||||
Dialer ContextDialer // Outgoing dialer.
|
||||
Local string // Local host name.
|
||||
Hosts []string // All hosts, including local in the grid.
|
||||
AddAuth AuthFn // Add authentication to the given audience.
|
||||
AuthRequest func(r *http.Request) error // Validate incoming requests.
|
||||
TLSConfig *tls.Config // TLS to apply to the connections.
|
||||
Incoming func(n int64) // Record incoming bytes.
|
||||
Outgoing func(n int64) // Record outgoing bytes.
|
||||
BlockConnect chan struct{} // If set, incoming and outgoing connections will be blocked until closed.
|
||||
Local string // Local host name.
|
||||
Hosts []string // All hosts, including local in the grid.
|
||||
Incoming func(n int64) // Record incoming bytes.
|
||||
Outgoing func(n int64) // Record outgoing bytes.
|
||||
BlockConnect chan struct{} // If set, incoming and outgoing connections will be blocked until closed.
|
||||
TraceTo *pubsub.PubSub[madmin.TraceInfo, madmin.TraceType]
|
||||
Dialer ConnDialer
|
||||
// Sign a token for the given audience.
|
||||
AuthFn AuthFn
|
||||
// Callbacks to validate incoming connections.
|
||||
AuthToken ValidateTokenFn
|
||||
}
|
||||
|
||||
// NewManager creates a new grid manager
|
||||
func NewManager(ctx context.Context, o ManagerOptions) (*Manager, error) {
|
||||
found := false
|
||||
if o.AuthRequest == nil {
|
||||
return nil, fmt.Errorf("grid: AuthRequest must be set")
|
||||
if o.AuthToken == nil {
|
||||
return nil, fmt.Errorf("grid: AuthToken not set")
|
||||
}
|
||||
if o.Dialer == nil {
|
||||
return nil, fmt.Errorf("grid: Dialer not set")
|
||||
}
|
||||
if o.AuthFn == nil {
|
||||
return nil, fmt.Errorf("grid: AuthFn not set")
|
||||
}
|
||||
m := &Manager{
|
||||
ID: uuid.New(),
|
||||
targets: make(map[string]*Connection, len(o.Hosts)),
|
||||
local: o.Local,
|
||||
authRequest: o.AuthRequest,
|
||||
ID: uuid.New(),
|
||||
targets: make(map[string]*Connection, len(o.Hosts)),
|
||||
local: o.Local,
|
||||
authToken: o.AuthToken,
|
||||
}
|
||||
m.handlers.init()
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
for _, host := range o.Hosts {
|
||||
if host == o.Local {
|
||||
if found {
|
||||
@@ -110,14 +119,13 @@ func NewManager(ctx context.Context, o ManagerOptions) (*Manager, error) {
|
||||
id: m.ID,
|
||||
local: o.Local,
|
||||
remote: host,
|
||||
dial: o.Dialer,
|
||||
handlers: &m.handlers,
|
||||
auth: o.AddAuth,
|
||||
blockConnect: o.BlockConnect,
|
||||
tlsConfig: o.TLSConfig,
|
||||
publisher: o.TraceTo,
|
||||
incomingBytes: o.Incoming,
|
||||
outgoingBytes: o.Outgoing,
|
||||
dialer: o.Dialer,
|
||||
authFn: o.AuthFn,
|
||||
})
|
||||
}
|
||||
if !found {
|
||||
@@ -128,13 +136,13 @@ func NewManager(ctx context.Context, o ManagerOptions) (*Manager, error) {
|
||||
}
|
||||
|
||||
// AddToMux will add the grid manager to the given mux.
|
||||
func (m *Manager) AddToMux(router *mux.Router) {
|
||||
router.Handle(RoutePath, m.Handler())
|
||||
func (m *Manager) AddToMux(router *mux.Router, authReq func(r *http.Request) error) {
|
||||
router.Handle(RoutePath, m.Handler(authReq))
|
||||
}
|
||||
|
||||
// Handler returns a handler that can be used to serve grid requests.
|
||||
// This should be connected on RoutePath to the main server.
|
||||
func (m *Manager) Handler() http.HandlerFunc {
|
||||
func (m *Manager) Handler(authReq func(r *http.Request) error) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
defer func() {
|
||||
if debugPrint {
|
||||
@@ -151,7 +159,7 @@ func (m *Manager) Handler() http.HandlerFunc {
|
||||
fmt.Printf("grid: Got a %s request for: %v\n", req.Method, req.URL)
|
||||
}
|
||||
ctx := req.Context()
|
||||
if err := m.authRequest(req); err != nil {
|
||||
if err := authReq(req); err != nil {
|
||||
gridLogOnceIf(ctx, fmt.Errorf("auth %s: %w", req.RemoteAddr, err), req.RemoteAddr)
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
@@ -164,76 +172,96 @@ func (m *Manager) Handler() http.HandlerFunc {
|
||||
w.WriteHeader(http.StatusUpgradeRequired)
|
||||
return
|
||||
}
|
||||
// will write an OpConnectResponse message to the remote and log it once locally.
|
||||
writeErr := func(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
return
|
||||
}
|
||||
gridLogOnceIf(ctx, err, req.RemoteAddr)
|
||||
resp := connectResp{
|
||||
ID: m.ID,
|
||||
Accepted: false,
|
||||
RejectedReason: err.Error(),
|
||||
}
|
||||
if b, err := resp.MarshalMsg(nil); err == nil {
|
||||
msg := message{
|
||||
Op: OpConnectResponse,
|
||||
Payload: b,
|
||||
}
|
||||
if b, err := msg.MarshalMsg(nil); err == nil {
|
||||
wsutil.WriteMessage(conn, ws.StateServerSide, ws.OpBinary, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
defer conn.Close()
|
||||
if debugPrint {
|
||||
fmt.Printf("grid: Upgraded request: %v\n", req.URL)
|
||||
}
|
||||
|
||||
msg, _, err := wsutil.ReadClientData(conn)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("reading connect: %w", err))
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if debugPrint {
|
||||
fmt.Printf("%s handler: Got message, length %v\n", m.local, len(msg))
|
||||
}
|
||||
|
||||
var message message
|
||||
_, _, err = message.parse(msg)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("error parsing grid connect: %w", err))
|
||||
return
|
||||
}
|
||||
if message.Op != OpConnect {
|
||||
writeErr(fmt.Errorf("unexpected connect op: %v", message.Op))
|
||||
return
|
||||
}
|
||||
var cReq connectReq
|
||||
_, err = cReq.UnmarshalMsg(message.Payload)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("error parsing connectReq: %w", err))
|
||||
return
|
||||
}
|
||||
remote := m.targets[cReq.Host]
|
||||
if remote == nil {
|
||||
writeErr(fmt.Errorf("unknown incoming host: %v", cReq.Host))
|
||||
return
|
||||
}
|
||||
if debugPrint {
|
||||
fmt.Printf("handler: Got Connect Req %+v\n", cReq)
|
||||
}
|
||||
writeErr(remote.handleIncoming(ctx, conn, cReq))
|
||||
m.IncomingConn(ctx, conn)
|
||||
}
|
||||
}
|
||||
|
||||
// IncomingConn will handle an incoming connection.
|
||||
// This should be called with the incoming connection after accept.
|
||||
// Auth is handled internally, as well as disconnecting any connections from the same host.
|
||||
func (m *Manager) IncomingConn(ctx context.Context, conn net.Conn) {
|
||||
remoteAddr := conn.RemoteAddr().String()
|
||||
// will write an OpConnectResponse message to the remote and log it once locally.
|
||||
defer conn.Close()
|
||||
writeErr := func(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
return
|
||||
}
|
||||
gridLogOnceIf(ctx, err, remoteAddr)
|
||||
resp := connectResp{
|
||||
ID: m.ID,
|
||||
Accepted: false,
|
||||
RejectedReason: err.Error(),
|
||||
}
|
||||
if b, err := resp.MarshalMsg(nil); err == nil {
|
||||
msg := message{
|
||||
Op: OpConnectResponse,
|
||||
Payload: b,
|
||||
}
|
||||
if b, err := msg.MarshalMsg(nil); err == nil {
|
||||
wsutil.WriteMessage(conn, ws.StateServerSide, ws.OpBinary, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
defer conn.Close()
|
||||
if debugPrint {
|
||||
fmt.Printf("grid: Upgraded request: %v\n", remoteAddr)
|
||||
}
|
||||
|
||||
msg, _, err := wsutil.ReadClientData(conn)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("reading connect: %w", err))
|
||||
return
|
||||
}
|
||||
if debugPrint {
|
||||
fmt.Printf("%s handler: Got message, length %v\n", m.local, len(msg))
|
||||
}
|
||||
|
||||
var message message
|
||||
_, _, err = message.parse(msg)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("error parsing grid connect: %w", err))
|
||||
return
|
||||
}
|
||||
if message.Op != OpConnect {
|
||||
writeErr(fmt.Errorf("unexpected connect op: %v", message.Op))
|
||||
return
|
||||
}
|
||||
var cReq connectReq
|
||||
_, err = cReq.UnmarshalMsg(message.Payload)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("error parsing connectReq: %w", err))
|
||||
return
|
||||
}
|
||||
remote := m.targets[cReq.Host]
|
||||
if remote == nil {
|
||||
writeErr(fmt.Errorf("unknown incoming host: %v", cReq.Host))
|
||||
return
|
||||
}
|
||||
if time.Since(cReq.Time).Abs() > 5*time.Minute {
|
||||
writeErr(fmt.Errorf("time difference too large between servers: %v", time.Since(cReq.Time).Abs()))
|
||||
return
|
||||
}
|
||||
if err := m.authToken(cReq.Token, cReq.audience()); err != nil {
|
||||
writeErr(fmt.Errorf("auth token: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if debugPrint {
|
||||
fmt.Printf("handler: Got Connect Req %+v\n", cReq)
|
||||
}
|
||||
writeErr(remote.handleIncoming(ctx, conn, cReq))
|
||||
}
|
||||
|
||||
// AuthFn should provide an authentication string for the given aud.
|
||||
type AuthFn func(aud string) string
|
||||
|
||||
// ValidateAuthFn should check authentication for the given aud.
|
||||
type ValidateAuthFn func(auth, aud string) string
|
||||
|
||||
// Connection will return the connection for the specified host.
|
||||
// If the host does not exist nil will be returned.
|
||||
func (m *Manager) Connection(host string) *Connection {
|
||||
|
||||
+15
-2
@@ -21,6 +21,7 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
"github.com/zeebo/xxh3"
|
||||
@@ -255,8 +256,20 @@ type sender interface {
|
||||
}
|
||||
|
||||
type connectReq struct {
|
||||
ID [16]byte
|
||||
Host string
|
||||
ID [16]byte
|
||||
Host string
|
||||
Time time.Time
|
||||
Token string
|
||||
}
|
||||
|
||||
// audience returns the audience for the connect call.
|
||||
func (c *connectReq) audience() string {
|
||||
return fmt.Sprintf("%s-%d", c.Host, c.Time.Unix())
|
||||
}
|
||||
|
||||
// addToken will add the token to the connect request.
|
||||
func (c *connectReq) addToken(fn AuthFn) {
|
||||
c.Token = fn(c.audience())
|
||||
}
|
||||
|
||||
func (connectReq) Op() Op {
|
||||
|
||||
@@ -192,6 +192,18 @@ func (z *connectReq) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "Host")
|
||||
return
|
||||
}
|
||||
case "Time":
|
||||
z.Time, err = dc.ReadTime()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Time")
|
||||
return
|
||||
}
|
||||
case "Token":
|
||||
z.Token, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Token")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
@@ -205,9 +217,9 @@ func (z *connectReq) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *connectReq) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 2
|
||||
// map header, size 4
|
||||
// write "ID"
|
||||
err = en.Append(0x82, 0xa2, 0x49, 0x44)
|
||||
err = en.Append(0x84, 0xa2, 0x49, 0x44)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -226,19 +238,45 @@ func (z *connectReq) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Host")
|
||||
return
|
||||
}
|
||||
// write "Time"
|
||||
err = en.Append(0xa4, 0x54, 0x69, 0x6d, 0x65)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteTime(z.Time)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Time")
|
||||
return
|
||||
}
|
||||
// write "Token"
|
||||
err = en.Append(0xa5, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Token)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Token")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *connectReq) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 2
|
||||
// map header, size 4
|
||||
// string "ID"
|
||||
o = append(o, 0x82, 0xa2, 0x49, 0x44)
|
||||
o = append(o, 0x84, 0xa2, 0x49, 0x44)
|
||||
o = msgp.AppendBytes(o, (z.ID)[:])
|
||||
// string "Host"
|
||||
o = append(o, 0xa4, 0x48, 0x6f, 0x73, 0x74)
|
||||
o = msgp.AppendString(o, z.Host)
|
||||
// string "Time"
|
||||
o = append(o, 0xa4, 0x54, 0x69, 0x6d, 0x65)
|
||||
o = msgp.AppendTime(o, z.Time)
|
||||
// string "Token"
|
||||
o = append(o, 0xa5, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
||||
o = msgp.AppendString(o, z.Token)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -272,6 +310,18 @@ func (z *connectReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "Host")
|
||||
return
|
||||
}
|
||||
case "Time":
|
||||
z.Time, bts, err = msgp.ReadTimeBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Time")
|
||||
return
|
||||
}
|
||||
case "Token":
|
||||
z.Token, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Token")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
@@ -286,7 +336,7 @@ func (z *connectReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *connectReq) Msgsize() (s int) {
|
||||
s = 1 + 3 + msgp.ArrayHeaderSize + (16 * (msgp.ByteSize)) + 5 + msgp.StringPrefixSize + len(z.Host)
|
||||
s = 1 + 3 + msgp.ArrayHeaderSize + (16 * (msgp.ByteSize)) + 5 + msgp.StringPrefixSize + len(z.Host) + 5 + msgp.TimeSize + 6 + msgp.StringPrefixSize + len(z.Token)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -256,15 +256,26 @@ func (s *SkipReader) Read(p []byte) (int, error) {
|
||||
if l == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
for s.skipCount > 0 {
|
||||
if l > s.skipCount {
|
||||
l = s.skipCount
|
||||
if s.skipCount > 0 {
|
||||
tmp := p
|
||||
if s.skipCount > l && l < copyBufferSize {
|
||||
// We may get a very small buffer, so we grab a temporary buffer.
|
||||
bufp := copyBufPool.Get().(*[]byte)
|
||||
buf := *bufp
|
||||
tmp = buf[:copyBufferSize]
|
||||
defer copyBufPool.Put(bufp)
|
||||
l = int64(len(tmp))
|
||||
}
|
||||
n, err := s.Reader.Read(p[:l])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
for s.skipCount > 0 {
|
||||
if l > s.skipCount {
|
||||
l = s.skipCount
|
||||
}
|
||||
n, err := s.Reader.Read(tmp[:l])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
s.skipCount -= int64(n)
|
||||
}
|
||||
s.skipCount -= int64(n)
|
||||
}
|
||||
return s.Reader.Read(p)
|
||||
}
|
||||
@@ -274,9 +285,11 @@ func NewSkipReader(r io.Reader, n int64) io.Reader {
|
||||
return &SkipReader{r, n}
|
||||
}
|
||||
|
||||
const copyBufferSize = 32 * 1024
|
||||
|
||||
var copyBufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
b := make([]byte, 32*1024)
|
||||
b := make([]byte, copyBufferSize)
|
||||
return &b
|
||||
},
|
||||
}
|
||||
@@ -285,6 +298,7 @@ var copyBufPool = sync.Pool{
|
||||
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||
bufp := copyBufPool.Get().(*[]byte)
|
||||
buf := *bufp
|
||||
buf = buf[:copyBufferSize]
|
||||
defer copyBufPool.Put(bufp)
|
||||
|
||||
return io.CopyBuffer(dst, src, buf)
|
||||
|
||||
@@ -90,13 +90,14 @@ type Config struct {
|
||||
// buffer is full, new logs are just ignored and an error
|
||||
// is returned to the caller.
|
||||
type Target struct {
|
||||
totalMessages int64
|
||||
failedMessages int64
|
||||
status int32
|
||||
totalMessages atomic.Int64
|
||||
failedMessages atomic.Int64
|
||||
status atomic.Int32
|
||||
|
||||
// Worker control
|
||||
workers int64
|
||||
workers atomic.Int64
|
||||
maxWorkers int64
|
||||
|
||||
// workerStartMu sync.Mutex
|
||||
lastStarted time.Time
|
||||
|
||||
@@ -157,7 +158,7 @@ func (h *Target) String() string {
|
||||
|
||||
// IsOnline returns true if the target is reachable using a cached value
|
||||
func (h *Target) IsOnline(ctx context.Context) bool {
|
||||
return atomic.LoadInt32(&h.status) == statusOnline
|
||||
return h.status.Load() == statusOnline
|
||||
}
|
||||
|
||||
// Stats returns the target statistics.
|
||||
@@ -166,8 +167,8 @@ func (h *Target) Stats() types.TargetStats {
|
||||
queueLength := len(h.logCh)
|
||||
h.logChMu.RUnlock()
|
||||
stats := types.TargetStats{
|
||||
TotalMessages: atomic.LoadInt64(&h.totalMessages),
|
||||
FailedMessages: atomic.LoadInt64(&h.failedMessages),
|
||||
TotalMessages: h.totalMessages.Load(),
|
||||
FailedMessages: h.failedMessages.Load(),
|
||||
QueueLength: queueLength,
|
||||
}
|
||||
|
||||
@@ -221,9 +222,9 @@ func (h *Target) initMemoryStore(ctx context.Context) (err error) {
|
||||
func (h *Target) send(ctx context.Context, payload []byte, payloadType string, timeout time.Duration) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
atomic.StoreInt32(&h.status, statusOffline)
|
||||
h.status.Store(statusOffline)
|
||||
} else {
|
||||
atomic.StoreInt32(&h.status, statusOnline)
|
||||
h.status.Store(statusOnline)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -275,8 +276,8 @@ func (h *Target) startQueueProcessor(ctx context.Context, mainWorker bool) {
|
||||
}
|
||||
h.logChMu.RUnlock()
|
||||
|
||||
atomic.AddInt64(&h.workers, 1)
|
||||
defer atomic.AddInt64(&h.workers, -1)
|
||||
h.workers.Add(1)
|
||||
defer h.workers.Add(-1)
|
||||
|
||||
h.wg.Add(1)
|
||||
defer h.wg.Done()
|
||||
@@ -353,7 +354,7 @@ func (h *Target) startQueueProcessor(ctx context.Context, mainWorker bool) {
|
||||
}
|
||||
|
||||
if !isTick {
|
||||
atomic.AddInt64(&h.totalMessages, 1)
|
||||
h.totalMessages.Add(1)
|
||||
|
||||
if !isDirQueue {
|
||||
if err := enc.Encode(&entry); err != nil {
|
||||
@@ -362,7 +363,7 @@ func (h *Target) startQueueProcessor(ctx context.Context, mainWorker bool) {
|
||||
fmt.Errorf("unable to encode webhook log entry, err '%w' entry: %v\n", err, entry),
|
||||
h.Name(),
|
||||
)
|
||||
atomic.AddInt64(&h.failedMessages, 1)
|
||||
h.failedMessages.Add(1)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -395,7 +396,7 @@ func (h *Target) startQueueProcessor(ctx context.Context, mainWorker bool) {
|
||||
// and when it's been at least 30 seconds since
|
||||
// we launched a new worker.
|
||||
if mainWorker && len(h.logCh) > cap(h.logCh)/2 {
|
||||
nWorkers := atomic.LoadInt64(&h.workers)
|
||||
nWorkers := h.workers.Load()
|
||||
if nWorkers < h.maxWorkers {
|
||||
if time.Since(h.lastStarted).Milliseconds() > 10 {
|
||||
h.lastStarted = time.Now()
|
||||
@@ -493,10 +494,10 @@ func New(config Config) (*Target, error) {
|
||||
h := &Target{
|
||||
logCh: make(chan interface{}, config.QueueSize),
|
||||
config: config,
|
||||
status: statusOffline,
|
||||
batchSize: config.BatchSize,
|
||||
maxWorkers: int64(maxWorkers),
|
||||
}
|
||||
h.status.Store(statusOffline)
|
||||
|
||||
if config.BatchSize > 1 {
|
||||
h.payloadType = ""
|
||||
@@ -528,10 +529,17 @@ func (h *Target) SendFromStore(key store.Key) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
h.failedMessages.Add(1)
|
||||
defer func() {
|
||||
if err == nil {
|
||||
h.failedMessages.Add(-1)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := h.send(context.Background(), eventData, h.payloadType, webhookCallTimeout); err != nil {
|
||||
atomic.AddInt64(&h.failedMessages, 1)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete the event from store.
|
||||
return h.store.Del(key.Name)
|
||||
}
|
||||
@@ -540,7 +548,7 @@ func (h *Target) SendFromStore(key store.Key) (err error) {
|
||||
// Messages are queued in the disk if the store is enabled
|
||||
// If Cancel has been called the message is ignored.
|
||||
func (h *Target) Send(ctx context.Context, entry interface{}) error {
|
||||
if atomic.LoadInt32(&h.status) == statusClosed {
|
||||
if h.status.Load() == statusClosed {
|
||||
if h.migrateTarget != nil {
|
||||
return h.migrateTarget.Send(ctx, entry)
|
||||
}
|
||||
@@ -557,7 +565,7 @@ func (h *Target) Send(ctx context.Context, entry interface{}) error {
|
||||
retry:
|
||||
select {
|
||||
case h.logCh <- entry:
|
||||
atomic.AddInt64(&h.totalMessages, 1)
|
||||
h.totalMessages.Add(1)
|
||||
case <-ctx.Done():
|
||||
// return error only for context timedout.
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
@@ -565,11 +573,14 @@ retry:
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
if h.workers < h.maxWorkers {
|
||||
nWorkers := h.workers.Load()
|
||||
if nWorkers < h.maxWorkers {
|
||||
// Just sleep to avoid any possible hot-loops.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
goto retry
|
||||
}
|
||||
atomic.AddInt64(&h.totalMessages, 1)
|
||||
atomic.AddInt64(&h.failedMessages, 1)
|
||||
h.totalMessages.Add(1)
|
||||
h.failedMessages.Add(1)
|
||||
return errors.New("log buffer full")
|
||||
}
|
||||
|
||||
@@ -580,7 +591,7 @@ retry:
|
||||
// All queued messages are flushed and the function returns afterwards.
|
||||
// All messages sent to the target after this function has been called will be dropped.
|
||||
func (h *Target) Cancel() {
|
||||
atomic.StoreInt32(&h.status, statusClosed)
|
||||
h.status.Store(statusClosed)
|
||||
h.storeCtxCancel()
|
||||
|
||||
// Wait for messages to be sent...
|
||||
|
||||
Reference in New Issue
Block a user