Skip to content

Commit

Permalink
Add support for user certs in create-certs command
Browse files Browse the repository at this point in the history
  • Loading branch information
w1am committed Mar 22, 2024
1 parent 0bc87a8 commit 0415a77
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 6 deletions.
18 changes: 17 additions & 1 deletion certificates/create_certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Config struct {
Certificates struct {
CaCerts []CreateCAArguments `yaml:"ca-certs"`
Nodes []CreateNodeArguments `yaml:"node-certs"`
Users []CreateUserArguments `yaml:"user-certs"`
} `yaml:"certificates"`
}

Expand Down Expand Up @@ -63,7 +64,7 @@ func (c *CreateCertificates) Run(args []string) int {
return 1
}

if c.generateCaCerts(config, c.Config.Force) != 0 || c.generateNodes(config, c.Config.Force) != 0 {
if c.generateCaCerts(config, c.Config.Force) != 0 || c.generateNodes(config, c.Config.Force) != 0 || c.generateUsers(config, c.Config.Force) != 0 {
return 1
}

Expand Down Expand Up @@ -99,6 +100,21 @@ func (c *CreateCertificates) checkPaths(config Config, force bool) error {
wg.Wait()
return certError
}

func (c *CreateCertificates) generateUsers(config Config, force bool) int {
for _, user := range config.Certificates.Users {
user.Force = force
createUser := NewCreateUser(&cli.ColoredUi{
Ui: c.Ui,
OutputColor: cli.UiColorBlue,
})
if createUser.Run(toArguments(user)) != 0 {
return 1
}
}
return 0
}

func (c *CreateCertificates) generateNodes(config Config, force bool) int {
for _, node := range config.Certificates.Nodes {
node.Force = force
Expand Down
31 changes: 29 additions & 2 deletions certificates/create_certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ func TestCreateCertificates_ValidConfigFile_ShouldSucceed(t *testing.T) {
assert.FileExists(t, filepath.Join(tempCertsDir, node, "node.crt"), fmt.Sprintf("%s certificate should exist", node))
assert.FileExists(t, filepath.Join(tempCertsDir, node, "node.key"), fmt.Sprintf("%s certificate key should exist", node))
}

assert.FileExists(t, filepath.Join(tempCertsDir, "user-admin", "user-admin.crt"), "User admin certificate should exist")
assert.FileExists(t, filepath.Join(tempCertsDir, "user-admin", "user-admin.key"), "User admin private key should exist")
}

func TestCreateCertificates_ExistingCertificatesWithoutForceFlag_ShouldFail(t *testing.T) {
Expand Down Expand Up @@ -82,6 +85,9 @@ func TestCreateCertificates_ExistingCertificatesWithoutForceFlag_ShouldFail(t *t
assert.FileExists(t, filepath.Join(tempCertsDir, node, "node.key"), fmt.Sprintf("%s certificate key should exist", node))
}

assert.FileExists(t, filepath.Join(tempCertsDir, "user-admin", "user-admin.crt"), "User admin certificate should exist")
assert.FileExists(t, filepath.Join(tempCertsDir, "user-admin", "user-admin.key"), "User admin private key should exist")

// Try to generate the certificates again and expect and error
result = createCerts.Run(args)
assert.Equal(t, 1, result, "The create-certs command should fail the second time it is run since the certificates already exist")
Expand Down Expand Up @@ -123,9 +129,13 @@ func TestCreateCertificates_ForceFlagWithExistingCertificates_ShouldRegenerate(t
assert.FileExists(t, filepath.Join(tempCertsDir, node, "node.key"), fmt.Sprintf("%s certificate key should exist", node))
}

assert.FileExists(t, filepath.Join(tempCertsDir, "user-admin", "user-admin.crt"), "User admin certificate should exist")
assert.FileExists(t, filepath.Join(tempCertsDir, "user-admin", "user-admin.key"), "User admin private key should exist")

// Read the content of the key and crt files generated from the config file
originalCaCert, originalKeyCert := readAndDecodeCertificateAndKey(t, filepath.Join(tempCertsDir, "root_ca"), "ca")
originalIntermediateCaCert, originalIntermediateKeyCert := readAndDecodeCertificateAndKey(t, filepath.Join(tempCertsDir, "intermediate_ca"), "ca")
originalUserCert, originalUserCertKey := readAndDecodeCertificateAndKey(t, filepath.Join(tempCertsDir, "user-admin"), "user-admin")

originalCerts := make(map[string][2]interface{})

Expand All @@ -145,13 +155,17 @@ func TestCreateCertificates_ForceFlagWithExistingCertificates_ShouldRegenerate(t

newRootCaCert, newRootCaKey := readAndDecodeCertificateAndKey(t, filepath.Join(tempCertsDir, "root_ca"), "ca")
newIntermediateCaCert, newIntermediateKeyCert := readAndDecodeCertificateAndKey(t, filepath.Join(tempCertsDir, "intermediate_ca"), "ca")
newUserCert, newUserCertKey := readAndDecodeCertificateAndKey(t, filepath.Join(tempCertsDir, "user-admin"), "user-admin")

assert.NotEqual(t, originalCaCert, newRootCaCert, "Root CA certificate should be regenerated")
assert.NotEqual(t, originalKeyCert, newRootCaKey, "Root CA key should be regenerated")

assert.NotEqual(t, originalIntermediateCaCert, newIntermediateCaCert, "Intermediate CA certificate should be regenerated")
assert.NotEqual(t, originalIntermediateKeyCert, newIntermediateKeyCert, "Intermediate CA key should be regenerated")

assert.NotEqual(t, originalUserCert, newUserCert, "User certificate should be regenerated")
assert.NotEqual(t, originalUserCertKey, newUserCertKey, "User certificate key should be regenerated")

for _, node := range nodes {
newCAHash, newKeyHash := readAndDecodeCertificateAndKey(t, filepath.Join(tempCertsDir, node), "node")
assert.NotEqual(t, originalCerts[node][0], newCAHash, fmt.Sprintf("%s certificate should be regenerated", node))
Expand Down Expand Up @@ -183,6 +197,8 @@ func TestCreateCertificates_ValidConfigWithCustomNames_ShouldCreateNamedCertific
assert.FileExists(t, filepath.Join(tempCertsDir, "custom_root", "custom_root.key"), "Root CA key should exist")
assert.FileExists(t, filepath.Join(tempCertsDir, "custom_intermediate", "custom_intermediate.crt"), "Intermediate certificate should exist")
assert.FileExists(t, filepath.Join(tempCertsDir, "custom_intermediate", "custom_intermediate.key"), "Intermediate certificate key should exist")
assert.FileExists(t, filepath.Join(tempCertsDir, "user-admin", "renamed.crt"), "User admin certificate should exist")
assert.FileExists(t, filepath.Join(tempCertsDir, "user-admin", "renamed.key"), "Intermediate certificate key should exist")

nodes := []string{"custom_node1", "custom_node2", "custom_node3"}
for _, node := range nodes {
Expand Down Expand Up @@ -250,7 +266,12 @@ var validCertificatesYaml = `certificates:
ca-certificate: "./intermediate_ca/ca.crt"
ca-key: "./intermediate_ca/ca.key"
ip-addresses: "127.0.0.3,172.20.240.3"
dns-names: "localhost,eventstore-node2.localhost.com"`
dns-names: "localhost,eventstore-node2.localhost.com"
user-certs:
- out: "./user-admin"
username: "admin"
ca-certificate: "./root_ca/ca.crt"
ca-key: "./root_ca/ca.key"`

// Invalid path defined at ca-certificate in the config
var certificatesYamlWithInvalidPath = `certificates:
Expand Down Expand Up @@ -295,7 +316,13 @@ var certificatesYamlWithOverrideName = `certificates:
ca-certificate: "./custom_intermediate/custom_intermediate.crt"
ca-key: "./custom_intermediate/custom_intermediate.key"
ip-addresses: "127.0.0.3,172.20.240.3"
dns-names: "localhost,eventstore-node2.localhost.com"`
dns-names: "localhost,eventstore-node2.localhost.com"
user-certs:
- out: "./user-admin"
username: "admin"
name: "renamed"
ca-certificate: "./custom_root/custom_root.crt"
ca-key: "./custom_root/custom_root.key"`

func setupCertificateTestEnvironment(t *testing.T) (cleanupFunc func(), tempCertsDir string, outputBuffer *bytes.Buffer, errorBuffer *bytes.Buffer, createCerts *CreateCertificates) {
tempCertsDir, err := os.MkdirTemp(os.TempDir(), "certs-*")
Expand Down
12 changes: 9 additions & 3 deletions certificates/create_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"errors"
"flag"
"fmt"
"path/filepath"
"time"

multierror "github.com/hashicorp/go-multierror"
Expand All @@ -28,6 +29,7 @@ type CreateUserArguments struct {
CAKeyPath string `yaml:"ca-key"`
Days int `yaml:"days"`
OutputDir string `yaml:"out"`
Name string `yaml:"name"`
Force bool `yaml:"force"`
}

Expand All @@ -41,6 +43,7 @@ func NewCreateUser(ui cli.Ui) *CreateUser {
c.Flags.StringVar(&c.Config.CAKeyPath, "ca-key", "./ca/ca.key", CaKeyFlagUsage)
c.Flags.IntVar(&c.Config.Days, "days", 0, DayFlagUsage)
c.Flags.StringVar(&c.Config.OutputDir, "out", "", OutDirFlagUsage)
c.Flags.StringVar(&c.Config.Name, "name", "", NameFlagUsage)
c.Flags.BoolVar(&c.Config.Force, "force", false, ForceFlagUsage)

return c
Expand Down Expand Up @@ -89,10 +92,13 @@ func (c *CreateUser) Run(args []string) int {
}

outputDir := c.Config.OutputDir
outputBaseFileName := "user-" + c.Config.Username
outputBaseFileName := c.Config.Name
if outputBaseFileName == "" {
outputBaseFileName = "user-" + c.Config.Username
}

if len(outputDir) == 0 {
outputDir = outputBaseFileName
if outputDir == "" {
outputDir = filepath.Dir(outputBaseFileName)
}

certErr := checkCertificatesLocationWithForce(outputDir, outputBaseFileName, c.Config.Force)
Expand Down
31 changes: 31 additions & 0 deletions certificates/create_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func TestCreateUserCertificate(t *testing.T) {
t.Run("TestCreateUserCertificate_WithAllRequiredParams_ShouldSucceed", TestCreateUserCertificate_WithAllRequiredParams_ShouldSucceed)
t.Run("TestCreateUserCertificate_WithNegativeDays_ShouldFail", TestCreateUserCertificate_WithNegativeDays_ShouldFail)
t.Run("TestCreateUserCertificate_WithForceFlag_ShouldRegenerate", TestCreateUserCertificate_WithForceFlag_ShouldRegenerate)
t.Run("TestCreateUserCertificate_WithNameFlag_ShouldSucceed", TestCreateUserCertificate_WithNameFlag_ShouldSucceed)
}

func TestCreateUserCertificate_WithoutParams_ShouldFail(t *testing.T) {
Expand Down Expand Up @@ -74,6 +75,7 @@ func TestCreateUserCertificate_WithAllRequiredParams_ShouldSucceed(t *testing.T)

_, err = cert.Verify(x509.VerifyOptions{Roots: roots, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}})
assert.NoError(t, err, "User certificate should be signed by the provided root CA")
assert.Equal(t, username, cert.Subject.CommonName, "The common name of the certificate should be the same as the provided username")
}

func TestCreateUserCertificate_WithNegativeDays_ShouldFail(t *testing.T) {
Expand Down Expand Up @@ -126,6 +128,35 @@ func TestCreateUserCertificate_WithForceFlag_ShouldRegenerate(t *testing.T) {
assert.NotEqual(t, originalUserKey, newUserKey, "The User key should be different")
}

func TestCreateUserCertificate_WithNameFlag_ShouldSucceed(t *testing.T) {
t.Parallel()

cleanup, tempUserDir, tempCaDir, _, _, createUser := setupCreateUserTestEnvironment(t)
defer cleanup()

username := "ouro"
name := "testing"
args := []string{
"-username", username,
"-name", name,
"-ca-certificate", filepath.Join(tempCaDir, "ca.crt"),
"-ca-key", filepath.Join(tempCaDir, "ca.key"),
"-out", tempUserDir,
}

result := createUser.Run(args)

assert.Equal(t, 0, result, "The 'create-user' create the certificates with the provided name")

assert.FileExists(t, filepath.Join(tempUserDir, name+".crt"), "User certificate should exist")
assert.FileExists(t, filepath.Join(tempUserDir, name+".key"), "User key should exist")

cert, err := readCertificateFromFile(filepath.Join(tempUserDir, name+".crt"))
assert.NoError(t, err, "Failed to read and parse certificate file")

assert.Equal(t, username, cert.Subject.CommonName, "The common name of the certificate should be the same as the provided username")
}

func setupCreateUserTestEnvironment(t *testing.T) (cleanupFunc func(), tempUserDir string, tempCaDir string, outputBuffer *bytes.Buffer, errorBuffer *bytes.Buffer, createUser *CreateUser) {
var err error

Expand Down
5 changes: 5 additions & 0 deletions references/certs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ certificates:
ca-key: "./intermediate_ca/ca.key"
ip-addresses: "127.0.0.3,172.20.240.3"
dns-names: "localhost,eventstore-node2.localhost.com"
user-certs:
- out: "./user-admin"
username: "admin"
ca-certificate: "./root_ca/ca.crt"
ca-key: "./root_ca/ca.key"
6 changes: 6 additions & 0 deletions references/named_certs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,9 @@ certificates:
ca-key: "./intermediate_ca/intermediate.key"
ip-addresses: "127.0.0.3,172.20.240.3"
dns-names: "localhost,eventstore-node2.localhost.com"
user-certs:
- out: "./user-admin"
username: "admin"
name: "admin"
ca-certificate: "./root_ca/root.crt"
ca-key: "./root_ca/root.key"

0 comments on commit 0415a77

Please sign in to comment.