github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/initializer/ca/config/config.go (about)

     1  /*
     2   * Copyright contributors to the Hyperledger Fabric Operator project
     3   *
     4   * SPDX-License-Identifier: Apache-2.0
     5   *
     6   * Licensed under the Apache License, Version 2.0 (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at:
     9   *
    10   * 	  http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  package config
    20  
    21  import (
    22  	"crypto/x509"
    23  	"encoding/pem"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"regexp"
    29  	"strings"
    30  
    31  	"github.com/pkg/errors"
    32  
    33  	v1 "github.com/IBM-Blockchain/fabric-operator/pkg/apis/ca/v1"
    34  	"github.com/IBM-Blockchain/fabric-operator/pkg/util"
    35  
    36  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    37  	"sigs.k8s.io/yaml"
    38  )
    39  
    40  type Type string
    41  
    42  const (
    43  	EnrollmentCA Type = "enrollment"
    44  	TLSCA        Type = "tls"
    45  )
    46  
    47  func (t Type) Is(typ Type) bool {
    48  	return t == typ
    49  }
    50  
    51  type InputType string
    52  
    53  var (
    54  	File   InputType = "File"
    55  	Pem    InputType = "Pem"
    56  	Base64 InputType = "Base64"
    57  	Bccsp  InputType = "Bccsp"
    58  )
    59  
    60  var log = logf.Log.WithName("initializer_config")
    61  
    62  type Config struct {
    63  	ServerConfig *v1.ServerConfig
    64  	HomeDir      string
    65  	MountPath    string
    66  	Update       bool
    67  	SqlitePath   string
    68  
    69  	tlsCrypto          map[string][]byte
    70  	dbCrypto           map[string][]byte
    71  	caCrypto           map[string][]byte
    72  	operationsCrypto   map[string][]byte
    73  	intermediateCrypto map[string][]byte
    74  }
    75  
    76  func (c *Config) GetServerConfig() *v1.ServerConfig {
    77  	return c.ServerConfig
    78  }
    79  
    80  func (c *Config) GetHomeDir() string {
    81  	return c.HomeDir
    82  }
    83  
    84  func (c *Config) GetTLSCrypto() map[string][]byte {
    85  	return c.tlsCrypto
    86  }
    87  
    88  func (c *Config) HandleCertInput(input, location string, store map[string][]byte) error {
    89  	var err error
    90  	inputType := GetInputType(input)
    91  
    92  	log.Info(fmt.Sprintf("Handling input of cert type '%s', to be stored at '%s'", inputType, location))
    93  
    94  	data := []byte{}
    95  	switch inputType {
    96  	case Pem:
    97  		data = util.PemStringToBytes(input)
    98  		err = c.StoreInMap(data, location, store)
    99  		if err != nil {
   100  			return err
   101  		}
   102  	case File:
   103  		// On an update of config overrides, file is not a valid override value as the operator
   104  		// won't have access to it. Cert can only be passed as base64.
   105  		if !c.Update {
   106  			data, err = util.FileToBytes(input)
   107  			if err != nil {
   108  				return err
   109  			}
   110  			err = c.StoreInMap(data, location, store)
   111  			if err != nil {
   112  				return err
   113  			}
   114  		}
   115  	case Base64:
   116  		data, err = util.Base64ToBytes(input)
   117  		if err != nil {
   118  			return err
   119  		}
   120  		err = c.StoreInMap(data, location, store)
   121  		if err != nil {
   122  			return err
   123  		}
   124  	case Bccsp:
   125  		return nil
   126  	default:
   127  		return errors.Errorf("invalid input type: %s", input)
   128  	}
   129  
   130  	if len(data) != 0 {
   131  		err := c.EnsureDirAndWriteFile(location, data)
   132  		if err != nil {
   133  			return err
   134  		}
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  func (c *Config) EnsureDirAndWriteFile(location string, data []byte) error {
   141  	path := filepath.Join(c.HomeDir, location)
   142  	err := util.EnsureDir(filepath.Dir(path))
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	err = ioutil.WriteFile(filepath.Clean(path), data, 0600)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  func (c *Config) HandleKeyInput(input, location string, store map[string][]byte) error {
   156  	var err error
   157  
   158  	inputType := GetInputType(input)
   159  
   160  	log.Info(fmt.Sprintf("Handling input of key type '%s', to be stored at '%s'", inputType, location))
   161  
   162  	data := []byte{}
   163  	switch inputType {
   164  	case Pem:
   165  		data = util.PemStringToBytes(input)
   166  		err = c.StoreInMap(data, location, store)
   167  		if err != nil {
   168  			return err
   169  		}
   170  	case File:
   171  		// On an update of config overrides, file is not a valid override value as the operator
   172  		// won't have access to it. Key can only be passed as base64.
   173  		if !c.Update {
   174  			data, err = util.FileToBytes(input)
   175  			if err != nil {
   176  				return err
   177  			}
   178  			err = c.StoreInMap(data, location, store)
   179  			if err != nil {
   180  				return err
   181  			}
   182  		}
   183  	case Base64:
   184  		data, err = util.Base64ToBytes(input)
   185  		if err != nil {
   186  			return err
   187  		}
   188  		err = c.StoreInMap(data, location, store)
   189  		if err != nil {
   190  			return err
   191  		}
   192  	case Bccsp:
   193  		// If HSM enabled, don't try to read key from file system
   194  		if c.UsingPKCS11() {
   195  			return nil
   196  		}
   197  		// On an update of config overrides, reading from keystore is not valid. After init create
   198  		// the key stored in a kubernetes secret and operator won't have access to it.
   199  		if !c.Update {
   200  			data, err = c.GetSigningKey(c.HomeDir)
   201  			if err != nil {
   202  				return err
   203  			}
   204  			err = c.StoreInMap(data, location, store)
   205  			if err != nil {
   206  				return err
   207  			}
   208  		}
   209  	default:
   210  		return errors.Errorf("invalid input type: %s", input)
   211  	}
   212  
   213  	if len(data) != 0 {
   214  		err := c.EnsureDirAndWriteFile(location, data)
   215  		if err != nil {
   216  			return err
   217  		}
   218  	}
   219  
   220  	return nil
   221  }
   222  
   223  func (c *Config) StoreInMap(data []byte, location string, store map[string][]byte) error {
   224  	if len(data) == 0 {
   225  		return nil
   226  	}
   227  
   228  	key := ConvertStringForSecrets(location, true)
   229  	store[key] = data
   230  	return nil
   231  }
   232  
   233  // GetSigningKey applies to non-hsm use cases where the key exists on the filesystem.
   234  // The filesystem is read and then key is then stored in a kubernetes secret.
   235  func (c *Config) GetSigningKey(path string) ([]byte, error) {
   236  
   237  	keystoreDir := filepath.Join(path, "msp", "keystore")
   238  	files, err := ioutil.ReadDir(keystoreDir)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  
   243  	if len(files) == 0 {
   244  		return nil, fmt.Errorf("no keys found in keystore directory: %s", keystoreDir)
   245  	}
   246  
   247  	// Need this loop to find appropriate key. Three files are generated
   248  	// by default by the CA: IssuerRevocationPrivateKey, IssuerSecretKey, and *_sk
   249  	// We are only interested in file ending with 'sk' which the is Private Key
   250  	// associated with the x509 certificate
   251  	for _, file := range files {
   252  		fileBytes, err := ioutil.ReadFile(filepath.Clean(filepath.Join(keystoreDir, file.Name())))
   253  		if err != nil {
   254  			return nil, err
   255  		}
   256  
   257  		block, _ := pem.Decode(fileBytes)
   258  		if block == nil {
   259  			continue
   260  		}
   261  
   262  		_, err = x509.ParsePKCS8PrivateKey(block.Bytes)
   263  		if err == nil {
   264  			return fileBytes, nil
   265  		}
   266  	}
   267  
   268  	return nil, errors.Errorf("failed to parse CA's private key")
   269  }
   270  
   271  func (c *Config) SetUpdate(update bool) {
   272  	c.Update = update
   273  }
   274  
   275  func (c *Config) SetServerConfig(cfg *v1.ServerConfig) {
   276  	c.ServerConfig = cfg
   277  }
   278  
   279  func (c *Config) SetMountPaths(caType Type) {
   280  	switch caType {
   281  	case EnrollmentCA:
   282  		c.CAMountPath()
   283  		c.DBMountPath()
   284  		c.IntermediateMountPath()
   285  		c.OperationsMountPath()
   286  		c.TLSMountPath()
   287  	case TLSCA:
   288  		c.CAMountPath()
   289  		c.DBMountPath()
   290  	}
   291  }
   292  
   293  func (c *Config) UsingPKCS11() bool {
   294  	if c.ServerConfig != nil && c.ServerConfig.CAConfig.CSP != nil {
   295  		if strings.ToLower(c.ServerConfig.CAConfig.CSP.ProviderName) == "pkcs11" {
   296  			return true
   297  		}
   298  	}
   299  	return false
   300  }
   301  
   302  func GetInputType(input string) InputType {
   303  	data := []byte(input)
   304  	block, _ := pem.Decode(data)
   305  	if block != nil {
   306  		return Pem
   307  	}
   308  
   309  	data, err := util.Base64ToBytes(input)
   310  	if err == nil && data != nil {
   311  		return Base64
   312  	}
   313  
   314  	// If input string is found as an already exisiting file, return CertFile type
   315  	_, err = os.Stat(input)
   316  	if err == nil {
   317  		return File
   318  	}
   319  
   320  	return Bccsp
   321  }
   322  
   323  func ConvertStringForSecrets(filepath string, forward bool) string {
   324  	// shared//tlsca//db/certs/certfile0.pem
   325  	if forward {
   326  		return strings.Replace(filepath, "/", "_", -1)
   327  	}
   328  	// data[shared__tlsca__db_certs_certfile0.pem
   329  	return strings.Replace(filepath, "_", "/", -1)
   330  }
   331  
   332  func IsValidPostgressDatasource(datasourceStr string) bool {
   333  	regexpssions := []string{`host=\S+`, `port=\d+`, `user=\S+`, `password=\S+`, `dbname=\S+`, `sslmode=\S+`}
   334  	for _, regexpression := range regexpssions {
   335  		re := regexp.MustCompile(regexpression)
   336  		matches := len(re.FindStringSubmatch(datasourceStr))
   337  		if matches == 0 {
   338  			return false
   339  		}
   340  	}
   341  	return true
   342  }
   343  
   344  func ValidCryptoInput(certFile, keyFile string) error {
   345  	if certFile == "" && keyFile != "" {
   346  		return errors.New("Key file specified but no corresponding certificate file specified, both must be passed")
   347  	}
   348  	if certFile != "" && keyFile == "" {
   349  		return errors.New("Certificate file specified but no corresponding key file specified, both must be passed")
   350  	}
   351  	return nil
   352  }
   353  
   354  func ReadFrom(from *[]byte) (*Config, error) {
   355  	config := &v1.ServerConfig{}
   356  	err := yaml.Unmarshal(*from, config)
   357  	if err != nil {
   358  		return nil, err
   359  	}
   360  
   361  	return &Config{
   362  		ServerConfig: config,
   363  	}, nil
   364  }