agent/consul: basic sign endpoint not tested yet
This commit is contained in:
parent
6550ff9492
commit
a360c5cca4
|
@ -40,6 +40,21 @@ func ParseSigner(pemValue string) (crypto.Signer, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// ParseCSR parses a CSR from a PEM-encoded value. The certificate request
|
||||
// must be the the first block in the PEM value.
|
||||
func ParseCSR(pemValue string) (*x509.CertificateRequest, error) {
|
||||
block, _ := pem.Decode([]byte(pemValue))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("no PEM-encoded data found")
|
||||
}
|
||||
|
||||
if block.Type != "CERTIFICATE REQUEST" {
|
||||
return nil, fmt.Errorf("first PEM-block should be CERTIFICATE REQUEST type")
|
||||
}
|
||||
|
||||
return x509.ParseCertificateRequest(block.Bytes)
|
||||
}
|
||||
|
||||
// KeyId returns a x509 KeyId from the given signing key. The key must be
|
||||
// an *ecdsa.PublicKey, but is an interface type to support crypto.Signer.
|
||||
func KeyId(raw interface{}) ([]byte, error) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
@ -45,7 +46,7 @@ func TestCA(t testing.T, xc *structs.CARoot) *structs.CARoot {
|
|||
signer := testPrivateKey(t, &result)
|
||||
|
||||
// The serial number for the cert
|
||||
sn, err := SerialNumber()
|
||||
sn, err := testSerialNumber()
|
||||
if err != nil {
|
||||
t.Fatalf("error generating serial number: %s", err)
|
||||
}
|
||||
|
@ -124,7 +125,11 @@ func TestCA(t testing.T, xc *structs.CARoot) *structs.CARoot {
|
|||
// the given CA Root.
|
||||
func TestLeaf(t testing.T, service string, root *structs.CARoot) string {
|
||||
// Parse the CA cert and signing key from the root
|
||||
caCert, err := ParseCert(root.RootCert)
|
||||
cert := root.SigningCert
|
||||
if cert == "" {
|
||||
cert = root.RootCert
|
||||
}
|
||||
caCert, err := ParseCert(cert)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing CA cert: %s", err)
|
||||
}
|
||||
|
@ -133,8 +138,16 @@ func TestLeaf(t testing.T, service string, root *structs.CARoot) string {
|
|||
t.Fatalf("error parsing signing key: %s", err)
|
||||
}
|
||||
|
||||
// Build the SPIFFE ID
|
||||
spiffeId := &SpiffeIDService{
|
||||
Host: fmt.Sprintf("%s.consul", testClusterID),
|
||||
Namespace: "default",
|
||||
Datacenter: "dc01",
|
||||
Service: service,
|
||||
}
|
||||
|
||||
// The serial number for the cert
|
||||
sn, err := SerialNumber()
|
||||
sn, err := testSerialNumber()
|
||||
if err != nil {
|
||||
t.Fatalf("error generating serial number: %s", err)
|
||||
}
|
||||
|
@ -143,6 +156,7 @@ func TestLeaf(t testing.T, service string, root *structs.CARoot) string {
|
|||
template := x509.Certificate{
|
||||
SerialNumber: sn,
|
||||
Subject: pkix.Name{CommonName: service},
|
||||
URIs: []*url.URL{spiffeId.URI()},
|
||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageDataEncipherment | x509.KeyUsageKeyAgreement,
|
||||
|
@ -171,6 +185,30 @@ func TestLeaf(t testing.T, service string, root *structs.CARoot) string {
|
|||
return buf.String()
|
||||
}
|
||||
|
||||
// TestCSR returns a CSR to sign the given service.
|
||||
func TestCSR(t testing.T, id SpiffeID) string {
|
||||
template := &x509.CertificateRequest{
|
||||
URIs: []*url.URL{id.URI()},
|
||||
}
|
||||
|
||||
// Create the private key we'll use
|
||||
signer := testPrivateKey(t, nil)
|
||||
|
||||
// Create the CSR itself
|
||||
bs, err := x509.CreateCertificateRequest(rand.Reader, template, signer)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating CSR: %s", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: bs})
|
||||
if err != nil {
|
||||
t.Fatalf("error encoding CSR: %s", err)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// testKeyID returns a KeyID from the given public key. The "raw" must be
|
||||
// an *ecdsa.PublicKey, but is an interface type to suppot crypto.Signer.Public
|
||||
// values.
|
||||
|
@ -221,10 +259,20 @@ func testPrivateKey(t testing.T, ca *structs.CARoot) crypto.Signer {
|
|||
if err != nil {
|
||||
t.Fatalf("error encoding private key: %s", err)
|
||||
}
|
||||
ca.SigningKey = buf.String()
|
||||
if ca != nil {
|
||||
ca.SigningKey = buf.String()
|
||||
}
|
||||
|
||||
// Memoize the key
|
||||
testMemoizePK.Store(pk)
|
||||
|
||||
return pk
|
||||
}
|
||||
|
||||
// testSerialNumber generates a serial number suitable for a certificate.
|
||||
// For testing, this just sets it to a random number.
|
||||
//
|
||||
// This function is taken directly from the Vault implementation.
|
||||
func testSerialNumber() (*big.Int, error) {
|
||||
return rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil))
|
||||
}
|
|
@ -1,6 +1,16 @@
|
|||
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"
|
||||
|
@ -53,3 +63,94 @@ func (s *ConnectCA) Roots(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
// 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.IndexedCARoots) 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")
|
||||
}
|
||||
|
||||
var root *structs.CARoot
|
||||
|
||||
// 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,
|
||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageDataEncipherment | x509.KeyUsageKeyAgreement,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
},
|
||||
NotAfter: time.Now().Add(10 * 365 * 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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ type CARoot struct {
|
|||
RootCert string
|
||||
|
||||
// SigningCert is the PEM-encoded signing certificate and SigningKey
|
||||
// is the PEM-encoded private key for the signing certificate.
|
||||
// is the PEM-encoded private key for the signing certificate. These
|
||||
// may actually be empty if the CA plugin in use manages these for us.
|
||||
SigningCert string
|
||||
SigningKey string
|
||||
|
||||
|
@ -37,3 +38,21 @@ type CARoot struct {
|
|||
|
||||
// CARoots is a list of CARoot structures.
|
||||
type CARoots []*CARoot
|
||||
|
||||
// CASignRequest is the request for signing a service certificate.
|
||||
type CASignRequest struct {
|
||||
// Datacenter is the target for this request.
|
||||
Datacenter string
|
||||
|
||||
// CSR is the PEM-encoded CSR.
|
||||
CSR string
|
||||
|
||||
// WriteRequest is a common struct containing ACL tokens and other
|
||||
// write-related common elements for requests.
|
||||
WriteRequest
|
||||
}
|
||||
|
||||
// RequestDatacenter returns the datacenter for a given request.
|
||||
func (q *CASignRequest) RequestDatacenter() string {
|
||||
return q.Datacenter
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
package connect
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// ParseCert parses the x509 certificate from a PEM-encoded value.
|
||||
func ParseCert(pemValue string) (*x509.Certificate, error) {
|
||||
block, _ := pem.Decode([]byte(pemValue))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("no PEM-encoded data found")
|
||||
}
|
||||
|
||||
if block.Type != "CERTIFICATE" {
|
||||
return nil, fmt.Errorf("first PEM-block should be CERTIFICATE type")
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
}
|
||||
|
||||
// ParseSigner parses a crypto.Signer from a PEM-encoded key. The private key
|
||||
// is expected to be the first block in the PEM value.
|
||||
func ParseSigner(pemValue string) (crypto.Signer, error) {
|
||||
block, _ := pem.Decode([]byte(pemValue))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("no PEM-encoded data found")
|
||||
}
|
||||
|
||||
switch block.Type {
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(block.Bytes)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown PEM block type for signing key: %s", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// SerialNumber generates a serial number suitable for a certificate.
|
||||
//
|
||||
// This function is taken directly from the Vault implementation.
|
||||
func SerialNumber() (*big.Int, error) {
|
||||
return rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil))
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
// Package connect contains utilities and helpers for working with the
|
||||
// Connect feature of Consul.
|
||||
package connect
|
Loading…
Reference in New Issue