config.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. // Package config contains the multi-root configuration file parser.
  2. package config
  3. import (
  4. "bufio"
  5. "crypto"
  6. "crypto/x509"
  7. "errors"
  8. "fmt"
  9. "io/ioutil"
  10. "net"
  11. "net/url"
  12. "os"
  13. "path/filepath"
  14. "regexp"
  15. "strings"
  16. "github.com/cloudflare/cfssl/certdb/dbconf"
  17. "github.com/cloudflare/cfssl/config"
  18. "github.com/cloudflare/cfssl/helpers"
  19. "github.com/cloudflare/cfssl/helpers/derhelpers"
  20. "github.com/cloudflare/cfssl/log"
  21. "github.com/cloudflare/cfssl/whitelist"
  22. "github.com/cloudflare/redoctober/client"
  23. "github.com/cloudflare/redoctober/core"
  24. "github.com/jmoiron/sqlx"
  25. )
  26. // RawMap is shorthand for the type used as a map from string to raw Root struct.
  27. type RawMap map[string]map[string]string
  28. var (
  29. configSection = regexp.MustCompile("^\\s*\\[\\s*(\\w+)\\s*\\]\\s*$")
  30. quotedConfigLine = regexp.MustCompile("^\\s*(\\w+)\\s*=\\s*[\"'](.*)[\"']\\s*$")
  31. configLine = regexp.MustCompile("^\\s*(\\w+)\\s*=\\s*(.*)\\s*$")
  32. commentLine = regexp.MustCompile("^#.*$")
  33. blankLine = regexp.MustCompile("^\\s*$")
  34. defaultSection = "default"
  35. )
  36. // ParseToRawMap takes the filename as a string and returns a RawMap.
  37. func ParseToRawMap(fileName string) (cfg RawMap, err error) {
  38. var file *os.File
  39. cfg = make(RawMap, 0)
  40. file, err = os.Open(fileName)
  41. if err != nil {
  42. return
  43. }
  44. defer file.Close()
  45. scanner := bufio.NewScanner(file)
  46. var currentSection string
  47. for scanner.Scan() {
  48. line := scanner.Text()
  49. if commentLine.MatchString(line) {
  50. continue
  51. } else if blankLine.MatchString(line) {
  52. continue
  53. } else if configSection.MatchString(line) {
  54. section := configSection.ReplaceAllString(line, "$1")
  55. if !cfg.SectionInConfig(section) {
  56. cfg[section] = make(map[string]string, 0)
  57. }
  58. currentSection = section
  59. } else if configLine.MatchString(line) {
  60. regex := configLine
  61. if quotedConfigLine.MatchString(line) {
  62. regex = quotedConfigLine
  63. }
  64. if currentSection == "" {
  65. currentSection = defaultSection
  66. if !cfg.SectionInConfig(currentSection) {
  67. cfg[currentSection] = make(map[string]string, 0)
  68. }
  69. }
  70. key := regex.ReplaceAllString(line, "$1")
  71. val := regex.ReplaceAllString(line, "$2")
  72. cfg[currentSection][key] = val
  73. } else {
  74. err = fmt.Errorf("invalid config file")
  75. break
  76. }
  77. }
  78. return
  79. }
  80. // SectionInConfig determines whether a section is in the configuration.
  81. func (c *RawMap) SectionInConfig(section string) bool {
  82. for s := range *c {
  83. if section == s {
  84. return true
  85. }
  86. }
  87. return false
  88. }
  89. // A Root represents a single certificate authority root key pair.
  90. type Root struct {
  91. PrivateKey crypto.Signer
  92. Certificate *x509.Certificate
  93. Config *config.Signing
  94. ACL whitelist.NetACL
  95. DB *sqlx.DB
  96. }
  97. // LoadRoot parses a config structure into a Root structure
  98. func LoadRoot(cfg map[string]string) (*Root, error) {
  99. var root Root
  100. var err error
  101. spec, ok := cfg["private"]
  102. if !ok {
  103. return nil, ErrMissingPrivateKey
  104. }
  105. certPath, ok := cfg["certificate"]
  106. if !ok {
  107. return nil, ErrMissingCertificatePath
  108. }
  109. configPath, ok := cfg["config"]
  110. if !ok {
  111. return nil, ErrMissingConfigPath
  112. }
  113. root.PrivateKey, err = parsePrivateKeySpec(spec, cfg)
  114. if err != nil {
  115. return nil, err
  116. }
  117. in, err := ioutil.ReadFile(certPath)
  118. if err != nil {
  119. return nil, err
  120. }
  121. root.Certificate, err = helpers.ParseCertificatePEM(in)
  122. if err != nil {
  123. return nil, err
  124. }
  125. conf, err := config.LoadFile(configPath)
  126. if err != nil {
  127. return nil, err
  128. }
  129. root.Config = conf.Signing
  130. nets := cfg["nets"]
  131. if nets != "" {
  132. root.ACL, err = parseACL(nets)
  133. if err != nil {
  134. return nil, err
  135. }
  136. }
  137. dbConfig := cfg["dbconfig"]
  138. if dbConfig != "" {
  139. db, err := dbconf.DBFromConfig(dbConfig)
  140. if err != nil {
  141. return nil, err
  142. }
  143. root.DB = db
  144. }
  145. return &root, nil
  146. }
  147. func parsePrivateKeySpec(spec string, cfg map[string]string) (crypto.Signer, error) {
  148. specURL, err := url.Parse(spec)
  149. if err != nil {
  150. return nil, err
  151. }
  152. var priv crypto.Signer
  153. switch specURL.Scheme {
  154. case "file":
  155. // A file spec will be parsed such that the root
  156. // directory of a relative path will be stored as the
  157. // hostname, and the remainder of the file's path is
  158. // stored in the Path field.
  159. log.Debug("loading private key file", specURL.Path)
  160. path := filepath.Join(specURL.Host, specURL.Path)
  161. in, err := ioutil.ReadFile(path)
  162. if err != nil {
  163. return nil, err
  164. }
  165. log.Debug("attempting to load PEM-encoded private key")
  166. priv, err = helpers.ParsePrivateKeyPEM(in)
  167. if err != nil {
  168. log.Debug("file is not a PEM-encoded private key")
  169. log.Debug("attempting to load DER-encoded private key")
  170. priv, err = derhelpers.ParsePrivateKeyDER(in)
  171. if err != nil {
  172. return nil, err
  173. }
  174. }
  175. log.Debug("loaded private key")
  176. return priv, nil
  177. case "rofile":
  178. log.Warning("Red October support is currently experimental")
  179. path := filepath.Join(specURL.Host, specURL.Path)
  180. in, err := ioutil.ReadFile(path)
  181. if err != nil {
  182. return nil, err
  183. }
  184. roServer := cfg["ro_server"]
  185. if roServer == "" {
  186. return nil, errors.New("config: no RedOctober server available")
  187. }
  188. // roCAPath can be empty; if it is, the client uses
  189. // the system default CA roots.
  190. roCAPath := cfg["ro_ca"]
  191. roUser := cfg["ro_user"]
  192. if roUser == "" {
  193. return nil, errors.New("config: no RedOctober user available")
  194. }
  195. roPass := cfg["ro_pass"]
  196. if roPass == "" {
  197. return nil, errors.New("config: no RedOctober passphrase available")
  198. }
  199. log.Debug("decrypting key via RedOctober Server")
  200. roClient, err := client.NewRemoteServer(roServer, roCAPath)
  201. if err != nil {
  202. return nil, err
  203. }
  204. req := core.DecryptRequest{
  205. Name: roUser,
  206. Password: roPass,
  207. Data: in,
  208. }
  209. in, err = roClient.DecryptIntoData(req)
  210. if err != nil {
  211. return nil, err
  212. }
  213. log.Debug("attempting to load PEM-encoded private key")
  214. priv, err = helpers.ParsePrivateKeyPEM(in)
  215. if err != nil {
  216. log.Debug("file is not a PEM-encoded private key")
  217. log.Debug("attempting to load DER-encoded private key")
  218. priv, err = derhelpers.ParsePrivateKeyDER(in)
  219. if err != nil {
  220. return nil, err
  221. }
  222. }
  223. log.Debug("loaded private key")
  224. return priv, nil
  225. default:
  226. return nil, ErrUnsupportedScheme
  227. }
  228. }
  229. func parseACL(nets string) (whitelist.NetACL, error) {
  230. wl := whitelist.NewBasicNet()
  231. netList := strings.Split(nets, ",")
  232. for i := range netList {
  233. netList[i] = strings.TrimSpace(netList[i])
  234. _, n, err := net.ParseCIDR(netList[i])
  235. if err != nil {
  236. return nil, err
  237. }
  238. wl.Add(n)
  239. }
  240. return wl, nil
  241. }
  242. // A RootList associates a set of labels with the appropriate private
  243. // keys and their certificates.
  244. type RootList map[string]*Root
  245. var (
  246. // ErrMissingPrivateKey indicates that the configuration is
  247. // missing a private key specifier.
  248. ErrMissingPrivateKey = errors.New("config: root is missing private key spec")
  249. // ErrMissingCertificatePath indicates that the configuration
  250. // is missing a certificate specifier.
  251. ErrMissingCertificatePath = errors.New("config: root is missing certificate path")
  252. // ErrMissingConfigPath indicates that the configuration lacks
  253. // a valid CFSSL configuration.
  254. ErrMissingConfigPath = errors.New("config: root is missing configuration file path")
  255. // ErrInvalidConfig indicates the configuration is invalid.
  256. ErrInvalidConfig = errors.New("config: invalid configuration")
  257. // ErrUnsupportedScheme indicates a private key scheme that is not currently supported.
  258. ErrUnsupportedScheme = errors.New("config: unsupported private key scheme")
  259. )
  260. // Parse loads a RootList from a file.
  261. func Parse(filename string) (RootList, error) {
  262. cfgMap, err := ParseToRawMap(filename)
  263. if err != nil {
  264. return nil, err
  265. }
  266. var rootList = RootList{}
  267. for label, entries := range cfgMap {
  268. root, err := LoadRoot(entries)
  269. if err != nil {
  270. return nil, err
  271. }
  272. rootList[label] = root
  273. }
  274. return rootList, nil
  275. }