console license page improvements and fixes (#647)

- fixed issue when deploying tenant with tls disabled
- applied new design for tenant details and license screens
- added license refresh job to operator console
- added new refresh license endpoint
- console operator not longer store CONSOLE_ACCESS_KEY and
  CONSOLE_SECRET_KEY values in the tenant-console-secret

Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
This commit is contained in:
Lenin Alevski
2021-03-22 11:08:31 -07:00
committed by GitHub
parent 2a704d3d59
commit 7ce36bac42
29 changed files with 1382 additions and 135 deletions

View File

@@ -21,6 +21,7 @@ import (
"log"
"os"
"path/filepath"
"time"
"github.com/go-openapi/loads"
"github.com/jessevdk/go-flags"
@@ -135,6 +136,28 @@ func startServer(ctx *cli.Context) error {
server.ConfigureAPI()
// subnet license refresh process
go func() {
failedAttempts := 0
for {
if err := restapi.RefreshLicense(); err != nil {
log.Println(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)
}
}()
if err := server.Serve(); err != nil {
log.Fatalln(err)
}

2
go.mod
View File

@@ -21,7 +21,7 @@ require (
github.com/minio/mc v0.0.0-20210301162250-f9d36f9b5243
github.com/minio/minio v0.0.0-20210301203133-e8d8dfa3ae8f
github.com/minio/minio-go/v7 v7.0.10
github.com/minio/operator v0.0.0-20210201110528-753019b838b4
github.com/minio/operator v0.0.0-20210317030027-207337abe7fd
github.com/minio/operator/logsearchapi v0.0.0-20210201110528-753019b838b4
github.com/minio/selfupdate v0.3.1
github.com/mitchellh/go-homedir v1.1.0

6
go.sum
View File

@@ -895,7 +895,7 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/minio/cli v1.22.0 h1:VTQm7lmXm3quxO917X3p+el1l0Ca5X3S4PM2ruUYO68=
github.com/minio/cli v1.22.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
github.com/minio/controller-tools v0.4.6/go.mod h1:xES4iNis9dGrLQuP6nquTZZNg2T0/EM8wduC4/GWfZ0=
github.com/minio/controller-tools v0.4.7/go.mod h1:xES4iNis9dGrLQuP6nquTZZNg2T0/EM8wduC4/GWfZ0=
github.com/minio/direct-csi v1.2.8 h1:jOpefwTGZYUIzouz5McSQ8EqeXO7Qu5aCJIwstbc6vs=
github.com/minio/direct-csi v1.2.8/go.mod h1:+Zw8NjMQ5rQqySezan6G9DsQjHSHQCPWJli3v9dP80o=
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
@@ -917,8 +917,8 @@ github.com/minio/minio-go/v7 v7.0.7-0.20201217170524-3baf9ea06f7c/go.mod h1:pEZB
github.com/minio/minio-go/v7 v7.0.8-0.20210127003153-c40722862654/go.mod h1:pEZBUa+L2m9oECoIA6IcSK8bv/qggtQVLovjeKK5jYc=
github.com/minio/minio-go/v7 v7.0.10 h1:1oUKe4EOPUEhw2qnPQaPsJ0lmVTYLFu03SiItauXs94=
github.com/minio/minio-go/v7 v7.0.10/go.mod h1:td4gW1ldOsj1PbSNS+WYK43j+P1XVhX/8W8awaYlBFo=
github.com/minio/operator v0.0.0-20210201110528-753019b838b4 h1:2TtnWOrVkMC8N/wLWwlnsEIMOHpZOIsF8JZ0cPDI1m0=
github.com/minio/operator v0.0.0-20210201110528-753019b838b4/go.mod h1:xjLK0CsJr9Zo0AUdgpsnBbTjHyM5XXr15JM/MHNBppI=
github.com/minio/operator v0.0.0-20210317030027-207337abe7fd h1:ibM7ebC9WdtgJh6p3mqbU0W1vpoFXqJgTHDBKJWGRQI=
github.com/minio/operator v0.0.0-20210317030027-207337abe7fd/go.mod h1:1Bpqm6g8f30YeePauZfSet7EWrWeyqu1eJhY5/4sn28=
github.com/minio/operator/logsearchapi v0.0.0-20210201110528-753019b838b4 h1:7HNd0WPMFcQzQbgPs7VQJfiXVm8xjuxnS3/1yi4twwM=
github.com/minio/operator/logsearchapi v0.0.0-20210201110528-753019b838b4/go.mod h1:ngzK3RurLvshJ4XmJ6eP4WTOIc9Vu1HQNq0Hm6XOZmw=
github.com/minio/selfupdate v0.3.1 h1:BWEFSNnrZVMUWXbXIgLDNDjbejkmpAmZvy/nCz1HlEs=

View File

@@ -30,7 +30,8 @@ const (
// Constants for subnet configuration
ConsoleSubnetURL = "CONSOLE_SUBNET_URL"
// Subnet endpoints
publicKey = "/downloads/license-pubkey.pem"
loginEndpoint = "/api/auth/login"
licenseKeyEndpoint = "/api/auth/subscription/license-key"
publicKey = "/downloads/license-pubkey.pem"
loginEndpoint = "/api/auth/login"
refreshLicenseKeyEndpoint = "/api/auth/subscription/renew-license"
licenseKeyEndpoint = "/api/auth/subscription/license-key"
)

View File

@@ -67,6 +67,41 @@ type subnetLicenseResponse struct {
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,
}
// http body for login request
payloadBytes, err := json.Marshal(request)
if err != nil {
return "", 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) {
@@ -171,3 +206,18 @@ func ValidateLicense(client cluster.HTTPClientI, licenseKey, email, password str
}
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
}
licenseInfo, rawLicense, err := ValidateLicense(client, license, "", "")
if err != nil {
return nil, "", err
}
return licenseInfo, rawLicense, nil
}
return nil, "", errors.New("invalid license")
}

View File

@@ -9,7 +9,7 @@
"static/js/2.33fe03f5.chunk.js": "/static/js/2.33fe03f5.chunk.js",
"static/js/2.33fe03f5.chunk.js.map": "/static/js/2.33fe03f5.chunk.js.map",
"index.html": "/index.html",
"static/css/2.76b14b73.chunk.css.map": "/static/css/2.76b14b73.chunk.css.map",
"static/css/2.f324abd6.chunk.css.map": "/static/css/2.f324abd6.chunk.css.map",
"static/css/main.a19f3d53.chunk.css.map": "/static/css/main.a19f3d53.chunk.css.map",
"static/js/2.33fe03f5.chunk.js.LICENSE.txt": "/static/js/2.33fe03f5.chunk.js.LICENSE.txt",
"static/media/minio_console_logo.0837460e.svg": "/static/media/minio_console_logo.0837460e.svg",

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,271 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
* Chart.js v2.9.4
* https://www.chartjs.org
* (c) 2020 Chart.js Contributors
* Released under the MIT License
*/
/*!
* cookie
* Copyright(c) 2012-2014 Roman Shtylman
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/
/*! Conditions:: INITIAL */
/*! Moment Duration Format v2.2.2
* https://github.com/jsmreese/moment-duration-format
* Date: 2018-02-16
*
* Duration format plugin function for the Moment.js library
* http://momentjs.com/
*
* Copyright 2018 John Madhavan-Reese
* Released under the MIT license
*/
/*! Production:: $accept : expression $end */
/*! Production:: css_value : ANGLE */
/*! Production:: css_value : CHS */
/*! Production:: css_value : EMS */
/*! Production:: css_value : EXS */
/*! Production:: css_value : FREQ */
/*! Production:: css_value : LENGTH */
/*! Production:: css_value : PERCENTAGE */
/*! Production:: css_value : REMS */
/*! Production:: css_value : RES */
/*! Production:: css_value : SUB css_value */
/*! Production:: css_value : TIME */
/*! Production:: css_value : VHS */
/*! Production:: css_value : VMAXS */
/*! Production:: css_value : VMINS */
/*! Production:: css_value : VWS */
/*! Production:: css_variable : CSS_VAR LPAREN CSS_CPROP COMMA math_expression RPAREN */
/*! Production:: css_variable : CSS_VAR LPAREN CSS_CPROP RPAREN */
/*! Production:: expression : math_expression EOF */
/*! Production:: math_expression : LPAREN math_expression RPAREN */
/*! Production:: math_expression : NESTED_CALC LPAREN math_expression RPAREN */
/*! Production:: math_expression : SUB PREFIX SUB NESTED_CALC LPAREN math_expression RPAREN */
/*! Production:: math_expression : css_value */
/*! Production:: math_expression : css_variable */
/*! Production:: math_expression : math_expression ADD math_expression */
/*! Production:: math_expression : math_expression DIV math_expression */
/*! Production:: math_expression : math_expression MUL math_expression */
/*! Production:: math_expression : math_expression SUB math_expression */
/*! Production:: math_expression : value */
/*! Production:: value : NUMBER */
/*! Production:: value : SUB NUMBER */
/*! Rule:: $ */
/*! Rule:: (--[0-9a-z-A-Z-]*) */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)% */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)Hz\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)ch\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)cm\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)deg\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)dpcm\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)dpi\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)dppx\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)em\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)ex\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)grad\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)in\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)kHz\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)mm\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)ms\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)pc\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)pt\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)px\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)rad\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)rem\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)s\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)turn\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)vh\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)vmax\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)vmin\b */
/*! Rule:: ([0-9]+(\.[0-9]*)?|\.[0-9]+)vw\b */
/*! Rule:: ([a-z]+) */
/*! Rule:: (calc) */
/*! Rule:: (var) */
/*! Rule:: , */
/*! Rule:: - */
/*! Rule:: \( */
/*! Rule:: \) */
/*! Rule:: \* */
/*! Rule:: \+ */
/*! Rule:: \/ */
/*! Rule:: \s+ */
/*! decimal.js-light v2.5.1 https://github.com/MikeMcl/decimal.js-light/LICENCE */
/**
* A better abstraction over CSS.
*
* @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present
* @website https://github.com/cssinjs/jss
* @license MIT
*/
/** @license React v0.20.1
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.1
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.1
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.1
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**!
* @fileOverview Kickass library to create and place poppers near their reference elements.
* @version 1.16.1-lts
* @license
* Copyright (c) 2016 Federico Zivolo and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
//! moment.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -28,7 +28,7 @@ import { planDetails, planItems, planButtons } from "./utils";
import ActivationModal from "./ActivationModal";
import api from "../../../common/api";
import { LicenseInfo } from "./types";
import { LinearProgress } from "@material-ui/core";
import { CircularProgress, LinearProgress } from "@material-ui/core";
import { AppState } from "../../../store";
import { connect } from "react-redux";
import { niceBytes } from "../../../common/utils";
@@ -50,6 +50,7 @@ const styles = (theme: Theme) =>
padding: "20px 52px 20px 28px",
},
licenseContainer: {
position: "relative",
padding: "20px 52px 0px 28px",
background: "#032F51",
boxShadow: "0px 3px 7px #00000014",
@@ -94,7 +95,7 @@ const styles = (theme: Theme) =>
marginBottom: 26,
paddingTop: 18,
},
currentPlan: {
activePlanHeader: {
fontWeight: 700,
background: "#D5DDE5",
borderRadius: "3px 3px 0px 0px",
@@ -103,6 +104,12 @@ const styles = (theme: Theme) =>
borderTop: "1px solid #D5DDE5",
marginTop: -2,
},
planHeader: {
background: "#FFFFFF",
borderRadius: "3px 3px 0px 0px",
padding: 8,
borderTop: "1px solid #D5DDE5",
},
detailsPrice: {
fontSize: 13,
fontWeight: 700,
@@ -198,6 +205,16 @@ const styles = (theme: Theme) =>
textDecoration: "underline",
cursor: "pointer",
},
subnetRefreshLicenseLink: {
color: "#1C5A8D",
fontWeight: "bold",
clear: "both",
background: "none",
border: "none",
textDecoration: "underline",
cursor: "pointer",
fontSize: 13,
},
fullWidth: {
width: "100%",
height: "100%",
@@ -213,14 +230,16 @@ const styles = (theme: Theme) =>
height: "100%",
borderRadius: "0px 3px 0px 0px !important",
},
licenseInfo: { color: "#FFFFFF" },
licenseInfo: { color: "#FFFFFF", position: "relative" },
licenseInfoTitle: {
textTransform: "none",
color: "#FFFFFF",
color: "#BFBFBF",
fontSize: 11,
},
licenseInfoValue: {
textTransform: "none",
fontSize: 17,
fontSize: 14,
fontWeight: "bold",
},
licenseDescription: {
background: "#032F51",
@@ -228,6 +247,10 @@ const styles = (theme: Theme) =>
borderTop: "1px solid #e2e5e4",
borderLeft: "1px solid #e2e5e4",
borderRight: "1px solid #e2e5e4",
bottom: 0,
left: "5%",
right: "5%",
position: "absolute",
},
currentPlanBG: {
background: "#022A4A 0% 0% no-repeat padding-box",
@@ -244,6 +267,18 @@ const styles = (theme: Theme) =>
planItemsPadding: {
padding: "23px 33px",
},
subnetSubTitle: {
fontSize: 12,
},
verifiedIcon: {
width: 96,
position: "absolute",
right: 0,
bottom: 29,
},
loadingLoginStrategy: {
textAlign: "center",
},
...containerForHeader(theme.spacing(4)),
});
@@ -278,6 +313,27 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
setLoadingLicenseInfo(false);
});
};
const refreshLicense = () => {
setLoadingRefreshLicense(true);
api
.invoke("POST", `/api/v1/subscription/refresh`, {})
.then((res: LicenseInfo) => {
if (res) {
if (res.plan === "STANDARD") {
setCurrentPlanID(1);
} else if (res.plan === "ENTERPRISE") {
setCurrentPlanID(2);
} else {
setCurrentPlanID(1);
}
setLicenseInfo(res);
}
setLoadingRefreshLicense(false);
})
.catch((err: any) => {
setLoadingRefreshLicense(false);
});
};
const [activateProductModal, setActivateProductModal] = useState<boolean>(
false
@@ -286,6 +342,9 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
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();
@@ -303,11 +362,9 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
<React.Fragment>
<PageHeader label="License" />
<Grid container>
<Grid item xs={operatorMode ? 12 : 6} className={classes.container}>
<Grid item xs={12} className={classes.container}>
<Paper
className={`${classes.licenseContainer} ${
operatorMode ? classes.midWidth : classes.fullWidth
}`}
className={`${classes.licenseContainer} ${classes.midWidth}`}
>
{licenseInfo ? (
<React.Fragment>
@@ -378,7 +435,9 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
gutterBottom
className={classes.licenseInfoValue}
>
<Moment>{licenseInfo.expires_at}</Moment>
<Moment format="YYYY-MM-DD">
{licenseInfo.expires_at}
</Moment>
</Typography>
</Grid>
<Grid item xs={6}>
@@ -415,11 +474,15 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
{licenseInfo.email}
</Typography>
</Grid>
<img
className={classes.verifiedIcon}
src={"/verified.svg"}
/>
</Grid>
</React.Fragment>
) : (
<React.Fragment>
<img src="agpl.svg" height={40} alt="agpl" />
<img src="/agpl.svg" height={40} alt="agpl" />
<Typography component="h2" variant="h6">
GNU Affero General Public License
</Typography>
@@ -441,50 +504,96 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
</React.Fragment>
)}
</Paper>
{
<Paper className={`${classes.paper} ${classes.smallWidth}`}>
<Typography
component="h2"
variant="h6"
className={classes.pageTitle}
>
Choosing between GNU AGPL v3 and Commercial License
</Typography>
<Typography component="h6">
If you are building proprietary applications, you may want to
choose the commercial license included as part of the Standard
and Enterprise subscription plans. Applications must otherwise
comply with all the GNU AGPLv3 License & Trademark
obligations. Follow the links below to learn more about the
compliance policy.
</Typography>
<br />
<a
href="https://min.io/compliance?ref=op"
className={classes.openSourcePolicy}
target="_blank"
rel="nofollow noopener noreferrer"
>
Open Source Policy Compliance
</a>
<br />
<br />
<a
href="https://min.io/logo?ref=op"
className={classes.openSourcePolicy}
target="_blank"
rel="nofollow noopener noreferrer"
>
Trademark Policy
</a>
</Paper>
}
<Paper className={`${classes.paper} ${classes.smallWidth}`}>
{licenseInfo ? (
<React.Fragment>
{" "}
<Typography
component="h2"
variant="h6"
className={classes.pageTitle}
>
Login to MinIO SUBNET !
</Typography>
<Typography component="h6" className={classes.subnetSubTitle}>
It combines a commercial license with a support experience
unlike any other.
</Typography>
<br />
<Button
variant="contained"
color="primary"
className={classes.button}
target="_blank"
rel="noopener noreferrer"
href="https://subnet.min.io/support/?ref=op"
>
Login to SUBNET
</Button>
{operatorMode && (
<React.Fragment>
{" "}
<br />
<br />
<button
className={classes.subnetRefreshLicenseLink}
onClick={(e) => {
e.preventDefault();
refreshLicense();
}}
>
Refresh Licence
</button>
{loadingRefreshLicense && (
<CircularProgress
size={16}
className={classes.loadingLoginStrategy}
/>
)}
</React.Fragment>
)}
</React.Fragment>
) : (
<React.Fragment>
<Typography
component="h2"
variant="h6"
className={classes.pageTitle}
>
Choosing between GNU AGPL v3 and Commercial License
</Typography>
<Typography component="h6">
If you are building proprietary applications, you may want
to choose the commercial license included as part of the
Standard and Enterprise subscription plans. Applications
must otherwise comply with all the GNU AGPLv3 License &
Trademark obligations. Follow the links below to learn more
about the compliance policy.
</Typography>
<br />
<a
href="https://min.io/compliance?ref=op"
className={classes.openSourcePolicy}
target="_blank"
rel="nofollow noopener noreferrer"
>
Open Source Policy Compliance
</a>
<br />
<br />
<a
href="https://min.io/logo?ref=op"
className={classes.openSourcePolicy}
target="_blank"
rel="nofollow noopener noreferrer"
>
Trademark Policy
</a>
</React.Fragment>
)}
</Paper>
</Grid>
<Grid
item
xs={12}
className={clsx(classes.container, classes.planItemsPadding)}
>
<Grid item xs={12} className={clsx(classes.planItemsPadding)}>
<Paper
className={classes.paper}
style={{ borderRadius: "0px 0px 3px 3px" }}
@@ -521,11 +630,17 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
currentPlan ? classes.currentPlanBG : ""
)}
>
{currentPlan ? (
<Grid item xs={12} className={classes.currentPlan}>
Current Plan
</Grid>
) : null}
<Grid
item
xs={12}
className={
currentPlan
? classes.activePlanHeader
: classes.planHeader
}
>
{currentPlan ? "Current Plan" : "\u00A0"}
</Grid>
<Grid item xs={12} className={classes.detailsTitle}>
{details.title}
</Grid>

View File

@@ -148,6 +148,7 @@ const AddTenant = ({
const gcpPrivateKeyID = fields.encryption.gcpPrivateKeyID;
const gcpPrivateKey = fields.encryption.gcpPrivateKey;
const enableAutoCert = fields.security.enableAutoCert;
const enableTLS = fields.security.enableTLS;
const ecParity = fields.tenantSize.ecParity;
const distribution = fields.tenantSize.distribution;
const memorySize = fields.tenantSize.memorySize;
@@ -167,7 +168,7 @@ const AddTenant = ({
namespace: namespace,
access_key: "",
secret_key: "",
enable_tls: enableAutoCert,
enable_tls: enableTLS && enableAutoCert,
enable_console: true,
enable_prometheus: true,
service_name: "",
@@ -221,7 +222,7 @@ const AddTenant = ({
};
}
if (minioCertificates.length > 0) {
if (enableTLS && minioCertificates.length > 0) {
tenantCerts = {
minio: minioCertificates
.map((keyPair: KeyPair) => ({
@@ -233,6 +234,7 @@ const AddTenant = ({
}
if (
enableTLS &&
consoleCertificate.encoded_cert !== "" &&
consoleCertificate.encoded_key !== ""
) {

View File

@@ -42,6 +42,7 @@ import PencilIcon from "../../Common/TableWrapper/TableActionIcons/PencilIcon";
import { LicenseInfo } from "../../License/types";
import { Link } from "react-router-dom";
import { setErrorSnackMessage } from "../../../../actions";
import Moment from "react-moment";
interface ITenantDetailsProps {
classes: any;
@@ -118,6 +119,44 @@ const styles = (theme: Theme) =>
poolLabel: {
color: "#666666",
},
licenseContainer: {
position: "relative",
padding: "20px 52px 0px 28px",
background: "#032F51",
boxShadow: "0px 3px 7px #00000014",
"& h2": {
color: "#FFF",
marginBottom: 67,
},
"& a": {
textDecoration: "none",
},
"& h3": {
color: "#FFFFFF",
marginBottom: "30px",
fontWeight: "bold",
},
"& h6": {
color: "#FFFFFF !important",
},
},
licenseInfo: { color: "#FFFFFF", position: "relative" },
licenseInfoTitle: {
textTransform: "none",
color: "#BFBFBF",
fontSize: 11,
},
licenseInfoValue: {
textTransform: "none",
fontSize: 14,
fontWeight: "bold",
},
verifiedIcon: {
width: 96,
position: "absolute",
right: 0,
bottom: 29,
},
...modalBasic,
...containerForHeader(theme.spacing(4)),
});
@@ -432,34 +471,130 @@ const TenantDetails = ({
<React.Fragment>
<Grid container>
<Grid item xs={12}>
<Paper className={classes.paper}>
<Paper
className={
tenant && tenant.subnet_license
? classes.licenseContainer
: ""
}
>
{tenant && tenant.subnet_license ? (
<Grid className={classes.paperContainer}>
<Typography
component="h2"
variant="h6"
className={classes.pageTitle}
>
Subscription Information
</Typography>
Account ID: {tenant.subnet_license.account_id}
<br />
<br />
Email: {tenant.subnet_license.email}
<br />
<br />
Plan: {tenant.subnet_license.plan}
<br />
<br />
Organization: {tenant.subnet_license.organization}
<br />
<br />
Storage Capacity:{" "}
{tenant.subnet_license.storage_capacity}
<br />
<br />
Expiration: {tenant.subnet_license.expires_at}
</Grid>
<React.Fragment>
<Grid container className={classes.licenseInfo}>
<Grid item xs={6}>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
License
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
Commercial License
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Organization
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{tenant.subnet_license.organization}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Registered Capacity
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{niceBytes(
(
tenant.subnet_license.storage_capacity *
1099511627776
) // 1 Terabyte = 1099511627776 Bytes
.toString(10)
)}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Expiry Date
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
<Moment format="YYYY-MM-DD">
{tenant.subnet_license.expires_at}
</Moment>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Subscription Plan
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{tenant.subnet_license.plan}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Requester
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{tenant.subnet_license.email}
</Typography>
</Grid>
<img
className={classes.verifiedIcon}
src={"/verified.svg"}
/>
</Grid>
</React.Fragment>
) : (
!loadingLicenseInfo && (
<Grid className={classes.paperContainer}>

View File

@@ -19,6 +19,7 @@ package restapi
import (
"context"
"errors"
"fmt"
"log"
"time"
@@ -63,6 +64,40 @@ func registerSubscriptionHandlers(api *operations.ConsoleAPI) {
}
return admin_api.NewSubscriptionInfoOK().WithPayload(license)
})
// Refresh license for k8s cluster
api.AdminAPISubscriptionRefreshHandler = admin_api.SubscriptionRefreshHandlerFunc(func(params admin_api.SubscriptionRefreshParams, session *models.Principal) middleware.Responder {
license, err := getSubscriptionRefreshResponse(session)
if err != nil {
return admin_api.NewSubscriptionRefreshDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewSubscriptionRefreshOK().WithPayload(license)
})
}
// retrieveLicense returns license from K8S secrets (If console is deployed in operator mode) or from
// the configured CONSOLE_SUBNET_LICENSE environment variable
func retrieveLicense(ctx context.Context, sessionToken string) (string, error) {
var license string
// If Console is running in operator mode retrieve License stored in K8s secrets
if acl.GetOperatorMode() {
// 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
}
} else {
// If Console is running in Tenant Admin mode retrieve license from env variable
license = GetSubnetLicense()
}
return license, nil
}
// addSubscriptionLicenseToTenant replace existing console tenant secret and adds the subnet license key
@@ -175,6 +210,47 @@ func saveSubscriptionLicense(ctx context.Context, clientSet K8sClientI, license
return nil
}
// updateTenantLicenseAndRestartConsole
func updateTenantLicenseAndRestartConsole(ctx context.Context, clientSet K8sClientI, license, namespace, tenantName string) error {
consoleSelector := fmt.Sprintf("%s-console", tenantName)
consoleSecretName := fmt.Sprintf("%s-secret", consoleSelector)
// read current console configuration from k8s secrets
currentConsoleSecret, err := clientSet.getSecret(ctx, namespace, consoleSecretName, metav1.GetOptions{})
if err != nil || currentConsoleSecret == nil {
return err
}
secretData := currentConsoleSecret.Data
secretData[ConsoleSubnetLicense] = []byte(license)
// delete existing console configuration from k8s secrets
err = clientSet.deleteSecret(ctx, namespace, consoleSecretName, metav1.DeleteOptions{})
if err != nil {
// log the error if any and continue
log.Println(err)
}
// Save subnet license in k8s secrets
imm := true
consoleConfigSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: consoleSecretName,
},
Immutable: &imm,
Data: secretData,
}
_, err = clientSet.createSecret(ctx, namespace, consoleConfigSecret, metav1.CreateOptions{})
if err != nil {
return err
}
// restart Console pods based on label:
// v1.min.io/console: TENANT-console
err = clientSet.deletePodCollection(ctx, namespace, metav1.DeleteOptions{}, metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s%s", miniov2.ConsoleTenantLabel, tenantName, miniov2.ConsoleName),
})
if err != nil {
return err
}
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)
@@ -237,37 +313,123 @@ func getSubscriptionLicense(ctx context.Context, clientSet K8sClientI, namespace
// 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
var license string
client := &cluster.HTTPClient{
Client: GetConsoleSTSClient(),
}
// If Console is running in operator mode retrieve License stored in K8s secrets
if acl.GetOperatorMode() {
// configure kubernetes client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(errInvalidLicense, nil, err)
}
k8sClient := k8sClient{
client: clientSet,
}
// Get cluster subscription license
license, err = getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName)
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
} else {
// If Console is running in Tenant Admin mode retrieve license from env variable
license = GetSubnetLicense()
licenseKey, err := retrieveLicense(context.Background(), session.STSSessionToken)
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
// validate license key and obtain license info
licenseInfo, _, err := subscriptionValidate(client, license, "", "")
licenseInfo, _, err = subscriptionValidate(client, licenseKey, "", "")
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
return licenseInfo, nil
}
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
}
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: GetConsoleSTSClient(),
}
licenseKey, err := retrieveLicense(context.Background(), 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(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,
}
tenants, err := listTenants(ctx, opClient, "", nil)
if err != nil {
return nil, prepareError(err)
}
// iterate over all tenants, update console configuration and restart console pods
for _, tenant := range tenants.Tenants {
if err := updateTenantLicenseAndRestartConsole(ctx, &k8sClient, licenseRaw, tenant.Namespace, tenant.Name); err != nil {
log.Println(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: GetConsoleSTSClient(),
}
// Attempt to refresh license
_, refreshedLicenseKey, err := subscriptionRefresh(client, licenseKey)
if err != nil {
return err
}
// store new license in memory for console ui
LicenseKey = refreshedLicenseKey
// Update in memory license and update k8s secret
if refreshedLicenseKey != "" {
if acl.GetOperatorMode() {
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,
}
if err = saveSubscriptionLicense(ctx, &k8sClient, refreshedLicenseKey); err != nil {
return err
}
}
}
return nil
}

View File

@@ -739,8 +739,8 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
}
// optionals are set below
var consoleAccess string
var consoleSecret string
var tenantUserAccessKey string
var tenantUserSecretKey string
enableConsole := true
if tenantReq.EnableConsole != nil && *tenantReq.EnableConsole {
@@ -748,16 +748,24 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
if enableConsole {
// provision initial user for tenant
tenantUserAccessKey = RandomCharString(16)
tenantUserSecretKey = RandomCharString(32)
consoleUserSecretName := fmt.Sprintf("%s-user-secret", tenantName)
consoleUserSecretData := map[string][]byte{
"CONSOLE_ACCESS_KEY": []byte(tenantUserAccessKey),
"CONSOLE_SECRET_KEY": []byte(tenantUserSecretKey),
}
_, err := createOrReplaceSecrets(ctx, &k8sClient, ns, []tenantSecret{{Name: consoleUserSecretName, Content: consoleUserSecretData}}, tenantName)
if err != nil {
return nil, prepareError(errorGeneric, nil, err)
}
minInst.Spec.Users = []*corev1.LocalObjectReference{{Name: consoleUserSecretName}}
consoleSelector := fmt.Sprintf("%s-console", tenantName)
consoleSecretName := fmt.Sprintf("%s-secret", consoleSelector)
consoleAccess = RandomCharString(16)
consoleSecret = RandomCharString(32)
consoleSecretData := map[string][]byte{
"CONSOLE_PBKDF_PASSPHRASE": []byte(RandomCharString(16)),
"CONSOLE_PBKDF_SALT": []byte(RandomCharString(8)),
"CONSOLE_ACCESS_KEY": []byte(consoleAccess),
"CONSOLE_SECRET_KEY": []byte(consoleSecret),
}
// If Subnet License is present in k8s secrets, copy that to the CONSOLE_SUBNET_LICENSE env variable
// of the console tenant
@@ -928,8 +936,8 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
// Attach Console Credentials
if enableConsole {
response.Console = &models.CreateTenantResponseConsole{
AccessKey: consoleAccess,
SecretKey: consoleSecret,
AccessKey: tenantUserAccessKey,
SecretKey: tenantUserSecretKey,
}
}
return response, nil

View File

@@ -47,6 +47,9 @@ var (
// SessionDuration cookie validity duration
SessionDuration = 45 * time.Minute
// LicenseKey in memory license key used by console ui
LicenseKey = ""
)
var (
@@ -257,7 +260,13 @@ func getPrometheusURL() string {
// GetSubnetLicense returns the current subnet jwt license
func GetSubnetLicense() string {
return env.Get(ConsoleSubnetLicense, "")
// if we have a license key in memory return that
if LicenseKey != "" {
return LicenseKey
}
// return license configured via environment variable
LicenseKey = env.Get(ConsoleSubnetLicense, "")
return LicenseKey
}
func initVars() {

View File

@@ -2876,12 +2876,35 @@ func init() {
}
}
},
"/subscription/refresh": {
"post": {
"tags": [
"AdminAPI"
],
"summary": "Refresh existing subscription license",
"operationId": "SubscriptionRefresh",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/license"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/subscription/validate": {
"post": {
"tags": [
"AdminAPI"
],
"summary": "Validate a provided subscription license",
"summary": "Validates subscription license",
"operationId": "SubscriptionValidate",
"parameters": [
{
@@ -8570,12 +8593,35 @@ func init() {
}
}
},
"/subscription/refresh": {
"post": {
"tags": [
"AdminAPI"
],
"summary": "Refresh existing subscription license",
"operationId": "SubscriptionRefresh",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/license"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/subscription/validate": {
"post": {
"tags": [
"AdminAPI"
],
"summary": "Validate a provided subscription license",
"summary": "Validates subscription license",
"operationId": "SubscriptionValidate",
"parameters": [
{

View File

@@ -0,0 +1,90 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package 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"
)
// SubscriptionRefreshHandlerFunc turns a function with the right signature into a subscription refresh handler
type SubscriptionRefreshHandlerFunc func(SubscriptionRefreshParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn SubscriptionRefreshHandlerFunc) Handle(params SubscriptionRefreshParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// SubscriptionRefreshHandler interface for that can handle valid subscription refresh params
type SubscriptionRefreshHandler interface {
Handle(SubscriptionRefreshParams, *models.Principal) middleware.Responder
}
// NewSubscriptionRefresh creates a new http.Handler for the subscription refresh operation
func NewSubscriptionRefresh(ctx *middleware.Context, handler SubscriptionRefreshHandler) *SubscriptionRefresh {
return &SubscriptionRefresh{Context: ctx, Handler: handler}
}
/*SubscriptionRefresh swagger:route POST /subscription/refresh AdminAPI subscriptionRefresh
Refresh existing subscription license
*/
type SubscriptionRefresh struct {
Context *middleware.Context
Handler SubscriptionRefreshHandler
}
func (o *SubscriptionRefresh) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewSubscriptionRefreshParams()
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,62 @@
// 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"
)
// NewSubscriptionRefreshParams creates a new SubscriptionRefreshParams object
// no default values defined in spec.
func NewSubscriptionRefreshParams() SubscriptionRefreshParams {
return SubscriptionRefreshParams{}
}
// SubscriptionRefreshParams contains all the bound params for the subscription refresh operation
// typically these are obtained from a http.Request
//
// swagger:parameters SubscriptionRefresh
type SubscriptionRefreshParams 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 NewSubscriptionRefreshParams() beforehand.
func (o *SubscriptionRefreshParams) 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"
)
// SubscriptionRefreshOKCode is the HTTP code returned for type SubscriptionRefreshOK
const SubscriptionRefreshOKCode int = 200
/*SubscriptionRefreshOK A successful response.
swagger:response subscriptionRefreshOK
*/
type SubscriptionRefreshOK struct {
/*
In: Body
*/
Payload *models.License `json:"body,omitempty"`
}
// NewSubscriptionRefreshOK creates SubscriptionRefreshOK with default headers values
func NewSubscriptionRefreshOK() *SubscriptionRefreshOK {
return &SubscriptionRefreshOK{}
}
// WithPayload adds the payload to the subscription refresh o k response
func (o *SubscriptionRefreshOK) WithPayload(payload *models.License) *SubscriptionRefreshOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the subscription refresh o k response
func (o *SubscriptionRefreshOK) SetPayload(payload *models.License) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SubscriptionRefreshOK) 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
}
}
}
/*SubscriptionRefreshDefault Generic error response.
swagger:response subscriptionRefreshDefault
*/
type SubscriptionRefreshDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewSubscriptionRefreshDefault creates SubscriptionRefreshDefault with default headers values
func NewSubscriptionRefreshDefault(code int) *SubscriptionRefreshDefault {
if code <= 0 {
code = 500
}
return &SubscriptionRefreshDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the subscription refresh default response
func (o *SubscriptionRefreshDefault) WithStatusCode(code int) *SubscriptionRefreshDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the subscription refresh default response
func (o *SubscriptionRefreshDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the subscription refresh default response
func (o *SubscriptionRefreshDefault) WithPayload(payload *models.Error) *SubscriptionRefreshDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the subscription refresh default response
func (o *SubscriptionRefreshDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *SubscriptionRefreshDefault) 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"
)
// SubscriptionRefreshURL generates an URL for the subscription refresh operation
type SubscriptionRefreshURL 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 *SubscriptionRefreshURL) WithBasePath(bp string) *SubscriptionRefreshURL {
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 *SubscriptionRefreshURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *SubscriptionRefreshURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/subscription/refresh"
_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 *SubscriptionRefreshURL) 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 *SubscriptionRefreshURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *SubscriptionRefreshURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on SubscriptionRefreshURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on SubscriptionRefreshURL")
}
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 *SubscriptionRefreshURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -50,7 +50,7 @@ func NewSubscriptionValidate(ctx *middleware.Context, handler SubscriptionValida
/*SubscriptionValidate swagger:route POST /subscription/validate AdminAPI subscriptionValidate
Validate a provided subscription license
Validates subscription license
*/
type SubscriptionValidate struct {

View File

@@ -307,6 +307,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
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")
}),
AdminAPISubscriptionRefreshHandler: admin_api.SubscriptionRefreshHandlerFunc(func(params admin_api.SubscriptionRefreshParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.SubscriptionRefresh has not yet been implemented")
}),
AdminAPISubscriptionValidateHandler: admin_api.SubscriptionValidateHandlerFunc(func(params admin_api.SubscriptionValidateParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.SubscriptionValidate has not yet been implemented")
}),
@@ -551,6 +554,8 @@ type ConsoleAPI struct {
AdminAPISubscriptionActivateHandler admin_api.SubscriptionActivateHandler
// AdminAPISubscriptionInfoHandler sets the operation handler for the subscription info operation
AdminAPISubscriptionInfoHandler admin_api.SubscriptionInfoHandler
// AdminAPISubscriptionRefreshHandler sets the operation handler for the subscription refresh operation
AdminAPISubscriptionRefreshHandler admin_api.SubscriptionRefreshHandler
// AdminAPISubscriptionValidateHandler sets the operation handler for the subscription validate operation
AdminAPISubscriptionValidateHandler admin_api.SubscriptionValidateHandler
// AdminAPITenantAddPoolHandler sets the operation handler for the tenant add pool operation
@@ -890,6 +895,9 @@ func (o *ConsoleAPI) Validate() error {
if o.AdminAPISubscriptionInfoHandler == nil {
unregistered = append(unregistered, "admin_api.SubscriptionInfoHandler")
}
if o.AdminAPISubscriptionRefreshHandler == nil {
unregistered = append(unregistered, "admin_api.SubscriptionRefreshHandler")
}
if o.AdminAPISubscriptionValidateHandler == nil {
unregistered = append(unregistered, "admin_api.SubscriptionValidateHandler")
}
@@ -1349,6 +1357,10 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/subscription/refresh"] = admin_api.NewSubscriptionRefresh(o.context, o.AdminAPISubscriptionRefreshHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/subscription/validate"] = admin_api.NewSubscriptionValidate(o.context, o.AdminAPISubscriptionValidateHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)

View File

@@ -1495,7 +1495,7 @@ paths:
/subscription/validate:
post:
summary: Validate a provided subscription license
summary: Validates subscription license
operationId: SubscriptionValidate
parameters:
- name: body
@@ -1515,6 +1515,21 @@ paths:
tags:
- AdminAPI
/subscription/refresh:
post:
summary: Refresh existing subscription license
operationId: SubscriptionRefresh
responses:
200:
description: A successful response.
schema:
$ref: "#/definitions/license"
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- AdminAPI
/subscription/namespaces/{namespace}/tenants/{tenant}/activate:
post: