diff --git a/provisioners/step.go b/provisioners/step.go index 3966905b3547ff6ff373c9f96aeed62e100887c6..fbb48a1393e035cfdf42a5a75591c96ad8f56336 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] +}