Skip to content

Commit 55f5c12

Browse files
w1amjosephcummings
andauthored
DEV-78 - Allow specifying certificate file names (#25)
* Allow specifying certificate file names * Improve test coverage * Some refactoring * Add more create node test * Make sure tests runs on Windows * Add support for user certs in create-certs command --------- Co-authored-by: Joseph Cummings <josephjcummings@outlook.com>
1 parent 9b364b1 commit 55f5c12

18 files changed

+1265
-571
lines changed

Diff for: .github/workflows/ci.yml

+12
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,15 @@ jobs:
8989
tags: ghcr.io/${{ steps.meta.outputs.tags }}
9090
labels: ${{ steps.meta.outputs.labels }}
9191
platforms: linux/amd64,linux/arm64
92+
93+
test-windows:
94+
runs-on: windows-latest
95+
steps:
96+
- name: Checkout
97+
uses: actions/checkout@v3
98+
- name: Set up Go
99+
uses: actions/setup-go@v3
100+
with:
101+
go-version: 1.21
102+
- name: Run Tests
103+
run: go test ./...

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ ca/
3030
es-gencert-cli
3131
certs.yml
3232
.DS_Store
33+
*.crt
34+
*.key

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ certificates:
9494
dns-names: "localhost,eventstore-node2.localhost.com"
9595
```
9696
97+
If you want to specify the name of the certificates from the config file, you can add the name field to the certificate definition. You can see an example of this in the [example configuration](references/named_certs.yml).
98+
9799
## Development
98100
99101
Building or working on `es-gencert-cli` requires a Go environment, version 1.14 or higher.

Diff for: certificates/boring_linux.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ import (
88

99
func isBoringEnabled() bool {
1010
return boring.Enabled()
11-
}
11+
}

Diff for: certificates/certificates.go

-60
This file was deleted.

Diff for: certificates/common.go

+27-20
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,18 @@ import (
1111
"math/big"
1212
"os"
1313
"path"
14-
"text/tabwriter"
14+
"path/filepath"
1515
)
1616

17-
const defaultKeySize = 2048
18-
19-
const forceOption = "Force overwrite of existing files without prompting"
20-
2117
const (
22-
ErrFileExists = "Error: Existing files would be overwritten. Use -force to proceed"
18+
ForceFlagUsage = "Force overwrite of existing files without prompting"
19+
NameFlagUsage = "The name of the CA certificate and key file"
20+
OutDirFlagUsage = "The output directory"
21+
DayFlagUsage = "the validity period of the certificate in days"
22+
CaKeyFlagUsage = "the path to the CA key file"
23+
CaCertFlagUsage = "the path to the CA certificate file"
2324
)
25+
const defaultKeySize = 2048
2426

2527
func generateSerialNumber(bits uint) (*big.Int, error) {
2628
maxValue := new(big.Int).Lsh(big.NewInt(1), bits)
@@ -48,13 +50,9 @@ func writeFileWithDir(filePath string, data []byte, perm os.FileMode) error {
4850
return os.WriteFile(filePath, data, perm)
4951
}
5052

51-
func writeHelpOption(w *tabwriter.Writer, title string, description string) {
52-
fmt.Fprintf(w, "\t-%s\t%s\n", title, description)
53-
}
54-
5553
func writeCertAndKey(outputDir string, fileName string, certPem, privateKeyPem *bytes.Buffer, force bool) error {
56-
certFile := path.Join(outputDir, fileName+".crt")
57-
keyFile := path.Join(outputDir, fileName+".key")
54+
certFile := filepath.ToSlash(fmt.Sprintf("%s/%s.crt", outputDir, fileName))
55+
keyFile := filepath.ToSlash(fmt.Sprintf("%s/%s.key", outputDir, fileName))
5856

5957
if force {
6058
if _, err := os.Stat(certFile); err == nil {
@@ -76,19 +74,12 @@ func writeCertAndKey(outputDir string, fileName string, certPem, privateKeyPem *
7674

7775
err = writeFileWithDir(keyFile, privateKeyPem.Bytes(), 0400)
7876
if err != nil {
79-
return fmt.Errorf("error writing certificate private key to %s: %s", keyFile, err.Error())
77+
return fmt.Errorf("error writing private key to %s: %s", keyFile, err.Error())
8078
}
8179

8280
return nil
8381
}
8482

85-
func fileExists(path string, force bool) bool {
86-
if _, err := os.Stat(path); !os.IsNotExist(err) && !force {
87-
return true
88-
}
89-
return false
90-
}
91-
9283
func readCertificateFromFile(path string) (*x509.Certificate, error) {
9384
pemBytes, err := os.ReadFile(path)
9485
if err != nil {
@@ -124,3 +115,19 @@ func readRSAKeyFromFile(path string) (*rsa.PrivateKey, error) {
124115
}
125116
return key, nil
126117
}
118+
119+
func checkCertificatesLocationWithForce(dir, certificateName string, force bool) error {
120+
// Throw an error if the path for the CA and key certificates already
121+
// exists and the 'force' flag is not set.
122+
123+
checkFile := func(ext string) bool {
124+
_, err := os.Stat(filepath.Join(dir, certificateName+ext))
125+
return !os.IsNotExist(err)
126+
}
127+
128+
if !force && (checkFile(".key") || checkFile(".crt")) {
129+
return fmt.Errorf("existing files would be overwritten. Use -force to proceed")
130+
}
131+
132+
return nil
133+
}

Diff for: certificates/common_test.go

+42-27
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,57 @@ package certificates
33
import (
44
"crypto/rsa"
55
"crypto/x509"
6+
"fmt"
67
"github.com/stretchr/testify/assert"
7-
"os"
8-
"path"
8+
"path/filepath"
9+
"regexp"
10+
"strings"
911
"testing"
1012
)
1113

12-
func assertFilesExist(t *testing.T, files ...string) {
13-
for _, file := range files {
14-
_, err := os.Stat(file)
15-
assert.False(t, os.IsNotExist(err))
16-
}
17-
}
14+
func extractErrors(errorMessage string) []string {
15+
// Sometimes errors are shown in a multi-line format (multierror.Append), so we need to extract them and return them
16+
// as a list. However, this method can be used with single line errors as well and will return a list with a single
17+
// element. Also perform some basic cleanup of the error message (TrimSpace).
1818

19-
func generateAndAssertCACert(t *testing.T, years int, days int, outputDirCa string, force bool) (*x509.Certificate, *rsa.PrivateKey) {
20-
certificateError := generateCACertificate(years, days, outputDirCa, nil, nil, force)
21-
assert.NoError(t, certificateError)
19+
var errors []string
2220

23-
certFilePath := path.Join(outputDirCa, "ca.crt")
24-
keyFilePath := path.Join(outputDirCa, "ca.key")
25-
assertFilesExist(t, certFilePath, keyFilePath)
21+
// Pattern for multi-line errors
22+
multiLinePattern := regexp.MustCompile(`\* (.+)`)
23+
multiLineMatches := multiLinePattern.FindAllStringSubmatch(errorMessage, -1)
2624

27-
caCertificate, err := readCertificateFromFile(certFilePath)
28-
assert.NoError(t, err)
29-
caPrivateKey, err := readRSAKeyFromFile(keyFilePath)
30-
assert.NoError(t, err)
25+
ansiCodePattern := regexp.MustCompile(`\x1b\[[0-9;]*m`)
26+
errorMessage = ansiCodePattern.ReplaceAllString(errorMessage, "")
3127

32-
return caCertificate, caPrivateKey
33-
}
34-
35-
func cleanupDirsForTest(t *testing.T, dirs ...string) {
36-
cleanupDirs := func() {
37-
for _, dir := range dirs {
38-
os.RemoveAll(dir)
28+
if len(multiLineMatches) > 0 {
29+
for _, match := range multiLineMatches {
30+
if len(match) > 1 {
31+
errors = append(errors, strings.TrimSpace(match[1]))
32+
}
3933
}
34+
} else {
35+
// Single line error
36+
cleanedError := strings.TrimSpace(errorMessage)
37+
errors = append(errors, cleanedError)
4038
}
4139

42-
cleanupDirs()
43-
t.Cleanup(cleanupDirs)
40+
return errors
41+
}
42+
43+
func readAndDecodeCertificateAndKey(t *testing.T, dir, name string) (*x509.Certificate, *rsa.PrivateKey) {
44+
// In the test suite, we often need to verify that a certificate and key pair exist in a given directory.
45+
// This is usually carried out after a call to the create_ca or create_node commands. This method reads the certificate
46+
// and key from the given directory and returns them. It will throw an error if the certificate or key cannot be
47+
// read from the given directory.
48+
49+
certPath := filepath.Join(dir, fmt.Sprintf("%s.crt", name))
50+
keyPath := filepath.Join(dir, fmt.Sprintf("%s.key", name))
51+
52+
ca, caErr := readCertificateFromFile(certPath)
53+
assert.NoError(t, caErr)
54+
55+
key, keyErr := readRSAKeyFromFile(keyPath)
56+
assert.NoError(t, keyErr)
57+
58+
return ca, key
4459
}

0 commit comments

Comments
 (0)