Files
openmaxio-object-browser/operatorapi/operator_tenants.go

2409 lines
84 KiB
Go
Raw Normal View History

// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package operatorapi
import (
2021-05-10 15:27:52 -07:00
"bytes"
"context"
"crypto/x509"
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
"encoding/base64"
"encoding/json"
"encoding/pem"
2020-07-27 14:06:12 -07:00
"errors"
"fmt"
"net"
"net/http"
"os"
"sort"
"strconv"
"strings"
"time"
"github.com/dustin/go-humanize"
"github.com/minio/console/restapi"
"github.com/minio/console/operatorapi/operations/operator_api"
"github.com/minio/console/pkg/auth/utils"
2021-05-10 15:27:52 -07:00
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/api/resource"
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
"k8s.io/apimachinery/pkg/types"
corev1 "k8s.io/api/core/v1"
"github.com/minio/console/cluster"
"github.com/minio/madmin-go"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/operatorapi/operations"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-05-10 15:27:52 -07:00
k8sJson "k8s.io/apimachinery/pkg/runtime/serializer/json"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
type imageRegistry struct {
Auths map[string]imageRegistryCredentials `json:"auths"`
}
type imageRegistryCredentials struct {
Username string `json:"username"`
Password string `json:"password"`
Auth string `json:"auth"`
}
func registerTenantHandlers(api *operations.OperatorAPI) {
// Add Tenant
api.OperatorAPICreateTenantHandler = operator_api.CreateTenantHandlerFunc(func(params operator_api.CreateTenantParams, session *models.Principal) middleware.Responder {
resp, err := getTenantCreatedResponse(session, params)
if err != nil {
return operator_api.NewCreateTenantDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewCreateTenantOK().WithPayload(resp)
})
// List All Tenants of all namespaces
api.OperatorAPIListAllTenantsHandler = operator_api.ListAllTenantsHandlerFunc(func(params operator_api.ListAllTenantsParams, session *models.Principal) middleware.Responder {
resp, err := getListAllTenantsResponse(session, params)
if err != nil {
return operator_api.NewListTenantsDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewListTenantsOK().WithPayload(resp)
})
// List Tenants by namespace
api.OperatorAPIListTenantsHandler = operator_api.ListTenantsHandlerFunc(func(params operator_api.ListTenantsParams, session *models.Principal) middleware.Responder {
resp, err := getListTenantsResponse(session, params)
if err != nil {
return operator_api.NewListTenantsDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewListTenantsOK().WithPayload(resp)
})
// Detail Tenant
api.OperatorAPITenantDetailsHandler = operator_api.TenantDetailsHandlerFunc(func(params operator_api.TenantDetailsParams, session *models.Principal) middleware.Responder {
resp, err := getTenantDetailsResponse(session, params)
if err != nil {
return operator_api.NewTenantDetailsDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewTenantDetailsOK().WithPayload(resp)
})
// Tenant Security details
api.OperatorAPITenantSecurityHandler = operator_api.TenantSecurityHandlerFunc(func(params operator_api.TenantSecurityParams, session *models.Principal) middleware.Responder {
resp, err := getTenantSecurityResponse(session, params)
if err != nil {
return operator_api.NewTenantSecurityDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewTenantSecurityOK().WithPayload(resp)
})
// Update Tenant Security configuration
api.OperatorAPIUpdateTenantSecurityHandler = operator_api.UpdateTenantSecurityHandlerFunc(func(params operator_api.UpdateTenantSecurityParams, session *models.Principal) middleware.Responder {
err := getUpdateTenantSecurityResponse(session, params)
if err != nil {
return operator_api.NewUpdateTenantSecurityDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewUpdateTenantSecurityNoContent()
})
// Delete Tenant
api.OperatorAPIDeleteTenantHandler = operator_api.DeleteTenantHandlerFunc(func(params operator_api.DeleteTenantParams, session *models.Principal) middleware.Responder {
err := getDeleteTenantResponse(session, params)
if err != nil {
return operator_api.NewDeleteTenantDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewDeleteTenantNoContent()
})
// Delete Pod
api.OperatorAPIDeletePodHandler = operator_api.DeletePodHandlerFunc(func(params operator_api.DeletePodParams, session *models.Principal) middleware.Responder {
err := getDeletePodResponse(session, params)
if err != nil {
return operator_api.NewDeletePodDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewDeletePodNoContent()
})
// Update Tenant
api.OperatorAPIUpdateTenantHandler = operator_api.UpdateTenantHandlerFunc(func(params operator_api.UpdateTenantParams, session *models.Principal) middleware.Responder {
err := getUpdateTenantResponse(session, params)
if err != nil {
return operator_api.NewUpdateTenantDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewUpdateTenantCreated()
})
2020-07-13 20:36:27 -07:00
// Add Tenant Pools
api.OperatorAPITenantAddPoolHandler = operator_api.TenantAddPoolHandlerFunc(func(params operator_api.TenantAddPoolParams, session *models.Principal) middleware.Responder {
err := getTenantAddPoolResponse(session, params)
2020-07-13 20:36:27 -07:00
if err != nil {
return operator_api.NewTenantAddPoolDefault(int(err.Code)).WithPayload(err)
2020-07-13 20:36:27 -07:00
}
return operator_api.NewTenantAddPoolCreated()
2020-07-13 20:36:27 -07:00
})
2020-07-27 14:19:40 -07:00
// Get Tenant Usage
api.OperatorAPIGetTenantUsageHandler = operator_api.GetTenantUsageHandlerFunc(func(params operator_api.GetTenantUsageParams, session *models.Principal) middleware.Responder {
2020-07-27 14:19:40 -07:00
payload, err := getTenantUsageResponse(session, params)
if err != nil {
return operator_api.NewGetTenantUsageDefault(int(err.Code)).WithPayload(err)
2020-07-27 14:19:40 -07:00
}
return operator_api.NewGetTenantUsageOK().WithPayload(payload)
2020-07-27 14:19:40 -07:00
})
api.OperatorAPIGetTenantPodsHandler = operator_api.GetTenantPodsHandlerFunc(func(params operator_api.GetTenantPodsParams, session *models.Principal) middleware.Responder {
payload, err := getTenantPodsResponse(session, params)
if err != nil {
return operator_api.NewGetTenantPodsDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewGetTenantPodsOK().WithPayload(payload)
})
api.OperatorAPIGetPodLogsHandler = operator_api.GetPodLogsHandlerFunc(func(params operator_api.GetPodLogsParams, session *models.Principal) middleware.Responder {
payload, err := getPodLogsResponse(session, params)
if err != nil {
return operator_api.NewGetPodLogsDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewGetPodLogsOK().WithPayload(payload)
})
api.OperatorAPIGetPodEventsHandler = operator_api.GetPodEventsHandlerFunc(func(params operator_api.GetPodEventsParams, session *models.Principal) middleware.Responder {
payload, err := getPodEventsResponse(session, params)
if err != nil {
return operator_api.NewGetPodEventsDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewGetPodEventsOK().WithPayload(payload)
})
// Update Tenant Pools
api.OperatorAPITenantUpdatePoolsHandler = operator_api.TenantUpdatePoolsHandlerFunc(func(params operator_api.TenantUpdatePoolsParams, session *models.Principal) middleware.Responder {
resp, err := getTenantUpdatePoolResponse(session, params)
if err != nil {
return operator_api.NewTenantUpdatePoolsDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewTenantUpdatePoolsOK().WithPayload(resp)
})
// Update Tenant Certificates
api.OperatorAPITenantUpdateCertificateHandler = operator_api.TenantUpdateCertificateHandlerFunc(func(params operator_api.TenantUpdateCertificateParams, session *models.Principal) middleware.Responder {
err := getTenantUpdateCertificatesResponse(session, params)
if err != nil {
return operator_api.NewTenantUpdateCertificateDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewTenantUpdateCertificateCreated()
})
// Update Tenant Encryption Configuration
api.OperatorAPITenantUpdateEncryptionHandler = operator_api.TenantUpdateEncryptionHandlerFunc(func(params operator_api.TenantUpdateEncryptionParams, session *models.Principal) middleware.Responder {
err := getTenantUpdateEncryptionResponse(session, params)
if err != nil {
return operator_api.NewTenantUpdateEncryptionDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewTenantUpdateEncryptionCreated()
})
2021-05-10 15:27:52 -07:00
// Get Tenant YAML
api.OperatorAPIGetTenantYAMLHandler = operator_api.GetTenantYAMLHandlerFunc(func(params operator_api.GetTenantYAMLParams, principal *models.Principal) middleware.Responder {
2021-05-10 15:27:52 -07:00
payload, err := getTenantYAML(principal, params)
if err != nil {
return operator_api.NewGetTenantYAMLDefault(int(err.Code)).WithPayload(err)
2021-05-10 15:27:52 -07:00
}
return operator_api.NewGetTenantYAMLOK().WithPayload(payload)
2021-05-10 15:27:52 -07:00
})
// Update Tenant YAML
api.OperatorAPIPutTenantYAMLHandler = operator_api.PutTenantYAMLHandlerFunc(func(params operator_api.PutTenantYAMLParams, principal *models.Principal) middleware.Responder {
2021-05-10 15:27:52 -07:00
err := getUpdateTenantYAML(principal, params)
if err != nil {
return operator_api.NewPutTenantYAMLDefault(int(err.Code)).WithPayload(err)
2021-05-10 15:27:52 -07:00
}
return operator_api.NewPutTenantYAMLCreated()
2021-05-10 15:27:52 -07:00
})
}
// getDeleteTenantResponse gets the output of deleting a minio instance
func getDeleteTenantResponse(session *models.Principal, params operator_api.DeleteTenantParams) *models.Error {
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
}
// get Kubernetes Client
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
deleteTenantPVCs := false
if params.Body != nil {
deleteTenantPVCs = params.Body.DeletePvcs
}
if err = deleteTenantAction(context.Background(), opClient, clientset.CoreV1(), params.Namespace, params.Tenant, deleteTenantPVCs); err != nil {
return prepareError(err)
}
return nil
}
// 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 OperatorClientI,
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", miniov2.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
}
// getDeleteTenantResponse gets the output of deleting a minio instance
func getDeletePodResponse(session *models.Principal, params operator_api.DeletePodParams) *models.Error {
ctx := context.Background()
// get Kubernetes Client
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
}
listOpts := metav1.ListOptions{
LabelSelector: fmt.Sprintf("v1.min.io/tenant=%s", params.Tenant),
FieldSelector: fmt.Sprintf("metadata.name=%s%s", params.Tenant, params.PodName[len(params.Tenant):]),
}
if err = clientset.CoreV1().Pods(params.Namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, listOpts); err != nil {
return prepareError(err)
}
return nil
}
2020-09-21 21:31:30 -07:00
// GetTenantServiceURL gets tenant's service url with the proper scheme and port
func GetTenantServiceURL(mi *miniov2.Tenant) (svcURL string) {
scheme := "http"
port := miniov2.MinIOPortLoadBalancerSVC
2020-07-23 11:13:05 -07:00
if mi.AutoCert() || mi.ExternalCert() {
scheme = "https"
port = miniov2.MinIOTLSPortLoadBalancerSVC
}
return fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(mi.MinIOFQDNServiceName(), strconv.Itoa(port)))
}
func getTenantAdminClient(ctx context.Context, client K8sClientI, tenant *miniov2.Tenant, svcURL string) (*madmin.AdminClient, error) {
tenantCreds, err := getTenantCreds(ctx, client, tenant)
if err != nil {
return nil, err
}
sessionToken := ""
mAdmin, pErr := restapi.NewAdminClientWithInsecure(svcURL, tenantCreds.accessKey, tenantCreds.secretKey, sessionToken, false)
if pErr != nil {
return nil, pErr.Cause
}
return mAdmin, nil
}
type tenantKeys struct {
accessKey string
secretKey string
}
func getTenantCreds(ctx context.Context, client K8sClientI, tenant *miniov2.Tenant) (*tenantKeys, error) {
if tenant == nil || tenant.Spec.CredsSecret == nil {
return nil, errors.New("invalid arguments")
}
// get admin credentials from secret
creds, err := client.getSecret(ctx, tenant.Namespace, tenant.Spec.CredsSecret.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
tenantAccessKey, ok := creds.Data["accesskey"]
if !ok {
restapi.LogError("tenant's secret doesn't contain accesskey")
return nil, restapi.ErrorGeneric
}
tenantSecretKey, ok := creds.Data["secretkey"]
if !ok {
restapi.LogError("tenant's secret doesn't contain secretkey")
return nil, restapi.ErrorGeneric
}
// TODO:
// We need to avoid using minio root credentials to talk to tenants, and instead use a different user credentials
// when that its implemented we also need to check here if the tenant has LDAP enabled so we authenticate first against AD
return &tenantKeys{accessKey: string(tenantAccessKey), secretKey: string(tenantSecretKey)}, nil
}
func getTenant(ctx context.Context, operatorClient OperatorClientI, namespace, tenantName string) (*miniov2.Tenant, error) {
2020-07-25 14:38:16 -07:00
minInst, err := operatorClient.TenantGet(ctx, namespace, tenantName, metav1.GetOptions{})
if err != nil {
return nil, err
}
return minInst, nil
}
func isPrometheusEnabled(annotations map[string]string) bool {
if annotations == nil {
return false
}
// if one of the following prometheus annotations are not present
// we consider the tenant as not integrated with prometheus
if _, ok := annotations[prometheusPath]; !ok {
return false
}
if _, ok := annotations[prometheusPort]; !ok {
return false
}
if _, ok := annotations[prometheusScrape]; !ok {
return false
}
return true
}
func getTenantInfo(tenant *miniov2.Tenant) *models.Tenant {
var pools []*models.Pool
consoleImage := ""
2020-07-25 14:38:16 -07:00
var totalSize int64
for _, p := range tenant.Spec.Pools {
pools = append(pools, parseTenantPool(&p))
poolSize := int64(p.Servers) * int64(p.VolumesPerServer) * p.VolumeClaimTemplate.Spec.Resources.Requests.Storage().Value()
totalSize = totalSize + poolSize
}
var deletion string
if tenant.ObjectMeta.DeletionTimestamp != nil {
deletion = tenant.ObjectMeta.DeletionTimestamp.Format(time.RFC3339)
}
if tenant.HasConsoleEnabled() {
consoleImage = tenant.Spec.Console.Image
}
return &models.Tenant{
CreationDate: tenant.ObjectMeta.CreationTimestamp.Format(time.RFC3339),
DeletionDate: deletion,
Name: tenant.Name,
TotalSize: totalSize,
CurrentState: tenant.Status.CurrentState,
Pools: pools,
Namespace: tenant.ObjectMeta.Namespace,
Image: tenant.Spec.Image,
ConsoleImage: consoleImage,
EnablePrometheus: isPrometheusEnabled(tenant.Annotations),
}
}
func getTenantDetailsResponse(session *models.Principal, params operator_api.TenantDetailsParams) (*models.Tenant, *models.Error) {
2020-07-25 14:38:16 -07:00
// 5 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
2020-07-25 14:38:16 -07:00
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return nil, prepareError(err)
}
2020-07-25 14:38:16 -07:00
2020-07-27 14:19:40 -07:00
info := getTenantInfo(minTenant)
// detect if AD is enabled
adEnabled := false
for _, env := range minTenant.Spec.Env {
if env.Name == "MINIO_IDENTITY_LDAP_SERVER_ADDR" && env.Value != "" {
adEnabled = true
}
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
k8sClient := k8sClient{
client: clientSet,
}
// detect if OpenID is enabled
oicEnabled := false
consoleSelector := fmt.Sprintf("%s-console", minTenant.Name)
consoleSecretName := fmt.Sprintf("%s-secret", consoleSelector)
consoleSecret, err := clientSet.CoreV1().Secrets(minTenant.Namespace).Get(ctx, consoleSecretName, metav1.GetOptions{})
// we can tolerate not getting this secret
if err != nil {
restapi.LogError("unable to fetch existing secrets for %s: %v", minTenant.Name, err)
}
if consoleSecret != nil {
if _, ok := consoleSecret.Data["CONSOLE_IDP_URL"]; ok {
oicEnabled = true
}
}
if minTenant.HasConsoleEnabled() {
for _, env := range minTenant.Spec.Console.Env {
if env.Name == "CONSOLE_IDP_URL" {
oicEnabled = true
}
}
}
info.LogEnabled = minTenant.HasLogEnabled()
info.MonitoringEnabled = minTenant.HasPrometheusEnabled()
info.EncryptionEnabled = minTenant.HasKESEnabled()
info.IdpAdEnabled = adEnabled
info.IdpOicEnabled = oicEnabled
info.MinioTLS = minTenant.TLS()
info.ConsoleTLS = minTenant.AutoCert() || minTenant.ConsoleExternalCert()
info.ConsoleEnabled = minTenant.HasConsoleEnabled()
if minTenant.Spec.Console != nil {
// obtain current subnet license for tenant (if exists)
license, _ := getSubscriptionLicense(context.Background(), &k8sClient, params.Namespace, minTenant.Spec.Console.ConsoleSecret.Name)
if license != "" {
client := &cluster.HTTPClient{
Client: restapi.GetConsoleSTSClient(),
}
licenseInfo, _, _ := subscriptionValidate(client, license, "", "")
// if licenseInfo is present attach it to the tenantInfo response
if licenseInfo != nil {
info.SubnetLicense = licenseInfo
}
}
}
// attach status information
info.Status = &models.TenantStatus{
HealthStatus: string(minTenant.Status.HealthStatus),
DrivesHealing: minTenant.Status.DrivesHealing,
DrivesOffline: minTenant.Status.DrivesOffline,
DrivesOnline: minTenant.Status.DrivesOnline,
WriteQuorum: minTenant.Status.WriteQuorum,
}
// get tenant service
minTenant.EnsureDefaults()
//minio service
minSvc, err := k8sClient.getService(ctx, minTenant.Namespace, minTenant.MinIOCIServiceName(), metav1.GetOptions{})
if err != nil {
// we can tolerate this error
restapi.LogError("Unable to get MinIO service name: %v, continuing", err)
}
//console service
conSvc, err := k8sClient.getService(ctx, minTenant.Namespace, minTenant.ConsoleCIServiceName(), metav1.GetOptions{})
if err != nil {
// we can tolerate this error
restapi.LogError("Unable to get MinIO console service name: %v, continuing", err)
}
schema := "http"
consoleSchema := "http"
consolePort := ":9090"
if minTenant.TLS() {
schema = "https"
}
if minTenant.AutoCert() || minTenant.ConsoleExternalCert() {
consoleSchema = "https"
consolePort = ":9443"
}
var minioEndpoint string
var consoleEndpoint string
if minSvc != nil && len(minSvc.Status.LoadBalancer.Ingress) > 0 {
if minSvc.Status.LoadBalancer.Ingress[0].IP != "" {
minioEndpoint = fmt.Sprintf("%s://%s", schema, minSvc.Status.LoadBalancer.Ingress[0].IP)
}
if minSvc.Status.LoadBalancer.Ingress[0].Hostname != "" {
minioEndpoint = fmt.Sprintf("%s://%s", schema, minSvc.Status.LoadBalancer.Ingress[0].Hostname)
}
}
if conSvc != nil && len(conSvc.Status.LoadBalancer.Ingress) > 0 {
if conSvc.Status.LoadBalancer.Ingress[0].IP != "" {
consoleEndpoint = fmt.Sprintf("%s://%s%s", consoleSchema, conSvc.Status.LoadBalancer.Ingress[0].IP, consolePort)
}
if conSvc.Status.LoadBalancer.Ingress[0].Hostname != "" {
consoleEndpoint = fmt.Sprintf("%s://%s%s", consoleSchema, conSvc.Status.LoadBalancer.Ingress[0].Hostname, consolePort)
}
}
if minioEndpoint != "" || consoleEndpoint != "" {
info.Endpoints = &models.TenantEndpoints{
Console: consoleEndpoint,
Minio: minioEndpoint,
}
}
return info, nil
}
// parseTenantCertificates convert public key pem certificates stored in k8s secrets for a given Tenant into x509 certificates
func parseTenantCertificates(ctx context.Context, clientSet K8sClientI, namespace string, secrets []*miniov2.LocalCertificateReference) ([]*models.CertificateInfo, error) {
var certificates []*models.CertificateInfo
publicKey := "public.crt"
// Iterate over TLS secrets and build array of CertificateInfo structure
// that will be used to display information about certs in the UI
for _, secret := range secrets {
keyPair, err := clientSet.getSecret(ctx, namespace, secret.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
if secret.Type == "kubernetes.io/tls" || secret.Type == "cert-manager.io/v1alpha2" {
publicKey = "tls.crt"
}
// Extract public key from certificate TLS secret
if rawCert, ok := keyPair.Data[publicKey]; ok {
block, _ := pem.Decode(rawCert)
if block == nil {
// If certificate failed to decode skip
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
certificates = append(certificates, &models.CertificateInfo{
SerialNumber: cert.SerialNumber.String(),
Name: secret.Name,
Domains: cert.DNSNames,
Expiry: cert.NotAfter.String(),
})
}
}
return certificates, nil
}
func getTenantSecurity(ctx context.Context, clientSet K8sClientI, tenant *miniov2.Tenant) (response *models.TenantSecurityResponse, err error) {
var minioExternalCertificates []*models.CertificateInfo
var minioExternalCaCertificates []*models.CertificateInfo
var consoleExternalCertificates []*models.CertificateInfo
var consoleExternalCaCertificates []*models.CertificateInfo
// Certificates used by MinIO server
if minioExternalCertificates, err = parseTenantCertificates(ctx, clientSet, tenant.Namespace, tenant.Spec.ExternalCertSecret); err != nil {
return nil, err
}
// CA Certificates used by MinIO server
if minioExternalCaCertificates, err = parseTenantCertificates(ctx, clientSet, tenant.Namespace, tenant.Spec.ExternalCaCertSecret); err != nil {
return nil, err
}
if tenant.HasConsoleEnabled() {
// Certificate used by Console server
if tenant.Spec.Console.ExternalCertSecret != nil {
if consoleExternalCertificates, err = parseTenantCertificates(ctx, clientSet, tenant.Namespace, []*miniov2.LocalCertificateReference{tenant.Spec.Console.ExternalCertSecret}); err != nil {
return nil, err
}
}
// CA Certificates used by Console server
if consoleExternalCaCertificates, err = parseTenantCertificates(ctx, clientSet, tenant.Namespace, tenant.Spec.Console.ExternalCaCertSecret); err != nil {
return nil, err
}
}
return &models.TenantSecurityResponse{
AutoCert: tenant.AutoCert(),
CustomCertificates: &models.TenantSecurityResponseCustomCertificates{
Minio: minioExternalCertificates,
MinioCAs: minioExternalCaCertificates,
Console: consoleExternalCertificates,
ConsoleCAs: consoleExternalCaCertificates,
},
}, nil
}
func getTenantSecurityResponse(session *models.Principal, params operator_api.TenantSecurityParams) (*models.TenantSecurityResponse, *models.Error) {
// 5 seconds timeout
ctx := context.Background()
//ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
//defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return nil, prepareError(err)
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
k8sClient := k8sClient{
client: clientSet,
}
if err != nil {
return nil, prepareError(err)
}
info, err := getTenantSecurity(ctx, &k8sClient, minTenant)
if err != nil {
return nil, prepareError(err)
}
return info, nil
}
func getUpdateTenantSecurityResponse(session *models.Principal, params operator_api.UpdateTenantSecurityParams) *models.Error {
// 5 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
}
k8sClient := k8sClient{
client: clientSet,
}
opClient := &operatorClient{
client: opClientClientSet,
}
if err := updateTenantSecurity(ctx, opClient, &k8sClient, params.Namespace, params); err != nil {
return prepareError(err, errors.New("unable to update tenant"))
}
return nil
}
// updateTenantSecurity
func updateTenantSecurity(ctx context.Context, operatorClient OperatorClientI, client K8sClientI, namespace string, params operator_api.UpdateTenantSecurityParams) error {
minInst, err := operatorClient.TenantGet(ctx, namespace, params.Tenant, metav1.GetOptions{})
if err != nil {
return err
}
// Update AutoCert
minInst.Spec.RequestAutoCert = &params.Body.AutoCert
var newMinIOExternalCertSecret []*miniov2.LocalCertificateReference
var newMinIOExternalCaCertSecret []*miniov2.LocalCertificateReference
var newConsoleExternalCertSecret *miniov2.LocalCertificateReference
var newConsoleExternalCaCertSecret []*miniov2.LocalCertificateReference
// Remove Certificate Secrets from MinIO (Tenant.Spec.ExternalCertSecret)
for _, certificate := range minInst.Spec.ExternalCertSecret {
skip := false
for _, certificateToBeDeleted := range params.Body.CustomCertificates.SecretsToBeDeleted {
if certificate.Name == certificateToBeDeleted {
skip = true
break
}
}
if skip {
continue
}
newMinIOExternalCertSecret = append(newMinIOExternalCertSecret, certificate)
}
// Remove Certificate Secrets from MinIO CAs (Tenant.Spec.ExternalCaCertSecret)
for _, certificate := range minInst.Spec.ExternalCaCertSecret {
skip := false
for _, certificateToBeDeleted := range params.Body.CustomCertificates.SecretsToBeDeleted {
if certificate.Name == certificateToBeDeleted {
skip = true
break
}
}
if skip {
continue
}
newMinIOExternalCaCertSecret = append(newMinIOExternalCaCertSecret, certificate)
}
if minInst.HasConsoleEnabled() {
// Remove Certificate Secrets from Console (Tenant.Spec.Console.ExternalCertSecret)
if minInst.ConsoleExternalCert() {
newConsoleExternalCertSecret = minInst.Spec.Console.ExternalCertSecret
for _, certificateToBeDeleted := range params.Body.CustomCertificates.SecretsToBeDeleted {
if newConsoleExternalCertSecret.Name == certificateToBeDeleted {
newConsoleExternalCertSecret = nil
break
}
}
}
// Remove Certificate Secrets from Console CAs (Tenant.Spec.Console.ExternalCaCertSecret)
for _, certificate := range minInst.Spec.Console.ExternalCaCertSecret {
skip := false
for _, certificateToBeDeleted := range params.Body.CustomCertificates.SecretsToBeDeleted {
if certificate.Name == certificateToBeDeleted {
skip = true
break
}
}
if skip {
continue
}
newConsoleExternalCaCertSecret = append(newConsoleExternalCaCertSecret, certificate)
}
}
//Create new Certificate Secrets for MinIO
secretName := fmt.Sprintf("%s-%s", minInst.Name, strings.ToLower(utils.RandomCharString(5)))
externalCertSecretName := fmt.Sprintf("%s-external-certificates", secretName)
externalCertSecrets, err := createOrReplaceExternalCertSecrets(ctx, client, minInst.Namespace, params.Body.CustomCertificates.Minio, externalCertSecretName, minInst.Name)
if err != nil {
return err
}
newMinIOExternalCertSecret = append(newMinIOExternalCertSecret, externalCertSecrets...)
// Create new CAs Certificate Secrets for MinIO
var caCertificates []tenantSecret
for i, caCertificate := range params.Body.CustomCertificates.MinioCAs {
certificateContent, err := base64.StdEncoding.DecodeString(caCertificate)
if err != nil {
return err
}
caCertificates = append(caCertificates, tenantSecret{
Name: fmt.Sprintf("%s-ca-certificate-%d", secretName, i),
Content: map[string][]byte{
"public.crt": certificateContent,
},
})
}
if len(caCertificates) > 0 {
certificateSecrets, err := createOrReplaceSecrets(ctx, client, minInst.Namespace, caCertificates, minInst.Name)
if err != nil {
return err
}
newMinIOExternalCaCertSecret = append(newMinIOExternalCaCertSecret, certificateSecrets...)
}
// Create new Certificate Secrets for Console
consoleExternalCertSecretName := fmt.Sprintf("%s-console-external-certificates", secretName)
consoleExternalCertSecrets, err := createOrReplaceExternalCertSecrets(ctx, client, minInst.Namespace, params.Body.CustomCertificates.Console, consoleExternalCertSecretName, minInst.Name)
if err != nil {
return err
}
if len(consoleExternalCertSecrets) > 0 {
newConsoleExternalCertSecret = consoleExternalCertSecrets[0]
}
// Create new CAs Certificate Secrets for Console
var consoleCaCertificates []tenantSecret
for i, caCertificate := range params.Body.CustomCertificates.ConsoleCAs {
certificateContent, err := base64.StdEncoding.DecodeString(caCertificate)
if err != nil {
return err
}
consoleCaCertificates = append(consoleCaCertificates, tenantSecret{
Name: fmt.Sprintf("%s-console-ca-certificate-%d", secretName, i),
Content: map[string][]byte{
"public.crt": certificateContent,
},
})
}
if len(consoleCaCertificates) > 0 {
certificateSecrets, err := createOrReplaceSecrets(ctx, client, minInst.Namespace, consoleCaCertificates, minInst.Name)
if err != nil {
return err
}
newConsoleExternalCaCertSecret = append(newConsoleExternalCaCertSecret, certificateSecrets...)
}
// Update External Certificates
minInst.Spec.ExternalCertSecret = newMinIOExternalCertSecret
minInst.Spec.ExternalCaCertSecret = newMinIOExternalCaCertSecret
if minInst.HasConsoleEnabled() {
minInst.Spec.Console.ExternalCertSecret = newConsoleExternalCertSecret
minInst.Spec.Console.ExternalCaCertSecret = newConsoleExternalCaCertSecret
}
_, err = operatorClient.TenantUpdate(ctx, minInst, metav1.UpdateOptions{})
if err != nil {
return err
}
// Remove Certificate Secrets from Tenant namespace
for _, secretName := range params.Body.CustomCertificates.SecretsToBeDeleted {
err = client.deleteSecret(ctx, minInst.Namespace, secretName, metav1.DeleteOptions{})
if err != nil {
restapi.LogError("error deleting secret: %v", err)
}
}
return nil
}
func listTenants(ctx context.Context, operatorClient OperatorClientI, namespace string, limit *int32) (*models.ListTenantsResponse, error) {
listOpts := metav1.ListOptions{
Limit: 10,
}
if limit != nil {
listOpts.Limit = int64(*limit)
}
2020-07-25 14:38:16 -07:00
minTenants, err := operatorClient.TenantList(ctx, namespace, listOpts)
if err != nil {
return nil, err
}
var tenants []*models.TenantList
2020-07-27 18:03:47 -07:00
for _, tenant := range minTenants.Items {
var totalSize int64
var instanceCount int64
var volumeCount int64
for _, pool := range tenant.Spec.Pools {
instanceCount = instanceCount + int64(pool.Servers)
volumeCount = volumeCount + int64(pool.Servers*pool.VolumesPerServer)
if pool.VolumeClaimTemplate != nil {
poolSize := int64(pool.VolumesPerServer) * int64(pool.Servers) * pool.VolumeClaimTemplate.Spec.Resources.Requests.Storage().Value()
totalSize = totalSize + poolSize
2020-07-27 18:03:47 -07:00
}
}
var deletion string
if tenant.ObjectMeta.DeletionTimestamp != nil {
deletion = tenant.ObjectMeta.DeletionTimestamp.Format(time.RFC3339)
}
tenants = append(tenants, &models.TenantList{
CreationDate: tenant.ObjectMeta.CreationTimestamp.Format(time.RFC3339),
DeletionDate: deletion,
Name: tenant.ObjectMeta.Name,
PoolCount: int64(len(tenant.Spec.Pools)),
InstanceCount: instanceCount,
VolumeCount: volumeCount,
CurrentState: tenant.Status.CurrentState,
Namespace: tenant.ObjectMeta.Namespace,
2020-07-27 18:03:47 -07:00
TotalSize: totalSize,
HealthStatus: string(tenant.Status.HealthStatus),
})
}
return &models.ListTenantsResponse{
Tenants: tenants,
Total: int64(len(tenants)),
}, nil
}
func getListAllTenantsResponse(session *models.Principal, params operator_api.ListAllTenantsParams) (*models.ListTenantsResponse, *models.Error) {
ctx := context.Background()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
listT, err := listTenants(ctx, opClient, "", params.Limit)
if err != nil {
return nil, prepareError(err)
}
return listT, nil
}
// getListTenantsResponse list tenants by namespace
func getListTenantsResponse(session *models.Principal, params operator_api.ListTenantsParams) (*models.ListTenantsResponse, *models.Error) {
ctx := context.Background()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
listT, err := listTenants(ctx, opClient, params.Namespace, params.Limit)
if err != nil {
return nil, prepareError(err)
}
return listT, nil
}
func getTenantCreatedResponse(session *models.Principal, params operator_api.CreateTenantParams) (response *models.CreateTenantResponse, mError *models.Error) {
tenantReq := params.Body
minioImage := tenantReq.Image
ctx := context.Background()
consoleHasTLS := false
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
if minioImage == "" {
minImg, err := cluster.GetMinioImage()
2020-08-11 22:29:33 -07:00
// we can live without figuring out the latest version of MinIO, Operator will use a hardcoded value
if err == nil {
minioImage = *minImg
}
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
k8sClient := k8sClient{
client: clientSet,
}
if err != nil {
return nil, prepareError(err)
}
ns := *tenantReq.Namespace
// if access/secret are provided, use them, else create a random pair
accessKey := restapi.RandomCharString(16)
secretKey := restapi.RandomCharString(32)
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
if tenantReq.AccessKey != "" {
accessKey = tenantReq.AccessKey
}
if tenantReq.SecretKey != "" {
secretKey = tenantReq.SecretKey
}
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
tenantName := *tenantReq.Name
imm := true
var instanceSecret corev1.Secret
var users []*corev1.LocalObjectReference
// Create the secret for the root credentials
secretName := fmt.Sprintf("%s-secret", tenantName)
instanceSecret = corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Labels: map[string]string{
miniov2.TenantLabel: tenantName,
},
},
Immutable: &imm,
Data: map[string][]byte{
"accesskey": []byte(accessKey),
"secretkey": []byte(secretKey),
},
}
_, err = clientSet.CoreV1().Secrets(ns).Create(ctx, &instanceSecret, metav1.CreateOptions{})
if err != nil {
return nil, prepareError(err)
}
// delete secrets created if an error occurred during tenant creation,
defer func() {
if mError != nil {
restapi.LogError("deleting secrets created for failed tenant: %s if any: %v", tenantName, mError)
opts := metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", miniov2.TenantLabel, tenantName),
}
err = clientSet.CoreV1().Secrets(ns).DeleteCollection(ctx, metav1.DeleteOptions{}, opts)
if err != nil {
restapi.LogError("error deleting tenant's secrets: %v", err)
}
}
}()
var environmentVariables []corev1.EnvVar
2020-08-04 22:32:41 -07:00
// Check the Erasure Coding Parity for validity and pass it to Tenant
if tenantReq.ErasureCodingParity > 0 {
if tenantReq.ErasureCodingParity < 2 || tenantReq.ErasureCodingParity > 8 {
return nil, prepareError(errorInvalidErasureCodingValue)
2020-08-04 22:32:41 -07:00
}
environmentVariables = append(environmentVariables, corev1.EnvVar{
2020-08-04 22:32:41 -07:00
Name: "MINIO_STORAGE_CLASS_STANDARD",
2020-08-07 12:28:46 -07:00
Value: fmt.Sprintf("EC:%d", tenantReq.ErasureCodingParity),
2020-08-04 22:32:41 -07:00
})
}
//Construct a MinIO Instance with everything we are getting from parameters
minInst := miniov2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: tenantName,
Labels: tenantReq.Labels,
},
Spec: miniov2.TenantSpec{
Image: minioImage,
Mountpath: "/export",
CredsSecret: &corev1.LocalObjectReference{
Name: secretName,
},
Env: environmentVariables,
},
}
idpEnabled := false
// Enable IDP (Active Directory) for MinIO
if tenantReq.Idp != nil && tenantReq.Idp.ActiveDirectory != nil {
url := *tenantReq.Idp.ActiveDirectory.URL
userNameFormat := *tenantReq.Idp.ActiveDirectory.UsernameFormat
userSearchFilter := *tenantReq.Idp.ActiveDirectory.UserSearchFilter
tlsSkipVerify := tenantReq.Idp.ActiveDirectory.SkipTLSVerification
serverInsecure := tenantReq.Idp.ActiveDirectory.ServerInsecure
groupSearchDN := tenantReq.Idp.ActiveDirectory.GroupSearchBaseDn
groupSearchFilter := tenantReq.Idp.ActiveDirectory.GroupSearchFilter
groupNameAttribute := tenantReq.Idp.ActiveDirectory.GroupNameAttribute
if url != "" && userNameFormat != "" && userSearchFilter != "" {
// CONSOLE_LDAP_ENABLED
idpEnabled = true
minInst.Spec.Env = append(minInst.Spec.Env, corev1.EnvVar{
Name: "MINIO_IDENTITY_LDAP_SERVER_ADDR",
Value: userNameFormat,
}, corev1.EnvVar{
Name: "MINIO_IDENTITY_LDAP_USERNAME_FORMAT",
Value: userNameFormat,
}, corev1.EnvVar{
Name: "MINIO_IDENTITY_LDAP_USERNAME_SEARCH_FILTER",
Value: userSearchFilter,
}, corev1.EnvVar{
Name: "MINIO_IDENTITY_LDAP_USERNAME_SEARCH_FILTER",
Value: userSearchFilter,
}, corev1.EnvVar{
Name: "MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN",
Value: groupSearchDN,
}, corev1.EnvVar{
Name: "MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER",
Value: groupSearchFilter,
}, corev1.EnvVar{
Name: "MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE",
Value: groupNameAttribute,
})
if tlsSkipVerify {
minInst.Spec.Env = append(minInst.Spec.Env, corev1.EnvVar{
Name: "MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY",
Value: "on",
})
}
if serverInsecure {
minInst.Spec.Env = append(minInst.Spec.Env, corev1.EnvVar{
Name: "MINIO_IDENTITY_LDAP_SERVER_INSECURE",
Value: "on",
})
}
}
}
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
// Create the secret any built-in user passed if no external IDP was configured
if tenantReq.Idp != nil && len(tenantReq.Idp.Keys) > 0 && tenantReq.Idp.ActiveDirectory == nil && tenantReq.Idp.Oidc == nil {
for i := 0; i < len(tenantReq.Idp.Keys); i++ {
userSecretName := fmt.Sprintf("%s-user-%d", tenantName, i)
users = append(users, &corev1.LocalObjectReference{Name: userSecretName})
userSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: userSecretName,
Labels: map[string]string{
miniov2.TenantLabel: tenantName,
},
},
Immutable: &imm,
Data: map[string][]byte{
"CONSOLE_ACCESS_KEY": []byte(*tenantReq.Idp.Keys[i].AccessKey),
"CONSOLE_SECRET_KEY": []byte(*tenantReq.Idp.Keys[i].SecretKey),
},
}
_, err := clientSet.CoreV1().Secrets(ns).Create(ctx, &userSecret, metav1.CreateOptions{})
if err != nil {
return nil, prepareError(err)
}
}
// attach the users to the tenant
minInst.Spec.Users = users
}
isEncryptionEnabled := false
if tenantReq.EnableTLS != nil {
// if enableTLS is defined in the create tenant request we assign the value
// to the RequestAutoCert attribute in the tenant spec
minInst.Spec.RequestAutoCert = tenantReq.EnableTLS
if *tenantReq.EnableTLS {
// requestAutoCert is enabled, MinIO will be deployed with TLS enabled and encryption can be enabled
isEncryptionEnabled = true
consoleHasTLS = true
}
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
}
// External TLS certificates for MinIO
if tenantReq.TLS != nil && len(tenantReq.TLS.Minio) > 0 {
isEncryptionEnabled = true
// Certificates used by the MinIO instance
externalCertSecretName := fmt.Sprintf("%s-instance-external-certificates", secretName)
externalCertSecret, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, tenantReq.TLS.Minio, externalCertSecretName, tenantName)
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
if err != nil {
return nil, prepareError(err)
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
}
minInst.Spec.ExternalCertSecret = externalCertSecret
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
}
// If encryption configuration is present and TLS will be enabled (using AutoCert or External certificates)
if tenantReq.Encryption != nil && isEncryptionEnabled {
// KES client mTLSCertificates used by MinIO instance
if tenantReq.Encryption.Client != nil {
tenantExternalClientCertSecretName := fmt.Sprintf("%s-tenant-external-client-cert", secretName)
certificates := []*models.KeyPairConfiguration{tenantReq.Encryption.Client}
certificateSecrets, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, certificates, tenantExternalClientCertSecretName, tenantName)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric)
}
if len(certificateSecrets) > 0 {
minInst.Spec.ExternalClientCertSecret = certificateSecrets[0]
}
}
// KES configuration for Tenant instance
minInst.Spec.KES, err = getKESConfiguration(ctx, &k8sClient, ns, tenantReq.Encryption, secretName, tenantName)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric)
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
}
// Set Labels, Annotations and Node Selector for KES
minInst.Spec.KES.Labels = tenantReq.Encryption.Labels
minInst.Spec.KES.Annotations = tenantReq.Encryption.Annotations
minInst.Spec.KES.NodeSelector = tenantReq.Encryption.NodeSelector
}
// External TLS CA certificates for MinIO
if tenantReq.TLS != nil && len(tenantReq.TLS.CaCertificates) > 0 {
var caCertificates []tenantSecret
for i, caCertificate := range tenantReq.TLS.CaCertificates {
certificateContent, err := base64.StdEncoding.DecodeString(caCertificate)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric, nil, err)
}
caCertificates = append(caCertificates, tenantSecret{
Name: fmt.Sprintf("ca-certificate-%d", i),
Content: map[string][]byte{
"public.crt": certificateContent,
},
})
}
if len(caCertificates) > 0 {
certificateSecrets, err := createOrReplaceSecrets(ctx, &k8sClient, ns, caCertificates, tenantName)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric, nil, err)
}
minInst.Spec.ExternalCaCertSecret = certificateSecrets
}
}
// optionals are set below
var tenantUserAccessKey string
var tenantUserSecretKey string
keyElementEmpty := len(tenantReq.Idp.Keys) == 1 && (*tenantReq.Idp.Keys[0].AccessKey == "" && *tenantReq.Idp.Keys[0].SecretKey == "")
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
2020-08-09 17:19:39 -07:00
enableConsole := true
if tenantReq.EnableConsole != nil && *tenantReq.EnableConsole {
enableConsole = *tenantReq.EnableConsole
}
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
if enableConsole {
consoleSelector := fmt.Sprintf("%s-console", tenantName)
consoleSecretName := fmt.Sprintf("%s-secret", consoleSelector)
consoleSecretData := map[string][]byte{
"CONSOLE_PBKDF_PASSPHRASE": []byte(restapi.RandomCharString(16)),
"CONSOLE_PBKDF_SALT": []byte(restapi.RandomCharString(8)),
}
// If Subnet License is present in k8s secrets, copy that to the CONSOLE_SUBNET_LICENSE env variable
// of the console tenant
license, _ := getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName)
if license != "" {
consoleSecretData[restapi.ConsoleSubnetLicense] = []byte(license)
}
imm := true
instanceSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: consoleSecretName,
Labels: map[string]string{
miniov2.TenantLabel: tenantName,
},
},
Immutable: &imm,
Data: consoleSecretData,
}
minInst.Spec.Console = &miniov2.ConsoleConfiguration{
Replicas: 1,
Image: getConsoleImage(),
ConsoleSecret: &corev1.LocalObjectReference{Name: consoleSecretName},
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("64Mi"),
},
},
}
if tenantReq.TLS != nil && tenantReq.TLS.Console != nil {
consoleHasTLS = true
// Certificates used by the console instance
externalCertSecretName := fmt.Sprintf("%s-console-external-certificates", secretName)
certificates := []*models.KeyPairConfiguration{tenantReq.TLS.Console}
externalCertSecret, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, certificates, externalCertSecretName, tenantName)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric)
}
if len(externalCertSecret) > 0 {
minInst.Spec.Console.ExternalCertSecret = externalCertSecret[0]
}
}
// If IDP is not already enabled via LDAP (Active Directory) and OIDC configuration is present then
// enable oidc for console
if !idpEnabled && tenantReq.Idp != nil && tenantReq.Idp.Oidc != nil {
url := *tenantReq.Idp.Oidc.URL
clientID := *tenantReq.Idp.Oidc.ClientID
secretID := *tenantReq.Idp.Oidc.SecretID
if url != "" && clientID != "" && secretID != "" {
instanceSecret.Data["CONSOLE_IDP_URL"] = []byte(url)
instanceSecret.Data["CONSOLE_IDP_CLIENT_ID"] = []byte(clientID)
instanceSecret.Data["CONSOLE_IDP_SECRET"] = []byte(secretID)
consoleScheme := "http"
consolePort := 9090
// If Console will be deployed with TLS enabled (using AutoCert or External certificates)
if consoleHasTLS {
consoleScheme = "https"
consolePort = 9443
}
// https://[HOSTNAME]:9443 will be replaced by javascript in the browser to use the actual hostname
// assigned to Console, eg: https://localhost:9443
instanceSecret.Data["CONSOLE_IDP_CALLBACK"] = []byte(fmt.Sprintf("%s://[HOSTNAME]:%d/oauth_callback", consoleScheme, consolePort))
}
}
_, err = clientSet.CoreV1().Secrets(ns).Create(ctx, &instanceSecret, metav1.CreateOptions{})
if err != nil {
return nil, prepareError(restapi.ErrorGeneric)
}
// Set Labels, Annotations and Node Selector for Console
if tenantReq.Console != nil {
minInst.Spec.Console.Annotations = tenantReq.Console.Annotations
minInst.Spec.Console.Labels = tenantReq.Console.Labels
minInst.Spec.Console.NodeSelector = tenantReq.Console.NodeSelector
}
// External TLS CA certificates for Console
if tenantReq.TLS != nil && len(tenantReq.TLS.ConsoleCaCertificates) > 0 {
var caCertificates []tenantSecret
for i, caCertificate := range tenantReq.TLS.ConsoleCaCertificates {
certificateContent, err := base64.StdEncoding.DecodeString(caCertificate)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric, nil, err)
}
caCertificates = append(caCertificates, tenantSecret{
Name: fmt.Sprintf("console-ca-certificate-%d", i),
Content: map[string][]byte{
"public.crt": certificateContent,
},
})
}
if len(caCertificates) > 0 {
certificateSecrets, err := createOrReplaceSecrets(ctx, &k8sClient, ns, caCertificates, tenantName)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric, nil, err)
}
minInst.Spec.Console.ExternalCaCertSecret = certificateSecrets
}
}
}
2020-08-05 12:35:41 -07:00
// add annotations
var annotations map[string]string
if len(tenantReq.Annotations) > 0 {
2020-08-05 12:35:41 -07:00
annotations = tenantReq.Annotations
minInst.Annotations = annotations
2020-08-05 12:35:41 -07:00
}
// set the pools if they are provided
for _, pool := range tenantReq.Pools {
pool, err := parseTenantPoolRequest(pool)
if err != nil {
restapi.LogError("parseTenantPoolRequest failed: %v", err)
return nil, prepareError(err)
}
minInst.Spec.Pools = append(minInst.Spec.Pools, *pool)
}
// Set Mount Path if provided
if tenantReq.MounthPath != "" {
minInst.Spec.Mountpath = tenantReq.MounthPath
}
// 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, tenantReq.ImageRegistry, clientSet.CoreV1(), ns, tenantName); err != nil {
return nil, prepareError(err)
}
// pass the image pull secret to the Tenant
if imagePullSecret != "" {
minInst.Spec.ImagePullSecret = corev1.LocalObjectReference{
Name: imagePullSecret,
}
}
// prometheus annotations support
if tenantReq.EnablePrometheus != nil && *tenantReq.EnablePrometheus && minInst.Annotations != nil {
minInst.Annotations[prometheusPath] = "/minio/prometheus/metrics"
minInst.Annotations[prometheusPort] = fmt.Sprint(miniov2.MinIOPort)
minInst.Annotations[prometheusScrape] = "true"
}
// set console image if provided
if tenantReq.ConsoleImage != "" {
minInst.Spec.Console.Image = tenantReq.ConsoleImage
}
//Default class name for Log search
diskSpaceFromAPI := int64(5) * humanize.GiByte // Default is 5Gi
logSearchStorageClass := "" // Default is ""
logSearchImage := ""
logSearchPgImage := ""
if tenantReq.LogSearchConfiguration != nil {
if tenantReq.LogSearchConfiguration.StorageSize != nil {
diskSpaceFromAPI = int64(*tenantReq.LogSearchConfiguration.StorageSize) * humanize.GiByte
}
if tenantReq.LogSearchConfiguration.StorageClass != "" {
logSearchStorageClass = tenantReq.LogSearchConfiguration.StorageClass
}
if tenantReq.LogSearchConfiguration.StorageClass == "" && len(tenantReq.Pools) > 0 {
logSearchStorageClass = tenantReq.Pools[0].VolumeConfiguration.StorageClassName
}
if tenantReq.LogSearchConfiguration.Image != "" {
logSearchImage = tenantReq.LogSearchConfiguration.Image
}
if tenantReq.LogSearchConfiguration.PostgresImage != "" {
logSearchPgImage = tenantReq.LogSearchConfiguration.PostgresImage
}
}
logSearchDiskSpace := resource.NewQuantity(diskSpaceFromAPI, resource.DecimalExponent)
// the audit max cap cannot be larger than disk size on the DB, else it won't trim the data
auditMaxCap := 10
if (diskSpaceFromAPI / humanize.GiByte) < int64(auditMaxCap) {
auditMaxCap = int(diskSpaceFromAPI / humanize.GiByte)
}
// default activate lgo search and prometheus
minInst.Spec.Log = &miniov2.LogConfig{
Audit: &miniov2.AuditConfig{DiskCapacityGB: swag.Int(auditMaxCap)},
Db: &miniov2.LogDbConfig{
VolumeClaimTemplate: &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: tenantName + "-log",
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: *logSearchDiskSpace,
},
},
StorageClassName: &logSearchStorageClass,
},
},
},
}
if logSearchImage != "" {
minInst.Spec.Log.Image = logSearchImage
}
if logSearchPgImage != "" {
minInst.Spec.Log.Db.Image = logSearchPgImage
}
prometheusDiskSpace := 5 // Default is 5 by API
prometheusStorageClass := "" // Default is ""
prometheusImage := "" // Default is ""
if tenantReq.PrometheusConfiguration != nil {
if tenantReq.PrometheusConfiguration.StorageSize != nil {
prometheusDiskSpace = int(*tenantReq.PrometheusConfiguration.StorageSize)
}
if tenantReq.PrometheusConfiguration.StorageClass != "" {
prometheusStorageClass = tenantReq.PrometheusConfiguration.StorageClass
}
// Default class name for prometheus
if tenantReq.PrometheusConfiguration.StorageClass == "" && len(tenantReq.Pools) > 0 {
prometheusStorageClass = tenantReq.Pools[0].VolumeConfiguration.StorageClassName
}
if tenantReq.PrometheusConfiguration.Image != "" {
prometheusImage = tenantReq.PrometheusConfiguration.Image
}
}
minInst.Spec.Prometheus = &miniov2.PrometheusConfig{
DiskCapacityDB: swag.Int(prometheusDiskSpace),
StorageClassName: &prometheusStorageClass,
}
if prometheusImage != "" {
minInst.Spec.Prometheus.Image = prometheusImage
}
// expose services
if tenantReq.ExposeMinio || tenantReq.ExposeConsole {
minInst.Spec.ExposeServices = &miniov2.ExposeServices{
MinIO: tenantReq.ExposeMinio,
Console: tenantReq.ExposeConsole,
}
}
opClient, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
_, err = opClient.MinioV2().Tenants(ns).Create(context.Background(), &minInst, metav1.CreateOptions{})
if err != nil {
restapi.LogError("Creating new tenant failed with: %v", err)
return nil, prepareError(err)
}
// Integrations
if os.Getenv("GKE_INTEGRATION") != "" {
err := gkeIntegration(clientSet, tenantName, ns, session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
}
response = &models.CreateTenantResponse{}
// Attach Console Credentials
if enableConsole {
var itemsToReturn []*models.TenantResponseItem
if len(tenantReq.Idp.Keys) == 0 || keyElementEmpty {
itemsToReturn = append(itemsToReturn, &models.TenantResponseItem{AccessKey: tenantUserAccessKey, SecretKey: tenantUserSecretKey})
} else { // IDP Keys
for _, item := range tenantReq.Idp.Keys {
itemsToReturn = append(itemsToReturn, &models.TenantResponseItem{AccessKey: *item.AccessKey, SecretKey: *item.SecretKey})
}
}
response.Console = itemsToReturn
}
return response, nil
}
// 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, req *models.ImageRegistry, clientset v1.CoreV1Interface, namespace, tenantName string) (string, error) {
if req == nil || req.Registry == nil || req.Username == nil || req.Password == nil {
return "", nil
}
credentials := make(map[string]imageRegistryCredentials)
// username:password encoded
authData := []byte(fmt.Sprintf("%s:%s", *req.Username, *req.Password))
authStr := base64.StdEncoding.EncodeToString(authData)
credentials[*req.Registry] = imageRegistryCredentials{
Username: *req.Username,
Password: *req.Password,
Auth: authStr,
}
imRegistry := imageRegistry{
Auths: credentials,
}
imRegistryJSON, err := json.Marshal(imRegistry)
if err != nil {
return "", err
}
pullSecretName := fmt.Sprintf("%s-regcred", tenantName)
secretCredentials := map[string][]byte{
corev1.DockerConfigJsonKey: []byte(string(imRegistryJSON)),
}
// Get or Create secret if it doesn't exist
currentSecret, err := clientset.Secrets(namespace).Get(ctx, pullSecretName, metav1.GetOptions{})
if err != nil {
if k8sErrors.IsNotFound(err) {
instanceSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: pullSecretName,
Labels: map[string]string{
miniov2.TenantLabel: tenantName,
},
},
Data: secretCredentials,
Type: corev1.SecretTypeDockerConfigJson,
}
_, err = clientset.Secrets(namespace).Create(ctx, &instanceSecret, metav1.CreateOptions{})
if err != nil {
return "", err
}
return pullSecretName, nil
}
return "", err
}
currentSecret.Data = secretCredentials
_, err = clientset.Secrets(namespace).Update(ctx, currentSecret, metav1.UpdateOptions{})
if err != nil {
return "", err
}
return pullSecretName, nil
}
2020-07-25 14:38:16 -07:00
// updateTenantAction does an update on the minioTenant by patching the desired changes
func updateTenantAction(ctx context.Context, operatorClient OperatorClientI, clientset v1.CoreV1Interface, httpCl cluster.HTTPClientI, namespace string, params operator_api.UpdateTenantParams) error {
imageToUpdate := params.Body.Image
imageRegistryReq := params.Body.ImageRegistry
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, imageRegistryReq, clientset, namespace, params.Tenant); err != nil {
restapi.LogError("error setting image registry secret: %v", 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 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
}
}
// Prometheus Annotations
currentAnnotations := minInst.Annotations
prometheusAnnotations := map[string]string{
prometheusPath: "/minio/prometheus/metrics",
prometheusPort: fmt.Sprint(miniov2.MinIOPort),
prometheusScrape: "true",
}
2020-09-21 21:31:30 -07:00
if params.Body.EnablePrometheus && currentAnnotations != nil {
// add prometheus annotations to the tenant
minInst.Annotations = addAnnotations(currentAnnotations, prometheusAnnotations)
// add prometheus annotations to the each pool
if minInst.Spec.Pools != nil {
for _, pool := range minInst.Spec.Pools {
poolAnnotations := pool.VolumeClaimTemplate.GetObjectMeta().GetAnnotations()
pool.VolumeClaimTemplate.GetObjectMeta().SetAnnotations(addAnnotations(poolAnnotations, prometheusAnnotations))
}
}
} else {
// remove prometheus annotations to the tenant
minInst.Annotations = removeAnnotations(currentAnnotations, prometheusAnnotations)
// add prometheus annotations from each pool
if minInst.Spec.Pools != nil {
for _, pool := range minInst.Spec.Pools {
poolAnnotations := pool.VolumeClaimTemplate.GetObjectMeta().GetAnnotations()
pool.VolumeClaimTemplate.GetObjectMeta().SetAnnotations(removeAnnotations(poolAnnotations, prometheusAnnotations))
}
}
}
payloadBytes, err := json.Marshal(minInst)
if err != nil {
return err
}
_, err = operatorClient.TenantPatch(ctx, namespace, minInst.Name, types.MergePatchType, payloadBytes, metav1.PatchOptions{})
if err != nil {
return err
}
return nil
}
// addAnnotations will merge two annotation maps
func addAnnotations(annotationsOne, annotationsTwo map[string]string) map[string]string {
if annotationsOne == nil {
annotationsOne = map[string]string{}
}
for key, value := range annotationsTwo {
annotationsOne[key] = value
}
return annotationsOne
}
// removeAnnotations will remove keys from the first annotations map based on the second one
func removeAnnotations(annotationsOne, annotationsTwo map[string]string) map[string]string {
if annotationsOne == nil {
annotationsOne = map[string]string{}
}
for key := range annotationsTwo {
delete(annotationsOne, key)
}
return annotationsOne
}
func getUpdateTenantResponse(session *models.Principal, params operator_api.UpdateTenantParams) *models.Error {
ctx := context.Background()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
httpC := &cluster.HTTPClient{
Client: &http.Client{
Timeout: 4 * time.Second,
},
}
if err := updateTenantAction(ctx, opClient, clientSet.CoreV1(), httpC, params.Namespace, params); err != nil {
return prepareError(err, errors.New("unable to update tenant"))
}
2020-07-13 20:36:27 -07:00
return nil
}
// addTenantPool creates a pool to a defined tenant
func addTenantPool(ctx context.Context, operatorClient OperatorClientI, params operator_api.TenantAddPoolParams) error {
tenant, err := operatorClient.TenantGet(ctx, params.Namespace, params.Tenant, metav1.GetOptions{})
2020-07-13 20:36:27 -07:00
if err != nil {
return err
}
poolParams := params.Body
pool, err := parseTenantPoolRequest(poolParams)
2020-07-27 14:06:12 -07:00
if err != nil {
return err
}
tenant.Spec.Pools = append(tenant.Spec.Pools, *pool)
payloadBytes, err := json.Marshal(tenant)
2020-07-13 20:36:27 -07:00
if err != nil {
return err
}
_, err = operatorClient.TenantPatch(ctx, params.Namespace, tenant.Name, types.MergePatchType, payloadBytes, metav1.PatchOptions{})
2020-07-13 20:36:27 -07:00
if err != nil {
return err
}
return nil
}
func getTenantAddPoolResponse(session *models.Principal, params operator_api.TenantAddPoolParams) *models.Error {
2020-07-13 20:36:27 -07:00
ctx := context.Background()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
2020-07-13 20:36:27 -07:00
if err != nil {
return prepareError(err)
2020-07-13 20:36:27 -07:00
}
opClient := &operatorClient{
client: opClientClientSet,
}
if err := addTenantPool(ctx, opClient, params); err != nil {
return prepareError(err, errors.New("unable to add pool"))
2020-07-13 20:36:27 -07:00
}
return nil
}
2020-07-27 14:19:40 -07:00
// getTenantUsageResponse returns the usage of a tenant
func getTenantUsageResponse(session *models.Principal, params operator_api.GetTenantUsageParams) (*models.TenantUsage, *models.Error) {
2020-07-27 14:19:40 -07:00
// 5 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
2020-07-27 14:19:40 -07:00
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantUsage)
2020-07-27 14:19:40 -07:00
}
clientSet, err := cluster.K8sClient(session.STSSessionToken)
2020-07-27 14:19:40 -07:00
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantUsage)
2020-07-27 14:19:40 -07:00
}
opClient := &operatorClient{
client: opClientClientSet,
}
k8sClient := &k8sClient{
client: clientSet,
2020-07-27 14:19:40 -07:00
}
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantUsage)
2020-07-27 14:19:40 -07:00
}
minTenant.EnsureDefaults()
2020-07-27 14:19:40 -07:00
2020-09-21 21:31:30 -07:00
svcURL := GetTenantServiceURL(minTenant)
// getTenantAdminClient will use all certificates under ~/.console/certs/CAs to trust the TLS connections with MinIO tenants
2020-07-27 14:19:40 -07:00
mAdmin, err := getTenantAdminClient(
ctx,
k8sClient,
minTenant,
2020-09-21 21:31:30 -07:00
svcURL,
)
2020-07-27 14:19:40 -07:00
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantUsage)
2020-07-27 14:19:40 -07:00
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := restapi.AdminClient{Client: mAdmin}
2020-07-27 14:19:40 -07:00
// serialize output
adminInfo, err := restapi.GetAdminInfo(ctx, adminClient)
2020-07-27 14:19:40 -07:00
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantUsage)
2020-07-27 14:19:40 -07:00
}
info := &models.TenantUsage{Used: adminInfo.Usage, DiskUsed: adminInfo.DisksUsage}
2020-07-27 14:19:40 -07:00
return info, nil
}
func getTenantPodsResponse(session *models.Principal, params operator_api.GetTenantPodsParams) ([]*models.TenantPod, *models.Error) {
ctx := context.Background()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
listOpts := metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", miniov2.TenantLabel, params.Tenant),
}
pods, err := clientset.CoreV1().Pods(params.Namespace).List(ctx, listOpts)
if err != nil {
return nil, prepareError(err)
}
retval := []*models.TenantPod{}
for _, pod := range pods.Items {
var restarts int64
if len(pod.Status.ContainerStatuses) > 0 {
restarts = int64(pod.Status.ContainerStatuses[0].RestartCount)
}
retval = append(retval, &models.TenantPod{
Name: swag.String(pod.Name),
Status: string(pod.Status.Phase),
TimeCreated: pod.CreationTimestamp.Unix(),
PodIP: pod.Status.PodIP,
Restarts: restarts,
Node: pod.Spec.NodeName})
}
return retval, nil
}
func getPodLogsResponse(session *models.Principal, params operator_api.GetPodLogsParams) (string, *models.Error) {
ctx := context.Background()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return "", prepareError(err)
}
listOpts := &corev1.PodLogOptions{}
logs := clientset.CoreV1().Pods(params.Namespace).GetLogs(params.PodName, listOpts)
buff, err := logs.DoRaw(ctx)
if err != nil {
return "", prepareError(err)
}
return string(buff), nil
}
func getPodEventsResponse(session *models.Principal, params operator_api.GetPodEventsParams) (models.EventListWrapper, *models.Error) {
ctx := context.Background()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
pod, err := clientset.CoreV1().Pods(params.Namespace).Get(ctx, params.PodName, metav1.GetOptions{})
if err != nil {
return nil, prepareError(err)
}
events, err := clientset.CoreV1().Events(params.Namespace).List(ctx, metav1.ListOptions{FieldSelector: fmt.Sprintf("involvedObject.uid=%s", pod.UID)})
if err != nil {
return nil, prepareError(err)
}
retval := models.EventListWrapper{}
for i := 0; i < len(events.Items); i++ {
retval = append(retval, &models.EventListElement{
Namespace: events.Items[i].Namespace,
LastSeen: events.Items[i].LastTimestamp.Unix(),
Message: events.Items[i].Message,
EventType: events.Items[i].Type,
Reason: events.Items[i].Reason,
})
}
sort.SliceStable(retval, func(i int, j int) bool {
return retval[i].LastSeen < retval[j].LastSeen
})
return retval, nil
}
// parseTenantPoolRequest parse pool request and returns the equivalent
// miniov2.Pool object
func parseTenantPoolRequest(poolParams *models.Pool) (*miniov2.Pool, error) {
if poolParams.VolumeConfiguration == nil {
return nil, errors.New("a volume configuration must be specified")
}
if poolParams.VolumeConfiguration.Size == nil || *poolParams.VolumeConfiguration.Size <= int64(0) {
return nil, errors.New("volume size must be greater than 0")
}
if poolParams.Servers == nil || *poolParams.Servers <= 0 {
return nil, errors.New("number of servers must be greater than 0")
}
if poolParams.VolumesPerServer == nil || *poolParams.VolumesPerServer <= 0 {
return nil, errors.New("number of volumes per server must be greater than 0")
}
volumeSize := resource.NewQuantity(*poolParams.VolumeConfiguration.Size, resource.DecimalExponent)
volTemp := corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: *volumeSize,
},
},
}
if poolParams.VolumeConfiguration.StorageClassName != "" {
volTemp.StorageClassName = &poolParams.VolumeConfiguration.StorageClassName
}
// parse resources' requests
TLS with user provided certificates and KES support for MinIO (#213) This PR adds the following features: - Allow user to provide its own keypair certificates for enable TLS in MinIO - Allow user to configure data encryption at rest in MinIO with KES - Removes JWT schema for login and instead Console authentication will use encrypted session tokens Enable TLS between client and MinIO with user provided certificates Instead of using AutoCert feature now the user can provide `cert` and `key` via `tls` object, values must be valid `x509.Certificate` formatted files encoded in `base64` Enable encryption at rest configuring KES User can deploy KES via Console/Operator by defining the encryption object, AutoCert must be enabled or custom certificates for KES must be provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`, previous configuration of the KMS is necessary. eg of body request for create-tenant ``` { "name": "honeywell", "access_key": "minio", "secret_key": "minio123", "enable_mcs": false, "enable_ssl": false, "service_name": "honeywell", "zones": [ { "name": "honeywell-zone-1", "servers": 1, "volumes_per_server": 4, "volume_configuration": { "size": 256000000, "storage_class": "vsan-default-storage-policy" } } ], "namespace": "default", "tls": { "tls.crt": "", "tls.key": "" }, "encryption": { "server": { "tls.crt": "", "tls.key": "" }, "client": { "tls.crt": "", "tls.key": "" }, "vault": { "endpoint": "http://vault:8200", "prefix": "", "approle": { "id": "", "secret": "" } } } } ```
2020-07-30 17:49:56 -07:00
resourcesRequests := make(corev1.ResourceList)
resourcesLimits := make(corev1.ResourceList)
if poolParams.Resources != nil {
for key, val := range poolParams.Resources.Requests {
resourcesRequests[corev1.ResourceName(key)] = *resource.NewQuantity(val, resource.BinarySI)
}
for key, val := range poolParams.Resources.Limits {
resourcesLimits[corev1.ResourceName(key)] = *resource.NewQuantity(val, resource.BinarySI)
}
}
// parse Node Affinity
nodeSelectorTerms := []corev1.NodeSelectorTerm{}
preferredSchedulingTerm := []corev1.PreferredSchedulingTerm{}
if poolParams.Affinity != nil && poolParams.Affinity.NodeAffinity != nil {
if poolParams.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
for _, elem := range poolParams.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms {
term := parseModelsNodeSelectorTerm(elem)
nodeSelectorTerms = append(nodeSelectorTerms, term)
}
}
for _, elem := range poolParams.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
pst := corev1.PreferredSchedulingTerm{
Weight: *elem.Weight,
Preference: parseModelsNodeSelectorTerm(elem.Preference),
}
preferredSchedulingTerm = append(preferredSchedulingTerm, pst)
}
}
var nodeAffinity *corev1.NodeAffinity
if len(nodeSelectorTerms) > 0 || len(preferredSchedulingTerm) > 0 {
nodeAffinity = &corev1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
NodeSelectorTerms: nodeSelectorTerms,
},
PreferredDuringSchedulingIgnoredDuringExecution: preferredSchedulingTerm,
}
}
// parse Pod Affinity
podAffinityTerms := []corev1.PodAffinityTerm{}
weightedPodAffinityTerms := []corev1.WeightedPodAffinityTerm{}
if poolParams.Affinity != nil && poolParams.Affinity.PodAffinity != nil {
for _, elem := range poolParams.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
podAffinityTerms = append(podAffinityTerms, parseModelPodAffinityTerm(elem))
}
for _, elem := range poolParams.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
wAffinityTerm := corev1.WeightedPodAffinityTerm{
Weight: *elem.Weight,
PodAffinityTerm: parseModelPodAffinityTerm(elem.PodAffinityTerm),
}
weightedPodAffinityTerms = append(weightedPodAffinityTerms, wAffinityTerm)
}
}
var podAffinity *corev1.PodAffinity
if len(podAffinityTerms) > 0 || len(weightedPodAffinityTerms) > 0 {
podAffinity = &corev1.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: podAffinityTerms,
PreferredDuringSchedulingIgnoredDuringExecution: weightedPodAffinityTerms,
}
}
// parse Pod Anti Affinity
podAntiAffinityTerms := []corev1.PodAffinityTerm{}
weightedPodAntiAffinityTerms := []corev1.WeightedPodAffinityTerm{}
if poolParams.Affinity != nil && poolParams.Affinity.PodAntiAffinity != nil {
for _, elem := range poolParams.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
podAntiAffinityTerms = append(podAntiAffinityTerms, parseModelPodAffinityTerm(elem))
}
for _, elem := range poolParams.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
wAffinityTerm := corev1.WeightedPodAffinityTerm{
Weight: *elem.Weight,
PodAffinityTerm: parseModelPodAffinityTerm(elem.PodAffinityTerm),
}
weightedPodAntiAffinityTerms = append(weightedPodAntiAffinityTerms, wAffinityTerm)
}
}
var podAntiAffinity *corev1.PodAntiAffinity
if len(podAntiAffinityTerms) > 0 || len(weightedPodAntiAffinityTerms) > 0 {
podAntiAffinity = &corev1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: podAntiAffinityTerms,
PreferredDuringSchedulingIgnoredDuringExecution: weightedPodAntiAffinityTerms,
}
}
var affinity *corev1.Affinity
if nodeAffinity != nil || podAffinity != nil || podAntiAffinity != nil {
affinity = &corev1.Affinity{
NodeAffinity: nodeAffinity,
PodAffinity: podAffinity,
PodAntiAffinity: podAntiAffinity,
}
}
// parse tolerations
tolerations := []corev1.Toleration{}
for _, elem := range poolParams.Tolerations {
var tolerationSeconds *int64
if elem.TolerationSeconds != nil {
// elem.TolerationSeconds.Seconds is allowed to be nil
tolerationSeconds = elem.TolerationSeconds.Seconds
}
toleration := corev1.Toleration{
Key: elem.Key,
Operator: corev1.TolerationOperator(elem.Operator),
Value: elem.Value,
Effect: corev1.TaintEffect(elem.Effect),
TolerationSeconds: tolerationSeconds,
}
tolerations = append(tolerations, toleration)
}
2020-08-05 12:35:41 -07:00
// Pass annotations to the volume
vct := &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "data",
Labels: poolParams.VolumeConfiguration.Labels,
Annotations: poolParams.VolumeConfiguration.Annotations,
},
2020-08-05 12:35:41 -07:00
Spec: volTemp,
}
pool := &miniov2.Pool{
Name: poolParams.Name,
Servers: int32(*poolParams.Servers),
VolumesPerServer: *poolParams.VolumesPerServer,
2020-08-05 12:35:41 -07:00
VolumeClaimTemplate: vct,
Resources: corev1.ResourceRequirements{
Requests: resourcesRequests,
Limits: resourcesLimits,
},
NodeSelector: poolParams.NodeSelector,
Affinity: affinity,
Tolerations: tolerations,
}
return pool, nil
}
func parseModelPodAffinityTerm(term *models.PodAffinityTerm) corev1.PodAffinityTerm {
labelMatchExpressions := []metav1.LabelSelectorRequirement{}
for _, exp := range term.LabelSelector.MatchExpressions {
labelSelectorReq := metav1.LabelSelectorRequirement{
Key: *exp.Key,
Operator: metav1.LabelSelectorOperator(*exp.Operator),
Values: exp.Values,
}
labelMatchExpressions = append(labelMatchExpressions, labelSelectorReq)
}
podAffinityTerm := corev1.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: labelMatchExpressions,
MatchLabels: term.LabelSelector.MatchLabels,
},
Namespaces: term.Namespaces,
TopologyKey: *term.TopologyKey,
}
return podAffinityTerm
}
func parseModelsNodeSelectorTerm(elem *models.NodeSelectorTerm) corev1.NodeSelectorTerm {
var term corev1.NodeSelectorTerm
for _, matchExpression := range elem.MatchExpressions {
matchExp := corev1.NodeSelectorRequirement{
Key: *matchExpression.Key,
Operator: corev1.NodeSelectorOperator(*matchExpression.Operator),
Values: matchExpression.Values,
}
term.MatchExpressions = append(term.MatchExpressions, matchExp)
}
for _, matchField := range elem.MatchFields {
matchF := corev1.NodeSelectorRequirement{
Key: *matchField.Key,
Operator: corev1.NodeSelectorOperator(*matchField.Operator),
Values: matchField.Values,
}
term.MatchFields = append(term.MatchFields, matchF)
}
return term
}
// parseTenantPool miniov2 pool object and returns the equivalent
// models.Pool object
func parseTenantPool(pool *miniov2.Pool) *models.Pool {
var size *int64
var storageClassName string
if pool.VolumeClaimTemplate != nil {
size = swag.Int64(pool.VolumeClaimTemplate.Spec.Resources.Requests.Storage().Value())
if pool.VolumeClaimTemplate.Spec.StorageClassName != nil {
storageClassName = *pool.VolumeClaimTemplate.Spec.StorageClassName
}
}
// parse resources' requests
var resources *models.PoolResources
2020-07-29 12:11:48 -07:00
resourcesRequests := make(map[string]int64)
resourcesLimits := make(map[string]int64)
for key, val := range pool.Resources.Requests {
resourcesRequests[key.String()] = val.Value()
}
for key, val := range pool.Resources.Limits {
resourcesLimits[key.String()] = val.Value()
}
if len(resourcesRequests) > 0 || len(resourcesLimits) > 0 {
resources = &models.PoolResources{
Limits: resourcesLimits,
Requests: resourcesRequests,
}
}
// parse Node Affinity
nodeSelectorTerms := []*models.NodeSelectorTerm{}
preferredSchedulingTerm := []*models.PoolAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0{}
if pool.Affinity != nil && pool.Affinity.NodeAffinity != nil {
if pool.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
for _, elem := range pool.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms {
term := parseNodeSelectorTerm(&elem)
nodeSelectorTerms = append(nodeSelectorTerms, term)
}
}
for _, elem := range pool.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
pst := &models.PoolAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0{
Weight: swag.Int32(elem.Weight),
Preference: parseNodeSelectorTerm(&elem.Preference),
}
preferredSchedulingTerm = append(preferredSchedulingTerm, pst)
}
}
var nodeAffinity *models.PoolAffinityNodeAffinity
if len(nodeSelectorTerms) > 0 || len(preferredSchedulingTerm) > 0 {
nodeAffinity = &models.PoolAffinityNodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &models.PoolAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution{
NodeSelectorTerms: nodeSelectorTerms,
},
PreferredDuringSchedulingIgnoredDuringExecution: preferredSchedulingTerm,
}
}
// parse Pod Affinity
podAffinityTerms := []*models.PodAffinityTerm{}
weightedPodAffinityTerms := []*models.PoolAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0{}
if pool.Affinity != nil && pool.Affinity.PodAffinity != nil {
for _, elem := range pool.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
podAffinityTerms = append(podAffinityTerms, parsePodAffinityTerm(&elem))
}
for _, elem := range pool.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
wAffinityTerm := &models.PoolAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0{
Weight: swag.Int32(elem.Weight),
PodAffinityTerm: parsePodAffinityTerm(&elem.PodAffinityTerm),
}
weightedPodAffinityTerms = append(weightedPodAffinityTerms, wAffinityTerm)
}
}
var podAffinity *models.PoolAffinityPodAffinity
if len(podAffinityTerms) > 0 || len(weightedPodAffinityTerms) > 0 {
podAffinity = &models.PoolAffinityPodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: podAffinityTerms,
PreferredDuringSchedulingIgnoredDuringExecution: weightedPodAffinityTerms,
}
}
// parse Pod Anti Affinity
podAntiAffinityTerms := []*models.PodAffinityTerm{}
weightedPodAntiAffinityTerms := []*models.PoolAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0{}
if pool.Affinity != nil && pool.Affinity.PodAntiAffinity != nil {
for _, elem := range pool.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
podAntiAffinityTerms = append(podAntiAffinityTerms, parsePodAffinityTerm(&elem))
}
for _, elem := range pool.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
wAffinityTerm := &models.PoolAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionItems0{
Weight: swag.Int32(elem.Weight),
PodAffinityTerm: parsePodAffinityTerm(&elem.PodAffinityTerm),
}
weightedPodAntiAffinityTerms = append(weightedPodAntiAffinityTerms, wAffinityTerm)
}
}
var podAntiAffinity *models.PoolAffinityPodAntiAffinity
if len(podAntiAffinityTerms) > 0 || len(weightedPodAntiAffinityTerms) > 0 {
podAntiAffinity = &models.PoolAffinityPodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: podAntiAffinityTerms,
PreferredDuringSchedulingIgnoredDuringExecution: weightedPodAntiAffinityTerms,
}
}
// build affinity object
var affinity *models.PoolAffinity
if nodeAffinity != nil || podAffinity != nil || podAntiAffinity != nil {
affinity = &models.PoolAffinity{
NodeAffinity: nodeAffinity,
PodAffinity: podAffinity,
PodAntiAffinity: podAntiAffinity,
}
}
// parse tolerations
var tolerations models.PoolTolerations
for _, elem := range pool.Tolerations {
var tolerationSecs *models.PoolTolerationSeconds
if elem.TolerationSeconds != nil {
tolerationSecs = &models.PoolTolerationSeconds{
Seconds: elem.TolerationSeconds,
}
}
toleration := &models.PoolTolerationsItems0{
Key: elem.Key,
Operator: string(elem.Operator),
Value: elem.Value,
Effect: string(elem.Effect),
TolerationSeconds: tolerationSecs,
}
tolerations = append(tolerations, toleration)
}
poolModel := &models.Pool{
Name: pool.Name,
Servers: swag.Int64(int64(pool.Servers)),
VolumesPerServer: swag.Int32(pool.VolumesPerServer),
VolumeConfiguration: &models.PoolVolumeConfiguration{
Size: size,
StorageClassName: storageClassName,
},
NodeSelector: pool.NodeSelector,
Resources: resources,
Affinity: affinity,
Tolerations: tolerations,
}
return poolModel
}
func parsePodAffinityTerm(term *corev1.PodAffinityTerm) *models.PodAffinityTerm {
labelMatchExpressions := []*models.PodAffinityTermLabelSelectorMatchExpressionsItems0{}
for _, exp := range term.LabelSelector.MatchExpressions {
labelSelectorReq := &models.PodAffinityTermLabelSelectorMatchExpressionsItems0{
Key: swag.String(exp.Key),
Operator: swag.String(string(exp.Operator)),
Values: exp.Values,
}
labelMatchExpressions = append(labelMatchExpressions, labelSelectorReq)
}
podAffinityTerm := &models.PodAffinityTerm{
LabelSelector: &models.PodAffinityTermLabelSelector{
MatchExpressions: labelMatchExpressions,
MatchLabels: term.LabelSelector.MatchLabels,
},
Namespaces: term.Namespaces,
TopologyKey: swag.String(term.TopologyKey),
}
return podAffinityTerm
}
func parseNodeSelectorTerm(term *corev1.NodeSelectorTerm) *models.NodeSelectorTerm {
var t models.NodeSelectorTerm
for _, matchExpression := range term.MatchExpressions {
matchExp := &models.NodeSelectorTermMatchExpressionsItems0{
Key: swag.String(matchExpression.Key),
Operator: swag.String(string(matchExpression.Operator)),
Values: matchExpression.Values,
}
t.MatchExpressions = append(t.MatchExpressions, matchExp)
}
for _, matchField := range term.MatchFields {
matchF := &models.NodeSelectorTermMatchFieldsItems0{
Key: swag.String(matchField.Key),
Operator: swag.String(string(matchField.Operator)),
Values: matchField.Values,
}
t.MatchFields = append(t.MatchFields, matchF)
}
return &t
}
func getTenantUpdatePoolResponse(session *models.Principal, params operator_api.TenantUpdatePoolsParams) (*models.Tenant, *models.Error) {
ctx := context.Background()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
t, err := updateTenantPools(ctx, opClient, params.Namespace, params.Tenant, params.Body.Pools)
if err != nil {
restapi.LogError("error updating Tenant's pools: %v", err)
return nil, prepareError(err)
}
// parse it to models.Tenant
tenant := getTenantInfo(t)
return tenant, nil
}
// updateTenantPools Sets the Tenant's pools to the ones provided by the request
//
// It does the equivalent to a PUT request on Tenant's pools
func updateTenantPools(
ctx context.Context,
operatorClient OperatorClientI,
namespace string,
tenantName string,
poolsReq []*models.Pool) (*miniov2.Tenant, error) {
minInst, err := operatorClient.TenantGet(ctx, namespace, tenantName, metav1.GetOptions{})
if err != nil {
return nil, err
}
// set the pools if they are provided
var newPoolArray []miniov2.Pool
for _, pool := range poolsReq {
pool, err := parseTenantPoolRequest(pool)
if err != nil {
return nil, err
}
newPoolArray = append(newPoolArray, *pool)
}
// replace pools array
minInst.Spec.Pools = newPoolArray
2020-09-16 23:01:28 -07:00
minInst = minInst.DeepCopy()
minInst.EnsureDefaults()
payloadBytes, err := json.Marshal(minInst)
if err != nil {
return nil, err
}
tenantUpdated, err := operatorClient.TenantPatch(ctx, namespace, minInst.Name, types.MergePatchType, payloadBytes, metav1.PatchOptions{})
if err != nil {
return nil, err
}
return tenantUpdated, nil
}
2021-05-10 15:27:52 -07:00
func getTenantYAML(session *models.Principal, params operator_api.GetTenantYAMLParams) (*models.TenantYAML, *models.Error) {
2021-05-10 15:27:52 -07:00
// get Kubernetes Client
opClient, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
2021-05-10 15:27:52 -07:00
}
tenant, err := opClient.MinioV2().Tenants(params.Namespace).Get(params.HTTPRequest.Context(), params.Tenant, metav1.GetOptions{})
if err != nil {
return nil, prepareError(err)
2021-05-10 15:27:52 -07:00
}
// remove managed fields
tenant.ManagedFields = []metav1.ManagedFieldsEntry{}
//yb, err := yaml.Marshal(tenant)
serializer := k8sJson.NewSerializerWithOptions(
k8sJson.DefaultMetaFactory, nil, nil,
k8sJson.SerializerOptions{
Yaml: true,
Pretty: true,
Strict: true,
},
)
buf := new(bytes.Buffer)
err = serializer.Encode(tenant, buf)
if err != nil {
return nil, prepareError(err)
2021-05-10 15:27:52 -07:00
}
yb := buf.String()
return &models.TenantYAML{Yaml: yb}, nil
}
func getUpdateTenantYAML(session *models.Principal, params operator_api.PutTenantYAMLParams) *models.Error {
2021-05-10 15:27:52 -07:00
// https://godoc.org/k8s.io/apimachinery/pkg/runtime#Scheme
scheme := runtime.NewScheme()
// https://godoc.org/k8s.io/apimachinery/pkg/runtime/serializer#CodecFactory
codecFactory := serializer.NewCodecFactory(scheme)
// https://godoc.org/k8s.io/apimachinery/pkg/runtime#Decoder
deserializer := codecFactory.UniversalDeserializer()
tenantObject, _, err := deserializer.Decode([]byte(params.Body.Yaml), nil, &miniov2.Tenant{})
if err != nil {
return &models.Error{Code: 400, Message: swag.String(err.Error())}
2021-05-10 15:27:52 -07:00
}
inTenant := tenantObject.(*miniov2.Tenant)
// get Kubernetes Client
opClient, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
2021-05-10 15:27:52 -07:00
}
tenant, err := opClient.MinioV2().Tenants(params.Namespace).Get(params.HTTPRequest.Context(), params.Tenant, metav1.GetOptions{})
if err != nil {
return prepareError(err)
2021-05-10 15:27:52 -07:00
}
upTenant := tenant.DeepCopy()
// only update safe fields: spec, metadata.finalizers, metadata.labels and metadata.annotations
upTenant.Labels = inTenant.Labels
upTenant.Annotations = inTenant.Annotations
upTenant.Finalizers = inTenant.Finalizers
2021-05-10 15:27:52 -07:00
upTenant.Spec = inTenant.Spec
_, err = opClient.MinioV2().Tenants(params.Namespace).Update(params.HTTPRequest.Context(), upTenant, metav1.UpdateOptions{})
if err != nil {
return &models.Error{Code: 400, Message: swag.String(err.Error())}
2021-05-10 15:27:52 -07:00
}
return nil
}