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