Security Tab for operator-ui (#804)

Security Tab allow users to set various configurations related to TLS
certificates for MinIO and Console

- Enable/Disable AutoCert
- Add/Delete MinIO and Console Certificates
- Add/Delete MinIO and Console CA Certificates

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
Lenin Alevski
2021-06-17 17:30:20 -07:00
committed by GitHub
parent 09503ed0c8
commit 85797749ba
38 changed files with 3717 additions and 34 deletions

2
go.mod
View File

@@ -21,7 +21,7 @@ require (
github.com/minio/madmin-go v1.0.12
github.com/minio/mc v0.0.0-20210531030240-fbbae711bdb4
github.com/minio/minio-go/v7 v7.0.11-0.20210517200026-f0518ca447d6
github.com/minio/operator v0.0.0-20210604224119-7e256f98cf90
github.com/minio/operator v0.0.0-20210616045941-65f31f5f78ae
github.com/minio/operator/logsearchapi v0.0.0-20210604224119-7e256f98cf90
github.com/minio/pkg v1.0.6
github.com/minio/selfupdate v0.3.1

4
go.sum
View File

@@ -1163,8 +1163,8 @@ github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77Z
github.com/minio/minio-go/v7 v7.0.11-0.20210302210017-6ae69c73ce78/go.mod h1:mTh2uJuAbEqdhMVl6CMIIZLUeiMiWtJR4JB8/5g2skw=
github.com/minio/minio-go/v7 v7.0.11-0.20210517200026-f0518ca447d6 h1:GVR+UTvfe2r2YTYHWrA/yRF5nouMjJh3kwxNTZ8npso=
github.com/minio/minio-go/v7 v7.0.11-0.20210517200026-f0518ca447d6/go.mod h1:td4gW1ldOsj1PbSNS+WYK43j+P1XVhX/8W8awaYlBFo=
github.com/minio/operator v0.0.0-20210604224119-7e256f98cf90 h1:bomLALJicW0gLAkFHomuPbyjU0OxR0znctdALiIdAJ4=
github.com/minio/operator v0.0.0-20210604224119-7e256f98cf90/go.mod h1:8/mIXK+CFdL6VqyxRn1SwD+PEX0jsN8uqjoadaw/Np0=
github.com/minio/operator v0.0.0-20210616045941-65f31f5f78ae h1:GONmqbjCi/KTEc1CGujnS/m1qeJeghcQ8dUBLh19qQo=
github.com/minio/operator v0.0.0-20210616045941-65f31f5f78ae/go.mod h1:8/mIXK+CFdL6VqyxRn1SwD+PEX0jsN8uqjoadaw/Np0=
github.com/minio/operator/logsearchapi v0.0.0-20210604224119-7e256f98cf90 h1:Qu6j6oE7+QNuq7Kr2DLyVYq3fqMdqFd/T8NAeNp47og=
github.com/minio/operator/logsearchapi v0.0.0-20210604224119-7e256f98cf90/go.mod h1:R+38Pf3wfm+JMiyLPb/r8OMrBm0vK2hZgUT4y4aYoSY=
github.com/minio/pkg v1.0.3/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=

View File

@@ -0,0 +1,76 @@
// 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 (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// CertificateInfo certificate info
//
// swagger:model certificateInfo
type CertificateInfo struct {
// domains
Domains []string `json:"domains"`
// expiry
Expiry string `json:"expiry,omitempty"`
// name
Name string `json:"name,omitempty"`
// serial number
SerialNumber string `json:"serialNumber,omitempty"`
}
// Validate validates this certificate info
func (m *CertificateInfo) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this certificate info based on context it is used
func (m *CertificateInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *CertificateInfo) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *CertificateInfo) UnmarshalBinary(b []byte) error {
var res CertificateInfo
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -36,6 +36,12 @@ import (
// swagger:model tenant
type Tenant struct {
// console enabled
ConsoleEnabled bool `json:"consoleEnabled,omitempty"`
// console TLS
ConsoleTLS bool `json:"consoleTLS,omitempty"`
// console image
ConsoleImage string `json:"console_image,omitempty"`
@@ -69,6 +75,9 @@ type Tenant struct {
// log enabled
LogEnabled bool `json:"logEnabled,omitempty"`
// minio TLS
MinioTLS bool `json:"minioTLS,omitempty"`
// monitoring enabled
MonitoringEnabled bool `json:"monitoringEnabled,omitempty"`

View File

@@ -0,0 +1,377 @@
// 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 (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// TenantSecurityResponse tenant security response
//
// swagger:model tenantSecurityResponse
type TenantSecurityResponse struct {
// auto cert
AutoCert bool `json:"autoCert,omitempty"`
// custom certificates
CustomCertificates *TenantSecurityResponseCustomCertificates `json:"customCertificates,omitempty"`
}
// Validate validates this tenant security response
func (m *TenantSecurityResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateCustomCertificates(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *TenantSecurityResponse) validateCustomCertificates(formats strfmt.Registry) error {
if swag.IsZero(m.CustomCertificates) { // not required
return nil
}
if m.CustomCertificates != nil {
if err := m.CustomCertificates.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates")
}
return err
}
}
return nil
}
// ContextValidate validate this tenant security response based on the context it is used
func (m *TenantSecurityResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateCustomCertificates(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *TenantSecurityResponse) contextValidateCustomCertificates(ctx context.Context, formats strfmt.Registry) error {
if m.CustomCertificates != nil {
if err := m.CustomCertificates.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *TenantSecurityResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *TenantSecurityResponse) UnmarshalBinary(b []byte) error {
var res TenantSecurityResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// TenantSecurityResponseCustomCertificates tenant security response custom certificates
//
// swagger:model TenantSecurityResponseCustomCertificates
type TenantSecurityResponseCustomCertificates struct {
// console
Console []*CertificateInfo `json:"console"`
// console c as
ConsoleCAs []*CertificateInfo `json:"consoleCAs"`
// minio
Minio []*CertificateInfo `json:"minio"`
// minio c as
MinioCAs []*CertificateInfo `json:"minioCAs"`
}
// Validate validates this tenant security response custom certificates
func (m *TenantSecurityResponseCustomCertificates) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConsole(formats); err != nil {
res = append(res, err)
}
if err := m.validateConsoleCAs(formats); err != nil {
res = append(res, err)
}
if err := m.validateMinio(formats); err != nil {
res = append(res, err)
}
if err := m.validateMinioCAs(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *TenantSecurityResponseCustomCertificates) validateConsole(formats strfmt.Registry) error {
if swag.IsZero(m.Console) { // not required
return nil
}
for i := 0; i < len(m.Console); i++ {
if swag.IsZero(m.Console[i]) { // not required
continue
}
if m.Console[i] != nil {
if err := m.Console[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "console" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *TenantSecurityResponseCustomCertificates) validateConsoleCAs(formats strfmt.Registry) error {
if swag.IsZero(m.ConsoleCAs) { // not required
return nil
}
for i := 0; i < len(m.ConsoleCAs); i++ {
if swag.IsZero(m.ConsoleCAs[i]) { // not required
continue
}
if m.ConsoleCAs[i] != nil {
if err := m.ConsoleCAs[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "consoleCAs" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *TenantSecurityResponseCustomCertificates) validateMinio(formats strfmt.Registry) error {
if swag.IsZero(m.Minio) { // not required
return nil
}
for i := 0; i < len(m.Minio); i++ {
if swag.IsZero(m.Minio[i]) { // not required
continue
}
if m.Minio[i] != nil {
if err := m.Minio[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "minio" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *TenantSecurityResponseCustomCertificates) validateMinioCAs(formats strfmt.Registry) error {
if swag.IsZero(m.MinioCAs) { // not required
return nil
}
for i := 0; i < len(m.MinioCAs); i++ {
if swag.IsZero(m.MinioCAs[i]) { // not required
continue
}
if m.MinioCAs[i] != nil {
if err := m.MinioCAs[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "minioCAs" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this tenant security response custom certificates based on the context it is used
func (m *TenantSecurityResponseCustomCertificates) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateConsole(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateConsoleCAs(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateMinio(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateMinioCAs(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *TenantSecurityResponseCustomCertificates) contextValidateConsole(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Console); i++ {
if m.Console[i] != nil {
if err := m.Console[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "console" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *TenantSecurityResponseCustomCertificates) contextValidateConsoleCAs(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.ConsoleCAs); i++ {
if m.ConsoleCAs[i] != nil {
if err := m.ConsoleCAs[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "consoleCAs" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *TenantSecurityResponseCustomCertificates) contextValidateMinio(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Minio); i++ {
if m.Minio[i] != nil {
if err := m.Minio[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "minio" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *TenantSecurityResponseCustomCertificates) contextValidateMinioCAs(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.MinioCAs); i++ {
if m.MinioCAs[i] != nil {
if err := m.MinioCAs[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "minioCAs" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *TenantSecurityResponseCustomCertificates) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *TenantSecurityResponseCustomCertificates) UnmarshalBinary(b []byte) error {
var res TenantSecurityResponseCustomCertificates
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,280 @@
// 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 (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// UpdateTenantSecurityRequest update tenant security request
//
// swagger:model updateTenantSecurityRequest
type UpdateTenantSecurityRequest struct {
// auto cert
AutoCert bool `json:"autoCert,omitempty"`
// custom certificates
CustomCertificates *UpdateTenantSecurityRequestCustomCertificates `json:"customCertificates,omitempty"`
}
// Validate validates this update tenant security request
func (m *UpdateTenantSecurityRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateCustomCertificates(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *UpdateTenantSecurityRequest) validateCustomCertificates(formats strfmt.Registry) error {
if swag.IsZero(m.CustomCertificates) { // not required
return nil
}
if m.CustomCertificates != nil {
if err := m.CustomCertificates.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates")
}
return err
}
}
return nil
}
// ContextValidate validate this update tenant security request based on the context it is used
func (m *UpdateTenantSecurityRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateCustomCertificates(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *UpdateTenantSecurityRequest) contextValidateCustomCertificates(ctx context.Context, formats strfmt.Registry) error {
if m.CustomCertificates != nil {
if err := m.CustomCertificates.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *UpdateTenantSecurityRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *UpdateTenantSecurityRequest) UnmarshalBinary(b []byte) error {
var res UpdateTenantSecurityRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// UpdateTenantSecurityRequestCustomCertificates update tenant security request custom certificates
//
// swagger:model UpdateTenantSecurityRequestCustomCertificates
type UpdateTenantSecurityRequestCustomCertificates struct {
// console
Console []*KeyPairConfiguration `json:"console"`
// console c as
ConsoleCAs []string `json:"consoleCAs"`
// minio
Minio []*KeyPairConfiguration `json:"minio"`
// minio c as
MinioCAs []string `json:"minioCAs"`
// secrets to be deleted
SecretsToBeDeleted []string `json:"secretsToBeDeleted"`
}
// Validate validates this update tenant security request custom certificates
func (m *UpdateTenantSecurityRequestCustomCertificates) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConsole(formats); err != nil {
res = append(res, err)
}
if err := m.validateMinio(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *UpdateTenantSecurityRequestCustomCertificates) validateConsole(formats strfmt.Registry) error {
if swag.IsZero(m.Console) { // not required
return nil
}
for i := 0; i < len(m.Console); i++ {
if swag.IsZero(m.Console[i]) { // not required
continue
}
if m.Console[i] != nil {
if err := m.Console[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "console" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *UpdateTenantSecurityRequestCustomCertificates) validateMinio(formats strfmt.Registry) error {
if swag.IsZero(m.Minio) { // not required
return nil
}
for i := 0; i < len(m.Minio); i++ {
if swag.IsZero(m.Minio[i]) { // not required
continue
}
if m.Minio[i] != nil {
if err := m.Minio[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "minio" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this update tenant security request custom certificates based on the context it is used
func (m *UpdateTenantSecurityRequestCustomCertificates) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateConsole(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateMinio(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *UpdateTenantSecurityRequestCustomCertificates) contextValidateConsole(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Console); i++ {
if m.Console[i] != nil {
if err := m.Console[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "console" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *UpdateTenantSecurityRequestCustomCertificates) contextValidateMinio(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Minio); i++ {
if m.Minio[i] != nil {
if err := m.Minio[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("customCertificates" + "." + "minio" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *UpdateTenantSecurityRequestCustomCertificates) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *UpdateTenantSecurityRequestCustomCertificates) UnmarshalBinary(b []byte) error {
var res UpdateTenantSecurityRequestCustomCertificates
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -49,6 +49,7 @@ var (
tenantsDetailPods = "/namespaces/:tenantNamespace/tenants/:tenantName/pods"
tenantsDetailPools = "/namespaces/:tenantNamespace/tenants/:tenantName/pools"
tenantsDetailLicense = "/namespaces/:tenantNamespace/tenants/:tenantName/license"
tenantsDetailSecurity = "/namespaces/:tenantNamespace/tenants/:tenantName/security"
storage = "/storage"
storageVolumes = "/storage/volumes"
storageDrives = "/storage/drives"
@@ -323,18 +324,19 @@ var endpointRules = map[string]ConfigurationActionSet{
// operatorRules contains the mapping between endpoints and ActionSets for operator only mode
var operatorRules = map[string]ConfigurationActionSet{
tenants: tenantsActionSet,
tenantsDetail: tenantsActionSet,
tenantsDetailSummary: tenantsActionSet,
tenantsDetailMetrics: tenantsActionSet,
tenantsDetailPods: tenantsActionSet,
tenantsDetailPools: tenantsActionSet,
tenantsDetailLicense: tenantsActionSet,
podsDetail: tenantsActionSet,
storage: storageActionSet,
storageDrives: storageActionSet,
storageVolumes: storageActionSet,
license: licenseActionSet,
tenants: tenantsActionSet,
tenantsDetail: tenantsActionSet,
tenantsDetailSummary: tenantsActionSet,
tenantsDetailMetrics: tenantsActionSet,
tenantsDetailPods: tenantsActionSet,
tenantsDetailPools: tenantsActionSet,
tenantsDetailLicense: tenantsActionSet,
tenantsDetailSecurity: tenantsActionSet,
podsDetail: tenantsActionSet,
storage: storageActionSet,
storageDrives: storageActionSet,
storageVolumes: storageActionSet,
license: licenseActionSet,
}
// operatorOnly ENV variable

View File

@@ -116,7 +116,7 @@ func TestOperatorOnlyEndpoints(t *testing.T) {
"admin:*",
},
},
want: 12,
want: 13,
},
{
name: "Operator Only - all s3 endpoints",
@@ -125,7 +125,7 @@ func TestOperatorOnlyEndpoints(t *testing.T) {
"s3:*",
},
},
want: 12,
want: 13,
},
{
name: "Operator Only - all admin and s3 endpoints",
@@ -135,14 +135,14 @@ func TestOperatorOnlyEndpoints(t *testing.T) {
"s3:*",
},
},
want: 12,
want: 13,
},
{
name: "Operator Only - default endpoints",
args: args{
[]string{},
},
want: 12,
want: 13,
},
}

View File

@@ -1,25 +1,25 @@
{
"files": {
"main.css": "/static/css/main.8cfac526.chunk.css",
"main.js": "/static/js/main.03b04a1b.chunk.js",
"main.js.map": "/static/js/main.03b04a1b.chunk.js.map",
"main.js": "/static/js/main.7915d40b.chunk.js",
"main.js.map": "/static/js/main.7915d40b.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.43a31377.js",
"runtime-main.js.map": "/static/js/runtime-main.43a31377.js.map",
"static/css/2.60e04a19.chunk.css": "/static/css/2.60e04a19.chunk.css",
"static/js/2.d609c10f.chunk.js": "/static/js/2.d609c10f.chunk.js",
"static/js/2.d609c10f.chunk.js.map": "/static/js/2.d609c10f.chunk.js.map",
"static/js/2.06e58ba0.chunk.js": "/static/js/2.06e58ba0.chunk.js",
"static/js/2.06e58ba0.chunk.js.map": "/static/js/2.06e58ba0.chunk.js.map",
"index.html": "/index.html",
"static/css/2.60e04a19.chunk.css.map": "/static/css/2.60e04a19.chunk.css.map",
"static/css/main.8cfac526.chunk.css.map": "/static/css/main.8cfac526.chunk.css.map",
"static/js/2.d609c10f.chunk.js.LICENSE.txt": "/static/js/2.d609c10f.chunk.js.LICENSE.txt",
"static/js/2.06e58ba0.chunk.js.LICENSE.txt": "/static/js/2.06e58ba0.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.43a31377.js",
"static/css/2.60e04a19.chunk.css",
"static/js/2.d609c10f.chunk.js",
"static/js/2.06e58ba0.chunk.js",
"static/css/main.8cfac526.chunk.css",
"static/js/main.03b04a1b.chunk.js"
"static/js/main.7915d40b.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.60e04a19.chunk.css" rel="stylesheet"><link href="/static/css/main.8cfac526.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.d609c10f.chunk.js"></script><script src="/static/js/main.03b04a1b.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.60e04a19.chunk.css" rel="stylesheet"><link href="/static/css/main.8cfac526.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.06e58ba0.chunk.js"></script><script src="/static/js/main.7915d40b.chunk.js"></script></body></html>

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

@@ -378,6 +378,10 @@ const Console = ({
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/license",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/security",
},
{
component: License,
path: "/license",

View File

@@ -92,6 +92,9 @@ export interface ITenant {
logEnabled: boolean;
monitoringEnabled: boolean;
encryptionEnabled: boolean;
minioTLS: boolean;
consoleTLS: boolean;
consoleEnabled: boolean;
idpAdEnabled: boolean;
idpOicEnabled: boolean;
health_status: string;

View File

@@ -0,0 +1,82 @@
import { ITenant } from "../ListTenants/types";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import {
containerForHeader,
tenantDetailsStyles,
} from "../../Common/FormComponents/common/styleLibrary";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
LinearProgress,
} from "@material-ui/core";
import React, { useState } from "react";
interface IConfirmationDialog {
classes: any;
open: boolean;
cancelLabel: string;
okLabel: string;
onClose: any;
cancelOnClick: any;
okOnClick: any;
title: string;
description: string;
}
const styles = (theme: Theme) =>
createStyles({
...tenantDetailsStyles,
...containerForHeader(theme.spacing(4)),
});
const ConfirmationDialog = ({
classes,
open,
cancelLabel,
okLabel,
onClose,
cancelOnClick,
okOnClick,
title,
description,
}: IConfirmationDialog) => {
const [isSending, setIsSending] = useState<boolean>(false);
const onClick = () => {
setIsSending(true);
if (okOnClick !== null) {
okOnClick();
}
setIsSending(false);
};
if (!open) return null;
return (
<Dialog
open={open}
onClose={onClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{title}</DialogTitle>
<DialogContent>
{isSending && <LinearProgress />}
<DialogContentText id="alert-dialog-description">
{description}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={cancelOnClick} color="primary" disabled={isSending}>
{cancelLabel || "Cancel"}
</Button>
<Button onClick={onClick} color="secondary" autoFocus>
{okLabel || "Ok"}
</Button>
</DialogActions>
</Dialog>
);
};
export default withStyles(styles)(ConfirmationDialog);

View File

@@ -45,6 +45,7 @@ import PoolsSummary from "./PoolsSummary";
import PodsSummary from "./PodsSummary";
import { AppState } from "../../../../store";
import TenantMetrics from "./TenantMetrics";
import TenantSecurity from "./TenantSecurity";
interface ITenantDetailsProps {
classes: any;
@@ -156,6 +157,7 @@ const TenantDetails = ({
case "pods":
case "metrics":
case "license":
case "security":
setTenantTab(section);
break;
default:
@@ -235,6 +237,7 @@ const TenantDetails = ({
>
<Tab value="summary" label="Summary" />
<Tab value="metrics" label="Metrics" />
<Tab value="security" label="Security" />
<Tab value="pools" label="Pools" />
<Tab value="pods" label="Pods" />
<Tab value="license" label="License" />
@@ -251,6 +254,10 @@ const TenantDetails = ({
path="/namespaces/:tenantNamespace/tenants/:tenantName/metrics"
component={TenantMetrics}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/security"
component={TenantSecurity}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/pools"
component={PoolsSummary}

View File

@@ -0,0 +1,950 @@
// 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/>.
import { ITenant } from "../ListTenants/types";
import { ICertificateInfo, ITenantSecurityResponse } from "../types";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import {
containerForHeader,
tenantDetailsStyles,
} from "../../Common/FormComponents/common/styleLibrary";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import Chip from "@material-ui/core/Chip";
import React, { Fragment, useEffect, useState } from "react";
import Moment from "react-moment";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import {
Button,
CircularProgress,
Divider,
LinearProgress,
Typography,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from "@material-ui/core";
import { KeyPair } from "../ListTenants/utils";
import FileSelector from "../../Common/FormComponents/FileSelector/FileSelector";
import api from "../../../../common/api";
import { setErrorSnackMessage } from "../../../../actions";
import { connect } from "react-redux";
import { AppState } from "../../../../store";
import { setTenantDetailsLoad } from "../actions";
import ConfirmationDialog from "./ConfirmationDialog";
interface ITenantSecurity {
classes: any;
loadingTenant: boolean;
tenant: ITenant | null;
setErrorSnackMessage: typeof setErrorSnackMessage;
setTenantDetailsLoad: typeof setTenantDetailsLoad;
}
const styles = (theme: Theme) =>
createStyles({
...tenantDetailsStyles,
loaderAlign: {
textAlign: "center",
},
title: {
marginTop: 35,
},
bold: { fontWeight: "bold" },
italic: { fontStyle: "italic" },
underline: { textDecorationLine: "underline" },
paperContainer: {
padding: "15px 15px 15px 50px",
},
verifiedIcon: {
width: 96,
position: "absolute",
right: 0,
bottom: 29,
},
noUnderLine: {
textDecoration: "none",
},
certificateInfo: {
height: "auto",
margin: 5,
},
certificateInfoName: {
fontWeight: "bold",
},
...containerForHeader(theme.spacing(4)),
});
const TenantSecurity = ({
classes,
tenant,
loadingTenant,
setErrorSnackMessage,
setTenantDetailsLoad,
}: ITenantSecurity) => {
const [isSending, setIsSending] = useState<boolean>(false);
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const [enableAutoCert, setEnableAutoCert] = useState<boolean>(false);
const [enableCustomCerts, setEnableCustomCerts] = useState<boolean>(false);
const [certificatesToBeRemoved, setCertificatesToBeRemoved] = useState<
string[]
>([]);
// Console certificates
const [consoleCertificates, setConsoleCertificates] = useState<KeyPair[]>([
{
cert: "",
encoded_cert: "",
encoded_key: "",
id: Date.now().toString(),
key: "",
},
]);
const [consoleCaCertificates, setConsoleCaCertificates] = useState<KeyPair[]>(
[
{
cert: "",
encoded_cert: "",
encoded_key: "",
id: Date.now().toString(),
key: "",
},
]
);
const [consoleTLSCertificateSecrets, setConsoleTLSCertificateSecrets] =
useState<ICertificateInfo[]>([]);
const [consoleTLSCaCertificateSecrets, setConsoleTLSCaCertificateSecrets] =
useState<ICertificateInfo[]>([]);
// MinIO certificates
const [minioCertificates, setMinioCertificates] = useState<KeyPair[]>([
{
cert: "",
encoded_cert: "",
encoded_key: "",
id: Date.now().toString(),
key: "",
},
]);
const [minioCaCertificates, setMinioCaCertificates] = useState<KeyPair[]>([
{
cert: "",
encoded_cert: "",
encoded_key: "",
id: Date.now().toString(),
key: "",
},
]);
const [minioTLSCertificateSecrets, setMinioTLSCertificateSecrets] = useState<
ICertificateInfo[]
>([]);
const [minioTLSCaCertificateSecrets, setMinioTLSCaCertificateSecrets] =
useState<ICertificateInfo[]>([]);
useEffect(() => {
if (tenant) {
getTenantSecurityInfo();
}
}, [tenant]);
const getTenantSecurityInfo = () => {
api
.invoke(
"GET",
`/api/v1/namespaces/${tenant?.namespace}/tenants/${tenant?.name}/security`
)
.then((res: ITenantSecurityResponse) => {
setEnableAutoCert(res.autoCert);
if (
res.customCertificates.minio ||
res.customCertificates.minioCAs ||
res.customCertificates.console ||
res.customCertificates.consoleCAs
) {
setEnableCustomCerts(true);
}
setMinioTLSCertificateSecrets(res.customCertificates.minio || []);
setMinioTLSCaCertificateSecrets(res.customCertificates.minioCAs || []);
setConsoleTLSCertificateSecrets(res.customCertificates.console || []);
setConsoleTLSCaCertificateSecrets(
res.customCertificates.consoleCAs || []
);
})
.catch((err) => {
setErrorSnackMessage(err.message);
});
};
const updateTenantSecurity = () => {
setIsSending(true);
let payload = {
autoCert: enableAutoCert,
customCertificates: {},
};
if (enableCustomCerts) {
payload["customCertificates"] = {
secretsToBeDeleted: certificatesToBeRemoved,
minio: minioCertificates
.map((keyPair: KeyPair) => ({
crt: keyPair.encoded_cert,
key: keyPair.encoded_key,
}))
.filter((cert: any) => cert.crt && cert.key),
minioCAs: minioCaCertificates
.map((keyPair: KeyPair) => keyPair.encoded_cert)
.filter((cert: any) => cert),
console: consoleCertificates
.map((keyPair: KeyPair) => ({
crt: keyPair.encoded_cert,
key: keyPair.encoded_key,
}))
.filter((cert: any) => cert.crt && cert.key),
consoleCAs: consoleCaCertificates
.map((keyPair: KeyPair) => keyPair.encoded_cert)
.filter((cert: any) => cert),
};
} else {
payload["customCertificates"] = {
secretsToBeDeleted: [
...minioTLSCertificateSecrets.map((cert) => cert.name),
...minioTLSCaCertificateSecrets.map((cert) => cert.name),
...consoleTLSCertificateSecrets.map((cert) => cert.name),
...consoleTLSCaCertificateSecrets.map((cert) => cert.name),
],
minio: [],
minioCAs: [],
console: [],
consoleCAs: [],
};
}
api
.invoke(
"POST",
`/api/v1/namespaces/${tenant?.namespace}/tenants/${tenant?.name}/security`,
payload
)
.then(() => {
setIsSending(false);
// Close confirmation modal
setDialogOpen(false);
// Refresh Information and reset forms
setMinioCertificates([
{
cert: "",
encoded_cert: "",
encoded_key: "",
id: Date.now().toString(),
key: "",
},
]);
setMinioCaCertificates([
{
cert: "",
encoded_cert: "",
encoded_key: "",
id: Date.now().toString(),
key: "",
},
]);
setConsoleCertificates([
{
cert: "",
encoded_cert: "",
encoded_key: "",
id: Date.now().toString(),
key: "",
},
]);
setConsoleCaCertificates([
{
cert: "",
encoded_cert: "",
encoded_key: "",
id: Date.now().toString(),
key: "",
},
]);
getTenantSecurityInfo();
})
.catch((err) => {
setErrorSnackMessage(err);
setIsSending(false);
});
};
const removeCertificate = (certificateInfo: ICertificateInfo) => {
// TLS certificate secrets can be referenced MinIO, Console or KES, we need to remove the secret from all list and update
// the arrays
// Add certificate to the global list of secrets to be removed
setCertificatesToBeRemoved([
...certificatesToBeRemoved,
certificateInfo.name,
]);
// Update MinIO TLS certificate secrets
const updatedMinIOTLSCertificateSecrets = minioTLSCertificateSecrets.filter(
(certificateSecret) => certificateSecret.name != certificateInfo.name
);
const updatedMinIOTLSCaCertificateSecrets =
minioTLSCaCertificateSecrets.filter(
(certificateSecret) => certificateSecret.name != certificateInfo.name
);
setMinioTLSCertificateSecrets(updatedMinIOTLSCertificateSecrets);
setMinioTLSCaCertificateSecrets(updatedMinIOTLSCaCertificateSecrets);
// Update Console TLS certificate secrets
const updatedConsoleTLSCertificateSecrets =
consoleTLSCertificateSecrets.filter(
(certificateSecret) => certificateSecret.name != certificateInfo.name
);
const updatedConsoleTLSCaCertificateSecrets =
consoleTLSCaCertificateSecrets.filter(
(certificateSecret) => certificateSecret.name != certificateInfo.name
);
setConsoleTLSCertificateSecrets(updatedConsoleTLSCertificateSecrets);
setConsoleTLSCaCertificateSecrets(updatedConsoleTLSCaCertificateSecrets);
};
const addFileToKeyPair = (
type: string,
id: string,
key: string,
fileName: string,
value: string
) => {
let certificates = minioCertificates;
let updateCertificates: any = () => {};
switch (type) {
case "minio": {
certificates = minioCertificates;
updateCertificates = setMinioCertificates;
break;
}
case "minioCAs": {
certificates = minioCaCertificates;
updateCertificates = setMinioCaCertificates;
break;
}
case "console": {
certificates = consoleCertificates;
updateCertificates = setConsoleCertificates;
break;
}
case "consoleCAs": {
certificates = consoleCaCertificates;
updateCertificates = setConsoleCaCertificates;
break;
}
default:
}
const NCertList = certificates.map((item: KeyPair) => {
if (item.id === id) {
return {
...item,
[key]: fileName,
[`encoded_${key}`]: value,
};
}
return item;
});
updateCertificates(NCertList);
};
const deleteKeyPair = (type: string, id: string) => {
let certificates = minioCertificates;
let updateCertificates: any = () => {};
switch (type) {
case "minio": {
certificates = minioCertificates;
updateCertificates = setMinioCertificates;
break;
}
case "minioCAs": {
certificates = minioCaCertificates;
updateCertificates = setMinioCaCertificates;
break;
}
case "console": {
certificates = consoleCertificates;
updateCertificates = setConsoleCertificates;
break;
}
case "consoleCAs": {
certificates = consoleCaCertificates;
updateCertificates = setConsoleCaCertificates;
break;
}
default:
}
if (certificates.length > 1) {
const cleanCertsList = certificates.filter(
(item: KeyPair) => item.id !== id
);
updateCertificates(cleanCertsList);
}
};
const addKeyPair = (type: string) => {
let certificates = minioCertificates;
let updateCertificates: any = () => {};
switch (type) {
case "minio": {
certificates = minioCertificates;
updateCertificates = setMinioCertificates;
break;
}
case "minioCAs": {
certificates = minioCaCertificates;
updateCertificates = setMinioCaCertificates;
break;
}
case "console": {
certificates = consoleCertificates;
updateCertificates = setConsoleCertificates;
break;
}
case "consoleCAs": {
certificates = consoleCaCertificates;
updateCertificates = setConsoleCaCertificates;
break;
}
default:
}
const updatedCertificates = [
...certificates,
{
id: Date.now().toString(),
key: "",
cert: "",
encoded_key: "",
encoded_cert: "",
},
];
updateCertificates(updatedCertificates);
};
return (
<React.Fragment>
<ConfirmationDialog
open={dialogOpen}
title="Save and Restart"
description="Are you sure you want to save the changes and restart the service?"
onClose={() => setDialogOpen(false)}
cancelOnClick={() => setDialogOpen(false)}
okOnClick={updateTenantSecurity}
cancelLabel="Cancel"
okLabel={"Restart"}
/>
<br />
<Paper className={classes.paperContainer}>
{loadingTenant ? (
<div className={classes.loaderAlign}>
<CircularProgress />
</div>
) : (
<Fragment>
<Grid item xs={12} className={classes.title}>
<FormSwitchWrapper
value="enableAutoCert"
id="enableAutoCert"
name="enableAutoCert"
checked={enableAutoCert}
onChange={(e) => {
const targetD = e.target;
const checked = targetD.checked;
setEnableAutoCert(checked);
}}
label={"Enable AutoCert"}
/>
<FormSwitchWrapper
value="enableCustomCerts"
id="enableCustomCerts"
name="enableCustomCerts"
checked={enableCustomCerts}
onChange={(e) => {
const targetD = e.target;
const checked = targetD.checked;
setEnableCustomCerts(checked);
}}
label={"Custom Certificates"}
/>
</Grid>
{enableCustomCerts && (
<Fragment>
<Grid container>
<Grid container item xs={12}>
<Typography variant="overline" display="block" gutterBottom>
MinIO Certificates
</Typography>
</Grid>
<Grid container item xs={12}>
{minioTLSCertificateSecrets.map(
(certificateInfo: ICertificateInfo) => (
<Chip
key={certificateInfo.name}
variant="outlined"
color="primary"
className={classes.certificateInfo}
label={
<div>
<Typography
variant="subtitle1"
display="block"
gutterBottom
>
{certificateInfo.name}
</Typography>
<Typography
className={classes.italic}
variant="caption"
display="block"
gutterBottom
>
{certificateInfo.domains.join(", ")}
</Typography>
<Typography
className={classes.bold}
variant="overline"
gutterBottom
>
Expiry:&nbsp;
</Typography>
<Typography variant="caption" gutterBottom>
<Moment format="YYYY-MM-DD">
{certificateInfo.expiry}
</Moment>
</Typography>
</div>
}
onDelete={() => removeCertificate(certificateInfo)}
/>
)
)}
</Grid>
<Grid container item xs={12}>
<br />
</Grid>
<Grid container item xs={12}>
{minioCertificates.map((keyPair) => (
<Fragment key={keyPair.id}>
<Grid item xs={5}>
<FileSelector
onChange={(encodedValue, fileName) =>
addFileToKeyPair(
"minio",
keyPair.id,
"cert",
fileName,
encodedValue
)
}
accept=".cer,.crt,.cert,.pem"
id="tlsCert"
name="tlsCert"
label="Cert"
value={keyPair.cert}
/>
</Grid>
<Grid item xs={5}>
<FileSelector
onChange={(encodedValue, fileName) =>
addFileToKeyPair(
"minio",
keyPair.id,
"key",
fileName,
encodedValue
)
}
accept=".key,.pem"
id="tlsKey"
name="tlsKey"
label="Key"
value={keyPair.key}
/>
</Grid>
<Grid item xs={1}>
<Button
onClick={() => deleteKeyPair("minio", keyPair.id)}
color="secondary"
>
Remove
</Button>
</Grid>
</Fragment>
))}
</Grid>
<Grid container item xs={12}>
<Button onClick={() => addKeyPair("minio")} color="primary">
Add More
</Button>
</Grid>
<Grid container item xs={12}>
<br />
</Grid>
<Grid container item xs={12}>
<Typography variant="overline" display="block" gutterBottom>
MinIO CA Certificates
</Typography>
</Grid>
<Grid container item xs={12}>
{minioTLSCaCertificateSecrets.map(
(certificateInfo: ICertificateInfo) => (
<Chip
key={certificateInfo.name}
variant="outlined"
color="primary"
className={classes.certificateInfo}
label={
<div>
<Typography
variant="subtitle1"
display="block"
gutterBottom
>
{certificateInfo.name}
</Typography>
<Typography
className={classes.italic}
variant="caption"
display="block"
gutterBottom
>
{certificateInfo.domains.join(", ")}
</Typography>
<Typography
className={classes.bold}
variant="overline"
gutterBottom
>
Expiry:&nbsp;
</Typography>
<Typography variant="caption" gutterBottom>
<Moment format="YYYY-MM-DD">
{certificateInfo.expiry}
</Moment>
</Typography>
</div>
}
onDelete={() => removeCertificate(certificateInfo)}
/>
)
)}
</Grid>
<Grid container item xs={12}>
<br />
</Grid>
<Grid container item xs={12}>
{minioCaCertificates.map((keyPair: KeyPair) => (
<Fragment key={keyPair.id}>
<Grid item xs={10}>
<FileSelector
onChange={(encodedValue, fileName) =>
addFileToKeyPair(
"minioCAs",
keyPair.id,
"cert",
fileName,
encodedValue
)
}
accept=".cer,.crt,.cert,.pem"
id="tlsCert"
name="tlsCert"
label="Cert"
value={keyPair.cert}
/>
</Grid>
<Grid item xs={1}>
<Button
onClick={() =>
deleteKeyPair("minioCAs", keyPair.id)
}
color="secondary"
>
Remove
</Button>
</Grid>
</Fragment>
))}
</Grid>
<Grid container item xs={12}>
<Button
onClick={() => addKeyPair("minioCAs")}
color="primary"
>
Add More
</Button>
</Grid>
</Grid>
<Grid container>
<Grid item xs={12}>
<br />
<Divider />
<br />
</Grid>
</Grid>
{tenant?.consoleEnabled ? (
<Fragment>
<Grid container>
<Grid container item xs={12}>
<Typography
variant="overline"
display="block"
gutterBottom
>
Console Certificates
</Typography>
</Grid>
<Grid container item xs={12}>
{consoleTLSCertificateSecrets.map(
(certificateInfo: ICertificateInfo) => (
<Chip
key={certificateInfo.name}
variant="outlined"
color="primary"
className={classes.certificateInfo}
label={
<div>
<Typography
variant="subtitle1"
display="block"
gutterBottom
>
{certificateInfo.name}
</Typography>
<Typography
className={classes.italic}
variant="caption"
display="block"
gutterBottom
>
{certificateInfo.domains.join(", ")}
</Typography>
<Typography
className={classes.bold}
variant="overline"
gutterBottom
>
Expiry:&nbsp;
</Typography>
<Typography variant="caption" gutterBottom>
<Moment format="YYYY-MM-DD">
{certificateInfo.expiry}
</Moment>
</Typography>
</div>
}
onDelete={() =>
removeCertificate(certificateInfo)
}
/>
)
)}
</Grid>
<Grid container item xs={12}>
<br />
</Grid>
<Grid container item xs={12}>
{consoleCertificates.map((keyPair: KeyPair) => (
<Fragment key={keyPair.id}>
<Grid item xs={6}>
<FileSelector
onChange={(encodedValue, fileName) =>
addFileToKeyPair(
"console",
keyPair.id,
"cert",
fileName,
encodedValue
)
}
accept=".cer,.crt,.cert,.pem"
id="consoleCert"
name="consoleCert"
label="Cert"
value={keyPair.cert}
/>
</Grid>
<Grid item xs={6}>
<FileSelector
onChange={(encodedValue, fileName) =>
addFileToKeyPair(
"console",
keyPair.id,
"key",
fileName,
encodedValue
)
}
accept=".key,.pem"
id="consoleKey"
name="consoleKey"
label="Key"
value={keyPair.key}
/>
</Grid>
</Fragment>
))}
</Grid>
<Grid container item xs={12}>
<Typography
variant="overline"
display="block"
gutterBottom
>
Console CA Certificates
</Typography>
</Grid>
<Grid container item xs={12}>
{consoleTLSCaCertificateSecrets.map(
(certificateInfo: ICertificateInfo) => (
<Chip
key={certificateInfo.name}
variant="outlined"
color="primary"
className={classes.certificateInfo}
label={
<div>
<Typography
variant="subtitle1"
display="block"
gutterBottom
>
{certificateInfo.name}
</Typography>
<Typography
className={classes.italic}
variant="caption"
display="block"
gutterBottom
>
{certificateInfo.domains.join(", ")}
</Typography>
<Typography
className={classes.bold}
variant="overline"
gutterBottom
>
Expiry:&nbsp;
</Typography>
<Typography variant="caption" gutterBottom>
<Moment format="YYYY-MM-DD">
{certificateInfo.expiry}
</Moment>
</Typography>
</div>
}
onDelete={() =>
removeCertificate(certificateInfo)
}
/>
)
)}
</Grid>
<Grid container item xs={12}>
<br />
</Grid>
<Grid container item xs={12}>
{consoleCaCertificates.map((keyPair: KeyPair) => (
<Fragment key={keyPair.id}>
<Grid item xs={10}>
<FileSelector
onChange={(encodedValue, fileName) =>
addFileToKeyPair(
"consoleCAs",
keyPair.id,
"cert",
fileName,
encodedValue
)
}
accept=".cer,.crt,.cert,.pem"
id="tlsCert"
name="tlsCert"
label="Cert"
value={keyPair.cert}
/>
</Grid>
<Grid item xs={1}>
<Button
onClick={() =>
deleteKeyPair("consoleCAs", keyPair.id)
}
color="secondary"
>
Remove
</Button>
</Grid>
</Fragment>
))}
</Grid>
<Grid container item xs={12}>
<Button
onClick={() => addKeyPair("consoleCAs")}
color="primary"
>
Add More
</Button>
</Grid>
</Grid>
<Grid container>
<Grid item xs={12}>
<br />
<Divider />
<br />
</Grid>
</Grid>
</Fragment>
) : null}
</Fragment>
)}
<Grid item xs={12} className={classes.buttonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={dialogOpen}
onClick={() => setDialogOpen(true)}
>
Save
</Button>
</Grid>
</Fragment>
)}
</Paper>
</React.Fragment>
);
};
const mapState = (state: AppState) => ({
loadingTenant: state.tenants.tenantDetails.loadingTenant,
selectedTenant: state.tenants.tenantDetails.currentTenant,
tenant: state.tenants.tenantDetails.tenantInfo,
});
const mapDispatchToProps = {
setErrorSnackMessage,
setTenantDetailsLoad,
};
const connector = connect(mapState, mapDispatchToProps);
export default withStyles(styles)(connector(TenantSecurity));

View File

@@ -39,6 +39,9 @@ interface ITenantsSummary {
logEnabled: boolean;
monitoringEnabled: boolean;
encryptionEnabled: boolean;
minioTLS: boolean;
consoleTLS: boolean;
consoleEnabled: boolean;
adEnabled: boolean;
oicEnabled: boolean;
loadingTenant: boolean;
@@ -77,6 +80,9 @@ const TenantSummary = ({
logEnabled,
monitoringEnabled,
encryptionEnabled,
minioTLS,
consoleTLS,
consoleEnabled,
adEnabled,
oicEnabled,
loadingTenant,
@@ -359,6 +365,32 @@ const TenantSummary = ({
</Button>
</td>
</tr>
<tr>
<td className={classes.titleCol}>MinIO TLS:</td>
<td>
<Button
color="primary"
className={classes.anchorButton}
>
{minioTLS ? "Enabled" : "Disabled"}
</Button>
</td>
{consoleEnabled ? (
<Fragment>
<td className={classes.titleCol}>Console TLS:</td>
<td>
<Button
color="primary"
className={classes.anchorButton}
>
{consoleTLS ? "Enabled" : "Disabled"}
</Button>
</td>
</Fragment>
) : (
<td />
)}
</tr>
<tr>
<td className={classes.titleCol}>Encryption:</td>
<td>
@@ -430,6 +462,13 @@ const mapState = (state: AppState) => ({
"encryptionEnabled",
false
),
minioTLS: get(state.tenants.tenantDetails.tenantInfo, "minioTLS", false),
consoleTLS: get(state.tenants.tenantDetails.tenantInfo, "consoleTLS", false),
consoleEnabled: get(
state.tenants.tenantDetails.tenantInfo,
"consoleEnabled",
false
),
adEnabled: get(state.tenants.tenantDetails.tenantInfo, "idpAdEnabled", false),
oicEnabled: get(
state.tenants.tenantDetails.tenantInfo,

View File

@@ -58,6 +58,24 @@ export const TENANT_DETAILS_SET_CURRENT_TENANT =
"TENANT_DETAILS/SET_CURRENT_TENANT";
export const TENANT_DETAILS_SET_TENANT = "TENANT_DETAILS/SET_TENANT";
export const TENANT_DETAILS_SET_TAB = "TENANT_DETAILS/SET_TAB";
export interface ICertificateInfo {
name: string;
serialNumber: string;
domains: string[];
expiry: string;
}
export interface ICustomCertificates {
minio: ICertificateInfo[];
minioCAs: ICertificateInfo[];
console: ICertificateInfo[];
consoleCAs: ICertificateInfo[];
}
export interface ITenantSecurityResponse {
autoCert: boolean;
customCertificates: ICustomCertificates;
}
export interface ICreateTenant {
page: number;

View File

@@ -19,8 +19,10 @@ package restapi
import (
"bytes"
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"net"
@@ -31,6 +33,8 @@ import (
"strings"
"time"
"github.com/minio/console/pkg/auth/utils"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
@@ -101,6 +105,26 @@ func registerTenantHandlers(api *operations.ConsoleAPI) {
})
// Tenant Security details
api.AdminAPITenantSecurityHandler = admin_api.TenantSecurityHandlerFunc(func(params admin_api.TenantSecurityParams, session *models.Principal) middleware.Responder {
resp, err := getTenantSecurityResponse(session, params)
if err != nil {
return admin_api.NewTenantSecurityDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewTenantSecurityOK().WithPayload(resp)
})
// Update Tenant Security configuration
api.AdminAPIUpdateTenantSecurityHandler = admin_api.UpdateTenantSecurityHandlerFunc(func(params admin_api.UpdateTenantSecurityParams, session *models.Principal) middleware.Responder {
err := getUpdateTenantSecurityResponse(session, params)
if err != nil {
return admin_api.NewUpdateTenantSecurityDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewUpdateTenantSecurityNoContent()
})
// Delete Tenant
api.AdminAPIDeleteTenantHandler = admin_api.DeleteTenantHandlerFunc(func(params admin_api.DeleteTenantParams, session *models.Principal) middleware.Responder {
err := getDeleteTenantResponse(session, params)
@@ -442,6 +466,9 @@ func getTenantDetailsResponse(session *models.Principal, params admin_api.Tenant
info.EncryptionEnabled = minTenant.HasKESEnabled()
info.IdpAdEnabled = adEnabled
info.IdpOicEnabled = oicEnabled
info.MinioTLS = minTenant.TLS()
info.ConsoleTLS = minTenant.AutoCert() || minTenant.ConsoleExternalCert()
info.ConsoleEnabled = minTenant.HasConsoleEnabled()
if minTenant.Spec.Console != nil {
// obtain current subnet license for tenant (if exists)
@@ -522,6 +549,285 @@ func getTenantDetailsResponse(session *models.Principal, params admin_api.Tenant
return info, nil
}
// parseTenantCertificates convert public key pem certificates stored in k8s secrets for a given Tenant into x509 certificates
func parseTenantCertificates(ctx context.Context, clientSet K8sClientI, namespace string, secrets []*miniov2.LocalCertificateReference) ([]*models.CertificateInfo, error) {
var certificates []*models.CertificateInfo
publicKey := "public.crt"
// Iterate over TLS secrets and build array of CertificateInfo structure
// that will be used to display information about certs in the UI
for _, secret := range secrets {
keyPair, err := clientSet.getSecret(ctx, namespace, secret.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
if secret.Type == "kubernetes.io/tls" || secret.Type == "cert-manager.io/v1alpha2" {
publicKey = "tls.crt"
}
// Extract public key from certificate TLS secret
if rawCert, ok := keyPair.Data[publicKey]; ok {
block, _ := pem.Decode(rawCert)
if block == nil {
// If certificate failed to decode skip
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
certificates = append(certificates, &models.CertificateInfo{
SerialNumber: cert.SerialNumber.String(),
Name: secret.Name,
Domains: cert.DNSNames,
Expiry: cert.NotAfter.String(),
})
}
}
return certificates, nil
}
func getTenantSecurity(ctx context.Context, clientSet K8sClientI, tenant *miniov2.Tenant) (response *models.TenantSecurityResponse, err error) {
var minioExternalCertificates []*models.CertificateInfo
var minioExternalCaCertificates []*models.CertificateInfo
var consoleExternalCertificates []*models.CertificateInfo
var consoleExternalCaCertificates []*models.CertificateInfo
// Certificates used by MinIO server
if minioExternalCertificates, err = parseTenantCertificates(ctx, clientSet, tenant.Namespace, tenant.Spec.ExternalCertSecret); err != nil {
return nil, err
}
// CA Certificates used by MinIO server
if minioExternalCaCertificates, err = parseTenantCertificates(ctx, clientSet, tenant.Namespace, tenant.Spec.ExternalCaCertSecret); err != nil {
return nil, err
}
if tenant.HasConsoleEnabled() {
// Certificate used by Console server
if tenant.Spec.Console.ExternalCertSecret != nil {
if consoleExternalCertificates, err = parseTenantCertificates(ctx, clientSet, tenant.Namespace, []*miniov2.LocalCertificateReference{tenant.Spec.Console.ExternalCertSecret}); err != nil {
return nil, err
}
}
// CA Certificates used by Console server
if consoleExternalCaCertificates, err = parseTenantCertificates(ctx, clientSet, tenant.Namespace, tenant.Spec.Console.ExternalCaCertSecret); err != nil {
return nil, err
}
}
return &models.TenantSecurityResponse{
AutoCert: tenant.AutoCert(),
CustomCertificates: &models.TenantSecurityResponseCustomCertificates{
Minio: minioExternalCertificates,
MinioCAs: minioExternalCaCertificates,
Console: consoleExternalCertificates,
ConsoleCAs: consoleExternalCaCertificates,
},
}, nil
}
func getTenantSecurityResponse(session *models.Principal, params admin_api.TenantSecurityParams) (*models.TenantSecurityResponse, *models.Error) {
// 5 seconds timeout
ctx := context.Background()
//ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
//defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return nil, prepareError(err)
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
k8sClient := k8sClient{
client: clientSet,
}
if err != nil {
return nil, prepareError(err)
}
info, err := getTenantSecurity(ctx, &k8sClient, minTenant)
if err != nil {
return nil, prepareError(err)
}
return info, nil
}
func getUpdateTenantSecurityResponse(session *models.Principal, params admin_api.UpdateTenantSecurityParams) *models.Error {
// 5 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
}
k8sClient := k8sClient{
client: clientSet,
}
opClient := &operatorClient{
client: opClientClientSet,
}
if err := updateTenantSecurity(ctx, opClient, &k8sClient, params.Namespace, params); err != nil {
return prepareError(err, errors.New("unable to update tenant"))
}
return nil
}
// updateTenantSecurity
func updateTenantSecurity(ctx context.Context, operatorClient OperatorClientI, client K8sClientI, namespace string, params admin_api.UpdateTenantSecurityParams) error {
minInst, err := operatorClient.TenantGet(ctx, namespace, params.Tenant, metav1.GetOptions{})
if err != nil {
return err
}
// Update AutoCert
minInst.Spec.RequestAutoCert = &params.Body.AutoCert
var newMinIOExternalCertSecret []*miniov2.LocalCertificateReference
var newMinIOExternalCaCertSecret []*miniov2.LocalCertificateReference
var newConsoleExternalCertSecret *miniov2.LocalCertificateReference
var newConsoleExternalCaCertSecret []*miniov2.LocalCertificateReference
// Remove Certificate Secrets from MinIO (Tenant.Spec.ExternalCertSecret)
for _, certificate := range minInst.Spec.ExternalCertSecret {
skip := false
for _, certificateToBeDeleted := range params.Body.CustomCertificates.SecretsToBeDeleted {
if certificate.Name == certificateToBeDeleted {
skip = true
break
}
}
if skip {
continue
}
newMinIOExternalCertSecret = append(newMinIOExternalCertSecret, certificate)
}
// Remove Certificate Secrets from MinIO CAs (Tenant.Spec.ExternalCaCertSecret)
for _, certificate := range minInst.Spec.ExternalCaCertSecret {
skip := false
for _, certificateToBeDeleted := range params.Body.CustomCertificates.SecretsToBeDeleted {
if certificate.Name == certificateToBeDeleted {
skip = true
break
}
}
if skip {
continue
}
newMinIOExternalCaCertSecret = append(newMinIOExternalCaCertSecret, certificate)
}
if minInst.HasConsoleEnabled() {
// Remove Certificate Secrets from Console (Tenant.Spec.Console.ExternalCertSecret)
if minInst.ConsoleExternalCert() {
newConsoleExternalCertSecret = minInst.Spec.Console.ExternalCertSecret
for _, certificateToBeDeleted := range params.Body.CustomCertificates.SecretsToBeDeleted {
if newConsoleExternalCertSecret.Name == certificateToBeDeleted {
newConsoleExternalCertSecret = nil
break
}
}
}
// Remove Certificate Secrets from Console CAs (Tenant.Spec.Console.ExternalCaCertSecret)
for _, certificate := range minInst.Spec.Console.ExternalCaCertSecret {
skip := false
for _, certificateToBeDeleted := range params.Body.CustomCertificates.SecretsToBeDeleted {
if certificate.Name == certificateToBeDeleted {
skip = true
break
}
}
if skip {
continue
}
newConsoleExternalCaCertSecret = append(newConsoleExternalCaCertSecret, certificate)
}
}
//Create new Certificate Secrets for MinIO
secretName := fmt.Sprintf("%s-%s", minInst.Name, strings.ToLower(utils.RandomCharString(5)))
externalCertSecretName := fmt.Sprintf("%s-external-certificates", secretName)
externalCertSecrets, err := createOrReplaceExternalCertSecrets(ctx, client, minInst.Namespace, params.Body.CustomCertificates.Minio, externalCertSecretName, minInst.Name)
if err != nil {
return err
}
newMinIOExternalCertSecret = append(newMinIOExternalCertSecret, externalCertSecrets...)
// Create new CAs Certificate Secrets for MinIO
var caCertificates []tenantSecret
for i, caCertificate := range params.Body.CustomCertificates.MinioCAs {
certificateContent, err := base64.StdEncoding.DecodeString(caCertificate)
if err != nil {
return err
}
caCertificates = append(caCertificates, tenantSecret{
Name: fmt.Sprintf("%s-ca-certificate-%d", secretName, i),
Content: map[string][]byte{
"public.crt": certificateContent,
},
})
}
if len(caCertificates) > 0 {
certificateSecrets, err := createOrReplaceSecrets(ctx, client, minInst.Namespace, caCertificates, minInst.Name)
if err != nil {
return err
}
newMinIOExternalCaCertSecret = append(newMinIOExternalCaCertSecret, certificateSecrets...)
}
// Create new Certificate Secrets for Console
consoleExternalCertSecretName := fmt.Sprintf("%s-console-external-certificates", secretName)
consoleExternalCertSecrets, err := createOrReplaceExternalCertSecrets(ctx, client, minInst.Namespace, params.Body.CustomCertificates.Console, consoleExternalCertSecretName, minInst.Name)
if err != nil {
return err
}
if len(consoleExternalCertSecrets) > 0 {
newConsoleExternalCertSecret = consoleExternalCertSecrets[0]
}
// Create new CAs Certificate Secrets for Console
var consoleCaCertificates []tenantSecret
for i, caCertificate := range params.Body.CustomCertificates.ConsoleCAs {
certificateContent, err := base64.StdEncoding.DecodeString(caCertificate)
if err != nil {
return err
}
consoleCaCertificates = append(consoleCaCertificates, tenantSecret{
Name: fmt.Sprintf("%s-console-ca-certificate-%d", secretName, i),
Content: map[string][]byte{
"public.crt": certificateContent,
},
})
}
if len(consoleCaCertificates) > 0 {
certificateSecrets, err := createOrReplaceSecrets(ctx, client, minInst.Namespace, consoleCaCertificates, minInst.Name)
if err != nil {
return err
}
newConsoleExternalCaCertSecret = append(newConsoleExternalCaCertSecret, certificateSecrets...)
}
// Update External Certificates
minInst.Spec.ExternalCertSecret = newMinIOExternalCertSecret
minInst.Spec.ExternalCaCertSecret = newMinIOExternalCaCertSecret
if minInst.HasConsoleEnabled() {
minInst.Spec.Console.ExternalCertSecret = newConsoleExternalCertSecret
minInst.Spec.Console.ExternalCaCertSecret = newConsoleExternalCaCertSecret
}
_, err = operatorClient.TenantUpdate(ctx, minInst, metav1.UpdateOptions{})
if err != nil {
return err
}
// Remove Certificate Secrets from Tenant namespace
for _, secretName := range params.Body.CustomCertificates.SecretsToBeDeleted {
err = client.deleteSecret(ctx, minInst.Namespace, secretName, metav1.DeleteOptions{})
if err != nil {
LogError("error deleting secret: %v", err)
}
}
return nil
}
func listTenants(ctx context.Context, operatorClient OperatorClientI, namespace string, limit *int32) (*models.ListTenantsResponse, error) {
listOpts := metav1.ListOptions{
Limit: 10,

View File

@@ -46,6 +46,8 @@ import (
var opClientTenantDeleteMock func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error
var opClientTenantGetMock func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error)
var opClientTenantPatchMock func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error)
var opClientTenantUpdateMock func(ctx context.Context, tenant *miniov2.Tenant, opts metav1.UpdateOptions) (*miniov2.Tenant, error)
var opClientTenantListMock func(ctx context.Context, namespace string, opts metav1.ListOptions) (*miniov2.TenantList, error)
var httpClientGetMock func(url string) (resp *http.Response, err error)
var httpClientPostMock func(url, contentType string, body io.Reader) (resp *http.Response, err error)
@@ -68,6 +70,11 @@ func (ac opClientMock) TenantPatch(ctx context.Context, namespace string, tenant
return opClientTenantPatchMock(ctx, namespace, tenantName, pt, data, options)
}
// mock function of TenantUpdate()
func (ac opClientMock) TenantUpdate(ctx context.Context, tenant *miniov2.Tenant, opts metav1.UpdateOptions) (*miniov2.Tenant, error) {
return opClientTenantUpdateMock(ctx, tenant, opts)
}
// mock function of TenantList()
func (ac opClientMock) TenantList(ctx context.Context, namespace string, opts metav1.ListOptions) (*miniov2.TenantList, error) {
return opClientTenantListMock(ctx, namespace, opts)

View File

@@ -3033,6 +3033,83 @@ func init() {
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/security": {
"get": {
"tags": [
"AdminAPI"
],
"summary": "Tenant Security",
"operationId": "TenantSecurity",
"parameters": [
{
"type": "string",
"name": "namespace",
"in": "path",
"required": true
},
{
"type": "string",
"name": "tenant",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/tenantSecurityResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
},
"post": {
"tags": [
"AdminAPI"
],
"summary": "Update Tenant Security",
"operationId": "UpdateTenantSecurity",
"parameters": [
{
"type": "string",
"name": "namespace",
"in": "path",
"required": true
},
{
"type": "string",
"name": "tenant",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/updateTenantSecurityRequest"
}
}
],
"responses": {
"204": {
"description": "A successful response."
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/usage": {
"get": {
"tags": [
@@ -4645,6 +4722,26 @@ func init() {
}
}
},
"certificateInfo": {
"type": "object",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"expiry": {
"type": "string"
},
"name": {
"type": "string"
},
"serialNumber": {
"type": "string"
}
}
},
"changeUserPasswordRequest": {
"type": "object",
"required": [
@@ -6901,6 +6998,12 @@ func init() {
"tenant": {
"type": "object",
"properties": {
"consoleEnabled": {
"type": "boolean"
},
"consoleTLS": {
"type": "boolean"
},
"console_image": {
"type": "string"
},
@@ -6942,6 +7045,9 @@ func init() {
"logEnabled": {
"type": "boolean"
},
"minioTLS": {
"type": "boolean"
},
"monitoringEnabled": {
"type": "boolean"
},
@@ -7041,6 +7147,43 @@ func init() {
}
}
},
"tenantSecurityResponse": {
"type": "object",
"properties": {
"autoCert": {
"type": "boolean"
},
"customCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"consoleCAs": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"minio": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"minioCAs": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
}
}
}
}
},
"tenantStatus": {
"type": "object",
"properties": {
@@ -7303,6 +7446,49 @@ func init() {
}
}
},
"updateTenantSecurityRequest": {
"type": "object",
"properties": {
"autoCert": {
"type": "boolean"
},
"customCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/keyPairConfiguration"
}
},
"consoleCAs": {
"type": "array",
"items": {
"type": "string"
}
},
"minio": {
"type": "array",
"items": {
"$ref": "#/definitions/keyPairConfiguration"
}
},
"minioCAs": {
"type": "array",
"items": {
"type": "string"
}
},
"secretsToBeDeleted": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"updateUser": {
"type": "object",
"required": [
@@ -10527,6 +10713,83 @@ func init() {
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/security": {
"get": {
"tags": [
"AdminAPI"
],
"summary": "Tenant Security",
"operationId": "TenantSecurity",
"parameters": [
{
"type": "string",
"name": "namespace",
"in": "path",
"required": true
},
{
"type": "string",
"name": "tenant",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/tenantSecurityResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
},
"post": {
"tags": [
"AdminAPI"
],
"summary": "Update Tenant Security",
"operationId": "UpdateTenantSecurity",
"parameters": [
{
"type": "string",
"name": "namespace",
"in": "path",
"required": true
},
{
"type": "string",
"name": "tenant",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/updateTenantSecurityRequest"
}
}
],
"responses": {
"204": {
"description": "A successful response."
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/usage": {
"get": {
"tags": [
@@ -12200,6 +12463,70 @@ func init() {
}
}
},
"TenantSecurityResponseCustomCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"consoleCAs": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"minio": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"minioCAs": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
}
}
},
"UpdateTenantSecurityRequestCustomCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/keyPairConfiguration"
}
},
"consoleCAs": {
"type": "array",
"items": {
"type": "string"
}
},
"minio": {
"type": "array",
"items": {
"$ref": "#/definitions/keyPairConfiguration"
}
},
"minioCAs": {
"type": "array",
"items": {
"type": "string"
}
},
"secretsToBeDeleted": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"VaultConfigurationApprole": {
"type": "object",
"required": [
@@ -12779,6 +13106,26 @@ func init() {
}
}
},
"certificateInfo": {
"type": "object",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"expiry": {
"type": "string"
},
"name": {
"type": "string"
},
"serialNumber": {
"type": "string"
}
}
},
"changeUserPasswordRequest": {
"type": "object",
"required": [
@@ -14888,6 +15235,12 @@ func init() {
"tenant": {
"type": "object",
"properties": {
"consoleEnabled": {
"type": "boolean"
},
"consoleTLS": {
"type": "boolean"
},
"console_image": {
"type": "string"
},
@@ -14929,6 +15282,9 @@ func init() {
"logEnabled": {
"type": "boolean"
},
"minioTLS": {
"type": "boolean"
},
"monitoringEnabled": {
"type": "boolean"
},
@@ -15028,6 +15384,43 @@ func init() {
}
}
},
"tenantSecurityResponse": {
"type": "object",
"properties": {
"autoCert": {
"type": "boolean"
},
"customCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"consoleCAs": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"minio": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"minioCAs": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
}
}
}
}
},
"tenantStatus": {
"type": "object",
"properties": {
@@ -15290,6 +15683,49 @@ func init() {
}
}
},
"updateTenantSecurityRequest": {
"type": "object",
"properties": {
"autoCert": {
"type": "boolean"
},
"customCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/keyPairConfiguration"
}
},
"consoleCAs": {
"type": "array",
"items": {
"type": "string"
}
},
"minio": {
"type": "array",
"items": {
"$ref": "#/definitions/keyPairConfiguration"
}
},
"minioCAs": {
"type": "array",
"items": {
"type": "string"
}
},
"secretsToBeDeleted": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"updateUser": {
"type": "object",
"required": [

View File

@@ -0,0 +1,88 @@
// 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 admin_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"
)
// TenantSecurityHandlerFunc turns a function with the right signature into a tenant security handler
type TenantSecurityHandlerFunc func(TenantSecurityParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn TenantSecurityHandlerFunc) Handle(params TenantSecurityParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// TenantSecurityHandler interface for that can handle valid tenant security params
type TenantSecurityHandler interface {
Handle(TenantSecurityParams, *models.Principal) middleware.Responder
}
// NewTenantSecurity creates a new http.Handler for the tenant security operation
func NewTenantSecurity(ctx *middleware.Context, handler TenantSecurityHandler) *TenantSecurity {
return &TenantSecurity{Context: ctx, Handler: handler}
}
/* TenantSecurity swagger:route GET /namespaces/{namespace}/tenants/{tenant}/security AdminAPI tenantSecurity
Tenant Security
*/
type TenantSecurity struct {
Context *middleware.Context
Handler TenantSecurityHandler
}
func (o *TenantSecurity) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewTenantSecurityParams()
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,112 @@
// 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 admin_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/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
)
// NewTenantSecurityParams creates a new TenantSecurityParams object
//
// There are no default values defined in the spec.
func NewTenantSecurityParams() TenantSecurityParams {
return TenantSecurityParams{}
}
// TenantSecurityParams contains all the bound params for the tenant security operation
// typically these are obtained from a http.Request
//
// swagger:parameters TenantSecurity
type TenantSecurityParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: path
*/
Namespace string
/*
Required: true
In: path
*/
Tenant 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 NewTenantSecurityParams() beforehand.
func (o *TenantSecurityParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
rNamespace, rhkNamespace, _ := route.Params.GetOK("namespace")
if err := o.bindNamespace(rNamespace, rhkNamespace, route.Formats); err != nil {
res = append(res, err)
}
rTenant, rhkTenant, _ := route.Params.GetOK("tenant")
if err := o.bindTenant(rTenant, rhkTenant, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindNamespace binds and validates parameter Namespace from path.
func (o *TenantSecurityParams) bindNamespace(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.Namespace = raw
return nil
}
// bindTenant binds and validates parameter Tenant from path.
func (o *TenantSecurityParams) bindTenant(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.Tenant = raw
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 admin_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"
)
// TenantSecurityOKCode is the HTTP code returned for type TenantSecurityOK
const TenantSecurityOKCode int = 200
/*TenantSecurityOK A successful response.
swagger:response tenantSecurityOK
*/
type TenantSecurityOK struct {
/*
In: Body
*/
Payload *models.TenantSecurityResponse `json:"body,omitempty"`
}
// NewTenantSecurityOK creates TenantSecurityOK with default headers values
func NewTenantSecurityOK() *TenantSecurityOK {
return &TenantSecurityOK{}
}
// WithPayload adds the payload to the tenant security o k response
func (o *TenantSecurityOK) WithPayload(payload *models.TenantSecurityResponse) *TenantSecurityOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the tenant security o k response
func (o *TenantSecurityOK) SetPayload(payload *models.TenantSecurityResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *TenantSecurityOK) 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
}
}
}
/*TenantSecurityDefault Generic error response.
swagger:response tenantSecurityDefault
*/
type TenantSecurityDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewTenantSecurityDefault creates TenantSecurityDefault with default headers values
func NewTenantSecurityDefault(code int) *TenantSecurityDefault {
if code <= 0 {
code = 500
}
return &TenantSecurityDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the tenant security default response
func (o *TenantSecurityDefault) WithStatusCode(code int) *TenantSecurityDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the tenant security default response
func (o *TenantSecurityDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the tenant security default response
func (o *TenantSecurityDefault) WithPayload(payload *models.Error) *TenantSecurityDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the tenant security default response
func (o *TenantSecurityDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *TenantSecurityDefault) 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,124 @@
// 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 admin_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"
"strings"
)
// TenantSecurityURL generates an URL for the tenant security operation
type TenantSecurityURL struct {
Namespace string
Tenant 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 *TenantSecurityURL) WithBasePath(bp string) *TenantSecurityURL {
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 *TenantSecurityURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *TenantSecurityURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/namespaces/{namespace}/tenants/{tenant}/security"
namespace := o.Namespace
if namespace != "" {
_path = strings.Replace(_path, "{namespace}", namespace, -1)
} else {
return nil, errors.New("namespace is required on TenantSecurityURL")
}
tenant := o.Tenant
if tenant != "" {
_path = strings.Replace(_path, "{tenant}", tenant, -1)
} else {
return nil, errors.New("tenant is required on TenantSecurityURL")
}
_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 *TenantSecurityURL) 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 *TenantSecurityURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *TenantSecurityURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on TenantSecurityURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on TenantSecurityURL")
}
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 *TenantSecurityURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,88 @@
// 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 admin_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"
)
// UpdateTenantSecurityHandlerFunc turns a function with the right signature into a update tenant security handler
type UpdateTenantSecurityHandlerFunc func(UpdateTenantSecurityParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn UpdateTenantSecurityHandlerFunc) Handle(params UpdateTenantSecurityParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// UpdateTenantSecurityHandler interface for that can handle valid update tenant security params
type UpdateTenantSecurityHandler interface {
Handle(UpdateTenantSecurityParams, *models.Principal) middleware.Responder
}
// NewUpdateTenantSecurity creates a new http.Handler for the update tenant security operation
func NewUpdateTenantSecurity(ctx *middleware.Context, handler UpdateTenantSecurityHandler) *UpdateTenantSecurity {
return &UpdateTenantSecurity{Context: ctx, Handler: handler}
}
/* UpdateTenantSecurity swagger:route POST /namespaces/{namespace}/tenants/{tenant}/security AdminAPI updateTenantSecurity
Update Tenant Security
*/
type UpdateTenantSecurity struct {
Context *middleware.Context
Handler UpdateTenantSecurityHandler
}
func (o *UpdateTenantSecurity) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewUpdateTenantSecurityParams()
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,151 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"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"
)
// NewUpdateTenantSecurityParams creates a new UpdateTenantSecurityParams object
//
// There are no default values defined in the spec.
func NewUpdateTenantSecurityParams() UpdateTenantSecurityParams {
return UpdateTenantSecurityParams{}
}
// UpdateTenantSecurityParams contains all the bound params for the update tenant security operation
// typically these are obtained from a http.Request
//
// swagger:parameters UpdateTenantSecurity
type UpdateTenantSecurityParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.UpdateTenantSecurityRequest
/*
Required: true
In: path
*/
Namespace string
/*
Required: true
In: path
*/
Tenant 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 NewUpdateTenantSecurityParams() beforehand.
func (o *UpdateTenantSecurityParams) 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.UpdateTenantSecurityRequest
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(context.Background())
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", ""))
}
rNamespace, rhkNamespace, _ := route.Params.GetOK("namespace")
if err := o.bindNamespace(rNamespace, rhkNamespace, route.Formats); err != nil {
res = append(res, err)
}
rTenant, rhkTenant, _ := route.Params.GetOK("tenant")
if err := o.bindTenant(rTenant, rhkTenant, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindNamespace binds and validates parameter Namespace from path.
func (o *UpdateTenantSecurityParams) bindNamespace(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.Namespace = raw
return nil
}
// bindTenant binds and validates parameter Tenant from path.
func (o *UpdateTenantSecurityParams) bindTenant(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.Tenant = raw
return nil
}

View File

@@ -0,0 +1,113 @@
// 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 admin_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"
)
// UpdateTenantSecurityNoContentCode is the HTTP code returned for type UpdateTenantSecurityNoContent
const UpdateTenantSecurityNoContentCode int = 204
/*UpdateTenantSecurityNoContent A successful response.
swagger:response updateTenantSecurityNoContent
*/
type UpdateTenantSecurityNoContent struct {
}
// NewUpdateTenantSecurityNoContent creates UpdateTenantSecurityNoContent with default headers values
func NewUpdateTenantSecurityNoContent() *UpdateTenantSecurityNoContent {
return &UpdateTenantSecurityNoContent{}
}
// WriteResponse to the client
func (o *UpdateTenantSecurityNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(204)
}
/*UpdateTenantSecurityDefault Generic error response.
swagger:response updateTenantSecurityDefault
*/
type UpdateTenantSecurityDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewUpdateTenantSecurityDefault creates UpdateTenantSecurityDefault with default headers values
func NewUpdateTenantSecurityDefault(code int) *UpdateTenantSecurityDefault {
if code <= 0 {
code = 500
}
return &UpdateTenantSecurityDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the update tenant security default response
func (o *UpdateTenantSecurityDefault) WithStatusCode(code int) *UpdateTenantSecurityDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the update tenant security default response
func (o *UpdateTenantSecurityDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the update tenant security default response
func (o *UpdateTenantSecurityDefault) WithPayload(payload *models.Error) *UpdateTenantSecurityDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the update tenant security default response
func (o *UpdateTenantSecurityDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *UpdateTenantSecurityDefault) 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,124 @@
// 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 admin_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"
"strings"
)
// UpdateTenantSecurityURL generates an URL for the update tenant security operation
type UpdateTenantSecurityURL struct {
Namespace string
Tenant 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 *UpdateTenantSecurityURL) WithBasePath(bp string) *UpdateTenantSecurityURL {
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 *UpdateTenantSecurityURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *UpdateTenantSecurityURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/namespaces/{namespace}/tenants/{tenant}/security"
namespace := o.Namespace
if namespace != "" {
_path = strings.Replace(_path, "{namespace}", namespace, -1)
} else {
return nil, errors.New("namespace is required on UpdateTenantSecurityURL")
}
tenant := o.Tenant
if tenant != "" {
_path = strings.Replace(_path, "{tenant}", tenant, -1)
} else {
return nil, errors.New("tenant is required on UpdateTenantSecurityURL")
}
_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 *UpdateTenantSecurityURL) 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 *UpdateTenantSecurityURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *UpdateTenantSecurityURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on UpdateTenantSecurityURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on UpdateTenantSecurityURL")
}
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 *UpdateTenantSecurityURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -393,6 +393,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
AdminAPITenantInfoHandler: admin_api.TenantInfoHandlerFunc(func(params admin_api.TenantInfoParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.TenantInfo has not yet been implemented")
}),
AdminAPITenantSecurityHandler: admin_api.TenantSecurityHandlerFunc(func(params admin_api.TenantSecurityParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.TenantSecurity has not yet been implemented")
}),
AdminAPITenantUpdateCertificateHandler: admin_api.TenantUpdateCertificateHandlerFunc(func(params admin_api.TenantUpdateCertificateParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.TenantUpdateCertificate has not yet been implemented")
}),
@@ -417,6 +420,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
AdminAPIUpdateTenantHandler: admin_api.UpdateTenantHandlerFunc(func(params admin_api.UpdateTenantParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.UpdateTenant has not yet been implemented")
}),
AdminAPIUpdateTenantSecurityHandler: admin_api.UpdateTenantSecurityHandlerFunc(func(params admin_api.UpdateTenantSecurityParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.UpdateTenantSecurity has not yet been implemented")
}),
AdminAPIUpdateUserGroupsHandler: admin_api.UpdateUserGroupsHandlerFunc(func(params admin_api.UpdateUserGroupsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.UpdateUserGroups has not yet been implemented")
}),
@@ -696,6 +702,8 @@ type ConsoleAPI struct {
AdminAPITenantDetailsHandler admin_api.TenantDetailsHandler
// AdminAPITenantInfoHandler sets the operation handler for the tenant info operation
AdminAPITenantInfoHandler admin_api.TenantInfoHandler
// AdminAPITenantSecurityHandler sets the operation handler for the tenant security operation
AdminAPITenantSecurityHandler admin_api.TenantSecurityHandler
// AdminAPITenantUpdateCertificateHandler sets the operation handler for the tenant update certificate operation
AdminAPITenantUpdateCertificateHandler admin_api.TenantUpdateCertificateHandler
// AdminAPITenantUpdateEncryptionHandler sets the operation handler for the tenant update encryption operation
@@ -712,6 +720,8 @@ type ConsoleAPI struct {
AdminAPIUpdateGroupHandler admin_api.UpdateGroupHandler
// AdminAPIUpdateTenantHandler sets the operation handler for the update tenant operation
AdminAPIUpdateTenantHandler admin_api.UpdateTenantHandler
// AdminAPIUpdateTenantSecurityHandler sets the operation handler for the update tenant security operation
AdminAPIUpdateTenantSecurityHandler admin_api.UpdateTenantSecurityHandler
// AdminAPIUpdateUserGroupsHandler sets the operation handler for the update user groups operation
AdminAPIUpdateUserGroupsHandler admin_api.UpdateUserGroupsHandler
// AdminAPIUpdateUserInfoHandler sets the operation handler for the update user info operation
@@ -1130,6 +1140,9 @@ func (o *ConsoleAPI) Validate() error {
if o.AdminAPITenantInfoHandler == nil {
unregistered = append(unregistered, "admin_api.TenantInfoHandler")
}
if o.AdminAPITenantSecurityHandler == nil {
unregistered = append(unregistered, "admin_api.TenantSecurityHandler")
}
if o.AdminAPITenantUpdateCertificateHandler == nil {
unregistered = append(unregistered, "admin_api.TenantUpdateCertificateHandler")
}
@@ -1154,6 +1167,9 @@ func (o *ConsoleAPI) Validate() error {
if o.AdminAPIUpdateTenantHandler == nil {
unregistered = append(unregistered, "admin_api.UpdateTenantHandler")
}
if o.AdminAPIUpdateTenantSecurityHandler == nil {
unregistered = append(unregistered, "admin_api.UpdateTenantSecurityHandler")
}
if o.AdminAPIUpdateUserGroupsHandler == nil {
unregistered = append(unregistered, "admin_api.UpdateUserGroupsHandler")
}
@@ -1698,6 +1714,10 @@ func (o *ConsoleAPI) initHandlerCache() {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/namespaces/{namespace}/tenants/{tenant}/info"] = admin_api.NewTenantInfo(o.context, o.AdminAPITenantInfoHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/namespaces/{namespace}/tenants/{tenant}/security"] = admin_api.NewTenantSecurity(o.context, o.AdminAPITenantSecurityHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
@@ -1730,6 +1750,10 @@ func (o *ConsoleAPI) initHandlerCache() {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/namespaces/{namespace}/tenants/{tenant}"] = admin_api.NewUpdateTenant(o.context, o.AdminAPIUpdateTenantHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/namespaces/{namespace}/tenants/{tenant}/security"] = admin_api.NewUpdateTenantSecurity(o.context, o.AdminAPIUpdateTenantSecurityHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}

View File

@@ -32,6 +32,7 @@ type OperatorClientI interface {
TenantDelete(ctx context.Context, namespace string, instanceName string, options metav1.DeleteOptions) error
TenantGet(ctx context.Context, namespace string, instanceName string, options metav1.GetOptions) (*miniov2.Tenant, error)
TenantPatch(ctx context.Context, namespace string, instanceName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error)
TenantUpdate(ctx context.Context, tenant *miniov2.Tenant, opts metav1.UpdateOptions) (*miniov2.Tenant, error)
TenantList(ctx context.Context, namespace string, opts metav1.ListOptions) (*miniov2.TenantList, error)
}
@@ -58,6 +59,11 @@ func (c *operatorClient) TenantPatch(ctx context.Context, namespace string, inst
return c.client.MinioV2().Tenants(namespace).Patch(ctx, instanceName, pt, data, options)
}
// TenantUpdate implements the minio instance patch action from minio operator
func (c *operatorClient) TenantUpdate(ctx context.Context, tenant *miniov2.Tenant, options metav1.UpdateOptions) (*miniov2.Tenant, error) {
return c.client.MinioV2().Tenants(tenant.Namespace).Update(ctx, tenant, options)
}
// TenantList implements the minio instance list action from minio operator
func (c *operatorClient) TenantList(ctx context.Context, namespace string, opts metav1.ListOptions) (*miniov2.TenantList, error) {
return c.client.MinioV2().Tenants(namespace).List(ctx, opts)

View File

@@ -2227,6 +2227,57 @@ paths:
tags:
- AdminAPI
/namespaces/{namespace}/tenants/{tenant}/security:
get:
summary: Tenant Security
operationId: TenantSecurity
parameters:
- name: namespace
in: path
required: true
type: string
- name: tenant
in: path
required: true
type: string
responses:
200:
description: A successful response.
schema:
$ref: "#/definitions/tenantSecurityResponse"
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- AdminAPI
post:
summary: Update Tenant Security
operationId: UpdateTenantSecurity
parameters:
- name: namespace
in: path
required: true
type: string
- name: tenant
in: path
required: true
type: string
- name: body
in: body
required: true
schema:
$ref: "#/definitions/updateTenantSecurityRequest"
responses:
204:
description: A successful response.
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- AdminAPI
/namespaces/{namespace}/tenants/{tenant}:
get:
summary: Tenant Details
@@ -3848,6 +3899,73 @@ definitions:
format: int32
health_status:
type: string
tenantSecurityResponse:
type: object
properties:
autoCert:
type: boolean
customCertificates:
type: object
properties:
minio:
type: array
items:
$ref: "#/definitions/certificateInfo"
minioCAs:
type: array
items:
$ref: "#/definitions/certificateInfo"
console:
type: array
items:
$ref: "#/definitions/certificateInfo"
consoleCAs:
type: array
items:
$ref: "#/definitions/certificateInfo"
updateTenantSecurityRequest:
type: object
properties:
autoCert:
type: boolean
customCertificates:
type: object
properties:
secretsToBeDeleted:
type: array
items:
type: string
minio:
type: array
items:
$ref: "#/definitions/keyPairConfiguration"
minioCAs:
type: array
items:
type: string
console:
type: array
items:
$ref: "#/definitions/keyPairConfiguration"
consoleCAs:
type: array
items:
type: string
certificateInfo:
type: object
properties:
serialNumber:
type: string
name:
type: string
domains:
type: array
items:
type: string
expiry:
type: string
tenant:
type: object
@@ -3896,6 +4014,12 @@ definitions:
type: boolean
status:
$ref: "#/definitions/tenantStatus"
minioTLS:
type: boolean
consoleTLS:
type: boolean
consoleEnabled:
type: boolean
tenantUsage:
type: object