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 }