config.go 7.7 KB

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