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": ""
        }
      }
    }
}
```
This commit is contained in:
Lenin Alevski
2020-07-30 17:49:56 -07:00
committed by GitHub
parent 88b697f072
commit ee8242d72a
25 changed files with 2965 additions and 388 deletions

View File

@@ -18,6 +18,9 @@ package restapi
import (
"context"
"crypto"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@@ -29,13 +32,16 @@ import (
"strings"
"time"
"github.com/minio/console/pkg/kes"
kes2 "github.com/minio/kes"
"gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/api/resource"
types "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/types"
corev1 "k8s.io/api/core/v1"
"github.com/minio/console/cluster"
madmin "github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/madmin"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
@@ -325,6 +331,7 @@ func getListTenantsResponse(session *models.Principal, params admin_api.ListTena
func getTenantCreatedResponse(session *models.Principal, params admin_api.CreateTenantParams) (*models.CreateTenantResponse, error) {
minioImage := params.Body.Image
if minioImage == "" {
minImg, err := cluster.GetMinioImage()
if err != nil {
@@ -343,14 +350,17 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
// if access/secret are provided, use them, else create a random pair
accessKey := RandomCharString(16)
secretKey := RandomCharString(32)
if params.Body.AccessKey != "" {
accessKey = params.Body.AccessKey
}
if params.Body.SecretKey != "" {
secretKey = params.Body.SecretKey
}
secretName := fmt.Sprintf("%s-secret", *params.Body.Name)
imm := true
instanceSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
@@ -367,15 +377,6 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
return nil, err
}
enableSSL := false
if params.Body.EnableSsl != nil {
enableSSL = *params.Body.EnableSsl
}
enableConsole := true
if params.Body.EnableConsole != nil {
enableConsole = *params.Body.EnableConsole
}
//Construct a MinIO Instance with everything we are getting from parameters
minInst := operator.Tenant{
ObjectMeta: metav1.ObjectMeta{
@@ -387,12 +388,284 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
CredsSecret: &corev1.LocalObjectReference{
Name: secretName,
},
RequestAutoCert: enableSSL,
Env: []corev1.EnvVar{},
},
}
// operator request AutoCert feature
encryption := false
if params.Body.EnableSsl != nil {
encryption = true
minInst.Spec.RequestAutoCert = *params.Body.EnableSsl
}
// User provided TLS certificates (this will take priority over autoCert)
if params.Body.TLS != nil && params.Body.TLS.Crt != nil && params.Body.TLS.Key != nil {
encryption = true
externalTLSCertificateSecretName := fmt.Sprintf("%s-instance-external-certificates", secretName)
// disable autoCert
minInst.Spec.RequestAutoCert = false
tlsCrt, err := base64.StdEncoding.DecodeString(*params.Body.TLS.Crt)
if err != nil {
return nil, err
}
tlsKey, err := base64.StdEncoding.DecodeString(*params.Body.TLS.Key)
if err != nil {
return nil, err
}
externalTLSCertificateSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: externalTLSCertificateSecretName,
},
Type: corev1.SecretTypeTLS,
Immutable: &imm,
Data: map[string][]byte{
"tls.crt": tlsCrt,
"tls.key": tlsKey,
},
}
_, err = clientset.CoreV1().Secrets(ns).Create(context.Background(), &externalTLSCertificateSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
// Certificates used by the minio instance
minInst.Spec.ExternalCertSecret = &operator.LocalCertificateReference{
Name: externalTLSCertificateSecretName,
Type: "kubernetes.io/tls",
}
}
if params.Body.Encryption != nil && encryption {
// Enable auto encryption
minInst.Spec.Env = append(minInst.Spec.Env, corev1.EnvVar{
Name: "MINIO_KMS_AUTO_ENCRYPTION",
Value: "on",
})
if params.Body.Encryption.MasterKey != "" {
// Configure MinIO to use MINIO_KMS_MASTER_KEY legacy key
// https://docs.min.io/docs/minio-vault-legacy.html
minInst.Spec.Env = append(minInst.Spec.Env, corev1.EnvVar{
Name: "MINIO_KMS_MASTER_KEY",
Value: params.Body.Encryption.MasterKey,
})
} else {
// KES configuration for Tenant instance
minInst.Spec.KES = &operator.KESConfig{
Image: "minio/kes:latest",
Replicas: 1,
Metadata: nil,
}
// Using custom image for KES
if params.Body.Encryption.Image != "" {
minInst.Spec.KES.Image = params.Body.Encryption.Image
}
// Secret to store KES server TLS certificates
serverTLSCrt, err := base64.StdEncoding.DecodeString(*params.Body.Encryption.Server.Crt)
if err != nil {
return nil, err
}
serverTLSKey, err := base64.StdEncoding.DecodeString(*params.Body.Encryption.Server.Key)
if err != nil {
return nil, err
}
kesExternalCertificateSecretName := fmt.Sprintf("%s-kes-external-certificates", secretName)
kesExternalCertificateSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: kesExternalCertificateSecretName,
},
Type: corev1.SecretTypeTLS,
Immutable: &imm,
Data: map[string][]byte{
"tls.crt": serverTLSCrt,
"tls.key": serverTLSKey,
},
}
_, err = clientset.CoreV1().Secrets(ns).Create(context.Background(), &kesExternalCertificateSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
// External certificates used by KES
minInst.Spec.KES.ExternalCertSecret = &operator.LocalCertificateReference{
Name: kesExternalCertificateSecretName,
Type: "kubernetes.io/tls",
}
// Secret to store KES clients TLS certificates (mTLS authentication)
clientTLSCrt, err := base64.StdEncoding.DecodeString(*params.Body.Encryption.Client.Crt)
if err != nil {
return nil, err
}
clientTLSKey, err := base64.StdEncoding.DecodeString(*params.Body.Encryption.Client.Key)
if err != nil {
return nil, err
}
instanceExternalClientCertificateSecretName := fmt.Sprintf("%s-instance-external-client-certificates", secretName)
instanceExternalClientCertificateSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: instanceExternalClientCertificateSecretName,
},
Type: corev1.SecretTypeTLS,
Immutable: &imm,
Data: map[string][]byte{
"tls.crt": clientTLSCrt,
"tls.key": clientTLSKey,
},
}
_, err = clientset.CoreV1().Secrets(ns).Create(context.Background(), &instanceExternalClientCertificateSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
// KES client certificates used by MinIO instance
minInst.Spec.ExternalClientCertSecret = &operator.LocalCertificateReference{
Name: instanceExternalClientCertificateSecretName,
Type: "kubernetes.io/tls",
}
// Calculate the client cert identity based on the clientTLSCrt
h := crypto.SHA256.New()
certificate, err := kes.ParseCertificate(clientTLSCrt)
if err != nil {
return nil, err
}
h.Write(certificate.RawSubjectPublicKeyInfo)
clientCrtIdentity := hex.EncodeToString(h.Sum(nil))
// Default configuration for KES
kesConfig := kes.ServerConfig{
Addr: "0.0.0.0:7373",
Root: "disabled",
TLS: kes.TLS{
KeyPath: "/tmp/kes/server.key",
CertPath: "/tmp/kes/server.crt",
},
Policies: map[string]kes.Policy{
"default-policy": {
Paths: []string{
"/v1/key/create/my-minio-key",
"/v1/key/generate/my-minio-key",
"/v1/key/decrypt/my-minio-key",
},
Identities: []kes2.Identity{
kes2.Identity(clientCrtIdentity),
},
},
},
Cache: kes.Cache{
Expiry: &kes.Expiry{
Any: 5 * time.Minute,
Unused: 20 * time.Second,
},
},
Log: kes.Log{
Error: "on",
Audit: "off",
},
Keys: kes.Keys{},
}
// if encryption is enabled and encryption is configured to use Vault
if params.Body.Encryption.Vault != nil {
// Initialize Vault Config
kesConfig.Keys.Vault = &kes.Vault{
Endpoint: *params.Body.Encryption.Vault.Endpoint,
EnginePath: params.Body.Encryption.Vault.Engine,
Namespace: params.Body.Encryption.Vault.Namespace,
Prefix: params.Body.Encryption.Vault.Prefix,
Status: &kes.VaultStatus{
Ping: 10 * time.Second,
},
}
// Vault AppRole credentials
if params.Body.Encryption.Vault.Approle != nil {
kesConfig.Keys.Vault.AppRole = &kes.AppRole{
EnginePath: params.Body.Encryption.Vault.Approle.Engine,
ID: *params.Body.Encryption.Vault.Approle.ID,
Secret: *params.Body.Encryption.Vault.Approle.Secret,
Retry: 15 * time.Second,
}
} else {
return nil, errors.New("approle credentials missing for kes")
}
} else if params.Body.Encryption.Aws != nil {
// Initialize AWS
kesConfig.Keys.Aws = &kes.Aws{
SecretsManager: &kes.AwsSecretManager{},
}
// AWS basic configuration
if params.Body.Encryption.Aws.Secretsmanager != nil {
kesConfig.Keys.Aws.SecretsManager.Endpoint = *params.Body.Encryption.Aws.Secretsmanager.Endpoint
kesConfig.Keys.Aws.SecretsManager.Region = *params.Body.Encryption.Aws.Secretsmanager.Region
kesConfig.Keys.Aws.SecretsManager.KmsKey = params.Body.Encryption.Aws.Secretsmanager.Kmskey
// AWS credentials
if params.Body.Encryption.Aws.Secretsmanager.Credentials != nil {
kesConfig.Keys.Aws.SecretsManager.Login = &kes.AwsSecretManagerLogin{
AccessKey: *params.Body.Encryption.Aws.Secretsmanager.Credentials.Accesskey,
SecretKey: *params.Body.Encryption.Aws.Secretsmanager.Credentials.Secretkey,
SessionToken: params.Body.Encryption.Aws.Secretsmanager.Credentials.Token,
}
}
}
} else if params.Body.Encryption.Gemalto != nil {
// Initialize Gemalto
kesConfig.Keys.Gemalto = &kes.Gemalto{
KeySecure: &kes.GemaltoKeySecure{},
}
// Gemalto Configuration
if params.Body.Encryption.Gemalto.Keysecure != nil {
kesConfig.Keys.Gemalto.KeySecure.Endpoint = *params.Body.Encryption.Gemalto.Keysecure.Endpoint
// Gemalto TLS configuration
if params.Body.Encryption.Gemalto.Keysecure.TLS != nil {
kesConfig.Keys.Gemalto.KeySecure.TLS = &kes.GemaltoTLS{
CAPath: *params.Body.Encryption.Gemalto.Keysecure.TLS.Ca,
}
}
// Gemalto Login
if params.Body.Encryption.Gemalto.Keysecure.Credentials != nil {
kesConfig.Keys.Gemalto.KeySecure.Credentials = &kes.GemaltoCredentials{
Token: *params.Body.Encryption.Gemalto.Keysecure.Credentials.Token,
Domain: *params.Body.Encryption.Gemalto.Keysecure.Credentials.Domain,
Retry: 15 * time.Second,
}
}
}
}
// Generate Yaml configuration for KES
serverConfigYaml, err := yaml.Marshal(kesConfig)
if err != nil {
return nil, err
}
// Secret to store KES server configuration
kesConfigurationSecretName := fmt.Sprintf("%s-kes-configuration", secretName)
kesConfigurationSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: kesConfigurationSecretName,
},
Immutable: &imm,
Data: map[string][]byte{
"server-config.yaml": serverConfigYaml,
},
}
_, err = clientset.CoreV1().Secrets(ns).Create(context.Background(), &kesConfigurationSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
// Configuration used by KES
minInst.Spec.KES.Configuration = &corev1.LocalObjectReference{
Name: kesConfigurationSecretName,
}
}
}
// optionals are set below
var consoleAccess string
var consoleSecret string
enableConsole := true
if params.Body.EnableConsole != nil {
enableConsole = *params.Body.EnableConsole
}
if enableConsole {
consoleSelector := fmt.Sprintf("%s-console", *params.Body.Name)
consoleSecretName := fmt.Sprintf("%s-secret", consoleSelector)
@@ -672,8 +945,8 @@ func parseTenantZoneRequest(zoneParams *models.Zone) (*operator.Zone, error) {
}
// parse resources' requests
var resourcesRequests corev1.ResourceList
var resourcesLimits corev1.ResourceList
resourcesRequests := make(corev1.ResourceList)
resourcesLimits := make(corev1.ResourceList)
if zoneParams.Resources != nil {
for key, val := range zoneParams.Resources.Requests {
resourcesRequests[corev1.ResourceName(key)] = *resource.NewQuantity(val, resource.BinarySI)