Compare commits

..

7 Commits

Author SHA1 Message Date
Harshavardhana
7fa3279a93 update all other dependent packages 2020-07-25 12:34:00 -07:00
Daniel Valdivia
b631ab38d0 Operator docker iamge 2020-07-25 12:07:22 -07:00
Daniel Valdivia
16c25c0880 Go mod with v3 tag for operator 2020-07-25 12:03:16 -07:00
Daniel Valdivia
5dbdf35295 Changes to operator v3 2020-07-25 12:01:24 -07:00
Daniel Valdivia
5ecb311f79 Update Operator References 2020-07-25 11:51:29 -07:00
Daniel Valdivia
e432183bd6 Update Service Account 2020-07-25 11:19:58 -07:00
Daniel Valdivia
008075133a Upgrade Operator to 3.0.0 2020-07-24 18:34:21 -07:00
1200 changed files with 107875 additions and 254412 deletions

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
node_modules/
dist/
target/
mcs
!mcs/
portal-ui/node_modules/

View File

@@ -1,49 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: community, triage
assignees: ''
---
## NOTE
Please subscribe to our [paid subscription plans](https://min.io/pricing) for 24x7 support from our Engineering team.
<!--- Provide a general summary of the issue in the title above -->
## Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
## Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
## Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.
## Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
## Regression
<!-- Is this issue a regression? (Yes / No) -->
<!-- If Yes, optionally please include the MinIO version or commit id or PR# that caused this regression, if you have these details. -->
## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
* MinIO version used (`minio --version`):
* Server setup and configuration:
* Operating System and version (`uname -a`):

52
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: "Code scanning - action"
on:
push:
pull_request:
schedule:
- cron: '0 19 * * 0'
jobs:
CodeQL-Build:
# CodeQL runs on ubuntu-latest and windows-latest
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

36
.github/workflows/go.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Go
on:
pull_request:
branches:
- master
push:
branches:
- master
jobs:
build:
name: Test on Go ${{ matrix.go-version }} and ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [1.13.x, 1.14.x]
os: [ubuntu-latest]
steps:
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Build on ${{ matrix.os }}
env:
GO111MODULE: on
GOOS: linux
run: |
make verifiers
make test
make mcs

View File

@@ -1,18 +0,0 @@
# @format
name: Issue Workflow
on:
issues:
types:
- opened
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.5.0
with:
project-url: https://github.com/orgs/miniohq/projects/2
github-token: ${{ secrets.BOT_PAT }}

File diff suppressed because it is too large Load Diff

View File

@@ -1,53 +0,0 @@
# @format
name: Vulnerability Check
on:
pull_request:
branches:
- master
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
vulncheck:
name: Analysis
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.23.8
check-latest: true
- name: Get official govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
shell: bash
- name: Run govulncheck
run: govulncheck ./...
shell: bash
react-code-known-vulnerabilities:
name: "React Code Has No Known Vulnerable Deps"
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ 1.23.x ]
os: [ ubuntu-latest ]
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Read .nvmrc
id: node_version
run: echo "$(cat .nvmrc)" && echo "NVMRC=$(cat .nvmrc)" >> $GITHUB_ENV
- name: Enable Corepack
run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NVMRC }}
- name: Checks for known security issues with the installed packages
working-directory: ./web-app
continue-on-error: false
run: |
yarn npm audit --recursive --environment production --no-deprecations

25
.gitignore vendored
View File

@@ -1,13 +1,3 @@
# Playwright Data
web-app/storage/
web-app/playwright/.auth/admin.json
# Report from Playwright
web-app/playwright-report/
# Coverage from Playwright
web-app/.nyc_output/
# Binaries for programs and plugins
*.exe
*.exe~
@@ -29,16 +19,11 @@ vendor/
# Ignore executables
target/
!pkg/logger/target/
console
!console/
mcs
!mcs/
dist/
# Ignore node_modules
web-app/node_modules/
# Ignore tls cert and key
private.key
public.crt
@@ -46,8 +31,4 @@ public.crt
# Ignore VsCode files
.vscode/
*.code-workspace
*~
.eslintcache
# Ignore Bin files
bin/
*~

View File

@@ -1,55 +0,0 @@
linters-settings:
misspell:
locale: US
testifylint:
disable:
- go-require
staticcheck:
checks:
[
"all",
"-ST1005",
"-ST1000",
"-SA4000",
"-SA9004",
"-SA1019",
"-SA1008",
"-U1000",
"-ST1016",
]
goheader:
values:
regexp:
copyright-holder: Copyright \(c\) (20\d\d\-20\d\d)|2021|({{year}})
template-path: .license.tmpl
linters:
disable-all: true
enable:
- goimports
- misspell
- govet
- revive
- ineffassign
- gosimple
- gomodguard
- gofmt
- unused
- staticcheck
- unconvert
- gocritic
- gofumpt
- durationcheck
issues:
exclude-use-default: false
exclude:
- should have a package comment
# TODO(y4m4): Remove once all exported ident. have comments!
- comment on exported function
- comment on exported type
- should have comment
- use leading k in Go names
- comment on exported const
exclude-dirs:
- api/operations

View File

@@ -1,73 +1,28 @@
version: "2"
linters-settings:
golint:
min-confidence: 0
misspell:
locale: US
linters:
default: none
disable-all: true
enable:
- durationcheck
- gocritic
- gomodguard
- govet
- ineffassign
- misspell
- revive
- staticcheck
- unconvert
- unused
settings:
goheader:
values:
regexp:
copyright-holder: Copyright \(c\) (20\d\d\-20\d\d)|2021|({{year}})
template-path: .license.tmpl
misspell:
locale: US
staticcheck:
checks:
- all
- -QF1001
- -QF1008
- -QF1010
- -QF1012
- -SA1008
- -SA1019
- -SA4000
- -SA9004
- -ST1000
- -ST1005
- -ST1016
- -ST1019
- -U1000
testifylint:
disable:
- go-require
exclusions:
generated: lax
rules:
- path: (.+)\.go$
text: should have a package comment
- path: (.+)\.go$
text: comment on exported function
- path: (.+)\.go$
text: comment on exported type
- path: (.+)\.go$
text: should have comment
- path: (.+)\.go$
text: use leading k in Go names
- path: (.+)\.go$
text: comment on exported const
paths:
- api/operations
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- gofumpt
- typecheck
- goimports
exclusions:
generated: lax
paths:
- api/operations
- third_party$
- builtin$
- examples$
- misspell
- govet
- golint
- ineffassign
- gosimple
- deadcode
- unparam
- unused
- structcheck
service:
golangci-lint-version: 1.21.0 # use the fixed version to not introduce new linters unexpectedly
run:
skip-dirs:
- pkg/clientgen

75
.goreleaser.yml Normal file
View File

@@ -0,0 +1,75 @@
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
project_name: mcs
before:
hooks:
# you may remove this if you don't use vgo
- go mod tidy
builds:
-
goos:
- freebsd
- windows
- linux
- darwin
goarch:
- amd64
- arm64
env:
- CGO_ENABLED=0
main: ./cmd/mcs/
flags:
- -trimpath
- --tags=kqueue
ldflags:
- -s -w -X github.com/minio/mcs/pkg.ReleaseTag={{.Tag}} -X github.com/minio/mcs/pkg.CommitID={{.FullCommit}} -X github.com/minio/mcs/pkg.Version={{.Version}} -X github.com/minio/mcs/pkg.ShortCommitID={{.ShortCommit}} -X github.com/minio/mcs/pkg.ReleaseTime={{.Date}}
archives:
-
replacements:
darwin: Darwin
linux: Linux
windows: Windows
freebsd: FreeBSD
amd64: x86_64
format_overrides:
- goos: windows
format: zip
files:
- README.md
- LICENSE
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: 'snapshot-{{ time "2006-01-02" }}'
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
nfpms:
-
vendor: MinIO Inc.
homepage: https://github.com/minio/mcs
maintainer: MinIO <minio@minio.io>
description: MinIO Console Server
license: GNU Affero General Public License v3.0
formats:
- deb
- rpm
replacements:
darwin: Darwin
linux: Linux
freebsd: FreeBSD
amd64: x86_64
dockers:
-
# GOOS of the built binary that should be used.
goos: linux
# GOARCH of the built binary that should be used.
goarch: amd64
dockerfile: Dockerfile.release
image_templates:
- "minio/mcs:{{ .Tag }}"
- "minio/mcs:latest"

View File

@@ -1,15 +0,0 @@
This file is part of MinIO Console Server
{{copyright-holder}} MinIO, Inc.
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/>.

1
.nvmrc
View File

@@ -1 +0,0 @@
18

View File

@@ -1 +0,0 @@
{}

View File

@@ -1,35 +0,0 @@
# Ignore git items
.gitignore
.git/
:include .gitignore
# Common large paths
node_modules/
web-app/node_modules/
build/
dist/
.idea/
vendor/
.env/
.venv/
.tox/
*.min.js
# Common test paths
test/
tests/
*_test.go
# Semgrep rules folder
.semgrep
# Semgrep-action log folder
.semgrep_logs/
# Ignore VsCode files
.vscode/
*.code-workspace
*~
.eslintcache
consoleApi.ts

View File

@@ -1,463 +0,0 @@
# Changelog
## Release v2.0.0
Community version is going back to be an object browser only.
Bug Fix:
- Fixed Dependencies vulnerabilities
Deprecations:
- Deprecated support of accounts & policies management, this can be managed by using mc admin commands. Please refer to the [MinIO Console User Management page](https://min.io/docs/minio/kubernetes/upstream/administration/identity-access-management/minio-user-management.html#id1) for more information.
- Deprecated support of bucket management, this can be managed by using mc commands. Please refer to the [MinIO Client](https://min.io/docs/minio/linux/reference/minio-mc.html) for more information.
- Deprecated support of configuration management, this can be managed by using mc admin config commands. Please refer to the [MinIO Client](https://min.io/docs/minio/linux/reference/minio-mc.html) for more information.
## Release v1.7.6
Bug Fix:
- Fix null pointer exception in Admin Info
- Ignore leading or trailing spaces in login request
- Fix file path on drag and drop
- Fix typo in User DN Search Filter example
## Release v1.7.5
Bug Fix:
- Fixed leaks during ZIP multiobject downloads
- Allow spaces in Policy names
## Release v1.7.4
Deprecations:
- Deprecated support tools User Interface in favor of mc admin commands. Please refer to the [MinIO SUBNET Registration page](https://min.io/docs/minio/linux/administration/console/subnet-registration.html#subnet) for more information.
- Deprecated Site replication User Interface in favor of mc admin commands. Please refer to the [MinIO Site Replication page](https://min.io/docs/minio/linux/operations/install-deploy-manage/multi-site-replication.html) for more information.
- Deprecated Lifecycle & Tiers User Interface in favor of mc admin commands. Please refer to the [MinIO Tiers page](https://min.io/docs/minio/linux/reference/minio-mc/mc-ilm-tier.html) for more information.
Bug Fix:
- Avoid loading unpkg.com call when login animation is off
## Release v1.7.3
Bug Fix:
- Use a fixed public license verification key
- Show non-expiring access keys as `no-expiry` instead of Jan 1, 1970
- Use "join Slack" button for non-commercial edition instead of "Signup"
- Fix setting policies on groups that have spaces
## Release v1.7.2
Bug Fix:
- Fixed issue in Server Health Info
- Fixed Security vulnerability in dependencies
- Fixed client string in trace message
Additional Changes:
- Remove live logs in Call Home Page
- Update License page
## Release v1.7.1
Bug Fix:
- Fixed issue that could cause a failure when attempting to view deleted files in the object browser
- Return network error when logging in and the network connection fails
Additional Changes:
- Added debug logging for console HTTP request (see [PR #3440](https://github.com/minio/console/pull/3440) for more detailed information)
## Release v1.7.0
Bug Fix:
- Fixed directory listing
- Fix MinIO videos link
Additional Changes:
- Removed deprecated KES functionality
## Release v1.6.3
Additional Changes:
- Updated go.mod version
## Release v1.6.2
Bug Fix:
- Fixed minor user session issues
- Updated project dependencies
Additional Changes:
- Improved Drives List visualization
- Improved WS request logic
- Updated License page with current MinIO plans.
## Release v1.6.1
Bug Fix:
- Fixed objectManager issues under certain conditions
- Fixed Security vulnerability in dependencies
Additional Changes:
- Improved Share Link behavior
## Release v1.6.0
Bug Fix:
- Fixed share link encoding
- Fixed Edit Lifecycle Storage Class
- Added Tiers Improvements for Bucket Lifecycle management
Additional Changes:
- Vulnerability updates
- Update Logo logic
## Release v1.5.0
Features:
- Added remove Tier functionality
Bug Fix:
- Fixed ILM rule tags not being shown
- Fixed race condition Object Browser websocket
- Fixed Encryption page crashing on empty response
- Fixed Replication Delete Marker comparisons
Additional Changes:
- Use automatic URI encoding for APIs
- Vulnerability updates
## Release v1.4.0
Features:
- Added VersionID support to metadata details
- Improved Websockets handlers
Bug Fix:
- Fixed vulnerabilities and updated dependencies
- Fixed an issue with Download URL decoding
- Fixed leak in Object Browser Websocket
- Minor UX fixes
## Release v1.3.0
Features:
- Adds ExpireDeleteMarker status to BucketLifecycleRule UI
Bug Fix:
- Fixed vulnerability
- Used URL-safe base64 enconding for Share API
- Made Prefix field optional when Adding Tier
- Added Console user agent in MinIO Admin Client
## Release v1.2.0
Features:
- Updated file share logic to work as Proxy
Bug Fix:
- Updated project dependencies
- Fixed Key Permissions UX
- Added permissions validation to rewind button
- Fixed Health report upload to SUBNET
- Misc Cosmetic fixes
## Release v1.1.1
Bug Fix:
- Fixed folder download issue
## Release v1.1.0
Features:
- Added Set Expired object all versions selector
Bug Fix:
- Updated Go Dependencies
## Release v1.0.0
Features:
- Updated Preview message alert
Bug Fix:
- Updated Websocket API
- Fixed issues with download manager
- Fixed policies issues
## Release v0.46.0
Features:
- Added latest help content to forms
Bug Fix:
- Disabled Create User button in certain policy cases
- Fixed an issue with Logout request
- Upgraded project dependencies
## Release v0.45.0
Deprecated:
- Deprecated Heal / Drives page
Features:
- Updated tines on menus & pages
Bug Fix:
- Upgraded project dependencies
## Release v0.44.0
Bug Fix:
- Upgraded project dependencies
- Fixed events icons not loading in subpaths
## Release v0.43.1
Bug Fix:
- Update Share Object UI to reflect maximum expiration time in UI
## Release v0.43.0
Features:
- Updated PDF preview method
Bug Fix:
- Fixed vulnerabilities
- Prevented non-necessary metadata calls in object browser
## Release v0.42.2
Bug Fix:
- Hidden Prometheus metrics if URL is empty
## Release v0.42.1
Bug Fix:
- Reset go version to 1.19
## Release v0.42.0
Features:
- Introducing Dark Mode
Bug Fix:
- Fixed vulnerabilities
- Changes on Upload and Delete object urls
- Fixed blocking subpath creation if not enough permissions
- Removed share object option at prefix level
- Updated allowed actions for a deleted object
## Release v0.41.0
Features:
- Updated pages to use mds components
- support for resolving IPv4/IPv6
Bug Fix:
- Remove cache for ClientIP
- Fixed override environment variables display in settings page
- Fixed daylight savings time support in share modal
## Release v0.40.0
Features:
- Updated OpenID page
- Added New bucket event types support
Bug Fix:
- Fixed crash in access keys page
- Fixed AuditLog filters issue
- Fixed multiple issues with Object Browser
## Release v0.39.0
Features:
- Migrated metrics page to mds
- Migrated Register page to mds
Bug Fix:
- Fixed LDAP configuration page issues
- Load available certificates in logout
- Updated dependencies & go version
- Fixed delete objects functionality
## Release v0.38.0
Features:
- Added extra information to Service Accounts page
- Updated Tiers, Site Replication, Speedtest, Heal & Watch pages components
Bug Fix:
- Fixed IDP expiry time errors
- Updated project Dependencies
## Release v0.37.0
Features:
- Updated Trace and Logs page components
- Updated Prometheus metrics
Bug Fix:
- Disabled input fields for Subscription features if MinIO is not registered
## Release v0.36.0
Features:
- Updated Settings page components
Bug Fix:
- Show LDAP Enabled value LDAP configuration
- Download multiple objects in same path as they were selected
## Release v0.35.1
Bug Fix:
- Change timestamp format for zip creation
## Release v0.35.0
Features:
- Add Exclude Folders and Exclude Prefixes during bucket creation
- Download multiple selected objects as zip and ignore deleted objects
- Updated Call Home, Inspet, Profile and Health components
Bug Fix:
- Remove extra white spaces for configuration strings
- Allow Create New Path in bucket view when having right permissions
## Release v0.34.0
Features:
- Updated Buckets components
Bug Fix:
- Fixed SUBNET Health report upload
- Updated Download Handler
- Fixes issue with rewind
- Avoid 1 hour expiration for IDP credentials
---
## Release v0.33.0
Features:
- Updated OpenID, LDAP components
Bug Fix:
- Fixed security issues
- Fixed navigation issues in Object Browser
- Fixed Dashboard metrics
---
## Release v0.32.0
Features:
- Updated Users and Groups components
- Added placeholder image for Help Menu
Bug Fix:
- Fixed memory leak in WebSocket API for Object Browser
---
## Release v0.31.0
**Breaking Changes:**
- **Removed support for Standalone Deployments**
Features:
- Updated way files are displayed in uploading component
- Updated Audit Logs and Policies components
Bug Fix:
- Fixed Download folders issue in Object Browser
- Added missing Notification Events (ILM & REPLICA) in Events Notification Page
- Fixed Security Vulnerability for `semver` dependency
---
## Release v0.30.0
Features:
- Added MinIO Console Help Menu
- Updated UI Menu components
Bug Fix:
- Disable the Upload button on Object Browser if the user is not allowed
- Fixed security vulnerability for `lestrrat-go/jwx` and `fast-xml-parser`
- Fixed bug on sub-paths for Object Browser
- Reduce the number of calls to `/session` API endpoint to improve performance
- Rolled back the previous change for the Share File feature to no longer ask for Service Account access keys

View File

@@ -4,80 +4,56 @@ This is a REST portal server created using [go-swagger](https://github.com/go-sw
The API handlers are created using a YAML definition located in `swagger.YAML`.
To add new api, the YAML file needs to be updated with all the desired apis using
the [Swagger Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/), this includes paths,
parameters, definitions, tags, etc.
To add new api, the YAML file needs to be updated with all the desired apis using the [Swagger Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/), this includes paths, parameters, definitions, tags, etc.
## Generate server from YAML
Once the YAML file is ready we can autogenerate the code needed for the new api by just running:
Validate it:
```
swagger validate ./swagger.yml
```
Update server code:
```
make swagger-gen
```
This will update all the necessary code.
`./api/configure_console.go` is a file that contains the handlers to be used by the application, here is the only place
where we need to update our code to support the new apis. This file is not affected when running the swagger generator
and it is safe to edit.
`./restapi/configure_mcs.go` is a file that contains the handlers to be used by the application, here is the only place where we need to update our code to support the new apis. This file is not affected when running the swagger generator and it is safe to edit.
## Unit Tests
`./api/handlers_test.go` needs to be updated with the proper tests for the new api.
`./restapi/handlers_test.go` needs to be updated with the proper tests for the new api.
To run tests:
```
go test ./api
go test ./restapi
```
## Commit changes
After verification, commit your changes. This is a [great post](https://chris.beams.io/posts/git-commit/) on how to
write useful commit messages
After verification, commit your changes. This is a [great post](https://chris.beams.io/posts/git-commit/) on how to write useful commit messages
```
$ git commit -am 'Add some feature'
```
### Push to the branch
Push your locally committed changes to the remote origin (your fork)
```
$ git push origin my-new-feature
```
### Create a Pull Request
Pull requests can be created via GitHub. Refer
to [this document](https://help.github.com/articles/creating-a-pull-request/) for detailed steps on how to create a pull
request. After a Pull Request gets peer reviewed and approved, it will be merged.
Pull requests can be created via GitHub. Refer to [this document](https://help.github.com/articles/creating-a-pull-request/) for detailed steps on how to create a pull request. After a Pull Request gets peer reviewed and approved, it will be merged.
## FAQs
### How does ``console`` manages dependencies?
### How does ``mcs`` manages dependencies?
``MinIO`` uses `go mod` to manage its dependencies.
- Run `go get foo/bar` in the source folder to add the dependency to `go.mod` file.
To remove a dependency
- Edit your code and remove the import reference.
- Run `go mod tidy` in the source folder to remove dependency from `go.mod` file.
### What are the coding guidelines for console?
``console`` is fully conformant with Golang style.
Refer: [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project. If you observe
offending code, please feel free to send a pull request or ping us on [Slack](https://slack.min.io).
### What are the coding guidelines for mcs?
``mcs`` is fully conformant with Golang style. Refer: [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project. If you observe offending code, please feel free to send a pull request or ping us on [Slack](https://slack.min.io).

23731
CREDITS

File diff suppressed because it is too large Load Diff

View File

@@ -1,83 +1,4 @@
# Developing MinIO Console
The MinIO Console requires the [MinIO Server](https://github.com/minio/minio). For development purposes, you also need
to run both the MinIO Console web app and the MinIO Console server.
## Running MinIO Console server
Build the server in the main folder by running:
```
make
```
> Note: If it's the first time running the server, you might need to run `go mod tidy` to ensure you have all modules
> required.
> To start the server run:
```
CONSOLE_ACCESS_KEY=<your-access-key>
CONSOLE_SECRET_KEY=<your-secret-key>
CONSOLE_MINIO_SERVER=<minio-server-endpoint>
CONSOLE_DEV_MODE=on
./console server
```
## Running MinIO Console web app
Refer to `/web-app` [instructions](/web-app/README.md) to run the web app locally.
# Building with MinIO
To test console in its shipping format, you need to build it from the MinIO repository, the following step will guide
you to do that.
### 0. Building with UI Changes
If you are performing changes in the UI components of console and want to test inside the MinIO binary, you need to
build assets first.
In the console folder run
```shell
make assets
```
This will regenerate all the static assets that will be served by MinIO.
### 1. Clone the `MinIO` repository
In the parent folder of where you cloned this `console` repository, clone the MinIO Repository
```shell
git clone https://github.com/minio/minio.git
```
### 2. Update `go.mod` to use your local version
In the MinIO repository open `go.mod` and after the first `require()` directive add a `replace()` directive
```
...
)
replace (
github.com/minio/console => "../console"
)
require (
...
```
### 3. Build `MinIO`
Still in the MinIO folder, run
```shell
make build
```
# LDAP authentication with Console
# LDAP authentication with MCS
## Setup
@@ -90,12 +11,44 @@ $ docker run --rm -p 389:389 -p 636:636 --name my-openldap-container --detach os
Run the `billy.ldif` file using `ldapadd` command to create a new user and assign it to a group.
```
$ docker cp console/docs/ldap/billy.ldif my-openldap-container:/container/service/slapd/assets/test/billy.ldif
$ docker exec my-openldap-container ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin -f /container/service/slapd/assets/test/billy.ldif -H ldap://localhost
$ cat > billy.ldif << EOF
# LDIF fragment to create group branch under root
dn: uid=billy,dc=example,dc=org
uid: billy
cn: billy
sn: 3
objectClass: top
objectClass: posixAccount
objectClass: inetOrgPerson
loginShell: /bin/bash
homeDirectory: /home/billy
uidNumber: 14583102
gidNumber: 14564100
userPassword: {SSHA}j3lBh1Seqe4rqF1+NuWmjhvtAni1JC5A
mail: billy@example.org
gecos: Billy User
# Create base group
dn: ou=groups,dc=example,dc=org
objectclass:organizationalunit
ou: groups
description: generic groups branch
# create mcsAdmin group (this already exists on minio and have a policy of s3::*)
dn: cn=mcsAdmin,ou=groups,dc=example,dc=org
objectClass: top
objectClass: posixGroup
gidNumber: 678
# Assing group to new user
dn: cn=mcsAdmin,ou=groups,dc=example,dc=org
changetype: modify
add: memberuid
memberuid: billy
EOF
$ docker cp billy.ldif my-openldap-container:/container/service/slapd/assets/test/billy.ldif
$ docker exec my-openldap-container ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin -f /container/service/slapd/assets/test/billy.ldif -H ldap://localhost -ZZ
```
Query the ldap server to check the user billy was created correctly and got assigned to the consoleAdmin group, you
should get a list
Query the ldap server to check the user billy was created correctly and got assigned to the mcsAdmin group, you should get a list
containing ldap users and groups.
```
@@ -110,7 +63,7 @@ $ docker exec my-openldap-container ldapsearch -x -H ldap://localhost -b uid=bil
### Change the password for user billy
Set the new password for `billy` to `minio123` and enter `admin` as the default `LDAP Password`
Set the new password for `billy` to `minio123` and enter `admin` as the default `LDAP Password`
```
$ docker exec -it my-openldap-container /bin/bash
@@ -120,10 +73,9 @@ Re-enter new password:
Enter LDAP Password:
```
### Add the consoleAdmin policy to user billy on MinIO
### Add the mcsAdmin policy to user billy on MinIO
```
$ cat > consoleAdmin.json << EOF
$ cat > mcsAdmin.json << EOF
{
"Version": "2012-10-17",
"Statement": [
@@ -147,8 +99,8 @@ $ cat > consoleAdmin.json << EOF
]
}
EOF
$ mc admin policy create myminio consoleAdmin consoleAdmin.json
$ mc admin policy attach myminio consoleAdmin --user="uid=billy,dc=example,dc=org"
$ mc admin policy add myminio mcsAdmin mcsAdmin.json
$ mc admin policy set myminio mcsAdmin user=billy
```
## Run MinIO
@@ -164,9 +116,12 @@ export MINIO_IDENTITY_LDAP_SERVER_INSECURE=on
./minio server ~/Data
```
## Run Console
## Run MCS
```
export CONSOLE_LDAP_ENABLED=on
./console server
export MCS_ACCESS_KEY=minio
export MCS_SECRET_KEY=minio123
...
export MCS_LDAP_ENABLED=on
./mcs server
```

26
Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
FROM golang:1.13
RUN apt-get update -y && apt-get install -y ca-certificates
ADD go.mod /go/src/github.com/minio/mcs/go.mod
ADD go.sum /go/src/github.com/minio/mcs/go.sum
WORKDIR /go/src/github.com/minio/mcs/
# Get dependencies - will also be cached if we won't change mod/sum
RUN go mod download
ADD . /go/src/github.com/minio/mcs/
WORKDIR /go/src/github.com/minio/mcs/
ENV CGO_ENABLED=0
RUN go build -ldflags "-w -s" -a -o mcs ./cmd/mcs
FROM scratch
MAINTAINER MinIO Development "dev@min.io"
EXPOSE 9090
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=0 /go/src/github.com/minio/mcs/mcs .
CMD ["/mcs"]

6
Dockerfile.release Normal file
View File

@@ -0,0 +1,6 @@
FROM scratch
MAINTAINER MinIO Development "dev@min.io"
EXPOSE 9090
COPY mcs /mcs
ENTRYPOINT ["/mcs"]

278
Makefile
View File

@@ -3,284 +3,62 @@ GOPATH := $(shell go env GOPATH)
# Sets the build version based on the output of the following command, if we are building for a tag, that's the build else it uses the current git branch as the build
BUILD_VERSION:=$(shell git describe --exact-match --tags $(git log -n1 --pretty='%h') 2>/dev/null || git rev-parse --abbrev-ref HEAD 2>/dev/null)
BUILD_TIME:=$(shell date 2>/dev/null)
TAG ?= "minio/console:$(BUILD_VERSION)-dev"
MINIO_VERSION ?= "quay.io/minio/minio:latest"
TARGET_BUCKET ?= "target"
NODE_VERSION := $(shell cat .nvmrc)
TAG ?= "minio/m3:$(VERSION)-dev"
default: console
default: mcs
.PHONY: console
console:
@echo "Building Console binary to './console'"
@(GO111MODULE=on CGO_ENABLED=0 go build -trimpath --tags=kqueue --ldflags "-s -w" -o console ./cmd/console)
.PHONY: mcs
mcs:
@echo "Building mcs binary to './mcs'"
@(GO111MODULE=on CGO_ENABLED=0 go build -trimpath --tags=kqueue --ldflags "-s -w" -o mcs ./cmd/mcs)
k8sdev:
@docker build -t $(TAG) --build-arg build_version=$(BUILD_VERSION) --build-arg build_time='$(BUILD_TIME)' .
@kind load docker-image $(TAG)
@echo "Done, now restart your mcs deployment"
getdeps:
@mkdir -p ${GOPATH}/bin
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin
@which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.27.0)
verifiers: getdeps fmt lint
fmt:
@echo "Running $@ check"
@(env bash $(PWD)/verify-gofmt.sh)
crosscompile:
@(env bash $(PWD)/cross-compile.sh $(arg1))
@GO111MODULE=on gofmt -d cmd/
@GO111MODULE=on gofmt -d pkg/
lint:
@echo "Running $@ check"
@GO111MODULE=on ${GOPATH}/bin/golangci-lint cache clean
@GO111MODULE=on ${GOPATH}/bin/golangci-lint run --timeout=5m --config ./.golangci.yml
lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
@echo "Running $@ check"
@GO111MODULE=on ${GOPATH}/bin/golangci-lint run --timeout=5m --config ./.golangci.yml --fix
install: mcs
@echo "Installing mcs binary to '$(GOPATH)/bin/mcs'"
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/mcs $(GOPATH)/bin/mcs
@echo "Installation successful. To learn more, try \"mcs --help\"."
install: console
@echo "Installing console binary to '$(GOPATH)/bin/console'"
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/console $(GOPATH)/bin/console
@echo "Installation successful. To learn more, try \"console --help\"."
swagger-gen: clean-swagger swagger-console apply-gofmt
@echo "Done Generating swagger server code from yaml"
apply-gofmt:
@echo "Applying gofmt to all generated an existing files"
@GO111MODULE=on gofmt -w .
clean-swagger:
@echo "cleaning"
@rm -rf models
@rm -rf api/operations
swagger-console:
swagger-gen:
@echo "Generating swagger server code from yaml"
@swagger generate server -A console --main-package=management --server-package=api --exclude-main -P models.Principal -f ./swagger.yml -r NOTICE
@echo "Ensure basic install"
@(cd web-app; yarn; cd ..)
@echo "Generating typescript api"
@make swagger-typescript-api path="../swagger.yml" output="./src/api" name="consoleApi.ts"
@git restore api/server.go
swagger-typescript-api:
@(cd web-app; yarn swagger-typescript-api -p $(path) -o $(output) -n $(name) --custom-config ../generator.config.js; cd ..)
@rm -rf models
@rm -rf restapi/operations
@swagger generate server -A mcs --main-package=mcs --exclude-main -P models.Principal -f ./swagger.yml -r NOTICE
assets:
@(if [ -f "${NVM_DIR}/nvm.sh" ]; then \. "${NVM_DIR}/nvm.sh" && nvm install && nvm use && npm install -g yarn ; fi &&\
cd web-app; corepack enable; yarn install --prefer-offline; make build-static; yarn prettier --write . --log-level warn; cd ..)
@(cd portal-ui; yarn install; make build-static; cd ..)
test-integration:
@(docker stop pgsqlcontainer || true)
@(docker stop minio || true)
@(docker stop minio2 || true)
@(docker network rm mynet123 || true)
@echo "create docker network to communicate containers MinIO & PostgreSQL"
@(docker network create --subnet=173.18.0.0/29 mynet123)
@echo "docker run with MinIO Version below:"
@echo $(MINIO_VERSION)
@echo "MinIO 1"
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 --net=mynet123 -d --name minio --rm -p 9000:9000 -p 9091:9091 -e MINIO_KMS_SECRET_KEY=my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw= $(MINIO_VERSION) server /data{1...4} --console-address ':9091' && sleep 5)
@echo "MinIO 2"
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 --net=mynet123 -d --name minio2 --rm -p 9001:9001 -p 9092:9092 -e MINIO_KMS_SECRET_KEY=my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw= $(MINIO_VERSION) server /data{1...4} --address ':9001' --console-address ':9092' && sleep 5)
@echo "Postgres"
@(docker run --net=mynet123 --ip=173.18.0.4 --name pgsqlcontainer --rm -p 5432:5432 -e POSTGRES_PASSWORD=password -d postgres && sleep 5)
@echo "execute test and get coverage for test-integration:"
@(cd integration && go test -coverpkg=../api -c -tags testrunmain . && mkdir -p coverage && ./integration.test -test.v -test.run "^Test*" -test.coverprofile=coverage/system.out)
@(docker stop pgsqlcontainer)
@(docker stop minio)
@(docker stop minio2)
@(docker network rm mynet123)
test-replication:
@(docker stop minio || true)
@(docker stop minio1 || true)
@(docker stop minio2 || true)
@(docker network rm mynet123 || true)
@(docker network create mynet123)
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 \
--net=mynet123 -d \
--name minio \
--rm \
-p 9000:9000 \
-p 6000:6000 \
-e MINIO_KMS_SECRET_KEY=my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw= \
-e MINIO_ROOT_USER="minioadmin" \
-e MINIO_ROOT_PASSWORD="minioadmin" \
$(MINIO_VERSION) server /data{1...4} \
--address :9000 \
--console-address :6000)
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 \
--net=mynet123 -d \
--name minio1 \
--rm \
-p 9001:9001 \
-p 6001:6001 \
-e MINIO_KMS_SECRET_KEY=my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw= \
-e MINIO_ROOT_USER="minioadmin" \
-e MINIO_ROOT_PASSWORD="minioadmin" \
$(MINIO_VERSION) server /data{1...4} \
--address :9001 \
--console-address :6001)
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 \
--net=mynet123 -d \
--name minio2 \
--rm \
-p 9002:9002 \
-p 6002:6002 \
-e MINIO_KMS_SECRET_KEY=my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw= \
-e MINIO_ROOT_USER="minioadmin" \
-e MINIO_ROOT_PASSWORD="minioadmin" \
$(MINIO_VERSION) server /data{1...4} \
--address :9002 \
--console-address :6002)
@(cd replication && go test -coverpkg=../api -c -tags testrunmain . && mkdir -p coverage && ./replication.test -test.v -test.run "^Test*" -test.coverprofile=coverage/replication.out)
@(docker stop minio || true)
@(docker stop minio1 || true)
@(docker stop minio2 || true)
@(docker network rm mynet123 || true)
test-sso-integration:
@echo "create the network in bridge mode to communicate all containers"
@(docker network create my-net)
@echo "run openldap container using MinIO Image: quay.io/minio/openldap:latest"
@(docker run \
-e LDAP_ORGANIZATION="MinIO Inc" \
-e LDAP_DOMAIN="min.io" \
-e LDAP_ADMIN_PASSWORD="admin" \
--network my-net \
-p 389:389 \
-p 636:636 \
--name openldap \
--detach quay.io/minio/openldap:latest)
@echo "Run Dex container using MinIO Image: quay.io/minio/dex:latest"
@(docker run \
-e DEX_ISSUER=http://dex:5556/dex \
-e DEX_CLIENT_REDIRECT_URI=http://127.0.0.1:9090/oauth_callback \
-e DEX_LDAP_SERVER=openldap:389 \
--network my-net \
-p 5556:5556 \
--name dex \
--detach quay.io/minio/dex:latest)
@echo "running minio server"
@(docker run \
-v /data1 -v /data2 -v /data3 -v /data4 \
--network my-net \
-d \
--name minio \
--rm \
-p 9000:9000 \
-p 9001:9001 \
-e MINIO_IDENTITY_OPENID_CLIENT_ID="minio-client-app" \
-e MINIO_IDENTITY_OPENID_CLIENT_SECRET="minio-client-app-secret" \
-e MINIO_IDENTITY_OPENID_CLAIM_NAME=name \
-e MINIO_IDENTITY_OPENID_CONFIG_URL=http://dex:5556/dex/.well-known/openid-configuration \
-e MINIO_IDENTITY_OPENID_REDIRECT_URI=http://127.0.0.1:9090/oauth_callback \
-e MINIO_ROOT_USER=minio \
-e MINIO_ROOT_PASSWORD=minio123 $(MINIO_VERSION) server /data{1...4} --address :9000 --console-address :9001)
@echo "run mc commands to set the policy"
@(docker run --name minio-client --network my-net -dit --entrypoint=/bin/sh minio/mc)
@(docker exec minio-client mc alias set myminio/ http://minio:9000 minio minio123)
@echo "adding policy to Dillon Harper to be able to login:"
@(cd sso-integration && docker cp allaccess.json minio-client:/ && docker exec minio-client mc admin policy create myminio "Dillon Harper" allaccess.json)
@echo "starting bash script"
@(env bash $(PWD)/sso-integration/set-sso.sh)
@echo "add python module"
@(pip3 install bs4)
@echo "Executing the test:"
@(cd sso-integration && go test -coverpkg=../api -c -tags testrunmain . && mkdir -p coverage && ./sso-integration.test -test.v -test.run "^Test*" -test.coverprofile=coverage/sso-system.out)
test-permissions-1:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-1/")
@(docker stop minio)
test-permissions-2:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-2/")
@(docker stop minio)
test-permissions-3:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-3/")
@(docker stop minio)
test-permissions-4:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-4/")
@(docker stop minio)
test-permissions-6:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-6/")
@(docker stop minio)
test-apply-permissions:
@(env bash $(PWD)/web-app/tests/scripts/initialize-env.sh)
test-start-docker-minio:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
initialize-permissions: test-start-docker-minio test-apply-permissions
@echo "Done initializing permissions test"
cleanup-permissions:
@(env bash $(PWD)/web-app/tests/scripts/cleanup-env.sh)
@(docker stop minio)
initialize-docker-network:
@(docker network create test-network)
test-start-docker-minio-w-redirect-url: initialize-docker-network
@(docker run \
-e MINIO_BROWSER_REDIRECT_URL='http://localhost:8000/console/subpath/' \
-e MINIO_SERVER_URL='http://localhost:9000' \
-v /data1 -v /data2 -v /data3 -v /data4 \
-d --network host --name minio --rm\
quay.io/minio/minio:latest server /data{1...4})
test-start-docker-nginx-w-subpath:
@(docker run \
--network host \
-d --rm \
--add-host=host.docker.internal:host-gateway \
-v ./web-app/tests/subpath-nginx/nginx.conf:/etc/nginx/nginx.conf \
--name test-nginx nginx)
test-initialize-minio-nginx: test-start-docker-minio-w-redirect-url test-start-docker-nginx-w-subpath
cleanup-minio-nginx:
@(docker stop minio test-nginx & docker network rm test-network)
# https://stackoverflow.com/questions/19200235/golang-tests-in-sub-directory
# Note: go test ./... will run tests on the current folder and all subfolders.
# This is needed because tests can be in the folder or sub-folder(s), let's include them all please!.
test:
@echo "execute test and get coverage"
@(cd api && mkdir -p coverage && GO111MODULE=on go test ./... -test.v -coverprofile=coverage/coverage.out)
# https://stackoverflow.com/questions/19200235/golang-tests-in-sub-directory
# Note: go test ./... will run tests on the current folder and all subfolders.
# This is since tests in pkg folder are in subfolders and were not executed.
test-pkg:
@echo "execute test and get coverage"
@(cd pkg && mkdir -p coverage && GO111MODULE=on go test ./... -test.v -coverprofile=coverage/coverage-pkg.out)
@(GO111MODULE=on go test -race -v github.com/minio/mcs/restapi/...)
@(GO111MODULE=on go test -race -v github.com/minio/mcs/pkg/...)
coverage:
@(GO111MODULE=on go test -v -coverprofile=coverage.out github.com/minio/console/api/... && go tool cover -html=coverage.out && open coverage.html)
@(GO111MODULE=on go test -v -coverprofile=coverage.out github.com/minio/mcs/restapi/... && go tool cover -html=coverage.out && open coverage.html)
clean:
@echo "Cleaning up all the generated files"
@find . -name '*.test' | xargs rm -fv
@find . -name '*~' | xargs rm -fv
@rm -vf console
@rm -vf mcs
docker:
@docker buildx build --output=type=docker --platform linux/amd64 -t $(TAG) --build-arg build_version=$(BUILD_VERSION) --build-arg build_time='$(BUILD_TIME)' --build-arg NODE_VERSION='$(NODE_VERSION)' .
release: swagger-gen
@echo "Generating Release: $(RELEASE)"
@make assets
@git add -u .
@git add web-app/build/
@docker build -t $(TAG) --build-arg build_version=$(BUILD_VERSION) --build-arg build_time='$(BUILD_TIME)' .

2
NOTICE
View File

@@ -1,5 +1,5 @@
This file is part of MinIO Console Server
Copyright (c) 2023 MinIO, Inc.
Copyright (c) 2020 MinIO, Inc.
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

237
README.md
View File

@@ -1,50 +1,28 @@
# MinIO Console
![build](https://github.com/minio/console/workflows/Go/badge.svg) ![license](https://img.shields.io/badge/license-AGPL%20V3-blue)
A graphical user interface for [MinIO](https://github.com/minio/minio)
| Object Browser | Dashboard | Creating a bucket |
|------------------------------------|-------------------------------|-------------------------------|
| ![Object Browser](images/pic3.png) | ![Dashboard](images/pic1.png) | ![Dashboard](images/pic2.png) |
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
**Table of Contents**
- [MinIO Console](#minio-console)
- [Install](#install)
- [Build from source](#build-from-source)
- [Setup](#setup)
- [1. Create a user `console` using `mc`](#1-create-a-user-console-using-mc)
- [2. Create a policy for `console` with admin access to all resources (for testing)](#2-create-a-policy-for-console-with-admin-access-to-all-resources-for-testing)
- [3. Set the policy for the new `console` user](#3-set-the-policy-for-the-new-console-user)
- [Start Console service:](#start-console-service)
- [Start Console service with TLS:](#start-console-service-with-tls)
- [Connect Console to a Minio using TLS and a self-signed certificate](#connect-console-to-a-minio-using-tls-and-a-self-signed-certificate)
- [Contribute to console Project](#contribute-to-console-project)
<!-- markdown-toc end -->
MinIO Console is a library that provides a management and browser UI overlay for the MinIO Server.
| Dashboard | Adding A User |
| ------------- | ------------- |
| ![Dashboard](images/pic1.png) | ![Dashboard](images/pic2.png) |
## Setup
All `console` needs is a MinIO user with admin privileges and URL pointing to your MinIO deployment.
All `mcs` needs is a MinIO user with admin privileges and URL pointing to your MinIO deployment.
> Note: We don't recommend using MinIO's Operator Credentials
### 1. Create a user `console` using `mc`
```bash
mc admin user add myminio/
Enter Access Key: console
Enter Secret Key: xxxxxxxx
1. Create a user for `mcs` using `mc`.
```
$ set +o history
$ mc admin user add myminio mcs YOURMCSSECRET
$ set -o history
```
### 2. Create a policy for `console` with admin access to all resources (for testing)
2. Create a policy for `mcs` with access to everything (for testing and debugging)
```sh
cat > admin.json << EOF
```
$ cat > mcsAdmin.json << EOF
{
"Version": "2012-10-17",
"Statement": [{
@@ -67,151 +45,86 @@ cat > admin.json << EOF
]
}
EOF
$ mc admin policy add myminio mcsAdmin mcsAdmin.json
```
```sh
mc admin policy create myminio/ consoleAdmin admin.json
3. Set the policy for the new `mcs` user
```
$ mc admin policy set myminio mcsAdmin user=mcs
```
### 3. Set the policy for the new `console` user
```sh
mc admin policy attach myminio consoleAdmin --user=console
### Note
Additionally, you can create policies to limit the privileges for `mcs` users, for example, if you want the user to only have access to dashboard, buckets, notifications and watch page, the policy should look like this:
```
> NOTE: Additionally, you can create policies to limit the privileges for other `console` users, for example, if you
> want the user to only have access to dashboard, buckets, notifications and watch page, the policy should look like
> this:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"admin:ServerInfo"
],
"Effect": "Allow",
"Sid": ""
},
{
"Action": [
"s3:ListenBucketNotification",
"s3:PutBucketNotification",
"s3:GetBucketNotification",
"s3:ListMultipartUploadParts",
"s3:ListBucketMultipartUploads",
"s3:ListBucket",
"s3:HeadBucket",
"s3:GetObject",
"s3:GetBucketLocation",
"s3:AbortMultipartUpload",
"s3:CreateBucket",
"s3:PutObject",
"s3:DeleteObject",
"s3:DeleteBucket",
"s3:PutBucketPolicy",
"s3:DeleteBucketPolicy",
"s3:GetBucketPolicy"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::*"
],
"Sid": ""
}
]
"Version": "2012-10-17",
"Statement": [{
"Action": [
"admin:ServerInfo"
],
"Effect": "Allow",
"Sid": ""
},
{
"Action": [
"s3:ListenBucketNotification",
"s3:PutBucketNotification",
"s3:GetBucketNotification",
"s3:ListMultipartUploadParts",
"s3:ListBucketMultipartUploads",
"s3:ListBucket",
"s3:HeadBucket",
"s3:GetObject",
"s3:GetBucketLocation",
"s3:AbortMultipartUpload",
"s3:CreateBucket",
"s3:PutObject",
"s3:DeleteObject",
"s3:DeleteBucket",
"s3:PutBucketPolicy",
"s3:DeleteBucketPolicy",
"s3:GetBucketPolicy"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::*"
],
"Sid": ""
}
]
}
```
## Start Console service:
Before running console service, following environment settings must be supplied
```sh
# Salt to encrypt JWT payload
export CONSOLE_PBKDF_PASSPHRASE=SECRET
# Required to encrypt JWT payload
export CONSOLE_PBKDF_SALT=SECRET
# MinIO Endpoint
export CONSOLE_MINIO_SERVER=http://localhost:9000
```
Now start the console service.
## Run MCS server
To run the server:
```
./console server
2021-01-19 02:36:08.893735 I | 2021/01/19 02:36:08 server.go:129: Serving console at http://localhost:9090
export MCS_HMAC_JWT_SECRET=YOURJWTSIGNINGSECRET
#required to encrypt jwet payload
export MCS_PBKDF_PASSPHRASE=SECRET
#required to encrypt jwet payload
export MCS_PBKDF_SALT=SECRET
export MCS_ACCESS_KEY=mcs
export MCS_SECRET_KEY=YOURMCSSECRET
export MCS_MINIO_SERVER=http://localhost:9000
./mcs server
```
By default `console` runs on port `9090` this can be changed with `--port` of your choice.
## Start Console service with TLS:
Copy your `public.crt` and `private.key` to `~/.console/certs`, then:
```sh
./console server
2021-01-19 02:36:08.893735 I | 2021/01/19 02:36:08 server.go:129: Serving console at http://[::]:9090
2021-01-19 02:36:08.893735 I | 2021/01/19 02:36:08 server.go:129: Serving console at https://[::]:9443
```
For advanced users, `console` has support for multiple certificates to service clients through multiple domains.
Following tree structure is expected for supporting multiple domains:
```sh
certs/
├─ public.crt
├─ private.key
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
├─ public.crt
└─ private.key
...
## Connect MCS to a Minio using TLS and a self-signed certificate
```
## Connect Console to a Minio using TLS and a self-signed certificate
Copy the MinIO `ca.crt` under `~/.console/certs/CAs`, then:
```sh
export CONSOLE_MINIO_SERVER=https://localhost:9000
./console server
...
export MCS_MINIO_SERVER_TLS_ROOT_CAS=<certificate_file_name>
export MCS_MINIO_SERVER=https://localhost:9000
./mcs server
```
You can verify that the apis work by doing the request on `localhost:9090/api/v1/...`
## Debug logging
In some cases it may be convenient to log all HTTP requests. This can be enabled by setting
the `CONSOLE_DEBUG_LOGLEVEL` environment variable to one of the following values:
- `0` (default) uses no logging.
- `1` log single line per request for server-side errors (status-code 5xx).
- `2` log single line per request for client-side and server-side errors (status-code 4xx/5xx).
- `3` log single line per request for all requests (status-code 4xx/5xx).
- `4` log details per request for server-side errors (status-code 5xx).
- `5` log details per request for client-side and server-side errors (status-code 4xx/5xx).
- `6` log details per request for all requests (status-code 4xx/5xx).
A single line logging has the following information:
- Remote endpoint (IP + port) of the request. Note that reverse proxies may hide the actual remote endpoint of the client's browser.
- HTTP method and URL
- Status code of the response (websocket connections are hijacked, so no response is shown)
- Duration of the request
The detailed logging also includes all request and response headers (if any).
# Contribute to console Project
Please follow console [Contributor's Guide](https://github.com/minio/console/blob/master/CONTRIBUTING.md)
# Contribute to mcs Project
Please follow mcs [Contributor's Guide](https://github.com/minio/mcs/blob/master/CONTRIBUTING.md)

View File

@@ -2,12 +2,12 @@
## Supported Versions
We always provide security updates for the [latest release](https://github.com/minio/console/releases/latest).
We always provide security updates for the [latest release](https://github.com/minio/mcs/releases/latest).
Whenever there is a security update you just need to upgrade to the latest version.
## Reporting a Vulnerability
All security bugs in [minio/console](https://github,com/minio/console) (or other minio/* repositories)
All security bugs in [minio/mcs](https://github,com/minio/mcs) (or other minio/* repositories)
should be reported by email to security@min.io. Your email will be acknowledged within 48 hours,
and you'll receive a more detailed response to your email within 72 hours indicating the next steps
in handling your report.
@@ -18,14 +18,13 @@ you need access credentials for a successful exploit).
If you have not received a reply to your email within 48 hours or you have not heard from the security team
for the past five days please contact the security team directly:
- Primary security coordinator: daniel@min.io
- Secondary coordinator: security@min.io
- If you receive no response: dev@min.io
- Primary security coordinator: lenin@min.io
- Secondary coordinator: daniel@min.io, cesar@min.io
- If you receive no response: dev@min.io
### Disclosure Process
MinIO Console uses the following disclosure process:
MinIO uses the following disclosure process:
1. Once the security report is received one member of the security team tries to verify and reproduce
the issue and determines the impact it has.
@@ -34,8 +33,8 @@ MinIO Console uses the following disclosure process:
3. Code is audited to find any potential similar problems.
4. Fixes are prepared for the latest release.
5. On the date that the fixes are applied a security advisory will be published on https://blog.min.io.
Please inform us in your report email whether MinIO Console should mention your contribution w.r.t. fixing
the security issue. By default MinIO Console will **not** publish this information to protect your privacy.
Please inform us in your report email whether MinIO should mention your contribution w.r.t. fixing
the security issue. By default MinIO will **not** publish this information to protect your privacy.
This process can take some time, especially when coordination is required with maintainers of other projects.
Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we

View File

@@ -1,38 +0,0 @@
## Vulnerability Management Policy
This document formally describes the process of addressing and managing a
reported vulnerability that has been found in the MinIO Console server code base,
any directly connected ecosystem component or a direct / indirect dependency
of the code base.
### Scope
The vulnerability management policy described in this document covers the
process of investigating, assessing and resolving a vulnerability report
opened by a MinIO Console employee or an external third party.
Therefore, it lists pre-conditions and actions that should be performed to
resolve and fix a reported vulnerability.
### Vulnerability Management Process
The vulnerability management process requires that the vulnerability report
contains the following information:
- The project / component that contains the reported vulnerability.
- A description of the vulnerability. In particular, the type of the
reported vulnerability and how it might be exploited. Alternatively,
a well-established vulnerability identifier, e.g. CVE number, can be
used instead.
Based on the description mentioned above, a MinIO Console engineer or security team
member investigates:
- Whether the reported vulnerability exists.
- The conditions that are required such that the vulnerability can be exploited.
- The steps required to fix the vulnerability.
In general, if the vulnerability exists in one of the MinIO Console code bases
itself - not in a code dependency - then MinIO Console will, if possible, fix
the vulnerability or implement reasonable countermeasures such that the
vulnerability cannot be exploited anymore.

View File

@@ -1,90 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// 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 api
import (
"context"
"time"
"github.com/minio/mc/cmd"
"github.com/minio/minio-go/v7"
)
type objectsListOpts struct {
BucketName string
Prefix string
Date time.Time
}
type ObjectsRequest struct {
Mode string `json:"mode,omitempty"`
BucketName string `json:"bucket_name"`
Prefix string `json:"prefix"`
Date string `json:"date"`
RequestID int64 `json:"request_id"`
}
type WSResponse struct {
RequestID int64 `json:"request_id,omitempty"`
Error *CodedAPIError `json:"error,omitempty"`
RequestEnd bool `json:"request_end,omitempty"`
Prefix string `json:"prefix,omitempty"`
BucketName string `json:"bucketName,omitempty"`
Data []ObjectResponse `json:"data,omitempty"`
}
type ObjectResponse struct {
Name string `json:"name,omitempty"`
LastModified string `json:"last_modified,omitempty"`
Size int64 `json:"size,omitempty"`
VersionID string `json:"version_id,omitempty"`
DeleteMarker bool `json:"delete_flag,omitempty"`
IsLatest bool `json:"is_latest,omitempty"`
}
func getObjectsOptionsFromReq(request ObjectsRequest) (*objectsListOpts, error) {
pOptions := objectsListOpts{
BucketName: request.BucketName,
Prefix: request.Prefix,
}
if request.Mode == "rewind" {
parsedDate, errDate := time.Parse(time.RFC3339, request.Date)
if errDate != nil {
return nil, errDate
}
pOptions.Date = parsedDate
}
return &pOptions, nil
}
func startObjectsListing(ctx context.Context, client MinioClient, objOpts *objectsListOpts) <-chan minio.ObjectInfo {
opts := minio.ListObjectsOptions{
Prefix: objOpts.Prefix,
}
return client.listObjects(ctx, objOpts.BucketName, opts)
}
func startRewindListing(ctx context.Context, client MCClient, objOpts *objectsListOpts) <-chan *cmd.ClientContent {
lsRewind := client.list(ctx, cmd.ListOptions{TimeRef: objOpts.Date, WithDeleteMarkers: true})
return lsRewind
}

View File

@@ -1,236 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// 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 api
import (
"context"
"testing"
"time"
mc "github.com/minio/mc/cmd"
"github.com/minio/minio-go/v7"
"github.com/stretchr/testify/assert"
)
func TestWSRewindObjects(t *testing.T) {
assert := assert.New(t)
client := s3ClientMock{}
tests := []struct {
name string
testOptions objectsListOpts
testMessages []*mc.ClientContent
}{
{
name: "Get list with multiple elements",
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/",
Date: time.Now(),
},
testMessages: []*mc.ClientContent{
{
BucketName: "buckettest",
URL: mc.ClientURL{Path: "/file1.txt"},
},
{
BucketName: "buckettest",
URL: mc.ClientURL{Path: "/file2.txt"},
},
{
BucketName: "buckettest",
URL: mc.ClientURL{Path: "/path1"},
},
},
},
{
name: "Empty list of elements",
testOptions: objectsListOpts{
BucketName: "emptybucket",
Prefix: "/",
Date: time.Now(),
},
testMessages: []*mc.ClientContent{},
},
{
name: "Get list with one element",
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/",
Date: time.Now(),
},
testMessages: []*mc.ClientContent{
{
BucketName: "buckettestsingle",
URL: mc.ClientURL{Path: "/file12.txt"},
},
},
},
{
name: "Get data from subpaths",
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/path1/path2",
Date: time.Now(),
},
testMessages: []*mc.ClientContent{
{
BucketName: "buckettestsingle",
URL: mc.ClientURL{Path: "/path1/path2/file12.txt"},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mcListMock = func(_ context.Context, _ mc.ListOptions) <-chan *mc.ClientContent {
ch := make(chan *mc.ClientContent)
go func() {
defer close(ch)
for _, m := range tt.testMessages {
ch <- m
}
}()
return ch
}
rewindList := startRewindListing(ctx, client, &tt.testOptions)
// check that the rewindList got the same number of data from Console.
totalItems := 0
for data := range rewindList {
// Compare elements as we are defining the channel responses
assert.Equal(tt.testMessages[totalItems].URL.Path, data.URL.Path)
totalItems++
}
assert.Equal(len(tt.testMessages), totalItems)
})
}
}
func TestWSListObjects(t *testing.T) {
assert := assert.New(t)
client := minioClientMock{}
tests := []struct {
name string
wantErr bool
testOptions objectsListOpts
testMessages []minio.ObjectInfo
}{
{
name: "Get list with multiple elements",
wantErr: false,
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/",
},
testMessages: []minio.ObjectInfo{
{
Key: "/file1.txt",
Size: 500,
IsLatest: true,
LastModified: time.Now(),
},
{
Key: "/file2.txt",
Size: 500,
IsLatest: true,
LastModified: time.Now(),
},
{
Key: "/path1",
},
},
},
{
name: "Empty list of elements",
wantErr: false,
testOptions: objectsListOpts{
BucketName: "emptybucket",
Prefix: "/",
},
testMessages: []minio.ObjectInfo{},
},
{
name: "Get list with one element",
wantErr: false,
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/",
},
testMessages: []minio.ObjectInfo{
{
Key: "/file2.txt",
Size: 500,
IsLatest: true,
LastModified: time.Now(),
},
},
},
{
name: "Get data from subpaths",
wantErr: false,
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/path1/path2",
},
testMessages: []minio.ObjectInfo{
{
Key: "/path1/path2/file1.txt",
Size: 500,
IsLatest: true,
LastModified: time.Now(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
minioListObjectsMock = func(_ context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo {
ch := make(chan minio.ObjectInfo)
go func() {
defer close(ch)
for _, m := range tt.testMessages {
ch <- m
}
}()
return ch
}
objectsListing := startObjectsListing(ctx, client, &tt.testOptions)
// check that the TestReceiver got the same number of data from Console
totalItems := 0
for data := range objectsListing {
// Compare elements as we are defining the channel responses
assert.Equal(tt.testMessages[totalItems].Key, data.Key)
totalItems++
}
assert.Equal(len(tt.testMessages), totalItems)
})
}
}

View File

@@ -1,190 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"context"
"net"
"net/http"
"net/url"
"regexp"
"strings"
"github.com/minio/console/pkg"
"github.com/minio/console/pkg/utils"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7/pkg/credentials"
)
const globalAppName = "MinIO Console"
// MinioAdmin interface with all functions to be implemented
// by mock when testing, it should include all MinioAdmin respective api calls
// that are used within this project.
type MinioAdmin interface {
AccountInfo(ctx context.Context) (madmin.AccountInfo, error)
// KMS
kmsStatus(ctx context.Context) (madmin.KMSStatus, error)
}
// Interface implementation
//
// Define the structure of a minIO Client and define the functions that are actually used
// from minIO api.
type AdminClient struct {
Client *madmin.AdminClient
}
// AccountInfo implements madmin.AccountInfo()
func (ac AdminClient) AccountInfo(ctx context.Context) (madmin.AccountInfo, error) {
return ac.Client.AccountInfo(ctx, madmin.AccountOpts{})
}
func (ac AdminClient) getBucketQuota(ctx context.Context, bucket string) (madmin.BucketQuota, error) {
return ac.Client.GetBucketQuota(ctx, bucket)
}
func (ac AdminClient) kmsStatus(ctx context.Context) (madmin.KMSStatus, error) {
return ac.Client.KMSStatus(ctx)
}
func NewMinioAdminClient(ctx context.Context, sessionClaims *models.Principal) (*madmin.AdminClient, error) {
clientIP := utils.ClientIPFromContext(ctx)
adminClient, err := newAdminFromClaims(sessionClaims, clientIP)
if err != nil {
return nil, err
}
adminClient.SetAppInfo(globalAppName, pkg.Version)
return adminClient, nil
}
// newAdminFromClaims creates a minio admin from Decrypted claims using Assume role credentials
func newAdminFromClaims(claims *models.Principal, clientIP string) (*madmin.AdminClient, error) {
tlsEnabled := getMinIOEndpointIsSecure()
endpoint := getMinIOEndpoint()
adminClient, err := madmin.NewWithOptions(endpoint, &madmin.Options{
Creds: credentials.NewStaticV4(claims.STSAccessKeyID, claims.STSSecretAccessKey, claims.STSSessionToken),
Secure: tlsEnabled,
})
if err != nil {
return nil, err
}
adminClient.SetAppInfo(globalAppName, pkg.Version)
adminClient.SetCustomTransport(PrepareSTSClientTransport(clientIP))
return adminClient, nil
}
// isLocalAddress returns true if the url contains an IPv4/IPv6 hostname
// that points to the local machine - FQDN are not supported
func isLocalIPEndpoint(endpoint string) bool {
u, err := url.Parse(endpoint)
if err != nil {
return false
}
return isLocalIPAddress(u.Hostname())
}
// isLocalAddress returns true if the url contains an IPv4/IPv6 hostname
// that points to the local machine - FQDN are not supported
func isLocalIPAddress(ipAddr string) bool {
if ipAddr == "" {
return false
}
if ipAddr == "localhost" {
return true
}
ip := net.ParseIP(ipAddr)
return ip != nil && ip.IsLoopback()
}
// GetConsoleHTTPClient caches different http clients depending on the target endpoint while taking
// in consideration CA certs stored in ${HOME}/.console/certs/CAs and ${HOME}/.minio/certs/CAs
// If the target endpoint points to a loopback device, skip the TLS verification.
func GetConsoleHTTPClient(clientIP string) *http.Client {
return PrepareConsoleHTTPClient(clientIP)
}
var (
// De-facto standard header keys.
xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
xRealIP = http.CanonicalHeaderKey("X-Real-IP")
)
var (
// RFC7239 defines a new "Forwarded: " header designed to replace the
// existing use of X-Forwarded-* headers.
// e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
forwarded = http.CanonicalHeaderKey("Forwarded")
// Allows for a sub-match of the first value after 'for=' to the next
// comma, semi-colon or space. The match is case-insensitive.
forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)(.*)`)
)
// getSourceIPFromHeaders retrieves the IP from the X-Forwarded-For, X-Real-IP
// and RFC7239 Forwarded headers (in that order)
func getSourceIPFromHeaders(r *http.Request) string {
var addr string
if fwd := r.Header.Get(xForwardedFor); fwd != "" {
// Only grab the first (client) address. Note that '192.168.0.1,
// 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
// the first may represent forwarding proxies earlier in the chain.
s := strings.Index(fwd, ", ")
if s == -1 {
s = len(fwd)
}
addr = fwd[:s]
} else if fwd := r.Header.Get(xRealIP); fwd != "" {
// X-Real-IP should only contain one IP address (the client making the
// request).
addr = fwd
} else if fwd := r.Header.Get(forwarded); fwd != "" {
// match should contain at least two elements if the protocol was
// specified in the Forwarded header. The first element will always be
// the 'for=' capture, which we ignore. In the case of multiple IP
// addresses (for=8.8.8.8, 8.8.4.4, 172.16.1.20 is valid) we only
// extract the first, which should be the client IP.
if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 {
// IPv6 addresses in Forwarded headers are quoted-strings. We strip
// these quotes.
addr = strings.Trim(match[1], `"`)
}
}
return addr
}
// getClientIP retrieves the IP from the request headers
// and falls back to r.RemoteAddr when necessary.
// however returns without bracketing.
func getClientIP(r *http.Request) string {
addr := getSourceIPFromHeaders(r)
if addr == "" {
addr = r.RemoteAddr
}
// Default to remote address if headers not set.
raddr, _, _ := net.SplitHostPort(addr)
if raddr == "" {
return addr
}
return raddr
}

View File

@@ -1,473 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"path"
"strings"
"time"
"github.com/minio/minio-go/v7/pkg/sse"
xnet "github.com/minio/pkg/v3/net"
"github.com/minio/console/models"
"github.com/minio/console/pkg"
"github.com/minio/console/pkg/auth"
"github.com/minio/console/pkg/auth/ldap"
xjwt "github.com/minio/console/pkg/auth/token"
mc "github.com/minio/mc/cmd"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/notification"
"github.com/minio/minio-go/v7/pkg/tags"
)
func init() {
// All minio-go API operations shall be performed only once,
// another way to look at this is we are turning off retries.
minio.MaxRetry = 1
}
// MinioClient interface with all functions to be implemented
// by mock when testing, it should include all MinioClient respective api calls
// that are used within this project.
type MinioClient interface {
listBucketsWithContext(ctx context.Context) ([]minio.BucketInfo, error)
makeBucketWithContext(ctx context.Context, bucketName, location string, objectLocking bool) error
setBucketPolicyWithContext(ctx context.Context, bucketName, policy string) error
removeBucket(ctx context.Context, bucketName string) error
getBucketNotification(ctx context.Context, bucketName string) (config notification.Configuration, err error)
getBucketPolicy(ctx context.Context, bucketName string) (string, error)
listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo
getObjectRetention(ctx context.Context, bucketName, objectName, versionID string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error)
getObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error)
putObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error)
putObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error
putObjectRetention(ctx context.Context, bucketName, objectName string, opts minio.PutObjectRetentionOptions) error
statObject(ctx context.Context, bucketName, prefix string, opts minio.GetObjectOptions) (objectInfo minio.ObjectInfo, err error)
setBucketEncryption(ctx context.Context, bucketName string, config *sse.Configuration) error
removeBucketEncryption(ctx context.Context, bucketName string) error
getBucketEncryption(ctx context.Context, bucketName string) (*sse.Configuration, error)
putObjectTagging(ctx context.Context, bucketName, objectName string, otags *tags.Tags, opts minio.PutObjectTaggingOptions) error
getObjectTagging(ctx context.Context, bucketName, objectName string, opts minio.GetObjectTaggingOptions) (*tags.Tags, error)
setObjectLockConfig(ctx context.Context, bucketName string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit) error
getBucketObjectLockConfig(ctx context.Context, bucketName string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error)
getObjectLockConfig(ctx context.Context, bucketName string) (lock string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error)
copyObject(ctx context.Context, dst minio.CopyDestOptions, src minio.CopySrcOptions) (minio.UploadInfo, error)
GetBucketTagging(ctx context.Context, bucketName string) (*tags.Tags, error)
SetBucketTagging(ctx context.Context, bucketName string, tags *tags.Tags) error
RemoveBucketTagging(ctx context.Context, bucketName string) error
}
// Interface implementation
//
// Define the structure of a minIO Client and define the functions that are actually used
// from minIO api.
type minioClient struct {
client *minio.Client
}
func (c minioClient) GetBucketTagging(ctx context.Context, bucketName string) (*tags.Tags, error) {
return c.client.GetBucketTagging(ctx, bucketName)
}
func (c minioClient) SetBucketTagging(ctx context.Context, bucketName string, tags *tags.Tags) error {
return c.client.SetBucketTagging(ctx, bucketName, tags)
}
func (c minioClient) RemoveBucketTagging(ctx context.Context, bucketName string) error {
return c.client.RemoveBucketTagging(ctx, bucketName)
}
// implements minio.ListBuckets(ctx)
func (c minioClient) listBucketsWithContext(ctx context.Context) ([]minio.BucketInfo, error) {
return c.client.ListBuckets(ctx)
}
// implements minio.MakeBucketWithContext(ctx, bucketName, location, objectLocking)
func (c minioClient) makeBucketWithContext(ctx context.Context, bucketName, location string, objectLocking bool) error {
return c.client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{
Region: location,
ObjectLocking: objectLocking,
})
}
// implements minio.SetBucketPolicyWithContext(ctx, bucketName, policy)
func (c minioClient) setBucketPolicyWithContext(ctx context.Context, bucketName, policy string) error {
return c.client.SetBucketPolicy(ctx, bucketName, policy)
}
// implements minio.RemoveBucket(bucketName)
func (c minioClient) removeBucket(ctx context.Context, bucketName string) error {
return c.client.RemoveBucket(ctx, bucketName)
}
// implements minio.GetBucketNotification(bucketName)
func (c minioClient) getBucketNotification(ctx context.Context, bucketName string) (config notification.Configuration, err error) {
return c.client.GetBucketNotification(ctx, bucketName)
}
// implements minio.GetBucketPolicy(bucketName)
func (c minioClient) getBucketPolicy(ctx context.Context, bucketName string) (string, error) {
return c.client.GetBucketPolicy(ctx, bucketName)
}
// implements minio.getBucketVersioning(ctx, bucketName)
func (c minioClient) getBucketVersioning(ctx context.Context, bucketName string) (minio.BucketVersioningConfiguration, error) {
return c.client.GetBucketVersioning(ctx, bucketName)
}
// implements minio.listObjects(ctx)
func (c minioClient) listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
return c.client.ListObjects(ctx, bucket, opts)
}
func (c minioClient) getObjectRetention(ctx context.Context, bucketName, objectName, versionID string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) {
return c.client.GetObjectRetention(ctx, bucketName, objectName, versionID)
}
func (c minioClient) getObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) {
return c.client.GetObjectLegalHold(ctx, bucketName, objectName, opts)
}
func (c minioClient) putObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error) {
return c.client.PutObject(ctx, bucketName, objectName, reader, objectSize, opts)
}
func (c minioClient) putObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error {
return c.client.PutObjectLegalHold(ctx, bucketName, objectName, opts)
}
func (c minioClient) putObjectRetention(ctx context.Context, bucketName, objectName string, opts minio.PutObjectRetentionOptions) error {
return c.client.PutObjectRetention(ctx, bucketName, objectName, opts)
}
func (c minioClient) statObject(ctx context.Context, bucketName, prefix string, opts minio.GetObjectOptions) (objectInfo minio.ObjectInfo, err error) {
return c.client.StatObject(ctx, bucketName, prefix, opts)
}
// implements minio.SetBucketEncryption(ctx, bucketName, config)
func (c minioClient) setBucketEncryption(ctx context.Context, bucketName string, config *sse.Configuration) error {
return c.client.SetBucketEncryption(ctx, bucketName, config)
}
// implements minio.RemoveBucketEncryption(ctx, bucketName)
func (c minioClient) removeBucketEncryption(ctx context.Context, bucketName string) error {
return c.client.RemoveBucketEncryption(ctx, bucketName)
}
// implements minio.GetBucketEncryption(ctx, bucketName, config)
func (c minioClient) getBucketEncryption(ctx context.Context, bucketName string) (*sse.Configuration, error) {
return c.client.GetBucketEncryption(ctx, bucketName)
}
func (c minioClient) putObjectTagging(ctx context.Context, bucketName, objectName string, otags *tags.Tags, opts minio.PutObjectTaggingOptions) error {
return c.client.PutObjectTagging(ctx, bucketName, objectName, otags, opts)
}
func (c minioClient) getObjectTagging(ctx context.Context, bucketName, objectName string, opts minio.GetObjectTaggingOptions) (*tags.Tags, error) {
return c.client.GetObjectTagging(ctx, bucketName, objectName, opts)
}
func (c minioClient) setObjectLockConfig(ctx context.Context, bucketName string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit) error {
return c.client.SetObjectLockConfig(ctx, bucketName, mode, validity, unit)
}
func (c minioClient) getBucketObjectLockConfig(ctx context.Context, bucketName string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) {
return c.client.GetBucketObjectLockConfig(ctx, bucketName)
}
func (c minioClient) getObjectLockConfig(ctx context.Context, bucketName string) (lock string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) {
return c.client.GetObjectLockConfig(ctx, bucketName)
}
func (c minioClient) copyObject(ctx context.Context, dst minio.CopyDestOptions, src minio.CopySrcOptions) (minio.UploadInfo, error) {
return c.client.CopyObject(ctx, dst, src)
}
// MCClient interface with all functions to be implemented
// by mock when testing, it should include all mc/S3Client respective api calls
// that are used within this project.
type MCClient interface {
addNotificationConfig(ctx context.Context, arn string, events []string, prefix, suffix string, ignoreExisting bool) *probe.Error
removeNotificationConfig(ctx context.Context, arn string, event string, prefix string, suffix string) *probe.Error
watch(ctx context.Context, options mc.WatchOptions) (*mc.WatchObject, *probe.Error)
remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass, forceDelete bool, contentCh <-chan *mc.ClientContent) <-chan mc.RemoveResult
list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent
get(ctx context.Context, opts mc.GetOptions) (io.ReadCloser, *probe.Error)
shareDownload(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error)
setVersioning(ctx context.Context, status string, excludePrefix []string, excludeFolders bool) *probe.Error
}
// Interface implementation
//
// Define the structure of a mc S3Client and define the functions that are actually used
// from mcS3client api.
type mcClient struct {
client *mc.S3Client
}
// implements S3Client.AddNotificationConfig()
func (c mcClient) addNotificationConfig(ctx context.Context, arn string, events []string, prefix, suffix string, ignoreExisting bool) *probe.Error {
return c.client.AddNotificationConfig(ctx, arn, events, prefix, suffix, ignoreExisting)
}
// implements S3Client.RemoveNotificationConfig()
func (c mcClient) removeNotificationConfig(ctx context.Context, arn string, event string, prefix string, suffix string) *probe.Error {
return c.client.RemoveNotificationConfig(ctx, arn, event, prefix, suffix)
}
func (c mcClient) watch(ctx context.Context, options mc.WatchOptions) (*mc.WatchObject, *probe.Error) {
return c.client.Watch(ctx, options)
}
func (c mcClient) setVersioning(ctx context.Context, status string, excludePrefix []string, excludeFolders bool) *probe.Error {
return c.client.SetVersion(ctx, status, excludePrefix, excludeFolders)
}
func (c mcClient) remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass, forceDelete bool, contentCh <-chan *mc.ClientContent) <-chan mc.RemoveResult {
return c.client.Remove(ctx, isIncomplete, isRemoveBucket, isBypass, forceDelete, contentCh)
}
func (c mcClient) list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent {
return c.client.List(ctx, opts)
}
func (c mcClient) get(ctx context.Context, opts mc.GetOptions) (io.ReadCloser, *probe.Error) {
rd, _, err := c.client.Get(ctx, opts)
return rd, err
}
func (c mcClient) shareDownload(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error) {
return c.client.ShareDownload(ctx, versionID, expires)
}
// ConsoleCredentialsI interface with all functions to be implemented
// by mock when testing, it should include all needed consoleCredentials.Login api calls
// that are used within this project.
type ConsoleCredentialsI interface {
Get() (credentials.Value, error)
Expire()
GetAccountAccessKey() string
}
// Interface implementation
type ConsoleCredentials struct {
ConsoleCredentials *credentials.Credentials
AccountAccessKey string
CredContext *credentials.CredContext
}
func (c ConsoleCredentials) GetAccountAccessKey() string {
return c.AccountAccessKey
}
// Get implements *Login.Get()
func (c ConsoleCredentials) Get() (credentials.Value, error) {
return c.ConsoleCredentials.GetWithContext(c.CredContext)
}
// Expire implements *Login.Expire()
func (c ConsoleCredentials) Expire() {
c.ConsoleCredentials.Expire()
}
// consoleSTSAssumeRole it's a STSAssumeRole wrapper, in general
// there's no need to use this struct anywhere else in the project, it's only required
// for passing a custom *http.Client to *credentials.STSAssumeRole
type consoleSTSAssumeRole struct {
stsAssumeRole *credentials.STSAssumeRole
}
func (s consoleSTSAssumeRole) RetrieveWithCredContext(cc *credentials.CredContext) (credentials.Value, error) {
return s.stsAssumeRole.RetrieveWithCredContext(cc)
}
func (s consoleSTSAssumeRole) Retrieve() (credentials.Value, error) {
return s.stsAssumeRole.Retrieve()
}
func (s consoleSTSAssumeRole) IsExpired() bool {
return s.stsAssumeRole.IsExpired()
}
func stsCredentials(minioURL, accessKey, secretKey, location string, client *http.Client) (*credentials.Credentials, error) {
if accessKey == "" || secretKey == "" {
return nil, errors.New("credentials endpoint, access and secret key are mandatory for AssumeRoleSTS")
}
opts := credentials.STSAssumeRoleOptions{
AccessKey: accessKey,
SecretKey: secretKey,
Location: location,
DurationSeconds: int(xjwt.GetConsoleSTSDuration().Seconds()),
}
stsAssumeRole := &credentials.STSAssumeRole{
Client: client,
STSEndpoint: minioURL,
Options: opts,
}
consoleSTSWrapper := consoleSTSAssumeRole{stsAssumeRole: stsAssumeRole}
return credentials.New(consoleSTSWrapper), nil
}
func NewConsoleCredentials(accessKey, secretKey, location string, client *http.Client) (*credentials.Credentials, error) {
minioURL := getMinIOServer()
// LDAP authentication for Console
if ldap.GetLDAPEnabled() {
creds, err := auth.GetCredentialsFromLDAP(client, minioURL, accessKey, secretKey)
if err != nil {
return nil, err
}
credContext := &credentials.CredContext{
Client: client,
}
// We verify if LDAP credentials are correct and no error is returned
_, err = creds.GetWithContext(credContext)
if err != nil && strings.Contains(strings.ToLower(err.Error()), "not found") {
// We try to use STS Credentials in case LDAP credentials are incorrect.
stsCreds, errSTS := stsCredentials(minioURL, accessKey, secretKey, location, client)
// If there is an error with STS too, then we return the original LDAP error
if errSTS != nil {
LogError("error in STS credentials for LDAP case: %v ", errSTS)
// We return LDAP result
return creds, nil
}
_, err := stsCreds.GetWithContext(credContext)
// There is an error with STS credentials, We return the result of LDAP as STS is not a priority in this case.
if err != nil {
return creds, nil
}
return stsCreds, nil
}
return creds, nil
}
return stsCredentials(minioURL, accessKey, secretKey, location, client)
}
// getConsoleCredentialsFromSession returns the *consoleCredentials.Login associated to the
// provided session token, this is useful for running the Expire() or IsExpired() operations
func getConsoleCredentialsFromSession(claims *models.Principal) *credentials.Credentials {
if claims == nil {
return credentials.NewStaticV4("", "", "")
}
return credentials.NewStaticV4(claims.STSAccessKeyID, claims.STSSecretAccessKey, claims.STSSessionToken)
}
// newMinioClient creates a new MinIO client based on the ConsoleCredentials extracted
// from the provided session token
func newMinioClient(claims *models.Principal, clientIP string) (*minio.Client, error) {
creds := getConsoleCredentialsFromSession(claims)
endpoint := getMinIOEndpoint()
secure := getMinIOEndpointIsSecure()
minioClient, err := minio.New(endpoint, &minio.Options{
Creds: creds,
Secure: secure,
Transport: GetConsoleHTTPClient(clientIP).Transport,
})
if err != nil {
return nil, err
}
// set user-agent to differentiate Console UI requests for auditing.
minioClient.SetAppInfo("MinIO Console", pkg.Version)
return minioClient, nil
}
// computeObjectURLWithoutEncode returns a MinIO url containing the object filename without encoding
func computeObjectURLWithoutEncode(bucketName, prefix string) (string, error) {
u, err := xnet.ParseHTTPURL(getMinIOServer())
if err != nil {
return "", fmt.Errorf("the provided endpoint: '%s' is invalid", getMinIOServer())
}
var p string
if strings.TrimSpace(bucketName) != "" {
p = path.Join(p, bucketName)
}
if strings.TrimSpace(prefix) != "" {
p = pathJoinFinalSlash(p, prefix)
}
return u.String() + "/" + p, nil
}
// newS3BucketClient creates a new mc S3Client to talk to the server based on a bucket
func newS3BucketClient(claims *models.Principal, bucketName string, prefix string, clientIP string) (*mc.S3Client, error) {
if claims == nil {
return nil, fmt.Errorf("the provided credentials are invalid")
}
// It's very important to avoid encoding the prefix since the minio client will encode the path itself
objectURL, err := computeObjectURLWithoutEncode(bucketName, prefix)
if err != nil {
return nil, fmt.Errorf("the provided endpoint is invalid")
}
s3Config := newS3Config(objectURL, claims.STSAccessKeyID, claims.STSSecretAccessKey, claims.STSSessionToken, clientIP)
client, pErr := mc.S3New(s3Config)
if pErr != nil {
return nil, pErr.Cause
}
s3Client, ok := client.(*mc.S3Client)
if !ok {
return nil, fmt.Errorf("the provided url doesn't point to a S3 server")
}
return s3Client, nil
}
// pathJoinFinalSlash - like path.Join() but retains trailing slashSeparator of the last element
func pathJoinFinalSlash(elem ...string) string {
if len(elem) > 0 {
if strings.HasSuffix(elem[len(elem)-1], SlashSeparator) {
return path.Join(elem...) + SlashSeparator
}
}
return path.Join(elem...)
}
// Deprecated
// newS3Config simply creates a new Config struct using the passed
// parameters.
func newS3Config(endpoint, accessKey, secretKey, sessionToken string, clientIP string) *mc.Config {
// We have a valid alias and hostConfig. We populate the/
// consoleCredentials from the match found in the config file.
return &mc.Config{
HostURL: endpoint,
AccessKey: accessKey,
SecretKey: secretKey,
SessionToken: sessionToken,
Signature: "S3v4",
AppName: globalAppName,
AppVersion: pkg.Version,
Insecure: isLocalIPEndpoint(endpoint),
Transport: &ConsoleTransport{
ClientIP: clientIP,
Transport: GlobalTransport,
},
}
}

View File

@@ -1,91 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2024 MinIO, Inc.
//
// 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 api
import "testing"
func Test_computeObjectURLWithoutEncode(t *testing.T) {
type args struct {
bucketName string
prefix string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "http://localhost:9000/bucket-1/小飼弾小飼弾小飼弾.jp",
args: args{
bucketName: "bucket-1",
prefix: "小飼弾小飼弾小飼弾.jpg",
},
want: "http://localhost:9000/bucket-1/小飼弾小飼弾小飼弾.jpg",
wantErr: false,
},
{
name: "http://localhost:9000/bucket-1/a a - a a & a a - a a a.jpg",
args: args{
bucketName: "bucket-1",
prefix: "a a - a a & a a - a a a.jpg",
},
want: "http://localhost:9000/bucket-1/a a - a a & a a - a a a.jpg",
wantErr: false,
},
{
name: "http://localhost:9000/bucket-1/02%20-%20FLY%20ME%20TO%20THE%20MOON%20.jpg",
args: args{
bucketName: "bucket-1",
prefix: "02%20-%20FLY%20ME%20TO%20THE%20MOON%20.jpg",
},
want: "http://localhost:9000/bucket-1/02%20-%20FLY%20ME%20TO%20THE%20MOON%20.jpg",
wantErr: false,
},
{
name: "http://localhost:9000/bucket-1/!@#$%^&*()_+.jpg",
args: args{
bucketName: "bucket-1",
prefix: "!@#$%^&*()_+.jpg",
},
want: "http://localhost:9000/bucket-1/!@#$%^&*()_+.jpg",
wantErr: false,
},
{
name: "http://localhost:9000/bucket-1/test/test2/小飼弾小飼弾小飼弾.jpg",
args: args{
bucketName: "bucket-1",
prefix: "test/test2/小飼弾小飼弾小飼弾.jpg",
},
want: "http://localhost:9000/bucket-1/test/test2/小飼弾小飼弾小飼弾.jpg",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
got, err := computeObjectURLWithoutEncode(tt.args.bucketName, tt.args.prefix)
if (err != nil) != tt.wantErr {
t.Errorf("computeObjectURLWithoutEncode() errors = %v, wantErr %v", err, tt.wantErr)
}
if err == nil {
if got != tt.want {
t.Errorf("computeObjectURLWithoutEncode() got = %v, want %v", got, tt.want)
}
}
})
}
}

View File

@@ -1,297 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"crypto/tls"
"crypto/x509"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/minio/console/pkg/auth/idp/oauth2"
xcerts "github.com/minio/pkg/v3/certs"
"github.com/minio/pkg/v3/env"
xnet "github.com/minio/pkg/v3/net"
)
var (
// Port console default port
Port = "9090"
// Hostname console hostname
// avoid listening on 0.0.0.0 by default
// instead listen on all IPv4 and IPv6
// - Hostname should be empty.
Hostname = ""
// TLSPort console tls port
TLSPort = "9443"
// TLSRedirect console tls redirect rule
TLSRedirect = "on"
ConsoleResourceName = "console-ui"
)
var (
// GlobalRootCAs is CA root certificates, a nil value means system certs pool will be used
GlobalRootCAs *x509.CertPool
// GlobalPublicCerts has certificates Console will use to serve clients
GlobalPublicCerts []*x509.Certificate
// GlobalTLSCertsManager custom TLS Manager for SNI support
GlobalTLSCertsManager *xcerts.Manager
// GlobalTransport is common transport used for all HTTP calls, this is set via
// MinIO server to be the correct transport, however we still define some defaults
// here just in case.
GlobalTransport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 15 * time.Second,
}).DialContext,
MaxIdleConns: 1024,
MaxIdleConnsPerHost: 1024,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 10 * time.Second,
DisableCompression: true, // Set to avoid auto-decompression
TLSClientConfig: &tls.Config{
// Can't use SSLv3 because of POODLE and BEAST
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
// Can't use TLSv1.1 because of RC4 cipher usage
MinVersion: tls.VersionTLS12,
// Console runs in the same pod/node as MinIO this is acceptable.
InsecureSkipVerify: true,
RootCAs: GlobalRootCAs,
},
}
)
// MinIOConfig represents application configuration passed in from the MinIO
// server to the console.
type MinIOConfig struct {
OpenIDProviders oauth2.OpenIDPCfg
}
// GlobalMinIOConfig is the global application configuration passed in from the
// MinIO server.
var GlobalMinIOConfig MinIOConfig
func getMinIOServer() string {
return strings.TrimSpace(env.Get(ConsoleMinIOServer, "http://localhost:9000"))
}
func GetMinIORegion() string {
return strings.TrimSpace(env.Get(ConsoleMinIORegion, ""))
}
func getMinIOEndpoint() string {
u, err := xnet.ParseHTTPURL(getMinIOServer())
if err != nil {
panic(err)
}
return u.Host
}
func getMinIOEndpointIsSecure() bool {
u, err := xnet.ParseHTTPURL(getMinIOServer())
if err != nil {
panic(err)
}
return u.Scheme == "https"
}
// GetHostname gets console hostname set on env variable,
// default one or defined on run command
func GetHostname() string {
return strings.ToLower(env.Get(ConsoleHostname, Hostname))
}
// GetPort gets console por set on env variable
// or default one
func GetPort() int {
port, err := strconv.Atoi(env.Get(ConsolePort, Port))
if err != nil {
port = 9090
}
return port
}
// GetTLSPort gets console tls port set on env variable
// or default one
func GetTLSPort() int {
port, err := strconv.Atoi(env.Get(ConsoleTLSPort, TLSPort))
if err != nil {
port = 9443
}
return port
}
// If GetTLSRedirect is set to true, then only allow HTTPS requests. Default is true.
func GetTLSRedirect() string {
return strings.ToLower(env.Get(ConsoleSecureTLSRedirect, TLSRedirect))
}
// Get secure middleware env variable configurations
func GetSecureAllowedHosts() []string {
allowedHosts := env.Get(ConsoleSecureAllowedHosts, "")
if allowedHosts != "" {
return strings.Split(allowedHosts, ",")
}
return []string{}
}
// AllowedHostsAreRegex determines, if the provided AllowedHosts slice contains valid regular expressions. Default is false.
func GetSecureAllowedHostsAreRegex() bool {
return strings.ToLower(env.Get(ConsoleSecureAllowedHostsAreRegex, "off")) == "on"
}
// If FrameDeny is set to true, adds the X-Frame-Options header with the value of `DENY`. Default is true.
func GetSecureFrameDeny() bool {
return strings.ToLower(env.Get(ConsoleSecureFrameDeny, "on")) == "on"
}
// If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value `nosniff`. Default is true.
func GetSecureContentTypeNonSniff() bool {
return strings.ToLower(env.Get(ConsoleSecureContentTypeNoSniff, "on")) == "on"
}
// If BrowserXssFilter is true, adds the X-XSS-Protection header with the value `1; mode=block`. Default is true.
func GetSecureBrowserXSSFilter() bool {
return strings.ToLower(env.Get(ConsoleSecureBrowserXSSFilter, "on")) == "on"
}
// ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "".
// Passing a template string will replace `$NONCE` with a dynamic nonce value of 16 bytes for each request which can be
// later retrieved using the Nonce function.
func GetSecureContentSecurityPolicy() string {
return env.Get(ConsoleSecureContentSecurityPolicy, "")
}
// ContentSecurityPolicyReportOnly allows the Content-Security-Policy-Report-Only header value to be set with a custom value. Default is "".
func GetSecureContentSecurityPolicyReportOnly() string {
return env.Get(ConsoleSecureContentSecurityPolicyReportOnly, "")
}
// HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request.
func GetSecureHostsProxyHeaders() []string {
allowedHosts := env.Get(ConsoleSecureHostsProxyHeaders, "")
if allowedHosts != "" {
return strings.Split(allowedHosts, ",")
}
return []string{}
}
// TLSHost is the host name that is used to redirect HTTP requests to HTTPS. Default is "", which indicates to use the same host.
func GetSecureTLSHost() string {
tlsHost := env.Get(ConsoleSecureTLSHost, "")
if tlsHost == "" && Hostname != "" {
return net.JoinHostPort(Hostname, TLSPort)
}
return ""
}
// STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header.
func GetSecureSTSSeconds() int64 {
seconds, err := strconv.Atoi(env.Get(ConsoleSecureSTSSeconds, "0"))
if err != nil {
seconds = 0
}
return int64(seconds)
}
// If STSIncludeSubdomains is set to true, the `includeSubdomains` will be appended to the Strict-Transport-Security header. Default is false.
func GetSecureSTSIncludeSubdomains() bool {
return strings.ToLower(env.Get(ConsoleSecureSTSIncludeSubdomains, "off")) == "on"
}
// If STSPreload is set to true, the `preload` flag will be appended to the Strict-Transport-Security header. Default is false.
func GetSecureSTSPreload() bool {
return strings.ToLower(env.Get(ConsoleSecureSTSPreload, "off")) == "on"
}
// If TLSTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
func GetSecureTLSTemporaryRedirect() bool {
return strings.ToLower(env.Get(ConsoleSecureTLSTemporaryRedirect, "off")) == "on"
}
// STS header is only included when the connection is HTTPS.
func GetSecureForceSTSHeader() bool {
return strings.ToLower(env.Get(ConsoleSecureForceSTSHeader, "off")) == "on"
}
// ReferrerPolicy allows the Referrer-Policy header with the value to be set with a custom value. Default is "".
func GetSecureReferrerPolicy() string {
return env.Get(ConsoleSecureReferrerPolicy, "")
}
// FeaturePolicy allows the Feature-Policy header with the value to be set with a custom value. Default is "".
func GetSecureFeaturePolicy() string {
return env.Get(ConsoleSecureFeaturePolicy, "")
}
func getLogSearchAPIToken() string {
if v := env.Get(ConsoleLogQueryAuthToken, ""); v != "" {
return v
}
return env.Get(LogSearchQueryAuthToken, "")
}
func getLogSearchURL() string {
return env.Get(ConsoleLogQueryURL, "")
}
func getPrometheusURL() string {
return env.Get(PrometheusURL, "")
}
func getPrometheusJobID() string {
return env.Get(PrometheusJobID, "minio-job")
}
func getMaxConcurrentUploadsLimit() int64 {
cu, err := strconv.ParseInt(env.Get(ConsoleMaxConcurrentUploads, "10"), 10, 64)
if err != nil {
return 10
}
return cu
}
func getMaxConcurrentDownloadsLimit() int64 {
cu, err := strconv.ParseInt(env.Get(ConsoleMaxConcurrentDownloads, "20"), 10, 64)
if err != nil {
return 20
}
return cu
}
func getConsoleDevMode() bool {
return strings.ToLower(env.Get(ConsoleDevMode, "off")) == "on"
}
func getConsoleAnimatedLogin() bool {
return strings.ToLower(env.Get(ConsoleAnimatedLogin, "on")) == "on"
}
func getConsoleBrowserRedirectURL() string {
return env.Get(ConsoleBrowserRedirectURL, "")
}

View File

@@ -1,393 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 api
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetHostname(t *testing.T) {
os.Setenv(ConsoleHostname, "x")
defer os.Unsetenv(ConsoleHostname)
assert.Equalf(t, "x", GetHostname(), "GetHostname()")
}
func TestGetPort(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want int
}{
{
name: "valid port",
args: args{
env: "9091",
},
want: 9091,
},
{
name: "invalid port",
args: args{
env: "duck",
},
want: 9090,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsolePort, tt.args.env)
assert.Equalf(t, tt.want, GetPort(), "GetPort()")
os.Unsetenv(ConsolePort)
})
}
}
func TestGetTLSPort(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want int
}{
{
name: "valid port",
args: args{
env: "9444",
},
want: 9444,
},
{
name: "invalid port",
args: args{
env: "duck",
},
want: 9443,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleTLSPort, tt.args.env)
assert.Equalf(t, tt.want, GetTLSPort(), "GetTLSPort()")
os.Unsetenv(ConsoleTLSPort)
})
}
}
func TestGetSecureAllowedHosts(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want []string
}{
{
name: "valid hosts",
args: args{
env: "host1,host2",
},
want: []string{"host1", "host2"},
},
{
name: "empty hosts",
args: args{
env: "",
},
want: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleSecureAllowedHosts, tt.args.env)
assert.Equalf(t, tt.want, GetSecureAllowedHosts(), "GetSecureAllowedHosts()")
os.Unsetenv(ConsoleSecureAllowedHosts)
})
}
}
func TestGetSecureHostsProxyHeaders(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want []string
}{
{
name: "valid headers",
args: args{
env: "header1,header2",
},
want: []string{"header1", "header2"},
},
{
name: "empty headers",
args: args{
env: "",
},
want: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleSecureHostsProxyHeaders, tt.args.env)
assert.Equalf(t, tt.want, GetSecureHostsProxyHeaders(), "GetSecureHostsProxyHeaders()")
os.Unsetenv(ConsoleSecureHostsProxyHeaders)
})
}
}
func TestGetSecureSTSSeconds(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want int64
}{
{
name: "valid",
args: args{
env: "1",
},
want: 1,
},
{
name: "invalid",
args: args{
env: "duck",
},
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleSecureSTSSeconds, tt.args.env)
assert.Equalf(t, tt.want, GetSecureSTSSeconds(), "GetSecureSTSSeconds()")
os.Unsetenv(ConsoleSecureSTSSeconds)
})
}
}
func Test_getLogSearchAPIToken(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want string
}{
{
name: "env set",
args: args{
env: "value",
},
want: "value",
},
{
name: "env not set",
args: args{
env: "",
},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleLogQueryAuthToken, tt.args.env)
assert.Equalf(t, tt.want, getLogSearchAPIToken(), "getLogSearchAPIToken()")
os.Setenv(ConsoleLogQueryAuthToken, tt.args.env)
})
}
}
func Test_getPrometheusURL(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want string
}{
{
name: "env set",
args: args{
env: "value",
},
want: "value",
},
{
name: "env not set",
args: args{
env: "",
},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(PrometheusURL, tt.args.env)
assert.Equalf(t, tt.want, getPrometheusURL(), "getPrometheusURL()")
os.Setenv(PrometheusURL, tt.args.env)
})
}
}
func Test_getPrometheusJobID(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want string
}{
{
name: "env set",
args: args{
env: "value",
},
want: "value",
},
{
name: "env not set",
args: args{
env: "",
},
want: "minio-job",
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(PrometheusJobID, tt.args.env)
assert.Equalf(t, tt.want, getPrometheusJobID(), "getPrometheusJobID()")
os.Setenv(PrometheusJobID, tt.args.env)
})
}
}
func Test_getMaxConcurrentUploadsLimit(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want int64
}{
{
name: "valid",
args: args{
env: "1",
},
want: 1,
},
{
name: "invalid",
args: args{
env: "duck",
},
want: 10,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleMaxConcurrentUploads, tt.args.env)
assert.Equalf(t, tt.want, getMaxConcurrentUploadsLimit(), "getMaxConcurrentUploadsLimit()")
os.Unsetenv(ConsoleMaxConcurrentUploads)
})
}
}
func Test_getMaxConcurrentDownloadsLimit(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want int64
}{
{
name: "valid",
args: args{
env: "1",
},
want: 1,
},
{
name: "invalid",
args: args{
env: "duck",
},
want: 20,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleMaxConcurrentDownloads, tt.args.env)
assert.Equalf(t, tt.want, getMaxConcurrentDownloadsLimit(), "getMaxConcurrentDownloadsLimit()")
os.Unsetenv(ConsoleMaxConcurrentDownloads)
})
}
}
func Test_getConsoleDevMode(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "value set",
args: args{
env: "on",
},
want: true,
},
{
name: "value not set",
args: args{
env: "",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleDevMode, tt.args.env)
assert.Equalf(t, tt.want, getConsoleDevMode(), "getConsoleDevMode()")
os.Unsetenv(ConsoleDevMode)
})
}
}

View File

@@ -1,564 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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/>.
// This file is safe to edit. Once it exists it will not be overwritten
package api
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
"io/fs"
"log"
"net"
"net/http"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/google/uuid"
"github.com/minio/console/pkg/logger"
"github.com/minio/console/pkg/utils"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/klauspost/compress/gzhttp"
portal_ui "github.com/minio/console/web-app"
"github.com/minio/pkg/v3/env"
"github.com/minio/pkg/v3/mimedb"
xnet "github.com/minio/pkg/v3/net"
"github.com/go-openapi/errors"
"github.com/go-openapi/swag"
"github.com/minio/console/api/operations"
"github.com/minio/console/models"
"github.com/minio/console/pkg/auth"
"github.com/unrolled/secure"
)
//go:generate swagger generate server --target ../../console --name Console --spec ../swagger.yml
var additionalServerFlags = struct {
CertsDir string `long:"certs-dir" description:"path to certs directory" env:"CONSOLE_CERTS_DIR"`
}{}
const (
SubPath = "CONSOLE_SUBPATH"
)
var (
cfgSubPath = "/"
subPathOnce sync.Once
)
func configureFlags(api *operations.ConsoleAPI) {
api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{
{
ShortDescription: "additional server flags",
Options: &additionalServerFlags,
},
}
}
func configureAPI(api *operations.ConsoleAPI) http.Handler {
// Applies when the "x-token" header is set
api.KeyAuth = func(token string, _ []string) (*models.Principal, error) {
// we are validating the session token by decrypting the claims inside, if the operation succeed that means the jwt
// was generated and signed by us in the first place
if token == "Anonymous" {
return &models.Principal{}, nil
}
claims, err := auth.ParseClaimsFromToken(token)
if err != nil {
api.Logger("Unable to validate the session token %s: %v", token, err)
return nil, errors.New(401, "incorrect api key auth")
}
return &models.Principal{
STSAccessKeyID: claims.STSAccessKeyID,
STSSecretAccessKey: claims.STSSecretAccessKey,
STSSessionToken: claims.STSSessionToken,
AccountAccessKey: claims.AccountAccessKey,
Hm: claims.HideMenu,
Ob: claims.ObjectBrowser,
CustomStyleOb: claims.CustomStyleOB,
}, nil
}
api.AnonymousAuth = func(_ string) (*models.Principal, error) {
return &models.Principal{}, nil
}
// Register login handlers
registerLoginHandlers(api)
// Register logout handlers
registerLogoutHandlers(api)
// Register bucket handlers
registerBucketsHandlers(api)
// Register session handlers
registerSessionHandlers(api)
// Register Object's Handlers
registerObjectsHandlers(api)
// Register Bucket Quota's Handlers
registerBucketQuotaHandlers(api)
// Register Bucket Policy's Handlers
registerPublicObjectsHandlers(api)
api.PreServerShutdown = func() {}
api.ServerShutdown = func() {}
return setupGlobalMiddleware(api.Serve(setupMiddlewares))
}
// The TLS configuration before HTTPS server starts.
func configureTLS(tlsConfig *tls.Config) {
tlsConfig.RootCAs = GlobalRootCAs
tlsConfig.GetCertificate = GlobalTLSCertsManager.GetCertificate
}
// The middleware configuration is for the handler executors. These do not apply to the swagger.json document.
// The middleware executes after routing but before authentication, binding and validation
func setupMiddlewares(handler http.Handler) http.Handler {
return handler
}
func ContextMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := uuid.NewString()
ctx := context.WithValue(r.Context(), utils.ContextRequestID, requestID)
ctx = context.WithValue(ctx, utils.ContextRequestUserAgent, r.UserAgent())
ctx = context.WithValue(ctx, utils.ContextRequestHost, r.Host)
ctx = context.WithValue(ctx, utils.ContextRequestRemoteAddr, r.RemoteAddr)
ctx = context.WithValue(ctx, utils.ContextClientIP, getClientIP(r))
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func AuditLogMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rw := logger.NewResponseWriter(w)
next.ServeHTTP(rw, r)
if strings.HasPrefix(r.URL.Path, "/ws") || strings.HasPrefix(r.URL.Path, "/api") {
logger.AuditLog(r.Context(), rw, r, map[string]interface{}{}, "Authorization", "Cookie", "Set-Cookie")
}
})
}
func DebugLogMiddleware(next http.Handler) http.Handler {
debugLogLevel, _ := env.GetInt("CONSOLE_DEBUG_LOGLEVEL", 0)
if debugLogLevel == 0 {
return next
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rw := logger.NewResponseWriter(w)
next.ServeHTTP(rw, r)
debugLog(debugLogLevel, r, rw)
})
}
func debugLog(debugLogLevel int, r *http.Request, rw *logger.ResponseWriter) {
switch debugLogLevel {
case 1:
// Log server errors only (summary)
if rw.StatusCode >= 500 {
debugLogSummary(r, rw)
}
case 2:
// Log server and client errors (summary)
if rw.StatusCode >= 400 {
debugLogSummary(r, rw)
}
case 3:
// Log all requests (summary)
debugLogSummary(r, rw)
case 4:
// Log server errors only (including headers)
if rw.StatusCode >= 500 {
debugLogDetails(r, rw)
}
case 5:
// Log server and client errors (including headers)
if rw.StatusCode >= 400 {
debugLogDetails(r, rw)
}
case 6:
// Log all requests (including headers)
debugLogDetails(r, rw)
}
}
func debugLogSummary(r *http.Request, rw *logger.ResponseWriter) {
statusCode := strconv.Itoa(rw.StatusCode)
if rw.Hijacked {
statusCode = "hijacked"
}
logger.Info(fmt.Sprintf("%s %s %s %s %dms", r.RemoteAddr, r.Method, r.URL, statusCode, time.Since(rw.StartTime).Milliseconds()))
}
func debugLogDetails(r *http.Request, rw *logger.ResponseWriter) {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("- Method/URL: %s %s\n", r.Method, r.URL))
sb.WriteString(fmt.Sprintf(" Remote endpoint: %s\n", r.RemoteAddr))
if rw.Hijacked {
sb.WriteString(" Status code: <hijacked, probably a websocket>\n")
} else {
sb.WriteString(fmt.Sprintf(" Status code: %d\n", rw.StatusCode))
}
sb.WriteString(fmt.Sprintf(" Duration (ms): %d\n", time.Since(rw.StartTime).Milliseconds()))
sb.WriteString(" Request headers: ")
debugLogHeaders(&sb, r.Header)
sb.WriteString(" Response headers: ")
debugLogHeaders(&sb, rw.Header())
logger.Info(sb.String())
}
func debugLogHeaders(sb *strings.Builder, h http.Header) {
keys := make([]string, 0, len(h))
for key := range h {
keys = append(keys, key)
}
sort.Strings(keys)
first := true
for _, key := range keys {
values := h[key]
for _, value := range values {
if !first {
sb.WriteString(" ")
} else {
first = false
}
sb.WriteString(fmt.Sprintf("%s: %s\n", key, value))
}
}
if first {
sb.WriteRune('\n')
}
}
// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
// So this is a good place to plug in a panic handling middleware, logger and metrics
func setupGlobalMiddleware(handler http.Handler) http.Handler {
gnext := gzhttp.GzipHandler(handler)
// if audit-log is enabled console will log all incoming request
next := AuditLogMiddleware(gnext)
// serve static files
next = FileServerMiddleware(next)
// add information to request context
next = ContextMiddleware(next)
// handle cookie or authorization header for session
next = AuthenticationMiddleware(next)
// handle debug logging
next = DebugLogMiddleware(next)
sslHostFn := secure.SSLHostFunc(func(host string) string {
xhost, err := xnet.ParseHost(host)
if err != nil {
return host
}
return net.JoinHostPort(xhost.Name, TLSPort)
})
// Secure middleware, this middleware wrap all the previous handlers and add
// HTTP security headers
secureOptions := secure.Options{
AllowedHosts: GetSecureAllowedHosts(),
AllowedHostsAreRegex: GetSecureAllowedHostsAreRegex(),
HostsProxyHeaders: GetSecureHostsProxyHeaders(),
SSLRedirect: GetTLSRedirect() == "on" && len(GlobalPublicCerts) > 0,
SSLHostFunc: &sslHostFn,
SSLHost: GetSecureTLSHost(),
STSSeconds: GetSecureSTSSeconds(),
STSIncludeSubdomains: GetSecureSTSIncludeSubdomains(),
STSPreload: GetSecureSTSPreload(),
SSLTemporaryRedirect: false,
ForceSTSHeader: GetSecureForceSTSHeader(),
FrameDeny: GetSecureFrameDeny(),
ContentTypeNosniff: GetSecureContentTypeNonSniff(),
BrowserXssFilter: GetSecureBrowserXSSFilter(),
ContentSecurityPolicy: GetSecureContentSecurityPolicy(),
ContentSecurityPolicyReportOnly: GetSecureContentSecurityPolicyReportOnly(),
ReferrerPolicy: GetSecureReferrerPolicy(),
FeaturePolicy: GetSecureFeaturePolicy(),
IsDevelopment: false,
}
secureMiddleware := secure.New(secureOptions)
next = secureMiddleware.Handler(next)
return RejectS3Middleware(next)
}
const apiRequestErr = `<?xml version="1.0" encoding="UTF-8"?><Error><Code>InvalidArgument</Code><Message>S3 API Requests must be made to API port.</Message><RequestId>0</RequestId></Error>`
// RejectS3Middleware will reject requests that have AWS S3 specific headers.
func RejectS3Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if len(r.Header.Get("X-Amz-Content-Sha256")) > 0 ||
len(r.Header.Get("X-Amz-Date")) > 0 ||
strings.HasPrefix(r.Header.Get("Authorization"), "AWS4-HMAC-SHA256") ||
r.URL.Query().Get("AWSAccessKeyId") != "" {
w.Header().Set("Location", getMinIOServer())
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(apiRequestErr))
return
}
next.ServeHTTP(w, r)
})
}
func AuthenticationMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token, err := auth.GetTokenFromRequest(r)
if err != nil && err != auth.ErrNoAuthToken {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
sessionToken, _ := auth.DecryptToken(token)
// All handlers handle appropriately to return errors
// based on their swagger rules, we do not need to
// additionally return error here, let the next ServeHTTPs
// handle it appropriately.
if len(sessionToken) > 0 {
r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", string(sessionToken)))
} else {
r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", "Anonymous"))
}
ctx := r.Context()
claims, _ := auth.ParseClaimsFromToken(string(sessionToken))
if claims != nil {
// save user session id context
ctx = context.WithValue(r.Context(), utils.ContextRequestUserID, claims.STSSessionToken)
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// FileServerMiddleware serves files from the static folder
func FileServerMiddleware(next http.Handler) http.Handler {
buildFs, err := fs.Sub(portal_ui.GetStaticAssets(), "build")
if err != nil {
panic(err)
}
spaFileHandler := wrapHandlerSinglePageApplication(requestBounce(http.FileServer(http.FS(buildFs))))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", globalAppName) // do not add version information
switch {
case strings.HasPrefix(r.URL.Path, "/ws"):
serveWS(w, r)
case strings.HasPrefix(r.URL.Path, "/api"):
next.ServeHTTP(w, r)
default:
spaFileHandler.ServeHTTP(w, r)
}
})
}
type notFoundRedirectRespWr struct {
http.ResponseWriter // We embed http.ResponseWriter
status int
}
func (w *notFoundRedirectRespWr) WriteHeader(status int) {
w.status = status // Store the status for our own use
if status != http.StatusNotFound {
w.ResponseWriter.WriteHeader(status)
}
}
func (w *notFoundRedirectRespWr) Write(p []byte) (int, error) {
if w.status != http.StatusNotFound {
return w.ResponseWriter.Write(p)
}
return len(p), nil // Lie that we successfully wrote it
}
// handleSPA handles the serving of the React Single Page Application
func handleSPA(w http.ResponseWriter, r *http.Request) {
basePath := "/"
// For SPA mode we will replace root base with a sub path if configured unless we received cp=y and cpb=/NEW/BASE
if v := r.URL.Query().Get("cp"); v == "y" {
if base := r.URL.Query().Get("cpb"); base != "" {
// make sure the subpath has a trailing slash
if !strings.HasSuffix(base, "/") {
base = fmt.Sprintf("%s/", base)
}
basePath = base
}
}
indexPage, err := portal_ui.GetStaticAssets().Open("build/index.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sts := r.URL.Query().Get("sts")
stsAccessKey := r.URL.Query().Get("sts_a")
stsSecretKey := r.URL.Query().Get("sts_s")
overridenStyles := r.URL.Query().Get("ov_st")
// if these three parameters are present we are being asked to issue a session with these values
if sts != "" && stsAccessKey != "" && stsSecretKey != "" {
creds := credentials.NewStaticV4(stsAccessKey, stsSecretKey, sts)
consoleCreds := &ConsoleCredentials{
ConsoleCredentials: creds,
AccountAccessKey: stsAccessKey,
}
sf := &auth.SessionFeatures{}
sf.HideMenu = true
sf.ObjectBrowser = true
if overridenStyles != "" {
err := ValidateEncodedStyles(overridenStyles)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sf.CustomStyleOB = overridenStyles
}
sessionID, err := login(consoleCreds, sf)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
cookie := NewSessionCookieForConsole(*sessionID)
http.SetCookie(w, &cookie)
// Allow us to be iframed
w.Header().Del("X-Frame-Options")
}
indexPageBytes, err := io.ReadAll(indexPage)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// if we have a seeded basePath. This should override CONSOLE_SUBPATH every time, thus the `if else`
if basePath != "/" {
indexPageBytes = replaceBaseInIndex(indexPageBytes, basePath)
// if we have a custom subpath replace it in
} else if getSubPath() != "/" {
indexPageBytes = replaceBaseInIndex(indexPageBytes, getSubPath())
}
indexPageBytes = replaceLicense(indexPageBytes)
// it's important to force "Content-Type: text/html", because a previous
// handler may have already set the content-type to a different value.
// (i.e. the FileServer when it detected that it couldn't find the file)
w.Header().Set("Content-Type", "text/html")
http.ServeContent(w, r, "index.html", time.Now(), bytes.NewReader(indexPageBytes))
}
// wrapHandlerSinglePageApplication handles a http.FileServer returning a 404 and overrides it with index.html
func wrapHandlerSinglePageApplication(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
handleSPA(w, r)
return
}
w.Header().Set("Content-Type", mimedb.TypeByExtension(filepath.Ext(r.URL.Path)))
nfw := &notFoundRedirectRespWr{ResponseWriter: w}
h.ServeHTTP(nfw, r)
if nfw.status == http.StatusNotFound {
handleSPA(w, r)
}
}
}
type nullWriter struct{}
func (lw nullWriter) Write(b []byte) (int, error) {
return len(b), nil
}
// As soon as server is initialized but not run yet, this function will be called.
// If you need to modify a config, store server instance to stop it individually later, this is the place.
// This function can be called multiple times, depending on the number of serving schemes.
// scheme value will be set accordingly: "http", "https" or "unix"
func configureServer(s *http.Server, _, _ string) {
// Turn-off random logger by Go net/http
s.ErrorLog = log.New(&nullWriter{}, "", 0)
}
func getSubPath() string {
subPathOnce.Do(func() {
cfgSubPath = parseSubPath(env.Get(SubPath, ""))
})
return cfgSubPath
}
func parseSubPath(v string) string {
v = strings.TrimSpace(v)
if v == "" {
return SlashSeparator
}
// Replace all unnecessary `\` to `/`
// also add pro-actively at the end.
subPath := path.Clean(filepath.ToSlash(v))
if !strings.HasPrefix(subPath, SlashSeparator) {
subPath = SlashSeparator + subPath
}
if !strings.HasSuffix(subPath, SlashSeparator) {
subPath += SlashSeparator
}
return subPath
}
func replaceBaseInIndex(indexPageBytes []byte, basePath string) []byte {
if basePath != "" {
validBasePath := regexp.MustCompile(`^[0-9a-zA-Z\/-]+$`)
if !validBasePath.MatchString(basePath) {
return indexPageBytes
}
indexPageStr := string(indexPageBytes)
newBase := fmt.Sprintf("<base href=\"%s\"/>", basePath)
indexPageStr = strings.Replace(indexPageStr, "<base href=\"/\"/>", newBase, 1)
indexPageBytes = []byte(indexPageStr)
}
return indexPageBytes
}
func replaceLicense(indexPageBytes []byte) []byte {
indexPageStr := string(indexPageBytes)
indexPageBytes = []byte(indexPageStr)
return indexPageBytes
}
func requestBounce(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "/") {
http.NotFound(w, r)
return
}
handler.ServeHTTP(w, r)
})
}

View File

@@ -1,125 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// 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 api
import (
"os"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_parseSubPath(t *testing.T) {
type args struct {
v string
}
tests := []struct {
name string
args args
want string
}{
{
name: "Empty",
args: args{
v: "",
},
want: "/",
},
{
name: "Slash",
args: args{
v: "/",
},
want: "/",
},
{
name: "Double Slash",
args: args{
v: "//",
},
want: "/",
},
{
name: "No slashes",
args: args{
v: "route",
},
want: "/route/",
},
{
name: "No trailing slashes",
args: args{
v: "/route",
},
want: "/route/",
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
assert.Equalf(t, tt.want, parseSubPath(tt.args.v), "parseSubPath(%v)", tt.args.v)
})
}
}
func Test_getSubPath(t *testing.T) {
type args struct {
envValue string
}
tests := []struct {
name string
args args
want string
}{
{
name: "Empty",
args: args{
envValue: "",
},
want: "/",
},
{
name: "Slash",
args: args{
envValue: "/",
},
want: "/",
},
{
name: "Valid Value",
args: args{
envValue: "/subpath/",
},
want: "/subpath/",
},
{
name: "No starting slash",
args: args{
envValue: "subpath/",
},
want: "/subpath/",
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
t.Setenv(SubPath, tt.args.envValue)
defer os.Unsetenv(SubPath)
subPathOnce = sync.Once{}
assert.Equalf(t, tt.want, getSubPath(), "getSubPath()")
})
}
}

View File

@@ -1,63 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
// list of all console environment constants
const (
// Constants for common configuration
ConsoleMinIOServer = "CONSOLE_MINIO_SERVER"
ConsoleSubnetProxy = "CONSOLE_SUBNET_PROXY"
ConsoleMinIORegion = "CONSOLE_MINIO_REGION"
ConsoleHostname = "CONSOLE_HOSTNAME"
ConsolePort = "CONSOLE_PORT"
ConsoleTLSPort = "CONSOLE_TLS_PORT"
// Constants for Secure middleware
ConsoleSecureAllowedHosts = "CONSOLE_SECURE_ALLOWED_HOSTS"
ConsoleSecureAllowedHostsAreRegex = "CONSOLE_SECURE_ALLOWED_HOSTS_ARE_REGEX"
ConsoleSecureFrameDeny = "CONSOLE_SECURE_FRAME_DENY"
ConsoleSecureContentTypeNoSniff = "CONSOLE_SECURE_CONTENT_TYPE_NO_SNIFF"
ConsoleSecureBrowserXSSFilter = "CONSOLE_SECURE_BROWSER_XSS_FILTER"
ConsoleSecureContentSecurityPolicy = "CONSOLE_SECURE_CONTENT_SECURITY_POLICY"
ConsoleSecureContentSecurityPolicyReportOnly = "CONSOLE_SECURE_CONTENT_SECURITY_POLICY_REPORT_ONLY"
ConsoleSecureHostsProxyHeaders = "CONSOLE_SECURE_HOSTS_PROXY_HEADERS"
ConsoleSecureSTSSeconds = "CONSOLE_SECURE_STS_SECONDS"
ConsoleSecureSTSIncludeSubdomains = "CONSOLE_SECURE_STS_INCLUDE_SUB_DOMAINS"
ConsoleSecureSTSPreload = "CONSOLE_SECURE_STS_PRELOAD"
ConsoleSecureTLSRedirect = "CONSOLE_SECURE_TLS_REDIRECT"
ConsoleSecureTLSHost = "CONSOLE_SECURE_TLS_HOST"
ConsoleSecureTLSTemporaryRedirect = "CONSOLE_SECURE_TLS_TEMPORARY_REDIRECT"
ConsoleSecureForceSTSHeader = "CONSOLE_SECURE_FORCE_STS_HEADER"
ConsoleSecurePublicKey = "CONSOLE_SECURE_PUBLIC_KEY"
ConsoleSecureReferrerPolicy = "CONSOLE_SECURE_REFERRER_POLICY"
ConsoleSecureFeaturePolicy = "CONSOLE_SECURE_FEATURE_POLICY"
ConsoleSecureExpectCTHeader = "CONSOLE_SECURE_EXPECT_CT_HEADER"
PrometheusURL = "CONSOLE_PROMETHEUS_URL"
PrometheusAuthToken = "CONSOLE_PROMETHEUS_AUTH_TOKEN"
PrometheusJobID = "CONSOLE_PROMETHEUS_JOB_ID"
PrometheusExtraLabels = "CONSOLE_PROMETHEUS_EXTRA_LABELS"
ConsoleLogQueryURL = "CONSOLE_LOG_QUERY_URL"
ConsoleLogQueryAuthToken = "CONSOLE_LOG_QUERY_AUTH_TOKEN"
ConsoleMaxConcurrentUploads = "CONSOLE_MAX_CONCURRENT_UPLOADS"
ConsoleMaxConcurrentDownloads = "CONSOLE_MAX_CONCURRENT_DOWNLOADS"
ConsoleDevMode = "CONSOLE_DEV_MODE"
ConsoleAnimatedLogin = "CONSOLE_ANIMATED_LOGIN"
ConsoleBrowserRedirectURL = "CONSOLE_BROWSER_REDIRECT_URL"
LogSearchQueryAuthToken = "LOGSEARCH_QUERY_AUTH_TOKEN"
SlashSeparator = "/"
LocalAddress = "127.0.0.1"
)

View File

@@ -1,556 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 api
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/go-openapi/runtime/flagext"
"github.com/go-openapi/swag"
flags "github.com/jessevdk/go-flags"
"golang.org/x/net/netutil"
"github.com/minio/console/api/operations"
)
const (
schemeHTTP = "http"
schemeHTTPS = "https"
schemeUnix = "unix"
)
var defaultSchemes []string
func init() {
defaultSchemes = []string{
schemeHTTP,
}
}
// NewServer creates a new api console server but does not configure it
func NewServer(api *operations.ConsoleAPI) *Server {
s := new(Server)
s.shutdown = make(chan struct{})
s.api = api
s.interrupt = make(chan os.Signal, 1)
return s
}
// ConfigureAPI configures the API and handlers.
func (s *Server) ConfigureAPI() {
if s.api != nil {
s.handler = configureAPI(s.api)
}
}
// ConfigureFlags configures the additional flags defined by the handlers. Needs to be called before the parser.Parse
func (s *Server) ConfigureFlags() {
if s.api != nil {
configureFlags(s.api)
}
}
// Server for the console API
type Server struct {
EnabledListeners []string `long:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec"`
CleanupTimeout time.Duration `long:"cleanup-timeout" description:"grace period for which to wait before killing idle connections" default:"10s"`
GracefulTimeout time.Duration `long:"graceful-timeout" description:"grace period for which to wait before shutting down the server" default:"15s"`
MaxHeaderSize flagext.ByteSize `long:"max-header-size" description:"controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body." default:"1MiB"`
SocketPath flags.Filename `long:"socket-path" description:"the unix socket to listen on" default:"/var/run/console.sock"`
domainSocketL net.Listener
Host string `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"`
Port int `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"`
ListenLimit int `long:"listen-limit" description:"limit the number of outstanding requests"`
KeepAlive time.Duration `long:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"`
ReadTimeout time.Duration `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"`
WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"`
httpServerL []net.Listener
TLSHost string `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
TLSPort int `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
TLSCertificate flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure connections" env:"TLS_PRIVATE_KEY"`
TLSCACertificate flags.Filename `long:"tls-ca" description:"the certificate authority file to be used with mutual tls auth" env:"TLS_CA_CERTIFICATE"`
TLSListenLimit int `long:"tls-listen-limit" description:"limit the number of outstanding requests"`
TLSKeepAlive time.Duration `long:"tls-keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)"`
TLSReadTimeout time.Duration `long:"tls-read-timeout" description:"maximum duration before timing out read of the request"`
TLSWriteTimeout time.Duration `long:"tls-write-timeout" description:"maximum duration before timing out write of the response"`
httpsServerL []net.Listener
api *operations.ConsoleAPI
handler http.Handler
hasListeners bool
shutdown chan struct{}
shuttingDown int32
interrupted bool
interrupt chan os.Signal
}
// Logf logs message either via defined user logger or via system one if no user logger is defined.
func (s *Server) Logf(f string, args ...interface{}) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
} else {
log.Printf(f, args...)
}
}
// Fatalf logs message either via defined user logger or via system one if no user logger is defined.
// Exits with non-zero status after printing
func (s *Server) Fatalf(f string, args ...interface{}) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
os.Exit(1)
}
log.Fatalf(f, args...)
}
// SetAPI configures the server with the specified API. Needs to be called before Serve
func (s *Server) SetAPI(api *operations.ConsoleAPI) {
if api == nil {
s.api = nil
s.handler = nil
return
}
s.api = api
s.handler = configureAPI(api)
}
func (s *Server) hasScheme(scheme string) bool {
schemes := s.EnabledListeners
if len(schemes) == 0 {
schemes = defaultSchemes
}
for _, v := range schemes {
if v == scheme {
return true
}
}
return false
}
// Serve the api
func (s *Server) Serve() (err error) {
if !s.hasListeners {
if err = s.Listen(); err != nil {
return err
}
}
// set default handler, if none is set
if s.handler == nil {
if s.api == nil {
return errors.New("can't create the default handler, as no api is set")
}
s.SetHandler(s.api.Serve(nil))
}
wg := new(sync.WaitGroup)
once := new(sync.Once)
signalNotify(s.interrupt)
go handleInterrupt(once, s)
servers := []*http.Server{}
if s.hasScheme(schemeUnix) {
domainSocket := new(http.Server)
domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize)
domainSocket.Handler = s.handler
if int64(s.CleanupTimeout) > 0 {
domainSocket.IdleTimeout = s.CleanupTimeout
}
configureServer(domainSocket, "unix", string(s.SocketPath))
servers = append(servers, domainSocket)
wg.Add(1)
s.Logf("Serving console at unix://%s", s.SocketPath)
go func(l net.Listener) {
defer wg.Done()
if err := domainSocket.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
}
s.Logf("Stopped serving console at unix://%s", s.SocketPath)
}(s.domainSocketL)
}
if s.hasScheme(schemeHTTP) {
httpServer := new(http.Server)
httpServer.MaxHeaderBytes = int(s.MaxHeaderSize)
httpServer.ReadTimeout = s.ReadTimeout
httpServer.WriteTimeout = s.WriteTimeout
httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0)
if s.ListenLimit > 0 {
for i := range s.httpServerL {
s.httpServerL[i] = netutil.LimitListener(s.httpServerL[i], s.ListenLimit)
}
}
if int64(s.CleanupTimeout) > 0 {
httpServer.IdleTimeout = s.CleanupTimeout
}
httpServer.Handler = s.handler
configureServer(httpServer, "http", s.httpServerL[0].Addr().String())
servers = append(servers, httpServer)
s.Logf("Serving console at http://%s", s.httpServerL[0].Addr())
for i := range s.httpServerL {
wg.Add(1)
go func(l net.Listener) {
defer wg.Done()
if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
}
s.Logf("Stopped serving console at http://%s", l.Addr())
}(s.httpServerL[i])
}
}
if s.hasScheme(schemeHTTPS) {
httpsServer := new(http.Server)
httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize)
httpsServer.ReadTimeout = s.TLSReadTimeout
httpsServer.WriteTimeout = s.TLSWriteTimeout
httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0)
if s.TLSListenLimit > 0 {
for i := range s.httpsServerL {
s.httpsServerL[i] = netutil.LimitListener(s.httpsServerL[i], s.TLSListenLimit)
}
}
if int64(s.CleanupTimeout) > 0 {
httpsServer.IdleTimeout = s.CleanupTimeout
}
httpsServer.Handler = s.handler
// Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
httpsServer.TLSConfig = &tls.Config{
// Causes servers to use Go's default ciphersuite preferences,
// which are tuned to avoid attacks. Does nothing on clients.
PreferServerCipherSuites: true,
// Only use curves which have assembly implementations
// https://github.com/golang/go/tree/master/src/crypto/elliptic
CurvePreferences: []tls.CurveID{tls.CurveP256},
// Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
NextProtos: []string{"h2", "http/1.1"},
// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
MinVersion: tls.VersionTLS12,
// These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
}
// build standard config from server options
if s.TLSCertificate != "" && s.TLSCertificateKey != "" {
httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(string(s.TLSCertificate), string(s.TLSCertificateKey))
if err != nil {
return err
}
}
if s.TLSCACertificate != "" {
// include specified CA certificate
caCert, caCertErr := os.ReadFile(string(s.TLSCACertificate))
if caCertErr != nil {
return caCertErr
}
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(caCert)
if !ok {
return fmt.Errorf("cannot parse CA certificate")
}
httpsServer.TLSConfig.ClientCAs = caCertPool
httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
// call custom TLS configurator
configureTLS(httpsServer.TLSConfig)
if len(httpsServer.TLSConfig.Certificates) == 0 && httpsServer.TLSConfig.GetCertificate == nil {
// after standard and custom config are passed, this ends up with no certificate
if s.TLSCertificate == "" {
if s.TLSCertificateKey == "" {
s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
}
s.Fatalf("the required flag `--tls-certificate` was not specified")
}
if s.TLSCertificateKey == "" {
s.Fatalf("the required flag `--tls-key` was not specified")
}
// this happens with a wrong custom TLS configurator
s.Fatalf("no certificate was configured for TLS")
}
configureServer(httpsServer, "https", s.httpsServerL[0].Addr().String())
servers = append(servers, httpsServer)
s.Logf("Serving console at https://%s", s.httpsServerL[0].Addr())
for i := range s.httpsServerL {
wg.Add(1)
go func(l net.Listener) {
defer wg.Done()
if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
}
s.Logf("Stopped serving console at https://%s", l.Addr())
}(tls.NewListener(s.httpsServerL[i], httpsServer.TLSConfig))
}
}
wg.Add(1)
go s.handleShutdown(wg, &servers)
wg.Wait()
return nil
}
// Listen creates the listeners for the server
func (s *Server) Listen() error {
if s.hasListeners { // already done this
return nil
}
if s.hasScheme(schemeHTTPS) {
// Use http host if https host wasn't defined
if s.TLSHost == "" {
s.TLSHost = s.Host
}
// Use http listen limit if https listen limit wasn't defined
if s.TLSListenLimit == 0 {
s.TLSListenLimit = s.ListenLimit
}
// Use http tcp keep alive if https tcp keep alive wasn't defined
if int64(s.TLSKeepAlive) == 0 {
s.TLSKeepAlive = s.KeepAlive
}
// Use http read timeout if https read timeout wasn't defined
if int64(s.TLSReadTimeout) == 0 {
s.TLSReadTimeout = s.ReadTimeout
}
// Use http write timeout if https write timeout wasn't defined
if int64(s.TLSWriteTimeout) == 0 {
s.TLSWriteTimeout = s.WriteTimeout
}
}
if s.hasScheme(schemeUnix) {
domSockListener, err := net.Listen("unix", string(s.SocketPath))
if err != nil {
return err
}
s.domainSocketL = domSockListener
}
lookup := func(addr string) []net.IP {
ips, err := net.LookupIP(addr)
if err == nil {
return ips
}
return []net.IP{net.ParseIP(addr)}
}
convert := func(ip net.IP) (string, string) {
if ip == nil {
return "", "tcp"
}
proto := "tcp4"
if ip.To4() == nil {
proto = "tcp6"
}
return ip.String(), proto
}
if s.hasScheme(schemeHTTP) {
for _, ip := range lookup(s.Host) {
host, proto := convert(ip)
listener, err := net.Listen(proto, net.JoinHostPort(host, strconv.Itoa(s.Port)))
if err != nil {
return err
}
if s.Host == "" || s.Port == 0 {
h, p, err := swag.SplitHostPort(listener.Addr().String())
if err != nil {
return err
}
s.Host = h
s.Port = p
}
s.httpServerL = append(s.httpServerL, listener)
}
}
if s.hasScheme(schemeHTTPS) {
for _, ip := range lookup(s.TLSHost) {
host, proto := convert(ip)
tlsListener, err := net.Listen(proto, net.JoinHostPort(host, strconv.Itoa(s.TLSPort)))
if err != nil {
return err
}
if s.TLSHost == "" || s.TLSPort == 0 {
sh, sp, err := swag.SplitHostPort(tlsListener.Addr().String())
if err != nil {
return err
}
s.TLSHost = sh
s.TLSPort = sp
}
s.httpsServerL = append(s.httpsServerL, tlsListener)
}
}
s.hasListeners = true
return nil
}
// Shutdown server and clean up resources
func (s *Server) Shutdown() error {
if atomic.CompareAndSwapInt32(&s.shuttingDown, 0, 1) {
close(s.shutdown)
}
return nil
}
func (s *Server) handleShutdown(wg *sync.WaitGroup, serversPtr *[]*http.Server) {
// wg.Done must occur last, after s.api.ServerShutdown()
// (to preserve old behavior)
defer wg.Done()
<-s.shutdown
servers := *serversPtr
ctx, cancel := context.WithTimeout(context.TODO(), s.GracefulTimeout)
defer cancel()
// first execute the pre-shutdown hook
s.api.PreServerShutdown()
shutdownChan := make(chan bool)
for i := range servers {
server := servers[i]
go func() {
var success bool
defer func() {
shutdownChan <- success
}()
if err := server.Shutdown(ctx); err != nil {
// Error from closing listeners, or context timeout:
s.Logf("HTTP server Shutdown: %v", err)
} else {
success = true
}
}()
}
// Wait until all listeners have successfully shut down before calling ServerShutdown
success := true
for range servers {
success = success && <-shutdownChan
}
if success {
s.api.ServerShutdown()
}
}
// GetHandler returns a handler useful for testing
func (s *Server) GetHandler() http.Handler {
return s.handler
}
// SetHandler allows for setting a http handler on this server
func (s *Server) SetHandler(handler http.Handler) {
s.handler = handler
}
// UnixListener returns the domain socket listener
func (s *Server) UnixListener() (net.Listener, error) {
if !s.hasListeners {
if err := s.Listen(); err != nil {
return nil, err
}
}
return s.domainSocketL, nil
}
// HTTPListener returns the http listener
func (s *Server) HTTPListener() ([]net.Listener, error) {
if !s.hasListeners {
if err := s.Listen(); err != nil {
return nil, err
}
}
return s.httpServerL, nil
}
// TLSListener returns the https listener
func (s *Server) TLSListener() ([]net.Listener, error) {
if !s.hasListeners {
if err := s.Listen(); err != nil {
return nil, err
}
}
return s.httpsServerL, nil
}
func handleInterrupt(once *sync.Once, s *Server) {
once.Do(func() {
for range s.interrupt {
if s.interrupted {
s.Logf("Server already shutting down")
continue
}
s.interrupted = true
s.Logf("Shutting down... ")
if err := s.Shutdown(); err != nil {
s.Logf("HTTP server Shutdown: %v", err)
}
}
})
}
func signalNotify(interrupt chan<- os.Signal) {
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,279 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// 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 api
import (
"context"
"errors"
"strings"
"github.com/minio/minio-go/v7"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
)
var (
ErrDefault = errors.New("an error occurred, please try again")
ErrInvalidLogin = errors.New("invalid login")
ErrForbidden = errors.New("403 Forbidden")
ErrBadRequest = errors.New("400 Bad Request")
ErrFileTooLarge = errors.New("413 File too Large")
ErrInvalidSession = errors.New("invalid session")
ErrNotFound = errors.New("not found")
ErrGroupAlreadyExists = errors.New("error group name already in use")
ErrInvalidErasureCodingValue = errors.New("invalid Erasure Coding Value")
ErrBucketBodyNotInRequest = errors.New("error bucket body not in request")
ErrBucketNameNotInRequest = errors.New("error bucket name not in request")
ErrGroupBodyNotInRequest = errors.New("error group body not in request")
ErrGroupNameNotInRequest = errors.New("error group name not in request")
ErrPolicyNameNotInRequest = errors.New("error policy name not in request")
ErrPolicyBodyNotInRequest = errors.New("error policy body not in request")
ErrInvalidEncryptionAlgorithm = errors.New("error invalid encryption algorithm")
ErrSSENotConfigured = errors.New("error server side encryption configuration not found")
ErrBucketLifeCycleNotConfigured = errors.New("error bucket life cycle configuration not found")
ErrChangePassword = errors.New("error please check your current password")
ErrInvalidLicense = errors.New("invalid license key")
ErrLicenseNotFound = errors.New("license not found")
ErrAvoidSelfAccountDelete = errors.New("logged in user cannot be deleted by itself")
ErrAccessDenied = errors.New("access denied")
ErrOauth2Provider = errors.New("unable to contact configured identity provider")
ErrOauth2Login = errors.New("unable to login using configured identity provider")
ErrNonUniqueAccessKey = errors.New("access key already in use")
ErrRemoteTierExists = errors.New("specified remote tier already exists")
ErrRemoteTierNotFound = errors.New("specified remote tier was not found")
ErrRemoteTierUppercase = errors.New("tier name must be in uppercase")
ErrRemoteTierBucketNotFound = errors.New("remote tier bucket not found")
ErrRemoteInvalidCredentials = errors.New("invalid remote tier credentials")
ErrUnableToGetTenantUsage = errors.New("unable to get tenant usage")
ErrTooManyNodes = errors.New("cannot request more nodes than what is available in the cluster")
ErrTooFewNodes = errors.New("there are not enough nodes in the cluster to support this tenant")
ErrTooFewAvailableNodes = errors.New("there is not enough available nodes to satisfy this requirement")
ErrFewerThanFourNodes = errors.New("at least 4 nodes are required for a tenant")
ErrUnableToGetTenantLogs = errors.New("unable to get tenant logs")
ErrUnableToUpdateTenantCertificates = errors.New("unable to update tenant certificates")
ErrUpdatingEncryptionConfig = errors.New("unable to update encryption configuration")
ErrDeletingEncryptionConfig = errors.New("error disabling tenant encryption")
ErrEncryptionConfigNotFound = errors.New("encryption configuration not found")
ErrPolicyNotFound = errors.New("policy does not exist")
ErrLoginNotAllowed = errors.New("login not allowed")
ErrHealthReportFail = errors.New("failure to generate Health report")
ErrNetworkError = errors.New("unable to login due to network error")
)
type CodedAPIError struct {
Code int
APIError *models.APIError
}
// ErrorWithContext :
func ErrorWithContext(ctx context.Context, err ...interface{}) *CodedAPIError {
errorCode := 500
errorMessage := ErrDefault.Error()
var detailedMessage string
var err1 error
var exists bool
if len(err) > 0 {
if err1, exists = err[0].(error); exists {
detailedMessage = err1.Error()
var lastError error
if len(err) > 1 {
if err2, lastExists := err[1].(error); lastExists {
lastError = err2
}
}
if err1.Error() == ErrForbidden.Error() {
errorCode = 403
}
if err1.Error() == ErrBadRequest.Error() {
errorCode = 400
}
if err1 == ErrNotFound {
errorCode = 404
errorMessage = ErrNotFound.Error()
}
if errors.Is(err1, ErrInvalidLogin) {
detailedMessage = ""
errorCode = 401
errorMessage = ErrInvalidLogin.Error()
}
if errors.Is(err1, ErrNetworkError) {
detailedMessage = ""
errorCode = 503
errorMessage = ErrNetworkError.Error()
}
if strings.Contains(strings.ToLower(err1.Error()), ErrAccessDenied.Error()) {
errorCode = 403
errorMessage = err1.Error()
}
// If the last error is ErrInvalidLogin, this is a login failure
if errors.Is(lastError, ErrInvalidLogin) {
detailedMessage = ""
errorCode = 401
errorMessage = err1.Error()
}
if strings.Contains(err1.Error(), ErrLoginNotAllowed.Error()) {
detailedMessage = ""
errorCode = 400
errorMessage = ErrLoginNotAllowed.Error()
}
// console invalid erasure coding value
if errors.Is(err1, ErrInvalidErasureCodingValue) {
errorCode = 400
errorMessage = ErrInvalidErasureCodingValue.Error()
}
if errors.Is(err1, ErrBucketBodyNotInRequest) {
errorCode = 400
errorMessage = ErrBucketBodyNotInRequest.Error()
}
if errors.Is(err1, ErrBucketNameNotInRequest) {
errorCode = 400
errorMessage = ErrBucketNameNotInRequest.Error()
}
if errors.Is(err1, ErrGroupBodyNotInRequest) {
errorCode = 400
errorMessage = ErrGroupBodyNotInRequest.Error()
}
if errors.Is(err1, ErrGroupNameNotInRequest) {
errorCode = 400
errorMessage = ErrGroupNameNotInRequest.Error()
}
if errors.Is(err1, ErrPolicyNameNotInRequest) {
errorCode = 400
errorMessage = ErrPolicyNameNotInRequest.Error()
}
if errors.Is(err1, ErrPolicyBodyNotInRequest) {
errorCode = 400
errorMessage = ErrPolicyBodyNotInRequest.Error()
}
// console invalid session errors
if errors.Is(err1, ErrInvalidSession) {
errorCode = 401
errorMessage = ErrInvalidSession.Error()
}
if errors.Is(err1, ErrGroupAlreadyExists) {
errorCode = 400
errorMessage = ErrGroupAlreadyExists.Error()
}
// Bucket life cycle not configured
if errors.Is(err1, ErrBucketLifeCycleNotConfigured) {
errorCode = 404
errorMessage = ErrBucketLifeCycleNotConfigured.Error()
}
// Encryption not configured
if errors.Is(err1, ErrSSENotConfigured) {
errorCode = 404
errorMessage = ErrSSENotConfigured.Error()
}
if errors.Is(err1, ErrEncryptionConfigNotFound) {
errorCode = 404
errorMessage = err1.Error()
}
// account change password
if errors.Is(err1, ErrChangePassword) {
errorCode = 403
errorMessage = ErrChangePassword.Error()
}
if madmin.ToErrorResponse(err1).Code == "SignatureDoesNotMatch" {
errorCode = 403
errorMessage = ErrChangePassword.Error()
}
if errors.Is(err1, ErrLicenseNotFound) {
errorCode = 404
errorMessage = ErrLicenseNotFound.Error()
}
if errors.Is(err1, ErrInvalidLicense) {
errorCode = 404
errorMessage = ErrInvalidLicense.Error()
}
if errors.Is(err1, ErrAvoidSelfAccountDelete) {
errorCode = 403
errorMessage = ErrAvoidSelfAccountDelete.Error()
}
if errors.Is(err1, ErrAccessDenied) {
errorCode = 403
errorMessage = ErrAccessDenied.Error()
}
if errors.Is(err1, ErrPolicyNotFound) {
errorCode = 404
errorMessage = ErrPolicyNotFound.Error()
}
if madmin.ToErrorResponse(err1).Code == "AccessDenied" {
errorCode = 403
errorMessage = ErrAccessDenied.Error()
}
if madmin.ToErrorResponse(err1).Code == "InvalidAccessKeyId" {
errorCode = 401
errorMessage = ErrInvalidSession.Error()
}
// console invalid session errors
if madmin.ToErrorResponse(err1).Code == "XMinioAdminNoSuchUser" {
errorCode = 401
errorMessage = ErrInvalidSession.Error()
}
// tiering errors
if err1.Error() == ErrRemoteTierExists.Error() {
errorCode = 400
errorMessage = err1.Error()
}
if err1.Error() == ErrRemoteTierNotFound.Error() {
errorCode = 400
errorMessage = err1.Error()
}
if err1.Error() == ErrRemoteTierUppercase.Error() {
errorCode = 400
errorMessage = err1.Error()
}
if err1.Error() == ErrRemoteTierBucketNotFound.Error() {
errorCode = 400
errorMessage = err1.Error()
}
if err1.Error() == ErrRemoteInvalidCredentials.Error() {
errorCode = 403
errorMessage = err1.Error()
}
if err1.Error() == ErrFileTooLarge.Error() {
errorCode = 413
errorMessage = err1.Error()
}
// bucket already exists
if minio.ToErrorResponse(err1).Code == "BucketAlreadyOwnedByYou" {
errorCode = 400
errorMessage = "Bucket already exists"
}
LogError("ErrorWithContext:%v", err...)
LogIf(ctx, err1, err...)
}
if len(err) > 1 && err[1] != nil {
if err2, ok := err[1].(error); ok {
errorMessage = err2.Error()
}
}
}
return &CodedAPIError{Code: errorCode, APIError: &models.APIError{Message: errorMessage, DetailedMessage: detailedMessage}}
}
// Error receives an errors object and parse it against k8sErrors, returns the right errors code paired with a generic errors message
func Error(err ...interface{}) *CodedAPIError {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
return ErrorWithContext(ctx, err...)
}

View File

@@ -1,158 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"context"
"fmt"
"testing"
"github.com/minio/console/models"
"github.com/stretchr/testify/assert"
)
func TestError(t *testing.T) {
type args struct {
err []interface{}
}
type testError struct {
name string
args args
want *CodedAPIError
}
var tests []testError
type expectedError struct {
err error
code int
}
appErrors := map[string]expectedError{
"ErrDefault": {code: 500, err: ErrDefault},
"ErrForbidden": {code: 403, err: ErrForbidden},
"ErrFileTooLarge": {code: 413, err: ErrFileTooLarge},
"ErrInvalidSession": {code: 401, err: ErrInvalidSession},
"ErrNotFound": {code: 404, err: ErrNotFound},
"ErrGroupAlreadyExists": {code: 400, err: ErrGroupAlreadyExists},
"ErrInvalidErasureCodingValue": {code: 400, err: ErrInvalidErasureCodingValue},
"ErrBucketBodyNotInRequest": {code: 400, err: ErrBucketBodyNotInRequest},
"ErrBucketNameNotInRequest": {code: 400, err: ErrBucketNameNotInRequest},
"ErrGroupBodyNotInRequest": {code: 400, err: ErrGroupBodyNotInRequest},
"ErrGroupNameNotInRequest": {code: 400, err: ErrGroupNameNotInRequest},
"ErrPolicyNameNotInRequest": {code: 400, err: ErrPolicyNameNotInRequest},
"ErrPolicyBodyNotInRequest": {code: 400, err: ErrPolicyBodyNotInRequest},
"ErrInvalidEncryptionAlgorithm": {code: 500, err: ErrInvalidEncryptionAlgorithm},
"ErrSSENotConfigured": {code: 404, err: ErrSSENotConfigured},
"ErrChangePassword": {code: 403, err: ErrChangePassword},
"ErrInvalidLicense": {code: 404, err: ErrInvalidLicense},
"ErrLicenseNotFound": {code: 404, err: ErrLicenseNotFound},
"ErrAvoidSelfAccountDelete": {code: 403, err: ErrAvoidSelfAccountDelete},
"ErrNonUniqueAccessKey": {code: 500, err: ErrNonUniqueAccessKey},
"ErrRemoteTierExists": {code: 400, err: ErrRemoteTierExists},
"ErrRemoteTierNotFound": {code: 400, err: ErrRemoteTierNotFound},
"ErrRemoteTierUppercase": {code: 400, err: ErrRemoteTierUppercase},
"ErrRemoteTierBucketNotFound": {code: 400, err: ErrRemoteTierBucketNotFound},
"ErrRemoteInvalidCredentials": {code: 403, err: ErrRemoteInvalidCredentials},
"ErrTooFewNodes": {code: 500, err: ErrTooFewNodes},
"ErrUnableToGetTenantUsage": {code: 500, err: ErrUnableToGetTenantUsage},
"ErrTooManyNodes": {code: 500, err: ErrTooManyNodes},
"ErrAccessDenied": {code: 403, err: ErrAccessDenied},
"ErrTooFewAvailableNodes": {code: 500, err: ErrTooFewAvailableNodes},
"ErrFewerThanFourNodes": {code: 500, err: ErrFewerThanFourNodes},
"ErrUnableToGetTenantLogs": {code: 500, err: ErrUnableToGetTenantLogs},
"ErrUnableToUpdateTenantCertificates": {code: 500, err: ErrUnableToUpdateTenantCertificates},
"ErrUpdatingEncryptionConfig": {code: 500, err: ErrUpdatingEncryptionConfig},
"ErrDeletingEncryptionConfig": {code: 500, err: ErrDeletingEncryptionConfig},
"ErrEncryptionConfigNotFound": {code: 404, err: ErrEncryptionConfigNotFound},
}
for k, e := range appErrors {
tests = append(tests, testError{
name: fmt.Sprintf("%s error", k),
args: args{
err: []interface{}{e.err},
},
want: &CodedAPIError{
Code: e.code,
APIError: &models.APIError{Message: e.err.Error(), DetailedMessage: e.err.Error()},
},
})
}
tests = append(tests,
testError{
name: "passing multiple errors but ErrInvalidLogin is last",
args: args{
err: []interface{}{ErrDefault, ErrInvalidLogin},
},
want: &CodedAPIError{
Code: int(401),
APIError: &models.APIError{Message: ErrDefault.Error(), DetailedMessage: ""},
},
})
tests = append(tests,
testError{
name: "login error omits detailedMessage",
args: args{
err: []interface{}{ErrInvalidLogin},
},
want: &CodedAPIError{
Code: int(401),
APIError: &models.APIError{Message: ErrInvalidLogin.Error(), DetailedMessage: ""},
},
})
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
got := Error(tt.args.err...)
assert.Equalf(t, tt.want.Code, got.Code, "Error(%v) Got (%v)", tt.want.Code, got.Code)
assert.Equalf(t, tt.want.APIError.DetailedMessage, got.APIError.DetailedMessage, "Error(%s) Got (%s)", tt.want.APIError.DetailedMessage, got.APIError.DetailedMessage)
})
}
}
func TestErrorWithContext(t *testing.T) {
type args struct {
ctx context.Context
err []interface{}
}
tests := []struct {
name string
args args
want *CodedAPIError
}{
{
name: "default error",
args: args{
ctx: context.Background(),
err: []interface{}{ErrDefault},
},
want: &CodedAPIError{
Code: 500, APIError: &models.APIError{Message: ErrDefault.Error(), DetailedMessage: ErrDefault.Error()},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
assert.Equalf(t, tt.want, ErrorWithContext(tt.args.ctx, tt.args.err...), "ErrorWithContext(%v, %v)", tt.args.ctx, tt.args.err)
})
}
}

View File

@@ -1,83 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"context"
"errors"
"log"
"os"
"github.com/minio/cli"
)
var (
infoLog = log.New(os.Stdout, "I: ", log.LstdFlags)
errorLog = log.New(os.Stdout, "E: ", log.LstdFlags)
)
func logInfo(msg string, data ...interface{}) {
infoLog.Printf(msg+"\n", data...)
}
func logError(msg string, data ...interface{}) {
errorLog.Printf(msg+"\n", data...)
}
func logIf(_ context.Context, _ error, _ ...interface{}) {
}
// globally changeable logger styles
var (
LogInfo = logInfo
LogError = logError
LogIf = logIf
)
// Context captures all command line flags values
type Context struct {
Host string
HTTPPort, HTTPSPort int
TLSRedirect string
// Legacy options, TODO: remove in future
TLSCertificate, TLSKey, TLSca string
}
// Load loads api Context from command line context.
func (c *Context) Load(ctx *cli.Context) error {
*c = Context{
Host: ctx.String("host"),
HTTPPort: ctx.Int("port"),
HTTPSPort: ctx.Int("tls-port"),
TLSRedirect: ctx.String("tls-redirect"),
// Legacy options to be removed.
TLSCertificate: ctx.String("tls-certificate"),
TLSKey: ctx.String("tls-key"),
TLSca: ctx.String("tls-ca"),
}
if c.HTTPPort > 65535 {
return errors.New("invalid argument --port out of range - ports can range from 1-65535")
}
if c.HTTPSPort > 65535 {
return errors.New("invalid argument --tls-port out of range - ports can range from 1-65535")
}
if c.TLSRedirect != "on" && c.TLSRedirect != "off" {
return errors.New("invalid argument --tls-redirect only accepts either 'on' or 'off'")
}
return nil
}

View File

@@ -1,110 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// 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 api
import (
"flag"
"fmt"
"testing"
"github.com/minio/cli"
"github.com/stretchr/testify/assert"
)
func TestContext_Load(t *testing.T) {
type fields struct {
Host string
HTTPPort int
HTTPSPort int
TLSRedirect string
TLSCertificate string
TLSKey string
TLSca string
}
type args struct {
values map[string]string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "valid args",
args: args{
values: map[string]string{
"tls-redirect": "on",
},
},
wantErr: false,
},
{
name: "invalid args",
args: args{
values: map[string]string{
"tls-redirect": "aaaa",
},
},
wantErr: true,
},
{
name: "invalid port http",
args: args{
values: map[string]string{
"tls-redirect": "on",
"port": "65536",
},
},
wantErr: true,
},
{
name: "invalid port https",
args: args{
values: map[string]string{
"tls-redirect": "on",
"port": "65534",
"tls-port": "65536",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
c := &Context{}
fs := flag.NewFlagSet("flags", flag.ContinueOnError)
for k, v := range tt.args.values {
fs.String(k, v, "ok")
}
ctx := cli.NewContext(nil, fs, &cli.Context{})
err := c.Load(ctx)
if tt.wantErr {
assert.NotNilf(t, err, fmt.Sprintf("Load(%v)", err))
} else {
assert.Nilf(t, err, fmt.Sprintf("Load(%v)", err))
}
})
}
}
func Test_logInfo(_ *testing.T) {
logInfo("message", nil)
}

View File

@@ -1,138 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// GetBucketRewindURL generates an URL for the get bucket rewind operation
type GetBucketRewindURL struct {
BucketName string
Date string
Prefix *string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetBucketRewindURL) WithBasePath(bp string) *GetBucketRewindURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetBucketRewindURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *GetBucketRewindURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{bucket_name}/rewind/{date}"
bucketName := o.BucketName
if bucketName != "" {
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
} else {
return nil, errors.New("bucketName is required on GetBucketRewindURL")
}
date := o.Date
if date != "" {
_path = strings.Replace(_path, "{date}", date, -1)
} else {
return nil, errors.New("date is required on GetBucketRewindURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
var prefixQ string
if o.Prefix != nil {
prefixQ = *o.Prefix
}
if prefixQ != "" {
qs.Set("prefix", prefixQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *GetBucketRewindURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *GetBucketRewindURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *GetBucketRewindURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on GetBucketRewindURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on GetBucketRewindURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *GetBucketRewindURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,135 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// GetBucketVersioningOKCode is the HTTP code returned for type GetBucketVersioningOK
const GetBucketVersioningOKCode int = 200
/*
GetBucketVersioningOK A successful response.
swagger:response getBucketVersioningOK
*/
type GetBucketVersioningOK struct {
/*
In: Body
*/
Payload *models.BucketVersioningResponse `json:"body,omitempty"`
}
// NewGetBucketVersioningOK creates GetBucketVersioningOK with default headers values
func NewGetBucketVersioningOK() *GetBucketVersioningOK {
return &GetBucketVersioningOK{}
}
// WithPayload adds the payload to the get bucket versioning o k response
func (o *GetBucketVersioningOK) WithPayload(payload *models.BucketVersioningResponse) *GetBucketVersioningOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get bucket versioning o k response
func (o *GetBucketVersioningOK) SetPayload(payload *models.BucketVersioningResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetBucketVersioningOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*
GetBucketVersioningDefault Generic error response.
swagger:response getBucketVersioningDefault
*/
type GetBucketVersioningDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewGetBucketVersioningDefault creates GetBucketVersioningDefault with default headers values
func NewGetBucketVersioningDefault(code int) *GetBucketVersioningDefault {
if code <= 0 {
code = 500
}
return &GetBucketVersioningDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the get bucket versioning default response
func (o *GetBucketVersioningDefault) WithStatusCode(code int) *GetBucketVersioningDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the get bucket versioning default response
func (o *GetBucketVersioningDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the get bucket versioning default response
func (o *GetBucketVersioningDefault) WithPayload(payload *models.APIError) *GetBucketVersioningDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get bucket versioning default response
func (o *GetBucketVersioningDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetBucketVersioningDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,88 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// GetMaxShareLinkExpHandlerFunc turns a function with the right signature into a get max share link exp handler
type GetMaxShareLinkExpHandlerFunc func(GetMaxShareLinkExpParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn GetMaxShareLinkExpHandlerFunc) Handle(params GetMaxShareLinkExpParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// GetMaxShareLinkExpHandler interface for that can handle valid get max share link exp params
type GetMaxShareLinkExpHandler interface {
Handle(GetMaxShareLinkExpParams, *models.Principal) middleware.Responder
}
// NewGetMaxShareLinkExp creates a new http.Handler for the get max share link exp operation
func NewGetMaxShareLinkExp(ctx *middleware.Context, handler GetMaxShareLinkExpHandler) *GetMaxShareLinkExp {
return &GetMaxShareLinkExp{Context: ctx, Handler: handler}
}
/*
GetMaxShareLinkExp swagger:route GET /buckets/max-share-exp Bucket getMaxShareLinkExp
Get max expiration time for share link in seconds
*/
type GetMaxShareLinkExp struct {
Context *middleware.Context
Handler GetMaxShareLinkExpHandler
}
func (o *GetMaxShareLinkExp) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewGetMaxShareLinkExpParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,135 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// GetMaxShareLinkExpOKCode is the HTTP code returned for type GetMaxShareLinkExpOK
const GetMaxShareLinkExpOKCode int = 200
/*
GetMaxShareLinkExpOK A successful response.
swagger:response getMaxShareLinkExpOK
*/
type GetMaxShareLinkExpOK struct {
/*
In: Body
*/
Payload *models.MaxShareLinkExpResponse `json:"body,omitempty"`
}
// NewGetMaxShareLinkExpOK creates GetMaxShareLinkExpOK with default headers values
func NewGetMaxShareLinkExpOK() *GetMaxShareLinkExpOK {
return &GetMaxShareLinkExpOK{}
}
// WithPayload adds the payload to the get max share link exp o k response
func (o *GetMaxShareLinkExpOK) WithPayload(payload *models.MaxShareLinkExpResponse) *GetMaxShareLinkExpOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get max share link exp o k response
func (o *GetMaxShareLinkExpOK) SetPayload(payload *models.MaxShareLinkExpResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetMaxShareLinkExpOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*
GetMaxShareLinkExpDefault Generic error response.
swagger:response getMaxShareLinkExpDefault
*/
type GetMaxShareLinkExpDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewGetMaxShareLinkExpDefault creates GetMaxShareLinkExpDefault with default headers values
func NewGetMaxShareLinkExpDefault(code int) *GetMaxShareLinkExpDefault {
if code <= 0 {
code = 500
}
return &GetMaxShareLinkExpDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the get max share link exp default response
func (o *GetMaxShareLinkExpDefault) WithStatusCode(code int) *GetMaxShareLinkExpDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the get max share link exp default response
func (o *GetMaxShareLinkExpDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the get max share link exp default response
func (o *GetMaxShareLinkExpDefault) WithPayload(payload *models.APIError) *GetMaxShareLinkExpDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get max share link exp default response
func (o *GetMaxShareLinkExpDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetMaxShareLinkExpDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,88 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// SetBucketVersioningHandlerFunc turns a function with the right signature into a set bucket versioning handler
type SetBucketVersioningHandlerFunc func(SetBucketVersioningParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn SetBucketVersioningHandlerFunc) Handle(params SetBucketVersioningParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// SetBucketVersioningHandler interface for that can handle valid set bucket versioning params
type SetBucketVersioningHandler interface {
Handle(SetBucketVersioningParams, *models.Principal) middleware.Responder
}
// NewSetBucketVersioning creates a new http.Handler for the set bucket versioning operation
func NewSetBucketVersioning(ctx *middleware.Context, handler SetBucketVersioningHandler) *SetBucketVersioning {
return &SetBucketVersioning{Context: ctx, Handler: handler}
}
/*
SetBucketVersioning swagger:route PUT /buckets/{bucket_name}/versioning Bucket setBucketVersioning
Set Bucket Versioning
*/
type SetBucketVersioning struct {
Context *middleware.Context
Handler SetBucketVersioningHandler
}
func (o *SetBucketVersioning) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewSetBucketVersioningParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,115 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// SetBucketVersioningCreatedCode is the HTTP code returned for type SetBucketVersioningCreated
const SetBucketVersioningCreatedCode int = 201
/*
SetBucketVersioningCreated A successful response.
swagger:response setBucketVersioningCreated
*/
type SetBucketVersioningCreated struct {
}
// NewSetBucketVersioningCreated creates SetBucketVersioningCreated with default headers values
func NewSetBucketVersioningCreated() *SetBucketVersioningCreated {
return &SetBucketVersioningCreated{}
}
// WriteResponse to the client
func (o *SetBucketVersioningCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(201)
}
/*
SetBucketVersioningDefault Generic error response.
swagger:response setBucketVersioningDefault
*/
type SetBucketVersioningDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewSetBucketVersioningDefault creates SetBucketVersioningDefault with default headers values
func NewSetBucketVersioningDefault(code int) *SetBucketVersioningDefault {
if code <= 0 {
code = 500
}
return &SetBucketVersioningDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the set bucket versioning default response
func (o *SetBucketVersioningDefault) WithStatusCode(code int) *SetBucketVersioningDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the set bucket versioning default response
func (o *SetBucketVersioningDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the set bucket versioning default response
func (o *SetBucketVersioningDefault) WithPayload(payload *models.APIError) *SetBucketVersioningDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the set bucket versioning default response
func (o *SetBucketVersioningDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SetBucketVersioningDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,713 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"fmt"
"net/http"
"strings"
"github.com/go-openapi/errors"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/runtime/security"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/minio/console/api/operations/auth"
"github.com/minio/console/api/operations/bucket"
"github.com/minio/console/api/operations/license"
"github.com/minio/console/api/operations/object"
"github.com/minio/console/api/operations/public"
"github.com/minio/console/api/operations/system"
"github.com/minio/console/api/operations/user"
"github.com/minio/console/models"
)
// NewConsoleAPI creates a new Console instance
func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
return &ConsoleAPI{
handlers: make(map[string]map[string]http.Handler),
formats: strfmt.Default,
defaultConsumes: "application/json",
defaultProduces: "application/json",
customConsumers: make(map[string]runtime.Consumer),
customProducers: make(map[string]runtime.Producer),
PreServerShutdown: func() {},
ServerShutdown: func() {},
spec: spec,
useSwaggerUI: false,
ServeError: errors.ServeError,
BasicAuthenticator: security.BasicAuth,
APIKeyAuthenticator: security.APIKeyAuth,
BearerAuthenticator: security.BearerAuth,
JSONConsumer: runtime.JSONConsumer(),
MultipartformConsumer: runtime.DiscardConsumer,
BinProducer: runtime.ByteStreamProducer(),
JSONProducer: runtime.JSONProducer(),
SystemAdminInfoHandler: system.AdminInfoHandlerFunc(func(params system.AdminInfoParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation system.AdminInfo has not yet been implemented")
}),
BucketBucketInfoHandler: bucket.BucketInfoHandlerFunc(func(params bucket.BucketInfoParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation bucket.BucketInfo has not yet been implemented")
}),
ObjectDeleteMultipleObjectsHandler: object.DeleteMultipleObjectsHandlerFunc(func(params object.DeleteMultipleObjectsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation object.DeleteMultipleObjects has not yet been implemented")
}),
ObjectDeleteObjectHandler: object.DeleteObjectHandlerFunc(func(params object.DeleteObjectParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation object.DeleteObject has not yet been implemented")
}),
ObjectDownloadObjectHandler: object.DownloadObjectHandlerFunc(func(params object.DownloadObjectParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation object.DownloadObject has not yet been implemented")
}),
ObjectDownloadMultipleObjectsHandler: object.DownloadMultipleObjectsHandlerFunc(func(params object.DownloadMultipleObjectsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation object.DownloadMultipleObjects has not yet been implemented")
}),
PublicDownloadSharedObjectHandler: public.DownloadSharedObjectHandlerFunc(func(params public.DownloadSharedObjectParams) middleware.Responder {
return middleware.NotImplemented("operation public.DownloadSharedObject has not yet been implemented")
}),
BucketGetBucketQuotaHandler: bucket.GetBucketQuotaHandlerFunc(func(params bucket.GetBucketQuotaParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation bucket.GetBucketQuota has not yet been implemented")
}),
BucketGetBucketRewindHandler: bucket.GetBucketRewindHandlerFunc(func(params bucket.GetBucketRewindParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation bucket.GetBucketRewind has not yet been implemented")
}),
BucketGetBucketVersioningHandler: bucket.GetBucketVersioningHandlerFunc(func(params bucket.GetBucketVersioningParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation bucket.GetBucketVersioning has not yet been implemented")
}),
BucketGetMaxShareLinkExpHandler: bucket.GetMaxShareLinkExpHandlerFunc(func(params bucket.GetMaxShareLinkExpParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation bucket.GetMaxShareLinkExp has not yet been implemented")
}),
ObjectGetObjectMetadataHandler: object.GetObjectMetadataHandlerFunc(func(params object.GetObjectMetadataParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation object.GetObjectMetadata has not yet been implemented")
}),
LicenseLicenseAcknowledgeHandler: license.LicenseAcknowledgeHandlerFunc(func(params license.LicenseAcknowledgeParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation license.LicenseAcknowledge has not yet been implemented")
}),
BucketListBucketsHandler: bucket.ListBucketsHandlerFunc(func(params bucket.ListBucketsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation bucket.ListBuckets has not yet been implemented")
}),
ObjectListObjectsHandler: object.ListObjectsHandlerFunc(func(params object.ListObjectsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation object.ListObjects has not yet been implemented")
}),
UserListUsersHandler: user.ListUsersHandlerFunc(func(params user.ListUsersParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user.ListUsers has not yet been implemented")
}),
AuthLoginHandler: auth.LoginHandlerFunc(func(params auth.LoginParams) middleware.Responder {
return middleware.NotImplemented("operation auth.Login has not yet been implemented")
}),
AuthLoginDetailHandler: auth.LoginDetailHandlerFunc(func(params auth.LoginDetailParams) middleware.Responder {
return middleware.NotImplemented("operation auth.LoginDetail has not yet been implemented")
}),
AuthLoginOauth2AuthHandler: auth.LoginOauth2AuthHandlerFunc(func(params auth.LoginOauth2AuthParams) middleware.Responder {
return middleware.NotImplemented("operation auth.LoginOauth2Auth has not yet been implemented")
}),
AuthLogoutHandler: auth.LogoutHandlerFunc(func(params auth.LogoutParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation auth.Logout has not yet been implemented")
}),
BucketMakeBucketHandler: bucket.MakeBucketHandlerFunc(func(params bucket.MakeBucketParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation bucket.MakeBucket has not yet been implemented")
}),
ObjectPostBucketsBucketNameObjectsUploadHandler: object.PostBucketsBucketNameObjectsUploadHandlerFunc(func(params object.PostBucketsBucketNameObjectsUploadParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation object.PostBucketsBucketNameObjectsUpload has not yet been implemented")
}),
BucketPutBucketTagsHandler: bucket.PutBucketTagsHandlerFunc(func(params bucket.PutBucketTagsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation bucket.PutBucketTags has not yet been implemented")
}),
ObjectPutObjectRestoreHandler: object.PutObjectRestoreHandlerFunc(func(params object.PutObjectRestoreParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation object.PutObjectRestore has not yet been implemented")
}),
ObjectPutObjectTagsHandler: object.PutObjectTagsHandlerFunc(func(params object.PutObjectTagsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation object.PutObjectTags has not yet been implemented")
}),
AuthSessionCheckHandler: auth.SessionCheckHandlerFunc(func(params auth.SessionCheckParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation auth.SessionCheck has not yet been implemented")
}),
BucketSetBucketVersioningHandler: bucket.SetBucketVersioningHandlerFunc(func(params bucket.SetBucketVersioningParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation bucket.SetBucketVersioning has not yet been implemented")
}),
ObjectShareObjectHandler: object.ShareObjectHandlerFunc(func(params object.ShareObjectParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation object.ShareObject has not yet been implemented")
}),
// Applies when the "X-Anonymous" header is set
AnonymousAuth: func(token string) (*models.Principal, error) {
return nil, errors.NotImplemented("api key auth (anonymous) X-Anonymous from header param [X-Anonymous] has not yet been implemented")
},
KeyAuth: func(token string, scopes []string) (*models.Principal, error) {
return nil, errors.NotImplemented("oauth2 bearer auth (key) has not yet been implemented")
},
// default authorizer is authorized meaning no requests are blocked
APIAuthorizer: security.Authorized(),
}
}
/*ConsoleAPI the console API */
type ConsoleAPI struct {
spec *loads.Document
context *middleware.Context
handlers map[string]map[string]http.Handler
formats strfmt.Registry
customConsumers map[string]runtime.Consumer
customProducers map[string]runtime.Producer
defaultConsumes string
defaultProduces string
Middleware func(middleware.Builder) http.Handler
useSwaggerUI bool
// BasicAuthenticator generates a runtime.Authenticator from the supplied basic auth function.
// It has a default implementation in the security package, however you can replace it for your particular usage.
BasicAuthenticator func(security.UserPassAuthentication) runtime.Authenticator
// APIKeyAuthenticator generates a runtime.Authenticator from the supplied token auth function.
// It has a default implementation in the security package, however you can replace it for your particular usage.
APIKeyAuthenticator func(string, string, security.TokenAuthentication) runtime.Authenticator
// BearerAuthenticator generates a runtime.Authenticator from the supplied bearer token auth function.
// It has a default implementation in the security package, however you can replace it for your particular usage.
BearerAuthenticator func(string, security.ScopedTokenAuthentication) runtime.Authenticator
// JSONConsumer registers a consumer for the following mime types:
// - application/json
JSONConsumer runtime.Consumer
// MultipartformConsumer registers a consumer for the following mime types:
// - multipart/form-data
MultipartformConsumer runtime.Consumer
// BinProducer registers a producer for the following mime types:
// - application/octet-stream
BinProducer runtime.Producer
// JSONProducer registers a producer for the following mime types:
// - application/json
JSONProducer runtime.Producer
// AnonymousAuth registers a function that takes a token and returns a principal
// it performs authentication based on an api key X-Anonymous provided in the header
AnonymousAuth func(string) (*models.Principal, error)
// KeyAuth registers a function that takes an access token and a collection of required scopes and returns a principal
// it performs authentication based on an oauth2 bearer token provided in the request
KeyAuth func(string, []string) (*models.Principal, error)
// APIAuthorizer provides access control (ACL/RBAC/ABAC) by providing access to the request and authenticated principal
APIAuthorizer runtime.Authorizer
// SystemAdminInfoHandler sets the operation handler for the admin info operation
SystemAdminInfoHandler system.AdminInfoHandler
// BucketBucketInfoHandler sets the operation handler for the bucket info operation
BucketBucketInfoHandler bucket.BucketInfoHandler
// ObjectDeleteMultipleObjectsHandler sets the operation handler for the delete multiple objects operation
ObjectDeleteMultipleObjectsHandler object.DeleteMultipleObjectsHandler
// ObjectDeleteObjectHandler sets the operation handler for the delete object operation
ObjectDeleteObjectHandler object.DeleteObjectHandler
// ObjectDownloadObjectHandler sets the operation handler for the download object operation
ObjectDownloadObjectHandler object.DownloadObjectHandler
// ObjectDownloadMultipleObjectsHandler sets the operation handler for the download multiple objects operation
ObjectDownloadMultipleObjectsHandler object.DownloadMultipleObjectsHandler
// PublicDownloadSharedObjectHandler sets the operation handler for the download shared object operation
PublicDownloadSharedObjectHandler public.DownloadSharedObjectHandler
// BucketGetBucketQuotaHandler sets the operation handler for the get bucket quota operation
BucketGetBucketQuotaHandler bucket.GetBucketQuotaHandler
// BucketGetBucketRewindHandler sets the operation handler for the get bucket rewind operation
BucketGetBucketRewindHandler bucket.GetBucketRewindHandler
// BucketGetBucketVersioningHandler sets the operation handler for the get bucket versioning operation
BucketGetBucketVersioningHandler bucket.GetBucketVersioningHandler
// BucketGetMaxShareLinkExpHandler sets the operation handler for the get max share link exp operation
BucketGetMaxShareLinkExpHandler bucket.GetMaxShareLinkExpHandler
// ObjectGetObjectMetadataHandler sets the operation handler for the get object metadata operation
ObjectGetObjectMetadataHandler object.GetObjectMetadataHandler
// LicenseLicenseAcknowledgeHandler sets the operation handler for the license acknowledge operation
LicenseLicenseAcknowledgeHandler license.LicenseAcknowledgeHandler
// BucketListBucketsHandler sets the operation handler for the list buckets operation
BucketListBucketsHandler bucket.ListBucketsHandler
// ObjectListObjectsHandler sets the operation handler for the list objects operation
ObjectListObjectsHandler object.ListObjectsHandler
// UserListUsersHandler sets the operation handler for the list users operation
UserListUsersHandler user.ListUsersHandler
// AuthLoginHandler sets the operation handler for the login operation
AuthLoginHandler auth.LoginHandler
// AuthLoginDetailHandler sets the operation handler for the login detail operation
AuthLoginDetailHandler auth.LoginDetailHandler
// AuthLoginOauth2AuthHandler sets the operation handler for the login oauth2 auth operation
AuthLoginOauth2AuthHandler auth.LoginOauth2AuthHandler
// AuthLogoutHandler sets the operation handler for the logout operation
AuthLogoutHandler auth.LogoutHandler
// BucketMakeBucketHandler sets the operation handler for the make bucket operation
BucketMakeBucketHandler bucket.MakeBucketHandler
// ObjectPostBucketsBucketNameObjectsUploadHandler sets the operation handler for the post buckets bucket name objects upload operation
ObjectPostBucketsBucketNameObjectsUploadHandler object.PostBucketsBucketNameObjectsUploadHandler
// BucketPutBucketTagsHandler sets the operation handler for the put bucket tags operation
BucketPutBucketTagsHandler bucket.PutBucketTagsHandler
// ObjectPutObjectRestoreHandler sets the operation handler for the put object restore operation
ObjectPutObjectRestoreHandler object.PutObjectRestoreHandler
// ObjectPutObjectTagsHandler sets the operation handler for the put object tags operation
ObjectPutObjectTagsHandler object.PutObjectTagsHandler
// AuthSessionCheckHandler sets the operation handler for the session check operation
AuthSessionCheckHandler auth.SessionCheckHandler
// BucketSetBucketVersioningHandler sets the operation handler for the set bucket versioning operation
BucketSetBucketVersioningHandler bucket.SetBucketVersioningHandler
// ObjectShareObjectHandler sets the operation handler for the share object operation
ObjectShareObjectHandler object.ShareObjectHandler
// ServeError is called when an error is received, there is a default handler
// but you can set your own with this
ServeError func(http.ResponseWriter, *http.Request, error)
// PreServerShutdown is called before the HTTP(S) server is shutdown
// This allows for custom functions to get executed before the HTTP(S) server stops accepting traffic
PreServerShutdown func()
// ServerShutdown is called when the HTTP(S) server is shut down and done
// handling all active connections and does not accept connections any more
ServerShutdown func()
// Custom command line argument groups with their descriptions
CommandLineOptionsGroups []swag.CommandLineOptionsGroup
// User defined logger function.
Logger func(string, ...interface{})
}
// UseRedoc for documentation at /docs
func (o *ConsoleAPI) UseRedoc() {
o.useSwaggerUI = false
}
// UseSwaggerUI for documentation at /docs
func (o *ConsoleAPI) UseSwaggerUI() {
o.useSwaggerUI = true
}
// SetDefaultProduces sets the default produces media type
func (o *ConsoleAPI) SetDefaultProduces(mediaType string) {
o.defaultProduces = mediaType
}
// SetDefaultConsumes returns the default consumes media type
func (o *ConsoleAPI) SetDefaultConsumes(mediaType string) {
o.defaultConsumes = mediaType
}
// SetSpec sets a spec that will be served for the clients.
func (o *ConsoleAPI) SetSpec(spec *loads.Document) {
o.spec = spec
}
// DefaultProduces returns the default produces media type
func (o *ConsoleAPI) DefaultProduces() string {
return o.defaultProduces
}
// DefaultConsumes returns the default consumes media type
func (o *ConsoleAPI) DefaultConsumes() string {
return o.defaultConsumes
}
// Formats returns the registered string formats
func (o *ConsoleAPI) Formats() strfmt.Registry {
return o.formats
}
// RegisterFormat registers a custom format validator
func (o *ConsoleAPI) RegisterFormat(name string, format strfmt.Format, validator strfmt.Validator) {
o.formats.Add(name, format, validator)
}
// Validate validates the registrations in the ConsoleAPI
func (o *ConsoleAPI) Validate() error {
var unregistered []string
if o.JSONConsumer == nil {
unregistered = append(unregistered, "JSONConsumer")
}
if o.MultipartformConsumer == nil {
unregistered = append(unregistered, "MultipartformConsumer")
}
if o.BinProducer == nil {
unregistered = append(unregistered, "BinProducer")
}
if o.JSONProducer == nil {
unregistered = append(unregistered, "JSONProducer")
}
if o.AnonymousAuth == nil {
unregistered = append(unregistered, "XAnonymousAuth")
}
if o.KeyAuth == nil {
unregistered = append(unregistered, "KeyAuth")
}
if o.SystemAdminInfoHandler == nil {
unregistered = append(unregistered, "system.AdminInfoHandler")
}
if o.BucketBucketInfoHandler == nil {
unregistered = append(unregistered, "bucket.BucketInfoHandler")
}
if o.ObjectDeleteMultipleObjectsHandler == nil {
unregistered = append(unregistered, "object.DeleteMultipleObjectsHandler")
}
if o.ObjectDeleteObjectHandler == nil {
unregistered = append(unregistered, "object.DeleteObjectHandler")
}
if o.ObjectDownloadObjectHandler == nil {
unregistered = append(unregistered, "object.DownloadObjectHandler")
}
if o.ObjectDownloadMultipleObjectsHandler == nil {
unregistered = append(unregistered, "object.DownloadMultipleObjectsHandler")
}
if o.PublicDownloadSharedObjectHandler == nil {
unregistered = append(unregistered, "public.DownloadSharedObjectHandler")
}
if o.BucketGetBucketQuotaHandler == nil {
unregistered = append(unregistered, "bucket.GetBucketQuotaHandler")
}
if o.BucketGetBucketRewindHandler == nil {
unregistered = append(unregistered, "bucket.GetBucketRewindHandler")
}
if o.BucketGetBucketVersioningHandler == nil {
unregistered = append(unregistered, "bucket.GetBucketVersioningHandler")
}
if o.BucketGetMaxShareLinkExpHandler == nil {
unregistered = append(unregistered, "bucket.GetMaxShareLinkExpHandler")
}
if o.ObjectGetObjectMetadataHandler == nil {
unregistered = append(unregistered, "object.GetObjectMetadataHandler")
}
if o.LicenseLicenseAcknowledgeHandler == nil {
unregistered = append(unregistered, "license.LicenseAcknowledgeHandler")
}
if o.BucketListBucketsHandler == nil {
unregistered = append(unregistered, "bucket.ListBucketsHandler")
}
if o.ObjectListObjectsHandler == nil {
unregistered = append(unregistered, "object.ListObjectsHandler")
}
if o.UserListUsersHandler == nil {
unregistered = append(unregistered, "user.ListUsersHandler")
}
if o.AuthLoginHandler == nil {
unregistered = append(unregistered, "auth.LoginHandler")
}
if o.AuthLoginDetailHandler == nil {
unregistered = append(unregistered, "auth.LoginDetailHandler")
}
if o.AuthLoginOauth2AuthHandler == nil {
unregistered = append(unregistered, "auth.LoginOauth2AuthHandler")
}
if o.AuthLogoutHandler == nil {
unregistered = append(unregistered, "auth.LogoutHandler")
}
if o.BucketMakeBucketHandler == nil {
unregistered = append(unregistered, "bucket.MakeBucketHandler")
}
if o.ObjectPostBucketsBucketNameObjectsUploadHandler == nil {
unregistered = append(unregistered, "object.PostBucketsBucketNameObjectsUploadHandler")
}
if o.BucketPutBucketTagsHandler == nil {
unregistered = append(unregistered, "bucket.PutBucketTagsHandler")
}
if o.ObjectPutObjectRestoreHandler == nil {
unregistered = append(unregistered, "object.PutObjectRestoreHandler")
}
if o.ObjectPutObjectTagsHandler == nil {
unregistered = append(unregistered, "object.PutObjectTagsHandler")
}
if o.AuthSessionCheckHandler == nil {
unregistered = append(unregistered, "auth.SessionCheckHandler")
}
if o.BucketSetBucketVersioningHandler == nil {
unregistered = append(unregistered, "bucket.SetBucketVersioningHandler")
}
if o.ObjectShareObjectHandler == nil {
unregistered = append(unregistered, "object.ShareObjectHandler")
}
if len(unregistered) > 0 {
return fmt.Errorf("missing registration: %s", strings.Join(unregistered, ", "))
}
return nil
}
// ServeErrorFor gets a error handler for a given operation id
func (o *ConsoleAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
return o.ServeError
}
// AuthenticatorsFor gets the authenticators for the specified security schemes
func (o *ConsoleAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {
result := make(map[string]runtime.Authenticator)
for name := range schemes {
switch name {
case "anonymous":
scheme := schemes[name]
result[name] = o.APIKeyAuthenticator(scheme.Name, scheme.In, func(token string) (interface{}, error) {
return o.AnonymousAuth(token)
})
case "key":
result[name] = o.BearerAuthenticator(name, func(token string, scopes []string) (interface{}, error) {
return o.KeyAuth(token, scopes)
})
}
}
return result
}
// Authorizer returns the registered authorizer
func (o *ConsoleAPI) Authorizer() runtime.Authorizer {
return o.APIAuthorizer
}
// ConsumersFor gets the consumers for the specified media types.
// MIME type parameters are ignored here.
func (o *ConsoleAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
result := make(map[string]runtime.Consumer, len(mediaTypes))
for _, mt := range mediaTypes {
switch mt {
case "application/json":
result["application/json"] = o.JSONConsumer
case "multipart/form-data":
result["multipart/form-data"] = o.MultipartformConsumer
}
if c, ok := o.customConsumers[mt]; ok {
result[mt] = c
}
}
return result
}
// ProducersFor gets the producers for the specified media types.
// MIME type parameters are ignored here.
func (o *ConsoleAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
result := make(map[string]runtime.Producer, len(mediaTypes))
for _, mt := range mediaTypes {
switch mt {
case "application/octet-stream":
result["application/octet-stream"] = o.BinProducer
case "application/json":
result["application/json"] = o.JSONProducer
}
if p, ok := o.customProducers[mt]; ok {
result[mt] = p
}
}
return result
}
// HandlerFor gets a http.Handler for the provided operation method and path
func (o *ConsoleAPI) HandlerFor(method, path string) (http.Handler, bool) {
if o.handlers == nil {
return nil, false
}
um := strings.ToUpper(method)
if _, ok := o.handlers[um]; !ok {
return nil, false
}
if path == "/" {
path = ""
}
h, ok := o.handlers[um][path]
return h, ok
}
// Context returns the middleware context for the console API
func (o *ConsoleAPI) Context() *middleware.Context {
if o.context == nil {
o.context = middleware.NewRoutableContext(o.spec, o, nil)
}
return o.context
}
func (o *ConsoleAPI) initHandlerCache() {
o.Context() // don't care about the result, just that the initialization happened
if o.handlers == nil {
o.handlers = make(map[string]map[string]http.Handler)
}
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/admin/info"] = system.NewAdminInfo(o.context, o.SystemAdminInfoHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{name}"] = bucket.NewBucketInfo(o.context, o.BucketBucketInfoHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/buckets/{bucket_name}/delete-objects"] = object.NewDeleteMultipleObjects(o.context, o.ObjectDeleteMultipleObjectsHandler)
if o.handlers["DELETE"] == nil {
o.handlers["DELETE"] = make(map[string]http.Handler)
}
o.handlers["DELETE"]["/buckets/{bucket_name}/objects"] = object.NewDeleteObject(o.context, o.ObjectDeleteObjectHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{bucket_name}/objects/download"] = object.NewDownloadObject(o.context, o.ObjectDownloadObjectHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/buckets/{bucket_name}/objects/download-multiple"] = object.NewDownloadMultipleObjects(o.context, o.ObjectDownloadMultipleObjectsHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/download-shared-object/{url}"] = public.NewDownloadSharedObject(o.context, o.PublicDownloadSharedObjectHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{name}/quota"] = bucket.NewGetBucketQuota(o.context, o.BucketGetBucketQuotaHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{bucket_name}/rewind/{date}"] = bucket.NewGetBucketRewind(o.context, o.BucketGetBucketRewindHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{bucket_name}/versioning"] = bucket.NewGetBucketVersioning(o.context, o.BucketGetBucketVersioningHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/max-share-exp"] = bucket.NewGetMaxShareLinkExp(o.context, o.BucketGetMaxShareLinkExpHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{bucket_name}/objects/metadata"] = object.NewGetObjectMetadata(o.context, o.ObjectGetObjectMetadataHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/license/acknowledge"] = license.NewLicenseAcknowledge(o.context, o.LicenseLicenseAcknowledgeHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets"] = bucket.NewListBuckets(o.context, o.BucketListBucketsHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{bucket_name}/objects"] = object.NewListObjects(o.context, o.ObjectListObjectsHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/users"] = user.NewListUsers(o.context, o.UserListUsersHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/login"] = auth.NewLogin(o.context, o.AuthLoginHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/login"] = auth.NewLoginDetail(o.context, o.AuthLoginDetailHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/login/oauth2/auth"] = auth.NewLoginOauth2Auth(o.context, o.AuthLoginOauth2AuthHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/logout"] = auth.NewLogout(o.context, o.AuthLogoutHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/buckets"] = bucket.NewMakeBucket(o.context, o.BucketMakeBucketHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/buckets/{bucket_name}/objects/upload"] = object.NewPostBucketsBucketNameObjectsUpload(o.context, o.ObjectPostBucketsBucketNameObjectsUploadHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/buckets/{bucket_name}/tags"] = bucket.NewPutBucketTags(o.context, o.BucketPutBucketTagsHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/buckets/{bucket_name}/objects/restore"] = object.NewPutObjectRestore(o.context, o.ObjectPutObjectRestoreHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/buckets/{bucket_name}/objects/tags"] = object.NewPutObjectTags(o.context, o.ObjectPutObjectTagsHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/session"] = auth.NewSessionCheck(o.context, o.AuthSessionCheckHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/buckets/{bucket_name}/versioning"] = bucket.NewSetBucketVersioning(o.context, o.BucketSetBucketVersioningHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{bucket_name}/objects/share"] = object.NewShareObject(o.context, o.ObjectShareObjectHandler)
}
// Serve creates a http handler to serve the API over HTTP
// can be used directly in http.ListenAndServe(":8000", api.Serve(nil))
func (o *ConsoleAPI) Serve(builder middleware.Builder) http.Handler {
o.Init()
if o.Middleware != nil {
return o.Middleware(builder)
}
if o.useSwaggerUI {
return o.context.APIHandlerSwaggerUI(builder)
}
return o.context.APIHandler(builder)
}
// Init allows you to just initialize the handler cache, you can then recompose the middleware as you see fit
func (o *ConsoleAPI) Init() {
if len(o.handlers) == 0 {
o.initHandlerCache()
}
}
// RegisterConsumer allows you to add (or override) a consumer for a media type.
func (o *ConsoleAPI) RegisterConsumer(mediaType string, consumer runtime.Consumer) {
o.customConsumers[mediaType] = consumer
}
// RegisterProducer allows you to add (or override) a producer for a media type.
func (o *ConsoleAPI) RegisterProducer(mediaType string, producer runtime.Producer) {
o.customProducers[mediaType] = producer
}
// AddMiddlewareFor adds a http middleware to existing handler
func (o *ConsoleAPI) AddMiddlewareFor(method, path string, builder middleware.Builder) {
um := strings.ToUpper(method)
if path == "/" {
path = ""
}
o.Init()
if h, ok := o.handlers[um][path]; ok {
o.handlers[um][path] = builder(h)
}
}

View File

@@ -1,115 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 license
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// LicenseAcknowledgeOKCode is the HTTP code returned for type LicenseAcknowledgeOK
const LicenseAcknowledgeOKCode int = 200
/*
LicenseAcknowledgeOK A successful response.
swagger:response licenseAcknowledgeOK
*/
type LicenseAcknowledgeOK struct {
}
// NewLicenseAcknowledgeOK creates LicenseAcknowledgeOK with default headers values
func NewLicenseAcknowledgeOK() *LicenseAcknowledgeOK {
return &LicenseAcknowledgeOK{}
}
// WriteResponse to the client
func (o *LicenseAcknowledgeOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(200)
}
/*
LicenseAcknowledgeDefault Generic error response.
swagger:response licenseAcknowledgeDefault
*/
type LicenseAcknowledgeDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewLicenseAcknowledgeDefault creates LicenseAcknowledgeDefault with default headers values
func NewLicenseAcknowledgeDefault(code int) *LicenseAcknowledgeDefault {
if code <= 0 {
code = 500
}
return &LicenseAcknowledgeDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the license acknowledge default response
func (o *LicenseAcknowledgeDefault) WithStatusCode(code int) *LicenseAcknowledgeDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the license acknowledge default response
func (o *LicenseAcknowledgeDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the license acknowledge default response
func (o *LicenseAcknowledgeDefault) WithPayload(payload *models.APIError) *LicenseAcknowledgeDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the license acknowledge default response
func (o *LicenseAcknowledgeDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LicenseAcknowledgeDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,194 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
)
// NewDeleteMultipleObjectsParams creates a new DeleteMultipleObjectsParams object
//
// There are no default values defined in the spec.
func NewDeleteMultipleObjectsParams() DeleteMultipleObjectsParams {
return DeleteMultipleObjectsParams{}
}
// DeleteMultipleObjectsParams contains all the bound params for the delete multiple objects operation
// typically these are obtained from a http.Request
//
// swagger:parameters DeleteMultipleObjects
type DeleteMultipleObjectsParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
In: query
*/
AllVersions *bool
/*
Required: true
In: path
*/
BucketName string
/*
In: query
*/
Bypass *bool
/*
Required: true
In: body
*/
Files []*models.DeleteFile
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewDeleteMultipleObjectsParams() beforehand.
func (o *DeleteMultipleObjectsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
qAllVersions, qhkAllVersions, _ := qs.GetOK("all_versions")
if err := o.bindAllVersions(qAllVersions, qhkAllVersions, route.Formats); err != nil {
res = append(res, err)
}
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
qBypass, qhkBypass, _ := qs.GetOK("bypass")
if err := o.bindBypass(qBypass, qhkBypass, route.Formats); err != nil {
res = append(res, err)
}
if runtime.HasBody(r) {
defer r.Body.Close()
var body []*models.DeleteFile
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("files", "body", ""))
} else {
res = append(res, errors.NewParseError("files", "body", "", err))
}
} else {
// validate array of body objects
for i := range body {
if body[i] == nil {
continue
}
if err := body[i].Validate(route.Formats); err != nil {
res = append(res, err)
break
}
}
if len(res) == 0 {
o.Files = body
}
}
} else {
res = append(res, errors.Required("files", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindAllVersions binds and validates parameter AllVersions from query.
func (o *DeleteMultipleObjectsParams) bindAllVersions(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("all_versions", "query", "bool", raw)
}
o.AllVersions = &value
return nil
}
// bindBucketName binds and validates parameter BucketName from path.
func (o *DeleteMultipleObjectsParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.BucketName = raw
return nil
}
// bindBypass binds and validates parameter Bypass from query.
func (o *DeleteMultipleObjectsParams) bindBypass(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("bypass", "query", "bool", raw)
}
o.Bypass = &value
return nil
}

View File

@@ -1,141 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
"github.com/go-openapi/swag"
)
// DeleteMultipleObjectsURL generates an URL for the delete multiple objects operation
type DeleteMultipleObjectsURL struct {
BucketName string
AllVersions *bool
Bypass *bool
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *DeleteMultipleObjectsURL) WithBasePath(bp string) *DeleteMultipleObjectsURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *DeleteMultipleObjectsURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *DeleteMultipleObjectsURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{bucket_name}/delete-objects"
bucketName := o.BucketName
if bucketName != "" {
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
} else {
return nil, errors.New("bucketName is required on DeleteMultipleObjectsURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
var allVersionsQ string
if o.AllVersions != nil {
allVersionsQ = swag.FormatBool(*o.AllVersions)
}
if allVersionsQ != "" {
qs.Set("all_versions", allVersionsQ)
}
var bypassQ string
if o.Bypass != nil {
bypassQ = swag.FormatBool(*o.Bypass)
}
if bypassQ != "" {
qs.Set("bypass", bypassQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *DeleteMultipleObjectsURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *DeleteMultipleObjectsURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *DeleteMultipleObjectsURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on DeleteMultipleObjectsURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on DeleteMultipleObjectsURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *DeleteMultipleObjectsURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,279 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// NewDeleteObjectParams creates a new DeleteObjectParams object
//
// There are no default values defined in the spec.
func NewDeleteObjectParams() DeleteObjectParams {
return DeleteObjectParams{}
}
// DeleteObjectParams contains all the bound params for the delete object operation
// typically these are obtained from a http.Request
//
// swagger:parameters DeleteObject
type DeleteObjectParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
In: query
*/
AllVersions *bool
/*
Required: true
In: path
*/
BucketName string
/*
In: query
*/
Bypass *bool
/*
In: query
*/
NonCurrentVersions *bool
/*
Required: true
In: query
*/
Prefix string
/*
In: query
*/
Recursive *bool
/*
In: query
*/
VersionID *string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewDeleteObjectParams() beforehand.
func (o *DeleteObjectParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
qAllVersions, qhkAllVersions, _ := qs.GetOK("all_versions")
if err := o.bindAllVersions(qAllVersions, qhkAllVersions, route.Formats); err != nil {
res = append(res, err)
}
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
qBypass, qhkBypass, _ := qs.GetOK("bypass")
if err := o.bindBypass(qBypass, qhkBypass, route.Formats); err != nil {
res = append(res, err)
}
qNonCurrentVersions, qhkNonCurrentVersions, _ := qs.GetOK("non_current_versions")
if err := o.bindNonCurrentVersions(qNonCurrentVersions, qhkNonCurrentVersions, route.Formats); err != nil {
res = append(res, err)
}
qPrefix, qhkPrefix, _ := qs.GetOK("prefix")
if err := o.bindPrefix(qPrefix, qhkPrefix, route.Formats); err != nil {
res = append(res, err)
}
qRecursive, qhkRecursive, _ := qs.GetOK("recursive")
if err := o.bindRecursive(qRecursive, qhkRecursive, route.Formats); err != nil {
res = append(res, err)
}
qVersionID, qhkVersionID, _ := qs.GetOK("version_id")
if err := o.bindVersionID(qVersionID, qhkVersionID, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindAllVersions binds and validates parameter AllVersions from query.
func (o *DeleteObjectParams) bindAllVersions(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("all_versions", "query", "bool", raw)
}
o.AllVersions = &value
return nil
}
// bindBucketName binds and validates parameter BucketName from path.
func (o *DeleteObjectParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.BucketName = raw
return nil
}
// bindBypass binds and validates parameter Bypass from query.
func (o *DeleteObjectParams) bindBypass(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("bypass", "query", "bool", raw)
}
o.Bypass = &value
return nil
}
// bindNonCurrentVersions binds and validates parameter NonCurrentVersions from query.
func (o *DeleteObjectParams) bindNonCurrentVersions(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("non_current_versions", "query", "bool", raw)
}
o.NonCurrentVersions = &value
return nil
}
// bindPrefix binds and validates parameter Prefix from query.
func (o *DeleteObjectParams) bindPrefix(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("prefix", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("prefix", "query", raw); err != nil {
return err
}
o.Prefix = raw
return nil
}
// bindRecursive binds and validates parameter Recursive from query.
func (o *DeleteObjectParams) bindRecursive(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("recursive", "query", "bool", raw)
}
o.Recursive = &value
return nil
}
// bindVersionID binds and validates parameter VersionID from query.
func (o *DeleteObjectParams) bindVersionID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
o.VersionID = &raw
return nil
}

View File

@@ -1,174 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
"github.com/go-openapi/swag"
)
// DeleteObjectURL generates an URL for the delete object operation
type DeleteObjectURL struct {
BucketName string
AllVersions *bool
Bypass *bool
NonCurrentVersions *bool
Prefix string
Recursive *bool
VersionID *string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *DeleteObjectURL) WithBasePath(bp string) *DeleteObjectURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *DeleteObjectURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *DeleteObjectURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{bucket_name}/objects"
bucketName := o.BucketName
if bucketName != "" {
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
} else {
return nil, errors.New("bucketName is required on DeleteObjectURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
var allVersionsQ string
if o.AllVersions != nil {
allVersionsQ = swag.FormatBool(*o.AllVersions)
}
if allVersionsQ != "" {
qs.Set("all_versions", allVersionsQ)
}
var bypassQ string
if o.Bypass != nil {
bypassQ = swag.FormatBool(*o.Bypass)
}
if bypassQ != "" {
qs.Set("bypass", bypassQ)
}
var nonCurrentVersionsQ string
if o.NonCurrentVersions != nil {
nonCurrentVersionsQ = swag.FormatBool(*o.NonCurrentVersions)
}
if nonCurrentVersionsQ != "" {
qs.Set("non_current_versions", nonCurrentVersionsQ)
}
prefixQ := o.Prefix
if prefixQ != "" {
qs.Set("prefix", prefixQ)
}
var recursiveQ string
if o.Recursive != nil {
recursiveQ = swag.FormatBool(*o.Recursive)
}
if recursiveQ != "" {
qs.Set("recursive", recursiveQ)
}
var versionIDQ string
if o.VersionID != nil {
versionIDQ = *o.VersionID
}
if versionIDQ != "" {
qs.Set("version_id", versionIDQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *DeleteObjectURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *DeleteObjectURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *DeleteObjectURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on DeleteObjectURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on DeleteObjectURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *DeleteObjectURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,134 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// DownloadMultipleObjectsOKCode is the HTTP code returned for type DownloadMultipleObjectsOK
const DownloadMultipleObjectsOKCode int = 200
/*
DownloadMultipleObjectsOK A successful response.
swagger:response downloadMultipleObjectsOK
*/
type DownloadMultipleObjectsOK struct {
/*
In: Body
*/
Payload io.ReadCloser `json:"body,omitempty"`
}
// NewDownloadMultipleObjectsOK creates DownloadMultipleObjectsOK with default headers values
func NewDownloadMultipleObjectsOK() *DownloadMultipleObjectsOK {
return &DownloadMultipleObjectsOK{}
}
// WithPayload adds the payload to the download multiple objects o k response
func (o *DownloadMultipleObjectsOK) WithPayload(payload io.ReadCloser) *DownloadMultipleObjectsOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the download multiple objects o k response
func (o *DownloadMultipleObjectsOK) SetPayload(payload io.ReadCloser) {
o.Payload = payload
}
// WriteResponse to the client
func (o *DownloadMultipleObjectsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
/*
DownloadMultipleObjectsDefault Generic error response.
swagger:response downloadMultipleObjectsDefault
*/
type DownloadMultipleObjectsDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewDownloadMultipleObjectsDefault creates DownloadMultipleObjectsDefault with default headers values
func NewDownloadMultipleObjectsDefault(code int) *DownloadMultipleObjectsDefault {
if code <= 0 {
code = 500
}
return &DownloadMultipleObjectsDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the download multiple objects default response
func (o *DownloadMultipleObjectsDefault) WithStatusCode(code int) *DownloadMultipleObjectsDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the download multiple objects default response
func (o *DownloadMultipleObjectsDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the download multiple objects default response
func (o *DownloadMultipleObjectsDefault) WithPayload(payload *models.APIError) *DownloadMultipleObjectsDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the download multiple objects default response
func (o *DownloadMultipleObjectsDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *DownloadMultipleObjectsDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,225 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// NewDownloadObjectParams creates a new DownloadObjectParams object
// with the default values initialized.
func NewDownloadObjectParams() DownloadObjectParams {
var (
// initialize parameters with default values
overrideFileNameDefault = string("")
previewDefault = bool(false)
)
return DownloadObjectParams{
OverrideFileName: &overrideFileNameDefault,
Preview: &previewDefault,
}
}
// DownloadObjectParams contains all the bound params for the download object operation
// typically these are obtained from a http.Request
//
// swagger:parameters Download Object
type DownloadObjectParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: path
*/
BucketName string
/*
In: query
Default: ""
*/
OverrideFileName *string
/*
Required: true
In: query
*/
Prefix string
/*
In: query
Default: false
*/
Preview *bool
/*
In: query
*/
VersionID *string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewDownloadObjectParams() beforehand.
func (o *DownloadObjectParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
qOverrideFileName, qhkOverrideFileName, _ := qs.GetOK("override_file_name")
if err := o.bindOverrideFileName(qOverrideFileName, qhkOverrideFileName, route.Formats); err != nil {
res = append(res, err)
}
qPrefix, qhkPrefix, _ := qs.GetOK("prefix")
if err := o.bindPrefix(qPrefix, qhkPrefix, route.Formats); err != nil {
res = append(res, err)
}
qPreview, qhkPreview, _ := qs.GetOK("preview")
if err := o.bindPreview(qPreview, qhkPreview, route.Formats); err != nil {
res = append(res, err)
}
qVersionID, qhkVersionID, _ := qs.GetOK("version_id")
if err := o.bindVersionID(qVersionID, qhkVersionID, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindBucketName binds and validates parameter BucketName from path.
func (o *DownloadObjectParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.BucketName = raw
return nil
}
// bindOverrideFileName binds and validates parameter OverrideFileName from query.
func (o *DownloadObjectParams) bindOverrideFileName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewDownloadObjectParams()
return nil
}
o.OverrideFileName = &raw
return nil
}
// bindPrefix binds and validates parameter Prefix from query.
func (o *DownloadObjectParams) bindPrefix(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("prefix", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("prefix", "query", raw); err != nil {
return err
}
o.Prefix = raw
return nil
}
// bindPreview binds and validates parameter Preview from query.
func (o *DownloadObjectParams) bindPreview(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewDownloadObjectParams()
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("preview", "query", "bool", raw)
}
o.Preview = &value
return nil
}
// bindVersionID binds and validates parameter VersionID from query.
func (o *DownloadObjectParams) bindVersionID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
o.VersionID = &raw
return nil
}

View File

@@ -1,134 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// DownloadObjectOKCode is the HTTP code returned for type DownloadObjectOK
const DownloadObjectOKCode int = 200
/*
DownloadObjectOK A successful response.
swagger:response downloadObjectOK
*/
type DownloadObjectOK struct {
/*
In: Body
*/
Payload io.ReadCloser `json:"body,omitempty"`
}
// NewDownloadObjectOK creates DownloadObjectOK with default headers values
func NewDownloadObjectOK() *DownloadObjectOK {
return &DownloadObjectOK{}
}
// WithPayload adds the payload to the download object o k response
func (o *DownloadObjectOK) WithPayload(payload io.ReadCloser) *DownloadObjectOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the download object o k response
func (o *DownloadObjectOK) SetPayload(payload io.ReadCloser) {
o.Payload = payload
}
// WriteResponse to the client
func (o *DownloadObjectOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
/*
DownloadObjectDefault Generic error response.
swagger:response downloadObjectDefault
*/
type DownloadObjectDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewDownloadObjectDefault creates DownloadObjectDefault with default headers values
func NewDownloadObjectDefault(code int) *DownloadObjectDefault {
if code <= 0 {
code = 500
}
return &DownloadObjectDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the download object default response
func (o *DownloadObjectDefault) WithStatusCode(code int) *DownloadObjectDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the download object default response
func (o *DownloadObjectDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the download object default response
func (o *DownloadObjectDefault) WithPayload(payload *models.APIError) *DownloadObjectDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the download object default response
func (o *DownloadObjectDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *DownloadObjectDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,156 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
"github.com/go-openapi/swag"
)
// DownloadObjectURL generates an URL for the download object operation
type DownloadObjectURL struct {
BucketName string
OverrideFileName *string
Prefix string
Preview *bool
VersionID *string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *DownloadObjectURL) WithBasePath(bp string) *DownloadObjectURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *DownloadObjectURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *DownloadObjectURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{bucket_name}/objects/download"
bucketName := o.BucketName
if bucketName != "" {
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
} else {
return nil, errors.New("bucketName is required on DownloadObjectURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
var overrideFileNameQ string
if o.OverrideFileName != nil {
overrideFileNameQ = *o.OverrideFileName
}
if overrideFileNameQ != "" {
qs.Set("override_file_name", overrideFileNameQ)
}
prefixQ := o.Prefix
if prefixQ != "" {
qs.Set("prefix", prefixQ)
}
var previewQ string
if o.Preview != nil {
previewQ = swag.FormatBool(*o.Preview)
}
if previewQ != "" {
qs.Set("preview", previewQ)
}
var versionIDQ string
if o.VersionID != nil {
versionIDQ = *o.VersionID
}
if versionIDQ != "" {
qs.Set("version_id", versionIDQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *DownloadObjectURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *DownloadObjectURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *DownloadObjectURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on DownloadObjectURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on DownloadObjectURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *DownloadObjectURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,135 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// GetObjectMetadataOKCode is the HTTP code returned for type GetObjectMetadataOK
const GetObjectMetadataOKCode int = 200
/*
GetObjectMetadataOK A successful response.
swagger:response getObjectMetadataOK
*/
type GetObjectMetadataOK struct {
/*
In: Body
*/
Payload *models.Metadata `json:"body,omitempty"`
}
// NewGetObjectMetadataOK creates GetObjectMetadataOK with default headers values
func NewGetObjectMetadataOK() *GetObjectMetadataOK {
return &GetObjectMetadataOK{}
}
// WithPayload adds the payload to the get object metadata o k response
func (o *GetObjectMetadataOK) WithPayload(payload *models.Metadata) *GetObjectMetadataOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get object metadata o k response
func (o *GetObjectMetadataOK) SetPayload(payload *models.Metadata) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetObjectMetadataOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*
GetObjectMetadataDefault Generic error response.
swagger:response getObjectMetadataDefault
*/
type GetObjectMetadataDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewGetObjectMetadataDefault creates GetObjectMetadataDefault with default headers values
func NewGetObjectMetadataDefault(code int) *GetObjectMetadataDefault {
if code <= 0 {
code = 500
}
return &GetObjectMetadataDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the get object metadata default response
func (o *GetObjectMetadataDefault) WithStatusCode(code int) *GetObjectMetadataDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the get object metadata default response
func (o *GetObjectMetadataDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the get object metadata default response
func (o *GetObjectMetadataDefault) WithPayload(payload *models.APIError) *GetObjectMetadataDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get object metadata default response
func (o *GetObjectMetadataDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetObjectMetadataDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,256 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// NewListObjectsParams creates a new ListObjectsParams object
// with the default values initialized.
func NewListObjectsParams() ListObjectsParams {
var (
// initialize parameters with default values
limitDefault = int32(20)
)
return ListObjectsParams{
Limit: &limitDefault,
}
}
// ListObjectsParams contains all the bound params for the list objects operation
// typically these are obtained from a http.Request
//
// swagger:parameters ListObjects
type ListObjectsParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: path
*/
BucketName string
/*
In: query
Default: 20
*/
Limit *int32
/*
In: query
*/
Prefix *string
/*
In: query
*/
Recursive *bool
/*
In: query
*/
WithMetadata *bool
/*
In: query
*/
WithVersions *bool
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewListObjectsParams() beforehand.
func (o *ListObjectsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
qLimit, qhkLimit, _ := qs.GetOK("limit")
if err := o.bindLimit(qLimit, qhkLimit, route.Formats); err != nil {
res = append(res, err)
}
qPrefix, qhkPrefix, _ := qs.GetOK("prefix")
if err := o.bindPrefix(qPrefix, qhkPrefix, route.Formats); err != nil {
res = append(res, err)
}
qRecursive, qhkRecursive, _ := qs.GetOK("recursive")
if err := o.bindRecursive(qRecursive, qhkRecursive, route.Formats); err != nil {
res = append(res, err)
}
qWithMetadata, qhkWithMetadata, _ := qs.GetOK("with_metadata")
if err := o.bindWithMetadata(qWithMetadata, qhkWithMetadata, route.Formats); err != nil {
res = append(res, err)
}
qWithVersions, qhkWithVersions, _ := qs.GetOK("with_versions")
if err := o.bindWithVersions(qWithVersions, qhkWithVersions, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindBucketName binds and validates parameter BucketName from path.
func (o *ListObjectsParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.BucketName = raw
return nil
}
// bindLimit binds and validates parameter Limit from query.
func (o *ListObjectsParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewListObjectsParams()
return nil
}
value, err := swag.ConvertInt32(raw)
if err != nil {
return errors.InvalidType("limit", "query", "int32", raw)
}
o.Limit = &value
return nil
}
// bindPrefix binds and validates parameter Prefix from query.
func (o *ListObjectsParams) bindPrefix(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
o.Prefix = &raw
return nil
}
// bindRecursive binds and validates parameter Recursive from query.
func (o *ListObjectsParams) bindRecursive(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("recursive", "query", "bool", raw)
}
o.Recursive = &value
return nil
}
// bindWithMetadata binds and validates parameter WithMetadata from query.
func (o *ListObjectsParams) bindWithMetadata(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("with_metadata", "query", "bool", raw)
}
o.WithMetadata = &value
return nil
}
// bindWithVersions binds and validates parameter WithVersions from query.
func (o *ListObjectsParams) bindWithVersions(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("with_versions", "query", "bool", raw)
}
o.WithVersions = &value
return nil
}

View File

@@ -1,88 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// PostBucketsBucketNameObjectsUploadHandlerFunc turns a function with the right signature into a post buckets bucket name objects upload handler
type PostBucketsBucketNameObjectsUploadHandlerFunc func(PostBucketsBucketNameObjectsUploadParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn PostBucketsBucketNameObjectsUploadHandlerFunc) Handle(params PostBucketsBucketNameObjectsUploadParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// PostBucketsBucketNameObjectsUploadHandler interface for that can handle valid post buckets bucket name objects upload params
type PostBucketsBucketNameObjectsUploadHandler interface {
Handle(PostBucketsBucketNameObjectsUploadParams, *models.Principal) middleware.Responder
}
// NewPostBucketsBucketNameObjectsUpload creates a new http.Handler for the post buckets bucket name objects upload operation
func NewPostBucketsBucketNameObjectsUpload(ctx *middleware.Context, handler PostBucketsBucketNameObjectsUploadHandler) *PostBucketsBucketNameObjectsUpload {
return &PostBucketsBucketNameObjectsUpload{Context: ctx, Handler: handler}
}
/*
PostBucketsBucketNameObjectsUpload swagger:route POST /buckets/{bucket_name}/objects/upload Object postBucketsBucketNameObjectsUpload
Uploads an Object.
*/
type PostBucketsBucketNameObjectsUpload struct {
Context *middleware.Context
Handler PostBucketsBucketNameObjectsUploadHandler
}
func (o *PostBucketsBucketNameObjectsUpload) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewPostBucketsBucketNameObjectsUploadParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,115 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// PostBucketsBucketNameObjectsUploadOKCode is the HTTP code returned for type PostBucketsBucketNameObjectsUploadOK
const PostBucketsBucketNameObjectsUploadOKCode int = 200
/*
PostBucketsBucketNameObjectsUploadOK A successful response.
swagger:response postBucketsBucketNameObjectsUploadOK
*/
type PostBucketsBucketNameObjectsUploadOK struct {
}
// NewPostBucketsBucketNameObjectsUploadOK creates PostBucketsBucketNameObjectsUploadOK with default headers values
func NewPostBucketsBucketNameObjectsUploadOK() *PostBucketsBucketNameObjectsUploadOK {
return &PostBucketsBucketNameObjectsUploadOK{}
}
// WriteResponse to the client
func (o *PostBucketsBucketNameObjectsUploadOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(200)
}
/*
PostBucketsBucketNameObjectsUploadDefault Generic error response.
swagger:response postBucketsBucketNameObjectsUploadDefault
*/
type PostBucketsBucketNameObjectsUploadDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewPostBucketsBucketNameObjectsUploadDefault creates PostBucketsBucketNameObjectsUploadDefault with default headers values
func NewPostBucketsBucketNameObjectsUploadDefault(code int) *PostBucketsBucketNameObjectsUploadDefault {
if code <= 0 {
code = 500
}
return &PostBucketsBucketNameObjectsUploadDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the post buckets bucket name objects upload default response
func (o *PostBucketsBucketNameObjectsUploadDefault) WithStatusCode(code int) *PostBucketsBucketNameObjectsUploadDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the post buckets bucket name objects upload default response
func (o *PostBucketsBucketNameObjectsUploadDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the post buckets bucket name objects upload default response
func (o *PostBucketsBucketNameObjectsUploadDefault) WithPayload(payload *models.APIError) *PostBucketsBucketNameObjectsUploadDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the post buckets bucket name objects upload default response
func (o *PostBucketsBucketNameObjectsUploadDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PostBucketsBucketNameObjectsUploadDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,130 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// PostBucketsBucketNameObjectsUploadURL generates an URL for the post buckets bucket name objects upload operation
type PostBucketsBucketNameObjectsUploadURL struct {
BucketName string
Prefix *string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *PostBucketsBucketNameObjectsUploadURL) WithBasePath(bp string) *PostBucketsBucketNameObjectsUploadURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *PostBucketsBucketNameObjectsUploadURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *PostBucketsBucketNameObjectsUploadURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{bucket_name}/objects/upload"
bucketName := o.BucketName
if bucketName != "" {
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
} else {
return nil, errors.New("bucketName is required on PostBucketsBucketNameObjectsUploadURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
var prefixQ string
if o.Prefix != nil {
prefixQ = *o.Prefix
}
if prefixQ != "" {
qs.Set("prefix", prefixQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *PostBucketsBucketNameObjectsUploadURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *PostBucketsBucketNameObjectsUploadURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *PostBucketsBucketNameObjectsUploadURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on PostBucketsBucketNameObjectsUploadURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on PostBucketsBucketNameObjectsUploadURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *PostBucketsBucketNameObjectsUploadURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,154 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// NewPutObjectRestoreParams creates a new PutObjectRestoreParams object
//
// There are no default values defined in the spec.
func NewPutObjectRestoreParams() PutObjectRestoreParams {
return PutObjectRestoreParams{}
}
// PutObjectRestoreParams contains all the bound params for the put object restore operation
// typically these are obtained from a http.Request
//
// swagger:parameters PutObjectRestore
type PutObjectRestoreParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: path
*/
BucketName string
/*
Required: true
In: query
*/
Prefix string
/*
Required: true
In: query
*/
VersionID string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewPutObjectRestoreParams() beforehand.
func (o *PutObjectRestoreParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
qPrefix, qhkPrefix, _ := qs.GetOK("prefix")
if err := o.bindPrefix(qPrefix, qhkPrefix, route.Formats); err != nil {
res = append(res, err)
}
qVersionID, qhkVersionID, _ := qs.GetOK("version_id")
if err := o.bindVersionID(qVersionID, qhkVersionID, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindBucketName binds and validates parameter BucketName from path.
func (o *PutObjectRestoreParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.BucketName = raw
return nil
}
// bindPrefix binds and validates parameter Prefix from query.
func (o *PutObjectRestoreParams) bindPrefix(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("prefix", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("prefix", "query", raw); err != nil {
return err
}
o.Prefix = raw
return nil
}
// bindVersionID binds and validates parameter VersionID from query.
func (o *PutObjectRestoreParams) bindVersionID(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("version_id", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("version_id", "query", raw); err != nil {
return err
}
o.VersionID = raw
return nil
}

View File

@@ -1,190 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewPutObjectTagsParams creates a new PutObjectTagsParams object
//
// There are no default values defined in the spec.
func NewPutObjectTagsParams() PutObjectTagsParams {
return PutObjectTagsParams{}
}
// PutObjectTagsParams contains all the bound params for the put object tags operation
// typically these are obtained from a http.Request
//
// swagger:parameters PutObjectTags
type PutObjectTagsParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.PutObjectTagsRequest
/*
Required: true
In: path
*/
BucketName string
/*
Required: true
In: query
*/
Prefix string
/*
Required: true
In: query
*/
VersionID string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewPutObjectTagsParams() beforehand.
func (o *PutObjectTagsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.PutObjectTagsRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(r.Context())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
qPrefix, qhkPrefix, _ := qs.GetOK("prefix")
if err := o.bindPrefix(qPrefix, qhkPrefix, route.Formats); err != nil {
res = append(res, err)
}
qVersionID, qhkVersionID, _ := qs.GetOK("version_id")
if err := o.bindVersionID(qVersionID, qhkVersionID, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindBucketName binds and validates parameter BucketName from path.
func (o *PutObjectTagsParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.BucketName = raw
return nil
}
// bindPrefix binds and validates parameter Prefix from query.
func (o *PutObjectTagsParams) bindPrefix(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("prefix", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("prefix", "query", raw); err != nil {
return err
}
o.Prefix = raw
return nil
}
// bindVersionID binds and validates parameter VersionID from query.
func (o *PutObjectTagsParams) bindVersionID(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("version_id", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("version_id", "query", raw); err != nil {
return err
}
o.VersionID = raw
return nil
}

View File

@@ -1,133 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// ShareObjectOKCode is the HTTP code returned for type ShareObjectOK
const ShareObjectOKCode int = 200
/*
ShareObjectOK A successful response.
swagger:response shareObjectOK
*/
type ShareObjectOK struct {
/*
In: Body
*/
Payload string `json:"body,omitempty"`
}
// NewShareObjectOK creates ShareObjectOK with default headers values
func NewShareObjectOK() *ShareObjectOK {
return &ShareObjectOK{}
}
// WithPayload adds the payload to the share object o k response
func (o *ShareObjectOK) WithPayload(payload string) *ShareObjectOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the share object o k response
func (o *ShareObjectOK) SetPayload(payload string) {
o.Payload = payload
}
// WriteResponse to the client
func (o *ShareObjectOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
/*
ShareObjectDefault Generic error response.
swagger:response shareObjectDefault
*/
type ShareObjectDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewShareObjectDefault creates ShareObjectDefault with default headers values
func NewShareObjectDefault(code int) *ShareObjectDefault {
if code <= 0 {
code = 500
}
return &ShareObjectDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the share object default response
func (o *ShareObjectDefault) WithStatusCode(code int) *ShareObjectDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the share object default response
func (o *ShareObjectDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the share object default response
func (o *ShareObjectDefault) WithPayload(payload *models.APIError) *ShareObjectDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the share object default response
func (o *ShareObjectDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *ShareObjectDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,142 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 object
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// ShareObjectURL generates an URL for the share object operation
type ShareObjectURL struct {
BucketName string
Expires *string
Prefix string
VersionID string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *ShareObjectURL) WithBasePath(bp string) *ShareObjectURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *ShareObjectURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *ShareObjectURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{bucket_name}/objects/share"
bucketName := o.BucketName
if bucketName != "" {
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
} else {
return nil, errors.New("bucketName is required on ShareObjectURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
var expiresQ string
if o.Expires != nil {
expiresQ = *o.Expires
}
if expiresQ != "" {
qs.Set("expires", expiresQ)
}
prefixQ := o.Prefix
if prefixQ != "" {
qs.Set("prefix", prefixQ)
}
versionIDQ := o.VersionID
if versionIDQ != "" {
qs.Set("version_id", versionIDQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *ShareObjectURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *ShareObjectURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *ShareObjectURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on ShareObjectURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on ShareObjectURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *ShareObjectURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,134 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 public
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// DownloadSharedObjectOKCode is the HTTP code returned for type DownloadSharedObjectOK
const DownloadSharedObjectOKCode int = 200
/*
DownloadSharedObjectOK A successful response.
swagger:response downloadSharedObjectOK
*/
type DownloadSharedObjectOK struct {
/*
In: Body
*/
Payload io.ReadCloser `json:"body,omitempty"`
}
// NewDownloadSharedObjectOK creates DownloadSharedObjectOK with default headers values
func NewDownloadSharedObjectOK() *DownloadSharedObjectOK {
return &DownloadSharedObjectOK{}
}
// WithPayload adds the payload to the download shared object o k response
func (o *DownloadSharedObjectOK) WithPayload(payload io.ReadCloser) *DownloadSharedObjectOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the download shared object o k response
func (o *DownloadSharedObjectOK) SetPayload(payload io.ReadCloser) {
o.Payload = payload
}
// WriteResponse to the client
func (o *DownloadSharedObjectOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
/*
DownloadSharedObjectDefault Generic error response.
swagger:response downloadSharedObjectDefault
*/
type DownloadSharedObjectDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewDownloadSharedObjectDefault creates DownloadSharedObjectDefault with default headers values
func NewDownloadSharedObjectDefault(code int) *DownloadSharedObjectDefault {
if code <= 0 {
code = 500
}
return &DownloadSharedObjectDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the download shared object default response
func (o *DownloadSharedObjectDefault) WithStatusCode(code int) *DownloadSharedObjectDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the download shared object default response
func (o *DownloadSharedObjectDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the download shared object default response
func (o *DownloadSharedObjectDefault) WithPayload(payload *models.APIError) *DownloadSharedObjectDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the download shared object default response
func (o *DownloadSharedObjectDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *DownloadSharedObjectDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,86 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// 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 policy
import (
"bytes"
"encoding/json"
"fmt"
"github.com/minio/madmin-go/v3"
)
// ReplacePolicyVariables replaces known variables from policies with known values
func ReplacePolicyVariables(claims map[string]interface{}, accountInfo *madmin.AccountInfo) json.RawMessage {
// AWS Variables
rawPolicy := bytes.ReplaceAll(accountInfo.Policy, []byte("${aws:username}"), []byte(accountInfo.AccountName))
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte("${aws:userid}"), []byte(accountInfo.AccountName))
// JWT Variables
rawPolicy = replaceJwtVariables(rawPolicy, claims)
// LDAP Variables
rawPolicy = replaceLDAPVariables(rawPolicy, claims)
return rawPolicy
}
func replaceJwtVariables(rawPolicy []byte, claims map[string]interface{}) json.RawMessage {
// list of valid JWT fields we will replace in policy if they are in the response
jwtFields := []string{
"sub",
"iss",
"aud",
"jti",
"upn",
"name",
"groups",
"given_name",
"family_name",
"middle_name",
"nickname",
"preferred_username",
"profile",
"picture",
"website",
"email",
"gender",
"birthdate",
"phone_number",
"address",
"scope",
"client_id",
}
// check which fields are in the claims and replace as variable by casting the value to string
for _, field := range jwtFields {
if val, ok := claims[field]; ok {
variable := fmt.Sprintf("${jwt:%s}", field)
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte(variable), []byte(fmt.Sprintf("%v", val)))
}
}
return rawPolicy
}
// ReplacePolicyVariables replaces known variables from policies with known values
func replaceLDAPVariables(rawPolicy []byte, claims map[string]interface{}) json.RawMessage {
// replace ${ldap:user}
if val, ok := claims["ldapUser"]; ok {
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte("${ldap:user}"), []byte(fmt.Sprintf("%v", val)))
}
// replace ${ldap:username}
if val, ok := claims["ldapUsername"]; ok {
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte("${ldap:username}"), []byte(fmt.Sprintf("%v", val)))
}
return rawPolicy
}

View File

@@ -1,112 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// 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 policy
import (
"bytes"
"reflect"
"testing"
"github.com/minio/madmin-go/v3"
minioIAMPolicy "github.com/minio/pkg/v3/policy"
)
func TestReplacePolicyVariables(t *testing.T) {
type args struct {
claims map[string]interface{}
accountInfo *madmin.AccountInfo
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "Bad Policy",
args: args{
claims: nil,
accountInfo: &madmin.AccountInfo{
AccountName: "test",
Server: madmin.BackendInfo{},
Policy: []byte(""),
Buckets: nil,
},
},
want: "",
wantErr: true,
},
{
name: "Replace basic AWS",
args: args{
claims: nil,
accountInfo: &madmin.AccountInfo{
AccountName: "test",
Server: madmin.BackendInfo{},
Policy: []byte(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::${aws:username}",
"arn:aws:s3:::${aws:userid}"
]
}
]
}`),
Buckets: nil,
},
},
want: `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::test",
"arn:aws:s3:::test"
]
}
]
}`,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
got := ReplacePolicyVariables(tt.args.claims, tt.args.accountInfo)
policy, err := minioIAMPolicy.ParseConfig(bytes.NewReader(got))
if (err != nil) != tt.wantErr {
t.Errorf("ReplacePolicyVariables() error = %v, wantErr %v", err, tt.wantErr)
}
wantPolicy, err := minioIAMPolicy.ParseConfig(bytes.NewReader([]byte(tt.want)))
if (err != nil) != tt.wantErr {
t.Errorf("ReplacePolicyVariables() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(policy, wantPolicy) {
t.Errorf("ReplacePolicyVariables() = %s, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,128 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2024 MinIO, Inc.
//
// 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 api
import (
"encoding/base64"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/api/operations"
"github.com/minio/console/api/operations/public"
xnet "github.com/minio/pkg/v3/net"
)
func registerPublicObjectsHandlers(api *operations.ConsoleAPI) {
api.PublicDownloadSharedObjectHandler = public.DownloadSharedObjectHandlerFunc(func(params public.DownloadSharedObjectParams) middleware.Responder {
resp, err := getDownloadPublicObjectResponse(params)
if err != nil {
return public.NewDownloadSharedObjectDefault(err.Code).WithPayload(err.APIError)
}
return resp
})
}
func getDownloadPublicObjectResponse(params public.DownloadSharedObjectParams) (middleware.Responder, *CodedAPIError) {
ctx := params.HTTPRequest.Context()
inputURLDecoded, err := decodeMinIOStringURL(params.URL)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
if inputURLDecoded == nil {
return nil, ErrorWithContext(ctx, ErrDefault, fmt.Errorf("decoded url is null"))
}
req, err := http.NewRequest(http.MethodGet, *inputURLDecoded, nil)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
clnt := PrepareConsoleHTTPClient(getClientIP(params.HTTPRequest))
resp, err := clnt.Do(req)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return middleware.ResponderFunc(func(rw http.ResponseWriter, _ runtime.Producer) {
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
http.Error(rw, resp.Status, resp.StatusCode)
return
}
urlObj, err := url.Parse(*inputURLDecoded)
if err != nil {
http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
return
}
// Add the filename
_, objectName := url2BucketAndObject(urlObj)
escapedName := url.PathEscape(objectName)
rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", escapedName))
_, err = io.Copy(rw, resp.Body)
if err != nil {
http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
return
}
}), nil
}
// decodeMinIOStringURL decodes url and validates is a MinIO url endpoint
func decodeMinIOStringURL(inputURL string) (*string, error) {
decodedURL, err := base64.RawURLEncoding.DecodeString(inputURL)
if err != nil {
return nil, err
}
// Validate input URL
parsedURL, err := xnet.ParseHTTPURL(string(decodedURL))
if err != nil {
return nil, err
}
// Ensure incoming url points to MinIO Server
minIOHost := getMinIOEndpoint()
if parsedURL.Host != minIOHost {
return nil, ErrForbidden
}
return swag.String(string(decodedURL)), nil
}
func url2BucketAndObject(u *url.URL) (bucketName, objectName string) {
tokens := splitStr(u.Path, "/", 3)
return tokens[1], tokens[2]
}
// splitStr splits a string into n parts, empty strings are added
// if we are not able to reach n elements
func splitStr(path, sep string, n int) []string {
splits := strings.SplitN(path, sep, n)
// Add empty strings if we found elements less than nr
for i := n - len(splits); i > 0; i-- {
splits = append(splits, "")
}
return splits
}

View File

@@ -1,111 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2024 MinIO, Inc.
//
// 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 api
import (
"testing"
"github.com/go-openapi/swag"
"github.com/stretchr/testify/assert"
)
func Test_decodeMinIOStringURL(t *testing.T) {
tAssert := assert.New(t)
type args struct {
encodedURL string
}
tests := []struct {
test string
args args
wantError *string
expected *string
}{
{
test: "valid encoded minIO URL returns decoded URL string", // http://localhost:9000/...
args: args{
encodedURL: "aHR0cDovL2xvY2FsaG9zdDo5MDAwL2J1Y2tldDEyMy9BdWRpbyUyMGljb24lMjgxJTI5LnN2Zz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPVVCTzFMMUM3VTg3UDFCUDI1MVRTJTJGMjAyNDA0MDUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNDA1VDIxMDEzM1omWC1BbXotRXhwaXJlcz00MzIwMCZYLUFtei1TZWN1cml0eS1Ub2tlbj1leUpoYkdjaU9pSklVelV4TWlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKaFkyTmxjM05MWlhraU9pSlZRazh4VERGRE4xVTROMUF4UWxBeU5URlVVeUlzSW1WNGNDSTZNVGN4TWpNNU5EQTRPU3dpY0dGeVpXNTBJam9pYldsdWFXOWhaRzFwYmlKOS5WLUtEZ3JMTVVCbG5KSEtYNlZ4SGw5LUFfLVBGRVdvazJkcFRxLTQ2YmxMbUxzdWVUeHNoVmFZNERad0dmb200VFQ1azhwaFVmZ2pjUWFuc25icmtlQSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmdmVyc2lvbklkPW51bGwmWC1BbXotU2lnbmF0dXJlPTA3Y2FkM2ViMmE2NzIyYjViYWVkMDljNmYxZmU0YTcwMWJmMTJmNDhlMTYyOGI5ZDQ1YzAxMWQ1OTU1Njc4NDU",
},
wantError: nil,
expected: swag.String("http://localhost:9000/bucket123/Audio%20icon%281%29.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=UBO1L1C7U87P1BP251TS%2F20240405%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240405T210133Z&X-Amz-Expires=43200&X-Amz-Security-Token=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJVQk8xTDFDN1U4N1AxQlAyNTFUUyIsImV4cCI6MTcxMjM5NDA4OSwicGFyZW50IjoibWluaW9hZG1pbiJ9.V-KDgrLMUBlnJHKX6VxHl9-A_-PFEWok2dpTq-46blLmLsueTxshVaY4DZwGfom4TT5k8phUfgjcQansnbrkeA&X-Amz-SignedHeaders=host&versionId=null&X-Amz-Signature=07cad3eb2a6722b5baed09c6f1fe4a701bf12f48e1628b9d45c011d595567845"),
},
{
test: "valid encoded url but not coming from MinIO server returns forbidden error", // http://non-minio-host:9000/...
args: args{
encodedURL: "aHR0cDovL25vbi1taW5pby1ob3N0OjkwMDAvYnVja2V0MTIzL0F1ZGlvJTIwaWNvbiUyODElMjkuc3ZnP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9VUJPMUwxQzdVODdQMUJQMjUxVFMlMkYyMDI0MDQwNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA0MDVUMjEwMTMzWiZYLUFtei1FeHBpcmVzPTQzMjAwJlgtQW16LVNlY3VyaXR5LVRva2VuPWV5SmhiR2NpT2lKSVV6VXhNaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpoWTJObGMzTkxaWGtpT2lKVlFrOHhUREZETjFVNE4xQXhRbEF5TlRGVVV5SXNJbVY0Y0NJNk1UY3hNak01TkRBNE9Td2ljR0Z5Wlc1MElqb2liV2x1YVc5aFpHMXBiaUo5LlYtS0RnckxNVUJsbkpIS1g2VnhIbDktQV8tUEZFV29rMmRwVHEtNDZibExtTHN1ZVR4c2hWYVk0RFp3R2ZvbTRUVDVrOHBoVWZnamNRYW5zbmJya2VBJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZ2ZXJzaW9uSWQ9bnVsbCZYLUFtei1TaWduYXR1cmU9MDdjYWQzZWIyYTY3MjJiNWJhZWQwOWM2ZjFmZTRhNzAxYmYxMmY0OGUxNjI4YjlkNDVjMDExZDU5NTU2Nzg0NQ",
},
wantError: swag.String("403 Forbidden"),
expected: nil,
},
{
test: "valid encoded url but not coming from MinIO server port returns forbidden error", // other port http://localhost:8902/...
args: args{
encodedURL: "aHR0cDovL2xvY2FsaG9zdDo4OTAyL2J1Y2tldDEyMy9BdWRpbyUyMGljb24lMjgxJTI5LnN2Zz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPVVCTzFMMUM3VTg3UDFCUDI1MVRTJTJGMjAyNDA0MDUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNDA1VDIxMDEzM1omWC1BbXotRXhwaXJlcz00MzIwMCZYLUFtei1TZWN1cml0eS1Ub2tlbj1leUpoYkdjaU9pSklVelV4TWlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKaFkyTmxjM05MWlhraU9pSlZRazh4VERGRE4xVTROMUF4UWxBeU5URlVVeUlzSW1WNGNDSTZNVGN4TWpNNU5EQTRPU3dpY0dGeVpXNTBJam9pYldsdWFXOWhaRzFwYmlKOS5WLUtEZ3JMTVVCbG5KSEtYNlZ4SGw5LUFfLVBGRVdvazJkcFRxLTQ2YmxMbUxzdWVUeHNoVmFZNERad0dmb200VFQ1azhwaFVmZ2pjUWFuc25icmtlQSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmdmVyc2lvbklkPW51bGwmWC1BbXotU2lnbmF0dXJlPTA3Y2FkM2ViMmE2NzIyYjViYWVkMDljNmYxZmU0YTcwMWJmMTJmNDhlMTYyOGI5ZDQ1YzAxMWQ1OTU1Njc4NDU",
},
wantError: swag.String("403 Forbidden"),
expected: nil,
},
{
test: "valid url but with invalid schema returns error",
args: args{
encodedURL: "cG9zdGdyZXM6Ly9wb3N0Z3JlczoxMjM0NTZAMTI3LjAuMC4xOjU0MzIvZHVtbXk", // postgres://postgres:123456@127.0.0.1:5432/dummy
},
wantError: swag.String("unexpected scheme found postgres"),
expected: nil,
},
{
test: "invalid url returns error",
args: args{
encodedURL: "YXNkc2Fkc2Rh", // asdsadsda
},
wantError: swag.String("unexpected scheme found "),
expected: nil,
},
{
test: "plain url",
args: args{
encodedURL: "aHR0cHM6Ly9sb2NhbGhvc3Q6OTAwMC9jZXN0ZXN0L0F1ZGlvJTIwaWNvbi5zdmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTY",
},
wantError: nil,
expected: swag.String("https://localhost:9000/cestest/Audio%20icon.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256"),
},
}
for _, tt := range tests {
t.Run(tt.test, func(_ *testing.T) {
url, err := decodeMinIOStringURL(tt.args.encodedURL)
if tt.wantError != nil {
if err != nil {
if err.Error() != *tt.wantError {
t.Errorf("decodeMinIOStringURL() error: `%v`, wantErr: `%s`", err, *tt.wantError)
return
}
} else {
t.Errorf("decodeMinIOStringURL() error: `%v`, wantErr: `%s`", err, *tt.wantError)
return
}
} else {
if err != nil {
t.Errorf("decodeMinIOStringURL() error: `%s`, wantErr: `%v`", err, tt.wantError)
return
}
tAssert.Equal(*tt.expected, *url)
}
})
}
}

View File

@@ -1,51 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"net/http"
)
type ConsoleTransport struct {
Transport http.RoundTripper
ClientIP string
}
func (t *ConsoleTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.ClientIP != "" {
// Do not set an empty x-forwarded-for
req.Header.Add(xForwardedFor, t.ClientIP)
}
return t.Transport.RoundTrip(req)
}
// PrepareSTSClientTransport :
func PrepareSTSClientTransport(clientIP string) *ConsoleTransport {
return &ConsoleTransport{
Transport: GlobalTransport,
ClientIP: clientIP,
}
}
// PrepareConsoleHTTPClient returns an http.Client with custom configurations need it by *credentials.STSAssumeRole
// custom configurations include the use of CA certificates
func PrepareConsoleHTTPClient(clientIP string) *http.Client {
// Return http client with default configuration
return &http.Client{
Transport: PrepareSTSClientTransport(clientIP),
}
}

View File

@@ -1,67 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"context"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
bucektApi "github.com/minio/console/api/operations/bucket"
"github.com/minio/console/models"
)
func registerBucketQuotaHandlers(api *operations.ConsoleAPI) {
// get bucket quota
api.BucketGetBucketQuotaHandler = bucektApi.GetBucketQuotaHandlerFunc(func(params bucektApi.GetBucketQuotaParams, session *models.Principal) middleware.Responder {
resp, err := getBucketQuotaResponse(session, params)
if err != nil {
return bucektApi.NewGetBucketQuotaDefault(err.Code).WithPayload(err.APIError)
}
return bucektApi.NewGetBucketQuotaOK().WithPayload(resp)
})
}
func getBucketQuotaResponse(session *models.Principal, params bucektApi.GetBucketQuotaParams) (*models.BucketQuota, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
quota, err := getBucketQuota(ctx, &adminClient, &params.Name)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return quota, nil
}
func getBucketQuota(ctx context.Context, ac *AdminClient, bucket *string) (*models.BucketQuota, error) {
quota, err := ac.getBucketQuota(ctx, *bucket)
if err != nil {
return nil, err
}
return &models.BucketQuota{
Quota: int64(quota.Quota),
Type: string(quota.Type),
}, nil
}

View File

@@ -1,668 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/minio/minio-go/v7"
"github.com/minio/madmin-go/v3"
"github.com/minio/mc/cmd"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/sse"
"github.com/minio/minio-go/v7/pkg/tags"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/api/operations"
bucketApi "github.com/minio/console/api/operations/bucket"
"github.com/minio/console/models"
"github.com/minio/console/pkg/auth/token"
"github.com/minio/minio-go/v7/pkg/policy"
minioIAMPolicy "github.com/minio/pkg/v3/policy"
)
func registerBucketsHandlers(api *operations.ConsoleAPI) {
// list buckets
api.BucketListBucketsHandler = bucketApi.ListBucketsHandlerFunc(func(params bucketApi.ListBucketsParams, session *models.Principal) middleware.Responder {
listBucketsResponse, err := getListBucketsResponse(session, params)
if err != nil {
return bucketApi.NewListBucketsDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewListBucketsOK().WithPayload(listBucketsResponse)
})
// make bucket
api.BucketMakeBucketHandler = bucketApi.MakeBucketHandlerFunc(func(params bucketApi.MakeBucketParams, session *models.Principal) middleware.Responder {
makeBucketResponse, err := getMakeBucketResponse(session, params)
if err != nil {
return bucketApi.NewMakeBucketDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewMakeBucketOK().WithPayload(makeBucketResponse)
})
// get bucket info
api.BucketBucketInfoHandler = bucketApi.BucketInfoHandlerFunc(func(params bucketApi.BucketInfoParams, session *models.Principal) middleware.Responder {
bucketInfoResp, err := getBucketInfoResponse(session, params)
if err != nil {
return bucketApi.NewBucketInfoDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewBucketInfoOK().WithPayload(bucketInfoResp)
})
// set bucket tags
api.BucketPutBucketTagsHandler = bucketApi.PutBucketTagsHandlerFunc(func(params bucketApi.PutBucketTagsParams, session *models.Principal) middleware.Responder {
err := getPutBucketTagsResponse(session, params)
if err != nil {
return bucketApi.NewPutBucketTagsDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewPutBucketTagsOK()
})
// get bucket versioning
api.BucketGetBucketVersioningHandler = bucketApi.GetBucketVersioningHandlerFunc(func(params bucketApi.GetBucketVersioningParams, session *models.Principal) middleware.Responder {
getBucketVersioning, err := getBucketVersionedResponse(session, params)
if err != nil {
return bucketApi.NewGetBucketVersioningDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewGetBucketVersioningOK().WithPayload(getBucketVersioning)
})
// update bucket versioning
api.BucketSetBucketVersioningHandler = bucketApi.SetBucketVersioningHandlerFunc(func(params bucketApi.SetBucketVersioningParams, session *models.Principal) middleware.Responder {
err := setBucketVersioningResponse(session, params)
if err != nil {
return bucketApi.NewSetBucketVersioningDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewSetBucketVersioningCreated()
})
// get objects rewind for a bucket
api.BucketGetBucketRewindHandler = bucketApi.GetBucketRewindHandlerFunc(func(params bucketApi.GetBucketRewindParams, session *models.Principal) middleware.Responder {
getBucketRewind, err := getBucketRewindResponse(session, params)
if err != nil {
return bucketApi.NewGetBucketRewindDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewGetBucketRewindOK().WithPayload(getBucketRewind)
})
// get max allowed share link expiration time
api.BucketGetMaxShareLinkExpHandler = bucketApi.GetMaxShareLinkExpHandlerFunc(func(params bucketApi.GetMaxShareLinkExpParams, session *models.Principal) middleware.Responder {
val, err := getMaxShareLinkExpirationResponse(session, params)
if err != nil {
return bucketApi.NewGetMaxShareLinkExpDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewGetMaxShareLinkExpOK().WithPayload(val)
})
}
type VersionState string
const (
VersionEnable VersionState = "enable"
VersionSuspend VersionState = "suspend"
)
// removeBucket deletes a bucket
func doSetVersioning(ctx context.Context, client MCClient, state VersionState, excludePrefix []string, excludeFolders bool) error {
err := client.setVersioning(ctx, string(state), excludePrefix, excludeFolders)
if err != nil {
return err.Cause
}
return nil
}
func setBucketVersioningResponse(session *models.Principal, params bucketApi.SetBucketVersioningParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
bucketName := params.BucketName
s3Client, err := newS3BucketClient(session, bucketName, "", getClientIP(params.HTTPRequest))
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a mc S3Client interface implementation
// defining the client to be used
amcClient := mcClient{client: s3Client}
versioningState := VersionSuspend
if params.Body.Enabled {
versioningState = VersionEnable
}
var excludePrefixes []string
if params.Body.ExcludePrefixes != nil {
excludePrefixes = params.Body.ExcludePrefixes
}
excludeFolders := params.Body.ExcludeFolders
if err := doSetVersioning(ctx, amcClient, versioningState, excludePrefixes, excludeFolders); err != nil {
return ErrorWithContext(ctx, fmt.Errorf("error setting versioning for bucket: %s", err))
}
return nil
}
func getBucketVersionedResponse(session *models.Principal, params bucketApi.GetBucketVersioningParams) (*models.BucketVersioningResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest))
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
// we will tolerate this call failing
res, err := minioClient.getBucketVersioning(ctx, params.BucketName)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
excludedPrefixes := make([]*models.BucketVersioningResponseExcludedPrefixesItems0, len(res.ExcludedPrefixes))
for i, v := range res.ExcludedPrefixes {
excludedPrefixes[i] = &models.BucketVersioningResponseExcludedPrefixesItems0{
Prefix: v.Prefix,
}
}
// serialize output
bucketVResponse := &models.BucketVersioningResponse{
ExcludeFolders: res.ExcludeFolders,
ExcludedPrefixes: excludedPrefixes,
MFADelete: res.MFADelete,
Status: res.Status,
}
return bucketVResponse, nil
}
// getAccountBuckets fetches a list of all buckets allowed to that particular client from MinIO Servers
func getAccountBuckets(ctx context.Context, client MinioAdmin) ([]*models.Bucket, error) {
info, err := client.AccountInfo(ctx)
if err != nil {
return []*models.Bucket{}, err
}
bucketInfos := []*models.Bucket{}
for _, bucket := range info.Buckets {
bucketElem := &models.Bucket{
CreationDate: bucket.Created.Format(time.RFC3339),
Details: &models.BucketDetails{
Quota: nil,
},
RwAccess: &models.BucketRwAccess{
Read: bucket.Access.Read,
Write: bucket.Access.Write,
},
Name: swag.String(bucket.Name),
Objects: int64(bucket.Objects),
Size: int64(bucket.Size),
}
if bucket.Details != nil {
if bucket.Details.Tagging != nil {
bucketElem.Details.Tags = bucket.Details.Tagging.ToMap()
}
bucketElem.Details.Locking = bucket.Details.Locking
bucketElem.Details.Replication = bucket.Details.Replication
bucketElem.Details.Versioning = bucket.Details.Versioning
bucketElem.Details.VersioningSuspended = bucket.Details.VersioningSuspended
if bucket.Details.Quota != nil {
bucketElem.Details.Quota = &models.BucketDetailsQuota{
Quota: int64(bucket.Details.Quota.Quota),
Type: string(bucket.Details.Quota.Type),
}
}
}
bucketInfos = append(bucketInfos, bucketElem)
}
return bucketInfos, nil
}
// getListBucketsResponse performs listBuckets() and serializes it to the handler's output
func getListBucketsResponse(session *models.Principal, params bucketApi.ListBucketsParams) (*models.ListBucketsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
buckets, err := getAccountBuckets(ctx, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// serialize output
listBucketsResponse := &models.ListBucketsResponse{
Buckets: buckets,
Total: int64(len(buckets)),
}
return listBucketsResponse, nil
}
// makeBucket creates a bucket for an specific minio client
func makeBucket(ctx context.Context, client MinioClient, bucketName string, objectLocking bool) error {
// creates a new bucket with bucketName with a context to control cancellations and timeouts.
return client.makeBucketWithContext(ctx, bucketName, "", objectLocking)
}
// getMakeBucketResponse performs makeBucket() to create a bucket with its access policy
func getMakeBucketResponse(session *models.Principal, params bucketApi.MakeBucketParams) (*models.MakeBucketsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// bucket request needed to proceed
br := params.Body
if br == nil {
return nil, ErrorWithContext(ctx, ErrBucketBodyNotInRequest)
}
mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest))
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
if err := makeBucket(ctx, minioClient, *br.Name, false); err != nil {
return nil, ErrorWithContext(ctx, err)
}
// make sure to delete bucket if an errors occurs after bucket was created
defer func() {
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("error creating bucket: %v", err))
if err := removeBucket(minioClient, *br.Name); err != nil {
ErrorWithContext(ctx, fmt.Errorf("error removing bucket: %v", err))
}
}
}()
return &models.MakeBucketsResponse{BucketName: *br.Name}, nil
}
// setBucketAccessPolicy set the access permissions on an existing bucket.
func setBucketAccessPolicy(ctx context.Context, client MinioClient, bucketName string, access models.BucketAccess, policyDefinition string) error {
if strings.TrimSpace(bucketName) == "" {
return fmt.Errorf("error: bucket name not present")
}
if strings.TrimSpace(string(access)) == "" {
return fmt.Errorf("error: bucket access not present")
}
// Prepare policyJSON corresponding to the access type
if access != models.BucketAccessPRIVATE && access != models.BucketAccessPUBLIC && access != models.BucketAccessCUSTOM {
return fmt.Errorf("access: `%s` not supported", access)
}
bucketAccessPolicy := policy.BucketAccessPolicy{Version: minioIAMPolicy.DefaultVersion}
if access == models.BucketAccessCUSTOM {
err := client.setBucketPolicyWithContext(ctx, bucketName, policyDefinition)
if err != nil {
return err
}
return nil
}
bucketPolicy := consoleAccess2policyAccess(access)
bucketAccessPolicy.Statements = policy.SetPolicy(bucketAccessPolicy.Statements,
bucketPolicy, bucketName, "")
policyJSON, err := json.Marshal(bucketAccessPolicy)
if err != nil {
return err
}
return client.setBucketPolicyWithContext(ctx, bucketName, string(policyJSON))
}
// putBucketTags sets tags for a bucket
func getPutBucketTagsResponse(session *models.Principal, params bucketApi.PutBucketTagsParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest))
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
req := params.Body
bucketName := params.BucketName
newTagSet, err := tags.NewTags(req.Tags, true)
if err != nil {
return ErrorWithContext(ctx, err)
}
err = minioClient.SetBucketTagging(ctx, bucketName, newTagSet)
if err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
// removeBucket deletes a bucket
func removeBucket(client MinioClient, bucketName string) error {
return client.removeBucket(context.Background(), bucketName)
}
// getBucketInfo return bucket information including name, policy access, size and creation date
func getBucketInfo(ctx context.Context, client MinioClient, adminClient MinioAdmin, bucketName string) (*models.Bucket, error) {
var bucketAccess models.BucketAccess
policyStr, err := client.getBucketPolicy(context.Background(), bucketName)
if err != nil {
// we can tolerate this errors
ErrorWithContext(ctx, fmt.Errorf("error getting bucket policy: %v", err))
}
if policyStr == "" {
bucketAccess = models.BucketAccessPRIVATE
} else {
var p policy.BucketAccessPolicy
if err = json.Unmarshal([]byte(policyStr), &p); err != nil {
return nil, err
}
policyAccess := policy.GetPolicy(p.Statements, bucketName, "")
if len(p.Statements) > 0 && policyAccess == policy.BucketPolicyNone {
bucketAccess = models.BucketAccessCUSTOM
} else {
bucketAccess = policyAccess2consoleAccess(policyAccess)
}
}
bucketTags, err := client.GetBucketTagging(ctx, bucketName)
if err != nil {
// we can tolerate this errors
ErrorWithContext(ctx, fmt.Errorf("error getting bucket tags: %v", err))
}
bucketDetails := &models.BucketDetails{}
if bucketTags != nil {
bucketDetails.Tags = bucketTags.ToMap()
}
info, err := adminClient.AccountInfo(ctx)
if err != nil {
return nil, err
}
var bucketInfo madmin.BucketAccessInfo
for _, bucket := range info.Buckets {
if bucket.Name == bucketName {
bucketInfo = bucket
break
}
}
return &models.Bucket{
Name: &bucketName,
Access: &bucketAccess,
Definition: policyStr,
CreationDate: bucketInfo.Created.Format(time.RFC3339),
Size: int64(bucketInfo.Size),
Details: bucketDetails,
Objects: int64(bucketInfo.Objects),
}, nil
}
// getBucketInfoResponse calls getBucketInfo() to get the bucket's info
func getBucketInfoResponse(session *models.Principal, params bucketApi.BucketInfoParams) (*models.Bucket, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest))
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
bucket, err := getBucketInfo(ctx, minioClient, adminClient, params.Name)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return bucket, nil
}
// policyAccess2consoleAccess gets the equivalent of policy.BucketPolicy to models.BucketAccess
func policyAccess2consoleAccess(bucketPolicy policy.BucketPolicy) (bucketAccess models.BucketAccess) {
switch bucketPolicy {
case policy.BucketPolicyReadWrite:
bucketAccess = models.BucketAccessPUBLIC
case policy.BucketPolicyNone:
bucketAccess = models.BucketAccessPRIVATE
default:
bucketAccess = models.BucketAccessCUSTOM
}
return bucketAccess
}
// consoleAccess2policyAccess gets the equivalent of models.BucketAccess to policy.BucketPolicy
func consoleAccess2policyAccess(bucketAccess models.BucketAccess) (bucketPolicy policy.BucketPolicy) {
switch bucketAccess {
case models.BucketAccessPUBLIC:
bucketPolicy = policy.BucketPolicyReadWrite
case models.BucketAccessPRIVATE:
bucketPolicy = policy.BucketPolicyNone
}
return bucketPolicy
}
// enableBucketEncryption will enable bucket encryption based on two encryption algorithms, sse-s3 (server side encryption with external KMS) or sse-kms (aws s3 kms key)
func enableBucketEncryption(ctx context.Context, client MinioClient, bucketName string, encryptionType models.BucketEncryptionType, kmsKeyID string) error {
var config *sse.Configuration
switch encryptionType {
case models.BucketEncryptionTypeSseDashKms:
config = sse.NewConfigurationSSEKMS(kmsKeyID)
case models.BucketEncryptionTypeSseDashS3:
config = sse.NewConfigurationSSES3()
default:
return ErrInvalidEncryptionAlgorithm
}
return client.setBucketEncryption(ctx, bucketName, config)
}
// disableBucketEncryption will disable bucket for the provided bucket name
func disableBucketEncryption(ctx context.Context, client MinioClient, bucketName string) error {
return client.removeBucketEncryption(ctx, bucketName)
}
func getBucketEncryptionInfo(ctx context.Context, client MinioClient, bucketName string) (*models.BucketEncryptionInfo, error) {
bucketInfo, err := client.getBucketEncryption(ctx, bucketName)
if err != nil {
return nil, err
}
if len(bucketInfo.Rules) == 0 {
return nil, ErrDefault
}
return &models.BucketEncryptionInfo{Algorithm: bucketInfo.Rules[0].Apply.SSEAlgorithm, KmsMasterKeyID: bucketInfo.Rules[0].Apply.KmsMasterKeyID}, nil
}
// setBucketRetentionConfig sets object lock configuration on a bucket
func setBucketRetentionConfig(ctx context.Context, client MinioClient, bucketName string, mode models.ObjectRetentionMode, unit models.ObjectRetentionUnit, validity *int32) error {
if validity == nil {
return errors.New("retention validity can't be nil")
}
var retentionMode minio.RetentionMode
switch mode {
case models.ObjectRetentionModeGovernance:
retentionMode = minio.Governance
case models.ObjectRetentionModeCompliance:
retentionMode = minio.Compliance
default:
return errors.New("invalid retention mode")
}
var retentionUnit minio.ValidityUnit
switch unit {
case models.ObjectRetentionUnitDays:
retentionUnit = minio.Days
case models.ObjectRetentionUnitYears:
retentionUnit = minio.Years
default:
return errors.New("invalid retention unit")
}
retentionValidity := uint(*validity)
return client.setObjectLockConfig(ctx, bucketName, &retentionMode, &retentionValidity, &retentionUnit)
}
func getBucketRetentionConfig(ctx context.Context, client MinioClient, bucketName string) (*models.GetBucketRetentionConfig, error) {
m, v, u, err := client.getBucketObjectLockConfig(ctx, bucketName)
if err != nil {
errResp := minio.ToErrorResponse(probe.NewError(err).ToGoError())
if errResp.Code == "ObjectLockConfigurationNotFoundError" {
return &models.GetBucketRetentionConfig{}, nil
}
return nil, err
}
// These values can be empty when all are empty, it means
// object was created with object locking enabled but
// does not have any default object locking configuration.
if m == nil && v == nil && u == nil {
return &models.GetBucketRetentionConfig{}, nil
}
var mode models.ObjectRetentionMode
var unit models.ObjectRetentionUnit
if m != nil {
switch *m {
case minio.Governance:
mode = models.ObjectRetentionModeGovernance
case minio.Compliance:
mode = models.ObjectRetentionModeCompliance
default:
return nil, errors.New("invalid retention mode")
}
}
if u != nil {
switch *u {
case minio.Days:
unit = models.ObjectRetentionUnitDays
case minio.Years:
unit = models.ObjectRetentionUnitYears
default:
return nil, errors.New("invalid retention unit")
}
}
var validity int32
if v != nil {
validity = int32(*v)
}
config := &models.GetBucketRetentionConfig{
Mode: mode,
Unit: unit,
Validity: validity,
}
return config, nil
}
func getBucketRewindResponse(session *models.Principal, params bucketApi.GetBucketRewindParams) (*models.RewindResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
prefix := ""
if params.Prefix != nil {
prefix = *params.Prefix
}
s3Client, err := newS3BucketClient(session, params.BucketName, prefix, getClientIP(params.HTTPRequest))
if err != nil {
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating S3Client: %v", err))
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
parsedDate, errDate := time.Parse(time.RFC3339, params.Date)
if errDate != nil {
return nil, ErrorWithContext(ctx, errDate)
}
var rewindItems []*models.RewindItem
for content := range mcClient.client.List(ctx, cmd.ListOptions{TimeRef: parsedDate, WithDeleteMarkers: true}) {
// build object name
name := strings.ReplaceAll(content.URL.Path, fmt.Sprintf("/%s/", params.BucketName), "")
listElement := &models.RewindItem{
LastModified: content.Time.Format(time.RFC3339),
Size: content.Size,
VersionID: content.VersionID,
DeleteFlag: content.IsDeleteMarker,
Action: "",
Name: name,
}
rewindItems = append(rewindItems, listElement)
}
return &models.RewindResponse{
Objects: rewindItems,
}, nil
}
func getMaxShareLinkExpirationResponse(session *models.Principal, params bucketApi.GetMaxShareLinkExpParams) (*models.MaxShareLinkExpResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
maxShareLinkExpSeconds, err := getMaxShareLinkExpirationSeconds(session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.MaxShareLinkExpResponse{Exp: swag.Int64(maxShareLinkExpSeconds)}, nil
}
// getMaxShareLinkExpirationSeconds returns the max share link expiration time in seconds which is the sts token expiration time
func getMaxShareLinkExpirationSeconds(session *models.Principal) (int64, error) {
creds := getConsoleCredentialsFromSession(session)
val, err := creds.GetWithContext(&credentials.CredContext{Client: http.DefaultClient})
if err != nil {
return 0, err
}
if val.SignerType.IsAnonymous() {
return 0, ErrAccessDenied
}
maxShareLinkExp := token.GetConsoleSTSDuration()
return int64(maxShareLinkExp.Seconds()), nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,324 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
authApi "github.com/minio/console/api/operations/auth"
"github.com/minio/console/models"
"github.com/minio/console/pkg/auth"
"github.com/minio/console/pkg/auth/idp/oauth2"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/pkg/v3/env"
xnet "github.com/minio/pkg/v3/net"
)
func registerLoginHandlers(api *operations.ConsoleAPI) {
// GET login strategy
api.AuthLoginDetailHandler = authApi.LoginDetailHandlerFunc(func(params authApi.LoginDetailParams) middleware.Responder {
loginDetails, err := getLoginDetailsResponse(params, GlobalMinIOConfig.OpenIDProviders)
if err != nil {
return authApi.NewLoginDetailDefault(err.Code).WithPayload(err.APIError)
}
return authApi.NewLoginDetailOK().WithPayload(loginDetails)
})
// POST login using user credentials
api.AuthLoginHandler = authApi.LoginHandlerFunc(func(params authApi.LoginParams) middleware.Responder {
loginResponse, err := getLoginResponse(params)
if err != nil {
return authApi.NewLoginDefault(err.Code).WithPayload(err.APIError)
}
// Custom response writer to set the session cookies
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := NewSessionCookieForConsole(loginResponse.SessionID)
http.SetCookie(w, &cookie)
authApi.NewLoginNoContent().WriteResponse(w, p)
})
})
// POST login using external IDP
api.AuthLoginOauth2AuthHandler = authApi.LoginOauth2AuthHandlerFunc(func(params authApi.LoginOauth2AuthParams) middleware.Responder {
loginResponse, err := getLoginOauth2AuthResponse(params, GlobalMinIOConfig.OpenIDProviders)
if err != nil {
return authApi.NewLoginOauth2AuthDefault(err.Code).WithPayload(err.APIError)
}
// Custom response writer to set the session cookies
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := NewSessionCookieForConsole(loginResponse.SessionID)
http.SetCookie(w, &cookie)
http.SetCookie(w, &http.Cookie{
Path: "/",
Name: "idp-refresh-token",
Value: loginResponse.IDPRefreshToken,
HttpOnly: true,
Secure: len(GlobalPublicCerts) > 0,
SameSite: http.SameSiteLaxMode,
})
authApi.NewLoginOauth2AuthNoContent().WriteResponse(w, p)
})
})
}
// login performs a check of ConsoleCredentials against MinIO, generates some claims and returns the jwt
// for subsequent authentication
func login(credentials ConsoleCredentialsI, sessionFeatures *auth.SessionFeatures) (*string, error) {
// try to obtain consoleCredentials,
tokens, err := credentials.Get()
if err != nil {
return nil, err
}
// if we made it here, the consoleCredentials work, generate a jwt with claims
token, err := auth.NewEncryptedTokenForClient(&tokens, credentials.GetAccountAccessKey(), sessionFeatures)
if err != nil {
LogError("error authenticating user: %v", err)
return nil, ErrInvalidLogin
}
return &token, nil
}
// getAccountInfo will return the current user information
func getAccountInfo(ctx context.Context, client MinioAdmin) (*madmin.AccountInfo, error) {
accountInfo, err := client.AccountInfo(ctx)
if err != nil {
return nil, err
}
return &accountInfo, nil
}
// getConsoleCredentials will return ConsoleCredentials interface
func getConsoleCredentials(accessKey, secretKey string, client *http.Client) (*ConsoleCredentials, error) {
creds, err := NewConsoleCredentials(accessKey, secretKey, GetMinIORegion(), client)
if err != nil {
return nil, err
}
return &ConsoleCredentials{
ConsoleCredentials: creds,
AccountAccessKey: accessKey,
CredContext: &credentials.CredContext{
Client: client,
},
}, nil
}
// getLoginResponse performs login() and serializes it to the handler's output
func getLoginResponse(params authApi.LoginParams) (*models.LoginResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
lr := params.Body
// trim any leading and trailing whitespace from the login request
lr.AccessKey = strings.TrimSpace(lr.AccessKey)
lr.SecretKey = strings.TrimSpace(lr.SecretKey)
lr.Sts = strings.TrimSpace(lr.Sts)
clientIP := getClientIP(params.HTTPRequest)
client := GetConsoleHTTPClient(clientIP)
var err error
var consoleCreds *ConsoleCredentials
// if we receive an STS we use that instead of the credentials
if lr.Sts != "" {
consoleCreds = &ConsoleCredentials{
ConsoleCredentials: credentials.NewStaticV4(lr.AccessKey, lr.SecretKey, lr.Sts),
AccountAccessKey: lr.AccessKey,
CredContext: &credentials.CredContext{
Client: client,
},
}
} else {
// prepare console credentials
consoleCreds, err = getConsoleCredentials(lr.AccessKey, lr.SecretKey, client)
if err != nil {
return nil, ErrorWithContext(ctx, err, ErrInvalidLogin)
}
}
sf := &auth.SessionFeatures{}
if lr.Features != nil {
sf.HideMenu = lr.Features.HideMenu
}
sessionID, err := login(consoleCreds, sf)
if err != nil {
if xnet.IsNetworkOrHostDown(err, true) {
return nil, ErrorWithContext(ctx, ErrNetworkError)
}
return nil, ErrorWithContext(ctx, err, ErrInvalidLogin)
}
// serialize output
loginResponse := &models.LoginResponse{
SessionID: *sessionID,
}
return loginResponse, nil
}
// isKubernetes returns true if minio is running in kubernetes.
func isKubernetes() bool {
// Kubernetes env used to validate if we are
// indeed running inside a kubernetes pod
// is KUBERNETES_SERVICE_HOST
// https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kubelet_pods.go#L541
return env.Get("KUBERNETES_SERVICE_HOST", "") != ""
}
// getLoginDetailsResponse returns information regarding the Console authentication mechanism.
func getLoginDetailsResponse(params authApi.LoginDetailParams, openIDProviders oauth2.OpenIDPCfg) (ld *models.LoginDetails, apiErr *CodedAPIError) {
loginStrategy := models.LoginDetailsLoginStrategyForm
var redirectRules []*models.RedirectRule
r := params.HTTPRequest
var loginDetails *models.LoginDetails
if len(openIDProviders) > 0 {
loginStrategy = models.LoginDetailsLoginStrategyRedirect
}
for name, provider := range openIDProviders {
// initialize new oauth2 client
oauth2Client, err := provider.GetOauth2Provider(name, nil, r, GetConsoleHTTPClient(getClientIP(params.HTTPRequest)))
if err != nil {
continue
}
// Validate user against IDP
identityProvider := &auth.IdentityProvider{
KeyFunc: provider.GetStateKeyFunc(),
Client: oauth2Client,
}
displayName := fmt.Sprintf("Login with SSO (%s)", name)
serviceType := ""
if provider.DisplayName != "" {
displayName = provider.DisplayName
}
if provider.RoleArn != "" {
splitRoleArn := strings.Split(provider.RoleArn, ":")
if len(splitRoleArn) > 2 {
serviceType = splitRoleArn[2]
}
}
redirectRule := models.RedirectRule{
Redirect: identityProvider.GenerateLoginURL(),
DisplayName: displayName,
ServiceType: serviceType,
}
redirectRules = append(redirectRules, &redirectRule)
}
if len(openIDProviders) > 0 && len(redirectRules) == 0 {
loginStrategy = models.LoginDetailsLoginStrategyForm
// No IDP configured fallback to username/password
}
loginDetails = &models.LoginDetails{
LoginStrategy: loginStrategy,
RedirectRules: redirectRules,
IsK8S: isKubernetes(),
AnimatedLogin: getConsoleAnimatedLogin(),
}
return loginDetails, nil
}
// verifyUserAgainstIDP will verify user identity against the configured IDP and return MinIO credentials
func verifyUserAgainstIDP(ctx context.Context, provider auth.IdentityProviderI, code, state string) (*credentials.Credentials, error) {
userCredentials, err := provider.VerifyIdentity(ctx, code, state)
if err != nil {
LogError("error validating user identity against idp: %v", err)
return nil, err
}
return userCredentials, nil
}
func getLoginOauth2AuthResponse(params authApi.LoginOauth2AuthParams, openIDProviders oauth2.OpenIDPCfg) (*models.LoginResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
r := params.HTTPRequest
lr := params.Body
client := GetConsoleHTTPClient(getClientIP(params.HTTPRequest))
if len(openIDProviders) > 0 {
// we read state
rState := *lr.State
decodedRState, err := base64.StdEncoding.DecodeString(rState)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
var requestItems oauth2.LoginURLParams
if err = json.Unmarshal(decodedRState, &requestItems); err != nil {
return nil, ErrorWithContext(ctx, err)
}
IDPName := requestItems.IDPName
state := requestItems.State
providerCfg, ok := openIDProviders[IDPName]
if !ok {
return nil, ErrorWithContext(ctx, fmt.Errorf("selected IDP %s does not exist", IDPName))
}
// Initialize new identity provider with new oauth2Client per IDPName
oauth2Client, err := providerCfg.GetOauth2Provider(IDPName, nil, r, client)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
identityProvider := auth.IdentityProvider{
KeyFunc: providerCfg.GetStateKeyFunc(),
Client: oauth2Client,
RoleARN: providerCfg.RoleArn,
}
// Validate user against IDP
userCredentials, err := verifyUserAgainstIDP(ctx, identityProvider, *lr.Code, state)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// initialize admin client
// login user against console and generate session token
token, err := login(&ConsoleCredentials{
ConsoleCredentials: userCredentials,
AccountAccessKey: "",
CredContext: &credentials.CredContext{Client: client},
}, nil)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// serialize output
loginResponse := &models.LoginResponse{
SessionID: *token,
IDPRefreshToken: identityProvider.Client.RefreshToken,
}
return loginResponse, nil
}
return nil, ErrorWithContext(ctx, ErrDefault)
}

View File

@@ -1,194 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"context"
"errors"
"reflect"
"testing"
xoauth2 "golang.org/x/oauth2"
"github.com/minio/madmin-go/v3"
iampolicy "github.com/minio/pkg/v3/policy"
"github.com/minio/console/pkg/auth"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/stretchr/testify/assert"
)
// Define a mock struct of ConsoleCredentialsI interface implementation
type consoleCredentialsMock struct{}
func (ac consoleCredentialsMock) GetActions() []string {
return []string{}
}
func (ac consoleCredentialsMock) GetAccountAccessKey() string {
return ""
}
// Common mocks
var consoleCredentialsGetMock func() (credentials.Value, error)
// mock function of Get()
func (ac consoleCredentialsMock) Get() (credentials.Value, error) {
return consoleCredentialsGetMock()
}
func TestLogin(t *testing.T) {
funcAssert := assert.New(t)
consoleCredentials := consoleCredentialsMock{}
// Test Case 1: Valid consoleCredentials
consoleCredentialsGetMock = func() (credentials.Value, error) {
return credentials.Value{
AccessKeyID: "fakeAccessKeyID",
SecretAccessKey: "fakeSecretAccessKey",
SessionToken: "fakeSessionToken",
SignerType: 0,
}, nil
}
token, err := login(consoleCredentials, nil)
funcAssert.NotEmpty(token, "Token was returned empty")
funcAssert.Nil(err, "error creating a session")
// Test Case 2: Invalid credentials
consoleCredentialsGetMock = func() (credentials.Value, error) {
return credentials.Value{}, errors.New("")
}
_, err = login(consoleCredentials, nil)
funcAssert.NotNil(err, "not error returned creating a session")
}
type IdentityProviderMock struct{}
var (
idpVerifyIdentityMock func(ctx context.Context, code, state string) (*credentials.Credentials, error)
idpVerifyIdentityForOperatorMock func(ctx context.Context, code, state string) (*xoauth2.Token, error)
idpGenerateLoginURLMock func() string
)
func (ac IdentityProviderMock) VerifyIdentity(ctx context.Context, code, state string) (*credentials.Credentials, error) {
return idpVerifyIdentityMock(ctx, code, state)
}
func (ac IdentityProviderMock) VerifyIdentityForOperator(ctx context.Context, code, state string) (*xoauth2.Token, error) {
return idpVerifyIdentityForOperatorMock(ctx, code, state)
}
func (ac IdentityProviderMock) GenerateLoginURL() string {
return idpGenerateLoginURLMock()
}
func Test_validateUserAgainstIDP(t *testing.T) {
provider := IdentityProviderMock{}
mockCode := "EAEAEAE"
mockState := "HUEHUEHUE"
type args struct {
ctx context.Context
provider auth.IdentityProviderI
code string
state string
}
tests := []struct {
name string
args args
want *credentials.Credentials
wantErr bool
mockFunc func()
}{
{
name: "failed to verify user identity with idp",
args: args{
ctx: context.Background(),
provider: provider,
code: mockCode,
state: mockState,
},
want: nil,
wantErr: true,
mockFunc: func() {
idpVerifyIdentityMock = func(_ context.Context, _, _ string) (*credentials.Credentials, error) {
return nil, errors.New("something went wrong")
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
got, err := verifyUserAgainstIDP(tt.args.ctx, tt.args.provider, tt.args.code, tt.args.state)
if (err != nil) != tt.wantErr {
t.Errorf("verifyUserAgainstIDP() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("verifyUserAgainstIDP() got = %v, want %v", got, tt.want)
}
})
}
}
func Test_getAccountInfo(t *testing.T) {
type args struct {
ctx context.Context
}
tests := []struct {
name string
args args
want *iampolicy.Policy
wantErr bool
mockFunc func(client *AdminClientMock)
}{
{
name: "error getting account info",
args: args{
ctx: context.Background(),
},
want: nil,
wantErr: true,
mockFunc: func(client *AdminClientMock) {
client.minioAccountInfoMock = func(_ context.Context) (madmin.AccountInfo, error) {
return madmin.AccountInfo{}, errors.New("something went wrong")
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
client := AdminClientMock{}
if tt.mockFunc != nil {
tt.mockFunc(&client)
}
got, err := getAccountInfo(tt.args.ctx, client)
if (err != nil) != tt.wantErr {
t.Errorf("getAccountInfo() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.want != nil {
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("getAccountInfo() got = %v, want %v", got, tt.want)
}
}
})
}
}

View File

@@ -1,123 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
authApi "github.com/minio/console/api/operations/auth"
"github.com/minio/console/models"
"github.com/minio/console/pkg/auth/idp/oauth2"
)
func registerLogoutHandlers(api *operations.ConsoleAPI) {
// logout from console
api.AuthLogoutHandler = authApi.LogoutHandlerFunc(func(params authApi.LogoutParams, session *models.Principal) middleware.Responder {
err := getLogoutResponse(session, params)
if err != nil {
api.Logger("IDP logout failed: %v", err.APIError.DetailedMessage)
}
// Custom response writer to expire the session cookies
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
if err != nil {
w.Header().Set("IDP-Logout", fmt.Sprintf("%v", err.APIError.DetailedMessage))
}
expiredCookie := ExpireSessionCookie()
// this will tell the browser to clear the cookie and invalidate user session
// additionally we are deleting the cookie from the client side
http.SetCookie(w, &expiredCookie)
http.SetCookie(w, &http.Cookie{
Path: "/",
Name: "idp-refresh-token",
Value: "",
MaxAge: -1,
Expires: time.Now().Add(-100 * time.Hour),
HttpOnly: true,
Secure: len(GlobalPublicCerts) > 0,
SameSite: http.SameSiteLaxMode,
})
authApi.NewLogoutOK().WriteResponse(w, p)
})
})
}
// logout() call Expire() on the provided ConsoleCredentials
func logout(credentials ConsoleCredentialsI) {
credentials.Expire()
}
// getLogoutResponse performs logout() and returns nil or errors
func getLogoutResponse(session *models.Principal, params authApi.LogoutParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
state := params.Body.State
if state != "" {
if err := logoutFromIDPProvider(params.HTTPRequest, state); err != nil {
return ErrorWithContext(ctx, err)
}
}
creds := getConsoleCredentialsFromSession(session)
credentials := ConsoleCredentials{ConsoleCredentials: creds}
logout(credentials)
return nil
}
func logoutFromIDPProvider(r *http.Request, state string) error {
decodedRState, err := base64.StdEncoding.DecodeString(state)
if err != nil {
return err
}
var requestItems oauth2.LoginURLParams
err = json.Unmarshal(decodedRState, &requestItems)
if err != nil {
return err
}
providerCfg := GlobalMinIOConfig.OpenIDProviders[requestItems.IDPName]
refreshToken, err := r.Cookie("idp-refresh-token")
if err != nil {
return err
}
if providerCfg.EndSessionEndpoint != "" {
params := url.Values{}
params.Add("client_id", providerCfg.ClientID)
params.Add("client_secret", providerCfg.ClientSecret)
params.Add("refresh_token", refreshToken.Value)
client := &http.Client{
Transport: GlobalTransport,
}
result, err := client.PostForm(providerCfg.EndSessionEndpoint, params)
if err != nil {
return errors.New(500, "failed to logout: %v", err.Error())
}
if result.StatusCode != 204 {
return errors.New(int32(result.StatusCode), "failed to logout")
}
}
return nil
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,287 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"bytes"
"context"
"encoding/json"
"strconv"
"time"
policies "github.com/minio/console/api/policy"
"github.com/minio/madmin-go/v3"
jwtgo "github.com/golang-jwt/jwt/v4"
"github.com/minio/pkg/v3/policy/condition"
minioIAMPolicy "github.com/minio/pkg/v3/policy"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
authApi "github.com/minio/console/api/operations/auth"
"github.com/minio/console/models"
"github.com/minio/console/pkg/auth/idp/oauth2"
"github.com/minio/console/pkg/auth/ldap"
)
type Conditions struct {
S3Prefix []string `json:"s3:prefix"`
}
func registerSessionHandlers(api *operations.ConsoleAPI) {
// session check
api.AuthSessionCheckHandler = authApi.SessionCheckHandlerFunc(func(params authApi.SessionCheckParams, session *models.Principal) middleware.Responder {
sessionResp, err := getSessionResponse(params.HTTPRequest.Context(), session)
if err != nil {
return authApi.NewSessionCheckDefault(err.Code).WithPayload(err.APIError)
}
return authApi.NewSessionCheckOK().WithPayload(sessionResp)
})
}
func getClaimsFromToken(sessionToken string) (map[string]interface{}, error) {
jp := jwtgo.NewParser()
var claims jwtgo.MapClaims
_, _, err := jp.ParseUnverified(sessionToken, &claims)
if err != nil {
return nil, err
}
return claims, nil
}
// getSessionResponse parse the token of the current session and returns a list of allowed actions to render in the UI
func getSessionResponse(ctx context.Context, session *models.Principal) (*models.SessionResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// serialize output
if session == nil {
return nil, ErrorWithContext(ctx, ErrInvalidSession)
}
tokenClaims, _ := getClaimsFromToken(session.STSSessionToken)
// initialize admin client
mAdminClient, err := NewMinioAdminClient(ctx, &models.Principal{
STSAccessKeyID: session.STSAccessKeyID,
STSSecretAccessKey: session.STSSecretAccessKey,
STSSessionToken: session.STSSessionToken,
})
if err != nil {
return nil, ErrorWithContext(ctx, err, ErrInvalidSession)
}
userAdminClient := AdminClient{Client: mAdminClient}
// Obtain the current policy assigned to this user
// necessary for generating the list of allowed endpoints
accountInfo, err := getAccountInfo(ctx, userAdminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err, ErrInvalidSession)
}
erasure := accountInfo.Server.Type == madmin.Erasure
rawPolicy := policies.ReplacePolicyVariables(tokenClaims, accountInfo)
policy, err := minioIAMPolicy.ParseConfig(bytes.NewReader(rawPolicy))
if err != nil {
return nil, ErrorWithContext(ctx, err, ErrInvalidSession)
}
currTime := time.Now().UTC()
customStyles := session.CustomStyleOb
// This actions will be global, meaning has to be attached to all resources
conditionValues := map[string][]string{
condition.AWSUsername.Name(): {session.AccountAccessKey},
// All calls to MinIO from console use temporary credentials.
condition.AWSPrincipalType.Name(): {"AssumeRole"},
condition.AWSSecureTransport.Name(): {strconv.FormatBool(getMinIOEndpointIsSecure())},
condition.AWSCurrentTime.Name(): {currTime.Format(time.RFC3339)},
condition.AWSEpochTime.Name(): {strconv.FormatInt(currTime.Unix(), 10)},
// All calls from console are signature v4.
condition.S3SignatureVersion.Name(): {"AWS4-HMAC-SHA256"},
// All calls from console use header-based authentication
condition.S3AuthType.Name(): {"REST-HEADER"},
// This is usually empty, may be set some times (rare).
condition.S3LocationConstraint.Name(): {GetMinIORegion()},
}
claims, err := getClaimsFromToken(session.STSSessionToken)
if err != nil {
return nil, ErrorWithContext(ctx, err, ErrInvalidSession)
}
// Support all LDAP, JWT variables
for k, v := range claims {
vstr, ok := v.(string)
if !ok {
// skip all non-strings
continue
}
// store all claims from sessionToken
conditionValues[k] = []string{vstr}
}
defaultActions := policy.IsAllowedActions("", "", conditionValues)
// Allow Create Access Key when admin:CreateServiceAccount is provided with a condition
for _, statement := range policy.Statements {
if statement.Effect == "Deny" && len(statement.Conditions) > 0 &&
statement.Actions.Contains(minioIAMPolicy.CreateServiceAccountAdminAction) {
defaultActions.Add(minioIAMPolicy.Action(minioIAMPolicy.CreateServiceAccountAdminAction))
}
}
permissions := map[string]minioIAMPolicy.ActionSet{
ConsoleResourceName: defaultActions,
}
deniedActions := map[string]minioIAMPolicy.ActionSet{}
var allowResources []*models.PermissionResource
for _, statement := range policy.Statements {
for _, resource := range statement.Resources.ToSlice() {
resourceName := resource.String()
statementActions := statement.Actions.ToSlice()
var prefixes []string
if statement.Effect == "Allow" {
// check if val are denied before adding them to the map
var allowedActions []minioIAMPolicy.Action
if dActions, ok := deniedActions[resourceName]; ok {
for _, action := range statementActions {
if len(dActions.Intersection(minioIAMPolicy.NewActionSet(action))) == 0 {
// It's ok to allow this action
allowedActions = append(allowedActions, action)
}
}
} else {
allowedActions = statementActions
}
// Add validated actions
if resourceActions, ok := permissions[resourceName]; ok {
mergedActions := append(resourceActions.ToSlice(), allowedActions...)
permissions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...)
} else {
mergedActions := append(defaultActions.ToSlice(), allowedActions...)
permissions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...)
}
// Allow Permissions request
conditions, err := statement.Conditions.MarshalJSON()
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
var wrapper map[string]Conditions
if err := json.Unmarshal(conditions, &wrapper); err != nil {
return nil, ErrorWithContext(ctx, err)
}
for condition, elements := range wrapper {
prefixes = elements.S3Prefix
resourceElement := models.PermissionResource{
Resource: resourceName,
Prefixes: prefixes,
ConditionOperator: condition,
}
allowResources = append(allowResources, &resourceElement)
}
} else {
// Add new banned actions to the map
if resourceActions, ok := deniedActions[resourceName]; ok {
mergedActions := append(resourceActions.ToSlice(), statementActions...)
deniedActions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...)
} else {
deniedActions[resourceName] = statement.Actions
}
// Remove existing val from key if necessary
if currentResourceActions, ok := permissions[resourceName]; ok {
var newAllowedActions []minioIAMPolicy.Action
for _, action := range currentResourceActions.ToSlice() {
if len(deniedActions[resourceName].Intersection(minioIAMPolicy.NewActionSet(action))) == 0 {
// It's ok to allow this action
newAllowedActions = append(newAllowedActions, action)
}
}
permissions[resourceName] = minioIAMPolicy.NewActionSet(newAllowedActions...)
}
}
}
}
resourcePermissions := map[string][]string{}
for key, val := range permissions {
var resourceActions []string
for _, action := range val.ToSlice() {
resourceActions = append(resourceActions, string(action))
}
resourcePermissions[key] = resourceActions
}
// environment constants
var envConstants models.EnvironmentConstants
envConstants.MaxConcurrentUploads = getMaxConcurrentUploadsLimit()
envConstants.MaxConcurrentDownloads = getMaxConcurrentDownloadsLimit()
sessionResp := &models.SessionResponse{
Features: getListOfEnabledFeatures(ctx, userAdminClient, session),
Status: models.SessionResponseStatusOk,
Operator: false,
DistributedMode: erasure,
Permissions: resourcePermissions,
AllowResources: allowResources,
CustomStyles: customStyles,
EnvConstants: &envConstants,
ServerEndPoint: getMinIOServer(),
}
return sessionResp, nil
}
// getListOfEnabledFeatures returns a list of features
func getListOfEnabledFeatures(ctx context.Context, minioClient MinioAdmin, session *models.Principal) []string {
features := []string{}
logSearchURL := getLogSearchURL()
oidcEnabled := oauth2.IsIDPEnabled()
ldapEnabled := ldap.GetLDAPEnabled()
if logSearchURL != "" {
features = append(features, "log-search")
}
if oidcEnabled {
features = append(features, "oidc-idp", "external-idp")
}
if ldapEnabled {
features = append(features, "ldap-idp", "external-idp")
}
if session.Hm {
features = append(features, "hide-menu")
}
if session.Ob {
features = append(features, "object-browser-only")
}
if minioClient != nil {
_, err := minioClient.kmsStatus(ctx)
if err == nil {
features = append(features, "kms")
}
}
return features
}

View File

@@ -1,141 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// 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 api
import (
"context"
"os"
"reflect"
"testing"
"github.com/minio/console/pkg/utils"
"github.com/minio/console/models"
"github.com/minio/console/pkg/auth/idp/oauth2"
"github.com/minio/console/pkg/auth/ldap"
"github.com/stretchr/testify/assert"
)
func Test_getSessionResponse(t *testing.T) {
type args struct {
ctx context.Context
session *models.Principal
}
ctx := context.WithValue(context.Background(), utils.ContextClientIP, "127.0.0.1")
tests := []struct {
name string
args args
want *models.SessionResponse
wantErr bool
preFunc func()
postFunc func()
}{
{
name: "empty session",
args: args{
ctx: ctx,
session: nil,
},
want: nil,
wantErr: true,
},
{
name: "malformed session",
args: args{
ctx: ctx,
session: &models.Principal{
STSAccessKeyID: "W257A03HTI7L30F7YCRD",
STSSecretAccessKey: "g+QVorWQR8aSy+k3OHOoYn0qKpENld72faCMfYps",
STSSessionToken: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJXMjU3QTAzSFRJN0wzMEY3WUNSRCIsImV4cCI6MTY1MTAxNjU1OCwicGFyZW50IjoibWluaW8ifQ.uFFIIEQ6qM_QvMM297ODi_uK2IA1pwvsDbyBGErkQKqtbY_Ynte8GUkNsSHBEMCT9Fr7uUwaxK41kUqjtbqAwA",
AccountAccessKey: "minio",
Hm: false,
},
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(_ *testing.T) {
if tt.preFunc != nil {
tt.preFunc()
}
session, err := getSessionResponse(tt.args.ctx, tt.args.session)
if (err != nil) != tt.wantErr {
t.Errorf("getSessionResponse() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(session, tt.want) {
t.Errorf("getSessionResponse() got = %v, want %v", session, tt.want)
}
if tt.postFunc != nil {
tt.postFunc()
}
})
}
}
func Test_getListOfEnabledFeatures(t *testing.T) {
type args struct {
session *models.Principal
}
tests := []struct {
name string
args args
want []string
preFunc func()
postFunc func()
}{
{
name: "all features are enabled",
args: args{
session: &models.Principal{
STSAccessKeyID: "",
STSSecretAccessKey: "",
STSSessionToken: "",
AccountAccessKey: "",
Hm: true,
},
},
want: []string{"log-search", "oidc-idp", "external-idp", "ldap-idp", "external-idp", "hide-menu"},
preFunc: func() {
os.Setenv(ConsoleLogQueryURL, "http://logsearchapi:8080")
os.Setenv(oauth2.ConsoleIDPURL, "http://external-idp.com")
os.Setenv(oauth2.ConsoleIDPClientID, "eaeaeaeaeaea")
os.Setenv(ldap.ConsoleLDAPEnabled, "on")
},
postFunc: func() {
os.Unsetenv(ConsoleLogQueryURL)
os.Unsetenv(oauth2.ConsoleIDPURL)
os.Unsetenv(oauth2.ConsoleIDPClientID)
os.Unsetenv(ldap.ConsoleLDAPEnabled)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
if tt.preFunc != nil {
tt.preFunc()
}
assert.Equalf(t, tt.want, getListOfEnabledFeatures(context.Background(), nil, tt.args.session), "getListOfEnabledFeatures(%v)", tt.args.session)
if tt.postFunc != nil {
tt.postFunc()
}
})
}
}

View File

@@ -1,255 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"io"
"net/http"
"strings"
"time"
xjwt "github.com/minio/console/pkg/auth/token"
)
// Do not use:
// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
// It relies on math/rand and therefore not on a cryptographically secure RNG => It must not be used
// for access/secret keys.
// The alphabet of random character string. Each character must be unique.
//
// The RandomCharString implementation requires that: 256 / len(letters) is a natural numbers.
// For example: 256 / 64 = 4. However, 5 > 256/62 > 4 and therefore we must not use a alphabet
// of 62 characters.
// The reason is that if 256 / len(letters) is not a natural number then certain characters become
// more likely then others.
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"
type CustomButtonStyle struct {
BackgroundColor *string `json:"backgroundColor"`
TextColor *string `json:"textColor"`
HoverColor *string `json:"hoverColor"`
HoverText *string `json:"hoverText"`
ActiveColor *string `json:"activeColor"`
ActiveText *string `json:"activeText"`
DisabledColor *string `json:"disabledColor"`
DisabledText *string `json:"disdabledText"`
}
type CustomTableStyle struct {
Border *string `json:"border"`
DisabledBorder *string `json:"disabledBorder"`
DisabledBG *string `json:"disabledBG"`
Selected *string `json:"selected"`
DeletedDisabled *string `json:"deletedDisabled"`
HoverColor *string `json:"hoverColor"`
}
type CustomInputStyle struct {
Border *string `json:"border"`
HoverBorder *string `json:"hoverBorder"`
TextColor *string `json:"textColor"`
BackgroundColor *string `json:"backgroundColor"`
}
type CustomSwitchStyle struct {
SwitchBackground *string `json:"switchBackground"`
BulletBorderColor *string `json:"bulletBorderColor"`
BulletBGColor *string `json:"bulletBGColor"`
DisabledBackground *string `json:"disabledBackground"`
DisabledBulletBorderColor *string `json:"disabledBulletBorderColor"`
DisabledBulletBGColor *string `json:"disabledBulletBGColor"`
}
type CustomStyles struct {
BackgroundColor *string `json:"backgroundColor"`
FontColor *string `json:"fontColor"`
SecondaryFontColor *string `json:"secondaryFontColor"`
BorderColor *string `json:"borderColor"`
LoaderColor *string `json:"loaderColor"`
BoxBackground *string `json:"boxBackground"`
OkColor *string `json:"okColor"`
ErrorColor *string `json:"errorColor"`
WarnColor *string `json:"warnColor"`
LinkColor *string `json:"linkColor"`
DisabledLinkColor *string `json:"disabledLinkColor"`
HoverLinkColor *string `json:"hoverLinkColor"`
ButtonStyles *CustomButtonStyle `json:"buttonStyles"`
SecondaryButtonStyles *CustomButtonStyle `json:"secondaryButtonStyles"`
RegularButtonStyles *CustomButtonStyle `json:"regularButtonStyles"`
TableColors *CustomTableStyle `json:"tableColors"`
InputBox *CustomInputStyle `json:"inputBox"`
Switch *CustomSwitchStyle `json:"switch"`
}
func RandomCharStringWithAlphabet(n int, alphabet string) string {
random := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, random); err != nil {
panic(err) // Can only happen if we would run out of entropy.
}
var s strings.Builder
for _, v := range random {
j := v % byte(len(alphabet))
s.WriteByte(alphabet[j])
}
return s.String()
}
func RandomCharString(n int) string {
return RandomCharStringWithAlphabet(n, letters)
}
// DifferenceArrays returns the elements in `a` that aren't in `b`.
func DifferenceArrays(a, b []string) []string {
mb := make(map[string]struct{}, len(b))
for _, x := range b {
mb[x] = struct{}{}
}
var diff []string
for _, x := range a {
if _, found := mb[x]; !found {
diff = append(diff, x)
}
}
return diff
}
// IsElementInArray returns true if the string belongs to the slice
func IsElementInArray(a []string, b string) bool {
for _, e := range a {
if e == b {
return true
}
}
return false
}
// UniqueKeys returns an array without duplicated keys
func UniqueKeys(a []string) []string {
keys := make(map[string]bool)
list := []string{}
for _, entry := range a {
if _, value := keys[entry]; !value {
keys[entry] = true
list = append(list, entry)
}
}
return list
}
func NewSessionCookieForConsole(token string) http.Cookie {
sessionDuration := xjwt.GetConsoleSTSDuration()
return http.Cookie{
Path: "/",
Name: "token",
Value: token,
MaxAge: int(sessionDuration.Seconds()), // default 1 hr
Expires: time.Now().Add(sessionDuration),
HttpOnly: true,
// if len(GlobalPublicCerts) > 0 is true, that means Console is running with TLS enable and the browser
// should not leak any cookie if we access the site using HTTP
Secure: len(GlobalPublicCerts) > 0,
// read more: https://web.dev/samesite-cookies-explained/
SameSite: http.SameSiteLaxMode,
}
}
func ExpireSessionCookie() http.Cookie {
return http.Cookie{
Path: "/",
Name: "token",
Value: "",
MaxAge: -1,
Expires: time.Now().Add(-100 * time.Hour),
HttpOnly: true,
// if len(GlobalPublicCerts) > 0 is true, that means Console is running with TLS enable and the browser
// should not leak any cookie if we access the site using HTTP
Secure: len(GlobalPublicCerts) > 0,
// read more: https://web.dev/samesite-cookies-explained/
SameSite: http.SameSiteLaxMode,
}
}
func ValidateEncodedStyles(encodedStyles string) error {
// encodedStyle JSON validation
str, err := base64.StdEncoding.DecodeString(encodedStyles)
if err != nil {
return err
}
var styleElements *CustomStyles
err = json.Unmarshal(str, &styleElements)
if err != nil {
return err
}
if styleElements.BackgroundColor == nil || styleElements.FontColor == nil || styleElements.ButtonStyles == nil || styleElements.BorderColor == nil || styleElements.OkColor == nil {
return errors.New("specified style is not in the correct format")
}
return nil
}
var safeMimeTypes = []string{
"image/jpeg",
"image/apng",
"image/avif",
"image/webp",
"image/bmp",
"image/x-icon",
"image/gif",
"image/png",
"image/heic",
"image/heif",
"application/pdf",
"text/plain",
"application/json",
"audio/wav",
"audio/mpeg",
"audio/aiff",
"audio/dsd",
"video/mp4",
"video/x-msvideo",
"video/mpeg",
"audio/webm",
"video/webm",
"video/quicktime",
"video/x-flv",
"audio/x-matroska",
"video/x-matroska",
"video/x-ms-wmv",
"application/metastream",
"video/avchd-stream",
"audio/mp4",
"video/mp4",
}
func isSafeToPreview(str string) bool {
for _, v := range safeMimeTypes {
if v == str {
return true
}
}
return false
}

View File

@@ -1,264 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestDifferenceArrays(t *testing.T) {
assert := assert.New(t)
exampleArrayAMock := []string{"a", "b", "c"}
exampleArrayBMock := []string{"b", "d"}
resultABArrayMock := []string{"a", "c"}
resultBAArrayMock := []string{"d"}
// Test-1: test DifferenceArrays() with array a vs array b
diffArray := DifferenceArrays(exampleArrayAMock, exampleArrayBMock)
assert.ElementsMatchf(diffArray, resultABArrayMock, "return array AB doesn't match %s")
// Test-2: test DifferenceArrays() with array b vs array a
diffArray2 := DifferenceArrays(exampleArrayBMock, exampleArrayAMock)
assert.ElementsMatchf(diffArray2, resultBAArrayMock, "return array BA doesn't match %s")
}
func TestIsElementInArray(t *testing.T) {
assert := assert.New(t)
exampleElementsArray := []string{"c", "a", "d", "b"}
// Test-1: test IsElementInArray() with element that is in the list
responseArray := IsElementInArray(exampleElementsArray, "a")
assert.Equal(true, responseArray)
// Test-2: test IsElementInArray() with element that is not in the list
responseArray2 := IsElementInArray(exampleElementsArray, "e")
assert.Equal(false, responseArray2)
}
func TestUniqueKeys(t *testing.T) {
assert := assert.New(t)
exampleMixedArray := []string{"a", "b", "c", "e", "d", "b", "c", "h", "f", "g"}
exampleUniqueArray := []string{"a", "b", "c", "e", "d", "h", "f", "g"}
// Test-1 test UniqueKeys returns an array with unique elements
responseArray := UniqueKeys(exampleMixedArray)
assert.ElementsMatchf(responseArray, exampleUniqueArray, "returned array doesn't contain the correct elements %s")
}
func TestRandomCharStringWithAlphabet(t *testing.T) {
type args struct {
n int
alphabet string
}
tests := []struct {
name string
args args
want string
}{
{
name: "generated random string has the right length",
args: args{
n: 10,
alphabet: "A",
},
want: "AAAAAAAAAA",
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
assert.Equalf(t, tt.want, RandomCharStringWithAlphabet(tt.args.n, tt.args.alphabet), "RandomCharStringWithAlphabet(%v, %v)", tt.args.n, tt.args.alphabet)
})
}
}
func TestNewSessionCookieForConsole(t *testing.T) {
type args struct {
token string
}
tests := []struct {
name string
args args
want http.Cookie
}{
{
name: "session cookie has the right token an security configuration",
args: args{
token: "jwt-xxxxxxxxx",
},
want: http.Cookie{
Path: "/",
Value: "jwt-xxxxxxxxx",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Name: "token",
MaxAge: 43200,
Expires: time.Now().Add(1 * time.Hour),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
got := NewSessionCookieForConsole(tt.args.token)
assert.Equalf(t, tt.want.Value, got.Value, "NewSessionCookieForConsole(%v)", tt.args.token)
assert.Equalf(t, tt.want.Path, got.Path, "NewSessionCookieForConsole(%v)", tt.args.token)
assert.Equalf(t, tt.want.HttpOnly, got.HttpOnly, "NewSessionCookieForConsole(%v)", tt.args.token)
assert.Equalf(t, tt.want.Name, got.Name, "NewSessionCookieForConsole(%v)", tt.args.token)
assert.Equalf(t, tt.want.MaxAge, got.MaxAge, "NewSessionCookieForConsole(%v)", tt.args.token)
assert.Equalf(t, tt.want.SameSite, got.SameSite, "NewSessionCookieForConsole(%v)", tt.args.token)
})
}
}
func TestExpireSessionCookie(t *testing.T) {
tests := []struct {
name string
want http.Cookie
}{
{
name: "cookie is expired correctly",
want: http.Cookie{
Name: "token",
Value: "",
MaxAge: -1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
got := ExpireSessionCookie()
assert.Equalf(t, tt.want.Name, got.Name, "ExpireSessionCookie()")
assert.Equalf(t, tt.want.Value, got.Value, "ExpireSessionCookie()")
assert.Equalf(t, tt.want.MaxAge, got.MaxAge, "ExpireSessionCookie()")
})
}
}
func Test_isSafeToPreview(t *testing.T) {
type args struct {
str string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "mime type is safe to preview",
args: args{
str: "image/jpeg",
},
want: true,
},
{
name: "mime type is not safe to preview",
args: args{
str: "application/zip",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
assert.Equalf(t, tt.want, isSafeToPreview(tt.args.str), "isSafeToPreview(%v)", tt.args.str)
})
}
}
func TestRandomCharString(t *testing.T) {
type args struct {
n int
}
tests := []struct {
name string
args args
wantLength int
}{
{
name: "valid string",
args: args{
n: 1,
},
wantLength: 1,
}, {
name: "valid string",
args: args{
n: 64,
},
wantLength: 64,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
assert.Equalf(t, tt.wantLength, len(RandomCharString(tt.args.n)), "RandomCharString(%v)", tt.args.n)
})
}
}
func TestValidateEncodedStyles(t *testing.T) {
type args struct {
encodedStyles string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "valid",
args: args{
encodedStyles: "ewogICJiYWNrZ3JvdW5kQ29sb3IiOiAiIzFjMWMxYyIsCiAgImZvbnRDb2xvciI6ICJ3aGl0ZSIsCiAgInNlY29uZGFyeUZvbnRDb2xvciI6ICJncmV5IiwKICAiYm9yZGVyQ29sb3IiOiAieWVsbG93IiwKICAibG9hZGVyQ29sb3IiOiAicmVkIiwKICAiYm94QmFja2dyb3VuZCI6ICIjMDU3OWFmIiwKICAib2tDb2xvciI6ICIjMDhhZjA1IiwKICAiZXJyb3JDb2xvciI6ICIjYmYxZTQ2IiwKICAid2FybkNvbG9yIjogIiNiZmFjMWUiLAogICJsaW5rQ29sb3IiOiAiIzFlYmZiZiIsCiAgImRpc2FibGVkTGlua0NvbG9yIjogIiM5ZGEwYTAiLAogICJob3ZlckxpbmtDb2xvciI6ICIjMGY0ZWJjIiwKICAidGFibGVDb2xvcnMiOiB7CiAgICAiYm9yZGVyIjogIiM0YmJjMGYiLAogICAgImRpc2FibGVkQm9yZGVyIjogIiM3MjhlNjMiLAogICAgImRpc2FibGVkQkciOiAiIzcyOGU2MyIsCiAgICAic2VsZWN0ZWQiOiAiIzU1ZGIwZCIsCiAgICAiZGVsZXRlZERpc2FibGVkIjogIiNlYWI2ZDAiLAogICAgImhvdmVyQ29sb3IiOiAiIzAwZmZmNiIKICB9LAogICJidXR0b25TdHlsZXMiOiB7CiAgICAiYmFja2dyb3VuZENvbG9yIjogIiMwMDczZmYiLAogICAgInRleHRDb2xvciI6ICIjZmZmZmZmIiwKICAgICJob3ZlckNvbG9yIjogIiMyYjhlZmYiLAogICAgImhvdmVyVGV4dCI6ICIjZmZmIiwKICAgICJhY3RpdmVDb2xvciI6ICIjMzg4M2Q4IiwKICAgICJhY3RpdmVUZXh0IjogIiNmZmYiLAogICAgImRpc2FibGVkQ29sb3IiOiAiIzc1OGU4ZCIsCiAgICAiZGlzYWJsZWRUZXh0IjogIiNkOWRkZGQiCiAgfSwKICAic2Vjb25kYXJ5QnV0dG9uU3R5bGVzIjogewogICAgImJhY2tncm91bmRDb2xvciI6ICIjZWEzMzc5IiwKICAgICJ0ZXh0Q29sb3IiOiAiI2VhMzM3OSIsCiAgICAiaG92ZXJDb2xvciI6ICIjZWEzMzAwIiwKICAgICJob3ZlclRleHQiOiAiI2VhMzMwMCIsCiAgICAiYWN0aXZlQ29sb3IiOiAiI2VhMzM3OSIsCiAgICAiYWN0aXZlVGV4dCI6ICIjM2NlYTMzIiwKICAgICJkaXNhYmxlZENvbG9yIjogIiM3ODdjNzciLAogICAgImRpc2FibGVkVGV4dCI6ICIjNzg3Yzc3IgogIH0sCiAgInJlZ3VsYXJCdXR0b25TdHlsZXMiOiB7CiAgICAiYmFja2dyb3VuZENvbG9yIjogIiMwMDczZmYiLAogICAgInRleHRDb2xvciI6ICIjMDA3M2ZmIiwKICAgICJob3ZlckNvbG9yIjogIiMyYjhlZmYiLAogICAgImhvdmVyVGV4dCI6ICIjMDA3M2ZmIiwKICAgICJhY3RpdmVDb2xvciI6ICIjMzg4M2Q4IiwKICAgICJhY3RpdmVUZXh0IjogIiMwMDczZmYiLAogICAgImRpc2FibGVkQ29sb3IiOiAiIzc1OGU4ZCIsCiAgICAiZGlzYWJsZWRUZXh0IjogIiNkOWRkZGQiCiAgfSwKICAiaW5wdXRCb3giOiB7CiAgICAiYm9yZGVyIjogImdyZWVuIiwKICAgICJob3ZlckJvcmRlciI6ICJibHVlIiwKICAgICJ0ZXh0Q29sb3IiOiAid2hpdGUiLAogICAgImJhY2tncm91bmRDb2xvciI6ICIjMjhkNGZmIgogIH0sCiAgInN3aXRjaCI6IHsKICAgICJzd2l0Y2hCYWNrZ3JvdW5kIjogIiMyOGQ0ZmYiLAogICAgImJ1bGxldEJvcmRlckNvbG9yIjogIiNhNmFjYWQiLAogICAgImJ1bGxldEJHQ29sb3IiOiAiI2RjZTFlMiIsCiAgICAiZGlzYWJsZWRCYWNrZ3JvdW5kIjogIiM0NzQ5NDkiLAogICAgImRpc2FibGVkQnVsbGV0Qm9yZGVyQ29sb3IiOiAiIzQ3NDk0OSIsCiAgICAiZGlzYWJsZWRCdWxsZXRCR0NvbG9yIjogIiM3Mzk3YTAiCiAgfQp9",
},
wantErr: false,
},
{
name: "invalid config",
args: args{
encodedStyles: "ewogICJvb3JnbGUiOiAic3MiCn0===",
},
wantErr: true,
},
{
name: "invalid style config",
args: args{
encodedStyles: "e30=",
},
wantErr: true,
},
{
name: "invalid base64",
args: args{
encodedStyles: "duck",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
if tt.wantErr {
assert.NotNilf(t, ValidateEncodedStyles(tt.args.encodedStyles), "Wanted an error")
} else {
assert.Nilf(t, ValidateEncodedStyles(tt.args.encodedStyles), "Did not wanted an error")
}
})
}
}

View File

@@ -1,233 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 api
import (
"context"
"errors"
"fmt"
"log"
"net"
"net/http"
"strings"
errorsApi "github.com/go-openapi/errors"
"github.com/minio/console/models"
"github.com/minio/console/pkg/auth"
"github.com/minio/console/pkg/utils"
"github.com/minio/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 0,
WriteBufferSize: 1024,
}
const (
// websocket base path
wsBasePath = "/ws"
)
type wsMinioClient struct {
// websocket connection.
conn wsConn
// MinIO admin Client
client minioClient
}
// WSConn interface with all functions to be implemented
// by mock when testing, it should include all websocket.Conn
// respective api calls that are used within this project.
type WSConn interface {
writeMessage(messageType int, data []byte) error
close() error
readMessage() (messageType int, p []byte, err error)
remoteAddress() string
}
// Interface implementation
//
// Define the structure of a websocket Connection
type wsConn struct {
conn *websocket.Conn
}
func (c wsConn) writeMessage(messageType int, data []byte) error {
return c.conn.WriteMessage(messageType, data)
}
func (c wsConn) close() error {
return c.conn.Close()
}
func (c wsConn) readMessage() (messageType int, p []byte, err error) {
return c.conn.ReadMessage()
}
func (c wsConn) remoteAddress() string {
clientIP, _, err := net.SplitHostPort(c.conn.RemoteAddr().String())
if err != nil {
// In case there's an error, return an empty string
log.Printf("Invalid ws.clientIP = %s\n", err)
return ""
}
return clientIP
}
// serveWS validates the incoming request and
// upgrades the request to a Websocket protocol.
// Websocket communication will be done depending
// on the path.
// Request should come like ws://<host>:<port>/ws/<api>
func serveWS(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
wsPath := strings.TrimPrefix(req.URL.Path, wsBasePath)
// Perform authentication before upgrading to a Websocket Connection
// authenticate WS connection with Console
session, err := auth.GetClaimsFromTokenInRequest(req)
if err != nil && (errors.Is(err, auth.ErrReadingToken) && !strings.HasPrefix(wsPath, `/objectManager`)) {
ErrorWithContext(ctx, err)
errorsApi.ServeError(w, req, errorsApi.New(http.StatusUnauthorized, "%v", err))
return
}
// If we are using a subpath we are most likely behind a reverse proxy so we most likely
// can't validate the proper Origin since we don't know the source domain, so we are going
// to allow the connection to be upgraded in this case.
if getSubPath() != "/" || getConsoleDevMode() {
upgrader.CheckOrigin = func(_ *http.Request) bool {
return true
}
}
// upgrades the HTTP server connection to the WebSocket protocol.
conn, err := upgrader.Upgrade(w, req, nil)
if err != nil {
ErrorWithContext(ctx, err)
errorsApi.ServeError(w, req, err)
return
}
clientIP := getSourceIPFromHeaders(req)
if clientIP == "" {
if ip, _, err := net.SplitHostPort(conn.RemoteAddr().String()); err == nil {
clientIP = ip
} else {
// In case there's an error, return an empty string
LogError("Invalid ws.RemoteAddr() = %v\n", err)
}
}
switch {
case strings.HasPrefix(wsPath, `/objectManager`):
wsMinioClient, err := newWebSocketMinioClient(conn, session, clientIP)
if err != nil {
ErrorWithContext(ctx, err)
closeWsConn(conn)
return
}
go wsMinioClient.objectManager(session)
default:
// path not found
closeWsConn(conn)
}
}
func newWebSocketMinioClient(conn *websocket.Conn, claims *models.Principal, clientIP string) (*wsMinioClient, error) {
mClient, err := newMinioClient(claims, clientIP)
if err != nil {
LogError("error creating MinioClient:", err)
return nil, err
}
// create a websocket connection interface implementation
// defining the connection to be used
wsConnection := wsConn{conn: conn}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
// create websocket client and handle request
wsMinioClient := &wsMinioClient{conn: wsConnection, client: minioClient}
return wsMinioClient, nil
}
// wsReadClientCtx reads the messages that come from the client
// if the client sends a Close Message the context will be
// canceled. If the connection is closed the goroutine inside
// will return.
func wsReadClientCtx(parentContext context.Context, conn WSConn) context.Context {
// a cancel context is needed to end all goroutines used
ctx, cancel := context.WithCancel(context.Background())
var requestID string
var SessionID string
var UserAgent string
var Host string
var RemoteHost string
if val, o := parentContext.Value(utils.ContextRequestID).(string); o {
requestID = val
}
if val, o := parentContext.Value(utils.ContextRequestUserID).(string); o {
SessionID = val
}
if val, o := parentContext.Value(utils.ContextRequestUserAgent).(string); o {
UserAgent = val
}
if val, o := parentContext.Value(utils.ContextRequestHost).(string); o {
Host = val
}
if val, o := parentContext.Value(utils.ContextRequestRemoteAddr).(string); o {
RemoteHost = val
}
ctx = context.WithValue(ctx, utils.ContextRequestID, requestID)
ctx = context.WithValue(ctx, utils.ContextRequestUserID, SessionID)
ctx = context.WithValue(ctx, utils.ContextRequestUserAgent, UserAgent)
ctx = context.WithValue(ctx, utils.ContextRequestHost, Host)
ctx = context.WithValue(ctx, utils.ContextRequestRemoteAddr, RemoteHost)
go func() {
defer cancel()
for {
_, _, err := conn.readMessage()
if err != nil {
// if errors of type websocket.CloseError and is Unexpected
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) {
ErrorWithContext(ctx, fmt.Errorf("error unexpected CloseError on ReadMessage: %v", err))
return
}
// Not all errors are of type websocket.CloseError.
if _, ok := err.(*websocket.CloseError); !ok {
ErrorWithContext(ctx, fmt.Errorf("error on ReadMessage: %v", err))
return
}
// else is an expected Close Error
return
}
}
}()
return ctx
}
// closeWsConn sends Close Message and closes the websocket connection
func closeWsConn(conn *websocket.Conn) {
conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
conn.Close()
}

View File

@@ -1,288 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// 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 api
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
"github.com/minio/console/models"
"github.com/minio/websocket"
)
func (wsc *wsMinioClient) objectManager(session *models.Principal) {
// Storage of Cancel Contexts for this connection
var cancelContexts sync.Map
// Initial goroutine
defer func() {
// We close socket at the end of requests
wsc.conn.close()
cancelContexts.Range(func(key, value interface{}) bool {
cancelFunc := value.(context.CancelFunc)
cancelFunc()
cancelContexts.Delete(key)
return true
})
}()
writeChannel := make(chan WSResponse)
done := make(chan interface{})
sendWSResponse := func(r WSResponse) {
select {
case writeChannel <- r:
case <-done:
}
}
// Read goroutine
go func() {
defer close(writeChannel)
for {
select {
case <-done:
return
default:
}
mType, message, err := wsc.conn.readMessage()
if err != nil {
LogInfo("Error while reading objectManager message: %s", err)
return
}
if mType == websocket.TextMessage {
// We get request data & review information
var messageRequest ObjectsRequest
if err := json.Unmarshal(message, &messageRequest); err != nil {
LogInfo("Error on message request unmarshal: %s", err)
continue
}
// new message, new context
ctx, cancel := context.WithCancel(context.Background())
// We store the cancel func associated with this request
cancelContexts.Store(messageRequest.RequestID, cancel)
switch messageRequest.Mode {
case "objects", "rewind":
// cancel all previous open objects requests for listing
cancelContexts.Range(func(key, value interface{}) bool {
rid := key.(int64)
if rid < messageRequest.RequestID {
cancelFunc := value.(context.CancelFunc)
cancelFunc()
cancelContexts.Delete(key)
}
return true
})
}
const itemsPerBatch = 1000
switch messageRequest.Mode {
case "close":
return
case "cancel":
// if we have that request id, cancel it
if cancelFunc, ok := cancelContexts.Load(messageRequest.RequestID); ok {
cancelFunc.(context.CancelFunc)()
cancelContexts.Delete(messageRequest.RequestID)
}
case "objects":
// start listing and writing to web socket
objectRqConfigs, err := getObjectsOptionsFromReq(messageRequest)
if err != nil {
LogInfo(fmt.Sprintf("Error during Objects OptionsParse %s", err.Error()))
sendWSResponse(WSResponse{
RequestID: messageRequest.RequestID,
Error: ErrorWithContext(ctx, err),
Prefix: messageRequest.Prefix,
BucketName: messageRequest.BucketName,
})
return
}
var buffer []ObjectResponse
for lsObj := range startObjectsListing(ctx, wsc.client, objectRqConfigs) {
if lsObj.Err != nil {
sendWSResponse(WSResponse{
RequestID: messageRequest.RequestID,
Error: ErrorWithContext(ctx, lsObj.Err),
Prefix: messageRequest.Prefix,
BucketName: messageRequest.BucketName,
})
continue
}
// if the key is same as requested prefix it would be nested directory object, so skip
// and show only objects under the prefix
// E.g:
// bucket/prefix1/prefix2/ -- this should be skipped from list item.
// bucket/prefix1/prefix2/an-object
// bucket/prefix1/prefix2/another-object
if messageRequest.Prefix != lsObj.Key {
objItem := ObjectResponse{
Name: lsObj.Key,
Size: lsObj.Size,
LastModified: lsObj.LastModified.Format(time.RFC3339),
VersionID: lsObj.VersionID,
IsLatest: lsObj.IsLatest,
DeleteMarker: lsObj.IsDeleteMarker,
}
buffer = append(buffer, objItem)
}
if len(buffer) >= itemsPerBatch {
sendWSResponse(WSResponse{
RequestID: messageRequest.RequestID,
Data: buffer,
})
buffer = nil
}
}
if len(buffer) > 0 {
sendWSResponse(WSResponse{
RequestID: messageRequest.RequestID,
Data: buffer,
})
}
sendWSResponse(WSResponse{
RequestID: messageRequest.RequestID,
RequestEnd: true,
})
// if we have that request id, cancel it
if cancelFunc, ok := cancelContexts.Load(messageRequest.RequestID); ok {
cancelFunc.(context.CancelFunc)()
cancelContexts.Delete(messageRequest.RequestID)
}
case "rewind":
// start listing and writing to web socket
objectRqConfigs, err := getObjectsOptionsFromReq(messageRequest)
if err != nil {
LogInfo(fmt.Sprintf("Error during Objects OptionsParse %s", err.Error()))
sendWSResponse(WSResponse{
RequestID: messageRequest.RequestID,
Error: ErrorWithContext(ctx, err),
Prefix: messageRequest.Prefix,
BucketName: messageRequest.BucketName,
})
return
}
clientIP := wsc.conn.remoteAddress()
s3Client, err := newS3BucketClient(session, objectRqConfigs.BucketName, objectRqConfigs.Prefix, clientIP)
if err != nil {
sendWSResponse(WSResponse{
RequestID: messageRequest.RequestID,
Error: ErrorWithContext(ctx, err),
Prefix: messageRequest.Prefix,
BucketName: messageRequest.BucketName,
})
return
}
mcS3C := mcClient{client: s3Client}
var buffer []ObjectResponse
for lsObj := range startRewindListing(ctx, mcS3C, objectRqConfigs) {
if lsObj.Err != nil {
sendWSResponse(WSResponse{
RequestID: messageRequest.RequestID,
Error: ErrorWithContext(ctx, lsObj.Err.ToGoError()),
Prefix: messageRequest.Prefix,
BucketName: messageRequest.BucketName,
})
continue
}
name := strings.Replace(lsObj.URL.Path, fmt.Sprintf("/%s/", objectRqConfigs.BucketName), "", 1)
objItem := ObjectResponse{
Name: name,
Size: lsObj.Size,
LastModified: lsObj.Time.Format(time.RFC3339),
VersionID: lsObj.VersionID,
IsLatest: lsObj.IsLatest,
DeleteMarker: lsObj.IsDeleteMarker,
}
buffer = append(buffer, objItem)
if len(buffer) >= itemsPerBatch {
sendWSResponse(WSResponse{
RequestID: messageRequest.RequestID,
Data: buffer,
})
buffer = nil
}
}
if len(buffer) > 0 {
sendWSResponse(WSResponse{
RequestID: messageRequest.RequestID,
Data: buffer,
})
}
sendWSResponse(WSResponse{
RequestID: messageRequest.RequestID,
RequestEnd: true,
})
// if we have that request id, cancel it
if cancelFunc, ok := cancelContexts.Load(messageRequest.RequestID); ok {
cancelFunc.(context.CancelFunc)()
cancelContexts.Delete(messageRequest.RequestID)
}
}
}
}
}()
defer close(done)
for writeM := range writeChannel {
jsonData, err := json.Marshal(writeM)
if err != nil {
LogInfo("Error while marshaling the response: %s", err)
return
}
err = wsc.conn.writeMessage(websocket.TextMessage, jsonData)
if err != nil {
LogInfo("Error while writing the message: %s", err)
return
}
}
}

959
bindata_assetfs.go Normal file

File diff suppressed because one or more lines are too long

65
cluster/cluster.go Normal file
View File

@@ -0,0 +1,65 @@
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2019 MinIO, Inc.
//
// 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 cluster
import (
operator "github.com/minio/operator/pkg/client/clientset/versioned"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
certutil "k8s.io/client-go/util/cert"
)
// getTLSClientConfig will return the right TLS configuration for the K8S client based on the configured TLS certificate
func getTLSClientConfig() rest.TLSClientConfig {
var defaultRootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
var customRootCAFile = getK8sAPIServerTLSRootCA()
tlsClientConfig := rest.TLSClientConfig{}
// if console is running inside k8s by default he will have access to the CA Cert from the k8s local authority
if _, err := certutil.NewPool(defaultRootCAFile); err == nil {
tlsClientConfig.CAFile = defaultRootCAFile
}
// if the user explicitly define a custom CA certificate, instead, we will use that
if customRootCAFile != "" {
if _, err := certutil.NewPool(customRootCAFile); err == nil {
tlsClientConfig.CAFile = customRootCAFile
}
}
return tlsClientConfig
}
// This operation will run only once at console startup
var tlsClientConfig = getTLSClientConfig()
func GetK8sConfig(token string) *rest.Config {
config := &rest.Config{
Host: GetK8sAPIServer(),
TLSClientConfig: tlsClientConfig,
APIPath: "/",
BearerToken: token,
}
return config
}
// OperatorClient returns an operator client using GetK8sConfig for its config
func OperatorClient(token string) (*operator.Clientset, error) {
return operator.NewForConfig(GetK8sConfig(token))
}
// K8sClient returns kubernetes client using GetK8sConfig for its config
func K8sClient(token string) (*kubernetes.Clientset, error) {
return kubernetes.NewForConfig(GetK8sConfig(token))
}

168
cluster/config.go Normal file
View File

@@ -0,0 +1,168 @@
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2019 MinIO, Inc.
//
// 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 cluster
import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"regexp"
"strings"
"time"
"github.com/minio/minio/pkg/env"
)
var (
errCantDetermineMinIOImage = errors.New("can't determine MinIO Image")
errCantDetermineMCImage = errors.New("can't determine MC Image")
)
func GetK8sAPIServer() string {
// if console is running inside a k8s pod KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT will contain the k8s api server apiServerAddress
// if console is not running inside k8s by default will look for the k8s api server on localhost:8001 (kubectl proxy)
// NOTE: using kubectl proxy is for local development only, since every request send to localhost:8001 will bypass service account authentication
// more info here: https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#directly-accessing-the-rest-api
// you can override this using MCS_K8S_API_SERVER, ie use the k8s cluster from `kubectl config view`
host, port := env.Get("KUBERNETES_SERVICE_HOST", ""), env.Get("KUBERNETES_SERVICE_PORT", "")
apiServerAddress := "http://localhost:8001"
if host != "" && port != "" {
apiServerAddress = "https://" + net.JoinHostPort(host, port)
}
return env.Get(McsK8sAPIServer, apiServerAddress)
}
// If MCS_K8S_API_SERVER_TLS_ROOT_CA is true mcs will load the certificate into the
// http.client rootCAs pool, this is useful for testing an k8s ApiServer or when working with self-signed certificates
func getK8sAPIServerTLSRootCA() string {
return strings.TrimSpace(env.Get(McsK8SAPIServerTLSRootCA, ""))
}
// GetNsFromFile assumes console is running inside a k8s pod and extract the current namespace from the
// /var/run/secrets/kubernetes.io/serviceaccount/namespace file
func GetNsFromFile() string {
dat, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
if err != nil {
return "default"
}
return string(dat)
}
// This operation will run only once at console startup
var namespace = GetNsFromFile()
// Returns the namespace in which the controller is installed
func GetNs() string {
return env.Get(McsNamespace, namespace)
}
// getLatestMinIOImage returns the latest docker image for MinIO if found on the internet
func getLatestMinIOImage(client HTTPClientI) (*string, error) {
resp, err := client.Get("https://dl.min.io/server/minio/release/linux-amd64/")
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var re = regexp.MustCompile(`(?m)\.\/minio\.(RELEASE.*?Z)"`)
// look for a single match
matches := re.FindAllStringSubmatch(string(body), 1)
for i := range matches {
release := matches[i][1]
dockerImage := fmt.Sprintf("minio/minio:%s", release)
return &dockerImage, nil
}
return nil, errCantDetermineMinIOImage
}
var latestMinIOImage, errLatestMinIOImage = getLatestMinIOImage(
&HTTPClient{
Client: &http.Client{
Timeout: 4 * time.Second,
},
})
// GetMinioImage returns the image URL to be used when deploying a MinIO instance, if there is
// a preferred image to be used (configured via ENVIRONMENT VARIABLES) GetMinioImage will return that
// if not, GetMinioImage will try to obtain the image URL for the latest version of MinIO and return that
func GetMinioImage() (*string, error) {
image := strings.TrimSpace(env.Get(McsMinioImage, ""))
// if there is a preferred image configured by the user we'll always return that
if image != "" {
return &image, nil
}
if errLatestMinIOImage != nil {
return nil, errLatestMinIOImage
}
return latestMinIOImage, nil
}
// GetLatestMinioImage returns the latest image URL on minio repository
func GetLatestMinioImage(client HTTPClientI) (*string, error) {
latestMinIOImage, err := getLatestMinIOImage(client)
if err != nil {
return nil, err
}
return latestMinIOImage, nil
}
// getLatestMCImage returns the latest docker image for MC if found on the internet
func getLatestMCImage() (*string, error) {
// Create an http client with a 4 second timeout
client := http.Client{
Timeout: 4 * time.Second,
}
resp, err := client.Get("https://dl.min.io/client/mc/release/linux-amd64/")
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var re = regexp.MustCompile(`(?m)\.\/mc\.(RELEASE.*?Z)"`)
// look for a single match
matches := re.FindAllStringSubmatch(string(body), 1)
for i := range matches {
release := matches[i][1]
dockerImage := fmt.Sprintf("minio/mc:%s", release)
return &dockerImage, nil
}
return nil, errCantDetermineMCImage
}
var latestMCImage, errLatestMCImage = getLatestMCImage()
func GetMCImage() (*string, error) {
image := strings.TrimSpace(env.Get(McsMCImage, ""))
// if there is a preferred image configured by the user we'll always return that
if image != "" {
return &image, nil
}
if errLatestMCImage != nil {
return nil, errLatestMCImage
}
return latestMCImage, nil
}

25
cluster/const.go Normal file
View File

@@ -0,0 +1,25 @@
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2019 MinIO, Inc.
//
// 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 cluster
const (
McsK8sAPIServer = "MCS_K8S_API_SERVER"
McsK8SAPIServerTLSRootCA = "MCS_K8S_API_SERVER_TLS_ROOT_CA"
McsMinioImage = "MCS_MINIO_IMAGE"
McsMCImage = "MCS_MC_IMAGE"
McsNamespace = "MCS_NAMESPACE"
)

View File

@@ -1,7 +1,5 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2020 MinIO, Inc.
//
// 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
@@ -15,30 +13,28 @@
//
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
package cluster
import (
"context"
"github.com/go-openapi/strfmt"
"net/http"
)
// IamEntity iam entity
// HTTPClientI interface with all functions to be implemented
// by mock when testing, it should include all HttpClient respective api calls
// that are used within this project.
type HTTPClientI interface {
Get(url string) (resp *http.Response, err error)
}
// HTTPClient Interface implementation
//
// swagger:model iamEntity
type IamEntity string
// Validate validates this iam entity
func (m IamEntity) Validate(formats strfmt.Registry) error {
return nil
// Define the structure of a http client and define the functions that are actually used
type HTTPClient struct {
Client *http.Client
}
// ContextValidate validates this iam entity based on context it is used
func (m IamEntity) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
// Get implements http.Client.Get()
func (c *HTTPClient) Get(url string) (resp *http.Response, err error) {
return c.Client.Get(url)
}

View File

@@ -1,96 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 main
import (
"context"
"fmt"
"net/http"
"strconv"
"time"
"github.com/minio/console/pkg/logger"
"github.com/minio/cli"
"github.com/minio/console/api"
)
var appCmds = []cli.Command{
serverCmd,
updateCmd,
}
// StartServer starts the console service
func StartServer(ctx *cli.Context) error {
if err := loadAllCerts(ctx); err != nil {
// Log this as a warning and continue running console without TLS certificates
api.LogError("Unable to load certs: %v", err)
}
xctx := context.Background()
transport := api.PrepareSTSClientTransport(api.LocalAddress).Transport.(*http.Transport)
if err := logger.InitializeLogger(xctx, transport); err != nil {
fmt.Println("error InitializeLogger", err)
logger.CriticalIf(xctx, err)
}
// custom error configuration
api.LogInfo = logger.Info
api.LogError = logger.Error
api.LogIf = logger.LogIf
var rctx api.Context
if err := rctx.Load(ctx); err != nil {
api.LogError("argument validation failed: %v", err)
return err
}
server, err := buildServer()
if err != nil {
api.LogError("Unable to initialize console server: %v", err)
return err
}
server.Host = rctx.Host
server.Port = rctx.HTTPPort
// set conservative timesout for uploads
server.ReadTimeout = 1 * time.Hour
// no timeouts for response for downloads
server.WriteTimeout = 0
api.Port = strconv.Itoa(server.Port)
api.Hostname = server.Host
if len(api.GlobalPublicCerts) > 0 {
// If TLS certificates are provided enforce the HTTPS schema, meaning console will redirect
// plain HTTP connections to HTTPS server
server.EnabledListeners = []string{"http", "https"}
server.TLSPort = rctx.HTTPSPort
// Need to store tls-port, tls-host un config variables so secure.middleware can read from there
api.TLSPort = strconv.Itoa(server.TLSPort)
api.Hostname = rctx.Host
api.TLSRedirect = rctx.TLSRedirect
}
defer server.Shutdown()
if err = server.Serve(); err != nil {
server.Logf("error serving API: %v", err)
return err
}
return nil
}

View File

@@ -1,178 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 main
import (
"fmt"
"os"
"path/filepath"
"syscall"
"github.com/go-openapi/loads"
"github.com/jessevdk/go-flags"
"github.com/minio/cli"
"github.com/minio/console/api"
"github.com/minio/console/api/operations"
"github.com/minio/console/pkg/certs"
)
// starts the server
var serverCmd = cli.Command{
Name: "server",
Aliases: []string{"srv"},
Usage: "Start MinIO Console server",
Action: StartServer,
Flags: []cli.Flag{
cli.StringFlag{
Name: "host",
Value: api.GetHostname(),
Usage: "bind to a specific HOST, HOST can be an IP or hostname",
},
cli.IntFlag{
Name: "port",
Value: api.GetPort(),
Usage: "bind to specific HTTP port",
},
// This is kept here for backward compatibility,
// hostname's do not have HTTP or HTTPs
// hostnames are opaque so using --host
// works for both HTTP and HTTPS setup.
cli.StringFlag{
Name: "tls-host",
Value: api.GetHostname(),
Hidden: true,
},
cli.StringFlag{
Name: "certs-dir",
Value: certs.GlobalCertsCADir.Get(),
Usage: "path to certs directory",
},
cli.IntFlag{
Name: "tls-port",
Value: api.GetTLSPort(),
Usage: "bind to specific HTTPS port",
},
cli.StringFlag{
Name: "tls-redirect",
Value: api.GetTLSRedirect(),
Usage: "toggle HTTP->HTTPS redirect",
},
cli.StringFlag{
Name: "tls-certificate",
Value: "",
Usage: "path to TLS public certificate",
Hidden: true,
},
cli.StringFlag{
Name: "tls-key",
Value: "",
Usage: "path to TLS private key",
Hidden: true,
},
cli.StringFlag{
Name: "tls-ca",
Value: "",
Usage: "path to TLS Certificate Authority",
Hidden: true,
},
},
}
func buildServer() (*api.Server, error) {
swaggerSpec, err := loads.Embedded(api.SwaggerJSON, api.FlatSwaggerJSON)
if err != nil {
return nil, err
}
consoleAPI := operations.NewConsoleAPI(swaggerSpec)
consoleAPI.Logger = api.LogInfo
server := api.NewServer(consoleAPI)
parser := flags.NewParser(server, flags.Default)
parser.ShortDescription = "MinIO Console Server"
parser.LongDescription = swaggerSpec.Spec().Info.Description
server.ConfigureFlags()
// register all APIs
server.ConfigureAPI()
for _, optsGroup := range consoleAPI.CommandLineOptionsGroups {
_, err := parser.AddGroup(optsGroup.ShortDescription, optsGroup.LongDescription, optsGroup.Options)
if err != nil {
return nil, err
}
}
if _, err := parser.Parse(); err != nil {
return nil, err
}
return server, nil
}
func loadAllCerts(ctx *cli.Context) error {
var err error
// Set all certs and CAs directories path
certs.GlobalCertsDir, _, err = certs.NewConfigDirFromCtx(ctx, "certs-dir", certs.DefaultCertsDir.Get)
if err != nil {
return err
}
certs.GlobalCertsCADir = &certs.ConfigDir{Path: filepath.Join(certs.GlobalCertsDir.Get(), certs.CertsCADir)}
// check if certs and CAs directories exists or can be created
if err = certs.MkdirAllIgnorePerm(certs.GlobalCertsCADir.Get()); err != nil {
return fmt.Errorf("unable to create certs CA directory at %s: failed with %w", certs.GlobalCertsCADir.Get(), err)
}
// load the certificates and the CAs
api.GlobalRootCAs, api.GlobalPublicCerts, api.GlobalTLSCertsManager, err = certs.GetAllCertificatesAndCAs()
if err != nil {
return fmt.Errorf("unable to load certificates at %s: failed with %w", certs.GlobalCertsDir.Get(), err)
}
{
// TLS flags from swagger server, used to support VMware vsphere operator version.
swaggerServerCertificate := ctx.String("tls-certificate")
swaggerServerCertificateKey := ctx.String("tls-key")
swaggerServerCACertificate := ctx.String("tls-ca")
// load tls cert and key from swagger server tls-certificate and tls-key flags
if swaggerServerCertificate != "" && swaggerServerCertificateKey != "" {
if err = api.GlobalTLSCertsManager.AddCertificate(swaggerServerCertificate, swaggerServerCertificateKey); err != nil {
return err
}
x509Certs, err := certs.ParsePublicCertFile(swaggerServerCertificate)
if err == nil {
api.GlobalPublicCerts = append(api.GlobalPublicCerts, x509Certs...)
}
}
// load ca cert from swagger server tls-ca flag
if swaggerServerCACertificate != "" {
caCert, caCertErr := os.ReadFile(swaggerServerCACertificate)
if caCertErr == nil {
api.GlobalRootCAs.AppendCertsFromPEM(caCert)
}
}
}
if api.GlobalTLSCertsManager != nil {
api.GlobalTLSCertsManager.ReloadOnSignal(syscall.SIGHUP)
}
return nil
}

View File

@@ -1,154 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// 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 main
import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"runtime"
"strings"
"time"
"github.com/blang/semver/v4"
"github.com/cheggaaa/pb/v3"
"github.com/minio/cli"
"github.com/minio/console/pkg"
"github.com/minio/selfupdate"
)
func getUpdateTransport(timeout time.Duration) http.RoundTripper {
var updateTransport http.RoundTripper = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: timeout,
KeepAlive: timeout,
DualStack: true,
}).DialContext,
IdleConnTimeout: timeout,
TLSHandshakeTimeout: timeout,
ExpectContinueTimeout: timeout,
DisableCompression: true,
}
return updateTransport
}
func getUpdateReaderFromURL(u string, transport http.RoundTripper) (io.ReadCloser, int64, error) {
clnt := &http.Client{
Transport: transport,
}
req, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
return nil, -1, err
}
resp, err := clnt.Do(req)
if err != nil {
return nil, -1, err
}
return resp.Body, resp.ContentLength, nil
}
const defaultPubKey = "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
func getLatestRelease(tr http.RoundTripper) (string, error) {
releaseURL := "https://api.github.com/repos/minio/console/releases/latest"
body, _, err := getUpdateReaderFromURL(releaseURL, tr)
if err != nil {
return "", fmt.Errorf("unable to access github release URL %w", err)
}
defer body.Close()
lm := make(map[string]interface{})
if err = json.NewDecoder(body).Decode(&lm); err != nil {
return "", err
}
rel, ok := lm["tag_name"].(string)
if !ok {
return "", errors.New("unable to find latest release tag")
}
return rel, nil
}
// update console in-place
var updateCmd = cli.Command{
Name: "update",
Usage: "update console to latest release",
Action: updateInplace,
}
func updateInplace(_ *cli.Context) error {
transport := getUpdateTransport(30 * time.Second)
rel, err := getLatestRelease(transport)
if err != nil {
return err
}
latest, err := semver.Make(strings.TrimPrefix(rel, "v"))
if err != nil {
return err
}
current, err := semver.Make(pkg.Version)
if err != nil {
return err
}
if current.GTE(latest) {
fmt.Printf("You are already running the latest version v%v.\n", pkg.Version)
return nil
}
consoleBin := fmt.Sprintf("https://github.com/minio/console/releases/download/%s/console-%s-%s", rel, runtime.GOOS, runtime.GOARCH)
reader, length, err := getUpdateReaderFromURL(consoleBin, transport)
if err != nil {
return fmt.Errorf("unable to fetch binary from %s: %w", consoleBin, err)
}
minisignPubkey := os.Getenv("CONSOLE_MINISIGN_PUBKEY")
if minisignPubkey == "" {
minisignPubkey = defaultPubKey
}
v := selfupdate.NewVerifier()
if err = v.LoadFromURL(consoleBin+".minisig", minisignPubkey, transport); err != nil {
return fmt.Errorf("unable to fetch binary signature for %s: %w", consoleBin, err)
}
opts := selfupdate.Options{
Verifier: v,
}
tmpl := `{{ red "Downloading:" }} {{bar . (red "[") (green "=") (red "]")}} {{speed . | rndcolor }}`
bar := pb.ProgressBarTemplate(tmpl).Start64(length)
barReader := bar.NewProxyReader(reader)
if err = selfupdate.Apply(barReader, opts); err != nil {
bar.Finish()
if rerr := selfupdate.RollbackError(err); rerr != nil {
return rerr
}
return err
}
bar.Finish()
fmt.Printf("Updated 'console' to latest release %s\n", rel)
return nil
}

View File

@@ -1,5 +1,5 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
// Copyright (c) 2020 MinIO, Inc.
//
// 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
@@ -23,38 +23,44 @@ import (
"sort"
"time"
"github.com/minio/mcs/pkg"
"github.com/minio/minio/pkg/console"
"github.com/minio/minio/pkg/trie"
"github.com/minio/minio/pkg/words"
"github.com/minio/cli"
"github.com/minio/console/pkg"
"github.com/minio/pkg/v3/console"
"github.com/minio/pkg/v3/trie"
"github.com/minio/pkg/v3/words"
)
// Help template for Console.
var consoleHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
// Help template for mcs.
var mcsHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
DESCRIPTION:
{{.Description}}
{{.Description}}
USAGE:
{{.HelpName}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}}{{end}} [ARGS...]
{{.HelpName}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}}{{end}} [ARGS...]
COMMANDS:
{{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{if .VisibleFlags}}
{{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{if .VisibleFlags}}
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
VERSION:
{{.Version}}
{{.Version}}
`
var appCmds = []cli.Command{
serverCmd,
}
func newApp(name string) *cli.App {
// Collection of console commands currently supported are.
// Collection of mcs commands currently supported are.
var commands []cli.Command
// Collection of console commands currently supported in a trie tree.
// Collection of mcs commands currently supported in a trie tree.
commandsTree := trie.NewTrie()
// registerCommand registers a cli command.
@@ -70,19 +76,21 @@ func newApp(name string) *cli.App {
findClosestCommands := func(command string) []string {
var closestCommands []string
closestCommands = append(closestCommands, commandsTree.PrefixMatch(command)...)
for _, value := range commandsTree.PrefixMatch(command) {
closestCommands = append(closestCommands, value.(string))
}
sort.Strings(closestCommands)
// Suggest other close commands - allow missed, wrongly added and
// even transposed characters
for _, value := range commandsTree.Walk(commandsTree.Root()) {
if sort.SearchStrings(closestCommands, value) < len(closestCommands) {
if sort.SearchStrings(closestCommands, value.(string)) < len(closestCommands) {
continue
}
// 2 is arbitrary and represents the max
// allowed number of typed errors
if words.DamerauLevenshteinDistance(command, value) < 2 {
closestCommands = append(closestCommands, value)
if words.DamerauLevenshteinDistance(command, value.(string)) < 2 {
closestCommands = append(closestCommands, value.(string))
}
}
@@ -100,13 +108,13 @@ func newApp(name string) *cli.App {
app.Author = "MinIO, Inc."
app.Usage = "MinIO Console Server"
app.Description = `MinIO Console Server`
app.Copyright = "(c) 2021 MinIO, Inc."
app.Copyright = "(c) 2020 MinIO, Inc."
app.Compiled, _ = time.Parse(time.RFC3339, pkg.ReleaseTime)
app.Commands = commands
app.HideHelpCommand = true // Hide `help, h` command, we already have `minio --help`.
app.CustomAppHelpTemplate = consoleHelpTemplate
app.CommandNotFound = func(_ *cli.Context, command string) {
console.Printf("%s is not a console sub-command. See console --help.\n", command)
app.CustomAppHelpTemplate = mcsHelpTemplate
app.CommandNotFound = func(ctx *cli.Context, command string) {
console.Printf("%s is not a mcs sub-command. See mcs --help.\n", command)
closestCommands := findClosestCommands(command)
if len(closestCommands) > 0 {
console.Println()

132
cmd/mcs/server.go Normal file
View File

@@ -0,0 +1,132 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// 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 main
import (
"fmt"
"log"
"os"
"github.com/go-openapi/loads"
"github.com/jessevdk/go-flags"
"github.com/minio/cli"
"github.com/minio/mcs/restapi"
"github.com/minio/mcs/restapi/operations"
)
// starts the server
var serverCmd = cli.Command{
Name: "server",
Aliases: []string{"srv"},
Usage: "starts mcs server",
Action: startServer,
Flags: []cli.Flag{
cli.StringFlag{
Name: "host",
Value: restapi.GetHostname(),
Usage: "HTTP server hostname",
},
cli.IntFlag{
Name: "port",
Value: restapi.GetPort(),
Usage: "HTTP Server port",
},
cli.StringFlag{
Name: "tls-host",
Value: restapi.GetSSLHostname(),
Usage: "HTTPS server hostname",
},
cli.IntFlag{
Name: "tls-port",
Value: restapi.GetSSLPort(),
Usage: "HTTPS server port",
},
cli.StringFlag{
Name: "tls-certificate",
Value: "",
Usage: "filename of public cert",
},
cli.StringFlag{
Name: "tls-key",
Value: "",
Usage: "filename of private key",
},
},
}
// starts the controller
func startServer(ctx *cli.Context) error {
swaggerSpec, err := loads.Embedded(restapi.SwaggerJSON, restapi.FlatSwaggerJSON)
if err != nil {
log.Fatalln(err)
}
api := operations.NewMcsAPI(swaggerSpec)
server := restapi.NewServer(api)
defer server.Shutdown()
parser := flags.NewParser(server, flags.Default)
parser.ShortDescription = "MinIO Console Server"
parser.LongDescription = swaggerSpec.Spec().Info.Description
server.ConfigureFlags()
for _, optsGroup := range api.CommandLineOptionsGroups {
_, err := parser.AddGroup(optsGroup.ShortDescription, optsGroup.LongDescription, optsGroup.Options)
if err != nil {
log.Fatalln(err)
}
}
if _, err := parser.Parse(); err != nil {
code := 1
if fe, ok := err.(*flags.Error); ok {
if fe.Type == flags.ErrHelp {
code = 0
}
}
os.Exit(code)
}
server.Host = ctx.String("host")
server.Port = ctx.Int("port")
restapi.Hostname = ctx.String("host")
restapi.Port = fmt.Sprintf("%v", ctx.Int("port"))
tlsCertificatePath := ctx.String("tls-certificate")
tlsCertificateKeyPath := ctx.String("tls-key")
if tlsCertificatePath != "" && tlsCertificateKeyPath != "" {
server.TLSCertificate = flags.Filename(tlsCertificatePath)
server.TLSCertificateKey = flags.Filename(tlsCertificateKeyPath)
// If TLS certificates are provided enforce the HTTPS schema, meaning mcs will redirect
// plain HTTP connections to HTTPS server
server.EnabledListeners = []string{"http", "https"}
server.TLSPort = ctx.Int("tls-port")
server.TLSHost = ctx.String("tls-host")
// Need to store tls-port, tls-host un config variables so secure.middleware can read from there
restapi.TLSPort = fmt.Sprintf("%v", ctx.Int("tls-port"))
restapi.TLSHostname = ctx.String("tls-host")
restapi.TLSRedirect = "on"
}
server.ConfigureAPI()
if err := server.Serve(); err != nil {
log.Fatalln(err)
}
return nil
}

View File

@@ -1,33 +0,0 @@
#!/bin/bash
set -e
# Enable tracing if set.
[ -n "$BASH_XTRACEFD" ] && set -x
## All binaries are static make sure to disable CGO.
export CGO_ENABLED=0
## List of architectures and OS to test cross compilation.
SUPPORTED_OSARCH_DEFAULTS="linux/ppc64le linux/mips64 linux/arm64 linux/s390x darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64"
SUPPORTED_OSARCH=${1:-$SUPPORTED_OSARCH_DEFAULTS}
_build() {
local osarch=$1
IFS=/ read -r -a arr <<<"$osarch"
os="${arr[0]}"
arch="${arr[1]}"
package=$(go list -f '{{.ImportPath}}' ./cmd/console)
printf -- "--> %15s:%s\n" "${osarch}" "${package}"
# go build -trimpath to build the binary.
GOOS=$os GOARCH=$arch GO111MODULE=on go build -trimpath --tags=kqueue --ldflags "-s -w" -o /dev/null ./cmd/console
}
main() {
echo "Testing builds for OS/Arch: ${SUPPORTED_OSARCH}"
for each_osarch in ${SUPPORTED_OSARCH}; do
_build "${each_osarch}"
done
}
main "$@"

View File

@@ -1,35 +0,0 @@
# LDIF fragment to create group branch under root
dn: uid=billy,dc=example,dc=org
uid: billy
cn: billy
sn: 3
objectClass: top
objectClass: posixAccount
objectClass: inetOrgPerson
loginShell: /bin/bash
homeDirectory: /home/billy
uidNumber: 14583102
gidNumber: 14564100
userPassword: {SSHA}j3lBh1Seqe4rqF1+NuWmjhvtAni1JC5A
mail: billy@example.org
gecos: Billy User
# Create base group
dn: ou=groups,dc=example,dc=org
objectclass:organizationalunit
ou: groups
description: generic groups branch
# create consoleAdmin group (this already exists on minio and have a policy of s3::*)
dn: cn=consoleAdmin,ou=groups,dc=example,dc=org
objectClass: top
objectClass: posixGroup
gidNumber: 678
# Assing group to new user
dn: cn=consoleAdmin,ou=groups,dc=example,dc=org
changetype: modify
add: memberuid
memberuid: billy

39
docs/mcs_operator_mode.md Normal file
View File

@@ -0,0 +1,39 @@
# Running MCS in Operator mode
`MCS` will authenticate against `Kubernetes`using bearer tokens via HTTP `Authorization` header. The user will provide this token once
in the login form, MCS will validate it against Kubernetes (list apis) and if valid will generate and return a new MCS sessions
with encrypted claims (the user Service account token will be inside the JWT in the data field)
# Kubernetes
The provided `JWT token` corresponds to the `Kubernetes service account` that `MCS` will use to run tasks on behalf of the
user, ie: list, create, edit, delete tenants, storage class, etc.
# Development
If console is running inside a k8s pod `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` will contain the k8s api server apiServerAddress
if console is not running inside k8s by default will look for the k8s api server on `localhost:8001` (kubectl proxy)
If you are running mcs in your local environment and wish to make request to `Kubernetes` you can set `MCS_K8S_API_SERVER`, if
the environment variable is not present by default `MCS` will use `"http://localhost:8001"`, additionally you will need to set the
`MCS_OPERATOR_MODE=on` variable to make MCS display the Operator UI.
NOTE: using `kubectl` proxy is for local development only, since every request send to localhost:8001 will bypass service account authentication
more info here: https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#directly-accessing-the-rest-api
you can override this using `MCS_K8S_API_SERVER`, ie use the k8s cluster from `kubectl config view`
## Extract the Service account token and use it with MCS
For local development you can use the jwt associated to the `mcs-sa` service account, you can get the token running
the following command in your terminal:
```
kubectl get secret $(kubectl get serviceaccount mcs-sa -o jsonpath="{.secrets[0].name}") -o jsonpath="{.data.token}" | base64 --decode
```
Then run the mcs server
```
MCS_OPERATOR_MODE=on ./mcs server
```

Some files were not shown because too many files have changed in this diff Show More