Multiple fixes for operator-ui (#948)

- fix: create tenant from operator-ui was broken due to migration from
  standalone console to embedded console
- fix: refresh, activate and attach license in subscription page was
  broken
- fix: tenant usage report in operator-ui
- fix: show tenant encryption enabled if MINIO_KMS_SECRET_KEY is present

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
Lenin Alevski
2021-08-18 13:23:03 -07:00
committed by GitHub
parent a09be99ae6
commit ec47df3cc1
46 changed files with 672 additions and 1948 deletions

4
go.mod
View File

@@ -22,8 +22,8 @@ require (
github.com/minio/madmin-go v1.0.17
github.com/minio/mc v0.0.0-20210626002108-cebf3318546f
github.com/minio/minio-go/v7 v7.0.13-0.20210715203016-9e713532886e
github.com/minio/operator v0.0.0-20210803012017-0f43eee7fd7a
github.com/minio/operator/logsearchapi v0.0.0-20210803012017-0f43eee7fd7a
github.com/minio/operator v0.0.0-20210812082324-26350f153661
github.com/minio/operator/logsearchapi v0.0.0-20210812082324-26350f153661
github.com/minio/pkg v1.0.8
github.com/minio/selfupdate v0.3.1
github.com/mitchellh/go-homedir v1.1.0

8
go.sum
View File

@@ -890,10 +890,10 @@ github.com/minio/minio-go/v7 v7.0.11-0.20210302210017-6ae69c73ce78/go.mod h1:mTh
github.com/minio/minio-go/v7 v7.0.11-0.20210607181445-e162fdb8e584/go.mod h1:WoyW+ySKAKjY98B9+7ZbI8z8S3jaxaisdcvj9TGlazA=
github.com/minio/minio-go/v7 v7.0.13-0.20210715203016-9e713532886e h1:aVnxKPpUI1gVeEf9vC+QEt8OxMXiiNMeUWcrBM62oDU=
github.com/minio/minio-go/v7 v7.0.13-0.20210715203016-9e713532886e/go.mod h1:S23iSP5/gbMwtxeY5FM71R+TkAYyzEdoNEDDwpt8yWs=
github.com/minio/operator v0.0.0-20210803012017-0f43eee7fd7a h1:jvEyFZBLo1mIc5YTg+AIAieDkzoAnc9+j4yT5kZO15E=
github.com/minio/operator v0.0.0-20210803012017-0f43eee7fd7a/go.mod h1:zQqn6VGT46xlSpVXh1I/VZRv+eSgHtVu6URdg71YKX8=
github.com/minio/operator/logsearchapi v0.0.0-20210803012017-0f43eee7fd7a h1:tnyzzgWP0PXM1nrwHtlyawuBguY+6R9/yee0bezbGDY=
github.com/minio/operator/logsearchapi v0.0.0-20210803012017-0f43eee7fd7a/go.mod h1:R+38Pf3wfm+JMiyLPb/r8OMrBm0vK2hZgUT4y4aYoSY=
github.com/minio/operator v0.0.0-20210812082324-26350f153661 h1:dGAJHpfmhNukFg0M0wDqH+G1OB2YPgZCcT6uv4n9YQk=
github.com/minio/operator v0.0.0-20210812082324-26350f153661/go.mod h1:zQqn6VGT46xlSpVXh1I/VZRv+eSgHtVu6URdg71YKX8=
github.com/minio/operator/logsearchapi v0.0.0-20210812082324-26350f153661 h1:tJw15hS3b1dVTf5PwA4roXZ/oRNnHyZ/8Y+yNTmQ5rA=
github.com/minio/operator/logsearchapi v0.0.0-20210812082324-26350f153661/go.mod h1:R+38Pf3wfm+JMiyLPb/r8OMrBm0vK2hZgUT4y4aYoSY=
github.com/minio/pkg v1.0.3/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
github.com/minio/pkg v1.0.4/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
github.com/minio/pkg v1.0.8 h1:lWQwHSeYlvnRoPpO+wS0I4mL6c00ABxBgbGjSmjwOi4=

View File

@@ -1,134 +0,0 @@
// 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/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// ConsoleConfiguration console configuration
//
// swagger:model consoleConfiguration
type ConsoleConfiguration struct {
MetadataFields
// image
Image string `json:"image,omitempty"`
}
// UnmarshalJSON unmarshals this object from a JSON structure
func (m *ConsoleConfiguration) UnmarshalJSON(raw []byte) error {
// AO0
var aO0 MetadataFields
if err := swag.ReadJSON(raw, &aO0); err != nil {
return err
}
m.MetadataFields = aO0
// AO1
var dataAO1 struct {
Image string `json:"image,omitempty"`
}
if err := swag.ReadJSON(raw, &dataAO1); err != nil {
return err
}
m.Image = dataAO1.Image
return nil
}
// MarshalJSON marshals this object to a JSON structure
func (m ConsoleConfiguration) MarshalJSON() ([]byte, error) {
_parts := make([][]byte, 0, 2)
aO0, err := swag.WriteJSON(m.MetadataFields)
if err != nil {
return nil, err
}
_parts = append(_parts, aO0)
var dataAO1 struct {
Image string `json:"image,omitempty"`
}
dataAO1.Image = m.Image
jsonDataAO1, errAO1 := swag.WriteJSON(dataAO1)
if errAO1 != nil {
return nil, errAO1
}
_parts = append(_parts, jsonDataAO1)
return swag.ConcatJSON(_parts...), nil
}
// Validate validates this console configuration
func (m *ConsoleConfiguration) Validate(formats strfmt.Registry) error {
var res []error
// validation for a type composition with MetadataFields
if err := m.MetadataFields.Validate(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validate this console configuration based on the context it is used
func (m *ConsoleConfiguration) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
// validation for a type composition with MetadataFields
if err := m.MetadataFields.ContextValidate(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// MarshalBinary interface implementation
func (m *ConsoleConfiguration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ConsoleConfiguration) UnmarshalBinary(b []byte) error {
var res ConsoleConfiguration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -43,12 +43,6 @@ type CreateTenantRequest struct {
// annotations
Annotations map[string]string `json:"annotations,omitempty"`
// console
Console *ConsoleConfiguration `json:"console,omitempty"`
// console image
ConsoleImage string `json:"console_image,omitempty"`
// enable console
EnableConsole *bool `json:"enable_console,omitempty"`
@@ -118,10 +112,6 @@ type CreateTenantRequest struct {
func (m *CreateTenantRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConsole(formats); err != nil {
res = append(res, err)
}
if err := m.validateEncryption(formats); err != nil {
res = append(res, err)
}
@@ -164,23 +154,6 @@ func (m *CreateTenantRequest) Validate(formats strfmt.Registry) error {
return nil
}
func (m *CreateTenantRequest) validateConsole(formats strfmt.Registry) error {
if swag.IsZero(m.Console) { // not required
return nil
}
if m.Console != nil {
if err := m.Console.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("console")
}
return err
}
}
return nil
}
func (m *CreateTenantRequest) validateEncryption(formats strfmt.Registry) error {
if swag.IsZero(m.Encryption) { // not required
return nil
@@ -334,10 +307,6 @@ func (m *CreateTenantRequest) validateTLS(formats strfmt.Registry) error {
func (m *CreateTenantRequest) 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.contextValidateEncryption(ctx, formats); err != nil {
res = append(res, err)
}
@@ -372,20 +341,6 @@ func (m *CreateTenantRequest) ContextValidate(ctx context.Context, formats strfm
return nil
}
func (m *CreateTenantRequest) contextValidateConsole(ctx context.Context, formats strfmt.Registry) error {
if m.Console != nil {
if err := m.Console.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("console")
}
return err
}
}
return nil
}
func (m *CreateTenantRequest) contextValidateEncryption(ctx context.Context, formats strfmt.Registry) error {
if m.Encryption != nil {

View File

@@ -38,6 +38,9 @@ type CreateTenantResponse struct {
// console
Console []*TenantResponseItem `json:"console"`
// external ID p
ExternalIDP bool `json:"externalIDP,omitempty"`
}
// Validate validates this create tenant response

View File

@@ -36,15 +36,6 @@ 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"`
// creation date
CreationDate string `json:"creation_date,omitempty"`

View File

@@ -125,12 +125,6 @@ func (m *TenantSecurityResponse) UnmarshalBinary(b []byte) error {
// swagger:model TenantSecurityResponseCustomCertificates
type TenantSecurityResponseCustomCertificates struct {
// console
Console []*CertificateInfo `json:"console"`
// console c as
ConsoleCAs []*CertificateInfo `json:"consoleCAs"`
// minio
Minio []*CertificateInfo `json:"minio"`
@@ -142,14 +136,6 @@ type TenantSecurityResponseCustomCertificates struct {
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)
}
@@ -164,54 +150,6 @@ func (m *TenantSecurityResponseCustomCertificates) Validate(formats strfmt.Regis
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
@@ -264,14 +202,6 @@ func (m *TenantSecurityResponseCustomCertificates) validateMinioCAs(formats strf
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)
}
@@ -286,42 +216,6 @@ func (m *TenantSecurityResponseCustomCertificates) ContextValidate(ctx context.C
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++ {

View File

@@ -39,12 +39,6 @@ type TLSConfiguration struct {
// ca certificates
CaCertificates []string `json:"ca_certificates"`
// console
Console *KeyPairConfiguration `json:"console,omitempty"`
// console ca certificates
ConsoleCaCertificates []string `json:"console_ca_certificates"`
// minio
Minio []*KeyPairConfiguration `json:"minio"`
}
@@ -53,10 +47,6 @@ type TLSConfiguration struct {
func (m *TLSConfiguration) 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)
}
@@ -67,23 +57,6 @@ func (m *TLSConfiguration) Validate(formats strfmt.Registry) error {
return nil
}
func (m *TLSConfiguration) validateConsole(formats strfmt.Registry) error {
if swag.IsZero(m.Console) { // not required
return nil
}
if m.Console != nil {
if err := m.Console.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("console")
}
return err
}
}
return nil
}
func (m *TLSConfiguration) validateMinio(formats strfmt.Registry) error {
if swag.IsZero(m.Minio) { // not required
return nil
@@ -112,10 +85,6 @@ func (m *TLSConfiguration) validateMinio(formats strfmt.Registry) error {
func (m *TLSConfiguration) 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)
}
@@ -126,20 +95,6 @@ func (m *TLSConfiguration) ContextValidate(ctx context.Context, formats strfmt.R
return nil
}
func (m *TLSConfiguration) contextValidateConsole(ctx context.Context, formats strfmt.Registry) error {
if m.Console != nil {
if err := m.Console.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("console")
}
return err
}
}
return nil
}
func (m *TLSConfiguration) contextValidateMinio(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Minio); i++ {

View File

@@ -36,10 +36,6 @@ import (
// swagger:model updateTenantRequest
type UpdateTenantRequest struct {
// console image
// Pattern: ^((.*?)/(.*?):(.+))$
ConsoleImage string `json:"console_image,omitempty"`
// enable prometheus
EnablePrometheus bool `json:"enable_prometheus,omitempty"`
@@ -58,10 +54,6 @@ type UpdateTenantRequest struct {
func (m *UpdateTenantRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConsoleImage(formats); err != nil {
res = append(res, err)
}
if err := m.validateImage(formats); err != nil {
res = append(res, err)
}
@@ -76,18 +68,6 @@ func (m *UpdateTenantRequest) Validate(formats strfmt.Registry) error {
return nil
}
func (m *UpdateTenantRequest) validateConsoleImage(formats strfmt.Registry) error {
if swag.IsZero(m.ConsoleImage) { // not required
return nil
}
if err := validate.Pattern("console_image", "body", m.ConsoleImage, `^((.*?)/(.*?):(.+))$`); err != nil {
return err
}
return nil
}
func (m *UpdateTenantRequest) validateImage(formats strfmt.Registry) error {
if swag.IsZero(m.Image) { // not required
return nil

View File

@@ -125,12 +125,6 @@ func (m *UpdateTenantSecurityRequest) UnmarshalBinary(b []byte) error {
// swagger:model UpdateTenantSecurityRequestCustomCertificates
type UpdateTenantSecurityRequestCustomCertificates struct {
// console
Console []*KeyPairConfiguration `json:"console"`
// console c as
ConsoleCAs []string `json:"consoleCAs"`
// minio
Minio []*KeyPairConfiguration `json:"minio"`
@@ -145,10 +139,6 @@ type UpdateTenantSecurityRequestCustomCertificates struct {
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)
}
@@ -159,30 +149,6 @@ func (m *UpdateTenantSecurityRequestCustomCertificates) Validate(formats strfmt.
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
@@ -211,10 +177,6 @@ func (m *UpdateTenantSecurityRequestCustomCertificates) validateMinio(formats st
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)
}
@@ -225,24 +187,6 @@ func (m *UpdateTenantSecurityRequestCustomCertificates) ContextValidate(ctx cont
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++ {

View File

@@ -56,12 +56,6 @@ var (
GlobalTLSCertsManager *xcerts.Manager
)
var consoleImage string
func init() {
consoleImage = env.Get(ConsoleOperatorConsoleImage, ConsoleImageDefaultVersion)
}
// getK8sSAToken assumes the plugin is running inside a k8s pod and extract the current service account from the
// /var/run/secrets/kubernetes.io/serviceaccount/token file
func getK8sSAToken() string {
@@ -71,6 +65,3 @@ func getK8sSAToken() string {
}
return string(dat)
}
func getConsoleImage() string {
return consoleImage
}

View File

@@ -93,7 +93,7 @@ func configureAPI(api *operations.OperatorAPI) http.Handler {
// Namespaces handlers
registerNamespaceHandlers(api)
// Subscription handlers
registerOperatorSubscriptionHandlers(api)
registerSubscriptionHandlers(api)
api.PreServerShutdown = func() {}

View File

@@ -18,37 +18,10 @@ package operatorapi
// list of all console environment constants
const (
// Constants for common configuration
ConsoleMinIOServer = "CONSOLE_MINIO_SERVER"
ConsoleMinIORegion = "CONSOLE_MINIO_REGION"
ConsoleHostname = "CONSOLE_HOSTNAME"
ConsolePort = "CONSOLE_PORT"
ConsoleTLSHostname = "CONSOLE_TLS_HOSTNAME"
ConsoleTLSPort = "CONSOLE_TLS_PORT"
ConsoleSubnetLicense = "CONSOLE_SUBNET_LICENSE"
// Constants for Secure middleware
ConsoleSecureAllowedHosts = "CONSOLE_SECURE_ALLOWED_HOSTS"
ConsoleSecureAllowedHostsAreRegex = "CONSOLE_SECURE_ALLOWED_HOSTS_ARE_REGEX"
ConsoleSecureFrameDeny = "CONSOLE_SECURE_FRAME_DENY"
ConsoleSecureContentTypeNoSniff = "CONSOLE_SECURE_CONTENT_TYPE_NO_SNIFF"
ConsoleSecureBrowserXSSFilter = "CONSOLE_SECURE_BROWSER_XSS_FILTER"
ConsoleSecureContentSecurityPolicy = "CONSOLE_SECURE_CONTENT_SECURITY_POLICY"
ConsoleSecureContentSecurityPolicyReportOnly = "CONSOLE_SECURE_CONTENT_SECURITY_POLICY_REPORT_ONLY"
ConsoleSecureHostsProxyHeaders = "CONSOLE_SECURE_HOSTS_PROXY_HEADERS"
ConsoleSecureSTSSeconds = "CONSOLE_SECURE_STS_SECONDS"
ConsoleSecureSTSIncludeSubdomains = "CONSOLE_SECURE_STS_INCLUDE_SUB_DOMAINS"
ConsoleSecureSTSPreload = "CONSOLE_SECURE_STS_PRELOAD"
ConsoleSecureTLSRedirect = "CONSOLE_SECURE_TLS_REDIRECT"
ConsoleSecureTLSHost = "CONSOLE_SECURE_TLS_HOST"
ConsoleSecureTLSTemporaryRedirect = "CONSOLE_SECURE_TLS_TEMPORARY_REDIRECT"
ConsoleSecureForceSTSHeader = "CONSOLE_SECURE_FORCE_STS_HEADER"
ConsoleSecurePublicKey = "CONSOLE_SECURE_PUBLIC_KEY"
ConsoleSecureReferrerPolicy = "CONSOLE_SECURE_REFERRER_POLICY"
ConsoleSecureFeaturePolicy = "CONSOLE_SECURE_FEATURE_POLICY"
ConsoleSecureExpectCTHeader = "CONSOLE_SECURE_EXPECT_CT_HEADER"
ConsoleOperatorSAToken = "CONSOLE_OPERATOR_SA_TOKEN"
ConsoleOperatorConsoleImage = "CONSOLE_OPERATOR_CONSOLE_IMAGE"
ConsoleSubnetLicense = "CONSOLE_SUBNET_LICENSE"
ConsoleOperatorSAToken = "CONSOLE_OPERATOR_SA_TOKEN"
ConsoleOperatorConsoleImage = "CONSOLE_OPERATOR_CONSOLE_IMAGE"
MinIOSubnetLicense = "MINIO_SUBNET_LICENSE"
// Constants for prometheus annotations
prometheusPath = "prometheus.io/path"

View File

@@ -1448,21 +1448,6 @@ func init() {
}
}
},
"consoleConfiguration": {
"allOf": [
{
"$ref": "#/definitions/metadataFields"
},
{
"type": "object",
"properties": {
"image": {
"type": "string"
}
}
}
]
},
"createTenantRequest": {
"type": "object",
"required": [
@@ -1480,13 +1465,6 @@ func init() {
"type": "string"
}
},
"console": {
"type": "object",
"$ref": "#/definitions/consoleConfiguration"
},
"console_image": {
"type": "string"
},
"enable_console": {
"type": "boolean",
"default": true
@@ -1570,6 +1548,9 @@ func init() {
"items": {
"$ref": "#/definitions/tenantResponseItem"
}
},
"externalIDP": {
"type": "boolean"
}
}
},
@@ -2716,15 +2697,6 @@ func init() {
"tenant": {
"type": "object",
"properties": {
"consoleEnabled": {
"type": "boolean"
},
"consoleTLS": {
"type": "boolean"
},
"console_image": {
"type": "string"
},
"creation_date": {
"type": "string"
},
@@ -2874,18 +2846,6 @@ func init() {
"customCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"consoleCAs": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"minio": {
"type": "array",
"items": {
@@ -2956,16 +2916,6 @@ func init() {
"type": "string"
}
},
"console": {
"type": "object",
"$ref": "#/definitions/keyPairConfiguration"
},
"console_ca_certificates": {
"type": "array",
"items": {
"type": "string"
}
},
"minio": {
"type": "array",
"items": {
@@ -2977,10 +2927,6 @@ func init() {
"updateTenantRequest": {
"type": "object",
"properties": {
"console_image": {
"type": "string",
"pattern": "^((.*?)/(.*?):(.+))$"
},
"enable_prometheus": {
"type": "boolean"
},
@@ -3005,18 +2951,6 @@ func init() {
"customCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/keyPairConfiguration"
}
},
"consoleCAs": {
"type": "array",
"items": {
"type": "string"
}
},
"minio": {
"type": "array",
"items": {
@@ -5046,18 +4980,6 @@ func init() {
"TenantSecurityResponseCustomCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"consoleCAs": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"minio": {
"type": "array",
"items": {
@@ -5075,18 +4997,6 @@ func init() {
"UpdateTenantSecurityRequestCustomCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/keyPairConfiguration"
}
},
"consoleCAs": {
"type": "array",
"items": {
"type": "string"
}
},
"minio": {
"type": "array",
"items": {
@@ -5217,21 +5127,6 @@ func init() {
}
}
},
"consoleConfiguration": {
"allOf": [
{
"$ref": "#/definitions/metadataFields"
},
{
"type": "object",
"properties": {
"image": {
"type": "string"
}
}
}
]
},
"createTenantRequest": {
"type": "object",
"required": [
@@ -5249,13 +5144,6 @@ func init() {
"type": "string"
}
},
"console": {
"type": "object",
"$ref": "#/definitions/consoleConfiguration"
},
"console_image": {
"type": "string"
},
"enable_console": {
"type": "boolean",
"default": true
@@ -5339,6 +5227,9 @@ func init() {
"items": {
"$ref": "#/definitions/tenantResponseItem"
}
},
"externalIDP": {
"type": "boolean"
}
}
},
@@ -6338,15 +6229,6 @@ func init() {
"tenant": {
"type": "object",
"properties": {
"consoleEnabled": {
"type": "boolean"
},
"consoleTLS": {
"type": "boolean"
},
"console_image": {
"type": "string"
},
"creation_date": {
"type": "string"
},
@@ -6496,18 +6378,6 @@ func init() {
"customCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"consoleCAs": {
"type": "array",
"items": {
"$ref": "#/definitions/certificateInfo"
}
},
"minio": {
"type": "array",
"items": {
@@ -6578,16 +6448,6 @@ func init() {
"type": "string"
}
},
"console": {
"type": "object",
"$ref": "#/definitions/keyPairConfiguration"
},
"console_ca_certificates": {
"type": "array",
"items": {
"type": "string"
}
},
"minio": {
"type": "array",
"items": {
@@ -6599,10 +6459,6 @@ func init() {
"updateTenantRequest": {
"type": "object",
"properties": {
"console_image": {
"type": "string",
"pattern": "^((.*?)/(.*?):(.+))$"
},
"enable_prometheus": {
"type": "boolean"
},
@@ -6627,18 +6483,6 @@ func init() {
"customCertificates": {
"type": "object",
"properties": {
"console": {
"type": "array",
"items": {
"$ref": "#/definitions/keyPairConfiguration"
}
},
"consoleCAs": {
"type": "array",
"items": {
"type": "string"
}
},
"minio": {
"type": "array",
"items": {

View File

@@ -37,6 +37,7 @@ type K8sClientI interface {
deletePodCollection(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
deleteSecret(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error
createSecret(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error)
updateSecret(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.UpdateOptions) (*v1.Secret, error)
}
// Interface implementation
@@ -70,6 +71,10 @@ func (c *k8sClient) createSecret(ctx context.Context, namespace string, secret *
return c.client.CoreV1().Secrets(namespace).Create(ctx, secret, opts)
}
func (c *k8sClient) updateSecret(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.UpdateOptions) (*v1.Secret, error) {
return c.client.CoreV1().Secrets(namespace).Update(ctx, secret, opts)
}
func (c *k8sClient) getNamespace(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Namespace, error) {
return c.client.CoreV1().Namespaces().Get(ctx, name, opts)
}

View File

@@ -20,13 +20,12 @@ package operatorapi
import (
"context"
"errors"
"fmt"
"time"
"github.com/minio/console/pkg/subnet"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/minio/console/restapi"
@@ -38,10 +37,10 @@ import (
"github.com/minio/console/operatorapi/operations/operator_api"
)
func registerOperatorSubscriptionHandlers(api *operations.OperatorAPI) {
func registerSubscriptionHandlers(api *operations.OperatorAPI) {
// Activate license subscription for a particular tenant
api.OperatorAPISubscriptionActivateHandler = operator_api.SubscriptionActivateHandlerFunc(func(params operator_api.SubscriptionActivateParams, session *models.Principal) middleware.Responder {
err := getOperatorSubscriptionActivateResponse(session, params.Namespace, params.Tenant)
err := getSubscriptionActivateResponse(session, params.Namespace, params.Tenant)
if err != nil {
return operator_api.NewSubscriptionActivateDefault(int(err.Code)).WithPayload(err)
}
@@ -55,10 +54,33 @@ func registerOperatorSubscriptionHandlers(api *operations.OperatorAPI) {
}
return operator_api.NewSubscriptionRefreshOK().WithPayload(license)
})
// Validate subscription handler
api.OperatorAPISubscriptionValidateHandler = operator_api.SubscriptionValidateHandlerFunc(func(params operator_api.SubscriptionValidateParams, session *models.Principal) middleware.Responder {
license, err := getSubscriptionValidateResponse(session, params.Body)
if err != nil {
return operator_api.NewSubscriptionValidateDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewSubscriptionValidateOK().WithPayload(license)
})
// Get subscription information handler
api.OperatorAPISubscriptionInfoHandler = operator_api.SubscriptionInfoHandlerFunc(func(params operator_api.SubscriptionInfoParams, session *models.Principal) middleware.Responder {
license, err := getSubscriptionInfoResponse(session)
if err != nil {
return operator_api.NewSubscriptionInfoDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewSubscriptionInfoOK().WithPayload(license)
})
// Refresh license for k8s cluster
api.OperatorAPISubscriptionRefreshHandler = operator_api.SubscriptionRefreshHandlerFunc(func(params operator_api.SubscriptionRefreshParams, session *models.Principal) middleware.Responder {
license, err := getSubscriptionRefreshResponse(session)
if err != nil {
return operator_api.NewSubscriptionRefreshDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewSubscriptionRefreshOK().WithPayload(license)
})
}
// retrieveLicense returns license from K8S secrets (If console is deployed in operator mode) or from
// the configured CONSOLE_SUBNET_LICENSE environment variable
// retrieveLicense returns license from K8S secrets
func retrieveLicense(ctx context.Context, sessionToken string) (string, error) {
var license string
@@ -79,46 +101,6 @@ func retrieveLicense(ctx context.Context, sessionToken string) (string, error) {
return license, nil
}
func getOperatorSubscriptionActivateResponse(session *models.Principal, namespace, tenant string) *models.Error {
// 20 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(restapi.ErrorGeneric, nil, err)
}
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(restapi.ErrorGeneric, nil, err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
minTenant, err := getTenant(ctx, opClient, namespace, tenant)
if err != nil {
return prepareError(err, restapi.ErrorGeneric)
}
// If console is not deployed for this tenant return an error
if minTenant.Spec.Console == nil {
return prepareError(restapi.ErrorGenericNotFound)
}
// configure kubernetes client
k8sClient := k8sClient{
client: clientSet,
}
// Get cluster subscription license
license, err := getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName)
if err != nil {
return prepareError(errInvalidCredentials, nil, err)
}
// add subscription license to existing console Tenant
if err = addSubscriptionLicenseToTenant(ctx, &k8sClient, license, namespace, tenant, minTenant.Spec.Console.ConsoleSecret.Name); err != nil {
return prepareError(err, restapi.ErrorGeneric)
}
return nil
}
// getSubscriptionLicense will retrieve stored license jwt from k8s secret
func getSubscriptionLicense(ctx context.Context, clientSet K8sClientI, namespace, secretName string) (string, error) {
// retrieve license stored in k8s
@@ -135,86 +117,66 @@ func getSubscriptionLicense(ctx context.Context, clientSet K8sClientI, namespace
}
// addSubscriptionLicenseToTenant replace existing console tenant secret and adds the subnet license key
func addSubscriptionLicenseToTenant(ctx context.Context, clientSet K8sClientI, license, namespace, tenantName, secretName string) error {
// Retrieve console secret for Tenant
consoleSecret, err := clientSet.getSecret(ctx, namespace, secretName, metav1.GetOptions{})
if err != nil {
return err
}
// Copy current console secret
dataNewSecret := consoleSecret.Data
// Add subnet license to the new console secret
dataNewSecret[ConsoleSubnetLicense] = []byte(license)
// Delete existing console secret
err = clientSet.deleteSecret(ctx, namespace, secretName, metav1.DeleteOptions{})
if err != nil {
return err
}
// Prepare the new Console Secret
imm := true
newConsoleSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Labels: map[string]string{
miniov2.TenantLabel: tenantName,
},
},
Immutable: &imm,
Data: dataNewSecret,
}
// Create new Console secret with the subnet License
_, err = clientSet.createSecret(ctx, namespace, newConsoleSecret, metav1.CreateOptions{})
if err != nil {
return err
}
// restart Console pods based on label:
// v1.min.io/console: TENANT-console
err = clientSet.deletePodCollection(ctx, namespace, metav1.DeleteOptions{}, metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s%s", miniov2.ConsoleTenantLabel, tenantName, miniov2.ConsoleName),
})
if err != nil {
return err
}
return nil
}
// updateTenantLicenseAndRestartConsole
func updateTenantLicenseAndRestartConsole(ctx context.Context, clientSet K8sClientI, license, namespace, tenantName string) error {
consoleSelector := fmt.Sprintf("%s-console", tenantName)
consoleSecretName := fmt.Sprintf("%s-secret", consoleSelector)
// read current console configuration from k8s secrets
currentConsoleSecret, err := clientSet.getSecret(ctx, namespace, consoleSecretName, metav1.GetOptions{})
if err != nil || currentConsoleSecret == nil {
return err
}
secretData := currentConsoleSecret.Data
secretData[ConsoleSubnetLicense] = []byte(license)
// delete existing console configuration from k8s secrets
err = clientSet.deleteSecret(ctx, namespace, consoleSecretName, metav1.DeleteOptions{})
if err != nil {
// log the error if any and continue
LogError("unable to delete secret %s: %v", consoleSecretName, err)
}
// Save subnet license in k8s secrets
imm := true
consoleConfigSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: consoleSecretName,
},
Immutable: &imm,
Data: secretData,
}
_, err = clientSet.createSecret(ctx, namespace, consoleConfigSecret, metav1.CreateOptions{})
if err != nil {
return err
}
// restart Console pods based on label:
// v1.min.io/console: TENANT-console
err = clientSet.deletePodCollection(ctx, namespace, metav1.DeleteOptions{}, metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s%s", miniov2.ConsoleTenantLabel, tenantName, miniov2.ConsoleName),
})
if err != nil {
return err
func addSubscriptionLicenseToTenant(ctx context.Context, clientSet K8sClientI, opClient OperatorClientI, license string, tenant *miniov2.Tenant) error {
// If Tenant has a configuration secret update the license there and MinIO pods doesn't need to get restarted
if tenant.HasConfigurationSecret() {
// Update the Tenant Configuration
tenantConfigurationSecret, err := clientSet.getSecret(ctx, tenant.Namespace, tenant.Spec.Configuration.Name, metav1.GetOptions{})
if err != nil {
return err
}
if _, ok := tenantConfigurationSecret.Data["config.env"]; ok {
updatedTenantConfiguration := map[string]string{}
tenantConfigurationMap := miniov2.ParseRawConfiguration(tenantConfigurationSecret.Data["config.env"])
for key, val := range tenantConfigurationMap {
updatedTenantConfiguration[key] = string(val)
}
updatedTenantConfiguration[MinIOSubnetLicense] = license
// removing accesskey & secretkey that are added automatically by parsing function
// and are not need it for the tenant itself
delete(updatedTenantConfiguration, "accesskey")
delete(updatedTenantConfiguration, "secretkey")
tenantConfigurationSecret.Data = map[string][]byte{
"config.env": []byte(GenerateTenantConfigurationFile(updatedTenantConfiguration)),
}
_, err = clientSet.updateSecret(ctx, tenant.Namespace, tenantConfigurationSecret, metav1.UpdateOptions{})
if err != nil {
return err
}
} else {
return errors.New("tenant configuration secret has wrong format")
}
} else {
// If configuration file is not present set the license to the container env
updatedTenant := tenant.DeepCopy()
// reset container env vars
updatedTenant.Spec.Env = []corev1.EnvVar{}
var licenseIsSet bool
for _, env := range tenant.GetEnvVars() {
// check if license already exists and override
if env.Name == MinIOSubnetLicense {
updatedTenant.Spec.Env = append(updatedTenant.Spec.Env, corev1.EnvVar{
Name: MinIOSubnetLicense,
Value: license,
})
licenseIsSet = true
} else {
// copy existing container env variables
updatedTenant.Spec.Env = append(updatedTenant.Spec.Env, env)
}
}
// if license didnt exists append it
if !licenseIsSet {
updatedTenant.Spec.Env = append(updatedTenant.Spec.Env, corev1.EnvVar{
Name: MinIOSubnetLicense,
Value: license,
})
}
// this will start MinIO pods rolling restart
_, err := opClient.TenantUpdate(ctx, updatedTenant, metav1.UpdateOptions{})
if err != nil {
return err
}
}
return nil
}
@@ -226,7 +188,7 @@ func getSubscriptionRefreshResponse(session *models.Principal) (*models.License,
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
licenseKey, err := retrieveLicense(context.Background(), session.STSSessionToken)
licenseKey, err := retrieveLicense(ctx, session.STSSessionToken)
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
@@ -251,17 +213,17 @@ func getSubscriptionRefreshResponse(session *models.Principal) (*models.License,
if err != nil {
return nil, prepareError(err)
}
opClient := &operatorClient{
opClient := operatorClient{
client: opClientClientSet,
}
tenants, err := listTenants(ctx, opClient, "", nil)
// iterate over all tenants and update licenses
tenants, err := opClient.TenantList(ctx, "", metav1.ListOptions{})
if err != nil {
return nil, prepareError(err)
}
// iterate over all tenants, update console configuration and restart console pods
for _, tenant := range tenants.Tenants {
if err := updateTenantLicenseAndRestartConsole(ctx, &k8sClient, licenseRaw, tenant.Namespace, tenant.Name); err != nil {
LogError("unable to updateTenantLicenseAndRestartConsole: %v", err)
for _, tenant := range tenants.Items {
if err = addSubscriptionLicenseToTenant(ctx, &k8sClient, &opClient, licenseRaw, &tenant); err != nil {
return nil, prepareError(err)
}
}
@@ -321,24 +283,31 @@ func subscriptionRefresh(httpClient *cluster.HTTPClient, license string) (*model
// saveSubscriptionLicense will create or replace an existing subnet license secret in the k8s cluster
func saveSubscriptionLicense(ctx context.Context, clientSet K8sClientI, license string) error {
// Delete subnet license secret if exists
err := clientSet.deleteSecret(ctx, cluster.Namespace, OperatorSubnetLicenseSecretName, metav1.DeleteOptions{})
licenseSecret, err := clientSet.getSecret(ctx, cluster.Namespace, OperatorSubnetLicenseSecretName, metav1.GetOptions{})
if err != nil {
// log the error if any and continue
LogError("unable to delete secret %s: %v", OperatorSubnetLicenseSecretName, err)
if k8serrors.IsNotFound(err) {
// Save subnet license in k8s secrets
licenseSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: OperatorSubnetLicenseSecretName,
},
Data: map[string][]byte{
ConsoleSubnetLicense: []byte(license),
},
}
_, err = clientSet.createSecret(ctx, cluster.Namespace, licenseSecret, metav1.CreateOptions{})
if err != nil {
return err
}
return nil
}
return err
}
// Save subnet license in k8s secrets
imm := true
licenseSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: OperatorSubnetLicenseSecretName,
},
Immutable: &imm,
Data: map[string][]byte{
ConsoleSubnetLicense: []byte(license),
},
// update existing license
licenseSecret.Data = map[string][]byte{
ConsoleSubnetLicense: []byte(license),
}
_, err = clientSet.createSecret(ctx, cluster.Namespace, licenseSecret, metav1.CreateOptions{})
_, err = clientSet.updateSecret(ctx, cluster.Namespace, licenseSecret, metav1.UpdateOptions{})
if err != nil {
return err
}
@@ -360,3 +329,88 @@ func subscriptionValidate(client cluster.HTTPClientI, license, email, password s
Organization: licenseInfo.Organization,
}, rawLicense, nil
}
// getSubscriptionValidateResponse
func getSubscriptionValidateResponse(session *models.Principal, params *models.SubscriptionValidateRequest) (*models.License, *models.Error) {
// 20 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
// validate license key
licenseInfo, license, err := subscriptionValidate(client, params.License, params.Email, params.Password)
if err != nil {
return nil, prepareError(errInvalidLicense, nil, err)
}
// configure kubernetes client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
k8sClient := k8sClient{
client: clientSet,
}
if err != nil {
return nil, prepareError(errorGeneric, nil, err)
}
// save license key to k8s
if err = saveSubscriptionLicense(ctx, &k8sClient, license); err != nil {
return nil, prepareError(errorGeneric, nil, err)
}
return licenseInfo, nil
}
func getSubscriptionActivateResponse(session *models.Principal, namespace, tenantName string) *models.Error {
// 20 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(errorGeneric, nil, err)
}
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(errorGeneric, nil, err)
}
opClient := operatorClient{
client: opClientClientSet,
}
tenant, err := getTenant(ctx, &opClient, namespace, tenantName)
if err != nil {
return prepareError(err, errorGeneric)
}
// configure kubernetes client
k8sClient := k8sClient{
client: clientSet,
}
// Get cluster subscription license
license, err := getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName)
if err != nil {
return prepareError(errInvalidCredentials, nil, err)
}
// add subscription license to existing console Tenant
if err = addSubscriptionLicenseToTenant(ctx, &k8sClient, &opClient, license, tenant); err != nil {
return prepareError(err, errorGeneric)
}
return nil
}
// getSubscriptionInfoResponse returns information about the current configured subnet license for Console
func getSubscriptionInfoResponse(session *models.Principal) (*models.License, *models.Error) {
// 20 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var licenseInfo *models.License
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
licenseKey, err := retrieveLicense(ctx, session.STSSessionToken)
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
// validate license key and obtain license info
licenseInfo, _, err = subscriptionValidate(client, licenseKey, "", "")
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
return licenseInfo, nil
}

View File

@@ -20,6 +20,8 @@ import (
"context"
"testing"
v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
"errors"
corev1 "k8s.io/api/core/v1"
@@ -28,13 +30,17 @@ import (
func Test_addSubscriptionLicenseToTenant(t *testing.T) {
k8sClient := k8sClientMock{}
opClient := opClientMock{}
tenant := &v2.Tenant{
ObjectMeta: metav1.ObjectMeta{},
Spec: v2.TenantSpec{},
}
type args struct {
ctx context.Context
clientSet K8sClientI
license string
namespace string
tenantName string
secretName string
ctx context.Context
clientSet K8sClientI
opClient OperatorClientI
license string
tenant *v2.Tenant
}
tests := []struct {
name string
@@ -43,150 +49,158 @@ func Test_addSubscriptionLicenseToTenant(t *testing.T) {
mockFunc func()
}{
{
name: "error because subnet license doesnt exists",
name: "success updating subscription for tenant with configuration file",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
license: "",
namespace: "",
tenantName: "",
secretName: "subnet-license",
},
wantErr: true,
mockFunc: func() {
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
return nil, errors.New("something went wrong")
}
},
},
{
name: "error because existing license could not be deleted",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
license: "",
namespace: "",
tenantName: "",
secretName: OperatorSubnetLicenseSecretName,
},
wantErr: true,
mockFunc: func() {
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
imm := true
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: OperatorSubnetLicenseSecretName,
},
Immutable: &imm,
Data: map[string][]byte{
ConsoleSubnetLicense: []byte("eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I"),
},
}, nil
}
DeleteSecretMock = func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return errors.New("something went wrong")
}
},
},
{
name: "error because unable to create new subnet license",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
license: "",
namespace: "",
tenantName: "",
secretName: OperatorSubnetLicenseSecretName,
},
wantErr: true,
mockFunc: func() {
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
imm := true
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: OperatorSubnetLicenseSecretName,
},
Immutable: &imm,
Data: map[string][]byte{
ConsoleSubnetLicense: []byte("eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I"),
},
}, nil
}
DeleteSecretMock = func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
}
CreateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.CreateOptions) (*corev1.Secret, error) {
return nil, errors.New("something went wrong")
}
},
},
{
name: "error because unable to delete pod collection",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
license: "",
namespace: "",
tenantName: "",
secretName: OperatorSubnetLicenseSecretName,
},
wantErr: true,
mockFunc: func() {
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
imm := true
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: OperatorSubnetLicenseSecretName,
},
Immutable: &imm,
Data: map[string][]byte{
ConsoleSubnetLicense: []byte("eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I"),
},
}, nil
}
DeleteSecretMock = func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
}
CreateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.CreateOptions) (*corev1.Secret, error) {
return nil, nil
}
DeletePodCollectionMock = func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
return errors.New("something went wrong")
}
},
},
{
name: "subscription updated successfully",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
license: "",
namespace: "",
tenantName: "",
secretName: OperatorSubnetLicenseSecretName,
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: false,
mockFunc: func() {
tenant.Spec.Configuration = &corev1.LocalObjectReference{
Name: "minio-configuration",
}
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
imm := true
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: OperatorSubnetLicenseSecretName,
Name: "minio-configuration",
},
Immutable: &imm,
Data: map[string][]byte{
ConsoleSubnetLicense: []byte("eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I"),
"config.env": []byte("export MINIO_SUBNET_LICENSE=\"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I\""),
},
}, nil
}
DeleteSecretMock = func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
}
CreateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.CreateOptions) (*corev1.Secret, error) {
UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) {
return nil, nil
}
DeletePodCollectionMock = func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
return nil
},
},
{
name: "error updating subscription for tenant because cannot get configuration file",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: true,
mockFunc: func() {
tenant.Spec.Configuration = &corev1.LocalObjectReference{
Name: "minio-configuration",
}
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
return nil, errors.New("something wrong happened")
}
UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) {
return nil, nil
}
},
},
{
name: "error updating subscription for tenant because configuration file has wrong format",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: true,
mockFunc: func() {
tenant.Spec.Configuration = &corev1.LocalObjectReference{
Name: "minio-configuration",
}
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "minio-configuration",
},
Data: map[string][]byte{
"aaaaa": []byte("export MINIO_SUBNET_LICENSE=\"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I\""),
},
}, nil
}
UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) {
return nil, nil
}
},
},
{
name: "error updating subscription for tenant because cannot update configuration file",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: true,
mockFunc: func() {
tenant.Spec.Configuration = &corev1.LocalObjectReference{
Name: "minio-configuration",
}
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "minio-configuration",
},
Data: map[string][]byte{
"config.env": []byte("export MINIO_SUBNET_LICENSE=\"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I\""),
},
}, nil
}
UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) {
return nil, errors.New("something wrong happened")
}
},
},
{
name: "success updating subscription for tenant with env variable",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: false,
mockFunc: func() {
tenant.Spec.Env = []corev1.EnvVar{
{
Name: "MINIO_SUBNET_LICENSE",
Value: "",
ValueFrom: nil,
},
}
opClientTenantUpdateMock = func(ctx context.Context, tenant *v2.Tenant, opts metav1.UpdateOptions) (*v2.Tenant, error) {
return nil, nil
}
},
},
{
name: "error updating subscription for tenant with env variable because of update tenant error",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: true,
mockFunc: func() {
tenant.Spec.Env = []corev1.EnvVar{
{
Name: "MINIO_SUBNET_LICENSE",
Value: "",
ValueFrom: nil,
},
}
opClientTenantUpdateMock = func(ctx context.Context, tenant *v2.Tenant, opts metav1.UpdateOptions) (*v2.Tenant, error) {
return nil, errors.New("something wrong happened")
}
},
},
@@ -196,7 +210,7 @@ func Test_addSubscriptionLicenseToTenant(t *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
if err := addSubscriptionLicenseToTenant(tt.args.ctx, tt.args.clientSet, tt.args.license, tt.args.namespace, tt.args.tenantName, tt.args.secretName); (err != nil) != tt.wantErr {
if err := addSubscriptionLicenseToTenant(tt.args.ctx, tt.args.clientSet, tt.args.opClient, tt.args.license, tt.args.tenant); (err != nil) != tt.wantErr {
t.Errorf("addSubscriptionLicenseToTenant() error = %v, wantErr %v", err, tt.wantErr)
}
})

View File

@@ -33,10 +33,6 @@ import (
"strings"
"time"
"github.com/minio/console/pkg/auth/ldap"
"github.com/minio/console/pkg/auth/idp/oauth2"
"github.com/dustin/go-humanize"
"github.com/minio/console/restapi"
@@ -342,7 +338,7 @@ func getTenantAdminClient(ctx context.Context, client K8sClientI, tenant *miniov
return nil, err
}
sessionToken := ""
mAdmin, pErr := restapi.NewAdminClientWithInsecure(svcURL, tenantCreds.accessKey, tenantCreds.secretKey, sessionToken, false)
mAdmin, pErr := restapi.NewAdminClientWithInsecure(svcURL, tenantCreds.accessKey, tenantCreds.secretKey, sessionToken, true)
if pErr != nil {
return nil, pErr.Cause
}
@@ -355,28 +351,21 @@ type tenantKeys struct {
}
func getTenantCreds(ctx context.Context, client K8sClientI, tenant *miniov2.Tenant) (*tenantKeys, error) {
if tenant == nil || tenant.Spec.CredsSecret == nil {
return nil, errors.New("invalid arguments")
}
// get admin credentials from secret
creds, err := client.getSecret(ctx, tenant.Namespace, tenant.Spec.CredsSecret.Name, metav1.GetOptions{})
tenantConfiguration, err := GetTenantConfiguration(ctx, client, tenant)
if err != nil {
return nil, err
}
tenantAccessKey, ok := creds.Data["accesskey"]
tenantAccessKey, ok := tenantConfiguration["accesskey"]
if !ok {
restapi.LogError("tenant's secret doesn't contain accesskey")
return nil, restapi.ErrorGeneric
}
tenantSecretKey, ok := creds.Data["secretkey"]
tenantSecretKey, ok := tenantConfiguration["secretkey"]
if !ok {
restapi.LogError("tenant's secret doesn't contain secretkey")
return nil, restapi.ErrorGeneric
}
// TODO:
// We need to avoid using minio root credentials to talk to tenants, and instead use a different user credentials
// when that its implemented we also need to check here if the tenant has LDAP enabled so we authenticate first against AD
return &tenantKeys{accessKey: string(tenantAccessKey), secretKey: string(tenantSecretKey)}, nil
return &tenantKeys{accessKey: tenantAccessKey, secretKey: tenantSecretKey}, nil
}
func getTenant(ctx context.Context, operatorClient OperatorClientI, namespace, tenantName string) (*miniov2.Tenant, error) {
@@ -407,7 +396,6 @@ func isPrometheusEnabled(annotations map[string]string) bool {
func getTenantInfo(tenant *miniov2.Tenant) *models.Tenant {
var pools []*models.Pool
consoleImage := ""
var totalSize int64
for _, p := range tenant.Spec.Pools {
pools = append(pools, parseTenantPool(&p))
@@ -419,10 +407,6 @@ func getTenantInfo(tenant *miniov2.Tenant) *models.Tenant {
deletion = tenant.ObjectMeta.DeletionTimestamp.Format(time.RFC3339)
}
if tenant.HasConsoleEnabled() {
consoleImage = tenant.Spec.Console.Image
}
return &models.Tenant{
CreationDate: tenant.ObjectMeta.CreationTimestamp.Format(time.RFC3339),
DeletionDate: deletion,
@@ -432,16 +416,14 @@ func getTenantInfo(tenant *miniov2.Tenant) *models.Tenant {
Pools: pools,
Namespace: tenant.ObjectMeta.Namespace,
Image: tenant.Spec.Image,
ConsoleImage: consoleImage,
EnablePrometheus: isPrometheusEnabled(tenant.Annotations),
}
}
func getTenantDetailsResponse(session *models.Principal, params operator_api.TenantDetailsParams) (*models.Tenant, *models.Error) {
// 5 seconds timeout
//ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
//defer cancel()
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)
@@ -468,32 +450,9 @@ func getTenantDetailsResponse(session *models.Principal, params operator_api.Ten
client: clientSet,
}
tenantConfiguration := map[string][]byte{}
for _, config := range minTenant.GetEnvVars() {
tenantConfiguration[config.Name] = []byte(config.Value)
}
if minTenant.HasCredsSecret() {
minioSecret, err := clientSet.CoreV1().Secrets(minTenant.Namespace).Get(ctx, minTenant.Spec.CredsSecret.Name, metav1.GetOptions{})
// we can tolerate not getting this secret
if err != nil {
restapi.LogError("unable to fetch existing secrets for %s: %v", minTenant.Name, err)
}
configFromCredsSecret := minioSecret.Data
for key, val := range configFromCredsSecret {
tenantConfiguration[key] = val
}
}
if minTenant.HasConfigurationSecret() {
minioConfigurationSecret, err := clientSet.CoreV1().Secrets(minTenant.Namespace).Get(ctx, minTenant.Spec.Configuration.Name, metav1.GetOptions{})
if err == nil {
configFromFile := miniov2.ParseRawConfiguration(minioConfigurationSecret.Data["config.env"])
for key, val := range configFromFile {
tenantConfiguration[key] = val
}
}
tenantConfiguration, err := GetTenantConfiguration(ctx, &k8sClient, minTenant)
if err != nil {
restapi.LogError("unable to fetch configuration for tenant %s: %v", minTenant.Name, err)
}
// detect if AD/LDAP is enabled
@@ -508,27 +467,26 @@ func getTenantDetailsResponse(session *models.Principal, params operator_api.Ten
oidcEnabled = true
}
// detect if encryption is enabled
if minTenant.HasKESEnabled() || string(tenantConfiguration["MINIO_KMS_SECRET_KEY"]) != "" {
info.EncryptionEnabled = true
}
info.LogEnabled = minTenant.HasLogEnabled()
info.MonitoringEnabled = minTenant.HasPrometheusEnabled()
info.EncryptionEnabled = minTenant.HasKESEnabled()
info.IdpAdEnabled = ldapEnabled
info.IdpOidcEnabled = oidcEnabled
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)
license, _ := getSubscriptionLicense(context.Background(), &k8sClient, params.Namespace, minTenant.Spec.Console.ConsoleSecret.Name)
if license != "" {
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
licenseInfo, _, _ := subscriptionValidate(client, license, "", "")
// if licenseInfo is present attach it to the tenantInfo response
if licenseInfo != nil {
info.SubnetLicense = licenseInfo
}
// obtain current subnet license for tenant (if exists)
if license, ok := tenantConfiguration[MinIOSubnetLicense]; ok {
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
licenseInfo, _, _ := subscriptionValidate(client, string(license), "", "")
// if licenseInfo is present attach it to the tenantInfo response
if licenseInfo != nil {
info.SubnetLicense = licenseInfo
}
}
@@ -558,13 +516,11 @@ func getTenantDetailsResponse(session *models.Principal, params operator_api.Ten
schema := "http"
consoleSchema := "http"
consolePort := ":9090"
consolePort := fmt.Sprintf(":%d", miniov2.ConsolePort)
if minTenant.TLS() {
schema = "https"
}
if minTenant.AutoCert() || minTenant.ConsoleExternalCert() {
consoleSchema = "https"
consolePort = ":9443"
consolePort = fmt.Sprintf(":%d", miniov2.ConsoleTLSPort)
}
var minioEndpoint string
var consoleEndpoint string
@@ -646,8 +602,6 @@ func parseTenantCertificates(ctx context.Context, clientSet K8sClientI, namespac
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
@@ -656,25 +610,11 @@ func getTenantSecurity(ctx context.Context, clientSet K8sClientI, tenant *miniov
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,
Minio: minioExternalCertificates,
MinioCAs: minioExternalCaCertificates,
},
}, nil
}
@@ -746,8 +686,6 @@ func updateTenantSecurity(ctx context.Context, operatorClient OperatorClientI, c
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
@@ -776,33 +714,6 @@ func updateTenantSecurity(ctx context.Context, operatorClient OperatorClientI, c
}
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)
@@ -811,7 +722,6 @@ func updateTenantSecurity(ctx context.Context, operatorClient OperatorClientI, c
return err
}
newMinIOExternalCertSecret = append(newMinIOExternalCertSecret, externalCertSecrets...)
// Create new CAs Certificate Secrets for MinIO
var caCertificates []tenantSecret
for i, caCertificate := range params.Body.CustomCertificates.MinioCAs {
@@ -833,46 +743,9 @@ func updateTenantSecurity(ctx context.Context, operatorClient OperatorClientI, c
}
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
@@ -978,8 +851,6 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
tenantReq := params.Body
minioImage := tenantReq.Image
ctx := context.Background()
consoleHasTLS := false
if minioImage == "" {
minImg, err := cluster.GetMinioImage()
// we can live without figuring out the latest version of MinIO, Operator will use a hardcoded value
@@ -1015,9 +886,9 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
var instanceSecret corev1.Secret
var users []*corev1.LocalObjectReference
var tenantConfigurationENV string
tenantConfigurationENV := map[string]string{}
// Create the secret for the root credentials
// Create the secret for the root credentials (deprecated)
secretName := fmt.Sprintf("%s-secret", tenantName)
instanceSecret = corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
@@ -1033,8 +904,14 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
},
}
tenantConfigurationENV += fmt.Sprintf("export MINIO_ROOT_USER=\"%s\"\n", accessKey)
tenantConfigurationENV += fmt.Sprintf("export MINIO_ROOT_PASSWORD=\"%s\"\n", secretKey)
// Enable/Disable console object browser for MinIO tenant (default is on)
enabledConsole := "on"
if tenantReq.EnableConsole != nil && !*tenantReq.EnableConsole {
enabledConsole = "off"
}
tenantConfigurationENV["MINIO_BROWSER"] = enabledConsole
tenantConfigurationENV["MINIO_ROOT_USER"] = accessKey
tenantConfigurationENV["MINIO_ROOT_PASSWORD"] = secretKey
_, err = clientSet.CoreV1().Secrets(ns).Create(ctx, &instanceSecret, metav1.CreateOptions{})
if err != nil {
@@ -1060,7 +937,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
if tenantReq.ErasureCodingParity < 2 || tenantReq.ErasureCodingParity > 8 {
return nil, prepareError(errorInvalidErasureCodingValue)
}
tenantConfigurationENV += fmt.Sprintf("export MINIO_STORAGE_CLASS_STANDARD=\"%s\"\n", fmt.Sprintf("EC:%d", tenantReq.ErasureCodingParity))
tenantConfigurationENV["MINIO_STORAGE_CLASS_STANDARD"] = fmt.Sprintf("EC:%d", tenantReq.ErasureCodingParity)
}
//Construct a MinIO Instance with everything we are getting from parameters
@@ -1077,10 +954,11 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
},
},
}
var tenantExternalIDPConfigured bool
if tenantReq.Idp != nil {
// Enable IDP (Active Directory) for MinIO
if tenantReq.Idp.ActiveDirectory != nil {
tenantExternalIDPConfigured = true
serverAddress := *tenantReq.Idp.ActiveDirectory.URL
userNameFormat := tenantReq.Idp.ActiveDirectory.UsernameFormat
userNameSearchFilter := tenantReq.Idp.ActiveDirectory.UsernameSearchFilter
@@ -1096,33 +974,33 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
serverStartTLS := tenantReq.Idp.ActiveDirectory.ServerStartTLS
// LDAP Server
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_LDAP_SERVER_ADDR=\"%s\"\n", serverAddress)
tenantConfigurationENV["MINIO_IDENTITY_LDAP_SERVER_ADDR"] = serverAddress
if tlsSkipVerify {
tenantConfigurationENV += "export MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY=on\n"
tenantConfigurationENV["MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY"] = "on"
}
if serverInsecure {
tenantConfigurationENV += "export MINIO_IDENTITY_LDAP_SERVER_INSECURE=on\n"
tenantConfigurationENV["MINIO_IDENTITY_LDAP_SERVER_INSECURE"] = "on"
}
if serverStartTLS {
tenantConfigurationENV += "export MINIO_IDENTITY_LDAP_SERVER_STARTTLS=on\n"
tenantConfigurationENV["MINIO_IDENTITY_LDAP_SERVER_STARTTLS"] = "on"
}
// LDAP Username
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_LDAP_USERNAME_FORMAT=\"%s\"\n", userNameFormat)
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_LDAP_USERNAME_SEARCH_FILTER=\"%s\"\n", userNameSearchFilter)
tenantConfigurationENV["MINIO_IDENTITY_LDAP_USERNAME_FORMAT"] = userNameFormat
tenantConfigurationENV["MINIO_IDENTITY_LDAP_USERNAME_SEARCH_FILTER"] = userNameSearchFilter
// LDAP Lookup
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN=\"%s\"\n", lookupBindDN)
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD=\"%s\"\n", lookupBindPassword)
tenantConfigurationENV["MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN"] = lookupBindDN
tenantConfigurationENV["MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD"] = lookupBindPassword
// LDAP User DN
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN=\"%s\"\n", userDNSearchBaseDN)
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER=\"%s\"\n", userDNSearchFilter)
tenantConfigurationENV["MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN"] = userDNSearchBaseDN
tenantConfigurationENV["MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER"] = userDNSearchFilter
// LDAP Group
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE=\"%s\"\n", groupNameAttribute)
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN=\"%s\"\n", groupSearchBaseDN)
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER=\"%s\"\n", groupSearchFilter)
tenantConfigurationENV["MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE"] = groupNameAttribute
tenantConfigurationENV["MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN"] = groupSearchBaseDN
tenantConfigurationENV["MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER"] = groupSearchFilter
// Attach the list of LDAP user DNs that will be administrator for the Tenant
for i, userDN := range tenantReq.Idp.ActiveDirectory.UserDNS {
@@ -1149,20 +1027,23 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
minInst.Spec.Users = users
} else if tenantReq.Idp.Oidc != nil {
tenantExternalIDPConfigured = true
// Enable IDP (OIDC) for MinIO
url := *tenantReq.Idp.Oidc.ConfigurationURL
clientID := *tenantReq.Idp.Oidc.ClientID
secretID := *tenantReq.Idp.Oidc.SecretID
claimName := *tenantReq.Idp.Oidc.ClaimName
scopes := tenantReq.Idp.Oidc.Scopes
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_OPENID_CONFIG_URL=\"%s\"\n", url)
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_OPENID_CLIENT_ID=\"%s\"\n", clientID)
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_OPENID_CLIENT_SECRET=\"%s\"\n", secretID)
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_OPENID_CLAIM_NAME=\"%s\"\n", claimName)
callbackURL := tenantReq.Idp.Oidc.CallbackURL
tenantConfigurationENV["MINIO_IDENTITY_OPENID_CONFIG_URL"] = url
tenantConfigurationENV["MINIO_IDENTITY_OPENID_CLIENT_ID"] = clientID
tenantConfigurationENV["MINIO_IDENTITY_OPENID_CLIENT_SECRET"] = secretID
tenantConfigurationENV["MINIO_IDENTITY_OPENID_CLAIM_NAME"] = claimName
tenantConfigurationENV["MINIO_IDENTITY_OPENID_REDIRECT_URI"] = callbackURL
if scopes == "" {
scopes = "openid,profile,email"
}
tenantConfigurationENV += fmt.Sprintf("export MINIO_IDENTITY_OPENID_SCOPES=\"%s\"\n", scopes)
tenantConfigurationENV["MINIO_IDENTITY_OPENID_SCOPES"] = scopes
} else if len(tenantReq.Idp.Keys) > 0 {
// Create the secret any built-in user passed if no external IDP was configured
for i := 0; i < len(tenantReq.Idp.Keys); i++ {
@@ -1200,7 +1081,6 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
if *tenantReq.EnableTLS {
// requestAutoCert is enabled, MinIO will be deployed with TLS enabled and encryption can be enabled
isEncryptionEnabled = true
consoleHasTLS = true
}
}
// External TLS certificates for MinIO
@@ -1262,130 +1142,12 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
minInst.Spec.ExternalCaCertSecret = certificateSecrets
}
}
// optionals are set below
var tenantUserAccessKey string
var tenantUserSecretKey string
keyElementEmpty := len(tenantReq.Idp.Keys) == 1 && (*tenantReq.Idp.Keys[0].AccessKey == "" && *tenantReq.Idp.Keys[0].SecretKey == "")
enableConsole := true
if tenantReq.EnableConsole != nil && *tenantReq.EnableConsole {
enableConsole = *tenantReq.EnableConsole
}
if enableConsole {
consoleSelector := fmt.Sprintf("%s-console", tenantName)
consoleSecretName := fmt.Sprintf("%s-secret", consoleSelector)
consoleSecretData := map[string][]byte{
"CONSOLE_PBKDF_PASSPHRASE": []byte(restapi.RandomCharString(16)),
"CONSOLE_PBKDF_SALT": []byte(restapi.RandomCharString(8)),
}
// If Subnet License is present in k8s secrets, copy that to the CONSOLE_SUBNET_LICENSE env variable
// of the console tenant
license, _ := getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName)
if license != "" {
consoleSecretData[restapi.ConsoleSubnetLicense] = []byte(license)
}
imm := true
consoleSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: consoleSecretName,
Labels: map[string]string{
miniov2.TenantLabel: tenantName,
},
},
Immutable: &imm,
Data: consoleSecretData,
}
minInst.Spec.Console = &miniov2.ConsoleConfiguration{
Replicas: 1,
Image: getConsoleImage(),
ConsoleSecret: &corev1.LocalObjectReference{Name: consoleSecretName},
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("64Mi"),
},
},
}
if tenantReq.TLS != nil && tenantReq.TLS.Console != nil {
consoleHasTLS = true
// Certificates used by the console instance
externalCertSecretName := fmt.Sprintf("%s-console-external-certificates", secretName)
certificates := []*models.KeyPairConfiguration{tenantReq.TLS.Console}
externalCertSecret, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, certificates, externalCertSecretName, tenantName)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric)
}
if len(externalCertSecret) > 0 {
minInst.Spec.Console.ExternalCertSecret = externalCertSecret[0]
}
}
// If IDP is not already enabled via LDAP (Active Directory) and OIDC configuration is present then
// enable oidc for console
if tenantReq.Idp != nil {
if tenantReq.Idp.ActiveDirectory != nil {
consoleSecret.Data[ldap.ConsoleLDAPEnabled] = []byte("on")
} else if tenantReq.Idp.Oidc != nil {
url := *tenantReq.Idp.Oidc.URL
clientID := *tenantReq.Idp.Oidc.ClientID
secretID := *tenantReq.Idp.Oidc.SecretID
callbackURL := tenantReq.Idp.Oidc.CallbackURL
if url != "" && clientID != "" && secretID != "" {
consoleSecret.Data[oauth2.ConsoleIdpURL] = []byte(url)
consoleSecret.Data[oauth2.ConsoleIdpClientID] = []byte(clientID)
consoleSecret.Data[oauth2.ConsoleIdpSecret] = []byte(secretID)
consoleScheme := "http"
consolePort := miniov2.ConsolePort
// If Console will be deployed with TLS enabled (using AutoCert or External certificates)
if consoleHasTLS {
consoleScheme = "https"
consolePort = miniov2.ConsoleTLSPort
}
// default callback url is https://localhost:9443/oauth_callback
consoleSecret.Data[oauth2.ConsoleIdpCallbackURL] = []byte(fmt.Sprintf("%s://localhost:%d/oauth_callback", consoleScheme, consolePort))
if callbackURL != "" {
consoleSecret.Data[oauth2.ConsoleIdpCallbackURL] = []byte(callbackURL)
}
}
}
}
_, err = clientSet.CoreV1().Secrets(ns).Create(ctx, &consoleSecret, metav1.CreateOptions{})
if err != nil {
return nil, prepareError(restapi.ErrorGeneric)
}
// Set Labels, Annotations and Node Selector for Console
if tenantReq.Console != nil {
minInst.Spec.Console.Annotations = tenantReq.Console.Annotations
minInst.Spec.Console.Labels = tenantReq.Console.Labels
minInst.Spec.Console.NodeSelector = tenantReq.Console.NodeSelector
}
// External TLS CA certificates for Console
if tenantReq.TLS != nil && len(tenantReq.TLS.ConsoleCaCertificates) > 0 {
var caCertificates []tenantSecret
for i, caCertificate := range tenantReq.TLS.ConsoleCaCertificates {
certificateContent, err := base64.StdEncoding.DecodeString(caCertificate)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric, nil, err)
}
caCertificates = append(caCertificates, tenantSecret{
Name: fmt.Sprintf("console-ca-certificate-%d", i),
Content: map[string][]byte{
"public.crt": certificateContent,
},
})
}
if len(caCertificates) > 0 {
certificateSecrets, err := createOrReplaceSecrets(ctx, &k8sClient, ns, caCertificates, tenantName)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric, nil, err)
}
minInst.Spec.Console.ExternalCaCertSecret = certificateSecrets
}
}
// If Subnet License is present in k8s secrets, copy that to the MINIO_SUBNET_LICENSE env variable
// of the console tenant
license, _ := getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName)
if license != "" {
tenantConfigurationENV[MinIOSubnetLicense] = license
}
// add annotations
@@ -1432,11 +1194,6 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
minInst.Annotations[prometheusScrape] = "true"
}
// set console image if provided
if tenantReq.ConsoleImage != "" {
minInst.Spec.Console.Image = tenantReq.ConsoleImage
}
//Default class name for Log search
diskSpaceFromAPI := int64(5) * humanize.GiByte // Default is 5Gi
logSearchStorageClass := "" // Default is ""
@@ -1450,18 +1207,15 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
if tenantReq.LogSearchConfiguration.StorageClass != "" {
logSearchStorageClass = tenantReq.LogSearchConfiguration.StorageClass
}
if tenantReq.LogSearchConfiguration.StorageClass == "" && len(tenantReq.Pools) > 0 {
logSearchStorageClass = tenantReq.Pools[0].VolumeConfiguration.StorageClassName
}
if tenantReq.LogSearchConfiguration.Image != "" {
logSearchImage = tenantReq.LogSearchConfiguration.Image
}
if tenantReq.LogSearchConfiguration.PostgresImage != "" {
logSearchPgImage = tenantReq.LogSearchConfiguration.PostgresImage
}
}
logSearchDiskSpace := resource.NewQuantity(diskSpaceFromAPI, resource.DecimalExponent)
@@ -1542,7 +1296,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
{
Name: tenantConfigurationName,
Content: map[string][]byte{
"config.env": []byte(tenantConfigurationENV),
"config.env": []byte(GenerateTenantConfigurationFile(tenantConfigurationENV)),
},
},
}, tenantName)
@@ -1569,20 +1323,16 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
return nil, prepareError(err)
}
}
response = &models.CreateTenantResponse{}
// Attach Console Credentials
if enableConsole {
var itemsToReturn []*models.TenantResponseItem
if len(tenantReq.Idp.Keys) == 0 || keyElementEmpty {
itemsToReturn = append(itemsToReturn, &models.TenantResponseItem{AccessKey: tenantUserAccessKey, SecretKey: tenantUserSecretKey})
} else { // IDP Keys
for _, item := range tenantReq.Idp.Keys {
itemsToReturn = append(itemsToReturn, &models.TenantResponseItem{AccessKey: *item.AccessKey, SecretKey: *item.SecretKey})
}
response = &models.CreateTenantResponse{
ExternalIDP: tenantExternalIDPConfigured,
}
if tenantReq.Idp != nil && !tenantExternalIDPConfigured {
for _, credential := range tenantReq.Idp.Keys {
response.Console = append(response.Console, &models.TenantResponseItem{
AccessKey: *credential.AccessKey,
SecretKey: *credential.SecretKey,
})
}
response.Console = itemsToReturn
}
return response, nil
}
@@ -1666,11 +1416,6 @@ func updateTenantAction(ctx context.Context, operatorClient OperatorClientI, cli
}
}
// update the console image
if strings.TrimSpace(params.Body.ConsoleImage) != "" && minInst.Spec.Console != nil {
minInst.Spec.Console.Image = params.Body.ConsoleImage
}
// if image to update is empty we'll use the latest image by default
if strings.TrimSpace(imageToUpdate) != "" {
minInst.Spec.Image = imageToUpdate
@@ -1811,8 +1556,8 @@ func getTenantAddPoolResponse(session *models.Principal, params operator_api.Ten
// getTenantUsageResponse returns the usage of a tenant
func getTenantUsageResponse(session *models.Principal, params operator_api.GetTenantUsageParams) (*models.TenantUsage, *models.Error) {
// 5 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// 30 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)

View File

@@ -63,22 +63,6 @@ func tenantUpdateCertificates(ctx context.Context, operatorClient OperatorClient
return err
}
}
// check if Console is deployed with external certs and user provided new Console keypair
if tenant.ConsoleExternalCert() && tenant.HasConsoleEnabled() && body.Console != nil {
consoleCertSecretName := fmt.Sprintf("%s-console-external-certificates", secretName)
// update certificates
certificates := []*models.KeyPairConfiguration{body.Console}
if _, err := createOrReplaceExternalCertSecrets(ctx, clientSet, namespace, certificates, consoleCertSecretName, tenantName); err != nil {
return err
}
// restart Console pods
err := clientSet.deletePodCollection(ctx, namespace, metav1.DeleteOptions{}, metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", miniov2.ConsoleTenantLabel, fmt.Sprintf("%s-console", tenantName)),
})
if err != nil {
return err
}
}
return nil
}
@@ -251,7 +235,6 @@ func createOrReplaceSecrets(ctx context.Context, clientSet K8sClientI, ns string
// log the error if any and continue
LogError("deleting secret name %s failed: %v, continuing..", secret.Name, err)
}
imm := true
k8sSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secret.Name,
@@ -259,9 +242,8 @@ func createOrReplaceSecrets(ctx context.Context, clientSet K8sClientI, ns string
miniov2.TenantLabel: tenantName,
},
},
Type: corev1.SecretTypeOpaque,
Immutable: &imm,
Data: secret.Content,
Type: corev1.SecretTypeOpaque,
Data: secret.Content,
}
_, err = clientSet.createSecret(ctx, ns, k8sSecret, metav1.CreateOptions{})
if err != nil {

View File

@@ -33,6 +33,7 @@ import (
var DeletePodCollectionMock func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
var DeleteSecretMock func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error
var CreateSecretMock func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error)
var UpdateSecretMock func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.UpdateOptions) (*v1.Secret, error)
func (c k8sClientMock) deletePodCollection(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
return DeletePodCollectionMock(ctx, namespace, opts, listOpts)
@@ -46,6 +47,10 @@ func (c k8sClientMock) createSecret(ctx context.Context, namespace string, secre
return CreateSecretMock(ctx, namespace, secret, opts)
}
func (c k8sClientMock) updateSecret(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.UpdateOptions) (*v1.Secret, error) {
return UpdateSecretMock(ctx, namespace, secret, opts)
}
func Test_tenantUpdateCertificates(t *testing.T) {
k8sClient := k8sClientMock{}
opClient := opClientMock{}
@@ -228,70 +233,6 @@ func Test_tenantUpdateCertificates(t *testing.T) {
},
wantErr: true,
},
{
name: "error replacing external certs for console because of missing keypair",
args: args{
ctx: context.Background(),
opClient: opClient,
clientSet: k8sClient,
namespace: "",
params: operator_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Console: &models.KeyPairConfiguration{},
},
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) {
return &miniov2.Tenant{
Spec: miniov2.TenantSpec{
Console: &miniov2.ConsoleConfiguration{
ExternalCertSecret: &miniov2.LocalCertificateReference{
Name: "secret",
},
},
},
}, nil
},
},
wantErr: true,
},
{
name: "certificates replaced but error during deleting existing tenant pods",
args: args{
ctx: context.Background(),
opClient: opClient,
clientSet: k8sClient,
namespace: "",
params: operator_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Console: &models.KeyPairConfiguration{
Crt: &crt,
Key: &key,
},
},
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) {
return &miniov2.Tenant{
Spec: miniov2.TenantSpec{
Console: &miniov2.ConsoleConfiguration{
ExternalCertSecret: &miniov2.LocalCertificateReference{
Name: "secret",
},
},
},
}, nil
},
mockDeleteSecret: func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
},
mockCreateSecret: func(ctx context.Context, namespace string, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error) {
return &v1.Secret{}, nil
},
mockDeletePodCollection: func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
return errors.New("error deleting console pods")
},
},
wantErr: true,
},
}
for _, tt := range tests {
opClientTenantGetMock = tt.args.mockTenantGet

View File

@@ -468,9 +468,6 @@ func Test_TenantInfo(t *testing.T) {
Spec: miniov2.TenantSpec{
Pools: []miniov2.Pool{},
Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z",
Console: &miniov2.ConsoleConfiguration{
Image: "minio/console:master",
},
},
Status: miniov2.TenantStatus{
CurrentState: "ready",
@@ -484,7 +481,6 @@ func Test_TenantInfo(t *testing.T) {
Namespace: "minio-ns",
Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z",
EnablePrometheus: false,
ConsoleImage: "minio/console:master",
},
},
}
@@ -1017,31 +1013,6 @@ func Test_UpdateTenantAction(t *testing.T) {
},
wantErr: false,
},
{
name: "Update minio console version no errors",
args: args{
ctx: context.Background(),
operatorClient: opClient,
httpCl: httpClientM,
nameSpace: "default",
tenantName: "minio-tenant",
mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) {
return &miniov2.Tenant{}, nil
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) {
return &miniov2.Tenant{}, nil
},
mockHTTPClientGet: func(url string) (resp *http.Response, err error) {
return nil, errors.New("use default minio")
},
params: operator_api.UpdateTenantParams{
Body: &models.UpdateTenantRequest{
ConsoleImage: "minio/console:v0.9.1",
},
},
},
wantErr: false,
},
{
name: "Update minio image pull secrets no errors",
args: args{

View File

@@ -86,11 +86,12 @@ func serveProxy(responseWriter http.ResponseWriter, req *http.Request) {
nsTenant := fmt.Sprintf("%s/%s", tenant.Namespace, tenant.Name)
tenantSchema := "http"
tenantPort := ":9090"
if tenant.AutoCert() || tenant.ConsoleExternalCert() {
tenantPort := fmt.Sprintf(":%d", v2.ConsolePort)
if tenant.AutoCert() {
tenantSchema = "https"
tenantPort = ":9443"
tenantPort = fmt.Sprintf(":%d", v2.ConsoleTLSPort)
}
tenantURL := fmt.Sprintf("%s://%s.%s.svc.%s%s", tenantSchema, tenant.ConsoleCIServiceName(), tenant.Namespace, v2.GetClusterDomain(), tenantPort)
// for development
//tenantURL = "http://localhost:9091"
@@ -112,34 +113,14 @@ func serveProxy(responseWriter http.ResponseWriter, req *http.Request) {
return
}
// FIXME: abstract this to a common tenant.GetConfiguration() function
tenantConfiguration := map[string][]byte{}
for _, config := range tenant.GetEnvVars() {
tenantConfiguration[config.Name] = []byte(config.Value)
k8sClient := k8sClient{
client: clientSet,
}
if tenant.HasCredsSecret() {
minioSecret, err := clientSet.CoreV1().Secrets(tenant.Namespace).Get(req.Context(), tenant.Spec.CredsSecret.Name, metav1.GetOptions{})
if err != nil {
log.Println(err)
responseWriter.WriteHeader(500)
return
}
configFromCredsSecret := minioSecret.Data
for key, val := range configFromCredsSecret {
tenantConfiguration[key] = val
}
}
if tenant.HasConfigurationSecret() {
minioConfigurationSecret, err := clientSet.CoreV1().Secrets(tenant.Namespace).Get(req.Context(), tenant.Spec.Configuration.Name, metav1.GetOptions{})
if err == nil {
configFromFile := v2.ParseRawConfiguration(minioConfigurationSecret.Data["config.env"])
for key, val := range configFromFile {
tenantConfiguration[key] = val
}
}
tenantConfiguration, err := GetTenantConfiguration(req.Context(), &k8sClient, tenant)
if err != nil {
log.Println(err)
responseWriter.WriteHeader(500)
return
}
data := map[string]string{

65
operatorapi/utils.go Normal file
View File

@@ -0,0 +1,65 @@
// 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 operatorapi
import (
"context"
"errors"
"fmt"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func GetTenantConfiguration(ctx context.Context, clientSet K8sClientI, tenant *miniov2.Tenant) (map[string]string, error) {
if tenant == nil {
return nil, errors.New("tenant cannot be nil")
}
tenantConfiguration := map[string]string{}
for _, config := range tenant.GetEnvVars() {
tenantConfiguration[config.Name] = config.Value
}
// legacy support for tenants with tenant.spec.credsSecret
if tenant.HasCredsSecret() {
minioSecret, err := clientSet.getSecret(ctx, tenant.Namespace, tenant.Spec.CredsSecret.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
configFromCredsSecret := minioSecret.Data
for key, val := range configFromCredsSecret {
tenantConfiguration[key] = string(val)
}
}
if tenant.HasConfigurationSecret() {
minioConfigurationSecret, err := clientSet.getSecret(ctx, tenant.Namespace, tenant.Spec.Configuration.Name, metav1.GetOptions{})
if err == nil {
configFromFile := miniov2.ParseRawConfiguration(minioConfigurationSecret.Data["config.env"])
for key, val := range configFromFile {
tenantConfiguration[key] = string(val)
}
}
}
return tenantConfiguration, nil
}
func GenerateTenantConfigurationFile(configuration map[string]string) string {
var rawConfiguration string
for key, val := range configuration {
rawConfiguration += fmt.Sprintf("export %s=\"%s\"\n", key, val)
}
return rawConfiguration
}

View File

@@ -150,6 +150,9 @@ func getLicenseFromCredentials(client cluster.HTTPClientI, username, password st
if err != nil {
return "", err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return "", fmt.Errorf("subnet served returned status %d code", resp.StatusCode)
}
userLicense := &subnetLicenseResponse{}
// Parse subnet license response
err = json.Unmarshal(bodyBytes, userLicense)

View File

@@ -144,11 +144,17 @@ func Test_getLicenseFromCredentials(t *testing.T) {
mockFunc: func() {
HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) {
// returning test jwt token
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"has_memberships\":true,\"token_info\":{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik4wRXdOa1V5UXpORU1UUkNOekU0UmpSR1JVWkJSa1UxUmtZNE9EY3lOekZHTXpjNU1qZ3hNZyJ9.eyJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2dyb3VwcyI6W10sImh0dHBzOi8vaWQuc3VibmV0Lm1pbi5pby9jbGFpbXMvcm9sZXMiOltdLCJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2VtYWlsIjoibGVuaW4rYzFAbWluaW8uaW8iLCJpc3MiOiJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vIiwic3ViIjoiYXV0aDB8NWZjZWFlYTMyNTNhZjEwMDc3NDZkMDM0IiwiYXVkIjoiaHR0cHM6Ly9zdWJuZXQubWluLmlvL2FwaSIsImlhdCI6MTYwODQxNjE5NiwiZXhwIjoxNjExMDA4MTk2LCJhenAiOiI1WTA0eVZlejNiOFgxUFVzRHVqSmxuZXVuY3ExVjZxaiIsInNjb3BlIjoib2ZmbGluZV9hY2Nlc3MiLCJndHkiOiJwYXNzd29yZCJ9.GC8DRLT0jUEteuBZBmyMXMswLSblCr_89Gu5NcVRUzKSYAaZ5VFW4UFgo1BpiC0sePuWJ0Vykitphx7znTfZfj5B3mZbOw3ejG6kxz7nm9DuYMmySJFYnwroZ9EP02vkW7-n_-YvEg8le1wXfkJ3lTUzO3aWddS4rfQRsZ2YJJUj61GiNyEK_QNP4PrYOuzLyD1wV75NejFqfcFoj7nRkT1K2BM0-89-_f2AFDGTjov6Ig6s1s-zLC9wxcYSmubNwpCJytZmQgPqIepOr065Y6OB4n0n0B5sXguuGuzb8VAkECrHhHPz8ta926fc0jC4XxVCNKdbV1_qC3-1yY7AJA\",\"expires_in\":2592000.0,\"token_type\":\"Bearer\"}}")))}, nil
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"has_memberships\":true,\"token_info\":{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik4wRXdOa1V5UXpORU1UUkNOekU0UmpSR1JVWkJSa1UxUmtZNE9EY3lOekZHTXpjNU1qZ3hNZyJ9.eyJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2dyb3VwcyI6W10sImh0dHBzOi8vaWQuc3VibmV0Lm1pbi5pby9jbGFpbXMvcm9sZXMiOltdLCJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2VtYWlsIjoibGVuaW4rYzFAbWluaW8uaW8iLCJpc3MiOiJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vIiwic3ViIjoiYXV0aDB8NWZjZWFlYTMyNTNhZjEwMDc3NDZkMDM0IiwiYXVkIjoiaHR0cHM6Ly9zdWJuZXQubWluLmlvL2FwaSIsImlhdCI6MTYwODQxNjE5NiwiZXhwIjoxNjExMDA4MTk2LCJhenAiOiI1WTA0eVZlejNiOFgxUFVzRHVqSmxuZXVuY3ExVjZxaiIsInNjb3BlIjoib2ZmbGluZV9hY2Nlc3MiLCJndHkiOiJwYXNzd29yZCJ9.GC8DRLT0jUEteuBZBmyMXMswLSblCr_89Gu5NcVRUzKSYAaZ5VFW4UFgo1BpiC0sePuWJ0Vykitphx7znTfZfj5B3mZbOw3ejG6kxz7nm9DuYMmySJFYnwroZ9EP02vkW7-n_-YvEg8le1wXfkJ3lTUzO3aWddS4rfQRsZ2YJJUj61GiNyEK_QNP4PrYOuzLyD1wV75NejFqfcFoj7nRkT1K2BM0-89-_f2AFDGTjov6Ig6s1s-zLC9wxcYSmubNwpCJytZmQgPqIepOr065Y6OB4n0n0B5sXguuGuzb8VAkECrHhHPz8ta926fc0jC4XxVCNKdbV1_qC3-1yY7AJA\",\"expires_in\":2592000.0,\"token_type\":\"Bearer\"}}"))),
}, nil
}
HTTPDoMock = func(req *http.Request) (*http.Response, error) {
// returning test jwt license
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"license\":\"" + license + "\",\"metadata\":{\"email\":\"lenin+c1@minio.io\",\"issuer\":\"subnet@minio.io\",\"accountId\":176,\"teamName\":\"console-customer\",\"serviceType\":\"STANDARD\",\"capacity\":25,\"requestedAt\":\"2020-12-19T22:23:31.609144732Z\",\"expiresAt\":\"2021-12-19T22:23:31.609144732Z\"}}")))}, nil
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"license\":\"" + license + "\",\"metadata\":{\"email\":\"lenin+c1@minio.io\",\"issuer\":\"subnet@minio.io\",\"accountId\":176,\"teamName\":\"console-customer\",\"serviceType\":\"STANDARD\",\"capacity\":25,\"requestedAt\":\"2020-12-19T22:23:31.609144732Z\",\"expiresAt\":\"2021-12-19T22:23:31.609144732Z\"}}"))),
}, nil
}
},
},

