certlist.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package certs
  15. import (
  16. "crypto"
  17. "crypto/x509"
  18. "fmt"
  19. "path/filepath"
  20. "yunion.io/x/pkg/errors"
  21. "yunion.io/x/onecloud/pkg/util/fileutils2"
  22. certutil "yunion.io/x/onecloud/pkg/util/tls/cert"
  23. pkiutil "yunion.io/x/onecloud/pkg/util/tls/pki"
  24. )
  25. type configMutatorsFunc func(*certutil.Config) error
  26. // QemuCert represents a cretificate that qemu required.
  27. type QemuCert struct {
  28. Name string
  29. LongName string
  30. BaseName string
  31. CAName string
  32. configMutators []configMutatorsFunc
  33. config certutil.Config
  34. }
  35. // GetConfig returns the definition for the given cert.
  36. func (k *QemuCert) GetConfig() (*certutil.Config, error) {
  37. for _, f := range k.configMutators {
  38. if err := f(&k.config); err != nil {
  39. return nil, err
  40. }
  41. }
  42. return &k.config, nil
  43. }
  44. // CreateFromCA makes and writes a certificate using the given CA cert and key.
  45. func (k *QemuCert) CreateFromCA(dir string, caCert *x509.Certificate, caKey crypto.Signer) error {
  46. cfg, err := k.GetConfig()
  47. if err != nil {
  48. return errors.Wrapf(err, "couldn't create %q certificate", k.Name)
  49. }
  50. cert, key, err := pkiutil.NewCertAndKey(
  51. caCert, caKey,
  52. &pkiutil.CertConfig{
  53. Config: *cfg,
  54. })
  55. if err != nil {
  56. return err
  57. }
  58. if err := writeCertificateFilesIfNotExist(
  59. dir,
  60. k.BaseName,
  61. caCert,
  62. cert,
  63. key,
  64. cfg,
  65. ); err != nil {
  66. return errors.Wrapf(err, "failed to write or validate certificate %q", k.Name)
  67. }
  68. return nil
  69. }
  70. // CreateAsCA creates a certificate authority, writing the files to disk and also returning the created CA so it can be used to sign child certs.
  71. func (k *QemuCert) CreateAsCA(dir string) (*x509.Certificate, crypto.Signer, error) {
  72. cfg, err := k.GetConfig()
  73. if err != nil {
  74. return nil, nil, errors.Wrapf(err, "couldn't get configuration for %q CA certificate", k.Name)
  75. }
  76. caCert, caKey, err := pkiutil.NewCertificateAuthority(&pkiutil.CertConfig{Config: *cfg})
  77. if err != nil {
  78. return nil, nil, errors.Wrapf(err, "couldn't generate %q CA certificate", k.Name)
  79. }
  80. if err := writeCertificateAuthorithyFilesIfNotExist(
  81. dir,
  82. k.BaseName,
  83. caCert,
  84. caKey,
  85. ); err != nil {
  86. return nil, nil, errors.Wrapf(err, "couldn't write out %q CA certificate", k.Name)
  87. }
  88. return caCert, caKey, nil
  89. }
  90. // CertificateTree is represents a one-level-deep tree, mapping a CA to the certs that depend on it.
  91. type CertificateTree map[*QemuCert]Certificates
  92. // CreateTree creates the CAs, certs signed by the CAs, and writes them all to disk.
  93. func (t CertificateTree) CreateTree(dir string) error {
  94. for ca, leaves := range t {
  95. cfg, err := ca.GetConfig()
  96. if err != nil {
  97. return err
  98. }
  99. var caKey crypto.Signer
  100. caCert, err := pkiutil.TryLoadCertFromDisk(dir, ca.BaseName)
  101. if err == nil {
  102. // Cert exists already, make sure it's valid
  103. if !caCert.IsCA {
  104. return errors.Errorf("certificate %q is not a CA", ca.Name)
  105. }
  106. // Try and load a CA Key
  107. caKey, err = pkiutil.TryLoadKeyFromDisk(dir, ca.BaseName)
  108. if err != nil {
  109. // If there's no CA key, make sure every certificate exists.
  110. for _, leaf := range leaves {
  111. cl := certKeyLocation{
  112. pkiDir: dir,
  113. baseName: leaf.BaseName,
  114. uxName: leaf.Name,
  115. }
  116. if err := validateSignedCertWithCA(cl, caCert); err != nil {
  117. return errors.Wrapf(err, "could not load expected certificate %q or validate the existence of key %q for it", leaf.Name, ca.Name)
  118. }
  119. }
  120. continue
  121. }
  122. // CA key exists; just use that to create new certificates.
  123. } else {
  124. // CACert doesn't already exist, create a new cert and key.
  125. caCert, caKey, err = pkiutil.NewCertificateAuthority(&pkiutil.CertConfig{Config: *cfg})
  126. if err != nil {
  127. return err
  128. }
  129. err = writeCertificateAuthorithyFilesIfNotExist(
  130. dir,
  131. ca.BaseName,
  132. caCert,
  133. caKey,
  134. )
  135. if err != nil {
  136. return err
  137. }
  138. }
  139. for _, leaf := range leaves {
  140. if err := leaf.CreateFromCA(dir, caCert, caKey); err != nil {
  141. return err
  142. }
  143. }
  144. }
  145. return nil
  146. }
  147. // CertificateMap is a flat map of certificates, keyed by Name.
  148. type CertificateMap map[string]*QemuCert
  149. // CertTree returns a one-level-deep tree, mapping a CA cert to an array of certificates that should be signed by it.
  150. func (m CertificateMap) CertTree() (CertificateTree, error) {
  151. caMap := make(CertificateTree)
  152. for _, cert := range m {
  153. if cert.CAName == "" {
  154. if _, ok := caMap[cert]; !ok {
  155. caMap[cert] = []*QemuCert{}
  156. }
  157. } else {
  158. ca, ok := m[cert.CAName]
  159. if !ok {
  160. return nil, errors.Errorf("certificate %q references unknown CA %q", cert.Name, cert.CAName)
  161. }
  162. caMap[ca] = append(caMap[ca], cert)
  163. }
  164. }
  165. return caMap, nil
  166. }
  167. // Certificates is a list of Certificates that should be created
  168. type Certificates []*QemuCert
  169. func (c Certificates) AsMap() CertificateMap {
  170. certMap := make(map[string]*QemuCert)
  171. for _, cert := range c {
  172. certMap[cert.Name] = cert
  173. }
  174. return certMap
  175. }
  176. const (
  177. CACertAndKeyBaseName = "ca"
  178. ServerCertBaseName = "server"
  179. QemuServerCertCommonName = "qemu-server"
  180. ClientCertBaseName = "client"
  181. QemuClientCertCommonName = "qemu-client"
  182. )
  183. var (
  184. QemuCertRootCA = QemuCert{
  185. Name: "ca",
  186. LongName: "self-signed CA to provision identities for other qemu actions",
  187. BaseName: CACertAndKeyBaseName,
  188. config: certutil.Config{
  189. CommonName: "qemu",
  190. },
  191. }
  192. QemuCertServer = QemuCert{
  193. Name: "server",
  194. LongName: "certificate for server",
  195. BaseName: ServerCertBaseName,
  196. CAName: "ca",
  197. config: certutil.Config{
  198. CommonName: QemuServerCertCommonName,
  199. Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
  200. AltNames: certutil.AltNames{
  201. /*
  202. * IPs: []net.IP{
  203. * net.ParseIP("192.168.121.21"),
  204. * net.ParseIP("192.168.121.61"),
  205. * },
  206. */
  207. },
  208. },
  209. }
  210. QemuCertClient = QemuCert{
  211. Name: "client",
  212. LongName: "certificate for the server to connect to client",
  213. BaseName: ClientCertBaseName,
  214. CAName: "ca",
  215. config: certutil.Config{
  216. CommonName: QemuClientCertCommonName,
  217. Organization: []string{"system:host"},
  218. Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
  219. AltNames: certutil.AltNames{
  220. /*
  221. * IPs: []net.IP{
  222. * net.ParseIP("192.168.121.21"),
  223. * net.ParseIP("192.168.121.61"),
  224. * },
  225. */
  226. },
  227. },
  228. }
  229. )
  230. func setCommonNameToNodeName(commonName string) configMutatorsFunc {
  231. return func(cc *certutil.Config) error {
  232. cc.CommonName = commonName
  233. return nil
  234. }
  235. }
  236. func init() {
  237. pkiutil.SetPathForCert(func(pkiPath, name string) string {
  238. return filepath.Join(pkiPath, fmt.Sprintf("%s-cert.pem", name))
  239. })
  240. pkiutil.SetPathForKey(func(pkiPath, name string) string {
  241. return filepath.Join(pkiPath, fmt.Sprintf("%s-key.pem", name))
  242. })
  243. }
  244. // GetDefaultCertList returns all of the certificates qemu requires.
  245. func GetDefaultCertList() Certificates {
  246. return Certificates{
  247. &QemuCertRootCA,
  248. &QemuCertServer,
  249. &QemuCertClient,
  250. }
  251. }
  252. const (
  253. CA_CERT_NAME = "ca-cert.pem"
  254. CA_KEY_NAME = "ca-key.pem"
  255. SERVER_CERT_NAME = "server-cert.pem"
  256. SERVER_KEY_NAME = "server-key.pem"
  257. CLIENT_CERT_NAME = "client-cert.pem"
  258. CLIENT_KEY_NAME = "client-key.pem"
  259. )
  260. func FetchDefaultCerts(dir string) (map[string]string, error) {
  261. ret := make(map[string]string)
  262. for _, key := range []string{
  263. CA_CERT_NAME,
  264. CA_KEY_NAME,
  265. SERVER_CERT_NAME,
  266. SERVER_KEY_NAME,
  267. CLIENT_CERT_NAME,
  268. CLIENT_KEY_NAME,
  269. } {
  270. fp := filepath.Join(dir, key)
  271. content, err := fileutils2.FileGetContents(fp)
  272. if err != nil {
  273. return nil, errors.Wrapf(err, "get %q content", fp)
  274. }
  275. ret[key] = content
  276. }
  277. return ret, nil
  278. }
  279. func CreateByMap(dir string, input map[string]string) error {
  280. for key := range input {
  281. fp := filepath.Join(dir, key)
  282. content := input[key]
  283. if err := fileutils2.FilePutContents(fp, content, false); err != nil {
  284. return errors.Wrapf(err, "put %q to %q", content, fp)
  285. }
  286. }
  287. return nil
  288. }