github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/initializer/ca/ca.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 initializer 20 21 import ( 22 "context" 23 "database/sql" 24 "fmt" 25 "io/ioutil" 26 "net/url" 27 "os" 28 "path/filepath" 29 "time" 30 31 _ "github.com/lib/pq" 32 33 v1 "github.com/IBM-Blockchain/fabric-operator/pkg/apis/ca/v1" 34 "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/ca/config" 35 "github.com/IBM-Blockchain/fabric-operator/pkg/util" 36 "github.com/IBM-Blockchain/fabric-operator/pkg/util/merge" 37 "github.com/IBM-Blockchain/fabric-operator/pkg/util/pointer" 38 "github.com/hyperledger/fabric-ca/lib" 39 "github.com/pkg/errors" 40 "github.com/spf13/viper" 41 "sigs.k8s.io/yaml" 42 ) 43 44 //go:generate counterfeiter -o mocks/config.go -fake-name CAConfig . CAConfig 45 46 type CAConfig interface { 47 GetServerConfig() *v1.ServerConfig 48 ParseCABlock() (map[string][]byte, error) 49 ParseDBBlock() (map[string][]byte, error) 50 ParseTLSBlock() (map[string][]byte, error) 51 ParseOperationsBlock() (map[string][]byte, error) 52 ParseIntermediateBlock() (map[string][]byte, error) 53 SetServerConfig(*v1.ServerConfig) 54 SetMountPaths(config.Type) 55 GetHomeDir() string 56 SetUpdate(bool) 57 UsingPKCS11() bool 58 } 59 60 type CA struct { 61 CN string 62 Config CAConfig 63 Viper *viper.Viper 64 Type config.Type 65 SqliteDir string 66 UsingHSMProxy bool 67 68 configFile string 69 } 70 71 func LoadConfigFromFile(file string) (*v1.ServerConfig, error) { 72 serverConfig := &v1.ServerConfig{} 73 bytes, err := ioutil.ReadFile(filepath.Clean(file)) 74 if err != nil { 75 return nil, err 76 } 77 78 err = yaml.Unmarshal(bytes, serverConfig) 79 if err != nil { 80 return nil, err 81 } 82 83 err = yaml.Unmarshal(bytes, &serverConfig.CAConfig) 84 if err != nil { 85 return nil, err 86 } 87 88 return serverConfig, nil 89 } 90 91 func NewCA(config CAConfig, caType config.Type, sqliteDir string, hsmProxy bool, cn string) *CA { 92 return &CA{ 93 CN: cn, 94 Config: config, 95 Viper: viper.New(), 96 Type: caType, 97 configFile: fmt.Sprintf("%s/fabric-ca-server-config.yaml", config.GetHomeDir()), 98 SqliteDir: sqliteDir, 99 UsingHSMProxy: hsmProxy, 100 } 101 } 102 103 func (ca *CA) OverrideServerConfig(newConfig *v1.ServerConfig) (err error) { 104 serverConfig := ca.Config.GetServerConfig() 105 106 log.Info("Overriding config values from ca initializer") 107 // If newConfig isn't passed, we want to make sure serverConfig.CAConfig.CSR.Cn is set 108 // to ca.CN by default; if newConfig is passed for an intermediate CA, the logic below 109 // will handle setting CN to blank if ParentServer.URL is set 110 serverConfig.CAConfig.CSR.CN = ca.CN 111 112 if newConfig != nil { 113 log.Info("Overriding config values from spec") 114 err = merge.WithOverwrite(ca.Config.GetServerConfig(), newConfig) 115 if err != nil { 116 return errors.Wrapf(err, "failed to merge override configuration") 117 } 118 119 if ca.Config.UsingPKCS11() { 120 ca.SetPKCS11Defaults(serverConfig) 121 } 122 123 // Passing in CN when enrolling an intermediate CA will cause the fabric-ca 124 // server to error out, a CN cannot be passed for intermediate CA. Setting 125 // CN to blank if ParentServer.URL is set 126 if serverConfig.CAConfig.Intermediate.ParentServer.URL != "" { 127 serverConfig.CAConfig.CSR.CN = "" 128 } 129 } 130 131 ca.setDefaults(serverConfig) 132 133 return nil 134 } 135 136 func (ca *CA) WriteConfig() (err error) { 137 dir := ca.Config.GetHomeDir() 138 log.Info(fmt.Sprintf("Writing config to file: '%s'", dir)) 139 140 bytes, err := ca.ConfigToBytes() 141 if err != nil { 142 return err 143 } 144 145 err = util.EnsureDir(dir) 146 if err != nil { 147 return err 148 } 149 150 err = ioutil.WriteFile(filepath.Clean(ca.configFile), bytes, 0600) 151 if err != nil { 152 return err 153 } 154 155 return nil 156 } 157 158 func (ca *CA) Init() (err error) { 159 if ca.Config.UsingPKCS11() && ca.UsingHSMProxy { 160 env := os.Getenv("PKCS11_PROXY_SOCKET") 161 if env == "" { 162 return errors.New("ca configured to use PKCS11, but no PKCS11 proxy endpoint set") 163 } 164 if !util.IsTCPReachable(env) { 165 return errors.New(fmt.Sprintf("Unable to reach PKCS11 proxy: %s", env)) 166 } 167 } 168 169 cfg, err := ca.ViperUnmarshal(ca.configFile) 170 if err != nil { 171 return errors.Wrap(err, "viper unmarshal failed") 172 } 173 174 dir := filepath.Dir(ca.configFile) 175 // TODO check if this is required!! 176 cfg.Metrics.Provider = "disabled" 177 178 if cfg.CAcfg.DB.Type == "postgres" { 179 if !ca.IsPostgresReachable(cfg.CAcfg.DB) { 180 return errors.New("Cannot initialize CA. Postgres is not reachable") 181 } 182 } 183 184 parentURL := cfg.CAcfg.Intermediate.ParentServer.URL 185 if parentURL != "" { 186 log.Info(fmt.Sprintf("Request received to enroll with parent server: %s", parentURL)) 187 188 err = ca.HealthCheck(parentURL, cfg.CAcfg.Intermediate.TLS.CertFiles[0]) 189 if err != nil { 190 return errors.Wrap(err, "could not connect to parent CA") 191 } 192 } 193 194 caserver := &lib.Server{ 195 HomeDir: dir, 196 Config: cfg, 197 CA: lib.CA{ 198 Config: &cfg.CAcfg, 199 }, 200 } 201 202 err = caserver.Init(false) 203 if err != nil { 204 return err 205 } 206 serverConfig := ca.Config.GetServerConfig() 207 serverConfig.CA.Certfile = caserver.CA.Config.CA.Certfile 208 serverConfig.CA.Keyfile = caserver.CA.Config.CA.Keyfile 209 serverConfig.CA.Chainfile = caserver.CA.Config.CA.Chainfile 210 211 if ca.Type.Is(config.EnrollmentCA) { 212 serverConfig.CAfiles = []string{"/data/tlsca/fabric-ca-server-config.yaml"} 213 } 214 215 return nil 216 } 217 218 func (ca *CA) IsPostgresReachable(db lib.CAConfigDB) bool { 219 220 datasource := db.Datasource 221 if db.TLS.CertFiles != nil && len(db.TLS.CertFiles) > 0 { 222 // The first cert because that is what hyperledger/fabric-ca uses 223 datasource = fmt.Sprintf("%s sslrootcert=%s", datasource, db.TLS.CertFiles[0]) 224 } 225 226 if db.TLS.Client.CertFile != "" { 227 datasource = fmt.Sprintf("%s sslcert=%s", datasource, db.TLS.Client.CertFile) 228 } 229 230 if db.TLS.Client.KeyFile != "" { 231 datasource = fmt.Sprintf("%s sslkey=%s", datasource, db.TLS.Client.KeyFile) 232 } 233 234 sqldb, err := sql.Open(db.Type, datasource) 235 if err != nil { 236 return false 237 } 238 defer sqldb.Close() 239 240 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 241 defer cancel() 242 243 err = sqldb.PingContext(ctx) 244 if err != nil { 245 return false 246 } 247 248 return true 249 } 250 251 // ViperUnmarshal as this is what fabric-ca uses when it reads it's configuration 252 // file 253 func (ca *CA) ViperUnmarshal(configFile string) (*lib.ServerConfig, error) { 254 ca.Viper.SetConfigFile(configFile) 255 err := ca.Viper.ReadInConfig() 256 if err != nil { 257 return nil, errors.Wrapf(err, "viper unable to read in config: %s", configFile) 258 } 259 260 config := &lib.ServerConfig{} 261 err = ca.Viper.Unmarshal(config) 262 if err != nil { 263 return nil, errors.Wrap(err, "viper unable to unmarshal into server level config") 264 } 265 266 err = ca.Viper.Unmarshal(&config.CAcfg) 267 if err != nil { 268 return nil, errors.Wrap(err, "viper unable to unmarshal into CA level config") 269 } 270 271 return config, nil 272 } 273 274 func (ca *CA) ParseCrypto() (map[string][]byte, error) { 275 switch ca.Type { 276 case config.EnrollmentCA: 277 return ca.ParseEnrollmentCACrypto() 278 case config.TLSCA: 279 return ca.ParseTLSCACrypto() 280 } 281 282 return nil, fmt.Errorf("unsupported ca type '%s'", ca.Type) 283 } 284 285 func (ca *CA) ParseEnrollmentCACrypto() (map[string][]byte, error) { 286 serverConfig := ca.Config.GetServerConfig() 287 if serverConfig.TLS.IsEnabled() { 288 // TLS cert and key file must always be set. Operator should auto generate 289 // TLS cert and key if none are provided. 290 if serverConfig.TLS.CertFile == "" && serverConfig.TLS.KeyFile == "" { 291 return nil, errors.New("no TLS cert and key file provided") 292 } 293 } 294 295 if serverConfig.Operations.TLS.IsEnabled() { 296 // Same set of TLS certificate that are used for CA endpoint is also used for operations endpoint 297 serverConfig.Operations.TLS.CertFile = serverConfig.TLS.CertFile 298 serverConfig.Operations.TLS.KeyFile = serverConfig.TLS.KeyFile 299 } 300 301 crypto, err := ca.Config.ParseCABlock() 302 if err != nil { 303 return nil, errors.Wrap(err, "failed to parse ca block") 304 } 305 306 tlsCrypto, err := ca.Config.ParseTLSBlock() 307 if err != nil { 308 return nil, errors.Wrap(err, "failed to parse tls block") 309 } 310 crypto = util.JoinMaps(crypto, tlsCrypto) 311 312 dbCrypto, err := ca.Config.ParseDBBlock() 313 if err != nil { 314 return nil, errors.Wrap(err, "failed to parse db block") 315 } 316 crypto = util.JoinMaps(crypto, dbCrypto) 317 318 opsCrypto, err := ca.Config.ParseOperationsBlock() 319 if err != nil { 320 return nil, errors.Wrap(err, "failed to parse operations block") 321 } 322 crypto = util.JoinMaps(crypto, opsCrypto) 323 324 intCrypto, err := ca.Config.ParseIntermediateBlock() 325 if err != nil { 326 return nil, errors.Wrap(err, "failed to parse intermediate block") 327 } 328 crypto = util.JoinMaps(crypto, intCrypto) 329 330 return crypto, nil 331 } 332 333 func (ca *CA) ParseTLSCACrypto() (map[string][]byte, error) { 334 crypto, err := ca.ParseCABlock() 335 if err != nil { 336 return nil, errors.Wrap(err, "failed to parse ca block") 337 } 338 339 tlsCrypto, err := ca.Config.ParseTLSBlock() 340 if err != nil { 341 return nil, errors.Wrap(err, "failed to parse tls block") 342 } 343 crypto = util.JoinMaps(crypto, tlsCrypto) 344 345 dbCrypto, err := ca.Config.ParseDBBlock() 346 if err != nil { 347 return nil, errors.Wrap(err, "failed to parse db block") 348 } 349 crypto = util.JoinMaps(crypto, dbCrypto) 350 351 intCrypto, err := ca.Config.ParseIntermediateBlock() 352 if err != nil { 353 return nil, errors.Wrap(err, "failed to parse intermediate block") 354 } 355 crypto = util.JoinMaps(crypto, intCrypto) 356 357 return crypto, nil 358 } 359 360 func (ca *CA) ParseCABlock() (map[string][]byte, error) { 361 crypto, err := ca.Config.ParseCABlock() 362 if err != nil { 363 return nil, err 364 } 365 366 return crypto, nil 367 } 368 369 func (ca *CA) ConfigToBytes() ([]byte, error) { 370 371 bytes, err := yaml.Marshal(ca.Config.GetServerConfig()) 372 if err != nil { 373 return nil, err 374 } 375 376 return bytes, nil 377 } 378 379 func (ca *CA) SetMountPaths() { 380 ca.Config.SetMountPaths(ca.Type) 381 } 382 383 func (ca *CA) SetPKCS11Defaults(serverConfig *v1.ServerConfig) { 384 if serverConfig.CAConfig.CSP.PKCS11 == nil { 385 serverConfig.CAConfig.CSP.PKCS11 = &v1.PKCS11Opts{} 386 } 387 388 if ca.UsingHSMProxy { 389 serverConfig.CAConfig.CSP.PKCS11.Library = "/usr/local/lib/libpkcs11-proxy.so" 390 } 391 392 serverConfig.CAConfig.CSP.PKCS11.FileKeyStore.KeyStorePath = "msp/keystore" 393 394 if serverConfig.CAConfig.CSP.PKCS11.HashFamily == "" { 395 serverConfig.CAConfig.CSP.PKCS11.HashFamily = "SHA2" 396 } 397 398 if serverConfig.CAConfig.CSP.PKCS11.SecLevel == 0 { 399 serverConfig.CAConfig.CSP.PKCS11.SecLevel = 256 400 } 401 } 402 403 func (ca *CA) GetHomeDir() string { 404 return ca.Config.GetHomeDir() 405 } 406 407 func (ca *CA) GetServerConfig() *v1.ServerConfig { 408 return ca.Config.GetServerConfig() 409 } 410 411 func (ca *CA) RemoveHomeDir() error { 412 err := os.RemoveAll(ca.GetHomeDir()) 413 if err != nil { 414 return err 415 } 416 return nil 417 } 418 419 func (ca *CA) IsBeingUpdated() { 420 ca.Config.SetUpdate(true) 421 } 422 423 func (ca *CA) IsHSMEnabled() bool { 424 if ca.Config.UsingPKCS11() { 425 return true 426 } 427 return false 428 } 429 430 func (ca *CA) HealthCheck(parentURL, certPath string) error { 431 parsedURL, err := url.Parse(parentURL) 432 if err != nil { 433 return errors.Wrapf(err, "invalid CA url") 434 } 435 436 healthURL := getHealthCheckEndpoint(parsedURL) 437 log.Info(fmt.Sprintf("Health checking parent server, pinging %s", healthURL)) 438 439 // Make sure that parent server is running before trying to enroll 440 // intermediate CA. Retry 5 times for a total of 5 seconds to make 441 // sure parent server is up. If parent server is found, bail early 442 // and continue with enrollment 443 cert, err := ioutil.ReadFile(filepath.Clean(certPath)) 444 if err != nil { 445 return errors.Wrap(err, "failed to read TLS cert for intermediate enrollment") 446 } 447 448 for i := 0; i < 5; i++ { 449 err = util.HealthCheck(healthURL, cert, 30*time.Second) 450 if err != nil { 451 log.Info(fmt.Sprintf("Health check error: %s", err)) 452 time.Sleep(1 * time.Second) 453 log.Info("Health check failed, retrying") 454 continue 455 } 456 log.Info("Health check successfull") 457 break 458 } 459 460 return nil 461 } 462 463 func (ca *CA) GetType() config.Type { 464 return ca.Type 465 } 466 467 func getHealthCheckEndpoint(u *url.URL) string { 468 return fmt.Sprintf("%s://%s/cainfo", u.Scheme, u.Host) 469 } 470 471 func (ca *CA) setDefaults(serverConfig *v1.ServerConfig) { 472 serverConfig.CAConfig.Cfg.Identities.AllowRemove = pointer.True() 473 serverConfig.CAConfig.Cfg.Affiliations.AllowRemove = pointer.True() 474 // Ignore Certificate Expiry for re-enroll 475 serverConfig.CA.ReenrollIgnoreCertExpiry = pointer.True() 476 }