Subnet cluster registration (#1338)

- Removed old registration flow
- Add support for new online and offline cluster registration flow
- Support login accounts with mfa enabled
- Registration screens

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
Lenin Alevski
2022-01-23 23:42:00 -06:00
committed by GitHub
parent ceff2840d8
commit 41b34645f9
60 changed files with 4944 additions and 1710 deletions

View File

@@ -225,30 +225,5 @@ func startOperatorServer(ctx *cli.Context) error {
defer server.Shutdown()
// subnet license refresh process
go func() {
// start refreshing subnet license after 5 seconds..
time.Sleep(time.Second * 5)
failedAttempts := 0
for {
if err := operatorapi.RefreshLicense(); err != nil {
operatorapi.LogError("Refreshing subnet license failed: %v", err)
failedAttempts++
// end license refresh after 3 consecutive failed attempts
if failedAttempts >= 3 {
return
}
// wait 5 minutes and retry again
time.Sleep(time.Minute * 5)
continue
}
// if license refreshed successfully reset the counter
failedAttempts = 0
// try to refresh license every 24 hrs
time.Sleep(time.Hour * 24)
}
}()
return server.Serve()
}

2
go.mod
View File

@@ -30,6 +30,7 @@ require (
github.com/rs/xid v1.3.0
github.com/secure-io/sio-go v0.3.1
github.com/stretchr/testify v1.7.0
github.com/tidwall/gjson v1.10.2
github.com/unrolled/secure v1.0.9
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
@@ -118,7 +119,6 @@ require (
github.com/rjeczalik/notify v0.9.2 // indirect
github.com/shirou/gopsutil/v3 v3.21.8 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/tidwall/gjson v1.10.2 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tinylib/msgp v1.1.6 // indirect

View File

@@ -0,0 +1,122 @@
// 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"
"github.com/go-openapi/validate"
)
// SubnetLoginMFARequest subnet login m f a request
//
// swagger:model subnetLoginMFARequest
type SubnetLoginMFARequest struct {
// mfa token
// Required: true
MfaToken *string `json:"mfa_token"`
// otp
// Required: true
Otp *string `json:"otp"`
// username
// Required: true
Username *string `json:"username"`
}
// Validate validates this subnet login m f a request
func (m *SubnetLoginMFARequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateMfaToken(formats); err != nil {
res = append(res, err)
}
if err := m.validateOtp(formats); err != nil {
res = append(res, err)
}
if err := m.validateUsername(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *SubnetLoginMFARequest) validateMfaToken(formats strfmt.Registry) error {
if err := validate.Required("mfa_token", "body", m.MfaToken); err != nil {
return err
}
return nil
}
func (m *SubnetLoginMFARequest) validateOtp(formats strfmt.Registry) error {
if err := validate.Required("otp", "body", m.Otp); err != nil {
return err
}
return nil
}
func (m *SubnetLoginMFARequest) validateUsername(formats strfmt.Registry) error {
if err := validate.Required("username", "body", m.Username); err != nil {
return err
}
return nil
}
// ContextValidate validates this subnet login m f a request based on context it is used
func (m *SubnetLoginMFARequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SubnetLoginMFARequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetLoginMFARequest) UnmarshalBinary(b []byte) error {
var res SubnetLoginMFARequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,73 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// SubnetLoginRequest subnet login request
//
// swagger:model subnetLoginRequest
type SubnetLoginRequest struct {
// api key
APIKey string `json:"apiKey,omitempty"`
// password
Password string `json:"password,omitempty"`
// username
Username string `json:"username,omitempty"`
}
// Validate validates this subnet login request
func (m *SubnetLoginRequest) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this subnet login request based on context it is used
func (m *SubnetLoginRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SubnetLoginRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetLoginRequest) UnmarshalBinary(b []byte) error {
var res SubnetLoginRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,142 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// SubnetLoginResponse subnet login response
//
// swagger:model subnetLoginResponse
type SubnetLoginResponse struct {
// access token
AccessToken string `json:"access_token,omitempty"`
// mfa token
MfaToken string `json:"mfa_token,omitempty"`
// organizations
Organizations []*SubnetOrganization `json:"organizations"`
// registered
Registered bool `json:"registered,omitempty"`
}
// Validate validates this subnet login response
func (m *SubnetLoginResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateOrganizations(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *SubnetLoginResponse) validateOrganizations(formats strfmt.Registry) error {
if swag.IsZero(m.Organizations) { // not required
return nil
}
for i := 0; i < len(m.Organizations); i++ {
if swag.IsZero(m.Organizations[i]) { // not required
continue
}
if m.Organizations[i] != nil {
if err := m.Organizations[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("organizations" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("organizations" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this subnet login response based on the context it is used
func (m *SubnetLoginResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateOrganizations(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *SubnetLoginResponse) contextValidateOrganizations(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Organizations); i++ {
if m.Organizations[i] != nil {
if err := m.Organizations[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("organizations" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("organizations" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *SubnetLoginResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetLoginResponse) UnmarshalBinary(b []byte) error {
var res SubnetLoginResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,82 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// SubnetOrganization subnet organization
//
// swagger:model subnetOrganization
type SubnetOrganization struct {
// account Id
AccountID int64 `json:"accountId,omitempty"`
// company
Company string `json:"company,omitempty"`
// is account owner
IsAccountOwner bool `json:"isAccountOwner,omitempty"`
// short name
ShortName string `json:"shortName,omitempty"`
// subscription status
SubscriptionStatus string `json:"subscriptionStatus,omitempty"`
// user Id
UserID int64 `json:"userId,omitempty"`
}
// Validate validates this subnet organization
func (m *SubnetOrganization) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this subnet organization based on context it is used
func (m *SubnetOrganization) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SubnetOrganization) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetOrganization) UnmarshalBinary(b []byte) error {
var res SubnetOrganization
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,67 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// SubnetRegTokenResponse subnet reg token response
//
// swagger:model SubnetRegTokenResponse
type SubnetRegTokenResponse struct {
// reg token
RegToken string `json:"regToken,omitempty"`
}
// Validate validates this subnet reg token response
func (m *SubnetRegTokenResponse) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this subnet reg token response based on context it is used
func (m *SubnetRegTokenResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SubnetRegTokenResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetRegTokenResponse) UnmarshalBinary(b []byte) error {
var res SubnetRegTokenResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,105 @@
// 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"
"github.com/go-openapi/validate"
)
// SubnetRegisterRequest subnet register request
//
// swagger:model subnetRegisterRequest
type SubnetRegisterRequest struct {
// account id
// Required: true
AccountID *string `json:"account_id"`
// token
// Required: true
Token *string `json:"token"`
}
// Validate validates this subnet register request
func (m *SubnetRegisterRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAccountID(formats); err != nil {
res = append(res, err)
}
if err := m.validateToken(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *SubnetRegisterRequest) validateAccountID(formats strfmt.Registry) error {
if err := validate.Required("account_id", "body", m.AccountID); err != nil {
return err
}
return nil
}
func (m *SubnetRegisterRequest) validateToken(formats strfmt.Registry) error {
if err := validate.Required("token", "body", m.Token); err != nil {
return err
}
return nil
}
// ContextValidate validates this subnet register request based on context it is used
func (m *SubnetRegisterRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SubnetRegisterRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetRegisterRequest) UnmarshalBinary(b []byte) error {
var res SubnetRegisterRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -94,8 +94,6 @@ func configureAPI(api *operations.OperatorAPI) http.Handler {
registerVolumesHandlers(api)
// Namespaces handlers
registerNamespaceHandlers(api)
// Subscription handlers
registerSubscriptionHandlers(api)
api.PreServerShutdown = func() {}

View File

@@ -1,416 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package operatorapi
import (
"context"
"errors"
"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"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/cluster"
"github.com/minio/console/models"
"github.com/minio/console/operatorapi/operations"
"github.com/minio/console/operatorapi/operations/operator_api"
)
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 := getSubscriptionActivateResponse(session, params.Namespace, params.Tenant)
if err != nil {
return operator_api.NewSubscriptionActivateDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewSubscriptionActivateNoContent()
})
// 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)
})
// 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
func retrieveLicense(ctx context.Context, sessionToken string) (string, error) {
var license string
// configure kubernetes client
clientSet, err := cluster.K8sClient(sessionToken)
if err != nil {
return "", err
}
k8sClient := k8sClient{
client: clientSet,
}
// Get cluster subscription license
license, err = getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName)
if err != nil {
return "", err
}
return license, 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
licenseSecret, err := clientSet.getSecret(ctx, namespace, secretName, metav1.GetOptions{})
if err != nil {
return "", err
}
license, ok := licenseSecret.Data[ConsoleSubnetLicense]
if !ok {
LogError("subnet secret does not contain a valid subnet license")
return "", restapi.ErrorGeneric
}
return string(license), nil
}
// addSubscriptionLicenseToTenant replace existing console tenant secret and adds the subnet license key
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
}
func getSubscriptionRefreshResponse(session *models.Principal) (*models.License, *models.Error) {
// 20 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
licenseKey, err := retrieveLicense(ctx, session.STSSessionToken)
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
newLicenseInfo, licenseRaw, err := subscriptionRefresh(client, licenseKey)
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
// configure kubernetes client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
k8sClient := k8sClient{
client: clientSet,
}
// save license key to k8s and restart all console pods
if err = saveSubscriptionLicense(ctx, &k8sClient, licenseRaw); err != nil {
return nil, prepareError(restapi.ErrorGeneric, nil, err)
}
// update license for all existing tenants
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
opClient := operatorClient{
client: opClientClientSet,
}
// iterate over all tenants and update licenses
tenants, err := opClient.TenantList(ctx, "", metav1.ListOptions{})
if err != nil {
return nil, prepareError(err)
}
for _, tenant := range tenants.Items {
if err = addSubscriptionLicenseToTenant(ctx, &k8sClient, &opClient, licenseRaw, &tenant); err != nil {
return nil, prepareError(err)
}
}
return newLicenseInfo, nil
}
// RefreshLicense will check current subnet license and try to renew it
func RefreshLicense() error {
// Get current license
saK8SToken := getK8sSAToken()
licenseKey, err := retrieveLicense(context.Background(), saK8SToken)
if licenseKey == "" {
return errors.New("no license present")
}
if err != nil {
return err
}
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
// Attempt to refresh license
_, refreshedLicenseKey, err := subscriptionRefresh(client, licenseKey)
if err != nil {
return err
}
if refreshedLicenseKey == "" {
return errors.New("license expired, please open a support ticket at https://subnet.min.io/")
}
// store new license in memory for console ui
LicenseKey = refreshedLicenseKey
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
clientSet, err := cluster.K8sClient(saK8SToken)
if err != nil {
return err
}
k8sClient := k8sClient{
client: clientSet,
}
return saveSubscriptionLicense(ctx, &k8sClient, refreshedLicenseKey)
}
func subscriptionRefresh(httpClient *cluster.HTTPClient, license string) (*models.License, string, error) {
licenseInfo, rawLicense, err := subnet.RefreshLicense(httpClient, license)
if err != nil {
return nil, "", err
}
return &models.License{
Email: licenseInfo.Email,
AccountID: licenseInfo.AccountID,
StorageCapacity: licenseInfo.StorageCapacity,
Plan: licenseInfo.Plan,
ExpiresAt: licenseInfo.ExpiresAt.String(),
Organization: licenseInfo.Organization,
}, rawLicense, nil
}
// saveSubscriptionLicense will create or replace an existing subnet license secret in the k8s cluster
func saveSubscriptionLicense(ctx context.Context, clientSet K8sClientI, license string) error {
licenseSecret, err := clientSet.getSecret(ctx, cluster.Namespace, OperatorSubnetLicenseSecretName, metav1.GetOptions{})
if err != nil {
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
}
// update existing license
licenseSecret.Data = map[string][]byte{
ConsoleSubnetLicense: []byte(license),
}
_, err = clientSet.updateSecret(ctx, cluster.Namespace, licenseSecret, metav1.UpdateOptions{})
if err != nil {
return err
}
return nil
}
// subscriptionValidate will validate the provided jwt license against the subnet public key
func subscriptionValidate(client cluster.HTTPClientI, license, email, password string) (*models.License, string, error) {
licenseInfo, rawLicense, err := subnet.ValidateLicense(client, license, email, password)
if err != nil {
return nil, "", err
}
return &models.License{
Email: licenseInfo.Email,
AccountID: licenseInfo.AccountID,
StorageCapacity: licenseInfo.StorageCapacity,
Plan: licenseInfo.Plan,
ExpiresAt: licenseInfo.ExpiresAt.String(),
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

@@ -1,359 +0,0 @@
// This file is part of MinIO Kubernetes Cloud
// 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"
"testing"
v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
"errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
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
opClient OperatorClientI
license string
tenant *v2.Tenant
}
tests := []struct {
name string
args args
wantErr bool
mockFunc func()
}{
{
name: "success updating subscription for tenant with configuration file",
args: args{
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) {
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, 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")
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
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)
}
})
}
}
func Test_saveSubscriptionLicense(t *testing.T) {
k8sClient := k8sClientMock{}
type args struct {
ctx context.Context
clientSet K8sClientI
license string
}
tests := []struct {
name string
args args
wantErr bool
mockFunc func()
}{
{
name: "error deleting existing secret",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
license: "1111111111",
},
mockFunc: func() {
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")
}
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
if err := saveSubscriptionLicense(tt.args.ctx, tt.args.clientSet, tt.args.license); (err != nil) != tt.wantErr {
t.Errorf("saveSubscriptionLicense() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_getSubscriptionLicense(t *testing.T) {
license := "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I"
k8sClient := k8sClientMock{}
type args struct {
ctx context.Context
clientSet K8sClientI
namespace string
secretName string
}
tests := []struct {
name string
args args
want string
wantErr bool
mockFunc func()
}{
{
name: "error because subscription license doesnt exists",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
namespace: "namespace",
secretName: OperatorSubnetLicenseSecretName,
},
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 license field doesnt exist in k8s secret",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
namespace: "namespace",
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(license),
},
}, nil
}
},
},
{
name: "license obtained successfully",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
namespace: "namespace",
secretName: OperatorSubnetLicenseSecretName,
},
wantErr: false,
want: license,
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(license),
},
}, nil
}
},
},
}
for _, tt := range tests {
if tt.mockFunc != nil {
tt.mockFunc()
}
t.Run(tt.name, func(t *testing.T) {
got, err := getSubscriptionLicense(tt.args.ctx, tt.args.clientSet, tt.args.namespace, tt.args.secretName)
if (err != nil) != tt.wantErr {
t.Errorf("getSubscriptionLicense() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("getSubscriptionLicense() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -558,18 +558,6 @@ func getTenantDetailsResponse(session *models.Principal, params operator_api.Ten
info.IdpOidcEnabled = oidcEnabled
info.MinioTLS = minTenant.TLS()
// 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
}
}
// attach status information
info.Status = &models.TenantStatus{
HealthStatus: string(minTenant.Status.HealthStatus),
@@ -1241,13 +1229,6 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
}
}
// 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
var annotations map[string]string

View File

@@ -49,3 +49,15 @@ func GetLicenseInfoFromJWT(license string, publicKeys []string) (*licverifier.Li
}
return nil, errors.New("invalid license key")
}
// MfaReq - JSON payload of the SUBNET mfa api
type MfaReq struct {
Username string `json:"username"`
OTP string `json:"otp"`
Token string `json:"token"`
}
type LoginResp struct {
AccessToken string
MfaToken string
}

View File

@@ -29,9 +29,4 @@ JkO2PfyyAYEO/5dBlPh1Undu9WQl6J7B
const (
// Constants for subnet configuration
ConsoleSubnetURL = "CONSOLE_SUBNET_URL"
// Subnet endpoints
publicKey = "/downloads/license-pubkey.pem"
loginEndpoint = "/api/auth/login"
refreshLicenseKeyEndpoint = "/api/auth/subscription/renew-license"
licenseKeyEndpoint = "/api/auth/subscription/license-key"
)

View File

@@ -18,209 +18,94 @@
package subnet
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"github.com/minio/console/models"
"github.com/minio/madmin-go"
mc "github.com/minio/mc/cmd"
"github.com/tidwall/gjson"
"github.com/minio/console/cluster"
"github.com/minio/pkg/licverifier"
)
// subnetLoginRequest body request for subnet login
type subnetLoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
func LoginWithMFA(client cluster.HTTPClientI, username, mfaToken, otp string) (*LoginResp, error) {
mfaLoginReq := MfaReq{Username: username, OTP: otp, Token: mfaToken}
resp, err := subnetPostReq(client, subnetMFAURL(), mfaLoginReq, nil)
if err != nil {
return nil, err
}
token := gjson.Get(resp, "token_info.access_token")
if token.Exists() {
return &LoginResp{AccessToken: token.String(), MfaToken: ""}, nil
}
return nil, errors.New("access token not found in response")
}
// tokenInfo
type tokenInfo struct {
AccessToken string `json:"access_token"`
ExpiresIn float64 `json:"expires_in"`
TokenType string `json:"token_type"`
}
// subnetLoginResponse body resonse from subnet after login
type subnetLoginResponse struct {
HasMembership bool `json:"has_memberships"`
TokenInfo tokenInfo `json:"token_info"`
}
// LicenseMetadata claims in subnet license
type LicenseMetadata struct {
Email string `json:"email"`
Issuer string `json:"issuer"`
TeamName string `json:"teamName"`
ServiceType string `json:"serviceType"`
RequestedAt string `json:"requestedAt"`
ExpiresAt string `json:"expiresAt"`
AccountID int64 `json:"accountId"`
Capacity int64 `json:"capacity"`
}
// subnetLicenseResponse body response returned by subnet license endpoint
type subnetLicenseResponse struct {
License string `json:"license"`
Metadata LicenseMetadata `json:"metadata"`
}
// subnetLoginRequest body request for subnet login
type subnetRefreshRequest struct {
License string `json:"license"`
}
// getNewLicenseFromExistingLicense will perform license refresh based on the provided license key
func getNewLicenseFromExistingLicense(client cluster.HTTPClientI, licenseKey string) (string, error) {
request := subnetRefreshRequest{
License: licenseKey,
func Login(client cluster.HTTPClientI, username, password string) (*LoginResp, error) {
loginReq := map[string]string{
"username": username,
"password": password,
}
// http body for login request
payloadBytes, err := json.Marshal(request)
respStr, err := subnetPostReq(client, subnetLoginURL(), loginReq, nil)
if err != nil {
return "", err
return nil, err
}
subnetURL := GetSubnetURL()
url := fmt.Sprintf("%s%s", subnetURL, refreshLicenseKeyEndpoint)
resp, err := client.Post(url, "application/json", bytes.NewReader(payloadBytes))
if err != nil {
return "", err
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
subnetLicense := &subnetLicenseResponse{}
// Parse subnet login response
err = json.Unmarshal(bodyBytes, subnetLicense)
if err != nil {
return "", err
}
return subnetLicense.License, nil
}
// getLicenseFromCredentials will perform authentication against subnet using
// user provided credentials and return the current subnet license key
func getLicenseFromCredentials(client cluster.HTTPClientI, username, password string) (string, error) {
request := subnetLoginRequest{
Username: username,
Password: password,
}
// http body for login request
payloadBytes, err := json.Marshal(request)
if err != nil {
return "", err
}
subnetURL := GetSubnetURL()
url := fmt.Sprintf("%s%s", subnetURL, loginEndpoint)
// Authenticate against subnet using email/password provided by user
resp, err := client.Post(url, "application/json", bytes.NewReader(payloadBytes))
if err != nil {
return "", err
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
subnetSession := &subnetLoginResponse{}
// Parse subnet login response
err = json.Unmarshal(bodyBytes, subnetSession)
if err != nil {
return "", err
}
// Get license key using session token
token := subnetSession.TokenInfo.AccessToken
url = fmt.Sprintf("%s%s", subnetURL, licenseKeyEndpoint)
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return "", err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
resp, err = client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
bodyBytes, err = ioutil.ReadAll(resp.Body)
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)
if err != nil {
return "", err
}
return userLicense.License, nil
}
// downloadSubnetPublicKey will download the current subnet public key.
func downloadSubnetPublicKey(client cluster.HTTPClientI) (string, error) {
// Get the public key directly from Subnet
url := fmt.Sprintf("%s%s", GetSubnetURL(), publicKey)
resp, err := client.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(resp.Body)
if err != nil {
return "", err
}
return buf.String(), err
}
// ValidateLicense will download the current subnet public key, if the public key its not available for license
// verification then console will fall back to verification with hardcoded public keys
func ValidateLicense(client cluster.HTTPClientI, licenseKey, email, password string) (licInfo *licverifier.LicenseInfo, license string, err error) {
var publicKeys []string
if email != "" && password != "" {
// fetch subnet license key using user credentials
license, err = getLicenseFromCredentials(client, email, password)
if err != nil {
return nil, "", err
mfaRequired := gjson.Get(respStr, "mfa_required").Bool()
if mfaRequired {
mfaToken := gjson.Get(respStr, "mfa_token").String()
if mfaToken == "" {
return nil, errors.New("missing mfa token")
}
} else if licenseKey != "" {
license = licenseKey
return &LoginResp{AccessToken: "", MfaToken: mfaToken}, nil
}
token := gjson.Get(respStr, "token_info.access_token")
if token.Exists() {
return &LoginResp{AccessToken: token.String(), MfaToken: ""}, nil
}
return nil, errors.New("access token not found in response")
}
func GetOrganizations(client cluster.HTTPClientI, token string) ([]*models.SubnetOrganization, error) {
headers := subnetAuthHeaders(token)
respStr, err := subnetGetReq(client, subnetOrgsURL(), headers)
if err != nil {
return nil, err
}
var organizations []*models.SubnetOrganization
err = json.Unmarshal([]byte(respStr), &organizations)
if err != nil {
log.Println(err)
}
return organizations, nil
}
func Register(client cluster.HTTPClientI, admInfo madmin.InfoMessage, apiKey, token, accountID string) (string, error) {
var headers map[string]string
regInfo := GetClusterRegInfo(admInfo)
regURL := subnetRegisterURL()
if apiKey != "" {
regURL += "?api_key=" + apiKey
} else {
return nil, "", errors.New("invalid license")
}
subnetPubKey, err := downloadSubnetPublicKey(client)
if err != nil {
log.Print(err)
// there was an issue getting the subnet public key
// use hardcoded public keys instead
publicKeys = OfflinePublicKeys
} else {
publicKeys = append(publicKeys, subnetPubKey)
}
licInfo, err = GetLicenseInfoFromJWT(license, publicKeys)
if err != nil {
return nil, "", err
}
return licInfo, license, nil
}
func RefreshLicense(client cluster.HTTPClientI, licenseKey string) (licInfo *licverifier.LicenseInfo, license string, err error) {
if licenseKey != "" {
license, err = getNewLicenseFromExistingLicense(client, licenseKey)
if err != nil {
return nil, "", err
if accountID == "" || token == "" {
return "", errors.New("missing accountID or authentication token")
}
licenseInfo, rawLicense, err := ValidateLicense(client, license, "", "")
if err != nil {
return nil, "", err
}
return licenseInfo, rawLicense, nil
headers = subnetAuthHeaders(token)
regURL += "?aid=" + accountID
}
return nil, "", errors.New("invalid license")
regToken, err := GenerateRegToken(regInfo)
if err != nil {
return "", err
}
reqPayload := mc.ClusterRegistrationReq{Token: regToken}
resp, err := subnetPostReq(client, regURL, reqPayload, headers)
if err != nil {
return "", err
}
subnetAPIKey := gjson.Parse(resp).Get("api_key").String()
if subnetAPIKey != "" {
return subnetAPIKey, nil
}
return "", errors.New("subnet api key not found")
}

View File

@@ -1,332 +0,0 @@
// This file is part of MinIO Kubernetes Cloud
// 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 subnet
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"strings"
"testing"
"errors"
)
var HTTPGetMock func(url string) (resp *http.Response, err error)
var HTTPPostMock func(url, contentType string, body io.Reader) (resp *http.Response, err error)
var HTTPDoMock func(req *http.Request) (*http.Response, error)
type HTTPClientMock struct {
Client *http.Client
}
func (c *HTTPClientMock) Get(url string) (resp *http.Response, err error) {
return HTTPGetMock(url)
}
func (c *HTTPClientMock) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
return HTTPPostMock(url, contentType, body)
}
func (c *HTTPClientMock) Do(req *http.Request) (*http.Response, error) {
return HTTPDoMock(req)
}
func Test_getLicenseFromCredentials(t *testing.T) {
// HTTP Client mock
clientMock := HTTPClientMock{
Client: &http.Client{},
}
type args struct {
client HTTPClientMock
username string
password string
}
tests := []struct {
name string
args args
want string
wantErr bool
mockFunc func()
}{
{
name: "error when login against subnet",
args: args{
client: clientMock,
username: "invalid",
password: "invalid",
},
want: "",
wantErr: true,
mockFunc: func() {
HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) {
return nil, errors.New("something went wrong")
}
},
},
{
name: "error because of malformed subnet response",
args: args{
client: clientMock,
username: "invalid",
password: "invalid",
},
want: "",
wantErr: true,
mockFunc: func() {
HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) {
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("foo")))}, nil
}
},
},
{
name: "error when obtaining license from subnet",
args: args{
client: clientMock,
username: "valid",
password: "valid",
},
want: "",
wantErr: true,
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
}
HTTPDoMock = func(req *http.Request) (*http.Response, error) {
return nil, errors.New("something went wrong")
}
},
},
{
name: "error when obtaining license from subnet because of malformed response",
args: args{
client: clientMock,
username: "valid",
password: "valid",
},
want: "",
wantErr: true,
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
}
HTTPDoMock = func(req *http.Request) (*http.Response, error) {
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("foo")))}, nil
}
},
},
{
name: "license obtained successfully",
args: args{
client: clientMock,
username: "valid",
password: "valid",
},
want: license,
wantErr: false,
mockFunc: func() {
HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) {
// returning test jwt token
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{
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
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
got, err := getLicenseFromCredentials(&tt.args.client, tt.args.username, tt.args.password)
if (err != nil) != tt.wantErr {
t.Errorf("getLicenseFromCredentials() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("getLicenseFromCredentials() got = %v, want %v", got, tt.want)
}
})
}
}
func Test_downloadSubnetPublicKey(t *testing.T) {
// HTTP Client mock
clientMock := HTTPClientMock{
Client: &http.Client{},
}
type args struct {
client HTTPClientMock
}
tests := []struct {
name string
args args
want string
wantErr bool
mockFunc func()
}{
{
name: "error downloading public key",
args: args{
client: clientMock,
},
mockFunc: func() {
HTTPGetMock = func(url string) (resp *http.Response, err error) {
return nil, errors.New("something went wrong")
}
},
wantErr: true,
want: "",
},
{
name: "public key download successfully",
args: args{
client: clientMock,
},
mockFunc: func() {
HTTPGetMock = func(url string) (resp *http.Response, err error) {
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("foo")))}, nil
}
},
wantErr: false,
want: "foo",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
got, err := downloadSubnetPublicKey(&tt.args.client)
if (err != nil) != tt.wantErr {
t.Errorf("downloadSubnetPublicKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("downloadSubnetPublicKey() got = %v, want %v", got, tt.want)
}
})
}
}
func TestValidateLicense(t *testing.T) {
// HTTP Client mock
clientMock := HTTPClientMock{
Client: &http.Client{},
}
type args struct {
client HTTPClientMock
licenseKey string
email string
password string
}
tests := []struct {
name string
args args
wantLicense string
wantErr bool
mockFunc func()
}{
{
name: "error because nor license nor user or password was provided",
args: args{
client: clientMock,
licenseKey: "",
email: "",
password: "",
},
wantErr: true,
},
{
name: "error because could not get license from credentials",
args: args{
client: clientMock,
licenseKey: "",
email: "email",
password: "password",
},
wantErr: true,
mockFunc: func() {
HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) {
return nil, errors.New("something went wrong")
}
},
},
{
name: "error because invalid license",
args: args{
client: clientMock,
licenseKey: "invalid license",
email: "",
password: "",
},
wantErr: true,
mockFunc: func() {
HTTPGetMock = func(url string) (resp *http.Response, err error) {
return &http.Response{Body: ioutil.NopCloser(strings.NewReader(publicKeys[0]))}, nil
}
},
},
{
name: "license validated successfully",
args: args{
client: clientMock,
licenseKey: license,
email: "",
password: "",
},
wantErr: false,
mockFunc: func() {
HTTPGetMock = func(url string) (resp *http.Response, err error) {
return &http.Response{Body: ioutil.NopCloser(strings.NewReader(publicKeys[0]))}, nil
}
},
wantLicense: license,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
_, gotLicense, err := ValidateLicense(&tt.args.client, tt.args.licenseKey, tt.args.email, tt.args.password)
if !tt.wantErr {
t.Skip() // FIXME: fix all success cases
}
if (err != nil) != tt.wantErr {
t.Errorf("ValidateLicense() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotLicense != tt.wantLicense {
t.Errorf("ValidateLicense() gotLicense = %v, want %v", gotLicense, tt.wantLicense)
}
})
}
}

