Skip to content

Commit

Permalink
Improvements for kubect-minio plugin
Browse files Browse the repository at this point in the history
- Use secure GenerateCredentials() function for tenants created via the
  krew plugin
- Use configuration secret instead of credsSecret for tenants created
  via the krew plugin

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
  • Loading branch information
Alevsk committed Jun 21, 2022
1 parent 11f30f7 commit 7cbdfe7
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 71 deletions.
67 changes: 44 additions & 23 deletions kubectl-minio/cmd/resources/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,73 @@
package resources

import (
"github.com/google/uuid"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func tenantSecretData() map[string][]byte {
m := make(map[string][]byte, 2)
m["accesskey"] = []byte(uuid.New().String())
m["secretkey"] = []byte(uuid.New().String())
return m
}

func consoleSecretData() map[string][]byte {
m := make(map[string][]byte, 5)
m["CONSOLE_ACCESS_KEY"] = []byte("admin")
m["CONSOLE_SECRET_KEY"] = []byte(uuid.New().String())
return m
// NewTenantCredsSecret : deprecated
func NewTenantCredsSecret(opts *TenantOptions) (*corev1.Secret, error) {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: opts.Name + "-creds-secret",
Namespace: opts.NS,
},
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: v1.SchemeGroupVersion.Version,
},
Data: map[string][]byte{
"accesskey": []byte(""),
"secretkey": []byte(""),
},
}, nil
}

// NewSecretForTenant will return a new secret a MinIO Tenant
func NewSecretForTenant(opts *TenantOptions) *corev1.Secret {
// NewTenantConfigurationSecret will return a new secret a MinIO Tenant
func NewTenantConfigurationSecret(opts *TenantOptions) (*corev1.Secret, error) {
accessKey, secretKey, err := miniov2.GenerateCredentials()
if err != nil {
return nil, err
}
tenantConfiguration := map[string]string{
"MINIO_ROOT_USER": accessKey,
"MINIO_ROOT_PASSWORD": secretKey,
}
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: opts.SecretName,
Name: opts.ConfigurationSecretName,
Namespace: opts.NS,
},
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: v1.SchemeGroupVersion.Version,
},
Data: tenantSecretData(),
}
Data: map[string][]byte{
"config.env": []byte(miniov2.GenerateTenantConfigurationFile(tenantConfiguration)),
},
}, nil
}

