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  }