github.com/canhui/fabric_ca2_2@v2.0.0-alpha+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/api"
    21  	"github.com/hyperledger/fabric-ca/lib"
    22  	"github.com/hyperledger/fabric-ca/lib/attr"
    23  	calog "github.com/hyperledger/fabric-ca/lib/common/log"
    24  	"github.com/hyperledger/fabric-ca/util"
    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  #  serialnumber - The serialnumber field, if specified, becomes part of the issued
   110  #     certificate's DN (Distinguished Name).  For example, one use case for this is
   111  #     a company with its own CA (Certificate Authority) which issues certificates
   112  #     to its employees and wants to include the employee's serial number in the DN
   113  #     of its issued certificates.
   114  #     WARNING: The serialnumber field should not be confused with the certificate's
   115  #     serial number which is set by the CA but is not a component of the
   116  #     certificate's DN.
   117  #
   118  #  names -  A list of name objects. Each name object should contain at least one
   119  #    "C", "L", "O", or "ST" value (or any combination of these) where these
   120  #    are abbreviations for the following:
   121  #        "C": country
   122  #        "L": locality or municipality (such as city or town name)
   123  #        "O": organization
   124  #        "OU": organizational unit, such as the department responsible for owning the key;
   125  #         it can also be used for a "Doing Business As" (DBS) name
   126  #        "ST": the state or province
   127  #
   128  #    Note that the "OU" or organizational units of an ECert are always set according
   129  #    to the values of the identities type and affiliation. OUs are calculated for an enroll
   130  #    as OU=<type>, OU=<affiliationRoot>, ..., OU=<affiliationLeaf>. For example, an identity
   131  #    of type "client" with an affiliation of "org1.dept2.team3" would have the following
   132  #    organizational units: OU=client, OU=org1, OU=dept2, OU=team3
   133  #
   134  #  hosts - A list of host names for which the certificate should be valid
   135  #
   136  #############################################################################
   137  csr:
   138    cn: <<<ENROLLMENT_ID>>>
   139    keyrequest:
   140      algo: ecdsa
   141      size: 256
   142    serialnumber:
   143    names:
   144      - C: US
   145        ST: North Carolina
   146        L:
   147        O: Hyperledger
   148        OU: Fabric
   149    hosts:
   150      - <<<MYHOST>>>
   151  
   152  #############################################################################
   153  #  Registration section used to register a new identity with fabric-ca server
   154  #
   155  #  name - Unique name of the identity
   156  #  type - Type of identity being registered (e.g. 'peer, app, user')
   157  #  affiliation - The identity's affiliation
   158  #  maxenrollments - The maximum number of times the secret can be reused to enroll.
   159  #                   Specially, -1 means unlimited; 0 means to use CA's max enrollment
   160  #                   value.
   161  #  attributes - List of name/value pairs of attribute for identity
   162  #############################################################################
   163  id:
   164    name:
   165    type:
   166    affiliation:
   167    maxenrollments: 0
   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  // ConfigInit initializes the configuration for the fabric-ca-client command
   203  func (c *ClientCmd) ConfigInit() error {
   204  	var err error
   205  
   206  	c.myViper.AutomaticEnv() // read in environment variables that match
   207  	logLevel := c.myViper.GetString("loglevel")
   208  	debug := c.myViper.GetBool("debug")
   209  
   210  	// If log level has been set via the new loglevel property use that as the loglevel
   211  	// and override any default log levels defined for the commands
   212  	if logLevel != "" {
   213  		c.logLevel = logLevel
   214  	}
   215  	calog.SetLogLevel(c.logLevel, debug)
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	c.cfgFileName, c.homeDirectory, err = util.ValidateAndReturnAbsConf(c.cfgFileName, c.homeDirectory, cmdName)
   221  	if err != nil {
   222  		return err
   223  	}
   224  
   225  	log.Debugf("Home directory: %s", c.homeDirectory)
   226  
   227  	// Set configuration file name for viper and configure it to read env variables
   228  	c.myViper.SetConfigFile(c.cfgFileName)
   229  
   230  	// If the config file doesn't exist, create a default one if enroll
   231  	// command being executed. Enroll should be the first command to be
   232  	// executed, and furthermore the default configuration file requires
   233  	// enrollment ID to populate CN field which is something the enroll
   234  	// command requires
   235  	if c.shouldCreateDefaultConfig() {
   236  		if !util.FileExists(c.cfgFileName) {
   237  			err = c.createDefaultConfigFile()
   238  			if err != nil {
   239  				return errors.WithMessage(err, "Failed to create default configuration file")
   240  			}
   241  			log.Infof("Created a default configuration file at %s", c.cfgFileName)
   242  		}
   243  	} else {
   244  		log.Infof("Configuration file location: %s", c.cfgFileName)
   245  	}
   246  
   247  	// Call viper to read the config
   248  	if util.FileExists(c.cfgFileName) {
   249  		err = c.myViper.ReadInConfig()
   250  		if err != nil {
   251  			return errors.Wrapf(err, "Failed to read config file at '%s'", c.cfgFileName)
   252  		}
   253  	}
   254  
   255  	err = c.myViper.Unmarshal(c.clientCfg)
   256  	if err != nil {
   257  		return errors.Wrapf(err, "Incorrect format in file '%s'", c.cfgFileName)
   258  	}
   259  
   260  	// If the CSR is not for a CA, set the CA pointer to nil
   261  	if c.clientCfg.CSR.CA != nil && c.clientCfg.CSR.CA.PathLength == 0 && !c.clientCfg.CSR.CA.PathLenZero {
   262  		c.clientCfg.CSR.CA = nil
   263  	}
   264  
   265  	purl, err := url.Parse(c.clientCfg.URL)
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	c.clientCfg.TLS.Enabled = purl.Scheme == "https"
   271  
   272  	err = processAttributes(c.cfgAttrs, c.clientCfg)
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	err = processAttributeRequests(c.cfgAttrReqs, c.clientCfg)
   278  	if err != nil {
   279  		return err
   280  	}
   281  
   282  	err = c.processCsrNames()
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	// Check for separaters and insert values back into slice
   288  	normalizeStringSlices(c.clientCfg)
   289  
   290  	// Commands other than 'enroll' and 'getcacert' require that client already
   291  	// be enrolled
   292  	if c.requiresEnrollment() {
   293  		err = checkForEnrollment(c.cfgFileName, c.clientCfg)
   294  		if err != nil {
   295  			return err
   296  		}
   297  	}
   298  
   299  	return nil
   300  }
   301  
   302  func (c *ClientCmd) createDefaultConfigFile() error {
   303  	// Create a default config, if URL provided via CLI or envar update config files
   304  	var cfg string
   305  	fabricCAServerURL := c.myViper.GetString("url")
   306  	if fabricCAServerURL == "" {
   307  		fabricCAServerURL = util.GetServerURL()
   308  	} else {
   309  		URL, err := url.Parse(fabricCAServerURL)
   310  		if err != nil {
   311  			return errors.Wrapf(err, "Failed to parse URL '%s'", fabricCAServerURL)
   312  		}
   313  		fabricCAServerURL = fmt.Sprintf("%s://%s", URL.Scheme, URL.Host)
   314  	}
   315  
   316  	myhost := c.myViper.GetString("myhost")
   317  
   318  	// Do string subtitution to get the default config
   319  	cfg = strings.Replace(defaultCfgTemplate, "<<<URL>>>", fabricCAServerURL, 1)
   320  	cfg = strings.Replace(cfg, "<<<MYHOST>>>", myhost, 1)
   321  	cfg = strings.Replace(cfg, "<<<MSPDIR>>>", c.clientCfg.MSPDir, 1)
   322  
   323  	user := ""
   324  	var err error
   325  	if c.requiresUser() {
   326  		user, _, err = util.GetUser(c.myViper)
   327  		if err != nil {
   328  			return err
   329  		}
   330  	}
   331  	cfg = strings.Replace(cfg, "<<<ENROLLMENT_ID>>>", user, 1)
   332  
   333  	// Create the directory if necessary
   334  	err = os.MkdirAll(c.homeDirectory, 0755)
   335  	if err != nil {
   336  		return errors.Wrapf(err, "Failed to create directory at '%s'", c.homeDirectory)
   337  	}
   338  
   339  	// Now write the file
   340  	return ioutil.WriteFile(c.cfgFileName, []byte(cfg), 0755)
   341  }
   342  
   343  // processAttributes parses attributes from command line or env variable
   344  func processAttributes(cfgAttrs []string, cfg *lib.ClientConfig) error {
   345  	if cfgAttrs != nil {
   346  		attrMap := make(map[string]string)
   347  		for _, attr := range cfgAttrs {
   348  			// skipping empty attributes
   349  			if len(attr) == 0 {
   350  				continue
   351  			}
   352  			sattr := strings.SplitN(attr, "=", 2)
   353  			if len(sattr) != 2 {
   354  				return errors.Errorf("Attribute '%s' is missing '=' ; it "+
   355  					"must be of the form <name>=<value>", attr)
   356  			}
   357  			attrMap[sattr[0]] = sattr[1]
   358  		}
   359  		var err error
   360  		cfg.ID.Attributes, err = attr.ConvertAttrs(attrMap)
   361  		if err != nil {
   362  			return err
   363  		}
   364  	}
   365  	return nil
   366  }
   367  
   368  // processAttributeRequests parses attribute requests from command line or env variable
   369  // Each string is of the form: <attrName>[:opt] where "opt" means the attribute is
   370  // optional and will not return an error if the identity does not possess the attribute.
   371  // The default is that each attribute name listed is required and so the identity must
   372  // possess the attribute.
   373  func processAttributeRequests(cfgAttrReqs []string, cfg *lib.ClientConfig) error {
   374  	if len(cfgAttrReqs) == 0 {
   375  		return nil
   376  	}
   377  	reqs := make([]*api.AttributeRequest, len(cfgAttrReqs))
   378  	for idx, req := range cfgAttrReqs {
   379  		sreq := strings.Split(req, ":")
   380  		name := sreq[0]
   381  		switch len(sreq) {
   382  		case 1:
   383  			reqs[idx] = &api.AttributeRequest{Name: name}
   384  		case 2:
   385  			if sreq[1] != "opt" {
   386  				return errors.Errorf("Invalid option in attribute request specification at '%s'; the value after the colon must be 'opt'", req)
   387  			}
   388  			reqs[idx] = &api.AttributeRequest{Name: name, Optional: true}
   389  		default:
   390  			return errors.Errorf("Multiple ':' characters not allowed in attribute request specification; error at '%s'", req)
   391  		}
   392  	}
   393  	cfg.Enrollment.AttrReqs = reqs
   394  	return nil
   395  }
   396  
   397  // processAttributes parses attributes from command line or env variable
   398  func (c *ClientCmd) processCsrNames() error {
   399  	if c.cfgCsrNames != nil {
   400  		c.clientCfg.CSR.Names = make([]csr.Name, len(c.cfgCsrNames))
   401  		for idx, name := range c.cfgCsrNames {
   402  			sname := strings.SplitN(name, "=", 2)
   403  			if len(sname) != 2 {
   404  				return errors.Errorf("CSR name/value '%s' is missing '=' ; it must be of the form <name>=<value>", name)
   405  			}
   406  			v := reflect.ValueOf(&c.clientCfg.CSR.Names[idx]).Elem().FieldByName(sname[0])
   407  			if v.IsValid() {
   408  				v.SetString(sname[1])
   409  			} else {
   410  				return errors.Errorf("Invalid CSR name: '%s'", sname[0])
   411  			}
   412  		}
   413  	}
   414  	return nil
   415  }
   416  
   417  // GetHomeDirectory returns the client's home directory
   418  func (c *ClientCmd) GetHomeDirectory() string {
   419  	return c.homeDirectory
   420  }
   421  
   422  func checkForEnrollment(cfgFileName string, cfg *lib.ClientConfig) error {
   423  	log.Debug("Checking for enrollment")
   424  	client := lib.Client{
   425  		HomeDir: filepath.Dir(cfgFileName),
   426  		Config:  cfg,
   427  	}
   428  	return client.CheckEnrollment()
   429  }
   430  
   431  func normalizeStringSlices(cfg *lib.ClientConfig) {
   432  	fields := []*[]string{
   433  		&cfg.CSR.Hosts,
   434  		&cfg.TLS.CertFiles,
   435  	}
   436  	for _, namePtr := range fields {
   437  		norm := util.NormalizeStringSlice(*namePtr)
   438  		*namePtr = norm
   439  	}
   440  }