165
pkg/subnet/utils.go Normal file
View File

@@ -0,0 +1,165 @@
// This file is part of MinIO Kubernetes Cloud
// 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 subnet
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/minio/console/cluster"
"github.com/minio/madmin-go"
mc "github.com/minio/mc/cmd"
"github.com/minio/pkg/env"
)
const (
subnetRespBodyLimit = 1 << 20 // 1 MiB
)
func subnetBaseURL() string {
return env.Get(ConsoleSubnetURL, "https://subnet.min.io")
}
func subnetRegisterURL() string {
return subnetBaseURL() + "/api/cluster/register"
}
func subnetLoginURL() string {
return subnetBaseURL() + "/api/auth/login"
}
func subnetOrgsURL() string {
return subnetBaseURL() + "/api/auth/organizations"
}
func subnetMFAURL() string {
return subnetBaseURL() + "/api/auth/mfa-login"
}
func GenerateRegToken(clusterRegInfo mc.ClusterRegistrationInfo) (string, error) {
token, e := json.Marshal(clusterRegInfo)
if e != nil {
return "", e
}
return base64.StdEncoding.EncodeToString(token), nil
}
func subnetAuthHeaders(authToken string) map[string]string {
return map[string]string{"Authorization": "Bearer " + authToken}
}
func httpDo(client cluster.HTTPClientI, req *http.Request) (*http.Response, error) {
//if globalSubnetProxyURL != nil {
// client.Transport.(*http.Transport).Proxy = http.ProxyURL(globalSubnetProxyURL)
//}
return client.Do(req)
}
func subnetReqDo(client cluster.HTTPClientI, r *http.Request, headers map[string]string) (string, error) {
for k, v := range headers {
r.Header.Add(k, v)
}
ct := r.Header.Get("Content-Type")
if len(ct) == 0 {
r.Header.Add("Content-Type", "application/json")
}
resp, e := httpDo(client, r)
if e != nil {
return "", e
}
defer resp.Body.Close()
respBytes, e := ioutil.ReadAll(io.LimitReader(resp.Body, subnetRespBodyLimit))
if e != nil {
return "", e
}
respStr := string(respBytes)
if resp.StatusCode == http.StatusOK {
return respStr, nil
}
return respStr, fmt.Errorf("Request failed with code %d and error: %s", resp.StatusCode, respStr)
}
func subnetGetReq(client cluster.HTTPClientI, reqURL string, headers map[string]string) (string, error) {
r, e := http.NewRequest(http.MethodGet, reqURL, nil)
if e != nil {
return "", e
}
return subnetReqDo(client, r, headers)
}
func subnetPostReq(client cluster.HTTPClientI, reqURL string, payload interface{}, headers map[string]string) (string, error) {
body, e := json.Marshal(payload)
if e != nil {
return "", e
}
r, e := http.NewRequest(http.MethodPost, reqURL, bytes.NewReader(body))
if e != nil {
return "", e
}
return subnetReqDo(client, r, headers)
}
func GetClusterRegInfo(admInfo madmin.InfoMessage) mc.ClusterRegistrationInfo {
noOfPools := 1
noOfDrives := 0
for _, srvr := range admInfo.Servers {
if srvr.PoolNumber > noOfPools {
noOfPools = srvr.PoolNumber
}
noOfDrives += len(srvr.Disks)
}
totalSpace, usedSpace := getDriveSpaceInfo(admInfo)
return mc.ClusterRegistrationInfo{
DeploymentID: admInfo.DeploymentID,
ClusterName: admInfo.DeploymentID,
UsedCapacity: admInfo.Usage.Size,
Info: mc.ClusterInfo{
MinioVersion: admInfo.Servers[0].Version,
NoOfServerPools: noOfPools,
NoOfServers: len(admInfo.Servers),
NoOfDrives: noOfDrives,
TotalDriveSpace: totalSpace,
UsedDriveSpace: usedSpace,
NoOfBuckets: admInfo.Buckets.Count,
NoOfObjects: admInfo.Objects.Count,
},
}
}
func getDriveSpaceInfo(admInfo madmin.InfoMessage) (uint64, uint64) {
total := uint64(0)
used := uint64(0)
for _, srvr := range admInfo.Servers {
for _, d := range srvr.Disks {
total += d.TotalSpace
used += d.UsedSpace
}
}
return total, used
}

View File

@@ -386,10 +386,17 @@ export const IAM_PAGES_PERMISSIONS = {
IAM_SCOPES.ADMIN_SERVER_INFO,
],
[IAM_PAGES.TOOLS_SPEEDTEST]: [IAM_SCOPES.ADMIN_HEALTH_INFO],
[IAM_PAGES.REGISTER_SUPPORT]: [IAM_SCOPES.ADMIN_HEALTH_INFO],
[IAM_PAGES.REGISTER_SUPPORT]: [
IAM_SCOPES.ADMIN_SERVER_INFO,
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
],
[IAM_PAGES.CALL_HOME]: [IAM_SCOPES.ADMIN_HEALTH_INFO],
[IAM_PAGES.PROFILE]: [IAM_SCOPES.ADMIN_HEALTH_INFO],
[IAM_PAGES.HEALTH]: [IAM_SCOPES.ADMIN_HEALTH_INFO],
[IAM_PAGES.LICENSE]: [
IAM_SCOPES.ADMIN_SERVER_INFO,
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
],
};
export const S3_ALL_RESOURCES = "arn:aws:s3:::*";

View File

@@ -0,0 +1,32 @@
import * as React from "react";
import { SVGProps } from "react";
const OfflineRegistrationBackIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
className={`min-icon`}
fill={"currentcolor"}
viewBox="0 0 256 256"
width="16.1"
height="13.5"
{...props}
>
<path data-name="Rect\xE1ngulo 1602" fill="none" d="M0 0h256v256H0z" />
<g fill="#2781b0">
<path
data-name="Trazado 7242"
d="m20.695 32.211 11.313-11.318 203.3 203.4-11.313 11.318Z"
/>
<path
data-name="Trazado 7243"
d="M19.371 106.631C6.694 118.186 0 133.962 0 152.26a61.725 61.725 0 0 0 20.253 46.312c12.578 11.424 29.547 17.714 47.778 17.714h114.108L55.275 89.429c-14.007 2.7-26.556 8.672-35.911 17.2Z"
/>
<path
data-name="Trazado 7244"
d="M238.286 203.889C249.875 194.662 256 180.961 256 164.264c0-30.939-24.23-47.692-48.894-51.341-3.258-20.595-12.03-38.216-25.568-51.249a76.817 76.817 0 0 0-53.589-21.459 73.336 73.336 0 0 0-41.553 12.506l151.47 151.492c.128-.107.285-.206.42-.313Z"
/>
</g>
</svg>
);
export default OfflineRegistrationBackIcon;

