github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/kms/kes.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package kms
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"crypto/subtle"
    24  	"crypto/tls"
    25  	"crypto/x509"
    26  	"errors"
    27  	"fmt"
    28  	"strings"
    29  	"sync"
    30  
    31  	"github.com/minio/kms-go/kes"
    32  	"github.com/minio/pkg/v2/certs"
    33  	"github.com/minio/pkg/v2/env"
    34  )
    35  
    36  const (
    37  	tlsClientSessionCacheSize = 100
    38  )
    39  
    40  // Config contains various KMS-related configuration
    41  // parameters - like KMS endpoints or authentication
    42  // credentials.
    43  type Config struct {
    44  	// Endpoints contains a list of KMS server
    45  	// HTTP endpoints.
    46  	Endpoints []string
    47  
    48  	// DefaultKeyID is the key ID used when
    49  	// no explicit key ID is specified for
    50  	// a cryptographic operation.
    51  	DefaultKeyID string
    52  
    53  	// APIKey is an credential provided by env. var.
    54  	// to authenticate to a KES server. Either an
    55  	// API key or a client certificate must be specified.
    56  	APIKey kes.APIKey
    57  
    58  	// Certificate is the client TLS certificate
    59  	// to authenticate to KMS via mTLS.
    60  	Certificate *certs.Certificate
    61  
    62  	// ReloadCertEvents is an event channel that receives
    63  	// the reloaded client certificate.
    64  	ReloadCertEvents <-chan tls.Certificate
    65  
    66  	// RootCAs is a set of root CA certificates
    67  	// to verify the KMS server TLS certificate.
    68  	RootCAs *x509.CertPool
    69  }
    70  
    71  // NewWithConfig returns a new KMS using the given
    72  // configuration.
    73  func NewWithConfig(config Config) (KMS, error) {
    74  	if len(config.Endpoints) == 0 {
    75  		return nil, errors.New("kms: no server endpoints")
    76  	}
    77  	endpoints := make([]string, len(config.Endpoints)) // Copy => avoid being affect by any changes to the original slice
    78  	copy(endpoints, config.Endpoints)
    79  
    80  	var client *kes.Client
    81  	if config.APIKey != nil {
    82  		cert, err := kes.GenerateCertificate(config.APIKey)
    83  		if err != nil {
    84  			return nil, err
    85  		}
    86  		client = kes.NewClientWithConfig("", &tls.Config{
    87  			MinVersion:         tls.VersionTLS12,
    88  			Certificates:       []tls.Certificate{cert},
    89  			RootCAs:            config.RootCAs,
    90  			ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize),
    91  		})
    92  	} else {
    93  		client = kes.NewClientWithConfig("", &tls.Config{
    94  			MinVersion:         tls.VersionTLS12,
    95  			Certificates:       []tls.Certificate{config.Certificate.Get()},
    96  			RootCAs:            config.RootCAs,
    97  			ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize),
    98  		})
    99  	}
   100  	client.Endpoints = endpoints
   101  
   102  	c := &kesClient{
   103  		client:       client,
   104  		defaultKeyID: config.DefaultKeyID,
   105  	}
   106  	go func() {
   107  		if config.Certificate == nil || config.ReloadCertEvents == nil {
   108  			return
   109  		}
   110  		var prevCertificate tls.Certificate
   111  		for {
   112  			certificate, ok := <-config.ReloadCertEvents
   113  			if !ok {
   114  				return
   115  			}
   116  			sameCert := len(certificate.Certificate) == len(prevCertificate.Certificate)
   117  			for i, b := range certificate.Certificate {
   118  				if !sameCert {
   119  					break
   120  				}
   121  				sameCert = sameCert && bytes.Equal(b, prevCertificate.Certificate[i])
   122  			}
   123  			// Do not reload if its the same cert as before.
   124  			if !sameCert {
   125  				client := kes.NewClientWithConfig("", &tls.Config{
   126  					MinVersion:         tls.VersionTLS12,
   127  					Certificates:       []tls.Certificate{certificate},
   128  					RootCAs:            config.RootCAs,
   129  					ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize),
   130  				})
   131  				client.Endpoints = endpoints
   132  
   133  				c.lock.Lock()
   134  				c.client = client
   135  				c.lock.Unlock()
   136  
   137  				prevCertificate = certificate
   138  			}
   139  		}
   140  	}()
   141  	return c, nil
   142  }
   143  
   144  type kesClient struct {
   145  	lock         sync.RWMutex
   146  	defaultKeyID string
   147  	client       *kes.Client
   148  }
   149  
   150  var ( // compiler checks
   151  	_ KMS             = (*kesClient)(nil)
   152  	_ KeyManager      = (*kesClient)(nil)
   153  	_ IdentityManager = (*kesClient)(nil)
   154  	_ PolicyManager   = (*kesClient)(nil)
   155  )
   156  
   157  // Stat returns the current KES status containing a
   158  // list of KES endpoints and the default key ID.
   159  func (c *kesClient) Stat(ctx context.Context) (Status, error) {
   160  	c.lock.RLock()
   161  	defer c.lock.RUnlock()
   162  
   163  	st, err := c.client.Status(ctx)
   164  	if err != nil {
   165  		return Status{}, err
   166  	}
   167  	endpoints := make([]string, len(c.client.Endpoints))
   168  	copy(endpoints, c.client.Endpoints)
   169  	return Status{
   170  		Name:       "KES",
   171  		Endpoints:  endpoints,
   172  		DefaultKey: c.defaultKeyID,
   173  		Details:    st,
   174  	}, nil
   175  }
   176  
   177  // IsLocal returns true if the KMS is a local implementation
   178  func (c *kesClient) IsLocal() bool {
   179  	return env.IsSet(EnvKMSSecretKey)
   180  }
   181  
   182  // List returns an array of local KMS Names
   183  func (c *kesClient) List() []kes.KeyInfo {
   184  	var kmsSecret []kes.KeyInfo
   185  	envKMSSecretKey := env.Get(EnvKMSSecretKey, "")
   186  	values := strings.SplitN(envKMSSecretKey, ":", 2)
   187  	if len(values) == 2 {
   188  		kmsSecret = []kes.KeyInfo{
   189  			{
   190  				Name: values[0],
   191  			},
   192  		}
   193  	}
   194  	return kmsSecret
   195  }
   196  
   197  // Metrics retrieves server metrics in the Prometheus exposition format.
   198  func (c *kesClient) Metrics(ctx context.Context) (kes.Metric, error) {
   199  	c.lock.RLock()
   200  	defer c.lock.RUnlock()
   201  
   202  	return c.client.Metrics(ctx)
   203  }
   204  
   205  // Version retrieves version information
   206  func (c *kesClient) Version(ctx context.Context) (string, error) {
   207  	c.lock.RLock()
   208  	defer c.lock.RUnlock()
   209  
   210  	return c.client.Version(ctx)
   211  }
   212  
   213  // APIs retrieves a list of supported API endpoints
   214  func (c *kesClient) APIs(ctx context.Context) ([]kes.API, error) {
   215  	c.lock.RLock()
   216  	defer c.lock.RUnlock()
   217  
   218  	return c.client.APIs(ctx)
   219  }
   220  
   221  // CreateKey tries to create a new key at the KMS with the
   222  // given key ID.
   223  //
   224  // If the a key with the same keyID already exists then
   225  // CreateKey returns kes.ErrKeyExists.
   226  func (c *kesClient) CreateKey(ctx context.Context, keyID string) error {
   227  	c.lock.RLock()
   228  	defer c.lock.RUnlock()
   229  
   230  	return c.client.CreateKey(ctx, keyID)
   231  }
   232  
   233  // DeleteKey deletes a key at the KMS with the given key ID.
   234  // Please note that is a dangerous operation.
   235  // Once a key has been deleted all data that has been encrypted with it cannot be decrypted
   236  // anymore, and therefore, is lost.
   237  func (c *kesClient) DeleteKey(ctx context.Context, keyID string) error {
   238  	c.lock.RLock()
   239  	defer c.lock.RUnlock()
   240  
   241  	return c.client.DeleteKey(ctx, keyID)
   242  }
   243  
   244  // ListKeys returns an iterator over all key names.
   245  func (c *kesClient) ListKeys(ctx context.Context) (*kes.ListIter[string], error) {
   246  	c.lock.RLock()
   247  	defer c.lock.RUnlock()
   248  
   249  	return &kes.ListIter[string]{
   250  		NextFunc: c.client.ListKeys,
   251  	}, nil
   252  }
   253  
   254  // GenerateKey generates a new data encryption key using
   255  // the key at the KES server referenced by the key ID.
   256  //
   257  // The default key ID will be used if keyID is empty.
   258  //
   259  // The context is associated and tied to the generated DEK.
   260  // The same context must be provided when the generated
   261  // key should be decrypted.
   262  func (c *kesClient) GenerateKey(ctx context.Context, keyID string, cryptoCtx Context) (DEK, error) {
   263  	c.lock.RLock()
   264  	defer c.lock.RUnlock()
   265  
   266  	if keyID == "" {
   267  		keyID = c.defaultKeyID
   268  	}
   269  	ctxBytes, err := cryptoCtx.MarshalText()
   270  	if err != nil {
   271  		return DEK{}, err
   272  	}
   273  
   274  	dek, err := c.client.GenerateKey(ctx, keyID, ctxBytes)
   275  	if err != nil {
   276  		return DEK{}, err
   277  	}
   278  	return DEK{
   279  		KeyID:      keyID,
   280  		Plaintext:  dek.Plaintext,
   281  		Ciphertext: dek.Ciphertext,
   282  	}, nil
   283  }
   284  
   285  // ImportKey imports a cryptographic key into the KMS.
   286  func (c *kesClient) ImportKey(ctx context.Context, keyID string, bytes []byte) error {
   287  	c.lock.RLock()
   288  	defer c.lock.RUnlock()
   289  
   290  	return c.client.ImportKey(ctx, keyID, &kes.ImportKeyRequest{
   291  		Key: bytes,
   292  	})
   293  }
   294  
   295  // EncryptKey Encrypts and authenticates a (small) plaintext with the cryptographic key
   296  // The plaintext must not exceed 1 MB
   297  func (c *kesClient) EncryptKey(keyID string, plaintext []byte, ctx Context) ([]byte, error) {
   298  	c.lock.RLock()
   299  	defer c.lock.RUnlock()
   300  
   301  	ctxBytes, err := ctx.MarshalText()
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	return c.client.Encrypt(context.Background(), keyID, plaintext, ctxBytes)
   306  }
   307  
   308  // DecryptKey decrypts the ciphertext with the key at the KES
   309  // server referenced by the key ID. The context must match the
   310  // context value used to generate the ciphertext.
   311  func (c *kesClient) DecryptKey(keyID string, ciphertext []byte, ctx Context) ([]byte, error) {
   312  	c.lock.RLock()
   313  	defer c.lock.RUnlock()
   314  
   315  	ctxBytes, err := ctx.MarshalText()
   316  	if err != nil {
   317  		return nil, err
   318  	}
   319  	return c.client.Decrypt(context.Background(), keyID, ciphertext, ctxBytes)
   320  }
   321  
   322  func (c *kesClient) DecryptAll(ctx context.Context, keyID string, ciphertexts [][]byte, contexts []Context) ([][]byte, error) {
   323  	c.lock.RLock()
   324  	defer c.lock.RUnlock()
   325  
   326  	plaintexts := make([][]byte, 0, len(ciphertexts))
   327  	for i := range ciphertexts {
   328  		ctxBytes, err := contexts[i].MarshalText()
   329  		if err != nil {
   330  			return nil, err
   331  		}
   332  		plaintext, err := c.client.Decrypt(ctx, keyID, ciphertexts[i], ctxBytes)
   333  		if err != nil {
   334  			return nil, err
   335  		}
   336  		plaintexts = append(plaintexts, plaintext)
   337  	}
   338  	return plaintexts, nil
   339  }
   340  
   341  // HMAC generates the HMAC checksum of the given msg using the key
   342  // with the given keyID at the KMS.
   343  func (c *kesClient) HMAC(ctx context.Context, keyID string, msg []byte) ([]byte, error) {
   344  	c.lock.RLock()
   345  	defer c.lock.RUnlock()
   346  
   347  	return c.client.HMAC(context.Background(), keyID, msg)
   348  }
   349  
   350  // DescribePolicy describes a policy by returning its metadata.
   351  // e.g. who created the policy at which point in time.
   352  func (c *kesClient) DescribePolicy(ctx context.Context, policy string) (*kes.PolicyInfo, error) {
   353  	c.lock.RLock()
   354  	defer c.lock.RUnlock()
   355  
   356  	return c.client.DescribePolicy(ctx, policy)
   357  }
   358  
   359  // ListPolicies returns an iterator over all policy names.
   360  func (c *kesClient) ListPolicies(ctx context.Context) (*kes.ListIter[string], error) {
   361  	c.lock.RLock()
   362  	defer c.lock.RUnlock()
   363  
   364  	return &kes.ListIter[string]{
   365  		NextFunc: c.client.ListPolicies,
   366  	}, nil
   367  }
   368  
   369  // GetPolicy gets a policy from KMS.
   370  func (c *kesClient) GetPolicy(ctx context.Context, policy string) (*kes.Policy, error) {
   371  	c.lock.RLock()
   372  	defer c.lock.RUnlock()
   373  
   374  	return c.client.GetPolicy(ctx, policy)
   375  }
   376  
   377  // DescribeIdentity describes an identity by returning its metadata.
   378  // e.g. which policy is currently assigned and whether its an admin identity.
   379  func (c *kesClient) DescribeIdentity(ctx context.Context, identity string) (*kes.IdentityInfo, error) {
   380  	c.lock.RLock()
   381  	defer c.lock.RUnlock()
   382  
   383  	return c.client.DescribeIdentity(ctx, kes.Identity(identity))
   384  }
   385  
   386  // DescribeSelfIdentity describes the identity issuing the request.
   387  // It infers the identity from the TLS client certificate used to authenticate.
   388  // It returns the identity and policy information for the client identity.
   389  func (c *kesClient) DescribeSelfIdentity(ctx context.Context) (*kes.IdentityInfo, *kes.Policy, error) {
   390  	c.lock.RLock()
   391  	defer c.lock.RUnlock()
   392  
   393  	return c.client.DescribeSelf(ctx)
   394  }
   395  
   396  // ListPolicies returns an iterator over all identities.
   397  func (c *kesClient) ListIdentities(ctx context.Context) (*kes.ListIter[kes.Identity], error) {
   398  	c.lock.RLock()
   399  	defer c.lock.RUnlock()
   400  
   401  	return &kes.ListIter[kes.Identity]{
   402  		NextFunc: c.client.ListIdentities,
   403  	}, nil
   404  }
   405  
   406  // Verify verifies all KMS endpoints and returns details
   407  func (c *kesClient) Verify(ctx context.Context) []VerifyResult {
   408  	c.lock.RLock()
   409  	defer c.lock.RUnlock()
   410  
   411  	results := []VerifyResult{}
   412  	kmsContext := Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
   413  	for _, endpoint := range c.client.Endpoints {
   414  		client := kes.Client{
   415  			Endpoints:  []string{endpoint},
   416  			HTTPClient: c.client.HTTPClient,
   417  		}
   418  
   419  		// 1. Get stats for the KES instance
   420  		state, err := client.Status(ctx)
   421  		if err != nil {
   422  			results = append(results, VerifyResult{Status: "offline", Endpoint: endpoint})
   423  			continue
   424  		}
   425  
   426  		// 2. Generate a new key using the KMS.
   427  		kmsCtx, err := kmsContext.MarshalText()
   428  		if err != nil {
   429  			results = append(results, VerifyResult{Status: "offline", Endpoint: endpoint})
   430  			continue
   431  		}
   432  		result := VerifyResult{Status: "online", Endpoint: endpoint, Version: state.Version}
   433  		key, err := client.GenerateKey(ctx, env.Get(EnvKESKeyName, ""), kmsCtx)
   434  		if err != nil {
   435  			result.Encrypt = fmt.Sprintf("Encryption failed: %v", err)
   436  		} else {
   437  			result.Encrypt = "success"
   438  		}
   439  		// 3. Verify that we can indeed decrypt the (encrypted) key
   440  		decryptedKey, err := client.Decrypt(ctx, env.Get(EnvKESKeyName, ""), key.Ciphertext, kmsCtx)
   441  		switch {
   442  		case err != nil:
   443  			result.Decrypt = fmt.Sprintf("Decryption failed: %v", err)
   444  		case subtle.ConstantTimeCompare(key.Plaintext, decryptedKey) != 1:
   445  			result.Decrypt = "Decryption failed: decrypted key does not match generated key"
   446  		default:
   447  			result.Decrypt = "success"
   448  		}
   449  		results = append(results, result)
   450  	}
   451  	return results
   452  }