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