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  }