View File

@@ -0,0 +1,71 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import * as React from "react";
import { SVGProps } from "react";
const OfflineRegistrationIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
className={`min-icon`}
fill={"currentcolor"}
viewBox="0 0 256 256"
width="26.9"
height="26.9"
{...props}
>
<defs>
<clipPath id="Offline-Registration_svg__a">
<path
data-name="Rect\xE1ngulo 1604"
fill="none"
d="M0 0h256v199.086H0z"
/>
</clipPath>
</defs>
<path data-name="Rect\xE1ngulo 1602" fill="none" d="M0 0h256v256H0z" />
<g data-name="Grupo 2526">
<path
data-name="Rect\xE1ngulo 1603"
fill="#00142f"
d="m19.235 39.602 10.497-10.49L218.26 217.77l-10.497 10.49z"
/>
<g data-name="Grupo 2525">
<g
data-name="Grupo 2524"
clipPath="url(#Offline-Registration_svg__a)"
fill="#00142f"
transform="translate(0 29.146)"
>
<path
data-name="Trazado 7273"
d="m17.968 79.492.007.015a55.559 55.559 0 0 0-17.96 42.3 57.238 57.238 0 0 0 18.783 42.92 65.482 65.482 0 0 0 44.3 16.431h105.817L51.268 63.545a68.63 68.63 0 0 0-33.3 15.947"
/>
<path
data-name="Trazado 7274"
d="m222.825 99.169-.074.015h-.333l-.326-.03a22.226 22.226 0 0 1-9.028-2.8 4.017 4.017 0 0 0-.651-.3 3.823 3.823 0 0 0-.533.244 18.331 18.331 0 0 1-9.665 2.745 18.542 18.542 0 0 1-3.559-.348l-.955-.185-.866-.429a19.149 19.149 0 0 1-9.332-10 5.281 5.281 0 0 0-.3-.525 4.064 4.064 0 0 0-.474-.1 18.625 18.625 0 0 1-12.12-6.21l-.585-.666-.422-.792a19.8 19.8 0 0 1-1.843-13.35 6.256 6.256 0 0 0 .067-.9 4.811 4.811 0 0 0-.437-.511 19.647 19.647 0 0 1-6.209-12.306l-.089-.807.089-.8a19.526 19.526 0 0 1 5.21-11.211c-.644-.688-1.251-1.413-1.924-2.079a71.234 71.234 0 0 0-49.687-19.901 68.071 68.071 0 0 0-38.525 11.6l140.41 140.462c.118-.1.266-.192.392-.289v-.007a45.043 45.043 0 0 0 16.428-36.742c0-14.652-5.876-25.849-14.66-33.774"
/>
<path
data-name="Trazado 7275"
d="M255.963 51.509a15.953 15.953 0 0 0-5.121-10.049 8.872 8.872 0 0 1-1.48-1.991 9.8 9.8 0 0 1 .059-2.753 16.071 16.071 0 0 0-1.487-10.967l-.207-.385-.3-.333a14.943 14.943 0 0 0-9.82-5 8.149 8.149 0 0 1-2.316-.7 8.935 8.935 0 0 1-1.359-2.096 15.448 15.448 0 0 0-7.563-8.192l-.437-.215-.481-.1a14.62 14.62 0 0 0-10.633 1.965 8.262 8.262 0 0 1-2.405.888 8.3 8.3 0 0 1-2.401-.888 14.639 14.639 0 0 0-10.638-1.961l-.474.1-.444.215a15.505 15.505 0 0 0-7.563 8.192 8.821 8.821 0 0 1-1.369 2.109 8.149 8.149 0 0 1-2.316.7 14.96 14.96 0 0 0-9.82 5l-.3.333-.207.392a16.144 16.144 0 0 0-1.48 10.9 9.96 9.96 0 0 1 .059 2.775 9.2 9.2 0 0 1-1.487 2.013 15.9 15.9 0 0 0-5.103 10.048l-.044.4.044.4a15.934 15.934 0 0 0 5.106 10.057 9.031 9.031 0 0 1 1.487 1.983 9.861 9.861 0 0 1-.059 2.76 16.112 16.112 0 0 0 1.48 10.952l.207.392.3.333a14.96 14.96 0 0 0 9.82 5 8.149 8.149 0 0 1 2.316.7 9.082 9.082 0 0 1 1.376 2.109 15.446 15.446 0 0 0 7.563 8.162l.437.215.474.089a14.639 14.639 0 0 0 10.635-1.96 8.262 8.262 0 0 1 2.405-.888 8.533 8.533 0 0 1 2.472.925 18.627 18.627 0 0 0 7.526 2.331l.155.015h.185a9.794 9.794 0 0 0 3.16-.525l.229-.074.215-.111a15.421 15.421 0 0 0 7.57-8.185 9.2 9.2 0 0 1 1.376-2.1 8.03 8.03 0 0 1 2.309-.7 14.943 14.943 0 0 0 9.82-5l.3-.326.2-.392a15.981 15.981 0 0 0 1.487-10.982 10.04 10.04 0 0 1-.059-2.745 8.957 8.957 0 0 1 1.48-1.976 15.953 15.953 0 0 0 5.121-10.049l.044-.407Zm-47.751 15.655-15.387-16.081 5.454-5.683 9.933 10.353 18.342-19.108 5.458 5.706Z"
/>
</g>
</g>
</g>
</svg>
);
export default OfflineRegistrationIcon;

View File

@@ -0,0 +1,88 @@
import * as React from "react";
import { SVGProps } from "react";
const OnlineRegistrationBackIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
className={`min-icon`}
fill={"currentcolor"}
viewBox="0 0 256 256"
width="16.52"
height="12.86"
{...props}
>
<defs>
<clipPath id="online-registration-back_svg__a">
<path
data-name="Rect\xE1ngulo 1600"
fill="#2781b0"
d="M0 0h256v199.269H0z"
/>
</clipPath>
</defs>
<path data-name="Rect\xE1ngulo 1602" fill="none" d="M0 0h256v256H0z" />
<g data-name="Grupo 2521">
<g
data-name="Grupo 2520"
clipPath="url(#online-registration-back_svg__a)"
fill="#2781b0"
transform="translate(0 22.634)"
>
<path
data-name="Trazado 7245"
d="M110.325 123.433a78.259 78.259 0 0 0 .768 10.936h13.5v-21.871h-13.5a78.271 78.271 0 0 0-.768 10.936Z"
/>
<path
data-name="Trazado 7246"
d="M112.411 105.696h12.187V85.56c-4.871 2.382-9.583 9.676-12.187 20.141"
/>
<path
data-name="Trazado 7247"
d="M124.599 161.316v-20.141h-12.188c2.6 10.464 7.316 17.761 12.187 20.141"
/>
<path
data-name="Trazado 7248"
d="M162.4 105.7a38.951 38.951 0 0 0-18.91-17.748 52.941 52.941 0 0 1 7.113 17.748Z"
/>
<path
data-name="Trazado 7249"
d="M103.53 123.433a85.92 85.92 0 0 1 .711-10.937H90.854a38.2 38.2 0 0 0 0 21.873h13.384a86.293 86.293 0 0 1-.711-10.936"
/>
<path
data-name="Trazado 7250"
d="M112.5 87.95a38.954 38.954 0 0 0-18.909 17.748h11.8a53.038 53.038 0 0 1 7.113-17.748"
/>
<path
data-name="Trazado 7251"
d="M93.597 141.173a38.956 38.956 0 0 0 18.909 17.748 52.942 52.942 0 0 1-7.113-17.748Z"
/>
<path
data-name="Trazado 7252"
d="M151.757 112.499a84.331 84.331 0 0 1 0 21.873h13.385a38.182 38.182 0 0 0 0-21.873Z"
/>
<path
data-name="Trazado 7253"
d="M143.491 158.922a38.962 38.962 0 0 0 18.91-17.748h-11.8a52.968 52.968 0 0 1-7.113 17.748"
/>
<path
data-name="Trazado 7254"
d="M192.789 69.359c.12-1.539.177-2.98.177-4.393a64.966 64.966 0 0 0-129.932 0c0 1.413.058 2.854.177 4.393a64.967 64.967 0 0 0 1.754 129.91h126.069a64.967 64.967 0 0 0 1.754-129.91Zm-21.947 69.376a3.373 3.373 0 0 1-.2.561 45.463 45.463 0 0 1-85.276 0 3.126 3.126 0 0 1-.2-.561 44.686 44.686 0 0 1 0-30.59 3.233 3.233 0 0 1 .2-.561 45.463 45.463 0 0 1 85.277 0 3.128 3.128 0 0 1 .2.561 44.711 44.711 0 0 1 0 30.59"
/>
<path
data-name="Trazado 7255"
d="M131.398 141.173v20.141c4.871-2.38 9.583-9.677 12.187-20.141Z"
/>
<path
data-name="Trazado 7256"
d="M131.398 85.557v20.141h12.187c-2.6-10.464-7.316-17.758-12.187-20.141"
/>
<path
data-name="Trazado 7257"
d="M145.671 123.433a78.26 78.26 0 0 0-.769-10.937h-13.5v21.872h13.5a78.262 78.262 0 0 0 .769-10.936Z"
/>
</g>
</g>
</svg>
);
export default OnlineRegistrationBackIcon;

View File

@@ -0,0 +1,109 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import * as React from "react";
import { SVGProps } from "react";
const OnlineRegistrationIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
className={`min-icon`}
fill={"currentcolor"}
viewBox="0 0 256 256"
width="32.12"
height="25"
{...props}
>
<defs>
<clipPath id="online-registration-icn_svg__a">
<path
data-name="Rect\xE1ngulo 1601"
fill="none"
d="M0 0h256v189.799H0z"
/>
</clipPath>
</defs>
<g data-name="Grupo 2523">
<g
data-name="Grupo 2522"
transform="translate(0 32.999)"
clipPath="url(#online-registration-icn_svg__a)"
fill="#00142f"
>
<path
data-name="Trazado 7258"
d="M105.956 117.2a75.071 75.071 0 0 0 .763 10.469h12.926v-20.938h-12.926a75.072 75.072 0 0 0-.763 10.469"
/>
<path
data-name="Trazado 7259"
d="M119.607 100.222V80.94a29.091 29.091 0 0 0-11.667 19.282Z"
/>
<path
data-name="Trazado 7260"
d="M119.614 153.467h.008v-19.282h-11.675a29.062 29.062 0 0 0 11.667 19.282"
/>
<path
data-name="Trazado 7261"
d="M155.805 100.221a37.276 37.276 0 0 0-18.1-16.993 50.754 50.754 0 0 1 6.807 16.993Z"
/>
<path
data-name="Trazado 7262"
d="M99.417 117.2h.034a81.388 81.388 0 0 1 .679-10.469H87.323a36.628 36.628 0 0 0 0 20.938h12.773a82.781 82.781 0 0 1-.679-10.469"
/>
<path
data-name="Trazado 7263"
d="M108.039 83.229a37.31 37.31 0 0 0-18.099 16.992h11.293a50.754 50.754 0 0 1 6.806-16.993"
/>
<path
data-name="Trazado 7264"
d="M89.947 134.178a37.31 37.31 0 0 0 18.1 16.993 50.754 50.754 0 0 1-6.806-16.993Z"
/>
<path
data-name="Trazado 7265"
d="M145.603 106.731a80.807 80.807 0 0 1 0 20.938h12.811a36.5 36.5 0 0 0 0-20.938Z"
/>
<path
data-name="Trazado 7266"
d="M137.706 151.171a37.31 37.31 0 0 0 18.1-16.993h-11.294a50.754 50.754 0 0 1-6.806 16.993"
/>
<path
data-name="Trazado 7267"
d="m230.957 100.848-.443.221-.473.16a13.816 13.816 0 0 1-4.494.748v-.023h-.671a22.917 22.917 0 0 1-9.309-2.884 4.907 4.907 0 0 0-.671-.313q-.275.114-.549.252a18.913 18.913 0 0 1-13.636 2.472l-.992-.2-.9-.443a19.76 19.76 0 0 1-9.619-10.306 5.449 5.449 0 0 0-.305-.542 5.087 5.087 0 0 0-.488-.107 19.2 19.2 0 0 1-12.5-6.4l-.61-.687-.427-.809a20.457 20.457 0 0 1-1.908-13.735 5.126 5.126 0 0 0 .046-.969 5.773 5.773 0 0 0-.443-.526 20.249 20.249 0 0 1-6.379-12.682l-.092-.832.092-.832a20.268 20.268 0 0 1 6.394-12.682 4.831 4.831 0 0 0 .427-.549 5.1 5.1 0 0 0-.069-.961 20.376 20.376 0 0 1 .992-11.552A62.2 62.2 0 0 0 60.692 61.216c0 1.351.053 2.732.168 4.2a62.2 62.2 0 0 0 1.678 124.381h120.683a62.1 62.1 0 0 0 53.886-93.717 19.522 19.522 0 0 1-6.15 4.769m-67.064 30.957a3.466 3.466 0 0 1-.2.534 43.494 43.494 0 0 1-81.645 0 2.641 2.641 0 0 1-.2-.534 42.738 42.738 0 0 1 0-29.285 2.641 2.641 0 0 1 .2-.534 43.494 43.494 0 0 1 81.645 0 2.642 2.642 0 0 1 .2.534 42.827 42.827 0 0 1 0 29.285"
/>
<path
data-name="Trazado 7268"
d="M126.131 134.178v19.282a29.062 29.062 0 0 0 11.67-19.282Z"
/>
<path
data-name="Trazado 7269"
d="M126.131 80.94v19.282h11.67a29.091 29.091 0 0 0-11.67-19.282"
/>
<path data-name="Trazado 7270" d="M139.79 117.194Z" />
<path
data-name="Trazado 7271"
d="M139.789 117.2a75.154 75.154 0 0 0-.763-10.469H126.1v20.93h12.926a74.96 74.96 0 0 0 .763-10.461"
/>
<path
data-name="Trazado 7272"
d="m251.907 61.322-.023-.008a12.677 12.677 0 0 0 4.113-8.02 12.677 12.677 0 0 0-4.113-8.02 12.75 12.75 0 0 1-2.564-3.632 13.77 13.77 0 0 1 0-4.746 12.755 12.755 0 0 0-1.167-8.783 11.643 11.643 0 0 0-7.714-3.884 12.384 12.384 0 0 1-4.3-1.442 13.206 13.206 0 0 1-2.564-3.739 12.157 12.157 0 0 0-5.99-6.532 11.279 11.279 0 0 0-8.279 1.526 12.67 12.67 0 0 1-4.419 1.528 12.67 12.67 0 0 1-4.426-1.526 11.279 11.279 0 0 0-8.279-1.526 12.2 12.2 0 0 0-5.975 6.524 13.175 13.175 0 0 1-2.587 3.762 12.346 12.346 0 0 1-4.281 1.435 11.643 11.643 0 0 0-7.714 3.884 12.757 12.757 0 0 0-1.152 8.737 14.158 14.158 0 0 1 0 4.746 13.16 13.16 0 0 1-2.587 3.67 12.632 12.632 0 0 0-4.105 8.027 12.6 12.6 0 0 0 4.113 8.012 13.135 13.135 0 0 1 2.587 3.632 14.2 14.2 0 0 1 0 4.754 12.8 12.8 0 0 0 1.16 8.783 11.643 11.643 0 0 0 7.714 3.884 12.346 12.346 0 0 1 4.281 1.435 13.246 13.246 0 0 1 2.587 3.754 12.165 12.165 0 0 0 5.975 6.493 11.285 11.285 0 0 0 8.279-1.526 12.67 12.67 0 0 1 4.43-1.527 12.67 12.67 0 0 1 4.426 1.526 15.413 15.413 0 0 0 6.219 1.923 6.5 6.5 0 0 0 2.053-.336 12.155 12.155 0 0 0 5.975-6.516 13.246 13.246 0 0 1 2.587-3.754 12.346 12.346 0 0 1 4.281-1.435 11.643 11.643 0 0 0 7.714-3.884 12.717 12.717 0 0 0 1.167-8.828 14.158 14.158 0 0 1 0-4.746 12.834 12.834 0 0 1 2.587-3.624m-41.363 7.706L194.689 52.44l5.631-5.883 10.233 10.683 18.931-19.679 5.631 5.883Z"
/>
</g>
</g>
<path data-name="Rect\xE1ngulo 1602" fill="none" d="M0 0h256v256H0z" />
</svg>
);
export default OnlineRegistrationIcon;

View File

