mirror of
https://github.com/status-im/consul.git
synced 2025-01-09 13:26:07 +00:00
agent/consul: basic sign endpoint not tested yet
This commit is contained in:
parent
548ce190d5
commit
f4ec28bfe3
@ -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
|
// 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.
|
// an *ecdsa.PublicKey, but is an interface type to support crypto.Signer.
|
||||||
func KeyId(raw interface{}) ([]byte, error) {
|
func KeyId(raw interface{}) ([]byte, error) {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -45,7 +46,7 @@ func TestCA(t testing.T, xc *structs.CARoot) *structs.CARoot {
|
|||||||
signer := testPrivateKey(t, &result)
|
signer := testPrivateKey(t, &result)
|
||||||
|
|
||||||
// The serial number for the cert
|
// The serial number for the cert
|
||||||
sn, err := SerialNumber()
|
sn, err := testSerialNumber()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error generating serial number: %s", err)
|
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.
|
// the given CA Root.
|
||||||
func TestLeaf(t testing.T, service string, root *structs.CARoot) string {
|
func TestLeaf(t testing.T, service string, root *structs.CARoot) string {
|
||||||
// Parse the CA cert and signing key from the root
|
// 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 {
|
if err != nil {
|
||||||
t.Fatalf("error parsing CA cert: %s", err)
|
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)
|
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
|
// The serial number for the cert
|
||||||
sn, err := SerialNumber()
|
sn, err := testSerialNumber()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error generating serial number: %s", err)
|
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{
|
template := x509.Certificate{
|
||||||
SerialNumber: sn,
|
SerialNumber: sn,
|
||||||
Subject: pkix.Name{CommonName: service},
|
Subject: pkix.Name{CommonName: service},
|
||||||
|
URIs: []*url.URL{spiffeId.URI()},
|
||||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
KeyUsage: x509.KeyUsageDataEncipherment | x509.KeyUsageKeyAgreement,
|
KeyUsage: x509.KeyUsageDataEncipherment | x509.KeyUsageKeyAgreement,
|
||||||
@ -171,6 +185,30 @@ func TestLeaf(t testing.T, service string, root *structs.CARoot) string {
|
|||||||
return buf.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
|
// 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
|
// an *ecdsa.PublicKey, but is an interface type to suppot crypto.Signer.Public
|
||||||
// values.
|
// values.
|
||||||
@ -221,10 +259,20 @@ func testPrivateKey(t testing.T, ca *structs.CARoot) crypto.Signer {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error encoding private key: %s", err)
|
t.Fatalf("error encoding private key: %s", err)
|
||||||
}
|
}
|
||||||
ca.SigningKey = buf.String()
|
if ca != nil {
|
||||||
|
ca.SigningKey = buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
// Memoize the key
|
// Memoize the key
|
||||||
testMemoizePK.Store(pk)
|
testMemoizePK.Store(pk)
|
||||||
|
|
||||||
return 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
|
package consul
|
||||||
|
|
||||||
import (
|
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/consul/state"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/go-memdb"
|
"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
|
RootCert string
|
||||||
|
|
||||||
// SigningCert is the PEM-encoded signing certificate and SigningKey
|
// 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
|
SigningCert string
|
||||||
SigningKey string
|
SigningKey string
|
||||||
|
|
||||||
@ -37,3 +38,21 @@ type CARoot struct {
|
|||||||
|
|
||||||
// CARoots is a list of CARoot structures.
|
// CARoots is a list of CARoot structures.
|
||||||
type CARoots []*CARoot
|
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…
x
Reference in New Issue
Block a user