Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add get-consul-client-ca command #211

Merged
merged 7 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

cmdACLInit "github.com/hashicorp/consul-k8s/subcommand/acl-init"
cmdDeleteCompletedJob "github.com/hashicorp/consul-k8s/subcommand/delete-completed-job"
cmdGetConsulClientCA "github.com/hashicorp/consul-k8s/subcommand/get-consul-client-ca"
cmdInjectConnect "github.com/hashicorp/consul-k8s/subcommand/inject-connect"
cmdLifecycleSidecar "github.com/hashicorp/consul-k8s/subcommand/lifecycle-sidecar"
cmdServerACLInit "github.com/hashicorp/consul-k8s/subcommand/server-acl-init"
Expand Down Expand Up @@ -45,6 +46,10 @@ func init() {
return &cmdDeleteCompletedJob.Command{UI: ui}, nil
},

"get-consul-client-ca": func() (cli.Command, error) {
return &cmdGetConsulClientCA.Command{UI: ui}, nil
},

"version": func() (cli.Command, error) {
return &cmdVersion.Command{UI: ui, Version: version.GetHumanVersion()}, nil
},
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/hashicorp/consul v1.7.1
github.com/hashicorp/consul/api v1.4.0
github.com/hashicorp/consul/sdk v0.4.0
github.com/hashicorp/go-discover v0.0.0-20191202160150-7ec2cfbda7a2
github.com/hashicorp/go-hclog v0.12.0
github.com/hashicorp/go-multierror v1.0.0
github.com/hashicorp/golang-lru v0.5.3 // indirect
Expand Down
175 changes: 8 additions & 167 deletions helper/cert/source_gen.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
package cert

import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"strings"
"sync"
"time"
)
Expand All @@ -40,7 +29,7 @@ type GenSource struct {
ExpiryWithin time.Duration

mu sync.Mutex
caCert []byte
caCert string
caCertTemplate *x509.Certificate
caSigner crypto.Signer
}
Expand All @@ -57,15 +46,14 @@ func (s *GenSource) Certificate(ctx context.Context, last *Bundle) (Bundle, erro
return result, err
}
}

// Set the CA cert
result.CACert = s.caCert
result.CACert = []byte(s.caCert)

// If we have a prior cert, we wait for getting near to the expiry
// (within 30 minutes arbitrarily chosen).
if last != nil {
// We have a prior certificate, let's parse it to get the expiry
cert, err := parseCert(last.Cert)
cert, err := ParseCert(last.Cert)
if err != nil {
return result, err
}
Expand All @@ -88,7 +76,7 @@ func (s *GenSource) Certificate(ctx context.Context, last *Bundle) (Bundle, erro
}

// Generate cert, set it on the result, and return
cert, key, err := s.generateCert()
cert, key, err := GenerateCert(s.Name+" Service", s.expiry(), s.caCertTemplate, s.caSigner, s.Hosts)
if err == nil {
result.Cert = []byte(cert)
result.Key = []byte(key)
Expand All @@ -114,162 +102,15 @@ func (s *GenSource) expiryWithin() time.Duration {
return time.Duration(float64(s.expiry()) * 0.10)
}

func (s *GenSource) generateCert() (string, string, error) {
// Create the private key we'll use for this leaf cert.
signer, keyPEM, err := s.privateKey()
if err != nil {
return "", "", err
}

// The serial number for the cert
sn, err := serialNumber()
if err != nil {
return "", "", err
}

// Create the leaf cert
template := x509.Certificate{
SerialNumber: sn,
Subject: pkix.Name{CommonName: s.Name + " Service"},
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
NotAfter: time.Now().Add(s.expiry()),
NotBefore: time.Now().Add(-1 * time.Minute),
}
for _, h := range s.Hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}

bs, err := x509.CreateCertificate(
rand.Reader, &template, s.caCertTemplate, signer.Public(), s.caSigner)
if err != nil {
return "", "", err
}
var buf bytes.Buffer
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
if err != nil {
return "", "", err
}

return buf.String(), keyPEM, nil
}

func (s *GenSource) generateCA() error {
// Create the private key we'll use for this CA cert.
signer, _, err := s.privateKey()
// generate the CA
signer, _, caCertPem, caCertTemplate, err := GenerateCA(s.Name + " CA")
if err != nil {
return err
}
s.caSigner = signer

// The serial number for the cert
sn, err := serialNumber()
if err != nil {
return err
}

signerKeyId, err := keyId(signer.Public())
if err != nil {
return err
}

// Create the CA cert
template := x509.Certificate{
SerialNumber: sn,
Subject: pkix.Name{CommonName: s.Name + " CA"},
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
IsCA: true,
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
NotBefore: time.Now().Add(-1 * time.Minute),
AuthorityKeyId: signerKeyId,
SubjectKeyId: signerKeyId,
}

bs, err := x509.CreateCertificate(
rand.Reader, &template, &template, signer.Public(), signer)
if err != nil {
return err
}

var buf bytes.Buffer
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
if err != nil {
return err
}

s.caCert = buf.Bytes()
s.caCertTemplate = &template
s.caCert = caCertPem
s.caCertTemplate = caCertTemplate

return nil
}

// privateKey returns a new ECDSA-based private key. Both a crypto.Signer
// and the key in PEM format are returned.
func (s *GenSource) privateKey() (crypto.Signer, string, error) {
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, "", err
}

bs, err := x509.MarshalECPrivateKey(pk)
if err != nil {
return nil, "", err
}

var buf bytes.Buffer
err = pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bs})
if err != nil {
return nil, "", err
}

return pk, buf.String(), nil
}

// serialNumber generates a new random serial number.
func serialNumber() (*big.Int, error) {
return rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil))
}

// keyId returns a x509 KeyId from the given signing key. The key must be
// an *ecdsa.PublicKey currently, but may support more types in the future.
func keyId(raw interface{}) ([]byte, error) {
switch raw.(type) {
case *ecdsa.PublicKey:
default:
return nil, fmt.Errorf("invalid key type: %T", raw)
}

// This is not standard; RFC allows any unique identifier as long as they
// match in subject/authority chains but suggests specific hashing of DER
// bytes of public key including DER tags.
bs, err := x509.MarshalPKIXPublicKey(raw)
if err != nil {
return nil, err
}

// String formatted
kID := sha256.Sum256(bs)
return []byte(strings.Replace(fmt.Sprintf("% x", kID), " ", ":", -1)), nil
}

// parseCert parses the x509 certificate from a PEM-encoded value.
func parseCert(pemValue []byte) (*x509.Certificate, error) {
// The _ result below is not an error but the remaining PEM bytes.
block, _ := pem.Decode(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)
}
2 changes: 1 addition & 1 deletion helper/cert/source_gen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func testBundleVerify(t *testing.T, bundle *Bundle) {
cmd := exec.Command(
"openssl", "verify", "-verbose", "-CAfile", "ca.pem", "leaf.pem")
cmd.Dir = td
output, err := cmd.Output()
output, err := cmd.CombinedOutput()
t.Log(string(output))
require.NoError(err)
}
Loading