Compare commits

...

6 Commits

Author SHA1 Message Date
Minio Trusted
14fe7c1269 update to v0.6.6 release with service 2021-03-27 17:06:01 -07:00
Lenin Alevski
b6938a5888 Check xcerts.Manager is initialized before adding certificates (#673) 2021-03-27 15:35:45 -07:00
Minio Trusted
901358e8d4 update to release v0.6.5 2021-03-27 13:26:58 -07:00
Lenin Alevski
5155aef802 Add support to load certificates from swagger tls flags (#672)
- Add support to load certificates via `--tls-certificate`, `--tls-key`
  and `--tls-ca` flags (standard TLS flags for the swagger server)
- Certificate keypair will be added to the certificate pool used by the
  Console server
2021-03-27 12:21:59 -07:00
adfost
23b3283014 Adding Bucket Policies UI to ViewBucket.tsx (#669)
* Adding ViewBucket.tsx

* Update ViewBucket.tsx

Co-authored-by: Adam Stafford <adam@minio.io>
2021-03-26 10:06:38 -07:00
Daniel Valdivia
53eb59f5ad Disable Bucket/Replication (feature) if not enough permissions (#662)
* Bucket/Replication (feature) if not enough permissions

* Address comments

* Remove Consts fetchPerms
2021-03-25 10:10:54 -07:00
37 changed files with 1560 additions and 124 deletions

View File

@@ -78,6 +78,10 @@ nfpms:
formats:
- deb
- rpm
contents:
# Basic file that applies to all packagers
- src: systemd/console.service
dst: /etc/systemd/system/minio-console.service
dockers:
- image_templates:

View File

@@ -6,8 +6,8 @@ COPY LICENSE /licenses/LICENSE
LABEL name="MinIO" \
vendor="MinIO Inc <dev@min.io>" \
maintainer="MinIO Inc <dev@min.io>" \
version="v0.6.4" \
release="v0.6.4" \
version="v0.6.6" \
release="v0.6.6" \
summary="A graphical user interface for MinIO" \
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."

View File

@@ -17,12 +17,16 @@
package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"time"
"github.com/minio/minio/cmd/config"
"github.com/go-openapi/loads"
"github.com/jessevdk/go-flags"
"github.com/minio/cli"
@@ -68,6 +72,21 @@ var serverCmd = cli.Command{
Value: certs.GlobalCertsCADir.Get(),
Usage: "path to certs directory",
},
cli.StringFlag{
Name: "tls-certificate",
Value: "",
Usage: "path tls certificate",
},
cli.StringFlag{
Name: "tls-key",
Value: "",
Usage: "path tls key",
},
cli.StringFlag{
Name: "tls-ca",
Value: "",
Usage: "path tls ca",
},
},
}
@@ -122,6 +141,29 @@ func startServer(ctx *cli.Context) error {
// load the certificates and the CAs
restapi.GlobalRootCAs, restapi.GlobalPublicCerts, restapi.GlobalTLSCertsManager = certs.GetAllCertificatesAndCAs()
// TLS flags from swagger server, used to support older versions of minio-operator
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 errAddCert := certs.AddCertificate(context.Background(), restapi.GlobalTLSCertsManager, swaggerServerCertificate, swaggerServerCertificateKey); errAddCert != nil {
log.Println(errAddCert)
}
if x509Certs, errParseCert := config.ParsePublicCertFile(swaggerServerCertificate); errParseCert == nil {
if len(x509Certs) > 0 {
restapi.GlobalPublicCerts = append(restapi.GlobalPublicCerts, x509Certs[0])
}
}
}
// load ca cert from swagger server tls-ca flag
if SwaggerServerCACertificate != "" {
caCert, caCertErr := ioutil.ReadFile(SwaggerServerCACertificate)
if caCertErr == nil {
restapi.GlobalRootCAs.AppendCertsFromPEM(caCert)
}
}
if len(restapi.GlobalPublicCerts) > 0 {
// If TLS certificates are provided enforce the HTTPS schema, meaning console will redirect
// plain HTTP connections to HTTPS server

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:v0.6.4
image: minio/console:v0.6.6
imagePullPolicy: "IfNotPresent"
args:
- server

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:v0.6.4
image: minio/console:v0.6.6
imagePullPolicy: "IfNotPresent"
env:
- name: CONSOLE_OPERATOR_MODE

View File

@@ -0,0 +1,97 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// HasPermissionRequest has permission request
//
// swagger:model hasPermissionRequest
type HasPermissionRequest struct {
// actions
Actions []*PolicyArgs `json:"actions"`
}
// Validate validates this has permission request
func (m *HasPermissionRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateActions(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *HasPermissionRequest) validateActions(formats strfmt.Registry) error {
if swag.IsZero(m.Actions) { // not required
return nil
}
for i := 0; i < len(m.Actions); i++ {
if swag.IsZero(m.Actions[i]) { // not required
continue
}
if m.Actions[i] != nil {
if err := m.Actions[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("actions" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *HasPermissionRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *HasPermissionRequest) UnmarshalBinary(b []byte) error {
var res HasPermissionRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,97 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// HasPermissionResponse has permission response
//
// swagger:model hasPermissionResponse
type HasPermissionResponse struct {
// permissions
Permissions []*PermissionAction `json:"permissions"`
}
// Validate validates this has permission response
func (m *HasPermissionResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validatePermissions(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *HasPermissionResponse) validatePermissions(formats strfmt.Registry) error {
if swag.IsZero(m.Permissions) { // not required
return nil
}
for i := 0; i < len(m.Permissions); i++ {
if swag.IsZero(m.Permissions[i]) { // not required
continue
}
if m.Permissions[i] != nil {
if err := m.Permissions[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("permissions" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *HasPermissionResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *HasPermissionResponse) UnmarshalBinary(b []byte) error {
var res HasPermissionResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,63 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// PermissionAction permission action
//
// swagger:model permissionAction
type PermissionAction struct {
// can
Can bool `json:"can,omitempty"`
// id
ID string `json:"id,omitempty"`
}
// Validate validates this permission action
func (m *PermissionAction) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *PermissionAction) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *PermissionAction) UnmarshalBinary(b []byte) error {
var res PermissionAction
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

66
models/policy_args.go Normal file
View File

@@ -0,0 +1,66 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// PolicyArgs policy args
//
// swagger:model policyArgs
type PolicyArgs struct {
// action
Action string `json:"action,omitempty"`
// bucket name
BucketName string `json:"bucket_name,omitempty"`
// id
ID string `json:"id,omitempty"`
}
// Validate validates this policy args
func (m *PolicyArgs) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *PolicyArgs) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *PolicyArgs) UnmarshalBinary(b []byte) error {
var res PolicyArgs
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -228,3 +228,14 @@ func GetAllCertificatesAndCAs() (*x509.CertPool, []*x509.Certificate, *xcerts.Ma
logger.FatalIf(err, "Unable to load the TLS configuration")
return GlobalRootCAs, globalPublicCerts, globalTLSCertsManager
}
// AddCertificate check if Manager is initialized and then append a new certificate to it
func AddCertificate(ctx context.Context, manager *xcerts.Manager, publicKey, privateKey string) (err error) {
// If Cert Manager is not nil add more certificates
if manager != nil {
return manager.AddCertificate(publicKey, privateKey)
}
// Initialize cert manager
manager, err = xcerts.NewManager(ctx, publicKey, privateKey, config.LoadX509KeyPair)
return err
}

View File

@@ -1,25 +1,25 @@
{
"files": {
"main.css": "/static/css/main.a19f3d53.chunk.css",
"main.js": "/static/js/main.1ecd68ff.chunk.js",
"main.js.map": "/static/js/main.1ecd68ff.chunk.js.map",
"main.js": "/static/js/main.fa0873e1.chunk.js",
"main.js.map": "/static/js/main.fa0873e1.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.f48e99e5.js",
"runtime-main.js.map": "/static/js/runtime-main.f48e99e5.js.map",
"static/css/2.76b14b73.chunk.css": "/static/css/2.76b14b73.chunk.css",
"static/js/2.41957633.chunk.js": "/static/js/2.41957633.chunk.js",
"static/js/2.41957633.chunk.js.map": "/static/js/2.41957633.chunk.js.map",
"static/css/2.f324abd6.chunk.css": "/static/css/2.f324abd6.chunk.css",
"static/js/2.44b7c49b.chunk.js": "/static/js/2.44b7c49b.chunk.js",
"static/js/2.44b7c49b.chunk.js.map": "/static/js/2.44b7c49b.chunk.js.map",
"index.html": "/index.html",
"static/css/2.76b14b73.chunk.css.map": "/static/css/2.76b14b73.chunk.css.map",
"static/css/2.f324abd6.chunk.css.map": "/static/css/2.f324abd6.chunk.css.map",
"static/css/main.a19f3d53.chunk.css.map": "/static/css/main.a19f3d53.chunk.css.map",
"static/js/2.41957633.chunk.js.LICENSE.txt": "/static/js/2.41957633.chunk.js.LICENSE.txt",
"static/js/2.44b7c49b.chunk.js.LICENSE.txt": "/static/js/2.44b7c49b.chunk.js.LICENSE.txt",
"static/media/minio_console_logo.0837460e.svg": "/static/media/minio_console_logo.0837460e.svg",
"static/media/minio_operator_logo.1312b7c9.svg": "/static/media/minio_operator_logo.1312b7c9.svg"
},
"entrypoints": [
"static/js/runtime-main.f48e99e5.js",
"static/css/2.76b14b73.chunk.css",
"static/js/2.41957633.chunk.js",
"static/css/2.f324abd6.chunk.css",
"static/js/2.44b7c49b.chunk.js",
"static/css/main.a19f3d53.chunk.css",
"static/js/main.1ecd68ff.chunk.js"
"static/js/main.fa0873e1.chunk.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.76b14b73.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.41957633.chunk.js"></script><script src="/static/js/main.1ecd68ff.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.f324abd6.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.44b7c49b.chunk.js"></script><script src="/static/js/main.fa0873e1.chunk.js"></script></body></html>

View File

@@ -1,2 +1,2 @@
.ReactVirtualized__Table__headerRow{font-weight:700;text-transform:uppercase}.ReactVirtualized__Table__headerRow,.ReactVirtualized__Table__row{display:flex;flex-direction:row;align-items:center}.ReactVirtualized__Table__headerTruncatedText{display:inline-block;max-width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.ReactVirtualized__Table__headerColumn,.ReactVirtualized__Table__rowColumn{margin-right:10px;min-width:0}.ReactVirtualized__Table__rowColumn{text-overflow:ellipsis;white-space:nowrap}.ReactVirtualized__Table__headerColumn:first-of-type,.ReactVirtualized__Table__rowColumn:first-of-type{margin-left:10px}.ReactVirtualized__Table__sortableHeaderColumn{cursor:pointer}.ReactVirtualized__Table__sortableHeaderIconContainer{display:flex;align-items:center}.ReactVirtualized__Table__sortableHeaderIcon{flex:0 0 24px;height:1em;width:1em;fill:currentColor}.react-grid-layout{position:relative;transition:height .2s ease}.react-grid-item{transition:all .2s ease;transition-property:left,top}.react-grid-item img{pointer-events:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.react-grid-item.cssTransforms{transition-property:transform}.react-grid-item.resizing{z-index:1;will-change:width,height}.react-grid-item.react-draggable-dragging{transition:none;z-index:3;will-change:transform}.react-grid-item.dropping{visibility:hidden}.react-grid-item.react-grid-placeholder{background:red;opacity:.2;transition-duration:.1s;z-index:2;-webkit-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.react-grid-item>.react-resizable-handle{position:absolute;width:20px;height:20px}.react-grid-item>.react-resizable-handle:after{content:"";position:absolute;right:3px;bottom:3px;width:5px;height:5px;border-right:2px solid rgba(0,0,0,.4);border-bottom:2px solid rgba(0,0,0,.4)}.react-resizable-hide>.react-resizable-handle{display:none}.react-grid-item>.react-resizable-handle.react-resizable-handle-sw{bottom:0;left:0;cursor:sw-resize;transform:rotate(90deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-se{bottom:0;right:0;cursor:se-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-nw{top:0;left:0;cursor:nw-resize;transform:rotate(180deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-ne{top:0;right:0;cursor:ne-resize;transform:rotate(270deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-e,.react-grid-item>.react-resizable-handle.react-resizable-handle-w{top:50%;margin-top:-10px;cursor:ew-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-w{left:0;transform:rotate(135deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-e{right:0;transform:rotate(315deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-n,.react-grid-item>.react-resizable-handle.react-resizable-handle-s{left:50%;margin-left:-10px;cursor:ns-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-n{top:0;transform:rotate(225deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-s{bottom:0;transform:rotate(45deg)}.react-resizable{position:relative}.react-resizable-handle{position:absolute;width:20px;height:20px;background-repeat:no-repeat;background-origin:content-box;box-sizing:border-box;background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgd2lkdGg9IjYiIGhlaWdodD0iNiI+PHBhdGggZD0iTTYgNkgwVjQuMmg0LjJWMEg2djZ6IiBvcGFjaXR5PSIuMzAyIi8+PC9zdmc+");background-position:100% 100%;padding:0 3px 3px 0}.react-resizable-handle-sw{bottom:0;left:0;cursor:sw-resize;transform:rotate(90deg)}.react-resizable-handle-se{bottom:0;right:0;cursor:se-resize}.react-resizable-handle-nw{top:0;left:0;cursor:nw-resize;transform:rotate(180deg)}.react-resizable-handle-ne{top:0;right:0;cursor:ne-resize;transform:rotate(270deg)}.react-resizable-handle-e,.react-resizable-handle-w{top:50%;margin-top:-10px;cursor:ew-resize}.react-resizable-handle-w{left:0;transform:rotate(135deg)}.react-resizable-handle-e{right:0;transform:rotate(315deg)}.react-resizable-handle-n,.react-resizable-handle-s{left:50%;margin-left:-10px;cursor:ns-resize}.react-resizable-handle-n{top:0;transform:rotate(225deg)}.react-resizable-handle-s{bottom:0;transform:rotate(45deg)}
/*# sourceMappingURL=2.76b14b73.chunk.css.map */
/*# sourceMappingURL=2.f324abd6.chunk.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -14,7 +14,7 @@
// 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/>.
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, Fragment } from "react";
import { connect } from "react-redux";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button } from "@material-ui/core";
@@ -23,7 +23,7 @@ import TextField from "@material-ui/core/TextField";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import Moment from "react-moment";
import { Bucket, BucketList } from "../types";
import { Bucket, BucketList, HasPermissionResponse } from "../types";
import { CreateIcon } from "../../../../icons";
import { niceBytes } from "../../../../common/utils";
import { AppState } from "../../../../store";
@@ -92,6 +92,43 @@ const ListBuckets = ({
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [selectedBucket, setSelectedBucket] = useState<string>("");
const [filterBuckets, setFilterBuckets] = useState<string>("");
const [loadingPerms, setLoadingPerms] = useState<boolean>(true);
const [canCreateBucket, setCanCreateBucket] = useState<boolean>(false);
// check the permissions for creating bucket
useEffect(() => {
if (loadingPerms) {
api
.invoke("POST", `/api/v1/has-permission`, {
actions: [
{
id: "createBucket",
action: "s3:CreateBucket",
},
],
})
.then((res: HasPermissionResponse) => {
setLoadingPerms(false);
if (!res.permissions) {
return;
}
const actions = res.permissions ? res.permissions : [];
let canCreate = actions.find((s) => s.id == "createBucket");
if (canCreate && canCreate.can) {
setCanCreateBucket(true);
} else {
setCanCreateBucket(false);
}
setLoadingPerms(false);
})
.catch((err: any) => {
setLoadingPerms(false);
setErrorSnackMessage(err);
});
}
}, [loadingPerms, setErrorSnackMessage]);
useEffect(() => {
if (loading) {
@@ -155,7 +192,7 @@ const ListBuckets = ({
});
return (
<React.Fragment>
<Fragment>
{addBucketModalOpen && (
<AddBucket
open={addBucketModalOpen}
@@ -192,16 +229,18 @@ const ListBuckets = ({
),
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
addBucketOpen(true);
}}
>
Create Bucket
</Button>
{canCreateBucket && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
addBucketOpen(true);
}}
>
Create Bucket
</Button>
)}
</Grid>
<Grid item xs={12}>
<br />
@@ -232,7 +271,7 @@ const ListBuckets = ({
</Grid>
</Grid>
</Grid>
</React.Fragment>
</Fragment>
);
};

View File

@@ -14,7 +14,7 @@
// 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/>.
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, Fragment } from "react";
import { connect } from "react-redux";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button, IconButton } from "@material-ui/core";
@@ -38,12 +38,12 @@ import {
BucketReplicationRule,
BucketReplicationRuleDeleteMarker,
BucketVersioning,
HasPermissionResponse,
} from "../types";
import { CreateIcon } from "../../../../icons";
import { niceBytes } from "../../../../common/utils";
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
import { setErrorSnackMessage } from "../../../../actions";
import SetAccessPolicy from "./SetAccessPolicy";
import SetRetentionConfig from "./SetRetentionConfig";
import AddEvent from "./AddEvent";
import DeleteEvent from "./DeleteEvent";
@@ -55,6 +55,9 @@ import PencilIcon from "../../Common/TableWrapper/TableActionIcons/PencilIcon";
import EnableVersioningModal from "./EnableVersioningModal";
import Typography from "@material-ui/core/Typography";
import UsageIcon from "../../../../icons/UsageIcon";
import AddPolicy from "../../Policies/AddPolicy";
import SetAccessPolicy from "./SetAccessPolicy";
import { Policy } from "../../Policies/types";
const styles = (theme: Theme) =>
createStyles({
@@ -66,7 +69,6 @@ const styles = (theme: Theme) =>
overflow: "auto",
flexDirection: "column",
},
addSideBar: {
width: "320px",
padding: "20px",
@@ -196,7 +198,7 @@ function TabPanel(props: TabPanelProps) {
style={{ marginTop: "5px" }}
{...other}
>
{value === index && <React.Fragment>{children}</React.Fragment>}
{value === index && <Fragment>{children}</Fragment>}
</div>
);
}
@@ -218,6 +220,8 @@ const ViewBucket = ({
const [replicationRules, setReplicationRules] = useState<
BucketReplicationRule[]
>([]);
const [bucketPolicy, setBucketPolicy] = useState<Policy[]>([]);
const [loadingPolicy, setLoadingPolicy] = useState<boolean>(true);
const [loadingBucket, setLoadingBucket] = useState<boolean>(true);
const [loadingEvents, setLoadingEvents] = useState<boolean>(true);
const [loadingVersioning, setLoadingVersioning] = useState<boolean>(true);
@@ -230,6 +234,7 @@ const ViewBucket = ({
);
const [curTab, setCurTab] = useState<number>(0);
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
const [policyScreenOpen, setPolicyScreenOpen] = useState<boolean>(false);
const [
enableEncryptionScreenOpen,
setEnableEncryptionScreenOpen,
@@ -244,9 +249,66 @@ const ViewBucket = ({
const [retentionConfigOpen, setRetentionConfigOpen] = useState<boolean>(
false
);
const [policyEdit, setPolicyEdit] = useState<any>(null);
const [enableVersioningOpen, setEnableVersioningOpen] = useState<boolean>(
false
);
const [loadingPerms, setLoadingPerms] = useState<boolean>(true);
const [canPutReplication, setCanPutReplication] = useState<boolean>(false);
const [canGetReplication, setCanGetReplication] = useState<boolean>(false);
// check the permissions for creating bucket
useEffect(() => {
if (loadingPerms) {
api
.invoke("POST", `/api/v1/has-permission`, {
actions: [
{
id: "PutReplicationConfiguration",
action: "s3:PutReplicationConfiguration",
bucket_name: bucketName,
},
{
id: "GetReplicationConfiguration",
action: "s3:GetReplicationConfiguration",
bucket_name: bucketName,
},
],
})
.then((res: HasPermissionResponse) => {
setLoadingPerms(false);
if (!res.permissions) {
return;
}
const actions = res.permissions ? res.permissions : [];
let canPutReplication = actions.find(
(s) => s.id == "PutReplicationConfiguration"
);
if (canPutReplication && canPutReplication.can) {
setCanPutReplication(true);
} else {
setCanPutReplication(false);
}
let canGetReplication = actions.find(
(s) => s.id == "GetReplicationConfiguration"
);
if (canGetReplication && canGetReplication.can) {
setCanGetReplication(true);
} else {
setCanGetReplication(false);
}
setLoadingPerms(false);
})
.catch((err: any) => {
setLoadingPerms(false);
setErrorSnackMessage(err);
});
}
}, [loadingPerms, setErrorSnackMessage]);
const bucketName = match.params["bucketName"];
@@ -312,6 +374,21 @@ const ViewBucket = ({
}
}, [loadingReplication, setErrorSnackMessage, bucketName]);
useEffect(() => {
if (loadingPolicy) {
api
.invoke("GET", `/api/v1/bucket-policy/${bucketName}`)
.then((res: any) => {
setBucketPolicy(res.policies);
setLoadingPolicy(false);
})
.catch((err: any) => {
setErrorSnackMessage(err);
setLoadingPolicy(false);
});
}
}, [loadingPolicy, setErrorSnackMessage, bucketName]);
useEffect(() => {
if (loadingSize) {
api
@@ -379,6 +456,18 @@ const ViewBucket = ({
setLoadingEncryption(true);
};
const closeAddModalAndRefresh = (refresh: boolean) => {
setPolicyScreenOpen(false);
if (refresh) {
fetchPolicies();
}
};
const fetchPolicies = () => {
setLoadingPolicy(true);
};
const closeAddEventAndRefresh = () => {
setAddScreenOpen(false);
loadAllBucketData();
@@ -430,19 +519,15 @@ const ViewBucket = ({
}
const eventsDisplay = (events: string[]) => {
return <React.Fragment>{events.join(", ")}</React.Fragment>;
return <Fragment>{events.join(", ")}</Fragment>;
};
const ruleDestDisplay = (events: BucketReplicationDestination) => {
return (
<React.Fragment>
{events.bucket.replace("arn:aws:s3:::", "")}
</React.Fragment>
);
return <Fragment>{events.bucket.replace("arn:aws:s3:::", "")}</Fragment>;
};
const ruleDelDisplay = (events: BucketReplicationRuleDeleteMarker) => {
return <React.Fragment>{events.status}</React.Fragment>;
return <Fragment>{events.status}</Fragment>;
};
const setOpenReplicationOpen = (open = false) => {
@@ -467,9 +552,15 @@ const ViewBucket = ({
};
const tableActions = [{ type: "delete", onClick: confirmDeleteEvent }];
const viewAction = (row: any) => {
setPolicyScreenOpen(true);
setPolicyEdit(row);
};
const PolicyActions = [{ type: "view", onClick: viewAction }];
return (
<React.Fragment>
<Fragment>
{addScreenOpen && (
<AddEvent
open={addScreenOpen}
@@ -492,6 +583,13 @@ const ViewBucket = ({
closeModalAndRefresh={closeSetAccessPolicy}
/>
)}
{policyScreenOpen && (
<AddPolicy
open={policyScreenOpen}
closeModalAndRefresh={closeAddModalAndRefresh}
policyEdit={policyEdit}
/>
)}
{retentionConfigOpen && (
<SetRetentionConfig
bucketName={bucketName}
@@ -573,10 +671,10 @@ const ViewBucket = ({
<span>{replicationRules.length ? "Yes" : "No"}</span>
</div>
{!hasObjectLocking && (
<React.Fragment>
<Fragment>
<div>Object Locking:</div>
<div>No</div>
</React.Fragment>
</Fragment>
)}
<div>Encryption:</div>
<div>
@@ -608,7 +706,7 @@ const ViewBucket = ({
variant="indeterminate"
/>
) : (
<React.Fragment>
<Fragment>
{isVersioned && !loadingVersioning ? "Yes" : "No"}
&nbsp;
<IconButton
@@ -622,7 +720,7 @@ const ViewBucket = ({
>
<PencilIcon active={true} />
</IconButton>
</React.Fragment>
</Fragment>
)}
</div>
<div>Retention:</div>
@@ -634,7 +732,7 @@ const ViewBucket = ({
variant="indeterminate"
/>
) : (
<React.Fragment>
<Fragment>
&nbsp;
<IconButton
color="primary"
@@ -647,7 +745,7 @@ const ViewBucket = ({
>
<PencilIcon active={true} />
</IconButton>
</React.Fragment>
</Fragment>
)}
</div>
</div>
@@ -671,7 +769,10 @@ const ViewBucket = ({
aria-label="cluster-tabs"
>
<Tab label="Events" {...a11yProps(0)} />
<Tab label="Replication" {...a11yProps(1)} />
{canGetReplication && (
<Tab label="Replication" {...a11yProps(1)} />
)}
<Tab label="Policies" {...a11yProps(2)} />
</Tabs>
</Grid>
<Grid item xs={6} className={classes.actionsTray}>
@@ -689,17 +790,21 @@ const ViewBucket = ({
</Button>
)}
{curTab === 1 && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
size="medium"
onClick={() => {
setOpenReplicationOpen(true);
}}
>
Add Replication Rule
</Button>
<Fragment>
{canPutReplication && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
size="medium"
onClick={() => {
setOpenReplicationOpen(true);
}}
>
Add Replication Rule
</Button>
)}
</Fragment>
)}
</Grid>
</Grid>
@@ -723,37 +828,49 @@ const ViewBucket = ({
idField="id"
/>
</TabPanel>
<TabPanel index={1} value={curTab}>
{canGetReplication && (
<TabPanel index={1} value={curTab}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "ID", elementKey: "id" },
{
label: "Priority",
elementKey: "priority",
},
{
label: "Destination",
elementKey: "destination",
renderFunction: ruleDestDisplay,
},
{
label: "Delete Replication",
elementKey: "delete_marker_replication",
renderFunction: ruleDelDisplay,
},
{ label: "Status", elementKey: "status" },
]}
isLoading={loadingEvents}
records={replicationRules}
entityName="Replication Rules"
idField="id"
/>
</TabPanel>
)}
<TabPanel index={2} value={curTab}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "ID", elementKey: "id" },
{
label: "Priority",
elementKey: "priority",
},
{
label: "Destination",
elementKey: "destination",
renderFunction: ruleDestDisplay,
},
{
label: "Delete Replication",
elementKey: "delete_marker_replication",
renderFunction: ruleDelDisplay,
},
{ label: "Status", elementKey: "status" },
]}
itemActions={PolicyActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loadingEvents}
records={replicationRules}
entityName="Replication Rules"
idField="id"
records={bucketPolicy}
entityName="Policies"
idField="name"
/>
</TabPanel>
</Grid>
</Grid>
</Grid>
</React.Fragment>
</Fragment>
);
};

View File

@@ -118,3 +118,12 @@ export interface IRemoteBucket {
status: string;
service: string;
}
export interface PermissionAction {
id: string;
can: boolean;
}
export interface HasPermissionResponse {
permissions: PermissionAction[];
}

View File

@@ -14,7 +14,7 @@
// 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/>.
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, Fragment } from "react";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import get from "lodash/get";
@@ -26,7 +26,7 @@ import SearchIcon from "@material-ui/icons/Search";
import { Button } from "@material-ui/core";
import { CreateIcon } from "../../../icons";
import { niceBytes } from "../../../common/utils";
import { Bucket, BucketList } from "../Buckets/types";
import { Bucket, BucketList, HasPermissionResponse } from "../Buckets/types";
import {
actionsTray,
objectBrowserCommon,
@@ -118,6 +118,40 @@ const BrowseBuckets = ({
const [records, setRecords] = useState<Bucket[]>([]);
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
const [filterBuckets, setFilterBuckets] = useState<string>("");
const [loadingPerms, setLoadingPerms] = useState<boolean>(true);
const [canCreateBucket, setCanCreateBucket] = useState<boolean>(false);
// check the permissions for creating bucket
useEffect(() => {
if (loadingPerms) {
api
.invoke("POST", `/api/v1/has-permission`, {
actions: [
{
id: "createBucket",
action: "s3:CreateBucket",
},
],
})
.then((res: HasPermissionResponse) => {
const canCreate = res.permissions
.filter((s) => s.id == "createBucket")
.pop();
if (canCreate && canCreate.can) {
setCanCreateBucket(true);
} else {
setCanCreateBucket(false);
}
setLoadingPerms(false);
// setRecords(res.buckets || []);
})
.catch((err: any) => {
setLoadingPerms(false);
setErrorSnackMessage(err);
});
}
}, [loadingPerms, setErrorSnackMessage]);
useEffect(() => {
resetRoutesList(true);
@@ -170,7 +204,7 @@ const BrowseBuckets = ({
};
return (
<React.Fragment>
<Fragment>
{addScreenOpen && (
<AddBucket
open={addScreenOpen}
@@ -182,18 +216,20 @@ const BrowseBuckets = ({
<div>
<BrowserBreadcrumbs />
</div>
<div>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddScreenOpen(true);
}}
>
Create Bucket
</Button>
</div>
{canCreateBucket && (
<div>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddScreenOpen(true);
}}
>
Create Bucket
</Button>
</div>
)}
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
@@ -251,7 +287,7 @@ const BrowseBuckets = ({
/>
</Grid>
</Grid>
</React.Fragment>
</Fragment>
);
};

View File

@@ -1028,7 +1028,7 @@ func Test_UpdateTenantAction(t *testing.T) {
},
params: admin_api.UpdateTenantParams{
Body: &models.UpdateTenantRequest{
ConsoleImage: "minio/console:v0.6.4",
ConsoleImage: "minio/console:v0.6.6",
},
},
},

View File

@@ -63,7 +63,7 @@ const (
// Image versions
const (
KESImageVersion = "minio/kes:v0.13.4"
ConsoleImageDefaultVersion = "minio/console:v0.6.4"
ConsoleImageDefaultVersion = "minio/console:v0.6.6"
)
// K8s

View File

@@ -1724,6 +1724,39 @@ func init() {
}
}
},
"/has-permission": {
"post": {
"tags": [
"UserAPI"
],
"summary": "Checks whether the user can perform a series of actions",
"operationId": "HasPermissionTo",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/hasPermissionRequest"
}
}
],
"responses": {
"201": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/hasPermissionResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/login": {
"get": {
"security": [],
@@ -4094,6 +4127,28 @@ func init() {
}
}
},
"hasPermissionRequest": {
"type": "object",
"properties": {
"actions": {
"type": "array",
"items": {
"$ref": "#/definitions/policyArgs"
}
}
}
},
"hasPermissionResponse": {
"type": "object",
"properties": {
"permissions": {
"type": "array",
"items": {
"$ref": "#/definitions/permissionAction"
}
}
}
},
"iamEntity": {
"type": "string",
"pattern": "^[\\w+=,.@-]{1,64}$"
@@ -4704,6 +4759,17 @@ func init() {
"type": "string"
}
},
"permissionAction": {
"type": "object",
"properties": {
"can": {
"type": "boolean"
},
"id": {
"type": "string"
}
}
},
"podAffinityTerm": {
"description": "Required. A pod affinity term, associated with the corresponding weight.",
"type": "object",
@@ -4777,6 +4843,20 @@ func init() {
}
}
},
"policyArgs": {
"type": "object",
"properties": {
"action": {
"type": "string"
},
"bucket_name": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"policyEntity": {
"type": "string",
"default": "user",
@@ -7483,6 +7563,39 @@ func init() {
}
}
},
"/has-permission": {
"post": {
"tags": [
"UserAPI"
],
"summary": "Checks whether the user can perform a series of actions",
"operationId": "HasPermissionTo",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/hasPermissionRequest"
}
}
],
"responses": {
"201": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/hasPermissionResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/login": {
"get": {
"security": [],
@@ -10462,6 +10575,28 @@ func init() {
}
}
},
"hasPermissionRequest": {
"type": "object",
"properties": {
"actions": {
"type": "array",
"items": {
"$ref": "#/definitions/policyArgs"
}
}
}
},
"hasPermissionResponse": {
"type": "object",
"properties": {
"permissions": {
"type": "array",
"items": {
"$ref": "#/definitions/permissionAction"
}
}
}
},
"iamEntity": {
"type": "string",
"pattern": "^[\\w+=,.@-]{1,64}$"
@@ -11028,6 +11163,17 @@ func init() {
"type": "string"
}
},
"permissionAction": {
"type": "object",
"properties": {
"can": {
"type": "boolean"
},
"id": {
"type": "string"
}
}
},
"podAffinityTerm": {
"description": "Required. A pod affinity term, associated with the corresponding weight.",
"type": "object",
@@ -11079,6 +11225,20 @@ func init() {
}
}
},
"policyArgs": {
"type": "object",
"properties": {
"action": {
"type": "string"
},
"bucket_name": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"policyEntity": {
"type": "string",
"default": "user",

View File

@@ -184,6 +184,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
AdminAPIGroupInfoHandler: admin_api.GroupInfoHandlerFunc(func(params admin_api.GroupInfoParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.GroupInfo has not yet been implemented")
}),
UserAPIHasPermissionToHandler: user_api.HasPermissionToHandlerFunc(func(params user_api.HasPermissionToParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.HasPermissionTo has not yet been implemented")
}),
AdminAPIListAllTenantsHandler: admin_api.ListAllTenantsHandlerFunc(func(params admin_api.ListAllTenantsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.ListAllTenants has not yet been implemented")
}),
@@ -475,6 +478,8 @@ type ConsoleAPI struct {
AdminAPIGetUserInfoHandler admin_api.GetUserInfoHandler
// AdminAPIGroupInfoHandler sets the operation handler for the group info operation
AdminAPIGroupInfoHandler admin_api.GroupInfoHandler
// UserAPIHasPermissionToHandler sets the operation handler for the has permission to operation
UserAPIHasPermissionToHandler user_api.HasPermissionToHandler
// AdminAPIListAllTenantsHandler sets the operation handler for the list all tenants operation
AdminAPIListAllTenantsHandler admin_api.ListAllTenantsHandler
// UserAPIListBucketEventsHandler sets the operation handler for the list bucket events operation
@@ -777,6 +782,9 @@ func (o *ConsoleAPI) Validate() error {
if o.AdminAPIGroupInfoHandler == nil {
unregistered = append(unregistered, "admin_api.GroupInfoHandler")
}
if o.UserAPIHasPermissionToHandler == nil {
unregistered = append(unregistered, "user_api.HasPermissionToHandler")
}
if o.AdminAPIListAllTenantsHandler == nil {
unregistered = append(unregistered, "admin_api.ListAllTenantsHandler")
}
@@ -1198,6 +1206,10 @@ func (o *ConsoleAPI) initHandlerCache() {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/groups/{name}"] = admin_api.NewGroupInfo(o.context, o.AdminAPIGroupInfoHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/has-permission"] = user_api.NewHasPermissionTo(o.context, o.UserAPIHasPermissionToHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}

View File

@@ -0,0 +1,90 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 user_api
// 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"
)
// HasPermissionToHandlerFunc turns a function with the right signature into a has permission to handler
type HasPermissionToHandlerFunc func(HasPermissionToParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn HasPermissionToHandlerFunc) Handle(params HasPermissionToParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// HasPermissionToHandler interface for that can handle valid has permission to params
type HasPermissionToHandler interface {
Handle(HasPermissionToParams, *models.Principal) middleware.Responder
}
// NewHasPermissionTo creates a new http.Handler for the has permission to operation
func NewHasPermissionTo(ctx *middleware.Context, handler HasPermissionToHandler) *HasPermissionTo {
return &HasPermissionTo{Context: ctx, Handler: handler}
}
/*HasPermissionTo swagger:route POST /has-permission UserAPI hasPermissionTo
Checks whether the user can perform a series of actions
*/
type HasPermissionTo struct {
Context *middleware.Context
Handler HasPermissionToHandler
}
func (o *HasPermissionTo) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewHasPermissionToParams()
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

@@ -0,0 +1,94 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 user_api
// 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/minio/console/models"
)
// NewHasPermissionToParams creates a new HasPermissionToParams object
// no default values defined in spec.
func NewHasPermissionToParams() HasPermissionToParams {
return HasPermissionToParams{}
}
// HasPermissionToParams contains all the bound params for the has permission to operation
// typically these are obtained from a http.Request
//
// swagger:parameters HasPermissionTo
type HasPermissionToParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.HasPermissionRequest
}
// 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 NewHasPermissionToParams() beforehand.
func (o *HasPermissionToParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.HasPermissionRequest
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)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,133 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 user_api
// 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"
)
// HasPermissionToCreatedCode is the HTTP code returned for type HasPermissionToCreated
const HasPermissionToCreatedCode int = 201
/*HasPermissionToCreated A successful response.
swagger:response hasPermissionToCreated
*/
type HasPermissionToCreated struct {
/*
In: Body
*/
Payload *models.HasPermissionResponse `json:"body,omitempty"`
}
// NewHasPermissionToCreated creates HasPermissionToCreated with default headers values
func NewHasPermissionToCreated() *HasPermissionToCreated {
return &HasPermissionToCreated{}
}
// WithPayload adds the payload to the has permission to created response
func (o *HasPermissionToCreated) WithPayload(payload *models.HasPermissionResponse) *HasPermissionToCreated {
o.Payload = payload
return o
}
// SetPayload sets the payload to the has permission to created response
func (o *HasPermissionToCreated) SetPayload(payload *models.HasPermissionResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *HasPermissionToCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(201)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*HasPermissionToDefault Generic error response.
swagger:response hasPermissionToDefault
*/
type HasPermissionToDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewHasPermissionToDefault creates HasPermissionToDefault with default headers values
func NewHasPermissionToDefault(code int) *HasPermissionToDefault {
if code <= 0 {
code = 500
}
return &HasPermissionToDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the has permission to default response
func (o *HasPermissionToDefault) WithStatusCode(code int) *HasPermissionToDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the has permission to default response
func (o *HasPermissionToDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the has permission to default response
func (o *HasPermissionToDefault) WithPayload(payload *models.Error) *HasPermissionToDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the has permission to default response
func (o *HasPermissionToDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *HasPermissionToDefault) 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

@@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 user_api
// 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"
)
// HasPermissionToURL generates an URL for the has permission to operation
type HasPermissionToURL struct {
_basePath string
}
// 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 *HasPermissionToURL) WithBasePath(bp string) *HasPermissionToURL {
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 *HasPermissionToURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *HasPermissionToURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/has-permission"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *HasPermissionToURL) 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 *HasPermissionToURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *HasPermissionToURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on HasPermissionToURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on HasPermissionToURL")
}
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 *HasPermissionToURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -21,6 +21,8 @@ import (
"net/http"
"time"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
@@ -42,6 +44,15 @@ func registerAccountHandlers(api *operations.ConsoleAPI) {
user_api.NewLoginCreated().WithPayload(changePasswordResponse).WriteResponse(w, p)
})
})
// Checks if user can perform an action
api.UserAPIHasPermissionToHandler = user_api.HasPermissionToHandlerFunc(func(params user_api.HasPermissionToParams, session *models.Principal) middleware.Responder {
hasPermissionRespose, err := getUserHasPermissionsResponse(session, params)
if err != nil {
return user_api.NewHasPermissionToDefault(500).WithPayload(err)
}
// Custom response writer to update the session cookies
return user_api.NewHasPermissionToCreated().WithPayload(hasPermissionRespose)
})
}
// changePassword validate current current user password and if it's correct set the new password
@@ -92,3 +103,48 @@ func getChangePasswordResponse(session *models.Principal, params user_api.Accoun
}
return loginResponse, nil
}
func getUserHasPermissionsResponse(session *models.Principal, params user_api.HasPermissionToParams) (*models.HasPermissionResponse, *models.Error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
mAdmin, err := newMAdminClient(session)
if err != nil {
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
userPolicy, err := getAccountPolicy(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
}
var perms []*models.PermissionAction
for _, p := range params.Body.Actions {
canPerform := userCanDo(iampolicy.Args{
Action: iampolicy.Action(p.Action),
BucketName: p.BucketName,
}, userPolicy)
perms = append(perms, &models.PermissionAction{
Can: canPerform,
ID: p.ID,
})
}
return &models.HasPermissionResponse{
Permissions: perms,
}, nil
}
func userCanDo(arg iampolicy.Args, userPolicy *iampolicy.Policy) bool {
// check in all the statements if any allows the passed action
for _, stmt := range userPolicy.Statements {
if stmt.IsAllowed(arg) {
return true
}
}
return false
}

View File

@@ -18,9 +18,12 @@ package restapi
import (
"context"
"encoding/json"
"errors"
"testing"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/console/models"
)
@@ -106,3 +109,152 @@ func Test_changePassword(t *testing.T) {
})
}
}
func Test_useCanDo(t *testing.T) {
type args struct {
arg iampolicy.Args
userPolicy string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Create Bucket",
args: args{
arg: iampolicy.Args{
Action: "s3:CreateBucket",
},
userPolicy: `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"admin:*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`,
},
want: true,
},
{
name: "Create Bucket, No Admin",
args: args{
arg: iampolicy.Args{
Action: "s3:CreateBucket",
},
userPolicy: `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`,
},
want: true,
},
{
name: "Create Bucket, By Prefix",
args: args{
arg: iampolicy.Args{
Action: "s3:CreateBucket",
},
userPolicy: `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket1"
]
}
]
}`,
},
want: false,
},
{
name: "Create Bucket, With Bucket Name",
args: args{
arg: iampolicy.Args{
Action: "s3:CreateBucket",
BucketName: "bucket2",
},
userPolicy: `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket1"
]
}
]
}`,
},
want: false,
},
{
name: "Can't Create Bucket",
args: args{
arg: iampolicy.Args{
Action: "s3:CreateBucket",
BucketName: "bucket2",
},
userPolicy: `{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": [
"arn:aws:s3:::bucket1",
"arn:aws:s3:::bucket1/*",
"arn:aws:s3:::lkasdkljasd090901",
"arn:aws:s3:::lkasdkljasd090901/*"
]
}
]
}`,
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var pol iampolicy.Policy
if err := json.Unmarshal([]byte(tt.args.userPolicy), &pol); err != nil {
t.Errorf("Policy can't be parsed: %s", err)
}
if got := userCanDo(tt.args.arg, &pol); got != tt.want {
t.Errorf("userCanDo() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -324,6 +324,7 @@ func getAccountInfo(ctx context.Context, client MinioAdmin) ([]*models.Bucket, e
if err != nil {
return []*models.Bucket{}, err
}
var bucketInfos []*models.Bucket
for _, bucket := range info.Buckets {
bucketElem := &models.Bucket{Name: swag.String(bucket.Name), CreationDate: bucket.Created.Format(time.RFC3339), Size: int64(bucket.Size)}

View File

@@ -946,6 +946,28 @@ paths:
tags:
- UserAPI
/has-permission:
post:
summary: Checks whether the user can perform a series of actions
operationId: HasPermissionTo
parameters:
- name: body
in: body
required: true
schema:
$ref: "#/definitions/hasPermissionRequest"
responses:
201:
description: A successful response.
schema:
$ref: "#/definitions/hasPermissionResponse"
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- UserAPI
/users:
get:
summary: List Users
@@ -1557,14 +1579,14 @@ paths:
summary: Activate a particular tenant using the existing subscription license
operationId: SubscriptionActivate
parameters:
- name: namespace
in: path
required: true
type: string
- name: tenant
in: path
required: true
type: string
- name: namespace
in: path
required: true
type: string
- name: tenant
in: path
required: true
type: string
responses:
204:
description: A successful response.
@@ -2145,7 +2167,7 @@ paths:
$ref: "#/definitions/error"
tags:
- AdminAPI
/direct-csi/drives:
get:
summary: Get direct-csi drives list
@@ -2170,7 +2192,7 @@ paths:
$ref: "#/definitions/error"
tags:
- AdminAPI
/direct-csi/volumes:
get:
summary: Get direct-csi volumes list
@@ -2778,7 +2800,7 @@ definitions:
type: string
status:
type: string
enum: [ok]
enum: [ ok ]
operator:
type: boolean
widgetResult:
@@ -4050,7 +4072,7 @@ definitions:
restart:
description: Returns wheter server needs to restart to apply changes or not
type: boolean
subscriptionValidateRequest:
type: object
@@ -4105,7 +4127,7 @@ definitions:
type: string
message:
type: string
getDirectCSIVolumeListResponse:
type: object
properties:
@@ -4113,7 +4135,7 @@ definitions:
type: array
items:
$ref: "#/definitions/directCSIVolumeInfo"
directCSIVolumeInfo:
type: object
properties:
@@ -4126,3 +4148,34 @@ definitions:
type: string
drive:
type: string
policyArgs:
type: object
properties:
id:
type: string
action:
type: string
bucket_name:
type: string
hasPermissionRequest:
type: object
properties:
actions:
type: array
items:
$ref: "#/definitions/policyArgs"
permissionAction:
type: object
properties:
id:
type: string
can:
type: boolean
hasPermissionResponse:
type: object
properties:
permissions:
type: array
items:
$ref: "#/definitions/permissionAction"