Go client for HAProxy configuration and runtime API
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

230 lines
7.4 KiB

package runtime
import (
"fmt"
"strings"
"time"
"github.com/go-openapi/strfmt"
native_errors "github.com/haproxytech/client-native/v6/errors"
"github.com/haproxytech/client-native/v6/models"
)
// ShowCerts returns Certs files description from runtime
func (s *SingleRuntime) ShowCerts() (models.SslCertificates, error) {
cmd := "show ssl cert"
response, err := s.ExecuteWithResponse(cmd)
if err != nil {
return nil, fmt.Errorf("%s %w", err.Error(), native_errors.ErrNotFound)
}
return s.parseCerts(response), nil
}
// parseCerts parses output from `show cert` command and return array of certificates
// First line in output represents format and is ignored
// Sample output format:
// /etc/ssl/cert-0.pem
// /etc/ssl/...
func (s *SingleRuntime) parseCerts(output string) models.SslCertificates {
output = strings.TrimSpace(output)
if output == "" {
return nil
}
certs := models.SslCertificates{}
lines := strings.Split(output, "\n")
for _, line := range lines {
c := s.parseCert(line)
if c != nil {
certs = append(certs, c)
}
}
return certs
}
// parseCert parses one line from cert files array and return it structured
func (s *SingleRuntime) parseCert(line string) *models.SslCertificate {
if line == "" || strings.HasPrefix(strings.TrimSpace(line), "# filename") {
return nil
}
split := strings.Split(line, "/")
cert := &models.SslCertificate{
StorageName: strings.TrimSpace(line),
Description: split[len(split)-1],
}
return cert
}
// GetCert returns one structured runtime certs
func (s *SingleRuntime) GetCert(storageName string) (*models.SslCertificate, error) {
if storageName == "" {
return nil, fmt.Errorf("%s %w", "Argument storageName empty", native_errors.ErrGeneral)
}
certs, err := s.ShowCerts()
if err != nil {
return nil, err
}
for _, c := range certs {
if c.StorageName == storageName {
return c, nil
}
}
return nil, fmt.Errorf("%s %w", storageName, native_errors.ErrNotFound)
}
// ShowCertEntry returns one CrtList runtime entries
func (s *SingleRuntime) ShowCertEntry(storageName string) (*models.SslCertEntry, error) {
if storageName == "" {
return nil, fmt.Errorf("%s %w", "Argument storageName empty", native_errors.ErrGeneral)
}
cmd := fmt.Sprintf("show ssl cert %s", storageName)
response, err := s.ExecuteWithResponse(cmd)
if err != nil {
return nil, fmt.Errorf("%s %w", err.Error(), native_errors.ErrNotFound)
}
return parseCertEntry(response)
}
// parseCertEntry parses one entry in one CrtList file/runtime and returns it structured
// example:
// Filename: /etc/ssl/cert-2.pem
// Status: Used
// Serial: 0D933C1B1089BF660AE5253A245BB388
// notBefore: Sep 9 00:00:00 2020 GMT
// notAfter: Sep 14 12:00:00 2021 GMT
// Subject Alternative Name: DNS:*.platform.domain.com, DNS:uaa.platform.domain.com
// Algorithm: RSA4096
// SHA1 FingerPrint: 59242F1838BDEF3E7DAFC83FFE4DD6C03B88805C
// Subject: /C=DE/ST=Baden-Württemberg/L=Walldorf/O=ORG SE/CN=*.platform.domain.com
// Issuer: /C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
// Chain Subject: /C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
// Chain Issuer: /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
func parseCertEntry(response string) (*models.SslCertEntry, error) {
if response == "" || strings.HasPrefix(strings.TrimSpace(response), "#") {
return nil, native_errors.ErrNotFound
}
c := &models.SslCertEntry{}
parts := strings.Split(response, "\n")
for _, p := range parts {
index := strings.Index(p, ":")
if index == -1 {
continue
}
keyString := strings.TrimSpace(p[0:index])
valueString := strings.TrimSpace(p[index+1:])
switch key := keyString; {
case key == "Filename":
c.StorageName = valueString
case key == "Status":
c.Status = valueString
case key == "Serial":
c.Serial = valueString
case key == "notBefore":
notBefore, _ := time.Parse("Jan 2 15:04:05 2006 MST", valueString)
c.NotBefore = strfmt.Date(notBefore)
case key == "notAfter":
notAfter, _ := time.Parse("Jan 2 15:04:05 2006 MST", valueString)
c.NotAfter = strfmt.Date(notAfter)
case key == "Subject Alternative Name":
c.SubjectAlternativeNames = strings.Split(valueString, ", ")
case key == "Algorithm":
c.Algorithm = valueString
case key == "SHA1 FingerPrint":
c.Sha1FingerPrint = valueString
case key == "Subject":
c.Subject = valueString
case key == "Issuer":
c.Issuer = valueString
case key == "Chain Subject":
c.ChainSubject = valueString
case key == "Chain Issuer":
c.ChainIssuer = valueString
}
}
return c, nil
}
// NewCertEntry adds an entry into the CrtList file
func (s *SingleRuntime) NewCertEntry(storageName string) error {
if storageName == "" {
return fmt.Errorf("%s %w", "Argument storageName empty", native_errors.ErrGeneral)
}
cmd := fmt.Sprintf("new ssl cert %s", storageName)
response, err := s.ExecuteWithResponse(cmd)
if err != nil {
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
}
if !strings.Contains(response, "New empty certificate store") {
return fmt.Errorf("%s %w", response, native_errors.ErrGeneral)
}
return nil
}
// SetCertEntry adds an entry into the CrtList file
func (s *SingleRuntime) SetCertEntry(storageName string, payload string) error {
if storageName == "" || payload == "" {
return fmt.Errorf("%s %w", "Argument storageName or payload empty", native_errors.ErrGeneral)
}
cmd := fmt.Sprintf("set ssl cert %s <<\n%s\n", storageName, payload)
response, err := s.ExecuteWithResponse(cmd)
if err != nil {
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
}
if !strings.Contains(response, "Transaction created for certificate") {
return fmt.Errorf("%s %w", response, native_errors.ErrGeneral)
}
return nil
}
// CommitCertEntry adds an entry into the CrtList file
func (s *SingleRuntime) CommitCertEntry(storageName string) error {
if storageName == "" {
return fmt.Errorf("%s %w", "Argument storageName empty", native_errors.ErrGeneral)
}
cmd := fmt.Sprintf("commit ssl cert %s", storageName)
response, err := s.ExecuteWithResponse(cmd)
if err != nil {
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
}
if !(strings.Contains(response, "Committing") && strings.Contains(response, "Success!")) {
return fmt.Errorf("%s %w", response, native_errors.ErrGeneral)
}
return nil
}
// AbortCertEntry adds an entry into the CrtList file
func (s *SingleRuntime) AbortCertEntry(storageName string) error {
if storageName == "" {
return fmt.Errorf("%s %w", "Argument storageName empty", native_errors.ErrGeneral)
}
cmd := fmt.Sprintf("abort ssl cert %s", storageName)
response, err := s.ExecuteWithResponse(cmd)
if err != nil {
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
}
if !strings.Contains(response, "Transaction aborted for certificate") {
return fmt.Errorf("%s %w", response, native_errors.ErrGeneral)
}
return nil
}
// DeleteCertEntry adds an entry into the CrtList file
func (s *SingleRuntime) DeleteCertEntry(storageName string) error {
if storageName == "" {
return fmt.Errorf("%s %w", "Argument storageName empty", native_errors.ErrGeneral)
}
cmd := fmt.Sprintf("del ssl cert %s", storageName)
response, err := s.ExecuteWithResponse(cmd)
if err != nil {
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
}
if !(strings.Contains(response, "Certificate") && strings.Contains(response, "deleted!")) {
return fmt.Errorf("%s %w", response, native_errors.ErrGeneral)
}
return nil
}