@@ -0,0 +1,44 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import * as React from "react";
import { SVGProps } from "react";
const VerifiedIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={20}
height={20}
className={`min-icon`}
fill={"currentcolor"}
{...props}
>
<defs>
<clipPath id="registration-icon_svg__a">
<path data-name="Rect\xE1ngulo 1593" fill="#4ccb92" d="M0 0h20v20H0z" />
</clipPath>
</defs>
<g data-name="Grupo 2469" clipPath="url(#registration-icon_svg__a)">
<path
data-name="Trazado 7117"
d="M19.075 11.962a3.1 3.1 0 0 0 1.008-1.965 3.1 3.1 0 0 0-1.008-1.963 3.134 3.134 0 0 1-.633-.894 3.4 3.4 0 0 1 0-1.164 3.121 3.121 0 0 0-.286-2.154 2.856 2.856 0 0 0-1.892-.952 3.024 3.024 0 0 1-1.053-.353 3.232 3.232 0 0 1-.628-.917A2.982 2.982 0 0 0 13.118 0a2.77 2.77 0 0 0-2.029.383 3.079 3.079 0 0 1-1.085.368 3.079 3.079 0 0 1-1.085-.37A2.77 2.77 0 0 0 6.89-.002a2.99 2.99 0 0 0-1.465 1.599 3.236 3.236 0 0 1-.633.922 3.033 3.033 0 0 1-1.05.351 2.856 2.856 0 0 0-1.892.953 3.133 3.133 0 0 0-.284 2.142 3.448 3.448 0 0 1 0 1.164 3.216 3.216 0 0 1-.633.9A3.1 3.1 0 0 0-.075 9.996a3.1 3.1 0 0 0 1.008 1.965 3.246 3.246 0 0 1 .633.89 3.462 3.462 0 0 1 0 1.166 3.133 3.133 0 0 0 .284 2.154 2.856 2.856 0 0 0 1.892.952 3.033 3.033 0 0 1 1.05.351 3.234 3.234 0 0 1 .633.921 2.982 2.982 0 0 0 1.465 1.592 2.77 2.77 0 0 0 2.029-.383 3.076 3.076 0 0 1 1.085-.37 3.077 3.077 0 0 1 1.085.368 3.769 3.769 0 0 0 1.525.472 1.561 1.561 0 0 0 .5-.082 2.978 2.978 0 0 0 1.465-1.6 3.249 3.249 0 0 1 .633-.921 3.032 3.032 0 0 1 1.05-.351 2.856 2.856 0 0 0 1.892-.952 3.113 3.113 0 0 0 .284-2.157 3.445 3.445 0 0 1 0-1.164 3.16 3.16 0 0 1 .633-.889m-10.13 1.894-3.89-4.066 1.38-1.437 2.51 2.618 4.638-4.833 1.38 1.442Z"
fill="#4ccb92"
/>
</g>
</svg>
);
export default VerifiedIcon;

View File

