open-consul/agent/consul/connect_ca_endpoint.go
2018-06-14 09:41:51 -07:00

179 lines
4.7 KiB
Go

package consul
import (
"bytes"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"time"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/go-memdb"
)
// ConnectCA manages the Connect CA.
type ConnectCA struct {
// srv is a pointer back to the server.
srv *Server
}
// Roots returns the currently trusted root certificates.
func (s *ConnectCA) Roots(
args *structs.DCSpecificRequest,
reply *structs.IndexedCARoots) error {
// Forward if necessary
if done, err := s.srv.forward("ConnectCA.Roots", args, args, reply); done {
return err
}
return s.srv.blockingQuery(
&args.QueryOptions, &reply.QueryMeta,
func(ws memdb.WatchSet, state *state.Store) error {
index, roots, err := state.CARoots(ws)
if err != nil {
return err
}
reply.Index, reply.Roots = index, roots
if reply.Roots == nil {
reply.Roots = make(structs.CARoots, 0)
}
// The API response must NEVER contain the secret information
// such as keys and so on. We use a whitelist below to copy the
// specific fields we want to expose.
for i, r := range reply.Roots {
// IMPORTANT: r must NEVER be modified, since it is a pointer
// directly to the structure in the memdb store.
reply.Roots[i] = &structs.CARoot{
ID: r.ID,
Name: r.Name,
RootCert: r.RootCert,
RaftIndex: r.RaftIndex,
Active: r.Active,
}
if r.Active {
reply.ActiveRootID = r.ID
}
}
return nil
},
)
}
// Sign signs a certificate for a service.
//
// NOTE(mitchellh): There is a LOT missing from this. I do next to zero
// validation of the incoming CSR, the way the cert is signed probably
// isn't right, we're not using enough of the CSR fields, etc.
func (s *ConnectCA) Sign(
args *structs.CASignRequest,
reply *structs.IssuedCert) error {
// Parse the CSR
csr, err := connect.ParseCSR(args.CSR)
if err != nil {
return err
}
// Parse the SPIFFE ID
spiffeId, err := connect.ParseSpiffeID(csr.URIs[0])
if err != nil {
return err
}
serviceId, ok := spiffeId.(*connect.SpiffeIDService)
if !ok {
return fmt.Errorf("SPIFFE ID in CSR must be a service ID")
}
// Get the currently active root
state := s.srv.fsm.State()
_, root, err := state.CARootActive(nil)
if err != nil {
return err
}
if root == nil {
return fmt.Errorf("no active CA found")
}
// Determine the signing certificate. It is the set signing cert
// unless that is empty, in which case it is identically to the public
// cert.
certPem := root.SigningCert
if certPem == "" {
certPem = root.RootCert
}
// Parse the CA cert and signing key from the root
caCert, err := connect.ParseCert(certPem)
if err != nil {
return fmt.Errorf("error parsing CA cert: %s", err)
}
signer, err := connect.ParseSigner(root.SigningKey)
if err != nil {
return fmt.Errorf("error parsing signing key: %s", err)
}
// The serial number for the cert. NOTE(mitchellh): in the final
// implementation this should be monotonically increasing based on
// some raft state.
sn, err := rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil))
if err != nil {
return fmt.Errorf("error generating serial number: %s", err)
}
// Create the keyId for the cert from the signing public key.
keyId, err := connect.KeyId(signer.Public())
if err != nil {
return err
}
// Cert template for generation
template := x509.Certificate{
SerialNumber: sn,
Subject: pkix.Name{CommonName: serviceId.Service},
URIs: csr.URIs,
Signature: csr.Signature,
SignatureAlgorithm: csr.SignatureAlgorithm,
PublicKeyAlgorithm: csr.PublicKeyAlgorithm,
PublicKey: csr.PublicKey,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageDataEncipherment | x509.KeyUsageKeyAgreement,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
NotAfter: time.Now().Add(3 * 24 * time.Hour),
NotBefore: time.Now(),
AuthorityKeyId: keyId,
SubjectKeyId: keyId,
}
// Create the certificate, PEM encode it and return that value.
var buf bytes.Buffer
bs, err := x509.CreateCertificate(
rand.Reader, &template, caCert, signer.Public(), signer)
if err != nil {
return fmt.Errorf("error generating certificate: %s", err)
}
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
if err != nil {
return fmt.Errorf("error encoding private key: %s", err)
}
// Set the response
*reply = structs.IssuedCert{
SerialNumber: template.SerialNumber,
Cert: buf.String(),
}
return nil
}