Compare commits

...

19 Commits

Author SHA1 Message Date
Minio Trusted
b2aa1349f8 update to v0.3.17 2020-08-24 15:27:12 -07:00
Lenin Alevski
8b62aec7fb Added support for prometheus addnotations #293 (#256) 2020-08-24 15:07:36 -07:00
Minio Trusted
83fe33b499 update to v0.3.16 2020-08-20 23:09:02 -07:00
Daniel Valdivia
54d0a1d342 Support for labels at pvc level (#254) 2020-08-20 22:46:07 -07:00
Minio Trusted
c59737a71d update v0.3.15 2020-08-20 21:02:34 -07:00
Lenin Alevski
7c2ba707eb add labels to tenant secrets for easy deletion (#252)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-08-20 19:09:13 -07:00
Cesar N
545a890c45 Delete secrets on tenant deletion (#253) 2020-08-20 18:57:34 -07:00
Minio Trusted
4b42308484 update console update to v0.3.14 2020-08-19 20:36:45 -07:00
Cesar N
5a95fed35b Add option to delete tenant's pvcs on tenant deletion (#251) 2020-08-19 20:34:43 -07:00
Lenin Alevski
f880e3976f encrypt token session using aes-gcm if cpu support it or ChaCha20 (#248)
Harsha's improvement to use binary encoding instead of json encoding
2020-08-18 12:42:13 -07:00
Daniel Valdivia
25fa2f3275 YARN upograde Dependencies (#247) 2020-08-15 21:52:36 -07:00
Minio Trusted
9f005b7537 update version v0.3.13 2020-08-11 22:30:18 -07:00
Daniel Valdivia
1ad6e977f2 Tolerate DL MinIO unreachable (#246) 2020-08-11 22:29:33 -07:00
Minio Trusted
e9a64c5479 update to v0.3.12 2020-08-11 21:15:37 -07:00
Daniel Valdivia
a2e7259ccb Allow to Specify the Tenant Console Image. Support Image Pull Secrets… (#245)
* Allow to Specify the Tenant Console Image. Support Image Pull Secrets by Name.

This PR adds support for `console_image` on create tenant and update tenant so the console image can be set by the caller. This is in case the image used is hosted in a private registry.

Also adds support to specify the Image Pull Secret, if it's not specified, the individual image registry credentials can still be specified.

* Add tests for new fields.
2020-08-11 18:20:43 -07:00
Lenin Alevski
d28e66a353 prepareSTSClientTransport tls function refactor (#244)
- Reading root ca certificates operation will run only once after Console
starts, reduce the chance of panics happening during runtime
- Fixed bug in which tls.config insecureSkipVerification configuration
  could get overrided after variable reasignation
2020-08-11 11:32:44 -07:00
Minio Trusted
e0ff6623bb update to version v0.3.11 2020-08-09 19:39:46 -07:00
Lenin Alevski
3d59e9ac30 fix npe for tls console/minio (#243) 2020-08-09 17:19:39 -07:00
Lenin Alevski
cff712f071 rename SSL to TLS in labels, env variables and normal variables/constants (#242) 2020-08-09 16:08:58 -07:00
30 changed files with 2295 additions and 5239 deletions

View File

@@ -47,12 +47,12 @@ var serverCmd = cli.Command{
},
cli.StringFlag{
Name: "tls-host",
Value: restapi.GetSSLHostname(),
Value: restapi.GetTLSHostname(),
Usage: "HTTPS server hostname",
},
cli.IntFlag{
Name: "tls-port",
Value: restapi.GetSSLPort(),
Value: restapi.GetTLSPort(),
Usage: "HTTPS server port",
},
cli.StringFlag{

1
go.mod
View File

@@ -21,6 +21,7 @@ require (
github.com/minio/minio-go/v7 v7.0.5-0.20200807085956-d7db33ea7618
github.com/minio/operator v0.0.0-20200806194125-c2ff646f4af1
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/secure-io/sio-go v0.3.1
github.com/stretchr/testify v1.6.1
github.com/unrolled/secure v1.0.7
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de

View File

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

View File

@@ -8,4 +8,4 @@ resources:
- console-configmap.yaml
- console-service.yaml
- console-deployment.yaml
- minio-operator.yaml
- https://github.com/minio/operator/?ref=v3.0.10

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@ rules:
- list
- patch
- update
- deletecollection
- apiGroups:
- ""
resources:
@@ -22,12 +23,21 @@ rules:
- services
- events
- resourcequotas
- nodes
verbs:
- get
- watch
- create
- list
- patch
- apiGroups:
- ""
resources:
- persistentvolumeclaims
verbs:
- deletecollection
- list
- get
- apiGroups:
- "storage.k8s.io"
resources:

View File

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

View File

@@ -8,4 +8,4 @@ resources:
- console-configmap.yaml
- console-service.yaml
- console-deployment.yaml
- minio-operator.yaml
- https://github.com/minio/operator/?ref=v3.0.10

File diff suppressed because it is too large Load Diff

View File

@@ -42,9 +42,15 @@ type CreateTenantRequest struct {
// annotations
Annotations map[string]string `json:"annotations,omitempty"`
// console image
ConsoleImage string `json:"console_image,omitempty"`
// enable console
EnableConsole *bool `json:"enable_console,omitempty"`
// enable prometheus
EnablePrometheus *bool `json:"enable_prometheus,omitempty"`
// enable tls
EnableTLS *bool `json:"enable_tls,omitempty"`
@@ -60,6 +66,9 @@ type CreateTenantRequest struct {
// image
Image string `json:"image,omitempty"`
// image pull secret
ImagePullSecret string `json:"image_pull_secret,omitempty"`
// image registry
ImageRegistry *ImageRegistry `json:"image_registry,omitempty"`

View File

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

View File

@@ -130,8 +130,8 @@ type IdpConfigurationActiveDirectory struct {
// server insecure
ServerInsecure bool `json:"server_insecure,omitempty"`
// skip ssl verification
SkipSslVerification bool `json:"skip_ssl_verification,omitempty"`
// skip tls verification
SkipTLSVerification bool `json:"skip_tls_verification,omitempty"`
// url
// Required: true

View File

@@ -34,10 +34,17 @@ import (
// swagger:model updateTenantRequest
type UpdateTenantRequest struct {
// console image
// Pattern: ^((.*?)/(.*?):(.+))$
ConsoleImage string `json:"console_image,omitempty"`
// image
// Pattern: ^((.*?)/(.*?):(.+))$
Image string `json:"image,omitempty"`
// image pull secret
ImagePullSecret string `json:"image_pull_secret,omitempty"`
// image registry
ImageRegistry *ImageRegistry `json:"image_registry,omitempty"`
}
@@ -46,6 +53,10 @@ type UpdateTenantRequest struct {
func (m *UpdateTenantRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConsoleImage(formats); err != nil {
res = append(res, err)
}
if err := m.validateImage(formats); err != nil {
res = append(res, err)
}
@@ -60,6 +71,19 @@ func (m *UpdateTenantRequest) Validate(formats strfmt.Registry) error {
return nil
}
func (m *UpdateTenantRequest) validateConsoleImage(formats strfmt.Registry) error {
if swag.IsZero(m.ConsoleImage) { // not required
return nil
}
if err := validate.Pattern("console_image", "body", string(m.ConsoleImage), `^((.*?)/(.*?):(.+))$`); err != nil {
return err
}
return nil
}
func (m *UpdateTenantRequest) validateImage(formats strfmt.Registry) error {
if swag.IsZero(m.Image) { // not required

View File

@@ -207,6 +207,9 @@ func (m *Zone) UnmarshalBinary(b []byte) error {
// swagger:model ZoneVolumeConfiguration
type ZoneVolumeConfiguration struct {
// labels
Labels map[string]string `json:"labels,omitempty"`
// size
// Required: true
Size *int64 `json:"size"`

View File

@@ -17,14 +17,17 @@
package auth
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
@@ -33,6 +36,9 @@ import (
"github.com/minio/console/models"
"github.com/minio/console/pkg/auth/token"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/secure-io/sio-go/sioutil"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/pbkdf2"
)
@@ -40,6 +46,7 @@ var (
errNoAuthToken = errors.New("session token missing")
errReadingToken = errors.New("session token internal data is malformed")
errClaimsFormat = errors.New("encrypted session token claims not in the right format")
errorGeneric = errors.New("an error has occurred")
)
// derivedKey is the key used to encrypt the session token claims, its derived using pbkdf on CONSOLE_PBKDF_PASSPHRASE with CONSOLE_PBKDF_SALT
@@ -102,9 +109,10 @@ func NewEncryptedTokenForClient(credentials *credentials.Value, actions []string
// returns a base64 encoded ciphertext
func encryptClaims(accessKeyID, secretAccessKey, sessionToken string, actions []string) (string, error) {
payload := []byte(fmt.Sprintf("%s#%s#%s#%s", accessKeyID, secretAccessKey, sessionToken, strings.Join(actions, ",")))
ciphertext, err := encrypt(payload)
ciphertext, err := encrypt(payload, []byte{})
if err != nil {
return "", err
log.Println(err)
return "", errorGeneric
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
@@ -116,7 +124,7 @@ func decryptClaims(ciphertext string) (*DecryptedClaims, error) {
log.Println(err)
return nil, errClaimsFormat
}
plaintext, err := decrypt(decoded)
plaintext, err := decrypt(decoded, []byte{})
if err != nil {
log.Println(err)
return nil, errClaimsFormat
@@ -136,37 +144,137 @@ func decryptClaims(ciphertext string) (*DecryptedClaims, error) {
}, nil
}
// Encrypt a blob of data using AEAD (AES-GCM) with a pbkdf2 derived key
func encrypt(plaintext []byte) ([]byte, error) {
block, _ := aes.NewCipher(derivedKey)
gcm, err := cipher.NewGCM(block)
const (
aesGcm = 0x00
c20p1305 = 0x01
)
// Encrypt a blob of data using AEAD scheme, AES-GCM if the executing CPU
// provides AES hardware support, otherwise will use ChaCha20-Poly1305
// with a pbkdf2 derived key, this function should be used to encrypt a session
// or data key provided as plaintext.
//
// The returned ciphertext data consists of:
// iv | AEAD ID | nonce | encrypted data
// 32 1 12 ~ len(data)
func encrypt(plaintext, associatedData []byte) ([]byte, error) {
iv, err := sioutil.Random(32) // 32 bit IV
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
var algorithm byte
if sioutil.NativeAES() {
algorithm = aesGcm
} else {
algorithm = c20p1305
}
var aead cipher.AEAD
switch algorithm {
case aesGcm:
mac := hmac.New(sha256.New, derivedKey)
mac.Write(iv)
sealingKey := mac.Sum(nil)
var block cipher.Block
block, err = aes.NewCipher(sealingKey)
if err != nil {
return nil, err
}
aead, err = cipher.NewGCM(block)
if err != nil {
return nil, err
}
case c20p1305:
var sealingKey []byte
sealingKey, err = chacha20.HChaCha20(derivedKey, iv)
if err != nil {
return nil, err
}
aead, err = chacha20poly1305.New(sealingKey)
if err != nil {
return nil, err
}
}
nonce, err := sioutil.Random(aead.NonceSize())
if err != nil {
return nil, err
}
cipherText := gcm.Seal(nonce, nonce, plaintext, nil)
return cipherText, nil
sealedBytes := aead.Seal(nil, nonce, plaintext, associatedData)
// ciphertext = iv | AEAD ID | nonce | sealed bytes
var buf bytes.Buffer
buf.Write(iv)
buf.WriteByte(algorithm)
buf.Write(nonce)
buf.Write(sealedBytes)
return buf.Bytes(), nil
}
// Decrypts a blob of data using AEAD (AES-GCM) with a pbkdf2 derived key
func decrypt(data []byte) ([]byte, error) {
block, err := aes.NewCipher(derivedKey)
// Decrypts a blob of data using AEAD scheme AES-GCM if the executing CPU
// provides AES hardware support, otherwise will use ChaCha20-Poly1305with
// and a pbkdf2 derived key
func decrypt(ciphertext []byte, associatedData []byte) ([]byte, error) {
var (
iv [32]byte
algorithm [1]byte
nonce [12]byte // This depends on the AEAD but both used ciphers have the same nonce length.
)
r := bytes.NewReader(ciphertext)
if _, err := io.ReadFull(r, iv[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(r, algorithm[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(r, nonce[:]); err != nil {
return nil, err
}
var aead cipher.AEAD
switch algorithm[0] {
case aesGcm:
mac := hmac.New(sha256.New, derivedKey)
mac.Write(iv[:])
sealingKey := mac.Sum(nil)
block, err := aes.NewCipher(sealingKey[:])
if err != nil {
return nil, err
}
aead, err = cipher.NewGCM(block)
if err != nil {
return nil, err
}
case c20p1305:
sealingKey, err := chacha20.HChaCha20(derivedKey, iv[:])
if err != nil {
return nil, err
}
aead, err = chacha20poly1305.New(sealingKey)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("invalid algorithm: %v", algorithm)
}
if len(nonce) != aead.NonceSize() {
return nil, fmt.Errorf("invalid nonce size %d, expected %d", len(nonce), aead.NonceSize())
}
sealedBytes, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
nonce, cipherText := data[:nonceSize], data[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, cipherText, nil)
plaintext, err := aead.Open(nil, nonce[:], sealedBytes, associatedData)
if err != nil {
return nil, err
}
return plaintext, nil
}

View File

@@ -36,12 +36,12 @@ func TestNewJWTWithClaimsForClient(t *testing.T) {
funcAssert := assert.New(t)
// Test-1 : NewEncryptedTokenForClient() is generated correctly without errors
function := "NewEncryptedTokenForClient()"
jwt, err := NewEncryptedTokenForClient(creds, []string{""})
if err != nil || jwt == "" {
token, err := NewEncryptedTokenForClient(creds, []string{""})
if err != nil || token == "" {
t.Errorf("Failed on %s:, error occurred: %s", function, err)
}
// saving jwt for future tests
goodToken = jwt
// saving token for future tests
goodToken = token
// Test-2 : NewEncryptedTokenForClient() throws error because of empty credentials
if _, err = NewEncryptedTokenForClient(nil, []string{""}); err != nil {
funcAssert.Equal("provided credentials are empty", err.Error())

File diff suppressed because one or more lines are too long

View File

@@ -111,7 +111,7 @@ const AddTenant = ({
const [accessKey, setAccessKey] = useState<string>("");
const [secretKey, setSecretKey] = useState<string>("");
const [enableConsole, setEnableConsole] = useState<boolean>(true);
const [enableSSL, setEnableSSL] = useState<boolean>(false);
const [enableTLS, setEnableTLS] = useState<boolean>(false);
const [sizeFactor, setSizeFactor] = useState<string>("Gi");
const [storageClasses, setStorageClassesList] = useState<Opts[]>([]);
const [validationErrors, setValidationErrors] = useState<any>({});
@@ -274,7 +274,7 @@ const AddTenant = ({
name: tenantName,
service_name: tenantName,
image: imageName,
enable_ssl: enableSSL,
enable_tls: enableTLS,
enable_console: enableConsole,
access_key: accessKey,
secret_key: secretKey,
@@ -750,17 +750,17 @@ const AddTenant = ({
</Grid>
<Grid item xs={12}>
<CheckboxWrapper
value="enable_ssl"
id="enable_ssl"
name="enable_ssl"
checked={enableSSL}
value="enable_tls"
id="enable_tls"
name="enable_tls"
checked={enableTLS}
onChange={(e) => {
const targetD = e.target;
const checked = targetD.checked;
setEnableSSL(checked);
setEnableTLS(checked);
}}
label={"Enable SSL"}
label={"Enable TLS"}
/>
</Grid>
</React.Fragment>
@@ -882,9 +882,9 @@ const AddTenant = ({
<React.Fragment>
<TableRow>
<TableCell align="right" className={classes.tableTitle}>
Enable SSL
Enable TLS
</TableCell>
<TableCell>{enableSSL ? "Enabled" : "Disabled"}</TableCell>
<TableCell>{enableTLS ? "Enabled" : "Disabled"}</TableCell>
</TableRow>
<TableRow>
<TableCell align="right" className={classes.tableTitle}>

File diff suppressed because it is too large Load Diff

View File

@@ -54,10 +54,6 @@ import (
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
const (
minioRegCred = "minio-regcred-secret"
)
type imageRegistry struct {
Auths map[string]imageRegistryCredentials `json:"auths"`
}
@@ -114,7 +110,7 @@ func registerTenantHandlers(api *operations.ConsoleAPI) {
err := getDeleteTenantResponse(session, params)
if err != nil {
log.Println(err)
return admin_api.NewTenantInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String("Unable to delete tenant")})
return admin_api.NewTenantInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return admin_api.NewTenantInfoOK()
@@ -149,25 +145,58 @@ func registerTenantHandlers(api *operations.ConsoleAPI) {
})
}
// deleteTenantAction performs the actions of deleting a tenant
func deleteTenantAction(ctx context.Context, operatorClient OperatorClient, nameSpace, instanceName string) error {
err := operatorClient.TenantDelete(ctx, nameSpace, instanceName, metav1.DeleteOptions{})
if err != nil {
return err
}
return nil
}
// getDeleteTenantResponse gets the output of deleting a minio instance
func getDeleteTenantResponse(session *models.Principal, params admin_api.DeleteTenantParams) error {
opClientClientSet, err := cluster.OperatorClient(session.SessionToken)
if err != nil {
return err
}
// get Kubernetes Client
clientset, err := cluster.K8sClient(session.SessionToken)
if err != nil {
return err
}
opClient := &operatorClient{
client: opClientClientSet,
}
return deleteTenantAction(context.Background(), opClient, params.Namespace, params.Tenant)
deleteTenantPVCs := false
if params.Body != nil {
deleteTenantPVCs = params.Body.DeletePvcs
}
return deleteTenantAction(context.Background(), opClient, clientset.CoreV1(), params.Namespace, params.Tenant, deleteTenantPVCs)
}
// deleteTenantAction performs the actions of deleting a tenant
//
// It also adds the option of deleting the tenant's underlying pvcs if deletePvcs set
func deleteTenantAction(
ctx context.Context,
operatorClient OperatorClient,
clientset v1.CoreV1Interface,
namespace, tenantName string,
deletePvcs bool) error {
err := operatorClient.TenantDelete(ctx, namespace, tenantName, metav1.DeleteOptions{})
if err != nil {
// try to delete pvc even if the tenant doesn't exist anymore but only if deletePvcs is set to true,
// else, we return the error
if (deletePvcs && !k8sErrors.IsNotFound(err)) || !deletePvcs {
return err
}
}
if deletePvcs {
opts := metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", operator.TenantLabel, tenantName),
}
err = clientset.PersistentVolumeClaims(namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, opts)
if err != nil {
return err
}
// delete all tenant's secrets only if deletePvcs = true
return clientset.Secrets(namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, opts)
}
return nil
}
func getTenantScheme(mi *operator.Tenant) string {
@@ -345,10 +374,10 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
if minioImage == "" {
minImg, err := cluster.GetMinioImage()
if err != nil {
return nil, err
// we can live without figuring out the latest version of MinIO, Operator will use a hardcoded value
if err == nil {
minioImage = *minImg
}
minioImage = *minImg
}
// get Kubernetes Client
clientset, err := cluster.K8sClient(session.SessionToken)
@@ -369,12 +398,16 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
secretKey = tenantReq.SecretKey
}
secretName := fmt.Sprintf("%s-secret", *tenantReq.Name)
tenantName := *tenantReq.Name
secretName := fmt.Sprintf("%s-secret", tenantName)
imm := true
instanceSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Immutable: &imm,
Data: map[string][]byte{
@@ -403,7 +436,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
//Construct a MinIO Instance with everything we are getting from parameters
minInst := operator.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: *tenantReq.Name,
Name: tenantName,
},
Spec: operator.TenantSpec{
Image: minioImage,
@@ -420,7 +453,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
url := *tenantReq.Idp.ActiveDirectory.URL
userNameFormat := *tenantReq.Idp.ActiveDirectory.UsernameFormat
userSearchFilter := *tenantReq.Idp.ActiveDirectory.UserSearchFilter
tlsSkipVerify := tenantReq.Idp.ActiveDirectory.SkipSslVerification
tlsSkipVerify := tenantReq.Idp.ActiveDirectory.SkipTLSVerification
serverInsecure := tenantReq.Idp.ActiveDirectory.ServerInsecure
groupSearchDN := tenantReq.Idp.ActiveDirectory.GroupSearchBaseDn
groupSearchFilter := tenantReq.Idp.ActiveDirectory.GroupSearchFilter
@@ -467,7 +500,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
isEncryptionAvailable := false
if *tenantReq.EnableTLS {
if tenantReq.EnableTLS != nil && *tenantReq.EnableTLS {
// If user request autoCert, Operator will generate certificate keypair for MinIO (server), Console (server) and KES (server and app mTLS)
isEncryptionAvailable = true
minInst.Spec.RequestAutoCert = *tenantReq.EnableTLS
@@ -493,6 +526,9 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
externalTLSCertificateSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: externalTLSCertificateSecretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Type: corev1.SecretTypeTLS,
Immutable: &imm,
@@ -520,13 +556,13 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
})
// KES client mTLSCertificates used by MinIO instance, only if autoCert is not enabled
if !minInst.Spec.RequestAutoCert {
minInst.Spec.ExternalClientCertSecret, err = getTenantExternalClientCertificates(ctx, clientset, ns, tenantReq.Encryption, secretName)
minInst.Spec.ExternalClientCertSecret, err = getTenantExternalClientCertificates(ctx, clientset, ns, tenantReq.Encryption, secretName, tenantName)
if err != nil {
return nil, err
}
}
// KES configuration for Tenant instance
minInst.Spec.KES, err = getKESConfiguration(ctx, clientset, ns, tenantReq.Encryption, secretName, minInst.Spec.RequestAutoCert)
minInst.Spec.KES, err = getKESConfiguration(ctx, clientset, ns, tenantReq.Encryption, secretName, tenantName, minInst.Spec.RequestAutoCert)
if err != nil {
return nil, err
}
@@ -536,11 +572,13 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
var consoleAccess string
var consoleSecret string
//enableConsole := true
enableConsole := *tenantReq.EnableConsole
enableConsole := true
if tenantReq.EnableConsole != nil && *tenantReq.EnableConsole {
enableConsole = *tenantReq.EnableConsole
}
if enableConsole {
consoleSelector := fmt.Sprintf("%s-console", *tenantReq.Name)
consoleSelector := fmt.Sprintf("%s-console", tenantName)
consoleSecretName := fmt.Sprintf("%s-secret", consoleSelector)
consoleAccess = RandomCharString(16)
consoleSecret = RandomCharString(32)
@@ -548,6 +586,9 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
instanceSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: consoleSecretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Immutable: &imm,
Data: map[string][]byte{
@@ -585,9 +626,9 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
return nil, err
}
const consoleVersion = "minio/console:v0.3.10"
const consoleVersion = "minio/console:v0.3.17"
minInst.Spec.Console = &operator.ConsoleConfiguration{
Replicas: 2,
Replicas: 1,
Image: consoleVersion,
ConsoleSecret: &corev1.LocalObjectReference{Name: consoleSecretName},
Resources: corev1.ResourceRequirements{
@@ -597,7 +638,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
},
}
if !minInst.Spec.RequestAutoCert && tenantReq.TLS.Console != nil {
if !minInst.Spec.RequestAutoCert && tenantReq.TLS != nil && tenantReq.TLS.Console != nil {
consoleExternalTLSCertificateSecretName := fmt.Sprintf("%s-console-external-certificates", secretName)
tlsCrt, err := base64.StdEncoding.DecodeString(*tenantReq.TLS.Console.Crt)
if err != nil {
@@ -610,6 +651,9 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
consoleExternalTLSCertificateSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: consoleExternalTLSCertificateSecretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Type: corev1.SecretTypeTLS,
Immutable: &imm,
@@ -637,10 +681,12 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
// add annotations
var annotations map[string]string
if len(tenantReq.Annotations) > 0 {
if minInst.Spec.Metadata == nil {
minInst.Spec.Metadata = &metav1.ObjectMeta{}
if minInst.Spec.Metadata == nil {
minInst.Spec.Metadata = &metav1.ObjectMeta{
Annotations: map[string]string{},
}
}
if len(tenantReq.Annotations) > 0 {
annotations = tenantReq.Annotations
minInst.Spec.Metadata.Annotations = annotations
}
@@ -658,13 +704,32 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
minInst.Spec.Mountpath = tenantReq.MounthPath
}
if err := setImageRegistry(ctx, tenantReq.ImageRegistry, clientset.CoreV1(), ns); err != nil {
// We accept either `image_pull_secret` or the individual details of the `image_registry` but not both
var imagePullSecret string
if tenantReq.ImagePullSecret != "" {
imagePullSecret = tenantReq.ImagePullSecret
} else if imagePullSecret, err = setImageRegistry(ctx, tenantName, tenantReq.ImageRegistry, clientset.CoreV1(), ns); err != nil {
log.Println("error setting image registry secret:", err)
return nil, err
}
// pass the image pull secret to the Tenant
if imagePullSecret != "" {
minInst.Spec.ImagePullSecret = corev1.LocalObjectReference{
Name: imagePullSecret,
}
}
minInst.Spec.ImagePullSecret = corev1.LocalObjectReference{
Name: minioRegCred,
// prometheus annotations support
if tenantReq.EnablePrometheus != nil && *tenantReq.EnablePrometheus && minInst.Spec.Metadata != nil && minInst.Spec.Metadata.Annotations != nil {
minInst.Spec.Metadata.Annotations["prometheus.io/path"] = "/minio/prometheus/metrics"
minInst.Spec.Metadata.Annotations["prometheus.io/port"] = fmt.Sprint(operator.MinIOPort)
minInst.Spec.Metadata.Annotations["prometheus.io/scrape"] = "true"
}
// set console image if provided
if tenantReq.ConsoleImage != "" {
minInst.Spec.Console.Image = tenantReq.ConsoleImage
}
opClient, err := cluster.OperatorClient(session.SessionToken)
@@ -679,7 +744,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
// Integratrions
if os.Getenv("GKE_INTEGRATION") != "" {
err := gkeIntegration(clientset, *tenantReq.Name, ns, session.SessionToken)
err := gkeIntegration(clientset, tenantName, ns, session.SessionToken)
if err != nil {
return nil, err
}
@@ -698,9 +763,11 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
return response, nil
}
func setImageRegistry(ctx context.Context, req *models.ImageRegistry, clientset v1.CoreV1Interface, namespace string) error {
// setImageRegistry creates a secret to store the private registry credentials, if one exist it updates the existing one
// returns the name of the secret created/updated
func setImageRegistry(ctx context.Context, tenantName string, req *models.ImageRegistry, clientset v1.CoreV1Interface, namespace string) (string, error) {
if req == nil || req.Registry == nil || req.Username == nil || req.Password == nil {
return nil
return "", nil
}
credentials := make(map[string]imageRegistryCredentials)
@@ -718,12 +785,17 @@ func setImageRegistry(ctx context.Context, req *models.ImageRegistry, clientset
}
imRegistryJSON, err := json.Marshal(imRegistry)
if err != nil {
return err
return "", err
}
pullSecretName := fmt.Sprintf("%s-regcred", tenantName)
instanceSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: minioRegCred,
Name: pullSecretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Data: map[string][]byte{
corev1.DockerConfigJsonKey: []byte(string(imRegistryJSON)),
@@ -732,22 +804,22 @@ func setImageRegistry(ctx context.Context, req *models.ImageRegistry, clientset
}
// Get or Create secret if it doesn't exist
_, err = clientset.Secrets(namespace).Get(ctx, minioRegCred, metav1.GetOptions{})
_, err = clientset.Secrets(namespace).Get(ctx, pullSecretName, metav1.GetOptions{})
if err != nil {
if k8sErrors.IsNotFound(err) {
_, err = clientset.Secrets(namespace).Create(ctx, &instanceSecret, metav1.CreateOptions{})
if err != nil {
return err
return "", err
}
return nil
return "", nil
}
return err
return "", err
}
_, err = clientset.Secrets(namespace).Update(ctx, &instanceSecret, metav1.UpdateOptions{})
if err != nil {
return err
return "", err
}
return nil
return pullSecretName, nil
}
// updateTenantAction does an update on the minioTenant by patching the desired changes
@@ -755,25 +827,35 @@ func updateTenantAction(ctx context.Context, operatorClient OperatorClient, clie
imageToUpdate := params.Body.Image
imageRegistryReq := params.Body.ImageRegistry
if err := setImageRegistry(ctx, imageRegistryReq, clientset, namespace); err != nil {
log.Println("error setting image registry secret:", err)
return err
}
minInst, err := operatorClient.TenantGet(ctx, namespace, params.Tenant, metav1.GetOptions{})
if err != nil {
return err
}
// we can take either the `image_pull_secret` of the `image_registry` but not both
if params.Body.ImagePullSecret != "" {
minInst.Spec.ImagePullSecret.Name = params.Body.ImagePullSecret
} else {
// update the image pull secret content
if _, err := setImageRegistry(ctx, params.Tenant, imageRegistryReq, clientset, namespace); err != nil {
log.Println("error setting image registry secret:", err)
return err
}
}
// update the console image
if strings.TrimSpace(params.Body.ConsoleImage) != "" && minInst.Spec.Console != nil {
minInst.Spec.Console.Image = params.Body.ConsoleImage
}
// if image to update is empty we'll use the latest image by default
if strings.TrimSpace(imageToUpdate) != "" {
minInst.Spec.Image = imageToUpdate
} else {
im, err := cluster.GetLatestMinioImage(httpCl)
if err != nil {
return err
// if we can't get the MinIO image, we won' auto-update it unless it's explicit by name
if err == nil {
minInst.Spec.Image = *im
}
minInst.Spec.Image = *im
}
payloadBytes, err := json.Marshal(minInst)
@@ -1068,7 +1150,8 @@ func parseTenantZoneRequest(zoneParams *models.Zone, annotations map[string]stri
// Pass annotations to the volume
vct := &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "data",
Name: "data",
Labels: zoneParams.VolumeConfiguration.Labels,
},
Spec: volTemp,
}
@@ -1331,7 +1414,7 @@ func parseNodeSelectorTerm(term *corev1.NodeSelectorTerm) *models.NodeSelectorTe
return &t
}
func getTenantExternalClientCertificates(ctx context.Context, clientSet *kubernetes.Clientset, ns string, encryptionCfg *models.EncryptionConfiguration, secretName string) (clientCertificates *operator.LocalCertificateReference, err error) {
func getTenantExternalClientCertificates(ctx context.Context, clientSet *kubernetes.Clientset, ns string, encryptionCfg *models.EncryptionConfiguration, secretName, tenantName string) (clientCertificates *operator.LocalCertificateReference, err error) {
instanceExternalClientCertificateSecretName := fmt.Sprintf("%s-instance-external-client-mtls-certificates", secretName)
// If there's an error during this process we delete all KES configuration secrets
defer func() {
@@ -1356,6 +1439,9 @@ func getTenantExternalClientCertificates(ctx context.Context, clientSet *kuberne
instanceExternalClientCertificateSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: instanceExternalClientCertificateSecretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Type: corev1.SecretTypeTLS,
Immutable: &imm,
@@ -1376,7 +1462,7 @@ func getTenantExternalClientCertificates(ctx context.Context, clientSet *kuberne
return clientCertificates, nil
}
func getKESConfiguration(ctx context.Context, clientSet *kubernetes.Clientset, ns string, encryptionCfg *models.EncryptionConfiguration, secretName string, autoCert bool) (kesConfiguration *operator.KESConfig, err error) {
func getKESConfiguration(ctx context.Context, clientSet *kubernetes.Clientset, ns string, encryptionCfg *models.EncryptionConfiguration, secretName, tenantName string, autoCert bool) (kesConfiguration *operator.KESConfig, err error) {
// secrets used by the KES configuration
instanceExternalClientCertificateSecretName := fmt.Sprintf("%s-instance-external-client-mtls-certificates", secretName)
kesExternalCertificateSecretName := fmt.Sprintf("%s-kes-external-mtls-certificates", secretName)
@@ -1432,6 +1518,9 @@ func getKESConfiguration(ctx context.Context, clientSet *kubernetes.Clientset, n
kesExternalCertificateSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: kesExternalCertificateSecretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Type: corev1.SecretTypeTLS,
Immutable: &imm,
@@ -1617,6 +1706,9 @@ func getKESConfiguration(ctx context.Context, clientSet *kubernetes.Clientset, n
kesClientCertSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: kesClientCertSecretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Immutable: &imm,
Data: mTLSCertificates,
@@ -1640,6 +1732,9 @@ func getKESConfiguration(ctx context.Context, clientSet *kubernetes.Clientset, n
kesConfigurationSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: kesConfigurationSecretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
Immutable: &imm,
Data: map[string][]byte{

View File

@@ -33,9 +33,11 @@ import (
operator "github.com/minio/operator/pkg/apis/minio.min.io/v1"
v1 "github.com/minio/operator/pkg/apis/minio.min.io/v1"
corev1 "k8s.io/api/core/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/fake"
)
@@ -335,12 +337,13 @@ func Test_TenantInfo(t *testing.T) {
func Test_deleteTenantAction(t *testing.T) {
opClient := opClientMock{}
type args struct {
ctx context.Context
operatorClient OperatorClient
nameSpace string
tenantName string
deletePvcs bool
objs []runtime.Object
mockTenantDelete func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error
}
tests := []struct {
@@ -355,6 +358,7 @@ func Test_deleteTenantAction(t *testing.T) {
operatorClient: opClient,
nameSpace: "default",
tenantName: "minio-tenant",
deletePvcs: false,
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return nil
},
@@ -368,17 +372,155 @@ func Test_deleteTenantAction(t *testing.T) {
operatorClient: opClient,
nameSpace: "default",
tenantName: "minio-tenant",
deletePvcs: false,
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return errors.New("something happened")
},
},
wantErr: true,
},
{
// Delete only PVCs of the defined tenant on the specific namespace
name: "Delete PVCs on Tenant Deletion",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "minio-tenant",
tenantName: "tenant1",
deletePvcs: true,
objs: []runtime.Object{
&corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "PVC1",
Namespace: "minio-tenant",
Labels: map[string]string{
operator.TenantLabel: "tenant1",
operator.ZoneLabel: "zone-1",
},
},
},
},
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return nil
},
},
wantErr: false,
},
{
// Do not delete underlying pvcs
name: "Don't Delete PVCs on Tenant Deletion",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "minio-tenant",
tenantName: "tenant1",
deletePvcs: false,
objs: []runtime.Object{
&corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "PVC1",
Namespace: "minio-tenant",
Labels: map[string]string{
operator.TenantLabel: "tenant1",
operator.ZoneLabel: "zone-1",
},
},
},
},
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return nil
},
},
wantErr: false,
},
{
// If error is different than NotFound, PVC deletion should not continue
name: "Don't delete pvcs if error Deleting Tenant, return",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "minio-tenant",
tenantName: "tenant1",
deletePvcs: true,
objs: []runtime.Object{
&corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "PVC1",
Namespace: "minio-tenant",
Labels: map[string]string{
operator.TenantLabel: "tenant1",
operator.ZoneLabel: "zone-1",
},
},
},
},
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return errors.New("error returned")
},
},
wantErr: true,
},
{
// If error is NotFound while trying to Delete Tenant, PVC deletion should continue
name: "Delete pvcs if tenant not found",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "minio-tenant",
tenantName: "tenant1",
deletePvcs: true,
objs: []runtime.Object{
&corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "PVC1",
Namespace: "minio-tenant",
Labels: map[string]string{
operator.TenantLabel: "tenant1",
operator.ZoneLabel: "zone-1",
},
},
},
},
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return k8sErrors.NewNotFound(schema.GroupResource{}, "tenant1")
},
},
wantErr: false,
},
{
// If error is NotFound while trying to Delete Tenant and pvcdeletion=false,
// error should be returned
name: "Don't delete pvcs and return error if tenant not found",
args: args{
ctx: context.Background(),
operatorClient: opClient,
nameSpace: "minio-tenant",
tenantName: "tenant1",
deletePvcs: false,
objs: []runtime.Object{
&corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "PVC1",
Namespace: "minio-tenant",
Labels: map[string]string{
operator.TenantLabel: "tenant1",
operator.ZoneLabel: "zone-1",
},
},
},
},
mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error {
return k8sErrors.NewNotFound(schema.GroupResource{}, "tenant1")
},
},
wantErr: true,
},
}
for _, tt := range tests {
opClientTenantDeleteMock = tt.args.mockTenantDelete
kubeClient := fake.NewSimpleClientset(tt.args.objs...)
t.Run(tt.name, func(t *testing.T) {
if err := deleteTenantAction(tt.args.ctx, tt.args.operatorClient, tt.args.nameSpace, tt.args.tenantName); (err != nil) != tt.wantErr {
if err := deleteTenantAction(tt.args.ctx, tt.args.operatorClient, kubeClient.CoreV1(), tt.args.nameSpace, tt.args.tenantName, tt.args.deletePvcs); (err != nil) != tt.wantErr {
t.Errorf("deleteTenantAction() error = %v, wantErr %v", err, tt.wantErr)
}
})
@@ -647,6 +789,7 @@ func Test_UpdateTenantAction(t *testing.T) {
return &http.Response{}, nil
},
params: admin_api.UpdateTenantParams{
Tenant: "minio-tenant",
Body: &models.UpdateTenantRequest{
Image: "minio/minio:RELEASE.2020-06-03T22-13-49Z",
},
@@ -675,6 +818,7 @@ func Test_UpdateTenantAction(t *testing.T) {
}, nil
},
params: admin_api.UpdateTenantParams{
Tenant: "minio-tenant",
Body: &models.UpdateTenantRequest{
Image: "",
},
@@ -683,7 +827,7 @@ func Test_UpdateTenantAction(t *testing.T) {
wantErr: false,
},
{
name: "Empty image input Error retrieving latest image",
name: "Empty image input Error retrieving latest image, nothing happens",
args: args{
ctx: context.Background(),
operatorClient: opClient,
@@ -700,12 +844,63 @@ func Test_UpdateTenantAction(t *testing.T) {
return nil, errors.New("error")
},
params: admin_api.UpdateTenantParams{
Tenant: "minio-tenant",
Body: &models.UpdateTenantRequest{
Image: "",
},
},
},
wantErr: true,
wantErr: false,
},
{
name: "Update minio console version no errors",
args: args{
ctx: context.Background(),
operatorClient: opClient,
httpCl: httpClientM,
nameSpace: "default",
tenantName: "minio-tenant",
mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
mockHTTPClientGet: func(url string) (resp *http.Response, err error) {
return nil, errors.New("use default minio")
},
params: admin_api.UpdateTenantParams{
Body: &models.UpdateTenantRequest{
ConsoleImage: "minio/console:v0.3.17",
},
},
},
wantErr: false,
},
{
name: "Update minio image pull secrets no errors",
args: args{
ctx: context.Background(),
operatorClient: opClient,
httpCl: httpClientM,
nameSpace: "default",
tenantName: "minio-tenant",
mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*v1.Tenant, error) {
return &v1.Tenant{}, nil
},
mockHTTPClientGet: func(url string) (resp *http.Response, err error) {
return nil, errors.New("use default minio")
},
params: admin_api.UpdateTenantParams{
Body: &models.UpdateTenantRequest{
ImagePullSecret: "minio-regcred",
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
@@ -715,7 +910,7 @@ func Test_UpdateTenantAction(t *testing.T) {
cnsClient := fake.NewSimpleClientset(tt.objs...)
t.Run(tt.name, func(t *testing.T) {
if err := updateTenantAction(tt.args.ctx, tt.args.operatorClient, cnsClient.CoreV1(), tt.args.httpCl, tt.args.nameSpace, tt.args.params); (err != nil) != tt.wantErr {
t.Errorf("deleteTenantAction() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("updateTenantAction() error = %v, wantErr %v", err, tt.wantErr)
}
})
}

View File

@@ -249,7 +249,7 @@ func newMinioClient(claims *models.Principal) (*minio.Client, error) {
// newS3BucketClient creates a new mc S3Client to talk to the server based on a bucket
func newS3BucketClient(claims *models.Principal, bucketName string) (*mc.S3Client, error) {
endpoint := getMinIOServer()
useSSL := getMinIOEndpointIsSecure()
useTLS := getMinIOEndpointIsSecure()
if strings.TrimSpace(bucketName) != "" {
endpoint += fmt.Sprintf("/%s", bucketName)
@@ -259,7 +259,7 @@ func newS3BucketClient(claims *models.Principal, bucketName string) (*mc.S3Clien
return nil, fmt.Errorf("the provided credentials are invalid")
}
s3Config := newS3Config(endpoint, claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken, !useSSL)
s3Config := newS3Config(endpoint, claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken, !useTLS)
client, pErr := mc.S3New(s3Config)
if pErr != nil {
return nil, pErr.Cause

View File

@@ -105,15 +105,15 @@ func GetPort() int {
return port
}
// GetSSLHostname gets console ssl hostname set on env variable
// GetTLSHostname gets console tls hostname set on env variable
// or default one
func GetSSLHostname() string {
func GetTLSHostname() string {
return strings.ToLower(env.Get(ConsoleTLSHostname, TLSHostname))
}
// GetSSLPort gets console ssl port set on env variable
// GetTLSPort gets console tls port set on env variable
// or default one
func GetSSLPort() int {
func GetTLSPort() int {
port, err := strconv.Atoi(env.Get(ConsoleTLSPort, TLSPort))
if err != nil {
port = 9443
@@ -171,14 +171,14 @@ func getSecureHostsProxyHeaders() []string {
return []string{}
}
// If SSLRedirect is set to true, then only allow HTTPS requests. Default is true.
func getSSLRedirect() bool {
return strings.ToLower(env.Get(ConsoleSecureSSLRedirect, TLSRedirect)) == "on"
// If TLSRedirect is set to true, then only allow HTTPS requests. Default is true.
func getTLSRedirect() bool {
return strings.ToLower(env.Get(ConsoleSecureTLSRedirect, TLSRedirect)) == "on"
}
// SSLHost is the host name that is used to redirect HTTP requests to HTTPS. Default is "", which indicates to use the same host.
func getSecureSSLHost() string {
return env.Get(ConsoleSecureSSLHost, fmt.Sprintf("%s:%s", TLSHostname, TLSPort))
// TLSHost is the host name that is used to redirect HTTP requests to HTTPS. Default is "", which indicates to use the same host.
func getSecureTLSHost() string {
return env.Get(ConsoleSecureTLSHost, fmt.Sprintf("%s:%s", TLSHostname, TLSPort))
}
// STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header.
@@ -200,9 +200,9 @@ func getSecureSTSPreload() bool {
return strings.ToLower(env.Get(ConsoleSecureSTSPreload, "off")) == "on"
}
// If SSLTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
func getSecureSSLTemporaryRedirect() bool {
return strings.ToLower(env.Get(ConsoleSecureSSLTemporaryRedirect, "off")) == "on"
// If TLSTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
func getSecureTLSTemporaryRedirect() bool {
return strings.ToLower(env.Get(ConsoleSecureTLSTemporaryRedirect, "off")) == "on"
}
// STS header is only included when the connection is HTTPS.

View File

@@ -149,12 +149,12 @@ func setupGlobalMiddleware(handler http.Handler) http.Handler {
AllowedHosts: getSecureAllowedHosts(),
AllowedHostsAreRegex: getSecureAllowedHostsAreRegex(),
HostsProxyHeaders: getSecureHostsProxyHeaders(),
SSLRedirect: getSSLRedirect(),
SSLHost: getSecureSSLHost(),
SSLRedirect: getTLSRedirect(),
SSLHost: getSecureTLSHost(),
STSSeconds: getSecureSTSSeconds(),
STSIncludeSubdomains: getSecureSTSIncludeSubdomains(),
STSPreload: getSecureSTSPreload(),
SSLTemporaryRedirect: getSecureSSLTemporaryRedirect(),
SSLTemporaryRedirect: getSecureTLSTemporaryRedirect(),
SSLHostFunc: nil,
ForceSTSHeader: getSecureForceSTSHeader(),
FrameDeny: getSecureFrameDeny(),

View File

@@ -41,9 +41,9 @@ const (
ConsoleSecureSTSSeconds = "CONSOLE_SECURE_STS_SECONDS"
ConsoleSecureSTSIncludeSubdomains = "CONSOLE_SECURE_STS_INCLUDE_SUB_DOMAINS"
ConsoleSecureSTSPreload = "CONSOLE_SECURE_STS_PRELOAD"
ConsoleSecureSSLRedirect = "CONSOLE_SECURE_SSL_REDIRECT"
ConsoleSecureSSLHost = "CONSOLE_SECURE_SSL_HOST"
ConsoleSecureSSLTemporaryRedirect = "CONSOLE_SECURE_SSL_TEMPORARY_REDIRECT"
ConsoleSecureTLSRedirect = "CONSOLE_SECURE_TLS_REDIRECT"
ConsoleSecureTLSHost = "CONSOLE_SECURE_TLS_HOST"
ConsoleSecureTLSTemporaryRedirect = "CONSOLE_SECURE_TLS_TEMPORARY_REDIRECT"
ConsoleSecureForceSTSHeader = "CONSOLE_SECURE_FORCE_STS_HEADER"
ConsoleSecurePublicKey = "CONSOLE_SECURE_PUBLIC_KEY"
ConsoleSecureReferrerPolicy = "CONSOLE_SECURE_REFERRER_POLICY"

View File

@@ -1007,7 +1007,7 @@ func init() {
"tags": [
"AdminAPI"
],
"summary": "Delete Tenant",
"summary": "Delete tenant and underlying pvcs",
"operationId": "DeleteTenant",
"parameters": [
{
@@ -1021,6 +1021,13 @@ func init() {
"name": "tenant",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/deleteTenantRequest"
}
}
],
"responses": {
@@ -2024,10 +2031,17 @@ func init() {
"type": "string"
}
},
"console_image": {
"type": "string"
},
"enable_console": {
"type": "boolean",
"default": true
},
"enable_prometheus": {
"type": "boolean",
"default": false
},
"enable_tls": {
"type": "boolean",
"default": true
@@ -2046,6 +2060,9 @@ func init() {
"image": {
"type": "string"
},
"image_pull_secret": {
"type": "string"
},
"image_registry": {
"$ref": "#/definitions/imageRegistry"
},
@@ -2099,6 +2116,14 @@ func init() {
}
}
},
"deleteTenantRequest": {
"type": "object",
"properties": {
"delete_pvcs": {
"type": "boolean"
}
}
},
"encryptionConfiguration": {
"type": "object",
"properties": {
@@ -2235,7 +2260,7 @@ func init() {
"server_insecure": {
"type": "boolean"
},
"skip_ssl_verification": {
"skip_tls_verification": {
"type": "boolean"
},
"url": {
@@ -3059,10 +3084,17 @@ func init() {
"updateTenantRequest": {
"type": "object",
"properties": {
"console_image": {
"type": "string",
"pattern": "^((.*?)/(.*?):(.+))$"
},
"image": {
"type": "string",
"pattern": "^((.*?)/(.*?):(.+))$"
},
"image_pull_secret": {
"type": "string"
},
"image_registry": {
"$ref": "#/definitions/imageRegistry"
}
@@ -3222,6 +3254,12 @@ func init() {
"size"
],
"properties": {
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"size": {
"type": "integer"
},
@@ -4410,7 +4448,7 @@ func init() {
"tags": [
"AdminAPI"
],
"summary": "Delete Tenant",
"summary": "Delete tenant and underlying pvcs",
"operationId": "DeleteTenant",
"parameters": [
{
@@ -4424,6 +4462,13 @@ func init() {
"name": "tenant",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/deleteTenantRequest"
}
}
],
"responses": {
@@ -5341,7 +5386,7 @@ func init() {
"server_insecure": {
"type": "boolean"
},
"skip_ssl_verification": {
"skip_tls_verification": {
"type": "boolean"
},
"url": {
@@ -5686,6 +5731,12 @@ func init() {
"size"
],
"properties": {
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"size": {
"type": "integer"
},
@@ -5938,10 +5989,17 @@ func init() {
"type": "string"
}
},
"console_image": {
"type": "string"
},
"enable_console": {
"type": "boolean",
"default": true
},
"enable_prometheus": {
"type": "boolean",
"default": false
},
"enable_tls": {
"type": "boolean",
"default": true
@@ -5960,6 +6018,9 @@ func init() {
"image": {
"type": "string"
},
"image_pull_secret": {
"type": "string"
},
"image_registry": {
"$ref": "#/definitions/imageRegistry"
},
@@ -6013,6 +6074,14 @@ func init() {
}
}
},
"deleteTenantRequest": {
"type": "object",
"properties": {
"delete_pvcs": {
"type": "boolean"
}
}
},
"encryptionConfiguration": {
"type": "object",
"properties": {
@@ -6149,7 +6218,7 @@ func init() {
"server_insecure": {
"type": "boolean"
},
"skip_ssl_verification": {
"skip_tls_verification": {
"type": "boolean"
},
"url": {
@@ -6907,10 +6976,17 @@ func init() {
"updateTenantRequest": {
"type": "object",
"properties": {
"console_image": {
"type": "string",
"pattern": "^((.*?)/(.*?):(.+))$"
},
"image": {
"type": "string",
"pattern": "^((.*?)/(.*?):(.+))$"
},
"image_pull_secret": {
"type": "string"
},
"image_registry": {
"$ref": "#/definitions/imageRegistry"
}
@@ -7070,6 +7146,12 @@ func init() {
"size"
],
"properties": {
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"size": {
"type": "integer"
},

View File

@@ -50,7 +50,7 @@ func NewDeleteTenant(ctx *middleware.Context, handler DeleteTenantHandler) *Dele
/*DeleteTenant swagger:route DELETE /namespaces/{namespace}/tenants/{tenant} AdminAPI deleteTenant
Delete Tenant
Delete tenant and underlying pvcs
*/
type DeleteTenant struct {

View File

@@ -26,8 +26,11 @@ import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/minio/console/models"
)
// NewDeleteTenantParams creates a new DeleteTenantParams object
@@ -46,6 +49,10 @@ type DeleteTenantParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
In: body
*/
Body *models.DeleteTenantRequest
/*
Required: true
In: path
@@ -67,6 +74,22 @@ func (o *DeleteTenantParams) BindRequest(r *http.Request, route *middleware.Matc
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.DeleteTenantRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
res = append(res, errors.NewParseError("body", "body", "", err))
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
}
rNamespace, rhkNamespace, _ := route.Params.GetOK("namespace")
if err := o.bindNamespace(rNamespace, rhkNamespace, route.Formats); err != nil {
res = append(res, err)

View File

@@ -19,16 +19,30 @@ package restapi
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"time"
)
var (
certDontExists = "File certificate doesn't exists: %s"
)
func getCertPool() *x509.CertPool {
caCertFileNames := getMinioServerTLSRootCAs()
// If CAs certificates are configured we save them to the http.Client RootCAs store
certs := x509.NewCertPool()
for _, caCert := range caCertFileNames {
pemData, err := ioutil.ReadFile(caCert)
if err != nil {
// logging this error
log.Println(err)
continue
}
certs.AppendCertsFromPEM(pemData)
}
return certs
}
var certPool = getCertPool()
func prepareSTSClientTransport(insecure bool) *http.Transport {
// This takes github.com/minio/minio/pkg/madmin/transport.go as an example
@@ -36,18 +50,6 @@ func prepareSTSClientTransport(insecure bool) *http.Transport {
// DefaultTransport - this default transport is similar to
// http.DefaultTransport but with additional param DisableCompression
// is set to true to avoid decompressing content with 'gzip' encoding.
// Keep TLS config.
tlsConfig := &tls.Config{
// Can't use SSLv3 because of POODLE and BEAST
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
// Can't use TLSv1.1 because of RC4 cipher usage
MinVersion: tls.VersionTLS12,
}
if insecure {
tlsConfig.InsecureSkipVerify = true
}
DefaultTransport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
@@ -61,38 +63,14 @@ func prepareSTSClientTransport(insecure bool) *http.Transport {
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
DisableCompression: true,
TLSClientConfig: tlsConfig,
}
// If Minio instance is running with TLS enabled and it's using a self-signed certificate
// or a certificate issued by a custom certificate authority we prepare a new custom *http.Transport
if getMinIOEndpointIsSecure() {
caCertFileNames := getMinioServerTLSRootCAs()
tlsConfig := &tls.Config{
TLSClientConfig: &tls.Config{
// Can't use SSLv3 because of POODLE and BEAST
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
// Can't use TLSv1.1 because of RC4 cipher usage
MinVersion: tls.VersionTLS12,
}
// If CAs certificates are configured we save them to the http.Client RootCAs store
if len(caCertFileNames) > 0 {
certs := x509.NewCertPool()
for _, caCert := range caCertFileNames {
// Validate certificate exists
if FileExists(caCert) {
pemData, err := ioutil.ReadFile(caCert)
if err != nil {
// if there was an error reading pem file stop console
panic(err)
}
certs.AppendCertsFromPEM(pemData)
} else {
// if provided cert filename doesn't exists stop console
panic(fmt.Sprintf(certDontExists, caCert))
}
}
tlsConfig.RootCAs = certs
}
DefaultTransport.TLSClientConfig = tlsConfig
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: insecure,
RootCAs: certPool,
},
}
return DefaultTransport
}

View File

@@ -1069,7 +1069,7 @@ paths:
tags:
- AdminAPI
delete:
summary: Delete Tenant
summary: Delete tenant and underlying pvcs
operationId: DeleteTenant
parameters:
- name: namespace
@@ -1080,6 +1080,11 @@ paths:
in: path
required: true
type: string
- name: body
in: body
required: false
schema:
$ref: "#/definitions/deleteTenantRequest"
responses:
204:
description: A successful response.
@@ -1778,8 +1783,13 @@ definitions:
image:
type: string
pattern: "^((.*?)/(.*?):(.+))$"
console_image:
type: string
pattern: "^((.*?)/(.*?):(.+))$"
image_registry:
$ref: "#/definitions/imageRegistry"
image_pull_secret:
type: string
imageRegistry:
type: object
@@ -1807,6 +1817,8 @@ definitions:
pattern: "^[a-z0-9-]{3,63}$"
image:
type: string
console_image:
type: string
service_name:
type: string
zones:
@@ -1825,6 +1837,9 @@ definitions:
enable_tls:
type: boolean
default: true
enable_prometheus:
type: boolean
default: false
namespace:
type: string
erasureCodingParity:
@@ -1835,6 +1850,8 @@ definitions:
type: string
image_registry:
$ref: "#/definitions/imageRegistry"
image_pull_secret:
type: string
idp:
type: object
$ref: "#/definitions/idpConfiguration"
@@ -1901,7 +1918,7 @@ definitions:
type: string
group_name_attribute:
type: string
skip_ssl_verification:
skip_tls_verification:
type: boolean
server_insecure:
type: boolean
@@ -2074,6 +2091,10 @@ definitions:
type: integer
storage_class_name:
type: string
labels:
type: object
additionalProperties:
type: string
resources:
$ref: "#/definitions/zoneResources"
node_selector:
@@ -2507,3 +2528,9 @@ definitions:
used:
type: integer
format: int64
deleteTenantRequest:
type: object
properties:
delete_pvcs:
type: boolean