// NewSecretForConsole will return a new secret a MinIO Tenant Console
func NewSecretForConsole(opts *TenantOptions, name string) *corev1.Secret {
// NewUserCredentialsSecret will return a new secret a MinIO Tenant Console
func NewUserCredentialsSecret(opts *TenantOptions, secretName string) (*corev1.Secret, error) {
accessKey, secretKey, err := miniov2.GenerateCredentials()
if err != nil {
return nil, err
}
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Name: secretName,
Namespace: opts.NS,
},
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: v1.SchemeGroupVersion.Version,
},
Data: consoleSecretData(),
}
Data: map[string][]byte{
"CONSOLE_ACCESS_KEY": []byte(accessKey),
"CONSOLE_SECRET_KEY": []byte(secretKey),
},
}, nil
}
50 changes: 28 additions & 22 deletions kubectl-minio/cmd/resources/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,25 @@ import (

// TenantOptions encapsulates the CLI options for a MinIO Tenant
type TenantOptions struct {
Name string
SecretName string
Servers int32
Volumes int32
Capacity string
NS string
Image string
StorageClass string
KmsSecret string
ConsoleSecret string
DisableTLS bool
ImagePullSecret string
DisableAntiAffinity bool
EnableAuditLogs bool
AuditLogsDiskSpace int32
AuditLogsImage string
AuditLogsPGImage string
AuditLogsPGInitImage string
AuditLogsStorageClass string
Name string
ConfigurationSecretName string
Servers int32
Volumes int32
Capacity string
NS string
Image string
StorageClass string
KmsSecret string
ConsoleSecret string
DisableTLS bool
ImagePullSecret string
DisableAntiAffinity bool
EnableAuditLogs bool
AuditLogsDiskSpace int32
AuditLogsImage string
AuditLogsPGImage string
AuditLogsPGInitImage string
AuditLogsStorageClass string
}

// Validate Tenant Options
Expand Down Expand Up @@ -102,7 +102,7 @@ func storageClass(sc string) *string {
}

// NewTenant will return a new Tenant for a MinIO Operator
func NewTenant(opts *TenantOptions, user *v1.Secret) (*miniov2.Tenant, error) {
func NewTenant(opts *TenantOptions, userSecret *v1.Secret) (*miniov2.Tenant, error) {
autoCert := true
volumesPerServer := helpers.VolumesPerServer(opts.Volumes, opts.Servers)
capacityPerVolume, err := helpers.CapacityPerVolume(opts.Capacity, opts.Volumes)
Expand All @@ -121,8 +121,11 @@ func NewTenant(opts *TenantOptions, user *v1.Secret) (*miniov2.Tenant, error) {
},
Spec: miniov2.TenantSpec{
Image: opts.Image,
Configuration: &v1.LocalObjectReference{
Name: opts.ConfigurationSecretName,
},
CredsSecret: &v1.LocalObjectReference{
Name: opts.SecretName,
Name: opts.Name + "-creds-secret",
},
Pools: []miniov2.Pool{Pool(opts, volumesPerServer, *capacityPerVolume)},
RequestAutoCert: &autoCert,
Expand All @@ -136,14 +139,17 @@ func NewTenant(opts *TenantOptions, user *v1.Secret) (*miniov2.Tenant, error) {
ImagePullSecret: v1.LocalObjectReference{Name: opts.ImagePullSecret},
Users: []*v1.LocalObjectReference{
{
Name: user.Name,
Name: userSecret.Name,
},
},
},
}
if opts.EnableAuditLogs {
t.Spec.Log = getAuditLogConfig(opts)
}

t.EnsureDefaults()

return t, t.Validate()
}

Expand Down
71 changes: 45 additions & 26 deletions kubectl-minio/cmd/tenant-create.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ import (
const (
createDesc = `
'create' command creates a new MinIO tenant`
createExample = ` kubectl minio tenant create tenant1 --servers 4 --volumes 16 --capacity 16Ti --namespace tenant1-ns`
tenantSecretSuffix = "-creds-secret"
createExample = ` kubectl minio tenant create tenant1 --servers 4 --volumes 16 --capacity 16Ti --namespace tenant1-ns`
)

type createCmd struct {
Expand Down Expand Up @@ -112,68 +111,88 @@ func (c *createCmd) validate(args []string) error {
return err
}
c.tenantOpts.Name = args[0]
c.tenantOpts.SecretName = c.tenantOpts.Name + tenantSecretSuffix
c.tenantOpts.ConfigurationSecretName = fmt.Sprintf("%s-env-configuration", c.tenantOpts.Name)
return c.tenantOpts.Validate()
}

// run initializes local config and installs MinIO Operator to Kubernetes cluster.
func (c *createCmd) run(args []string) error {
// Create operator and kube client
oclient, err := helpers.GetKubeOperatorClient()
operatorClient, err := helpers.GetKubeOperatorClient()
if err != nil {
return err
}
path, _ := rootCmd.Flags().GetString(kubeconfig)
kclient, err := helpers.GetKubeClient(path)
kubeClient, err := helpers.GetKubeClient(path)
if err != nil {
return err
}

// Generate console suer
consoleUser := resources.NewSecretForConsole(&c.tenantOpts, fmt.Sprintf("%s-user-1", c.tenantOpts.Name))

// generate the resources
t, err := resources.NewTenant(&c.tenantOpts, consoleUser)
// Generate MinIO user credentials
tenantUserCredentials, err := resources.NewUserCredentialsSecret(&c.tenantOpts, fmt.Sprintf("%s-user-1", c.tenantOpts.Name))
if err != nil {
return err
}
// deprecated tenant credsSecret required for deploying tenant, will be removed in operator v5.x.x
tenantCredsSecret, err := resources.NewTenantCredsSecret(&c.tenantOpts)
if err != nil {
return err
}
// generate tenant configuration
tenantConfiguration, err := resources.NewTenantConfigurationSecret(&c.tenantOpts)
if err != nil {
return err
}
// generate tenant resource
tenant, err := resources.NewTenant(&c.tenantOpts, tenantUserCredentials)
if err != nil {
return err
}
s := resources.NewSecretForTenant(&c.tenantOpts)

// create resources
if !c.output {
return createTenant(oclient, kclient, t, s, consoleUser)
return createTenant(operatorClient, kubeClient, tenant, tenantCredsSecret, tenantConfiguration, tenantUserCredentials)
}
ot, err := yaml.Marshal(&t)
tenantYAML, err := yaml.Marshal(&tenant)
if err != nil {
return err
}
os, err := yaml.Marshal(&s)
tenantConfigurationYAML, err := yaml.Marshal(&tenantConfiguration)
if err != nil {
return err
}
oc, err := yaml.Marshal(&consoleUser)
tenantCredsSecretYAML, err := yaml.Marshal(&tenantCredsSecret)
if err != nil {
return err
}
fmt.Println(string(ot))
tenantUserYAML, err := yaml.Marshal(&tenantUserCredentials)
if err != nil {
return err
}
fmt.Println(string(tenantYAML))
fmt.Println("---")
fmt.Println(string(tenantConfigurationYAML))
fmt.Println("---")
fmt.Println(string(os))
fmt.Println(string(tenantCredsSecretYAML))
fmt.Println("---")
fmt.Println(string(oc))
fmt.Println(string(tenantUserYAML))
return nil
}

func createTenant(oclient *operatorv1.Clientset, kclient *kubernetes.Clientset, t *miniov2.Tenant, s, console *corev1.Secret) error {
if _, err := kclient.CoreV1().Namespaces().Get(context.Background(), t.Namespace, metav1.GetOptions{}); err != nil {
return fmt.Errorf("Namespace %s not found, please create the namespace using 'kubectl create ns %s'", t.Namespace, t.Namespace)
func createTenant(operatorClient *operatorv1.Clientset, kubeClient *kubernetes.Clientset, tenant *miniov2.Tenant, tenantCredsSecret, tenantConfiguration, console *corev1.Secret) error {
if _, err := kubeClient.CoreV1().Namespaces().Get(context.Background(), tenant.Namespace, metav1.GetOptions{}); err != nil {
return fmt.Errorf("namespace %s not found, please create the namespace using 'kubectl create ns %s'", tenant.Namespace, tenant.Namespace)
}
// deprecated tenant credsSecret required for deploying tenant
// The credsSecret field will be removed in operator v5.x.x
if _, err := kubeClient.CoreV1().Secrets(tenant.Namespace).Create(context.Background(), tenantCredsSecret, metav1.CreateOptions{}); err != nil {
return err
}
if _, err := kclient.CoreV1().Secrets(t.Namespace).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
if _, err := kubeClient.CoreV1().Secrets(tenant.Namespace).Create(context.Background(), tenantConfiguration, metav1.CreateOptions{}); err != nil {
return err
}
if _, err := kclient.CoreV1().Secrets(t.Namespace).Create(context.Background(), console, metav1.CreateOptions{}); err != nil {
if _, err := kubeClient.CoreV1().Secrets(tenant.Namespace).Create(context.Background(), console, metav1.CreateOptions{}); err != nil {
return err
}
to, err := oclient.MinioV2().Tenants(t.Namespace).Create(context.Background(), t, v1.CreateOptions{})
to, err := operatorClient.MinioV2().Tenants(tenant.Namespace).Create(context.Background(), tenant, v1.CreateOptions{})
if err != nil {
return err
}
Expand Down
83 changes: 83 additions & 0 deletions pkg/apis/minio.min.io/v2/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (C) 2022, MinIO, Inc.
//
// This code is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License, version 3,
// as published by the Free Software Foundation.
//
// 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, version 3,
// along with this program. If not, see <http://www.gnu.org/licenses/>

package v2

import (
"crypto/rand"
"encoding/base64"
"fmt"
"strings"
)

const (
// Maximum length for MinIO access key.
// There is no max length enforcement for access keys
accessKeyMaxLen = 20

// Maximum secret key length for MinIO, this
// is used when autogenerating new credentials.
// There is no max length enforcement for secret keys
secretKeyMaxLen = 40

// Alpha numeric table used for generating access keys.
alphaNumericTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

// Total length of the alpha numeric table.
alphaNumericTableLen = byte(len(alphaNumericTable))
)

// GenerateCredentials - creates randomly generated credentials of maximum allowed length.
func GenerateCredentials() (accessKey, secretKey string, err error) {
readBytes := func(size int) (data []byte, err error) {
data = make([]byte, size)
var n int
if n, err = rand.Read(data); err != nil {
return nil, err
} else if n != size {
return nil, fmt.Errorf("Not enough data. Expected to read: %v bytes, got: %v bytes", size, n)
}
return data, nil
}

// Generate access key.
keyBytes, err := readBytes(accessKeyMaxLen)
if err != nil {
return "", "", err
}
for i := 0; i < accessKeyMaxLen; i++ {
keyBytes[i] = alphaNumericTable[keyBytes[i]%alphaNumericTableLen]
}
accessKey = string(keyBytes)

// Generate secret key.
keyBytes, err = readBytes(secretKeyMaxLen)
if err != nil {
return "", "", err
}

secretKey = strings.ReplaceAll(string([]byte(base64.StdEncoding.EncodeToString(keyBytes))[:secretKeyMaxLen]),
"/", "+")

return accessKey, secretKey, nil
}

// GenerateTenantConfigurationFile :
func GenerateTenantConfigurationFile(configuration map[string]string) string {
var rawConfiguration strings.Builder
for key, val := range configuration {
rawConfiguration.WriteString(fmt.Sprintf(`export %s="%s"`, key, val) + "\n")
}
return rawConfiguration.String()
}

0 comments on commit 7cbdfe7

Please sign in to comment.