Skip to content

Commit 47805b7

Browse files
committed
Added keyfile generation
1 parent bf50520 commit 47805b7

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

main.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ var (
5050
allPortOffsetsUnique bool
5151
jwtSecretFile string
5252
sslKeyFile string
53+
sslAutoKeyFile bool
54+
sslAutoServerName string
55+
sslAutoOrganization string
5356
sslCAFile string
5457
dockerEndpoint string
5558
dockerImage string
@@ -86,6 +89,9 @@ func init() {
8689
f.StringVar(&jwtSecretFile, "jwtSecretFile", "", "name of a plain text file containing a JWT secret used for server authentication")
8790
f.StringVar(&sslKeyFile, "sslKeyFile", "", "path of a PEM encoded file containing a server certificate + private key")
8891
f.StringVar(&sslCAFile, "sslCAFile", "", "path of a PEM encoded file containing a CA certificate used for client authentication")
92+
f.BoolVar(&sslAutoKeyFile, "sslAutoKeyFile", false, "If set, a self-signed certificate will be created and used as --sslKeyFile")
93+
f.StringVar(&sslAutoServerName, "sslAutoServerName", "", "Server name put into self-signed certificate. See --sslAutoKeyFile")
94+
f.StringVar(&sslAutoOrganization, "sslAutoOrganization", "ArangoDB", "Organization name put into self-signed certificate. See --sslAutoKeyFile")
8995
}
9096

9197
// handleSignal listens for termination signals and stops this process onup termination.
@@ -208,6 +214,30 @@ func cmdMainRun(cmd *cobra.Command, args []string) {
208214
jwtSecret = strings.TrimSpace(string(content))
209215
}
210216

217+
// Auto create key file (if needed)
218+
if sslAutoKeyFile {
219+
if sslKeyFile != "" {
220+
log.Fatalf("Cannot specify both --sslAutoKeyFile and --sslKeyFile")
221+
}
222+
hosts := []string{"arangod.server"}
223+
if sslAutoServerName != "" {
224+
hosts = []string{sslAutoServerName}
225+
}
226+
if ownAddress != "" {
227+
hosts = append(hosts, ownAddress)
228+
}
229+
keyFile, err := service.CreateCertificate(service.CreateCertificateOptions{
230+
Hosts: hosts,
231+
RSABits: 2048,
232+
Organization: sslAutoOrganization,
233+
}, dataDir)
234+
if err != nil {
235+
log.Fatalf("Failed to create keyfile: %v", err)
236+
}
237+
sslKeyFile = keyFile
238+
log.Infof("Using self-signed certificate: %s", sslKeyFile)
239+
}
240+
211241
// Interrupt signal:
212242
sigChannel := make(chan os.Signal)
213243
rootCtx, cancel := context.WithCancel(context.Background())

service/certificate.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package service
2+
3+
import (
4+
"crypto/rand"
5+
"crypto/rsa"
6+
"crypto/x509"
7+
"crypto/x509/pkix"
8+
"encoding/pem"
9+
"fmt"
10+
"io/ioutil"
11+
"math/big"
12+
"net"
13+
"time"
14+
)
15+
16+
// CreateCertificateOptions configures how to create a certificate.
17+
type CreateCertificateOptions struct {
18+
Hosts []string // Host names and/or IP addresses
19+
ValidFor time.Duration
20+
RSABits int
21+
Organization string
22+
}
23+
24+
const (
25+
defaultValidFor = time.Hour * 24 * 365 // 1year
26+
)
27+
28+
func publicKey(priv interface{}) interface{} {
29+
switch k := priv.(type) {
30+
case *rsa.PrivateKey:
31+
return &k.PublicKey
32+
default:
33+
return nil
34+
}
35+
}
36+
37+
func pemBlockForKey(priv interface{}) *pem.Block {
38+
switch k := priv.(type) {
39+
case *rsa.PrivateKey:
40+
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
41+
default:
42+
return nil
43+
}
44+
}
45+
46+
// CreateCertificate creates a self-signed certificate according to the given configuration.
47+
// The resulting certificate + private key will be written into a single file in the given folder.
48+
// The path of that single file is returned.
49+
func CreateCertificate(options CreateCertificateOptions, folder string) (string, error) {
50+
priv, err := rsa.GenerateKey(rand.Reader, options.RSABits)
51+
if err != nil {
52+
return "", maskAny(err)
53+
}
54+
55+
notBefore := time.Now()
56+
if options.ValidFor == 0 {
57+
options.ValidFor = defaultValidFor
58+
}
59+
notAfter := notBefore.Add(options.ValidFor)
60+
61+
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
62+
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
63+
if err != nil {
64+
return "", maskAny(fmt.Errorf("failed to generate serial number: %v", err))
65+
}
66+
67+
template := x509.Certificate{
68+
SerialNumber: serialNumber,
69+
Subject: pkix.Name{
70+
Organization: []string{options.Organization},
71+
},
72+
NotBefore: notBefore,
73+
NotAfter: notAfter,
74+
75+
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
76+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
77+
BasicConstraintsValid: true,
78+
}
79+
80+
for _, h := range options.Hosts {
81+
if ip := net.ParseIP(h); ip != nil {
82+
template.IPAddresses = append(template.IPAddresses, ip)
83+
} else {
84+
template.DNSNames = append(template.DNSNames, h)
85+
}
86+
}
87+
88+
// Create the certificate
89+
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
90+
if err != nil {
91+
return "", maskAny(fmt.Errorf("Failed to create certificate: %v", err))
92+
}
93+
94+
// Write the certificate to disk
95+
f, err := ioutil.TempFile(folder, "key-")
96+
if err != nil {
97+
return "", maskAny(err)
98+
}
99+
defer f.Close()
100+
// Public key
101+
pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
102+
// Private key
103+
pem.Encode(f, pemBlockForKey(priv))
104+
105+
return f.Name(), nil
106+
}

0 commit comments

Comments
 (0)