@@ -144,12 +144,6 @@ export interface ChangeUserPasswordRequest {
newSecretKey: string;
}
export interface SubscriptionActivateRequest {
license: string;
email: string;
password: string;
}
export interface IRemoteBucket {
name: string;
accessKey: string;

View File

@@ -34,6 +34,7 @@ import {
tooltipHelper,
} from "../common/styleLibrary";
import HelpIcon from "../../../../../icons/HelpIcon";
import clsx from "clsx";
interface InputBoxProps {
label: string;
@@ -60,6 +61,7 @@ interface InputBoxProps {
noLabelMinWidth?: boolean;
pattern?: string;
autoFocus?: boolean;
className?: string;
}
const styles = (theme: Theme) =>
@@ -130,6 +132,7 @@ const InputBoxWrapper = ({
pattern = "",
autoFocus = false,
classes,
className = "",
}: InputBoxProps) => {
let inputProps: any = { "data-index": index, ...extraInputProps };
@@ -149,9 +152,10 @@ const InputBoxWrapper = ({
<React.Fragment>
<Grid
container
className={` ${
className={clsx(
className !== "" ? className : "",
error !== "" ? classes.errorInField : classes.inputBoxContainer
}`}
)}
>
{label !== "" && (
<InputLabel

View File

@@ -14,17 +14,24 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useState } from "react";
import React, { Fragment, useState } from "react";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { LinearProgress, TextField } from "@mui/material";
import { LinearProgress } from "@mui/material";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import { SubscriptionActivateRequest } from "../Buckets/types";
import {
SubnetLoginRequest,
SubnetLoginResponse,
SubnetLoginWithMFARequest,
SubnetOrganization,
SubnetRegisterRequest,
SubnetRegTokenResponse,
} from "./types";
import { setModalErrorSnackMessage } from "../../../actions";
import { ErrorResponseHandler } from "../../../common/types";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
@@ -33,23 +40,21 @@ import api from "../../../common/api";
import PersonOutlineOutlinedIcon from "@mui/icons-material/PersonOutlineOutlined";
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
import { formFieldStyles } from "../Common/FormComponents/common/styleLibrary";
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import Link from "@mui/material/Link";
const styles = (theme: Theme) =>
createStyles({
subnetLicenseKey: {
padding: "10px 10px 10px 0px",
borderRight: "1px solid rgba(0, 0, 0, 0.12)",
opacity: 0.5,
"&:hover": { opacity: 1 },
},
subnetLoginForm: {
padding: "10px 0px 10px 10px",
opacity: 0.5,
"&:hover": { opacity: 1 },
},
subnetLicenseKey: {},
subnetLoginForm: {},
licenseKeyField: {},
pageTitle: {
marginBottom: 20,
},
registrationMode: {
cursor: "pointer",
},
button: {
textTransform: "none",
fontSize: 15,
@@ -64,6 +69,7 @@ const styles = (theme: Theme) =>
fontWeight: 700,
marginLeft: 15,
},
...formFieldStyles,
...containerForHeader(theme.spacing(4)),
});
@@ -83,36 +89,288 @@ const ActivationModal = ({
const [license, setLicense] = useState<string>("");
const [subnetPassword, setSubnetPassword] = useState<string>("");
const [subnetEmail, setSubnetEmail] = useState<string>("");
const [subnetMFAToken, setSubnetMFAToken] = useState<string>("");
const [subnetOTP, setSubnetOTP] = useState<string>("");
const [subnetAccessToken, setSubnetAccessToken] = useState<string>("");
const [selectedSubnetOrganisation, setSelectedSubnetOrganisation] =
useState<string>("");
const [subnetRegToken, setSubnetRegToken] = useState<string>("");
const [subnetOrganizations, setSubnetOrganizations] = useState<
SubnetOrganization[]
>([]);
const [onlineActivation, setOnlineActivation] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
const activateProduct = () => {
const clearForm = () => {
setLicense("");
setSubnetPassword("");
setSubnetEmail("");
setSubnetMFAToken("");
setSubnetOTP("");
};
const fetchSubnetRegToken = () => {
if (loading || subnetRegToken) {
return;
}
setLoading(true);
api
.invoke("GET", "/api/v1/subnet/registration-token")
.then((resp: SubnetRegTokenResponse) => {
setLoading(false);
if (resp && resp.regToken) {
setSubnetRegToken(resp.regToken);
}
})
.catch((err: ErrorResponseHandler) => {
setLoading(false);
setModalErrorSnackMessage(err);
});
};
const subnetRegister = () => {
if (loading) {
return;
}
setLoading(true);
let request: SubscriptionActivateRequest = {
license: license,
email: subnetEmail,
password: subnetPassword,
if (subnetAccessToken && selectedSubnetOrganisation) {
const request: SubnetRegisterRequest = {
token: subnetAccessToken,
account_id: selectedSubnetOrganisation,
};
api
.invoke("POST", "/api/v1/subnet/register", request)
.then(() => {
setLoading(false);
clearForm();
closeModal();
})
.catch((err: ErrorResponseHandler) => {
setLoading(false);
setModalErrorSnackMessage(err);
});
}
};
const subnetLoginWithMFA = () => {
if (loading) {
return;
}
setLoading(true);
const request: SubnetLoginWithMFARequest = {
username: subnetEmail,
otp: subnetOTP,
mfa_token: subnetMFAToken,
};
api
.invoke("POST", "/api/v1/subscription/validate", request)
.then(() => {
.invoke("POST", "/api/v1/subnet/login/mfa", request)
.then((resp: SubnetLoginResponse) => {
setLoading(false);
setLicense("");
setSubnetPassword("");
setSubnetEmail("");
closeModal();
if (resp && resp.access_token && resp.organizations.length > 0) {
setSubnetAccessToken(resp.access_token);
setSubnetOrganizations(resp.organizations);
setSelectedSubnetOrganisation(
resp.organizations[0].accountId.toString()
);
}
})
.catch((err: ErrorResponseHandler) => {
setLoading(false);
setLicense("");
setSubnetPassword("");
setSubnetEmail("");
setSubnetOTP("");
setModalErrorSnackMessage(err);
});
};
const subnetLogin = () => {
if (loading) {
return;
}
setLoading(true);
let request: SubnetLoginRequest = {
username: subnetEmail,
password: subnetPassword,
apiKey: license,
};
api
.invoke("POST", "/api/v1/subnet/login", request)
.then((resp: SubnetLoginResponse) => {
setLoading(false);
if (resp && resp.registered) {
clearForm();
closeModal();
} else if (resp && resp.mfa_token) {
setSubnetMFAToken(resp.mfa_token);
} else if (resp && resp.access_token && resp.organizations.length > 0) {
setSubnetAccessToken(resp.access_token);
setSubnetOrganizations(resp.organizations);
setSelectedSubnetOrganisation(
resp.organizations[0].accountId.toString()
);
}
})
.catch((err: ErrorResponseHandler) => {
setLoading(false);
clearForm();
setModalErrorSnackMessage(err);
});
};
let clusterRegistrationForm: JSX.Element;
if (subnetAccessToken && subnetOrganizations.length > 0) {
clusterRegistrationForm = (
<Fragment>
<Grid item xs={12}>
<Typography variant="subtitle2" gutterBottom component="div">
Register MinIO cluster
</Typography>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<RadioGroupSelector
tooltip={"Please choose the organization for this cluster."}
currentSelection={selectedSubnetOrganisation}
id="subnet-organisation"
name="subnet-organisation"
label="Select an Organisation"
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
setSelectedSubnetOrganisation(e.target.value as string);
}}
selectorOptions={subnetOrganizations.map((organisation) => ({
value: organisation.accountId.toString(),
label: organisation.company,
}))}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<Button
className={classes.button}
color="primary"
onClick={() => subnetRegister()}
disabled={loading || subnetAccessToken.trim().length === 0}
variant="contained"
>
Register
</Button>
</Grid>
</Fragment>
);
} else if (subnetMFAToken) {
clusterRegistrationForm = (
<Fragment>
<Grid item xs={12}>
<Typography variant="subtitle2" gutterBottom component="div">
Two-Factor Authentication
</Typography>
<Typography variant="caption" display="block" gutterBottom>
Please enter the 6-digit verification code that was sent to your
email address. This code will be valid for 5 minutes.
</Typography>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
overlayIcon={<LockOutlinedIcon />}
id="subnet-otp"
name="subnet-otp"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setSubnetOTP(event.target.value);
}}
placeholder=""
label=""
type="text"
value={subnetOTP}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<Button
color="primary"
onClick={() => subnetLoginWithMFA()}
disabled={
loading ||
subnetOTP.trim().length === 0 ||
subnetMFAToken.trim().length === 0
}
variant="contained"
>
Verify
</Button>
</Grid>
</Fragment>
);
} else {
clusterRegistrationForm = (
<Fragment>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
overlayIcon={<PersonOutlineOutlinedIcon />}
id="subnet-email"
name="subnet-email"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setSubnetEmail(event.target.value);
}}
placeholder="email"
label=""
type="text"
value={subnetEmail}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
overlayIcon={<LockOutlinedIcon />}
id="subnet-password"
name="subnet-password"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setSubnetPassword(event.target.value);
}}
placeholder="password"
label=""
type="password"
value={subnetPassword}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<Button
color="primary"
onClick={() => subnetLogin()}
disabled={
loading ||
subnetEmail.trim().length === 0 ||
subnetPassword.trim().length === 0
}
variant="contained"
>
Login
</Button>
<Button
className={classes.buttonSignup}
color="primary"
target="_blank"
rel="noopener noreferrer"
href="#"
onClick={(e) => {
e.preventDefault();
window.open("https://min.io/pricing", "_blank");
}}
variant="outlined"
>
Sign Up
</Button>
</Grid>
<Typography variant="caption" display="block" gutterBottom>
<Link
className={classes.registrationMode}
color="inherit"
onClick={() => {
fetchSubnetRegToken();
setOnlineActivation(false);
}}
>
Offline Activation
</Link>
</Typography>
</Fragment>
);
}
return open ? (
<ModalWrapper
title=""
@@ -132,97 +390,83 @@ const ActivationModal = ({
Activate SUBNET License
</Typography>
</Grid>
<Grid item className={classes.subnetLicenseKey} xs={6}>
<Grid item xs={12}>
<Typography variant="caption" display="block" gutterBottom>
Enter your license key here
</Typography>
{onlineActivation ? (
<Grid item className={classes.subnetLoginForm} xs={12}>
<Grid container>{clusterRegistrationForm}</Grid>
</Grid>
<TextField
id="license-key"
placeholder=""
multiline
rows={3}
value={license}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setLicense(event.target.value)
}
fullWidth
className={classes.licenseKeyField}
variant="outlined"
/>
<br />
<br />
<Button
variant="contained"
color="primary"
onClick={() => activateProduct()}
disabled={loading || license.trim().length === 0}
>
Activate
</Button>
</Grid>
<Grid item className={classes.subnetLoginForm} xs={6}>
<Grid container>
<Grid item xs={12}>
) : (
<Grid item className={classes.subnetLicenseKey} xs={12}>
<Typography variant="caption" display="block" gutterBottom>
Step 1: Copy the following registration token
</Typography>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
overlayIcon={<PersonOutlineOutlinedIcon />}
id="subnet-email"
name="subnet-email"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setSubnetEmail(event.target.value);
}}
placeholder="email"
onChange={() => {}}
id="registration-token"
name="registration-token"
placeholder=""
label=""
type="text"
value={subnetEmail}
value={subnetRegToken}
disabled
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
overlayIcon={<LockOutlinedIcon />}
id="subnet-password"
name="subnet-password"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setSubnetPassword(event.target.value);
}}
placeholder="password"
label=""
type="password"
value={subnetPassword}
/>
</Grid>
<Grid item xs={12}>
<Grid item xs={12} className={classes.formFieldRow}>
<Button
className={classes.button}
color="primary"
onClick={() => activateProduct()}
disabled={
loading ||
subnetEmail.trim().length === 0 ||
subnetPassword.trim().length === 0
}
variant="contained"
color="primary"
onClick={() => navigator.clipboard.writeText(subnetRegToken)}
>
Copy
</Button>
</Grid>
<Typography variant="caption" display="block" gutterBottom>
Step 2: Use the previous token to register your cluster at:{" "}
<Link
color="inherit"
href="https://subnet.min.io/cluster/register"
target="_blank"
>
https://subnet.min.io/cluster/register
</Link>
</Typography>
<Typography variant="caption" display="block" gutterBottom>
Step 3: Enter the API key generated by SUBNET
</Typography>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
value={license}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setLicense(event.target.value)
}
id="api-key"
name="api-key"
placeholder=""
label=""
type="text"
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<Button
variant="contained"
color="primary"
onClick={() => subnetLogin()}
disabled={loading || license.trim().length === 0}
>
Activate
</Button>
<Button
className={classes.buttonSignup}
color="primary"
target="_blank"
rel="noopener noreferrer"
href="#"
onClick={(e) => {
e.preventDefault();
window.open("https://min.io/pricing", "_blank");
}}
variant="outlined"
>
Sign Up
</Button>
</Grid>
<Typography variant="caption" display="block" gutterBottom>
<Link
className={classes.registrationMode}
color="inherit"
onClick={() => setOnlineActivation(true)}
>
Online Activation
</Link>
</Typography>
</Grid>
</Grid>
)}
</Grid>
{loading && (
<Grid item xs={12}>

View File

@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
@@ -26,7 +26,7 @@ import Button from "@mui/material/Button";
import Moment from "react-moment";
import Typography from "@mui/material/Typography";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import { LicenseInfo } from "./types";
import { SubnetInfo } from "./types";
import { AppState } from "../../../store";
import { niceBytes } from "../../../common/utils";
import { ErrorResponseHandler } from "../../../common/types";
@@ -37,6 +37,12 @@ import ActivationModal from "./ActivationModal";
import LicenseModal from "./LicenseModal";
import api from "../../../common/api";
import { LicenseIcon } from "../../../icons";
import { hasPermission } from "../../../common/SecureComponent/SecureComponent";
import {
CONSOLE_UI_RESOURCE,
IAM_PAGES,
IAM_PAGES_PERMISSIONS,
} from "../../../common/SecureComponent/permissions";
const mapState = (state: AppState) => ({
operatorMode: state.system.operatorMode,
@@ -55,7 +61,6 @@ const styles = (theme: Theme) =>
fontWeight: "bold",
"& ul": {
listStyleType: "square",
// listStyleType: "none",
"& li": {
float: "left",
fontSize: 14,
@@ -301,36 +306,64 @@ interface ILicenseProps {
}
const License = ({ classes, operatorMode }: ILicenseProps) => {
const [activateProductModal, setActivateProductModal] =
useState<boolean>(false);
const [licenseModal, setLicenseModal] = useState<boolean>(false);
const [licenseInfo, setLicenseInfo] = useState<SubnetInfo>();
const [currentPlanID, setCurrentPlanID] = useState<number>(0);
const [loadingLicenseInfo, setLoadingLicenseInfo] = useState<boolean>(false);
const [initialLicenseLoading, setInitialLicenseLoading] =
useState<boolean>(true);
const [loadingRefreshLicense, setLoadingRefreshLicense] =
useState<boolean>(false);
const getSubnetInfo = hasPermission(
CONSOLE_UI_RESOURCE,
IAM_PAGES_PERMISSIONS[IAM_PAGES.LICENSE],
true
);
const closeModalAndFetchLicenseInfo = () => {
setActivateProductModal(false);
fetchLicenseInfo();
};
const fetchLicenseInfo = () => {
setLoadingLicenseInfo(true);
api
.invoke("GET", `/api/v1/subscription/info`)
.then((res: LicenseInfo) => {
if (res) {
if (res.plan === "STANDARD") {
setCurrentPlanID(1);
} else if (res.plan === "ENTERPRISE") {
setCurrentPlanID(2);
} else {
setCurrentPlanID(1);
const fetchLicenseInfo = useCallback(() => {
if (loadingLicenseInfo) {
return;
}
if (getSubnetInfo) {
setLoadingLicenseInfo(true);
api
.invoke("GET", `/api/v1/subnet/info`)
.then((res: SubnetInfo) => {
if (res) {
if (res.plan === "STANDARD") {
setCurrentPlanID(1);
} else if (res.plan === "ENTERPRISE") {
setCurrentPlanID(2);
} else {
setCurrentPlanID(1);
}
setLicenseInfo(res);
}
setLicenseInfo(res);
}
setLoadingLicenseInfo(false);
})
.catch(() => {
setLoadingLicenseInfo(false);
});
};
setLoadingLicenseInfo(false);
})
.catch(() => {
setLoadingLicenseInfo(false);
});
} else {
setLoadingLicenseInfo(false);
}
}, [loadingLicenseInfo, getSubnetInfo]);
const refreshLicense = () => {
setLoadingRefreshLicense(true);
api
.invoke("POST", `/api/v1/subscription/refresh`, {})
.then((res: LicenseInfo) => {
.then((res: SubnetInfo) => {
if (res) {
if (res.plan === "STANDARD") {
setCurrentPlanID(1);
@@ -348,20 +381,12 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
});
};
const [activateProductModal, setActivateProductModal] =
useState<boolean>(false);
const [licenseModal, setLicenseModal] = useState<boolean>(false);
const [licenseInfo, setLicenseInfo] = useState<LicenseInfo>();
const [currentPlanID, setCurrentPlanID] = useState<number>(0);
const [loadingLicenseInfo, setLoadingLicenseInfo] = useState<boolean>(true);
const [loadingRefreshLicense, setLoadingRefreshLicense] =
useState<boolean>(false);
useEffect(() => {
fetchLicenseInfo();
}, []);
if (initialLicenseLoading) {
fetchLicenseInfo();
setInitialLicenseLoading(false);
}
}, [fetchLicenseInfo, initialLicenseLoading, setInitialLicenseLoading]);
if (loadingLicenseInfo) {
return (
@@ -653,14 +678,8 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
<br />
</Grid>
<Grid item xs={12}>
<b>Pricing</b>
Are you already a customer?
</Grid>
<Grid item xs={12}>
The MinIO Subscription Network provides exclusive benefits across
licensing, operations and support. See the pricing table below for
more information.
</Grid>
<Grid item xs={12}>
<br />
</Grid>
@@ -669,12 +688,10 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
</Grid>
<Grid item xs={12} className={clsx(classes.planItemsPadding)}>
<Grid container>
{operatorMode ? (
<ActivationModal
open={activateProductModal}
closeModal={() => closeModalAndFetchLicenseInfo()}
/>
) : null}
<ActivationModal
open={activateProductModal}
closeModal={() => closeModalAndFetchLicenseInfo()}
/>
<Grid container item xs={12} className={classes.tableContainer}>
<Grid container item xs={12}>
<Grid item xs={3} className={classes.detailsContainer} />
@@ -892,8 +909,7 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
: button.text}
</Button>
</Grid>
{operatorMode &&
button.text === "Subscribe" &&
{button.text === "Subscribe" &&
!(
licenseInfo &&
licenseInfo.plan.toLowerCase() ===
@@ -907,7 +923,7 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
setActivateProductModal(true);
}}
>
Activate
Register
</button>
</Grid>
)}

View File

@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
export interface LicenseInfo {
export interface SubnetInfo {
account_id: number;
email: string;
expires_at: string;
@@ -22,3 +22,40 @@ export interface LicenseInfo {
storage_capacity: number;
organization: string;
}
export interface SubnetLoginRequest {
username?: string;
password?: string;
apiKey?: string;
}
export interface SubnetRegisterRequest {
token: string;
account_id: string;
}
export interface SubnetOrganization {
userId: number;
accountId: number;
subscriptionStatus: string;
isAccountOwner: boolean;
shortName: string;
company: string;
}
export interface SubnetLoginResponse {
registered: boolean;
mfa_token: string;
access_token: string;
organizations: SubnetOrganization[];
}
export interface SubnetLoginWithMFARequest {
username: string;
otp: string;
mfa_token: string;
}
export interface SubnetRegTokenResponse {
regToken: string;
}

View File

@@ -135,14 +135,14 @@ export const planButtons = [
{
id: 1,
text: "Subscribe",
text2: "Upgrade",
text2: "Sign up",
link: "https://subnet.min.io/subscription",
plan: "standard",
},
{
id: 2,
text: "Subscribe",
text2: "Upgrade",
text2: "Sign up",
link: "https://subnet.min.io/subscription",
plan: "enterprise",
},

View File

@@ -0,0 +1,618 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import {
actionsTray,
searchField,
containerForHeader,
} from "../Common/FormComponents/common/styleLibrary";
import withStyles from "@mui/styles/withStyles";
import { Button, Grid, Link, Typography } from "@mui/material";
import PageHeader from "../Common/PageHeader/PageHeader";
import PageLayout from "../Common/Layout/PageLayout";
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { CopyIcon, UsersIcon } from "../../../icons";
import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import OnlineRegistrationIcon from "../../../icons/OnlineRegistrationIcon";
import OfflineRegistrationBackIcon from "../../../icons/OfflineRegistrationBackIcon";
import OfflineRegistrationIcon from "../../../icons/OfflineRegistrationIcon";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import clsx from "clsx";
import OnlineRegistrationBackIcon from "../../../icons/OnlineRegistrationBackIcon";
import api from "../../../common/api";
import {
SubnetInfo,
SubnetLoginRequest,
SubnetLoginResponse,
SubnetLoginWithMFARequest,
SubnetOrganization,
SubnetRegisterRequest,
SubnetRegTokenResponse,
} from "../License/types";
import { ErrorResponseHandler } from "../../../common/types";
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
import SelectWrapper from "../Common/FormComponents/SelectWrapper/SelectWrapper";
import { hasPermission } from "../../../common/SecureComponent/SecureComponent";
import {
CONSOLE_UI_RESOURCE,
IAM_PAGES,
IAM_PAGES_PERMISSIONS,
} from "../../../common/SecureComponent/permissions";
import VerifiedIcon from "../../../icons/VerifiedIcon";
import { connect } from "react-redux";
import { setErrorSnackMessage } from "../../../actions";
interface IRegister {
classes: any;
displayErrorMessage: typeof setErrorSnackMessage;
}
const styles = (theme: Theme) =>
createStyles({
loading: {
paddingTop: 8,
paddingLeft: 40,
},
buttons: {
justifyContent: "flex-start",
gap: 20,
},
localMessage: {
fontSize: 24,
color: "#07193E",
fontWeight: "bold",
textAlign: "center",
marginBottom: 10,
},
registerActivationIcon: {
color: theme.palette.primary.main,
fontSize: 16,
fontWeight: "bold",
marginBottom: 20,
"& .min-icon": {
width: 32.12,
height: 25,
marginRight: 10,
verticalAlign: "middle",
},
},
registerActivationMode: {
textAlign: "right",
"& a": {
cursor: "pointer",
},
},
subnetDescription: {
textAlign: "left",
Font: "normal normal normal 14px/17px Lato",
letterSpacing: 0,
color: "#000000",
"& span": {
fontWeight: "bold",
},
},
subnetLoginInputBoxWrapper: {
paddingRight: 20,
},
registeredStatus: {
border: "1px solid #E2E2E2",
padding: "24px 24px 24px 24px",
borderRadius: 2,
marginBottom: 25,
backgroundColor: "#FBFAFA",
"& .min-icon": {
width: 20,
height: 20,
marginLeft: 48,
marginRight: 13,
verticalAlign: "middle",
marginTop: -3,
},
"& span": {
fontWeight: "bold",
},
},
offlineRegisterButton: {
textAlign: "right",
paddingRight: 20,
},
copyInputBox: {
"& button": {
border: "1px solid #5E5E5E",
borderRadius: 2,
},
},
link: {
color: "#2781B0",
},
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
});
const Register = ({ classes, displayErrorMessage }: IRegister) => {
const [license, setLicense] = useState<string>("");
const [subnetPassword, setSubnetPassword] = useState<string>("");
const [subnetEmail, setSubnetEmail] = useState<string>("");
const [subnetMFAToken, setSubnetMFAToken] = useState<string>("");
const [subnetOTP, setSubnetOTP] = useState<string>("");
const [subnetAccessToken, setSubnetAccessToken] = useState<string>("");
const [selectedSubnetOrganization, setSelectedSubnetOrganization] =
useState<string>("");
const [subnetRegToken, setSubnetRegToken] = useState<string>("");
const [subnetOrganizations, setSubnetOrganizations] = useState<
SubnetOrganization[]
>([]);
const [showPassword, setShowPassword] = useState<boolean>(false);
const [onlineActivation, setOnlineActivation] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
const [loadingLicenseInfo, setLoadingLicenseInfo] = useState<boolean>(false);
const [clusterRegistered, setClusterRegistered] = useState<boolean>(false);
const [initialLicenseLoading, setInitialLicenseLoading] =
useState<boolean>(true);
const clearForm = () => {
setSubnetAccessToken("");
setSelectedSubnetOrganization("");
setSubnetRegToken("");
setShowPassword(false);
setOnlineActivation(true);
setSubnetOrganizations([]);
setLicense("");
setSubnetPassword("");
setSubnetEmail("");
setSubnetMFAToken("");
setSubnetOTP("");
};
const getSubnetInfo = hasPermission(
CONSOLE_UI_RESOURCE,
IAM_PAGES_PERMISSIONS[IAM_PAGES.LICENSE],
true
);
const fetchLicenseInfo = useCallback(() => {
if (loadingLicenseInfo) {
return;
}
if (getSubnetInfo) {
setLoadingLicenseInfo(true);
api
.invoke("GET", `/api/v1/subnet/info`)
.then((res: SubnetInfo) => {
setClusterRegistered(true);
setLoadingLicenseInfo(false);
})
.catch((err: ErrorResponseHandler) => {
displayErrorMessage(err);
setClusterRegistered(false);
setLoadingLicenseInfo(false);
});
} else {
setLoadingLicenseInfo(false);
}
}, [loadingLicenseInfo, getSubnetInfo, displayErrorMessage]);
const fetchSubnetRegToken = () => {
if (loading || subnetRegToken) {
return;
}
setLoading(true);
api
.invoke("GET", "/api/v1/subnet/registration-token")
.then((resp: SubnetRegTokenResponse) => {
setLoading(false);
if (resp && resp.regToken) {
setSubnetRegToken(resp.regToken);
}
})
.catch((err: ErrorResponseHandler) => {
displayErrorMessage(err);
setLoading(false);
});
};
const callRegister = (token: string, account_id: string) => {
const request: SubnetRegisterRequest = {
token: token,
account_id: account_id,
};
api
.invoke("POST", "/api/v1/subnet/register", request)
.then(() => {
setLoading(false);
clearForm();
fetchLicenseInfo();
})
.catch((err: ErrorResponseHandler) => {
displayErrorMessage(err);
setLoading(false);
});
};
const subnetRegister = () => {
if (loading) {
return;
}
setLoading(true);
if (subnetAccessToken && selectedSubnetOrganization) {
callRegister(subnetAccessToken, selectedSubnetOrganization);
}
};
const subnetLoginWithMFA = () => {
if (loading) {
return;
}
setLoading(true);
const request: SubnetLoginWithMFARequest = {
username: subnetEmail,
otp: subnetOTP,
mfa_token: subnetMFAToken,
};
api
.invoke("POST", "/api/v1/subnet/login/mfa", request)
.then((resp: SubnetLoginResponse) => {
setLoading(false);
if (resp && resp.access_token && resp.organizations.length > 0) {
if (resp.organizations.length === 1) {
callRegister(
resp.access_token,
resp.organizations[0].accountId.toString()
);
} else {
setSubnetAccessToken(resp.access_token);
setSubnetOrganizations(resp.organizations);
setSelectedSubnetOrganization(
resp.organizations[0].accountId.toString()
);
}
}
})
.catch((err: ErrorResponseHandler) => {
displayErrorMessage(err);
setLoading(false);
setSubnetOTP("");
});
};
const subnetLogin = () => {
if (loading) {
return;
}
setLoading(true);
let request: SubnetLoginRequest = {
username: subnetEmail,
password: subnetPassword,
apiKey: license,
};
api
.invoke("POST", "/api/v1/subnet/login", request)
.then((resp: SubnetLoginResponse) => {
setLoading(false);
if (resp && resp.registered) {
clearForm();
fetchLicenseInfo();
} else if (resp && resp.mfa_token) {
setSubnetMFAToken(resp.mfa_token);
} else if (resp && resp.access_token && resp.organizations.length > 0) {
setSubnetAccessToken(resp.access_token);
setSubnetOrganizations(resp.organizations);
setSelectedSubnetOrganization(
resp.organizations[0].accountId.toString()
);
}
})
.catch((err: ErrorResponseHandler) => {
displayErrorMessage(err);
setLoading(false);
clearForm();
});
};
useEffect(() => {
if (initialLicenseLoading) {
fetchLicenseInfo();
setInitialLicenseLoading(false);
}
}, [fetchLicenseInfo, initialLicenseLoading, setInitialLicenseLoading]);
const title = onlineActivation ? (
<Fragment>
<OnlineRegistrationIcon />
Online Activation SUBNET License
</Fragment>
) : (
<Fragment>
<OfflineRegistrationIcon />
Offline Activating SUBNET License
</Fragment>
);
let clusterRegistrationForm: JSX.Element;
if (onlineActivation) {
if (subnetAccessToken && subnetOrganizations.length > 0) {
clusterRegistrationForm = (
<Fragment>
<Grid item xs={12} className={classes.subnetDescription}>
<Typography>Register MinIO cluster</Typography>
</Grid>
<br />
<Grid item xs={4} className={classes.actionsTray}>
<SelectWrapper
id="subnet-organization"
name="subnet-organization"
onChange={(e) =>
setSelectedSubnetOrganization(e.target.value as string)
}
label="Select an organization"
value={selectedSubnetOrganization}
options={subnetOrganizations.map((organization) => ({
label: organization.company,
value: organization.accountId.toString(),
}))}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<Button
className={classes.button}
color="primary"
onClick={() => subnetRegister()}
disabled={loading || subnetAccessToken.trim().length === 0}
variant="contained"
>
Register
</Button>
</Grid>
</Fragment>
);
} else if (subnetMFAToken) {
clusterRegistrationForm = (
<Fragment>
<Grid item xs={12} className={classes.subnetDescription}>
<Typography>Two-Factor Authentication</Typography>
<Typography variant="caption" display="block" gutterBottom>
Please enter the 6-digit verification code that was sent to your
email address. This code will be valid for 5 minutes.
</Typography>
</Grid>
<br />
<Grid item xs={3} className={clsx(classes.actionsTray)}>
<InputBoxWrapper
overlayIcon={<LockOutlinedIcon />}
id="subnet-otp"
name="subnet-otp"
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setSubnetOTP(event.target.value)
}
placeholder=""
label=""
value={subnetOTP}
/>
<Button
color="primary"
onClick={() => subnetLoginWithMFA()}
disabled={
loading ||
subnetOTP.trim().length === 0 ||
subnetMFAToken.trim().length === 0
}
variant="contained"
>
Verify
</Button>
</Grid>
</Fragment>
);
} else {
clusterRegistrationForm = (
<Fragment>
<Grid item xs={12} className={classes.subnetDescription}>
The MinIO Subscription Network (SUBNET for short) is a simple, yet
powerful software stack that ensures commercial customers are
getting the very best out of their MinIO instances. SUBNET is priced
on capacity - just like cloud storage and comes in two
configurations, Standard and Enterprise.
</Grid>
<br />
<Grid item xs={12} className={classes.subnetDescription}>
You can use your credentials from SUBNET to register.{" "}
<Link
className={classes.link}
color="inherit"
target="_blank"
href="https://min.io/product/subnet"
>
Learn more about SUBNET
</Link>
</Grid>
<br />
<Grid item xs={12} className={clsx(classes.actionsTray)}>
<InputBoxWrapper
className={classes.subnetLoginInputBoxWrapper}
id="subnet-email"
name="subnet-email"
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setSubnetEmail(event.target.value)
}
label="Email"
value={subnetEmail}
noLabelMinWidth
overlayIcon={<UsersIcon />}
/>
<InputBoxWrapper
className={classes.subnetLoginInputBoxWrapper}
id="subnet-password"
name="subnet-password"
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setSubnetPassword(event.target.value)
}
label="Password"
type={showPassword ? "text" : "password"}
value={subnetPassword}
noLabelMinWidth
overlayIcon={
showPassword ? <VisibilityOffIcon /> : <RemoveRedEyeIcon />
}
overlayAction={() => setShowPassword(!showPassword)}
/>
<Button
type="submit"
variant="contained"
color="primary"
disabled={
loading ||
subnetEmail.trim().length === 0 ||
subnetPassword.trim().length === 0
}
onClick={() => subnetLogin()}
>
Register
</Button>
</Grid>
</Fragment>
);
}
} else {
clusterRegistrationForm = (
<Fragment>
<Grid item xs={12} className={classes.subnetDescription}>
<span>Step 1:</span> Copy the following registration token
</Grid>
<Grid item xs={12} className={clsx(classes.actionsTray)}>
<InputBoxWrapper
className={clsx(
classes.subnetLoginInputBoxWrapper,
classes.copyInputBox
)}
id="registration-token"
name="registration-token"
placeholder=""
label=""
type="text"
onChange={() => {}}
value={subnetRegToken}
disabled
overlayIcon={<CopyIcon />}
overlayAction={() => navigator.clipboard.writeText(subnetRegToken)}
/>
</Grid>
<Grid item xs={12} className={classes.subnetDescription}>
<span>Step 2:</span> Use the previous token to register your cluster
at:{" "}
<Link
className={classes.link}
color="inherit"
href="https://subnet.min.io/cluster/register"
target="_blank"
>
https://subnet.min.io/cluster/register
</Link>
</Grid>
<br />
<Grid item xs={12} className={classes.subnetDescription}>
<span>Step 3:</span> Enter the API key generated by SUBNET
</Grid>
<Grid item xs={12} className={clsx(classes.actionsTray)}>
<InputBoxWrapper
className={classes.subnetLoginInputBoxWrapper}
value={license}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setLicense(event.target.value)
}
id="api-key"
name="api-key"
placeholder=""
label=""
type="text"
/>
</Grid>
<Grid item xs={12} className={classes.offlineRegisterButton}>
<Button
variant="contained"
color="primary"
onClick={() => subnetLogin()}
disabled={loading || license.trim().length === 0}
>
Register
</Button>
</Grid>
</Fragment>
);
}
return (
<Fragment>
<PageHeader label="Register" />
<PageLayout>
<Grid item xs={12} className={classes.boxy}>
{clusterRegistered && (
<Grid container>
<Grid item xs={12} className={classes.registeredStatus}>
Register Status:
<VerifiedIcon />
<span>Registered</span>
</Grid>
</Grid>
)}
<Grid container>
<Grid item xs={6} className={classes.registerActivationIcon}>
{title}
</Grid>
<Grid item xs={6} className={classes.registerActivationMode}>
{onlineActivation ? (
<Fragment>
<OfflineRegistrationBackIcon />
<Link
className={classes.link}
onClick={() => {
fetchSubnetRegToken();
setOnlineActivation(!onlineActivation);
}}
>
Offline Activation
</Link>
</Fragment>
) : (
<Fragment>
<OnlineRegistrationBackIcon />
<Link
className={classes.link}
onClick={() => setOnlineActivation(!onlineActivation)}
>
Back to Online Activation
</Link>
</Fragment>
)}
</Grid>
</Grid>
{clusterRegistrationForm}
</Grid>
</PageLayout>
</Fragment>
);
};
const connector = connect(null, {
displayErrorMessage: setErrorSnackMessage,
});
export default withStyles(styles)(connector(Register));

View File

@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { LicenseInfo } from "../../License/types";
import { SubnetInfo } from "../../License/types";
import { IAffinityModel } from "../../../../common/types";
import { NodeMaxAllocatableResources } from "../types";
@@ -114,7 +114,7 @@ export interface ITenant {
capacity_usage?: number;
// computed
total_capacity: string;
subnet_license: LicenseInfo;
subnet_license: SubnetInfo;
total_instances?: number;
total_volumes?: number;
}

View File

@@ -26,15 +26,15 @@ import Moment from "react-moment";
import { Link } from "react-router-dom";
import Paper from "@mui/material/Paper";
import { ITenant } from "../ListTenants/types";
import { LicenseInfo } from "../../License/types";
import RBIconButton from "../../Buckets/BucketDetails/SummaryItems/RBIconButton";
import { SubnetInfo } from "../../License/types";
interface ISubnetLicenseTenant {
classes: any;
tenant: ITenant | null;
loadingActivateProduct: any;
loadingLicenseInfo: boolean;
licenseInfo: LicenseInfo | undefined;
licenseInfo: SubnetInfo | undefined;
activateProduct: any;
}

View File

@@ -26,7 +26,7 @@ import {
tenantDetailsStyles,
} from "../../Common/FormComponents/common/styleLibrary";
import { ITenant } from "../ListTenants/types";
import { LicenseInfo } from "../../License/types";
import { SubnetInfo } from "../../License/types";
import { setErrorSnackMessage } from "../../../../actions";
import { AppState } from "../../../../store";
import { setTenantDetailsLoad } from "../actions";
@@ -56,7 +56,7 @@ const TenantLicense = ({
loadingTenant,
setTenantDetailsLoad,
}: ITenantLicense) => {
const [licenseInfo, setLicenseInfo] = useState<LicenseInfo>();
const [licenseInfo, setLicenseInfo] = useState<SubnetInfo>();
const [loadingLicenseInfo, setLoadingLicenseInfo] = useState<boolean>(true);
const [loadingActivateProduct, setLoadingActivateProduct] =
useState<boolean>(false);
@@ -87,7 +87,7 @@ const TenantLicense = ({
if (loadingLicenseInfo) {
api
.invoke("GET", `/api/v1/subscription/info`)
.then((res: LicenseInfo) => {
.then((res: SubnetInfo) => {
setLicenseInfo(res);
setLoadingLicenseInfo(false);
})

View File

@@ -19,6 +19,7 @@ import { Route, Router, Switch } from "react-router-dom";
import history from "../../../history";
import NotFoundPage from "../../NotFoundPage";
import ToolsList from "./ToolsPanel/ToolsList";
import Register from "../Support/Register";
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
import FeatureNotAvailablePage from "../Common/Components/FeatureNotAvailablePage";
import { SupportMenuIcon } from "../../../icons/SidebarMenus/MenuIcons";
@@ -28,20 +29,7 @@ const Tools = () => {
<Router history={history}>
<Switch>
<Route path={IAM_PAGES.TOOLS} exact component={ToolsList} />
<Route
path={IAM_PAGES.REGISTER_SUPPORT}
exact
render={() => {
return (
<FeatureNotAvailablePage
icon={<SupportMenuIcon />}
pageHeaderText={"Register"}
title={"Product registration"}
message={<div>This feature is currently not available.</div>}
/>
);
}}
/>
<Route path={IAM_PAGES.REGISTER_SUPPORT} exact component={Register} />
<Route
path={IAM_PAGES.CALL_HOME}
exact

282
restapi/admin_subnet.go Normal file
View File

@@ -0,0 +1,282 @@
// 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 restapi
import (
"context"
"errors"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/cluster"
"github.com/minio/console/models"
"github.com/minio/console/pkg/subnet"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
"github.com/minio/madmin-go"
)
func registerSubnetHandlers(api *operations.ConsoleAPI) {
// Get subnet login handler
api.AdminAPISubnetLoginHandler = admin_api.SubnetLoginHandlerFunc(func(params admin_api.SubnetLoginParams, session *models.Principal) middleware.Responder {
resp, err := GetSubnetLoginResponse(session, params)
if err != nil {
return admin_api.NewSubnetLoginDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewSubnetLoginOK().WithPayload(resp)
})
// Get subnet login with MFA handler
api.AdminAPISubnetLoginMFAHandler = admin_api.SubnetLoginMFAHandlerFunc(func(params admin_api.SubnetLoginMFAParams, session *models.Principal) middleware.Responder {
resp, err := GetSubnetLoginWithMFAResponse(params)
if err != nil {
return admin_api.NewSubnetLoginMFADefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewSubnetLoginMFAOK().WithPayload(resp)
})
// Get subnet register
api.AdminAPISubnetRegisterHandler = admin_api.SubnetRegisterHandlerFunc(func(params admin_api.SubnetRegisterParams, session *models.Principal) middleware.Responder {
err := GetSubnetRegisterResponse(session, params)
if err != nil {
return admin_api.NewSubnetRegisterDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewSubnetRegisterOK()
})
// Get subnet info
api.AdminAPISubnetInfoHandler = admin_api.SubnetInfoHandlerFunc(func(params admin_api.SubnetInfoParams, session *models.Principal) middleware.Responder {
err := GetSubnetInfoResponse(session)
if err != nil {
return admin_api.NewSubnetInfoDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewSubnetInfoOK()
})
// Get subnet registration token
api.AdminAPISubnetRegTokenHandler = admin_api.SubnetRegTokenHandlerFunc(func(params admin_api.SubnetRegTokenParams, session *models.Principal) middleware.Responder {
resp, err := GetSubnetRegTokenResponse(session)
if err != nil {
return admin_api.NewSubnetRegTokenDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewSubnetRegTokenOK().WithPayload(resp)
})
}
func SubnetRegisterWithAPIKey(ctx context.Context, minioClient MinioAdmin, apiKey string) (bool, error) {
serverInfo, err := minioClient.serverInfo(ctx)
if err != nil {
return false, err
}
subnetAPIKey, err := subnet.Register(httpClient, serverInfo, apiKey, "", "")
if err != nil {
return false, err
}
configStr := "subnet license= api_key=" + subnetAPIKey
_, err = minioClient.setConfigKV(ctx, configStr)
if err != nil {
return false, err
}
// cluster registered correctly
return true, nil
}
func SubnetLogin(client cluster.HTTPClientI, username, password string) (string, string, error) {
tokens, err := subnet.Login(client, username, password)
if err != nil {
return "", "", err
}
if tokens.MfaToken != "" {
// user needs to complete login flow using mfa
return "", tokens.MfaToken, nil
}
if tokens.AccessToken != "" {
// register token to minio
return tokens.AccessToken, "", nil
}
return "", "", errors.New("something went wrong")
}
func GetSubnetLoginResponse(session *models.Principal, params admin_api.SubnetLoginParams) (*models.SubnetLoginResponse, *models.Error) {
ctx := context.Background()
httpClient := &cluster.HTTPClient{
Client: GetConsoleHTTPClient(),
}
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
}
minioClient := AdminClient{Client: mAdmin}
apiKey := params.Body.APIKey
if apiKey != "" {
registered, err := SubnetRegisterWithAPIKey(ctx, minioClient, apiKey)
if err != nil {
return nil, prepareError(err)
}
return &models.SubnetLoginResponse{
Registered: registered,
Organizations: []*models.SubnetOrganization{},
}, nil
}
username := params.Body.Username
password := params.Body.Password
if username != "" && password != "" {
token, mfa, err := SubnetLogin(httpClient, username, password)
if err != nil {
return nil, prepareError(err)
}
return &models.SubnetLoginResponse{
MfaToken: mfa,
AccessToken: token,
Organizations: []*models.SubnetOrganization{},
}, nil
}
return nil, prepareError(ErrorGeneric)
}
type SubnetRegistration struct {
AccessToken string
MFAToken string
Organizations []models.SubnetOrganization
}
func SubnetLoginWithMFA(client cluster.HTTPClientI, username, mfaToken, otp string) (*models.SubnetLoginResponse, error) {
tokens, err := subnet.LoginWithMFA(client, username, mfaToken, otp)
if err != nil {
return nil, err
}
if tokens.AccessToken != "" {
organizations, errOrg := subnet.GetOrganizations(client, tokens.AccessToken)
if errOrg != nil {
return nil, errOrg
}
return &models.SubnetLoginResponse{
AccessToken: tokens.AccessToken,
Organizations: organizations,
}, nil
}
return nil, errors.New("something went wrong")
}
func GetSubnetLoginWithMFAResponse(params admin_api.SubnetLoginMFAParams) (*models.SubnetLoginResponse, *models.Error) {
client := &cluster.HTTPClient{
Client: GetConsoleHTTPClient(),
}
resp, err := SubnetLoginWithMFA(client, *params.Body.Username, *params.Body.MfaToken, *params.Body.Otp)
if err != nil {
return nil, prepareError(err)
}
return resp, nil
}
func GetSubnetKeyFromMinIOConfig(ctx context.Context, minioClient MinioAdmin, key string) (string, error) {
sh, err := minioClient.helpConfigKV(ctx, "subnet", "", false)
if err != nil {
return "", err
}
buf, err := minioClient.getConfigKV(ctx, "subnet")
if err != nil {
return "", err
}
tgt, err := madmin.ParseSubSysTarget(buf, sh)
if err != nil {
return "", err
}
for _, kv := range tgt.KVS {
if kv.Key == key {
return kv.Value, nil
}
}
return "", errors.New("")
}
func GetSubnetRegister(ctx context.Context, minioClient MinioAdmin, httpClient cluster.HTTPClientI, params admin_api.SubnetRegisterParams) error {
serverInfo, err := minioClient.serverInfo(ctx)
if err != nil {
return err
}
subnetAPIKey, err := subnet.Register(httpClient, serverInfo, "", *params.Body.Token, *params.Body.AccountID)
if err != nil {
return err
}
configStr := "subnet license= api_key=" + subnetAPIKey
_, err = minioClient.setConfigKV(ctx, configStr)
if err != nil {
return err
}
return nil
}
func GetSubnetRegisterResponse(session *models.Principal, params admin_api.SubnetRegisterParams) *models.Error {
ctx := context.Background()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
}
adminClient := AdminClient{Client: mAdmin}
client := &cluster.HTTPClient{
Client: GetConsoleHTTPClient(),
}
err = GetSubnetRegister(ctx, adminClient, client, params)
if err != nil {
return prepareError(err)
}
return nil
}
func GetSubnetInfoResponse(session *models.Principal) *models.Error {
ctx := context.Background()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
}
adminClient := AdminClient{Client: mAdmin}
apiKey, err := GetSubnetKeyFromMinIOConfig(ctx, adminClient, "api_key")
if err != nil {
return prepareError(err)
}
if apiKey == "" {
return prepareError(errLicenseNotFound)
}
return nil
}
func GetSubnetRegToken(ctx context.Context, minioClient MinioAdmin) (string, error) {
serverInfo, err := minioClient.serverInfo(ctx)
if err != nil {
return "", err
}
regInfo := subnet.GetClusterRegInfo(serverInfo)
regToken, err := subnet.GenerateRegToken(regInfo)
if err != nil {
return "", err
}
return regToken, nil
}
func GetSubnetRegTokenResponse(session *models.Principal) (*models.SubnetRegTokenResponse, *models.Error) {
ctx := context.Background()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
}
adminClient := AdminClient{Client: mAdmin}
token, err := GetSubnetRegToken(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
}
return &models.SubnetRegTokenResponse{
RegToken: token,
}, nil
}

View File

@@ -1,78 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package restapi
import (
"github.com/minio/console/cluster"
"github.com/minio/console/pkg/subnet"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
)
func registerSubscriptionHandlers(api *operations.ConsoleAPI) {
// Get subscription information handler
api.AdminAPISubscriptionInfoHandler = admin_api.SubscriptionInfoHandlerFunc(func(params admin_api.SubscriptionInfoParams, session *models.Principal) middleware.Responder {
license, err := getSubscriptionInfoResponse()
if err != nil {
return admin_api.NewSubscriptionInfoDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewSubscriptionInfoOK().WithPayload(license)
})
}
// retrieveLicense returns license from K8S secrets (If console is deployed in operator mode) or from
// the configured CONSOLE_SUBNET_LICENSE environment variable
func retrieveLicense() string {
// If Console is running in Tenant Admin mode retrieve license from env variable
license := GetSubnetLicense()
return license
}
// subscriptionValidate will validate the provided jwt license against the subnet public key
func subscriptionValidate(client cluster.HTTPClientI, license, email, password string) (*models.License, string, error) {
licenseInfo, rawLicense, err := subnet.ValidateLicense(client, license, email, password)
if err != nil {
return nil, "", err
}
return &models.License{
Email: licenseInfo.Email,
AccountID: licenseInfo.AccountID,
StorageCapacity: licenseInfo.StorageCapacity,
Plan: licenseInfo.Plan,
ExpiresAt: licenseInfo.ExpiresAt.String(),
Organization: licenseInfo.Organization,
}, rawLicense, nil
}
// getSubscriptionInfoResponse returns information about the current configured subnet license for Console
func getSubscriptionInfoResponse() (*models.License, *models.Error) {
var licenseInfo *models.License
client := &cluster.HTTPClient{
Client: GetConsoleHTTPClient(),
}
licenseKey := retrieveLicense()
// 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

@@ -118,8 +118,8 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
registerAdminBucketRemoteHandlers(api)
// Register admin log search
registerLogSearchHandlers(api)
// Register admin subscription handlers
registerSubscriptionHandlers(api)
// Register admin subnet handlers
registerSubnetHandlers(api)
// Register Account handlers
registerAdminTiersHandlers(api)

View File

@@ -3212,13 +3212,13 @@ func init() {
}
}
},
"/subscription/info": {
"/subnet/info": {
"get": {
"tags": [
"AdminAPI"
],
"summary": "Subscription info",
"operationId": "SubscriptionInfo",
"summary": "Subnet info",
"operationId": "SubnetInfo",
"responses": {
"200": {
"description": "A successful response.",
@@ -3235,6 +3235,125 @@ func init() {
}
}
},
"/subnet/login": {
"post": {
"tags": [
"AdminAPI"
],
"summary": "Login to subnet",
"operationId": "SubnetLogin",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/subnetLoginRequest"
}
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/subnetLoginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/subnet/login/mfa": {
"post": {
"tags": [
"AdminAPI"
],
"summary": "Login to subnet using mfa",
"operationId": "SubnetLoginMFA",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/subnetLoginMFARequest"
}
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/subnetLoginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/subnet/register": {
"post": {
"tags": [
"AdminAPI"
],
"summary": "Register cluster with Subnet",
"operationId": "SubnetRegister",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/subnetRegisterRequest"
}
}
],
"responses": {
"200": {
"description": "A successful response."
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/subnet/registration-token": {
"get": {
"tags": [
"AdminAPI"
],
"summary": "Subnet registraton token",
"operationId": "SubnetRegToken",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/SubnetRegTokenResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/user": {
"get": {
"tags": [
@@ -3575,6 +3694,14 @@ func init() {
}
},
"definitions": {
"SubnetRegTokenResponse": {
"type": "object",
"properties": {
"regToken": {
"type": "string"
}
}
},
"accessRule": {
"type": "object",
"properties": {
@@ -5547,6 +5674,97 @@ func init() {
}
}
},
"subnetLoginMFARequest": {
"type": "object",
"required": [
"username",
"otp",
"mfa_token"
],
"properties": {
"mfa_token": {
"type": "string"
},
"otp": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"subnetLoginRequest": {
"type": "object",
"properties": {
"apiKey": {
"type": "string"
},
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"subnetLoginResponse": {
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"mfa_token": {
"type": "string"
},
"organizations": {
"type": "array",
"items": {
"$ref": "#/definitions/subnetOrganization"
}
},
"registered": {
"type": "boolean"
}
}
},
"subnetOrganization": {
"type": "object",
"properties": {
"accountId": {
"type": "integer"
},
"company": {
"type": "string"
},
"isAccountOwner": {
"type": "boolean"
},
"shortName": {
"type": "string"
},
"subscriptionStatus": {
"type": "string"
},
"userId": {
"type": "integer"
}
}
},
"subnetRegisterRequest": {
"type": "object",
"required": [
"token",
"account_id"
],
"properties": {
"account_id": {
"type": "string"
},
"token": {
"type": "string"
}
}
},
"tier": {
"type": "object",
"properties": {
@@ -9061,13 +9279,13 @@ func init() {
}
}
},
"/subscription/info": {
"/subnet/info": {
"get": {
"tags": [
"AdminAPI"
],
"summary": "Subscription info",
"operationId": "SubscriptionInfo",
"summary": "Subnet info",
"operationId": "SubnetInfo",
"responses": {
"200": {
"description": "A successful response.",
@@ -9084,6 +9302,125 @@ func init() {
}
}
},
"/subnet/login": {
"post": {
"tags": [
"AdminAPI"
],
"summary": "Login to subnet",
"operationId": "SubnetLogin",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/subnetLoginRequest"
}
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/subnetLoginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/subnet/login/mfa": {
"post": {
"tags": [
"AdminAPI"
],
"summary": "Login to subnet using mfa",
"operationId": "SubnetLoginMFA",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/subnetLoginMFARequest"
}
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/subnetLoginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/subnet/register": {
"post": {
"tags": [
"AdminAPI"
],
"summary": "Register cluster with Subnet",
"operationId": "SubnetRegister",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/subnetRegisterRequest"
}
}
],
"responses": {
"200": {
"description": "A successful response."
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/subnet/registration-token": {
"get": {
"tags": [
"AdminAPI"
],
"summary": "Subnet registraton token",
"operationId": "SubnetRegToken",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/SubnetRegTokenResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/user": {
"get": {
"tags": [
@@ -9490,6 +9827,14 @@ func init() {
}
}
},
"SubnetRegTokenResponse": {
"type": "object",
"properties": {
"regToken": {
"type": "string"
}
}
},
"WidgetDetailsOptions": {
"type": "object",
"properties": {
@@ -11516,6 +11861,97 @@ func init() {
}
}
},
"subnetLoginMFARequest": {
"type": "object",
"required": [
"username",
"otp",
"mfa_token"
],
"properties": {
"mfa_token": {
"type": "string"
},
"otp": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"subnetLoginRequest": {
"type": "object",
"properties": {
"apiKey": {
"type": "string"
},
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"subnetLoginResponse": {
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"mfa_token": {
"type": "string"
},
"organizations": {
"type": "array",
"items": {
"$ref": "#/definitions/subnetOrganization"
}
},
"registered": {
"type": "boolean"
}
}
},
"subnetOrganization": {
"type": "object",
"properties": {
"accountId": {
"type": "integer"
},
"company": {
"type": "string"
},
"isAccountOwner": {
"type": "boolean"
},
"shortName": {
"type": "string"
},
"subscriptionStatus": {
"type": "string"
},
"userId": {
"type": "integer"
}
}
},
"subnetRegisterRequest": {
"type": "object",
"required": [
"token",
"account_id"
],
"properties": {
"account_id": {
"type": "string"
},
"token": {
"type": "string"
}
}
},
"tier": {
"type": "object",
"properties": {

View File

@@ -30,40 +30,40 @@ import (
"github.com/minio/console/models"
)
// SubscriptionInfoHandlerFunc turns a function with the right signature into a subscription info handler
type SubscriptionInfoHandlerFunc func(SubscriptionInfoParams, *models.Principal) middleware.Responder
// SubnetInfoHandlerFunc turns a function with the right signature into a subnet info handler
type SubnetInfoHandlerFunc func(SubnetInfoParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn SubscriptionInfoHandlerFunc) Handle(params SubscriptionInfoParams, principal *models.Principal) middleware.Responder {
func (fn SubnetInfoHandlerFunc) Handle(params SubnetInfoParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// SubscriptionInfoHandler interface for that can handle valid subscription info params
type SubscriptionInfoHandler interface {
Handle(SubscriptionInfoParams, *models.Principal) middleware.Responder
// SubnetInfoHandler interface for that can handle valid subnet info params
type SubnetInfoHandler interface {
Handle(SubnetInfoParams, *models.Principal) middleware.Responder
}
// NewSubscriptionInfo creates a new http.Handler for the subscription info operation
func NewSubscriptionInfo(ctx *middleware.Context, handler SubscriptionInfoHandler) *SubscriptionInfo {
return &SubscriptionInfo{Context: ctx, Handler: handler}
// NewSubnetInfo creates a new http.Handler for the subnet info operation
func NewSubnetInfo(ctx *middleware.Context, handler SubnetInfoHandler) *SubnetInfo {
return &SubnetInfo{Context: ctx, Handler: handler}
}
/* SubscriptionInfo swagger:route GET /subscription/info AdminAPI subscriptionInfo
/* SubnetInfo swagger:route GET /subnet/info AdminAPI subnetInfo
Subscription info
Subnet info
*/
type SubscriptionInfo struct {
type SubnetInfo struct {
Context *middleware.Context
Handler SubscriptionInfoHandler
Handler SubnetInfoHandler
}
func (o *SubscriptionInfo) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
func (o *SubnetInfo) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewSubscriptionInfoParams()
var Params = NewSubnetInfoParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)

View File

@@ -29,19 +29,19 @@ import (
"github.com/go-openapi/runtime/middleware"
)
// NewSubscriptionInfoParams creates a new SubscriptionInfoParams object
// NewSubnetInfoParams creates a new SubnetInfoParams object
//
// There are no default values defined in the spec.
func NewSubscriptionInfoParams() SubscriptionInfoParams {
func NewSubnetInfoParams() SubnetInfoParams {
return SubscriptionInfoParams{}
return SubnetInfoParams{}
}
// SubscriptionInfoParams contains all the bound params for the subscription info operation
// SubnetInfoParams contains all the bound params for the subnet info operation
// typically these are obtained from a http.Request
//
// swagger:parameters SubscriptionInfo
type SubscriptionInfoParams struct {
// swagger:parameters SubnetInfo
type SubnetInfoParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
@@ -50,8 +50,8 @@ type SubscriptionInfoParams struct {
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewSubscriptionInfoParams() beforehand.
func (o *SubscriptionInfoParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
// To ensure default values, the struct must have been initialized with NewSubnetInfoParams() beforehand.
func (o *SubnetInfoParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r

View File

@@ -30,14 +30,14 @@ import (
"github.com/minio/console/models"
)
// SubscriptionInfoOKCode is the HTTP code returned for type SubscriptionInfoOK
const SubscriptionInfoOKCode int = 200
// SubnetInfoOKCode is the HTTP code returned for type SubnetInfoOK
const SubnetInfoOKCode int = 200
/*SubscriptionInfoOK A successful response.
/*SubnetInfoOK A successful response.
swagger:response subscriptionInfoOK
swagger:response subnetInfoOK
*/
type SubscriptionInfoOK struct {
type SubnetInfoOK struct {
/*
In: Body
@@ -45,25 +45,25 @@ type SubscriptionInfoOK struct {
Payload *models.License `json:"body,omitempty"`
}
// NewSubscriptionInfoOK creates SubscriptionInfoOK with default headers values
func NewSubscriptionInfoOK() *SubscriptionInfoOK {
// NewSubnetInfoOK creates SubnetInfoOK with default headers values
func NewSubnetInfoOK() *SubnetInfoOK {
return &SubscriptionInfoOK{}
return &SubnetInfoOK{}
}
// WithPayload adds the payload to the subscription info o k response
func (o *SubscriptionInfoOK) WithPayload(payload *models.License) *SubscriptionInfoOK {
// WithPayload adds the payload to the subnet info o k response
func (o *SubnetInfoOK) WithPayload(payload *models.License) *SubnetInfoOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the subscription info o k response
func (o *SubscriptionInfoOK) SetPayload(payload *models.License) {
// SetPayload sets the payload to the subnet info o k response
func (o *SubnetInfoOK) SetPayload(payload *models.License) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SubscriptionInfoOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
func (o *SubnetInfoOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
@@ -74,11 +74,11 @@ func (o *SubscriptionInfoOK) WriteResponse(rw http.ResponseWriter, producer runt
}
}
/*SubscriptionInfoDefault Generic error response.
/*SubnetInfoDefault Generic error response.
swagger:response subscriptionInfoDefault
swagger:response subnetInfoDefault
*/
type SubscriptionInfoDefault struct {
type SubnetInfoDefault struct {
_statusCode int
/*
@@ -87,41 +87,41 @@ type SubscriptionInfoDefault struct {
Payload *models.Error `json:"body,omitempty"`
}
// NewSubscriptionInfoDefault creates SubscriptionInfoDefault with default headers values
func NewSubscriptionInfoDefault(code int) *SubscriptionInfoDefault {
// NewSubnetInfoDefault creates SubnetInfoDefault with default headers values
func NewSubnetInfoDefault(code int) *SubnetInfoDefault {
if code <= 0 {
code = 500
}
return &SubscriptionInfoDefault{
return &SubnetInfoDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the subscription info default response
func (o *SubscriptionInfoDefault) WithStatusCode(code int) *SubscriptionInfoDefault {
// WithStatusCode adds the status to the subnet info default response
func (o *SubnetInfoDefault) WithStatusCode(code int) *SubnetInfoDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the subscription info default response
func (o *SubscriptionInfoDefault) SetStatusCode(code int) {
// SetStatusCode sets the status to the subnet info default response
func (o *SubnetInfoDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the subscription info default response
func (o *SubscriptionInfoDefault) WithPayload(payload *models.Error) *SubscriptionInfoDefault {
// WithPayload adds the payload to the subnet info default response
func (o *SubnetInfoDefault) WithPayload(payload *models.Error) *SubnetInfoDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the subscription info default response
func (o *SubscriptionInfoDefault) SetPayload(payload *models.Error) {
// SetPayload sets the payload to the subnet info default response
func (o *SubnetInfoDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SubscriptionInfoDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
func (o *SubnetInfoDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {

View File

@@ -28,15 +28,15 @@ import (
golangswaggerpaths "path"
)
// SubscriptionInfoURL generates an URL for the subscription info operation
type SubscriptionInfoURL struct {
// SubnetInfoURL generates an URL for the subnet info operation
type SubnetInfoURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *SubscriptionInfoURL) WithBasePath(bp string) *SubscriptionInfoURL {
func (o *SubnetInfoURL) WithBasePath(bp string) *SubnetInfoURL {
o.SetBasePath(bp)
return o
}
@@ -44,15 +44,15 @@ func (o *SubscriptionInfoURL) WithBasePath(bp string) *SubscriptionInfoURL {
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *SubscriptionInfoURL) SetBasePath(bp string) {
func (o *SubnetInfoURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *SubscriptionInfoURL) Build() (*url.URL, error) {
func (o *SubnetInfoURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/subscription/info"
var _path = "/subnet/info"
_basePath := o._basePath
if _basePath == "" {
@@ -64,7 +64,7 @@ func (o *SubscriptionInfoURL) Build() (*url.URL, error) {
}
// Must is a helper function to panic when the url builder returns an error
func (o *SubscriptionInfoURL) Must(u *url.URL, err error) *url.URL {
func (o *SubnetInfoURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
@@ -75,17 +75,17 @@ func (o *SubscriptionInfoURL) Must(u *url.URL, err error) *url.URL {
}
// String returns the string representation of the path with query string
func (o *SubscriptionInfoURL) String() string {
func (o *SubnetInfoURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *SubscriptionInfoURL) BuildFull(scheme, host string) (*url.URL, error) {
func (o *SubnetInfoURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on SubscriptionInfoURL")
return nil, errors.New("scheme is required for a full url on SubnetInfoURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on SubscriptionInfoURL")
return nil, errors.New("host is required for a full url on SubnetInfoURL")
}
base, err := o.Build()
@@ -99,6 +99,6 @@ func (o *SubscriptionInfoURL) BuildFull(scheme, host string) (*url.URL, error) {
}
// StringFull returns the string representation of a complete url
func (o *SubscriptionInfoURL) StringFull(scheme, host string) string {
func (o *SubnetInfoURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,88 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// SubnetLoginHandlerFunc turns a function with the right signature into a subnet login handler
type SubnetLoginHandlerFunc func(SubnetLoginParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn SubnetLoginHandlerFunc) Handle(params SubnetLoginParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// SubnetLoginHandler interface for that can handle valid subnet login params
type SubnetLoginHandler interface {
Handle(SubnetLoginParams, *models.Principal) middleware.Responder
}
// NewSubnetLogin creates a new http.Handler for the subnet login operation
func NewSubnetLogin(ctx *middleware.Context, handler SubnetLoginHandler) *SubnetLogin {
return &SubnetLogin{Context: ctx, Handler: handler}
}
/* SubnetLogin swagger:route POST /subnet/login AdminAPI subnetLogin
Login to subnet
*/
type SubnetLogin struct {
Context *middleware.Context
Handler SubnetLoginHandler
}
func (o *SubnetLogin) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewSubnetLoginParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,88 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// SubnetLoginMFAHandlerFunc turns a function with the right signature into a subnet login m f a handler
type SubnetLoginMFAHandlerFunc func(SubnetLoginMFAParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn SubnetLoginMFAHandlerFunc) Handle(params SubnetLoginMFAParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// SubnetLoginMFAHandler interface for that can handle valid subnet login m f a params
type SubnetLoginMFAHandler interface {
Handle(SubnetLoginMFAParams, *models.Principal) middleware.Responder
}
// NewSubnetLoginMFA creates a new http.Handler for the subnet login m f a operation
func NewSubnetLoginMFA(ctx *middleware.Context, handler SubnetLoginMFAHandler) *SubnetLoginMFA {
return &SubnetLoginMFA{Context: ctx, Handler: handler}
}
/* SubnetLoginMFA swagger:route POST /subnet/login/mfa AdminAPI subnetLoginMFA
Login to subnet using mfa
*/
type SubnetLoginMFA struct {
Context *middleware.Context
Handler SubnetLoginMFAHandler
}
func (o *SubnetLoginMFA) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewSubnetLoginMFAParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,102 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewSubnetLoginMFAParams creates a new SubnetLoginMFAParams object
//
// There are no default values defined in the spec.
func NewSubnetLoginMFAParams() SubnetLoginMFAParams {
return SubnetLoginMFAParams{}
}
// SubnetLoginMFAParams contains all the bound params for the subnet login m f a operation
// typically these are obtained from a http.Request
//
// swagger:parameters SubnetLoginMFA
type SubnetLoginMFAParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.SubnetLoginMFARequest
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewSubnetLoginMFAParams() beforehand.
func (o *SubnetLoginMFAParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.SubnetLoginMFARequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(context.Background())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,133 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// SubnetLoginMFAOKCode is the HTTP code returned for type SubnetLoginMFAOK
const SubnetLoginMFAOKCode int = 200
/*SubnetLoginMFAOK A successful response.
swagger:response subnetLoginMFAOK
*/
type SubnetLoginMFAOK struct {
/*
In: Body
*/
Payload *models.SubnetLoginResponse `json:"body,omitempty"`
}
// NewSubnetLoginMFAOK creates SubnetLoginMFAOK with default headers values
func NewSubnetLoginMFAOK() *SubnetLoginMFAOK {
return &SubnetLoginMFAOK{}
}
// WithPayload adds the payload to the subnet login m f a o k response
func (o *SubnetLoginMFAOK) WithPayload(payload *models.SubnetLoginResponse) *SubnetLoginMFAOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the subnet login m f a o k response
func (o *SubnetLoginMFAOK) SetPayload(payload *models.SubnetLoginResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SubnetLoginMFAOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*SubnetLoginMFADefault Generic error response.
swagger:response subnetLoginMFADefault
*/
type SubnetLoginMFADefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewSubnetLoginMFADefault creates SubnetLoginMFADefault with default headers values
func NewSubnetLoginMFADefault(code int) *SubnetLoginMFADefault {
if code <= 0 {
code = 500
}
return &SubnetLoginMFADefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the subnet login m f a default response
func (o *SubnetLoginMFADefault) WithStatusCode(code int) *SubnetLoginMFADefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the subnet login m f a default response
func (o *SubnetLoginMFADefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the subnet login m f a default response
func (o *SubnetLoginMFADefault) WithPayload(payload *models.Error) *SubnetLoginMFADefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the subnet login m f a default response
func (o *SubnetLoginMFADefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SubnetLoginMFADefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// SubnetLoginMFAURL generates an URL for the subnet login m f a operation
type SubnetLoginMFAURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *SubnetLoginMFAURL) WithBasePath(bp string) *SubnetLoginMFAURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *SubnetLoginMFAURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *SubnetLoginMFAURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/subnet/login/mfa"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *SubnetLoginMFAURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *SubnetLoginMFAURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *SubnetLoginMFAURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on SubnetLoginMFAURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on SubnetLoginMFAURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *SubnetLoginMFAURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,102 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewSubnetLoginParams creates a new SubnetLoginParams object
//
// There are no default values defined in the spec.
func NewSubnetLoginParams() SubnetLoginParams {
return SubnetLoginParams{}
}
// SubnetLoginParams contains all the bound params for the subnet login operation
// typically these are obtained from a http.Request
//
// swagger:parameters SubnetLogin
type SubnetLoginParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.SubnetLoginRequest
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewSubnetLoginParams() beforehand.
func (o *SubnetLoginParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.SubnetLoginRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(context.Background())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,133 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// SubnetLoginOKCode is the HTTP code returned for type SubnetLoginOK
const SubnetLoginOKCode int = 200
/*SubnetLoginOK A successful response.
swagger:response subnetLoginOK
*/
type SubnetLoginOK struct {
/*
In: Body
*/
Payload *models.SubnetLoginResponse `json:"body,omitempty"`
}
// NewSubnetLoginOK creates SubnetLoginOK with default headers values
func NewSubnetLoginOK() *SubnetLoginOK {
return &SubnetLoginOK{}
}
// WithPayload adds the payload to the subnet login o k response
func (o *SubnetLoginOK) WithPayload(payload *models.SubnetLoginResponse) *SubnetLoginOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the subnet login o k response
func (o *SubnetLoginOK) SetPayload(payload *models.SubnetLoginResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SubnetLoginOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*SubnetLoginDefault Generic error response.
swagger:response subnetLoginDefault
*/
type SubnetLoginDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewSubnetLoginDefault creates SubnetLoginDefault with default headers values
func NewSubnetLoginDefault(code int) *SubnetLoginDefault {
if code <= 0 {
code = 500
}
return &SubnetLoginDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the subnet login default response
func (o *SubnetLoginDefault) WithStatusCode(code int) *SubnetLoginDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the subnet login default response
func (o *SubnetLoginDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the subnet login default response
func (o *SubnetLoginDefault) WithPayload(payload *models.Error) *SubnetLoginDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the subnet login default response
func (o *SubnetLoginDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SubnetLoginDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// SubnetLoginURL generates an URL for the subnet login operation
type SubnetLoginURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *SubnetLoginURL) WithBasePath(bp string) *SubnetLoginURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *SubnetLoginURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *SubnetLoginURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/subnet/login"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *SubnetLoginURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *SubnetLoginURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *SubnetLoginURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on SubnetLoginURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on SubnetLoginURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *SubnetLoginURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,88 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// SubnetRegTokenHandlerFunc turns a function with the right signature into a subnet reg token handler
type SubnetRegTokenHandlerFunc func(SubnetRegTokenParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn SubnetRegTokenHandlerFunc) Handle(params SubnetRegTokenParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// SubnetRegTokenHandler interface for that can handle valid subnet reg token params
type SubnetRegTokenHandler interface {
Handle(SubnetRegTokenParams, *models.Principal) middleware.Responder
}
// NewSubnetRegToken creates a new http.Handler for the subnet reg token operation
func NewSubnetRegToken(ctx *middleware.Context, handler SubnetRegTokenHandler) *SubnetRegToken {
return &SubnetRegToken{Context: ctx, Handler: handler}
}
/* SubnetRegToken swagger:route GET /subnet/registration-token AdminAPI subnetRegToken
Subnet registraton token
*/
type SubnetRegToken struct {
Context *middleware.Context
Handler SubnetRegTokenHandler
}
func (o *SubnetRegToken) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewSubnetRegTokenParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,63 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
)
// NewSubnetRegTokenParams creates a new SubnetRegTokenParams object
//
// There are no default values defined in the spec.
func NewSubnetRegTokenParams() SubnetRegTokenParams {
return SubnetRegTokenParams{}
}
// SubnetRegTokenParams contains all the bound params for the subnet reg token operation
// typically these are obtained from a http.Request
//
// swagger:parameters SubnetRegToken
type SubnetRegTokenParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewSubnetRegTokenParams() beforehand.
func (o *SubnetRegTokenParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,133 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// SubnetRegTokenOKCode is the HTTP code returned for type SubnetRegTokenOK
const SubnetRegTokenOKCode int = 200
/*SubnetRegTokenOK A successful response.
swagger:response subnetRegTokenOK
*/
type SubnetRegTokenOK struct {
/*
In: Body
*/
Payload *models.SubnetRegTokenResponse `json:"body,omitempty"`
}
// NewSubnetRegTokenOK creates SubnetRegTokenOK with default headers values
func NewSubnetRegTokenOK() *SubnetRegTokenOK {
return &SubnetRegTokenOK{}
}
// WithPayload adds the payload to the subnet reg token o k response
func (o *SubnetRegTokenOK) WithPayload(payload *models.SubnetRegTokenResponse) *SubnetRegTokenOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the subnet reg token o k response
func (o *SubnetRegTokenOK) SetPayload(payload *models.SubnetRegTokenResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SubnetRegTokenOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*SubnetRegTokenDefault Generic error response.
swagger:response subnetRegTokenDefault
*/
type SubnetRegTokenDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewSubnetRegTokenDefault creates SubnetRegTokenDefault with default headers values
func NewSubnetRegTokenDefault(code int) *SubnetRegTokenDefault {
if code <= 0 {
code = 500
}
return &SubnetRegTokenDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the subnet reg token default response
func (o *SubnetRegTokenDefault) WithStatusCode(code int) *SubnetRegTokenDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the subnet reg token default response
func (o *SubnetRegTokenDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the subnet reg token default response
func (o *SubnetRegTokenDefault) WithPayload(payload *models.Error) *SubnetRegTokenDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the subnet reg token default response
func (o *SubnetRegTokenDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SubnetRegTokenDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// SubnetRegTokenURL generates an URL for the subnet reg token operation
type SubnetRegTokenURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *SubnetRegTokenURL) WithBasePath(bp string) *SubnetRegTokenURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *SubnetRegTokenURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *SubnetRegTokenURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/subnet/registration-token"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *SubnetRegTokenURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *SubnetRegTokenURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *SubnetRegTokenURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on SubnetRegTokenURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on SubnetRegTokenURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *SubnetRegTokenURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,88 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// SubnetRegisterHandlerFunc turns a function with the right signature into a subnet register handler
type SubnetRegisterHandlerFunc func(SubnetRegisterParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn SubnetRegisterHandlerFunc) Handle(params SubnetRegisterParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// SubnetRegisterHandler interface for that can handle valid subnet register params
type SubnetRegisterHandler interface {
Handle(SubnetRegisterParams, *models.Principal) middleware.Responder
}
// NewSubnetRegister creates a new http.Handler for the subnet register operation
func NewSubnetRegister(ctx *middleware.Context, handler SubnetRegisterHandler) *SubnetRegister {
return &SubnetRegister{Context: ctx, Handler: handler}
}
/* SubnetRegister swagger:route POST /subnet/register AdminAPI subnetRegister
Register cluster with Subnet
*/
type SubnetRegister struct {
Context *middleware.Context
Handler SubnetRegisterHandler
}
func (o *SubnetRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewSubnetRegisterParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,102 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewSubnetRegisterParams creates a new SubnetRegisterParams object
//
// There are no default values defined in the spec.
func NewSubnetRegisterParams() SubnetRegisterParams {
return SubnetRegisterParams{}
}
// SubnetRegisterParams contains all the bound params for the subnet register operation
// typically these are obtained from a http.Request
//
// swagger:parameters SubnetRegister
type SubnetRegisterParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.SubnetRegisterRequest
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewSubnetRegisterParams() beforehand.
func (o *SubnetRegisterParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.SubnetRegisterRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(context.Background())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,113 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// SubnetRegisterOKCode is the HTTP code returned for type SubnetRegisterOK
const SubnetRegisterOKCode int = 200
/*SubnetRegisterOK A successful response.
swagger:response subnetRegisterOK
*/
type SubnetRegisterOK struct {
}
// NewSubnetRegisterOK creates SubnetRegisterOK with default headers values
func NewSubnetRegisterOK() *SubnetRegisterOK {
return &SubnetRegisterOK{}
}
// WriteResponse to the client
func (o *SubnetRegisterOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(200)
}
/*SubnetRegisterDefault Generic error response.
swagger:response subnetRegisterDefault
*/
type SubnetRegisterDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewSubnetRegisterDefault creates SubnetRegisterDefault with default headers values
func NewSubnetRegisterDefault(code int) *SubnetRegisterDefault {
if code <= 0 {
code = 500
}
return &SubnetRegisterDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the subnet register default response
func (o *SubnetRegisterDefault) WithStatusCode(code int) *SubnetRegisterDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the subnet register default response
func (o *SubnetRegisterDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the subnet register default response
func (o *SubnetRegisterDefault) WithPayload(payload *models.Error) *SubnetRegisterDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the subnet register default response
func (o *SubnetRegisterDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SubnetRegisterDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// SubnetRegisterURL generates an URL for the subnet register operation
type SubnetRegisterURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *SubnetRegisterURL) WithBasePath(bp string) *SubnetRegisterURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *SubnetRegisterURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *SubnetRegisterURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/subnet/register"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *SubnetRegisterURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *SubnetRegisterURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *SubnetRegisterURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on SubnetRegisterURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on SubnetRegisterURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *SubnetRegisterURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -350,8 +350,20 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
UserAPIShareObjectHandler: user_api.ShareObjectHandlerFunc(func(params user_api.ShareObjectParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.ShareObject has not yet been implemented")
}),
AdminAPISubscriptionInfoHandler: admin_api.SubscriptionInfoHandlerFunc(func(params admin_api.SubscriptionInfoParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.SubscriptionInfo has not yet been implemented")
AdminAPISubnetInfoHandler: admin_api.SubnetInfoHandlerFunc(func(params admin_api.SubnetInfoParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.SubnetInfo has not yet been implemented")
}),
AdminAPISubnetLoginHandler: admin_api.SubnetLoginHandlerFunc(func(params admin_api.SubnetLoginParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.SubnetLogin has not yet been implemented")
}),
AdminAPISubnetLoginMFAHandler: admin_api.SubnetLoginMFAHandlerFunc(func(params admin_api.SubnetLoginMFAParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.SubnetLoginMFA has not yet been implemented")
}),
AdminAPISubnetRegTokenHandler: admin_api.SubnetRegTokenHandlerFunc(func(params admin_api.SubnetRegTokenParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.SubnetRegToken has not yet been implemented")
}),
AdminAPISubnetRegisterHandler: admin_api.SubnetRegisterHandlerFunc(func(params admin_api.SubnetRegisterParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.SubnetRegister has not yet been implemented")
}),
AdminAPITiersListHandler: admin_api.TiersListHandlerFunc(func(params admin_api.TiersListParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.TiersList has not yet been implemented")
@@ -616,8 +628,16 @@ type ConsoleAPI struct {
AdminAPISetPolicyMultipleHandler admin_api.SetPolicyMultipleHandler
// UserAPIShareObjectHandler sets the operation handler for the share object operation
UserAPIShareObjectHandler user_api.ShareObjectHandler
// AdminAPISubscriptionInfoHandler sets the operation handler for the subscription info operation
AdminAPISubscriptionInfoHandler admin_api.SubscriptionInfoHandler
// AdminAPISubnetInfoHandler sets the operation handler for the subnet info operation
AdminAPISubnetInfoHandler admin_api.SubnetInfoHandler
// AdminAPISubnetLoginHandler sets the operation handler for the subnet login operation
AdminAPISubnetLoginHandler admin_api.SubnetLoginHandler
// AdminAPISubnetLoginMFAHandler sets the operation handler for the subnet login m f a operation
AdminAPISubnetLoginMFAHandler admin_api.SubnetLoginMFAHandler
// AdminAPISubnetRegTokenHandler sets the operation handler for the subnet reg token operation
AdminAPISubnetRegTokenHandler admin_api.SubnetRegTokenHandler
// AdminAPISubnetRegisterHandler sets the operation handler for the subnet register operation
AdminAPISubnetRegisterHandler admin_api.SubnetRegisterHandler
// AdminAPITiersListHandler sets the operation handler for the tiers list operation
AdminAPITiersListHandler admin_api.TiersListHandler
// UserAPIUpdateBucketLifecycleHandler sets the operation handler for the update bucket lifecycle operation
@@ -1002,8 +1022,20 @@ func (o *ConsoleAPI) Validate() error {
if o.UserAPIShareObjectHandler == nil {
unregistered = append(unregistered, "user_api.ShareObjectHandler")
}
if o.AdminAPISubscriptionInfoHandler == nil {
unregistered = append(unregistered, "admin_api.SubscriptionInfoHandler")
if o.AdminAPISubnetInfoHandler == nil {
unregistered = append(unregistered, "admin_api.SubnetInfoHandler")
}
if o.AdminAPISubnetLoginHandler == nil {
unregistered = append(unregistered, "admin_api.SubnetLoginHandler")
}
if o.AdminAPISubnetLoginMFAHandler == nil {
unregistered = append(unregistered, "admin_api.SubnetLoginMFAHandler")
}
if o.AdminAPISubnetRegTokenHandler == nil {
unregistered = append(unregistered, "admin_api.SubnetRegTokenHandler")
}
if o.AdminAPISubnetRegisterHandler == nil {
unregistered = append(unregistered, "admin_api.SubnetRegisterHandler")
}
if o.AdminAPITiersListHandler == nil {
unregistered = append(unregistered, "admin_api.TiersListHandler")
@@ -1508,7 +1540,23 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/subscription/info"] = admin_api.NewSubscriptionInfo(o.context, o.AdminAPISubscriptionInfoHandler)
o.handlers["GET"]["/subnet/info"] = admin_api.NewSubnetInfo(o.context, o.AdminAPISubnetInfoHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/subnet/login"] = admin_api.NewSubnetLogin(o.context, o.AdminAPISubnetLoginHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/subnet/login/mfa"] = admin_api.NewSubnetLoginMFA(o.context, o.AdminAPISubnetLoginMFAHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/subnet/registration-token"] = admin_api.NewSubnetRegToken(o.context, o.AdminAPISubnetRegTokenHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/subnet/register"] = admin_api.NewSubnetRegister(o.context, o.AdminAPISubnetRegisterHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}

View File

@@ -19,7 +19,7 @@ securityDefinitions:
tokenUrl: http://min.io
# Apply the key security definition to all APIs
security:
- key: []
- key: [ ]
paths:
/login:
get:
@@ -35,7 +35,7 @@ paths:
schema:
$ref: "#/definitions/error"
# Exclude this API from the authentication requirement
security: []
security: [ ]
tags:
- UserAPI
post:
@@ -55,7 +55,7 @@ paths:
schema:
$ref: "#/definitions/error"
# Exclude this API from the authentication requirement
security: []
security: [ ]
tags:
- UserAPI
/login/oauth2/auth:
@@ -75,7 +75,7 @@ paths:
description: Generic error response.
schema:
$ref: "#/definitions/error"
security: []
security: [ ]
tags:
- UserAPI
@@ -2067,11 +2067,25 @@ paths:
$ref: "#/definitions/error"
tags:
- AdminAPI
/subscription/info:
/subnet/registration-token:
get:
summary: Subscription info
operationId: SubscriptionInfo
summary: Subnet registraton token
operationId: SubnetRegToken
responses:
200:
description: A successful response.
schema:
$ref: "#/definitions/SubnetRegTokenResponse"
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- AdminAPI
/subnet/info:
get:
summary: Subnet info
operationId: SubnetInfo
responses:
200:
description: A successful response.
@@ -2083,6 +2097,71 @@ paths:
$ref: "#/definitions/error"
tags:
- AdminAPI
/subnet/register:
post:
summary: Register cluster with Subnet
operationId: SubnetRegister
parameters:
- name: body
in: body
required: true
schema:
$ref: "#/definitions/subnetRegisterRequest"
responses:
200:
description: A successful response.
# schema:
# $ref: "#/definitions/subnetRegisterResponse"
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- AdminAPI
/subnet/login:
post:
summary: Login to subnet
operationId: SubnetLogin
parameters:
- name: body
in: body
required: true
schema:
$ref: "#/definitions/subnetLoginRequest"
responses:
200:
description: A successful response.
schema:
$ref: "#/definitions/subnetLoginResponse"
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- AdminAPI
/subnet/login/mfa:
post:
summary: Login to subnet using mfa
operationId: SubnetLoginMFA
parameters:
- name: body
in: body
required: true
schema:
$ref: "#/definitions/subnetLoginMFARequest"
responses:
200:
description: A successful response.
schema:
$ref: "#/definitions/subnetLoginResponse"
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- AdminAPI
/admin/info:
get:
@@ -2386,7 +2465,7 @@ paths:
- name: order
in: query
type: string
enum: [timeDesc, timeAsc]
enum: [ timeDesc, timeAsc ]
default: timeDesc
- name: timeStart
in: query
@@ -3152,7 +3231,7 @@ definitions:
properties:
loginStrategy:
type: string
enum: [form, redirect, service-account]
enum: [ form, redirect, service-account ]
redirect:
type: string
loginOauth2AuthRequest:
@@ -3238,7 +3317,7 @@ definitions:
type: string
status:
type: string
enum: [ok]
enum: [ ok ]
operator:
type: boolean
distributedMode:
@@ -3259,7 +3338,7 @@ definitions:
type: string
values:
type: array
items: {}
items: { }
resultTarget:
type: object
properties:
@@ -3539,7 +3618,7 @@ definitions:
type: string
service:
type: string
enum: [replication]
enum: [ replication ]
syncMode:
type: string
bandwidth:
@@ -4015,4 +4094,75 @@ definitions:
properties:
objectMetadata:
type: object
additionalProperties: true
additionalProperties: true
subnetLoginResponse:
type: object
properties:
access_token:
type: string
organizations:
type: array
items:
$ref: "#/definitions/subnetOrganization"
mfa_token:
type: string
registered:
type: boolean
subnetLoginRequest:
type: object
properties:
username:
type: string
password:
type: string
apiKey:
type: string
subnetLoginMFARequest:
type: object
required:
- username
- otp
- mfa_token
properties:
username:
type: string
otp:
type: string
mfa_token:
type: string
subnetRegisterRequest:
type: object
required:
- token
- account_id
properties:
token:
type: string
account_id:
type: string
SubnetRegTokenResponse:
type: object
properties:
regToken:
type: string
subnetOrganization:
type: object
properties:
userId:
type: integer
accountId:
type: integer
subscriptionStatus:
type: string
isAccountOwner:
type: boolean
company:
type: string
shortName:
type: string