View File

@@ -1,23 +1,23 @@
{
"files": {
"main.css": "./static/css/main.8cfac526.chunk.css",
"main.js": "./static/js/main.7c6441f0.chunk.js",
"main.js.map": "./static/js/main.7c6441f0.chunk.js.map",
"main.js": "./static/js/main.d8928293.chunk.js",
"main.js.map": "./static/js/main.d8928293.chunk.js.map",
"runtime-main.js": "./static/js/runtime-main.3fe0c1ac.js",
"runtime-main.js.map": "./static/js/runtime-main.3fe0c1ac.js.map",
"static/css/2.c018cba7.chunk.css": "./static/css/2.c018cba7.chunk.css",
"static/js/2.3262d01b.chunk.js": "./static/js/2.3262d01b.chunk.js",
"static/js/2.3262d01b.chunk.js.map": "./static/js/2.3262d01b.chunk.js.map",
"static/css/2.60e04a19.chunk.css": "./static/css/2.60e04a19.chunk.css",
"static/js/2.b98d309e.chunk.js": "./static/js/2.b98d309e.chunk.js",
"static/js/2.b98d309e.chunk.js.map": "./static/js/2.b98d309e.chunk.js.map",
"index.html": "./index.html",
"static/css/2.c018cba7.chunk.css.map": "./static/css/2.c018cba7.chunk.css.map",
"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.3262d01b.chunk.js.LICENSE.txt": "./static/js/2.3262d01b.chunk.js.LICENSE.txt"
"static/js/2.b98d309e.chunk.js.LICENSE.txt": "./static/js/2.b98d309e.chunk.js.LICENSE.txt"
},
"entrypoints": [
"static/js/runtime-main.3fe0c1ac.js",
"static/css/2.c018cba7.chunk.css",
"static/js/2.3262d01b.chunk.js",
"static/css/2.60e04a19.chunk.css",
"static/js/2.b98d309e.chunk.js",
"static/css/main.8cfac526.chunk.css",
"static/js/main.7c6441f0.chunk.js"
"static/js/main.d8928293.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 href="./styles/root-styles.css" 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.c018cba7.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 id="loader-block"><svg class="loader-svg-container" viewBox="22 22 44 44"><circle class="loader-style MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate" cx="44" cy="44" r="20.2" fill="none" stroke-width="3.6"></circle></svg></div></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.3262d01b.chunk.js"></script><script src="./static/js/main.7c6441f0.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 href="./styles/root-styles.css" 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 id="loader-block"><svg class="loader-svg-container" viewBox="22 22 44 44"><circle class="loader-style MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate" cx="44" cy="44" r="20.2" fill="none" stroke-width="3.6"></circle></svg></div></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.b98d309e.chunk.js"></script><script src="./static/js/main.d8928293.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

@@ -65,7 +65,11 @@ const CreateFolderModal = ({
return (
<React.Fragment>
<ModalWrapper modalOpen={modalOpen} title="Choose or create a new path" onClose={onClose}>
<ModalWrapper
modalOpen={modalOpen}
title="Choose or create a new path"
onClose={onClose}
>
<Grid container>
<h3 className={classes.pathLabel}>
Current Path: {folderTruncated}/

View File

@@ -73,6 +73,7 @@ const CredentialsPrompt = ({
}
const consoleCreds = get(newServiceAccount, "console", null);
const idp = get(newServiceAccount, "idp", false);
return (
<ModalWrapper
@@ -86,7 +87,7 @@ const CredentialsPrompt = ({
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
A new {entity} has been created with the following details:
{consoleCreds && (
{!idp && consoleCreds && (
<React.Fragment>
<Grid item xs={12} className={classes.credentialsPanel}>
<strong>Console Credentials</strong>
@@ -116,55 +117,67 @@ const CredentialsPrompt = ({
</Grid>
</React.Fragment>
)}
<Typography
component="p"
variant="body1"
className={classes.warningBlock}
>
Write these down, as this is the only time the secret will be
displayed.
</Typography>
{idp ? (
<Typography
component="p"
variant="body1"
className={classes.warningBlock}
>
Please Login via the configured external identity provider.
</Typography>
) : (
<Typography
component="p"
variant="body1"
className={classes.warningBlock}
>
Write these down, as this is the only time the secret will be
displayed.
</Typography>
)}
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<Button
onClick={() => {
let consoleExtras = {};
{!idp && (
<Button
onClick={() => {
let consoleExtras = {};
if (consoleCreds) {
if (!Array.isArray(consoleCreds)) {
consoleExtras = {
console: [
{
access_key: consoleCreds.accessKey,
secret_key: consoleCreds.secretKey,
},
],
};
} else {
const cCreds = consoleCreds.map((itemMap) => {
return {
access_key: itemMap.accessKey,
secret_key: itemMap.secretKey,
if (consoleCreds) {
if (!Array.isArray(consoleCreds)) {
consoleExtras = {
console: [
{
access_key: consoleCreds.accessKey,
secret_key: consoleCreds.secretKey,
},
],
};
});
} else {
const cCreds = consoleCreds.map((itemMap) => {
return {
access_key: itemMap.accessKey,
secret_key: itemMap.secretKey,
};
});
consoleExtras = {
console: [...cCreds],
};
consoleExtras = {
console: [...cCreds],
};
}
}
}
download(
"credentials.json",
JSON.stringify({
...consoleExtras,
})
);
}}
color="primary"
>
Download
</Button>
download(
"credentials.json",
JSON.stringify({
...consoleExtras,
})
);
}}
color="primary"
>
Download
</Button>
)}
<Button
onClick={() => {
closeModal();

View File

@@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
export interface NewServiceAccount {
idp?: boolean;
console?: ConsoleSA | ConsoleSA[];
accessKey?: string;
secretKey?: string;

View File

@@ -557,29 +557,30 @@ const AddTenant = ({
const consoleSAList = get(res, "console", []);
let newSrvAcc: NewServiceAccount = {
idp: get(res, "externalIDP", false),
console: [],
};
if (consoleSAList && Array.isArray(consoleSAList)) {
const consoleItem = consoleSAList.map((consoleKey) => {
return {
accessKey: consoleKey.access_key,
secretKey: consoleKey.secret_key,
if (consoleSAList) {
if (Array.isArray(consoleSAList)) {
const consoleItem = consoleSAList.map((consoleKey) => {
return {
accessKey: consoleKey.access_key,
secretKey: consoleKey.secret_key,
};
});
newSrvAcc.console = consoleItem;
} else {
newSrvAcc = {
console: {
accessKey: res.console.access_key,
secretKey: res.console.secret_key,
},
};
});
newSrvAcc.console = consoleItem;
} else {
newSrvAcc = {
console: {
accessKey: res.console.access_key,
secretKey: res.console.secret_key,
},
};
}
}
setAddSending(false);
setShowNewCredentials(true);
setCreatedAccount(newSrvAcc);
})

View File

@@ -31,10 +31,6 @@ import {
addKeyPair,
addFileToKeyPair,
deleteKeyPair,
addConsoleCertificate,
addFileToConsoleCaCertificates,
deleteConsoleCaCertificate,
addConsoleCaCertificate,
} from "../../actions";
import { AppState } from "../../../../../store";
import { KeyPair } from "../../ListTenants/utils";
@@ -48,20 +44,14 @@ interface ISecurityProps {
enableCustomCerts: boolean;
minioCertificates: KeyPair[];
caCertificates: KeyPair[];
consoleCertificate: KeyPair;
updateAddField: typeof updateAddField;
isPageValid: typeof isPageValid;
addFileToCaCertificates: typeof addFileToCaCertificates;
deleteCaCertificate: typeof deleteCaCertificate;
addCaCertificate: typeof addCaCertificate;
consoleCaCertificates: KeyPair[];
addFileToConsoleCaCertificates: typeof addFileToConsoleCaCertificates;
deleteConsoleCaCertificate: typeof deleteConsoleCaCertificate;
addConsoleCaCertificate: typeof addConsoleCaCertificate;
addKeyPair: typeof addKeyPair;
addFileToKeyPair: typeof addFileToKeyPair;
deleteKeyPair: typeof deleteKeyPair;
addConsoleCertificate: typeof addConsoleCertificate;
}
const styles = (theme: Theme) =>
@@ -80,17 +70,11 @@ const Security = ({
enableCustomCerts,
minioCertificates,
caCertificates,
consoleCertificate,
updateAddField,
isPageValid,
addFileToCaCertificates,
deleteCaCertificate,
addCaCertificate,
consoleCaCertificates,
addFileToConsoleCaCertificates,
deleteConsoleCaCertificate,
addConsoleCaCertificate,
addConsoleCertificate,
addKeyPair,
addFileToKeyPair,
deleteKeyPair,
@@ -298,87 +282,6 @@ const Security = ({
</Button>
</Grid>
</Grid>
<Grid container>
<Grid item xs={12}>
<br />
<Divider />
<br />
</Grid>
</Grid>
<Grid container>
<Grid item xs={12}>
<Typography variant="overline" display="block" gutterBottom>
Console Certificates
</Typography>
</Grid>
<Grid item xs={6}>
<FileSelector
onChange={(encodedValue, fileName) => {
addConsoleCertificate("cert", fileName, encodedValue);
}}
accept=".cer,.crt,.cert,.pem"
id="consoleCert"
name="consoleCert"
label="Cert"
value={consoleCertificate.cert}
/>
</Grid>
<Grid item xs={6}>
<FileSelector
onChange={(encodedValue, fileName) => {
addConsoleCertificate("key", fileName, encodedValue);
}}
accept=".key,.pem"
id="consoleKey"
name="consoleKey"
label="Key"
value={consoleCertificate.key}
/>
</Grid>
</Grid>
<Grid container>
<Grid item xs={12}>
<Typography variant="overline" display="block" gutterBottom>
Console CA Certificates
</Typography>
</Grid>
{consoleCaCertificates.map((keyPair: KeyPair) => (
<Fragment key={keyPair.id}>
<Grid item xs={10}>
<FileSelector
onChange={(encodedValue, fileName) => {
addFileToConsoleCaCertificates(
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={() => {
deleteConsoleCaCertificate(keyPair.id);
}}
color="secondary"
>
Remove
</Button>
</Grid>
</Fragment>
))}
<Grid item xs={12}>
<Button onClick={addConsoleCaCertificate} color="primary">
Add More
</Button>
</Grid>
</Grid>
</Fragment>
)}
</Fragment>
@@ -394,10 +297,6 @@ const mapState = (state: AppState) => ({
state.tenants.createTenant.fields.security.enableCustomCerts,
minioCertificates: state.tenants.createTenant.certificates.minioCertificates,
caCertificates: state.tenants.createTenant.certificates.caCertificates,
consoleCaCertificates:
state.tenants.createTenant.certificates.consoleCaCertificates,
consoleCertificate:
state.tenants.createTenant.certificates.consoleCertificate,
});
const connector = connect(mapState, {
@@ -409,10 +308,6 @@ const connector = connect(mapState, {
addKeyPair,
addFileToKeyPair,
deleteKeyPair,
addConsoleCertificate,
addFileToConsoleCaCertificates,
deleteConsoleCaCertificate,
addConsoleCaCertificate,
});
export default withStyles(styles)(connector(Security));

View File

@@ -94,23 +94,6 @@ const TenantSecurity = ({
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[]>(
[]
);
const [consoleTLSCertificateSecrets, setConsoleTLSCertificateSecrets] =
useState<ICertificateInfo[]>([]);
const [consoleTLSCaCertificateSecrets, setConsoleTLSCaCertificateSecrets] =
useState<ICertificateInfo[]>([]);
// MinIO certificates
const [minioCertificates, setMinioCertificates] = useState<KeyPair[]>([]);
const [minioCaCertificates, setMinioCaCertificates] = useState<KeyPair[]>([]);
@@ -128,20 +111,11 @@ const TenantSecurity = ({
)
.then((res: ITenantSecurityResponse) => {
setEnableAutoCert(res.autoCert);
if (
res.customCertificates.minio ||
res.customCertificates.minioCAs ||
res.customCertificates.console ||
res.customCertificates.consoleCAs
) {
if (res.customCertificates.minio || res.customCertificates.minioCAs) {
setEnableCustomCerts(true);
}
setMinioTLSCertificateSecrets(res.customCertificates.minio || []);
setMinioTLSCaCertificateSecrets(res.customCertificates.minioCAs || []);
setConsoleTLSCertificateSecrets(res.customCertificates.console || []);
setConsoleTLSCaCertificateSecrets(
res.customCertificates.consoleCAs || []
);
})
.catch((err: ErrorResponseHandler) => {
setErrorSnackMessage(err);
@@ -172,28 +146,15 @@ const TenantSecurity = ({
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
@@ -225,24 +186,6 @@ const TenantSecurity = ({
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: ErrorResponseHandler) => {
@@ -270,18 +213,6 @@ const TenantSecurity = ({
);
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 = (
@@ -305,16 +236,6 @@ const TenantSecurity = ({
updateCertificates = setMinioCaCertificates;
break;
}
case "console": {
certificates = consoleCertificates;
updateCertificates = setConsoleCertificates;
break;
}
case "consoleCAs": {
certificates = consoleCaCertificates;
updateCertificates = setConsoleCaCertificates;
break;
}
default:
}
@@ -346,16 +267,6 @@ const TenantSecurity = ({
updateCertificates = setMinioCaCertificates;
break;
}
case "console": {
certificates = consoleCertificates;
updateCertificates = setConsoleCertificates;
break;
}
case "consoleCAs": {
certificates = consoleCaCertificates;
updateCertificates = setConsoleCaCertificates;
break;
}
default:
}
@@ -382,16 +293,6 @@ const TenantSecurity = ({
updateCertificates = setMinioCaCertificates;
break;
}
case "console": {
certificates = consoleCertificates;
updateCertificates = setConsoleCertificates;
break;
}
case "consoleCAs": {
certificates = consoleCaCertificates;
updateCertificates = setConsoleCaCertificates;
break;
}
default:
}
const updatedCertificates = [
@@ -699,225 +600,6 @@ const TenantSecurity = ({
</Grid>
</Grid>
</Paper>
{tenant?.consoleEnabled ? (
<Fragment>
<br />
<Paper className={classes.paperContainer}>
<Grid container>
<Grid container item xs={12}>
<Typography variant="h5" 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 &&
certificateInfo.domains.map((dom) => {
return <div>{dom}</div>;
})}
</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="h6" display="block" gutterBottom>
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 &&
certificateInfo.domains.map((dom) => {
return <div>{dom}</div>;
})}
</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 Console Certificate
</Button>
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={dialogOpen || isSending}
onClick={() => setDialogOpen(true)}
>
Save
</Button>
</Grid>
</Paper>
</Fragment>
) : null}
</Fragment>
)}
</Fragment>

View File

@@ -18,11 +18,14 @@ package restapi
import (
"crypto/x509"
"io/ioutil"
"net"
"strconv"
"strings"
"time"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
xcerts "github.com/minio/pkg/certs"
"github.com/minio/pkg/env"
)
@@ -45,9 +48,6 @@ var (
// SessionDuration cookie validity duration
SessionDuration = 45 * time.Minute
// LicenseKey in memory license key used by console ui
LicenseKey = ""
)
func getMinIOServer() string {
@@ -243,13 +243,19 @@ func getPrometheusJobID() string {
// GetSubnetLicense returns the current subnet jwt license
func GetSubnetLicense() string {
// if we have a license key in memory return that
if LicenseKey != "" {
return LicenseKey
// if this is running on embedded console try to get the license from the MinIO tenant configuration
minioConfigPath := env.Get(MinIOConfigEnvFile, "")
if minioConfigPath != "" {
dat, err := ioutil.ReadFile(minioConfigPath)
if err == nil {
minioConfiguration := miniov2.ParseRawConfiguration(dat)
if val, ok := minioConfiguration[MinIOSubnetLicense]; ok {
return string(val)
}
}
}
// return license configured via environment variable
LicenseKey = env.Get(ConsoleSubnetLicense, "")
return LicenseKey
// fallback to console license env variable
return env.Get(ConsoleSubnetLicense, "")
}
var (

View File

@@ -26,6 +26,8 @@ const (
ConsoleTLSHostname = "CONSOLE_TLS_HOSTNAME"
ConsoleTLSPort = "CONSOLE_TLS_PORT"
ConsoleSubnetLicense = "CONSOLE_SUBNET_LICENSE"
MinIOConfigEnvFile = "MINIO_CONFIG_ENV_FILE"
MinIOSubnetLicense = "MINIO_SUBNET_LICENSE"
// Constants for Secure middleware
ConsoleSecureAllowedHosts = "CONSOLE_SECURE_ALLOWED_HOSTS"

View File

@@ -3286,7 +3286,6 @@ definitions:
objectLegalHoldStatus:
type: string
enum:

View File

@@ -1031,14 +1031,6 @@ definitions:
type: array
items:
$ref: "#/definitions/certificateInfo"
console:
type: array
items:
$ref: "#/definitions/certificateInfo"
consoleCAs:
type: array
items:
$ref: "#/definitions/certificateInfo"
updateTenantSecurityRequest:
type: object
@@ -1060,14 +1052,6 @@ definitions:
type: array
items:
type: string
console:
type: array
items:
$ref: "#/definitions/keyPairConfiguration"
consoleCAs:
type: array
items:
type: string
certificateInfo:
type: object
@@ -1100,8 +1084,6 @@ definitions:
$ref: "#/definitions/pool"
image:
type: string
console_image:
type: string
namespace:
type: string
total_size:
@@ -1132,10 +1114,6 @@ definitions:
$ref: "#/definitions/tenantStatus"
minioTLS:
type: boolean
consoleTLS:
type: boolean
consoleEnabled:
type: boolean
tenantUsage:
type: object
@@ -1190,9 +1168,6 @@ definitions:
image:
type: string
pattern: "^((.*?)/(.*?):(.+))$"
console_image:
type: string
pattern: "^((.*?)/(.*?):(.+))$"
image_registry:
$ref: "#/definitions/imageRegistry"
image_pull_secret:
@@ -1226,8 +1201,6 @@ definitions:
pattern: "^[a-z0-9-]{3,63}$"
image:
type: string
console_image:
type: string
pools:
type: array
items:
@@ -1276,9 +1249,6 @@ definitions:
encryption:
type: object
$ref: "#/definitions/encryptionConfiguration"
console:
type: object
$ref: "#/definitions/consoleConfiguration"
expose_minio:
type: boolean
expose_console:
@@ -1318,17 +1288,11 @@ definitions:
type: array
items:
$ref: "#/definitions/keyPairConfiguration"
console:
type: object
$ref: "#/definitions/keyPairConfiguration"
ca_certificates:
type: array
items:
type: string
console_ca_certificates:
type: array
items:
type: string
logSearchConfiguration:
type: object
properties:
@@ -1427,14 +1391,6 @@ definitions:
items:
type: string
consoleConfiguration:
allOf:
- $ref: "#/definitions/metadataFields"
- type: object
properties:
image:
type: string
encryptionConfiguration:
allOf:
- $ref: "#/definitions/metadataFields"
@@ -1602,6 +1558,8 @@ definitions:
createTenantResponse:
type: object
properties:
externalIDP:
type: boolean
console:
type: array
items: