/
config.go
193 lines (152 loc) · 5.57 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
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
package session
import (
"bufio"
"database/sql"
"encoding/hex"
"flag"
"fmt"
"code.grantmurray.com/mailbot"
"github.com/golang/glog"
_ "github.com/lib/pq"
"os"
)
/* Config holds the values loaded from the database. The database dataSourceName is bootstrapped from standard input.
Why store configuration in the database rather than other alternatives:
1. Because any running service's configuration can be observered accurately (dubious reason)
2. ALL data about the service is in ONE place only
3. No special configuration file parser is needed
4. Values can have their types enforced by the database
*/
type Config struct {
// *** from standard input ***
// DatabaseSource is the string used to open the PostgreSQL database
DatabaseSource string
// ServerKey is a secret used in hashing of passwords it
// is not stored in the database because that would
// diminish security
ServerKey []byte // entered as hex digit string
// *** from database table Session.config ***
// SessionTimeout in number of seconds
SessionTimeout int
// SessionMaxLife in number of seconds, session forced to expire
SessionMaxLife int
// DatabaseHandle is here as a convenience since we have it and
// it is deriviative of DatabaseSource and the only thing to do with
// DatabaseSource is to open a connection
DatabaseHandle *sql.DB
// Smtp is the outgoing smtp config as needed by mailbot'
Smtp mailbot.ServerConfig
// VerifyTemplate is the text template used when sending the email
// used to verify the email address of a new user
VerifyTemplate string
// ResetTemplate is the text template used when sending the email
// to reset a password
ResetTemplate string
// PasswordResetExpirationDuration is the number of seconds that a reset token will be valid for
PasswordResetExpiresDuration int
// *** instance specific ***
InstanceId string
// Certificates and key that are needed to start an https server
// (see create-test-certs.sh for some hints on loading these)
HttpsKey []byte
HttpsCert []byte
// HttpsHost is the address the server will listen on
// for example: "plog.org"
HttpsHost string
// HttpsPort is the port that the server will listen on
HttpsPort int
}
// Conf is a package global so that it is accessible everywhere.
var Conf *Config
// bootstrap sets DatabaseSource and ServerKey from stdin
func (c *Config) bootstrap() {
var scanner *bufio.Scanner
tfile, err := os.Open("/tmp/session_test.config.bootstrap.input")
if err == nil {
defer tfile.Close()
scanner = bufio.NewScanner(tfile)
} else {
scanner = bufio.NewScanner(os.Stdin)
}
// DatabaseSource
fmt.Print("Enter the PostgreSQL source string: ")
scanner.Scan()
c.DatabaseSource = scanner.Text()
fmt.Println()
// ServerKey
fmt.Print("Enter the secret server key: ")
scanner.Scan()
sk := scanner.Text()
fmt.Println()
if err = scanner.Err(); err != nil {
panic(fmt.Sprintf("Error reading input: %s", err))
}
c.ServerKey, err = hex.DecodeString(sk)
if err != nil {
panic(fmt.Sprintf("Failed to convert SERVERKEY [%s] to bytes: %s", sk, err))
}
}
func init() {
var err error
Conf = new(Config)
Conf.bootstrap()
flag.StringVar(&Conf.InstanceId, "instance", "None", "An identifier for the instance, used to fetch the instance specific configuration")
if len(Conf.ServerKey) != 64 {
panic(fmt.Sprintf("Serverkey needs to be 64 bytes long exactly, it was only %d bytes", len(Conf.ServerKey)))
}
Conf.DatabaseHandle, err = sql.Open("postgres", Conf.DatabaseSource)
if err != nil {
panic(err)
}
}
// Load reads configuration values from database tables and loads them into the global variable Conf.
func LoadConfig() {
var err error
// LoadConfig would ideally be done in init() but since loading depends on flag values it needs to be done after flag.Parse()
if len(Conf.InstanceId) == 0 || Conf.InstanceId == "None" {
panic("Command line parameter `instance` is missing, cannot complete config")
}
var iloaded bool = false
rows, err := Conf.DatabaseHandle.Query("SELECT SessionTimeout, SessionMaxLife, SmtpServerHost, SmtpServerPort, SmtpFrom, SmtpAuthUsername, SmtpAuthPassword, VerifyTemplate, ResetTemplate, PasswordResetExpiresDuration FROM session.config")
if err != nil {
panic(err)
}
for rows.Next() {
if err := rows.Scan(&Conf.SessionTimeout, &Conf.SessionMaxLife,
&Conf.Smtp.Host, &Conf.Smtp.Port, &Conf.Smtp.EmailFrom, &Conf.Smtp.User, &Conf.Smtp.Password,
&Conf.VerifyTemplate, &Conf.ResetTemplate, &Conf.PasswordResetExpiresDuration); err != nil {
panic(err)
}
iloaded = true
}
if err := rows.Err(); err != nil {
panic(err)
}
if !iloaded {
panic("Configuration failed to load")
}
if glog.V(1) {
glog.Info("Common configuration loaded from database")
}
iloaded = false
rows, err = Conf.DatabaseHandle.Query("SELECT HttpsKey, HttpsCert, HttpsHost, HttpsPort FROM session.instconfig WHERE InstanceId = $1", Conf.InstanceId)
if err != nil {
panic(fmt.Sprintf("instance:%s, err:%s", Conf.InstanceId, err))
}
for rows.Next() {
if err := rows.Scan(&Conf.HttpsKey, &Conf.HttpsCert, &Conf.HttpsHost, &Conf.HttpsPort); err != nil {
panic(err)
}
iloaded = true
}
if err := rows.Err(); err != nil {
panic(err)
}
if !iloaded {
panic(fmt.Sprintf("Missing instance configuration for %s", Conf.InstanceId))
}
if glog.V(1) {
glog.Info(fmt.Sprintf("Configuration for instance %s (%s:%d) loaded from database", Conf.InstanceId, Conf.HttpsHost, Conf.HttpsPort))
}
glog.Flush()
}