From 6e4d8cc8df5f3e3e0ad532ae11d1a6cf06c87fab Mon Sep 17 00:00:00 2001
From: Mariano Cano <mariano@smallstep.com>
Date: Mon, 20 Apr 2020 13:10:20 -0700
Subject: [PATCH] Add support for identity certificates.

Extract a subject from sans if common name is empty.
---
 provisioners/step.go | 71 +++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 63 insertions(+), 8 deletions(-)

diff --git a/provisioners/step.go b/provisioners/step.go
index 3966905..fbb48a1 100644
--- a/provisioners/step.go
+++ b/provisioners/step.go
@@ -20,7 +20,7 @@ var collection = new(sync.Map)
 // Step implements a Step JWK provisioners in charge of signing certificate
 // requests using step certificates.
 type Step struct {
-	uid         types.UID
+	name        string
 	provisioner *ca.Provisioner
 }
 
@@ -35,9 +35,22 @@ func New(iss *api.StepIssuer, password []byte) (*Step, error) {
 	if err != nil {
 		return nil, err
 	}
-	return &Step{
+
+	p := &Step{
+		name:        iss.Name + "." + iss.Namespace,
 		provisioner: provisioner,
-	}, nil
+	}
+
+	// Request identity certificate if required.
+	if version, err := provisioner.Version(); err == nil {
+		if version.RequireClientAuthentication {
+			if err := p.createIdentityCertificate(); err != nil {
+				return nil, err
+			}
+		}
+	}
+
+	return p, nil
 }
 
 // Load returns a Step provisioner by NamespacedName.
@@ -55,6 +68,30 @@ func Store(namespacedName types.NamespacedName, provisioner *Step) {
 	collection.Store(namespacedName, provisioner)
 }
 
+func (s *Step) createIdentityCertificate() error {
+	csr, pk, err := ca.CreateCertificateRequest(s.name)
+	if err != nil {
+		return err
+	}
+	token, err := s.provisioner.Token(s.name)
+	if err != nil {
+		return err
+	}
+	resp, err := s.provisioner.Sign(&capi.SignRequest{
+		CsrPEM: *csr,
+		OTT:    token,
+	})
+	if err != nil {
+		return err
+	}
+	tr, err := s.provisioner.Client.Transport(context.Background(), resp, pk)
+	if err != nil {
+		return err
+	}
+	s.provisioner.Client.SetTransport(tr)
+	return nil
+}
+
 // Sign sends the certificate requests to the Step CA and returns the signed
 // certificate.
 func (s *Step) Sign(ctx context.Context, cr *certmanager.CertificateRequest) ([]byte, []byte, error) {
@@ -80,15 +117,17 @@ func (s *Step) Sign(ctx context.Context, cr *certmanager.CertificateRequest) ([]
 		return nil, nil, err
 	}
 
-	var sans []string
-	for _, dns := range csr.DNSNames {
-		sans = append(sans, dns)
-	}
+	sans := append([]string{}, csr.DNSNames...)
 	for _, ip := range csr.IPAddresses {
 		sans = append(sans, ip.String())
 	}
 
-	token, err := s.provisioner.Token(csr.Subject.CommonName, sans...)
+	subject := csr.Subject.CommonName
+	if subject == "" {
+		subject = generateSubject(sans)
+	}
+
+	token, err := s.provisioner.Token(subject, sans...)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -151,3 +190,19 @@ func encodeX509(cert *x509.Certificate) ([]byte, error) {
 	}
 	return caPem.Bytes(), nil
 }
+
+// generateSubject returns the first SAN that is not 127.0.0.1 or localhost. The
+// CSRs generated by the Certificate resource have always those SANs. If no SANs
+// are available `step-issuer-certificate` will be used as a subject is always
+// required.
+func generateSubject(sans []string) string {
+	if len(sans) == 0 {
+		return "step-issuer-certificate"
+	}
+	for _, s := range sans {
+		if s != "127.0.0.1" && s != "localhost" {
+			return s
+		}
+	}
+	return sans[0]
+}
-- 
GitLab