/
config.go
160 lines (129 loc) · 3.81 KB
/
config.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
package main
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"github.com/go-acme/lego/v4/log"
"gopkg.in/yaml.v2"
)
type certConfig struct {
AccountEmail string `yaml:"account_email"`
AccountKey string `yaml:"account_key"`
CA string
Challenge string
CreateKeyIfMissing *bool `yaml:"create_key_if_missing"` // boolean pointer here to differentiate empty value from zero value
Description string
Domains []string
DumpPath string `yaml:"dump_path,omitempty"`
Env map[string]string `json:",omitempty" yaml:",omitempty"`
KeyType string `yaml:"key_type"`
Name string
Provider string `json:",omitempty" yaml:",omitempty"`
}
// We guarantee that a certConfig has a private key
// If it does not yet, create it here
func (cc certConfig) getPrivateKey() (crypto.PrivateKey, error) {
// See https://stackoverflow.com/questions/21322182/how-to-store-ecdsa-private-key-in-go
if _, err := os.Stat(cc.AccountKey); os.IsNotExist(err) && *cc.CreateKeyIfMissing {
// Create a new key, and save it to disk
// - cipher: ECDSA
// - encoding: x509
// - file format: pem
log.Infof("config: Generating new key ( %s does not exist )", cc.AccountKey)
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, fmt.Errorf("config: %v", err)
}
x509Encoded, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return nil, fmt.Errorf("config: %v", err)
}
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509Encoded})
err = ioutil.WriteFile(cc.AccountKey, pemEncoded, 0400)
if err != nil {
// TODO: delete file here
return nil, fmt.Errorf("config: %v", err)
}
return privateKey, nil
}
// Read private key from disk
log.Infof("config: Reading key from %s", cc.AccountKey)
b, err := ioutil.ReadFile(cc.AccountKey)
if err != nil {
return nil, fmt.Errorf("config: %v", err)
}
block, _ := pem.Decode(b)
if block == nil {
return nil, fmt.Errorf("config: unable to read PEM data from %s", cc.AccountKey)
}
x509Encoded := block.Bytes
privateKey, err := x509.ParseECPrivateKey(x509Encoded)
if err != nil {
return nil, fmt.Errorf("config: %v", err)
}
return privateKey, nil
}
type defaultConfig struct {
AccountEmail string `yaml:"account_email"`
AccountKey string `yaml:"account_key"`
CA string
Challenge string
CreateKeyIfMissing bool `yaml:"create_key_if_missing"`
Description string
DumpPath string `yaml:"dump_path,omitempty"`
KeyType string `yaml:"key_type"`
Provider string `json:",omitempty" yaml:",omitempty"`
}
type globalConfig struct {
Default defaultConfig
Certs []certConfig
}
func loadConfig(path string) (config globalConfig, err error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return config, err
}
err = yaml.Unmarshal(data, &config)
if err != nil {
return config, err
}
return config, nil
}
// Replace empty values by default
func mergeDefaultConfig(cc certConfig, dc defaultConfig) certConfig {
if cc.AccountEmail == "" {
cc.AccountEmail = dc.AccountEmail
}
if cc.AccountKey == "" {
cc.AccountKey = dc.AccountKey
}
if cc.CA == "" {
cc.CA = dc.CA
}
if cc.Challenge == "" {
cc.Challenge = dc.Challenge
}
if cc.CreateKeyIfMissing == nil {
cc.CreateKeyIfMissing = &dc.CreateKeyIfMissing
}
if cc.Description == "" {
// Instantiate default config format specifier
cc.Description = fmt.Sprintf(dc.Description, cc.Name)
}
if cc.DumpPath == "" {
cc.DumpPath = dc.DumpPath
}
if cc.KeyType == "" {
cc.KeyType = dc.KeyType
}
if cc.Provider == "" {
cc.Provider = dc.Provider
}
return cc
}