/
create_user.go
200 lines (165 loc) · 5.57 KB
/
create_user.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package certificates
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"flag"
"fmt"
"path/filepath"
"time"
multierror "github.com/hashicorp/go-multierror"
"github.com/mitchellh/cli"
)
type CreateUser struct {
Ui cli.Ui
Config CreateUserArguments
Flags *flag.FlagSet
}
type CreateUserArguments struct {
Username string `yaml:"username"`
CACertificatePath string `yaml:"ca-certificate"`
CAKeyPath string `yaml:"ca-key"`
Days int `yaml:"days"`
OutputDir string `yaml:"out"`
Name string `yaml:"name"`
Force bool `yaml:"force"`
}
func NewCreateUser(ui cli.Ui) *CreateUser {
c := &CreateUser{Ui: ui}
c.Flags = flag.NewFlagSet("create_user", flag.ContinueOnError)
c.Flags.Usage = func() { c.Ui.Info(c.Help()) }
c.Flags.StringVar(&c.Config.Username, "username", "", "the EventStoreDB user")
c.Flags.StringVar(&c.Config.CACertificatePath, "ca-certificate", "./ca/ca.crt", CaCertFlagUsage)
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
}
func (c *CreateUser) Run(args []string) int {
if err := c.Flags.Parse(args); err != nil {
c.Ui.Error(err.Error())
return 1
}
validationErrors := new(multierror.Error)
if len(c.Config.Username) == 0 {
_ = multierror.Append(validationErrors, errors.New("username is a required field"))
}
if len(c.Config.CACertificatePath) == 0 {
_ = multierror.Append(validationErrors, errors.New("ca-certificate is a required field"))
}
if len(c.Config.CAKeyPath) == 0 {
_ = multierror.Append(validationErrors, errors.New("ca-key is a required field"))
}
if c.Config.Days < 0 {
_ = multierror.Append(validationErrors, errors.New("days must be positive"))
}
if validationErrors.ErrorOrNil() != nil {
c.Ui.Error(validationErrors.Error())
return 1
}
caCert, err := readCertificateFromFile(c.Config.CACertificatePath)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
caKey, err := readRSAKeyFromFile(c.Config.CAKeyPath)
if err != nil {
err := fmt.Errorf("error: %s. please note that only RSA keys are currently supported", err.Error())
c.Ui.Error(err.Error())
return 1
}
outputDir := c.Config.OutputDir
outputBaseFileName := c.Config.Name
if outputBaseFileName == "" {
outputBaseFileName = "user-" + c.Config.Username
}
if outputDir == "" {
outputDir = filepath.Dir(outputBaseFileName)
}
certErr := checkCertificatesLocationWithForce(outputDir, outputBaseFileName, c.Config.Force)
if certErr != nil {
c.Ui.Error(certErr.Error())
return 1
}
/*default validity period*/
years := 1
days := 0
if c.Config.Days != 0 {
days = c.Config.Days
years = 0
}
err = generateUserCertificate(c.Config.Username, outputBaseFileName, caCert, caKey, years, days, outputDir, c.Config.Force)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
if isBoringEnabled() {
c.Ui.Output(fmt.Sprintf("A user certificate & key file have been generated in the '%s' directory (FIPS mode enabled).", outputDir))
} else {
c.Ui.Output(fmt.Sprintf("A user certificate & key file have been generated in the '%s' directory.", outputDir))
}
return 0
}
func generateUserCertificate(username string, outputBaseFileName string, caCert *x509.Certificate, caPrivateKey *rsa.PrivateKey, years int, days int, outputDir string, force bool) error {
serialNumber, err := generateSerialNumber(128)
if err != nil {
return fmt.Errorf("could not generate 128-bit serial number: %s", err.Error())
}
privateKey, err := rsa.GenerateKey(rand.Reader, defaultKeySize)
if err != nil {
return fmt.Errorf("could not generate RSA private key: %s", err.Error())
}
subjectKeyID := generateKeyIDFromRSAPublicKey(privateKey.N, privateKey.E)
authorityKeyID := generateKeyIDFromRSAPublicKey(caPrivateKey.N, caPrivateKey.E)
cert := &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: username,
},
IsCA: false,
BasicConstraintsValid: true,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(years, 0, days),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
SubjectKeyId: subjectKeyID,
AuthorityKeyId: authorityKeyID,
}
privateKeyPem := new(bytes.Buffer)
err = pem.Encode(privateKeyPem, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
})
if err != nil {
return fmt.Errorf("could not encode private key to PEM format: %s", err.Error())
}
certBytes, err := x509.CreateCertificate(rand.Reader, cert, caCert, &privateKey.PublicKey, caPrivateKey)
if err != nil {
return fmt.Errorf("could not generate certificate: %s", err.Error())
}
certPem := new(bytes.Buffer)
err = pem.Encode(certPem, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
if err != nil {
return fmt.Errorf("could not encode certificate to PEM format: %s", err.Error())
}
err = writeCertAndKey(outputDir, outputBaseFileName, certPem, privateKeyPem, force)
return err
}
func (c *CreateUser) Help() string {
var helpText bytes.Buffer
c.Flags.SetOutput(&helpText)
c.Flags.PrintDefaults()
return helpText.String()
}
func (c *CreateUser) Synopsis() string {
return "Generate a user TLS certificate to be used with EventStoreDB clients"
}