github.com/hyperledger/fabric-ca@v2.0.0-alpha.0.20201120210307-7b4f34729db1+incompatible/cmd/fabric-ca-client/command/config.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package command 8 9 import ( 10 "fmt" 11 "io/ioutil" 12 "net/url" 13 "os" 14 "path/filepath" 15 "reflect" 16 "strings" 17 18 "github.com/cloudflare/cfssl/csr" 19 "github.com/cloudflare/cfssl/log" 20 "github.com/hyperledger/fabric-ca/internal/pkg/api" 21 calog "github.com/hyperledger/fabric-ca/internal/pkg/log" 22 "github.com/hyperledger/fabric-ca/internal/pkg/util" 23 "github.com/hyperledger/fabric-ca/lib" 24 "github.com/hyperledger/fabric-ca/lib/attr" 25 "github.com/pkg/errors" 26 ) 27 28 const ( 29 longName = "Hyperledger Fabric Certificate Authority Client" 30 shortName = "fabric-ca client" 31 cmdName = "fabric-ca-client" 32 envVarPrefix = "FABRIC_CA_CLIENT" 33 homeEnvVar = "FABRIC_CA_CLIENT_HOME" 34 ) 35 36 const ( 37 defaultCfgTemplate = ` 38 ############################################################################# 39 # This is a configuration file for the fabric-ca-client command. 40 # 41 # COMMAND LINE ARGUMENTS AND ENVIRONMENT VARIABLES 42 # ------------------------------------------------ 43 # Each configuration element can be overridden via command line 44 # arguments or environment variables. The precedence for determining 45 # the value of each element is as follows: 46 # 1) command line argument 47 # Examples: 48 # a) --url https://localhost:7054 49 # To set the fabric-ca server url 50 # b) --tls.client.certfile certfile.pem 51 # To set the client certificate for TLS 52 # 2) environment variable 53 # Examples: 54 # a) FABRIC_CA_CLIENT_URL=https://localhost:7054 55 # To set the fabric-ca server url 56 # b) FABRIC_CA_CLIENT_TLS_CLIENT_CERTFILE=certfile.pem 57 # To set the client certificate for TLS 58 # 3) configuration file 59 # 4) default value (if there is one) 60 # All default values are shown beside each element below. 61 # 62 # FILE NAME ELEMENTS 63 # ------------------ 64 # The value of all fields whose name ends with "file" or "files" are 65 # name or names of other files. 66 # For example, see "tls.certfiles" and "tls.client.certfile". 67 # The value of each of these fields can be a simple filename, a 68 # relative path, or an absolute path. If the value is not an 69 # absolute path, it is interpretted as being relative to the location 70 # of this configuration file. 71 # 72 ############################################################################# 73 74 ############################################################################# 75 # Client Configuration 76 ############################################################################# 77 78 # URL of the Fabric-ca-server (default: http://localhost:7054) 79 url: <<<URL>>> 80 81 # Membership Service Provider (MSP) directory 82 # This is useful when the client is used to enroll a peer or orderer, so 83 # that the enrollment artifacts are stored in the format expected by MSP. 84 mspdir: <<<MSPDIR>>> 85 86 ############################################################################# 87 # TLS section for secure socket connection 88 # 89 # certfiles - PEM-encoded list of trusted root certificate files 90 # client: 91 # certfile - PEM-encoded certificate file for when client authentication 92 # is enabled on server 93 # keyfile - PEM-encoded key file for when client authentication 94 # is enabled on server 95 ############################################################################# 96 tls: 97 # TLS section for secure socket connection 98 certfiles: 99 client: 100 certfile: 101 keyfile: 102 103 ############################################################################# 104 # Certificate Signing Request section for generating the CSR for an 105 # enrollment certificate (ECert) 106 # 107 # cn - Used by CAs to determine which domain the certificate is to be generated for 108 # 109 # keyrequest - Properties to use when generating a private key. 110 # algo - key generation algorithm to use 111 # size - size of key to generate 112 # reusekey - reuse existing key during reenrollment 113 # 114 # serialnumber - The serialnumber field, if specified, becomes part of the issued 115 # certificate's DN (Distinguished Name). For example, one use case for this is 116 # a company with its own CA (Certificate Authority) which issues certificates 117 # to its employees and wants to include the employee's serial number in the DN 118 # of its issued certificates. 119 # WARNING: The serialnumber field should not be confused with the certificate's 120 # serial number which is set by the CA but is not a component of the 121 # certificate's DN. 122 # 123 # names - A list of name objects. Each name object should contain at least one 124 # "C", "L", "O", or "ST" value (or any combination of these) where these 125 # are abbreviations for the following: 126 # "C": country 127 # "L": locality or municipality (such as city or town name) 128 # "O": organization 129 # "OU": organizational unit, such as the department responsible for owning the key; 130 # it can also be used for a "Doing Business As" (DBS) name 131 # "ST": the state or province 132 # 133 # Note that the "OU" or organizational units of an ECert are always set according 134 # to the values of the identities type and affiliation. OUs are calculated for an enroll 135 # as OU=<type>, OU=<affiliationRoot>, ..., OU=<affiliationLeaf>. For example, an identity 136 # of type "client" with an affiliation of "org1.dept2.team3" would have the following 137 # organizational units: OU=client, OU=org1, OU=dept2, OU=team3 138 # 139 # hosts - A list of host names for which the certificate should be valid 140 # 141 ############################################################################# 142 csr: 143 cn: <<<ENROLLMENT_ID>>> 144 keyrequest: 145 algo: ecdsa 146 size: 256 147 reusekey: false 148 serialnumber: 149 names: 150 - C: US 151 ST: North Carolina 152 L: 153 O: Hyperledger 154 OU: Fabric 155 hosts: 156 - <<<MYHOST>>> 157 158 ############################################################################# 159 # Registration section used to register a new identity with fabric-ca server 160 # 161 # name - Unique name of the identity 162 # type - Type of identity being registered (e.g. 'peer, app, user') 163 # affiliation - The identity's affiliation 164 # maxenrollments - The maximum number of times the secret can be reused to enroll. 165 # Specially, -1 means unlimited; 0 means to use CA's max enrollment 166 # value. 167 # attributes - List of name/value pairs of attribute for identity 168 ############################################################################# 169 id: 170 name: 171 type: 172 affiliation: 173 maxenrollments: 0 174 attributes: 175 # - name: 176 # value: 177 178 ############################################################################# 179 # Enrollment section used to enroll an identity with fabric-ca server 180 # 181 # profile - Name of the signing profile to use in issuing the certificate 182 # label - Label to use in HSM operations 183 ############################################################################# 184 enrollment: 185 profile: 186 label: 187 188 ############################################################################# 189 # Name of the CA to connect to within the fabric-ca server 190 ############################################################################# 191 caname: 192 193 ############################################################################# 194 # BCCSP (BlockChain Crypto Service Provider) section allows to select which 195 # crypto implementation library to use 196 ############################################################################# 197 bccsp: 198 default: SW 199 sw: 200 hash: SHA2 201 security: 256 202 filekeystore: 203 # The directory used for the software file-based keystore 204 keystore: msp/keystore 205 ` 206 ) 207 208 // ConfigInit initializes the configuration for the fabric-ca-client command 209 func (c *ClientCmd) ConfigInit() error { 210 var err error 211 212 c.myViper.AutomaticEnv() // read in environment variables that match 213 logLevel := c.myViper.GetString("loglevel") 214 debug := c.myViper.GetBool("debug") 215 216 // If log level has been set via the new loglevel property use that as the loglevel 217 // and override any default log levels defined for the commands 218 if logLevel != "" { 219 c.logLevel = logLevel 220 } 221 calog.SetLogLevel(c.logLevel, debug) 222 if err != nil { 223 return err 224 } 225 226 c.cfgFileName, c.homeDirectory, err = util.ValidateAndReturnAbsConf(c.cfgFileName, c.homeDirectory, cmdName) 227 if err != nil { 228 return err 229 } 230 231 log.Debugf("Home directory: %s", c.homeDirectory) 232 233 // Set configuration file name for viper and configure it to read env variables 234 c.myViper.SetConfigFile(c.cfgFileName) 235 236 // If the config file doesn't exist, create a default one if enroll 237 // command being executed. Enroll should be the first command to be 238 // executed, and furthermore the default configuration file requires 239 // enrollment ID to populate CN field which is something the enroll 240 // command requires 241 if c.shouldCreateDefaultConfig() { 242 if !util.FileExists(c.cfgFileName) { 243 err = c.createDefaultConfigFile() 244 if err != nil { 245 return errors.WithMessage(err, "Failed to create default configuration file") 246 } 247 log.Infof("Created a default configuration file at %s", c.cfgFileName) 248 } 249 } else { 250 log.Infof("Configuration file location: %s", c.cfgFileName) 251 } 252 253 // Call viper to read the config 254 if util.FileExists(c.cfgFileName) { 255 err = c.myViper.ReadInConfig() 256 if err != nil { 257 return errors.Wrapf(err, "Failed to read config file at '%s'", c.cfgFileName) 258 } 259 } 260 261 err = c.myViper.Unmarshal(c.clientCfg) 262 if err != nil { 263 return errors.Wrapf(err, "Incorrect format in file '%s'", c.cfgFileName) 264 } 265 266 // If the CSR is not for a CA, set the CA pointer to nil 267 if c.clientCfg.CSR.CA != nil && c.clientCfg.CSR.CA.PathLength == 0 && !c.clientCfg.CSR.CA.PathLenZero { 268 c.clientCfg.CSR.CA = nil 269 } 270 271 purl, err := url.Parse(c.clientCfg.URL) 272 if err != nil { 273 return err 274 } 275 276 c.clientCfg.TLS.Enabled = purl.Scheme == "https" 277 278 err = processAttributes(c.cfgAttrs, c.clientCfg) 279 if err != nil { 280 return err 281 } 282 283 err = processAttributeRequests(c.cfgAttrReqs, c.clientCfg) 284 if err != nil { 285 return err 286 } 287 288 err = c.processCsrNames() 289 if err != nil { 290 return err 291 } 292 293 // Check for separaters and insert values back into slice 294 normalizeStringSlices(c.clientCfg) 295 296 // Commands other than 'enroll' and 'getcacert' require that client already 297 // be enrolled 298 if c.requiresEnrollment() { 299 err = checkForEnrollment(c.cfgFileName, c.clientCfg) 300 if err != nil { 301 return err 302 } 303 } 304 305 return nil 306 } 307 308 func (c *ClientCmd) createDefaultConfigFile() error { 309 // Create a default config, if URL provided via CLI or envar update config files 310 var cfg string 311 fabricCAServerURL := c.myViper.GetString("url") 312 if fabricCAServerURL == "" { 313 fabricCAServerURL = util.GetServerURL() 314 } else { 315 URL, err := url.Parse(fabricCAServerURL) 316 if err != nil { 317 return errors.Wrapf(err, "Failed to parse URL '%s'", fabricCAServerURL) 318 } 319 fabricCAServerURL = fmt.Sprintf("%s://%s", URL.Scheme, URL.Host) 320 } 321 322 myhost := c.myViper.GetString("myhost") 323 324 // Do string subtitution to get the default config 325 cfg = strings.Replace(defaultCfgTemplate, "<<<URL>>>", fabricCAServerURL, 1) 326 cfg = strings.Replace(cfg, "<<<MYHOST>>>", myhost, 1) 327 cfg = strings.Replace(cfg, "<<<MSPDIR>>>", c.clientCfg.MSPDir, 1) 328 329 user := "" 330 var err error 331 if c.requiresUser() { 332 user, _, err = util.GetUser(c.myViper) 333 if err != nil { 334 return err 335 } 336 } 337 cfg = strings.Replace(cfg, "<<<ENROLLMENT_ID>>>", user, 1) 338 339 // Create the directory if necessary 340 err = os.MkdirAll(c.homeDirectory, 0755) 341 if err != nil { 342 return errors.Wrapf(err, "Failed to create directory at '%s'", c.homeDirectory) 343 } 344 345 // Now write the file 346 return ioutil.WriteFile(c.cfgFileName, []byte(cfg), 0755) 347 } 348 349 // processAttributes parses attributes from command line or env variable 350 func processAttributes(cfgAttrs []string, cfg *lib.ClientConfig) error { 351 if cfgAttrs != nil { 352 attrMap := make(map[string]string) 353 for _, attr := range cfgAttrs { 354 // skipping empty attributes 355 if len(attr) == 0 { 356 continue 357 } 358 sattr := strings.SplitN(attr, "=", 2) 359 if len(sattr) != 2 { 360 return errors.Errorf("Attribute '%s' is missing '=' ; it "+ 361 "must be of the form <name>=<value>", attr) 362 } 363 attrMap[sattr[0]] = sattr[1] 364 } 365 var err error 366 cfg.ID.Attributes, err = attr.ConvertAttrs(attrMap) 367 if err != nil { 368 return err 369 } 370 } 371 return nil 372 } 373 374 // processAttributeRequests parses attribute requests from command line or env variable 375 // Each string is of the form: <attrName>[:opt] where "opt" means the attribute is 376 // optional and will not return an error if the identity does not possess the attribute. 377 // The default is that each attribute name listed is required and so the identity must 378 // possess the attribute. 379 func processAttributeRequests(cfgAttrReqs []string, cfg *lib.ClientConfig) error { 380 if len(cfgAttrReqs) == 0 { 381 return nil 382 } 383 reqs := make([]*api.AttributeRequest, len(cfgAttrReqs)) 384 for idx, req := range cfgAttrReqs { 385 sreq := strings.Split(req, ":") 386 name := sreq[0] 387 switch len(sreq) { 388 case 1: 389 reqs[idx] = &api.AttributeRequest{Name: name} 390 case 2: 391 if sreq[1] != "opt" { 392 return errors.Errorf("Invalid option in attribute request specification at '%s'; the value after the colon must be 'opt'", req) 393 } 394 reqs[idx] = &api.AttributeRequest{Name: name, Optional: true} 395 default: 396 return errors.Errorf("Multiple ':' characters not allowed in attribute request specification; error at '%s'", req) 397 } 398 } 399 cfg.Enrollment.AttrReqs = reqs 400 return nil 401 } 402 403 // processAttributes parses attributes from command line or env variable 404 func (c *ClientCmd) processCsrNames() error { 405 if c.cfgCsrNames != nil { 406 c.clientCfg.CSR.Names = make([]csr.Name, len(c.cfgCsrNames)) 407 for idx, name := range c.cfgCsrNames { 408 sname := strings.SplitN(name, "=", 2) 409 if len(sname) != 2 { 410 return errors.Errorf("CSR name/value '%s' is missing '=' ; it must be of the form <name>=<value>", name) 411 } 412 v := reflect.ValueOf(&c.clientCfg.CSR.Names[idx]).Elem().FieldByName(sname[0]) 413 if v.IsValid() { 414 v.SetString(sname[1]) 415 } else { 416 return errors.Errorf("Invalid CSR name: '%s'", sname[0]) 417 } 418 } 419 } 420 return nil 421 } 422 423 // GetHomeDirectory returns the client's home directory 424 func (c *ClientCmd) GetHomeDirectory() string { 425 return c.homeDirectory 426 } 427 428 func checkForEnrollment(cfgFileName string, cfg *lib.ClientConfig) error { 429 log.Debug("Checking for enrollment") 430 client := lib.Client{ 431 HomeDir: filepath.Dir(cfgFileName), 432 Config: cfg, 433 } 434 return client.CheckEnrollment() 435 } 436 437 func normalizeStringSlices(cfg *lib.ClientConfig) { 438 fields := []*[]string{ 439 &cfg.CSR.Hosts, 440 &cfg.TLS.CertFiles, 441 } 442 for _, namePtr := range fields { 443 norm := util.NormalizeStringSlice(*namePtr) 444 *namePtr = norm 445 } 446 }