forked from emitter-io/emitter
/
config.go
199 lines (172 loc) · 5.91 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
194
195
196
197
198
199
/**********************************************************************************
* Copyright (c) 2009-2017 Misakai Ltd.
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free Software
* Foundation, either version 3 of the License, or(at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see<http://www.gnu.org/licenses/>.
************************************************************************************/
package config
import (
"bytes"
"crypto/sha1"
"encoding/json"
"io"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
"golang.org/x/crypto/pbkdf2"
)
// Constants used throughout the service.
const (
ChannelSeparator = '/' // The separator character.
MaxMessageSize = 65536 // Maximum message size allowed from/to the peer.
)
// SecretStore represents a contract for a store capable of resolving secrets.
type SecretStore interface {
Configure(c *Config) error
GetSecret(secretName string) (string, bool)
}
// NewDefault creates a default configuration.
func NewDefault() *Config {
return &Config{
TCPPort: ":8080",
TLSPort: ":8443",
Vault: &VaultConfig{},
Cluster: &ClusterConfig{
Broadcast: "public",
Port: 4000,
Seed: "127.0.0.1:4000",
ClusterKey: "emitter-io",
},
}
}
// Config represents main configuration.
type Config struct {
TCPPort string `json:"tcp"` // The API port used for TCP & Websocket communication.'
TLSPort string `json:"tls"` // The API port used for Secure TCP & Websocket communication.'
License string `json:"license"` // The port used for gossip.'
Vault *VaultConfig `json:"vault,omitempty"` // The configuration for the Hashicorp Vault.
Cluster *ClusterConfig `json:"cluster,omitempty"` // The configuration for the clustering.
}
// VaultConfig represents Vault configuration.
type VaultConfig struct {
Address string `json:"address"` // The vault address to use.
Application string `json:"app"` // The vault application ID to use.
}
// ClusterConfig represents the configuration for the cluster.
type ClusterConfig struct {
NodeName string `json:"node,omitempty"` // The name of the node to use.
Broadcast string `json:"broadcast"` // The address to broadcast.
Port int `json:"port"` // The port used for gossip.'
Seed string `json:"seed"` // The seed address (or a domain name) for cluster join.
ClusterKey string `json:"key"` // The cluster passphrase for the handshake.
}
// Key returns the key based on the passphrase
func (c *ClusterConfig) Key() []byte {
if c.ClusterKey == "" {
return nil
}
return pbkdf2.Key([]byte(c.ClusterKey), []byte("emitter"), 4096, 16, sha1.New)
}
// HasVault checks whether hashicorp vault endpoint is configured.
func (c *Config) HasVault() bool {
return c.Vault != nil && c.Vault.Address != "" && c.Vault.Application != ""
}
// Write writes the configuration to a specific writer, in JSON format.
func (c *Config) write(output io.Writer) (int, error) {
var formatted bytes.Buffer
body, err := json.Marshal(c)
if err != nil {
return 0, err
}
if err := json.Indent(&formatted, body, "", "\t"); err != nil {
return 0, err
}
return output.Write(formatted.Bytes())
}
// createDefault writes the default configuration to disk.
func createDefault(path string) (*Config, error) {
f, err := os.OpenFile(path, os.O_CREATE, os.ModePerm)
if err != nil {
return nil, err
}
defer f.Close()
c := NewDefault()
if _, err := c.write(f); err != nil {
return nil, err
}
if err := f.Sync(); err != nil {
return nil, err
}
return c, nil
}
// ReadOrCreate reads or creates the configuration object.
func ReadOrCreate(path string, stores ...SecretStore) (cfg *Config, err error) {
cfg = new(Config)
if _, err := os.Stat(path); os.IsNotExist(err) {
// Create a configuration and write it to a file
if cfg, err = createDefault(path); err != nil {
return nil, err
}
} else {
// Read the config from file
b, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
// Unmarshal the configuration
if err := json.Unmarshal(b, cfg); err != nil {
return nil, err
}
}
// Apply all the store overrides, in order
for _, store := range stores {
if err := store.Configure(cfg); err == nil {
cfg.declassify("emitter", store)
}
}
return cfg, nil
}
// Declassify traverses the configuration and resolves secrets.
func (c *Config) declassify(prefix string, provider SecretStore) {
original := reflect.ValueOf(c)
declassifyRecursive(prefix, provider, original)
}
// DeclassifyRecursive traverses the configuration and resolves secrets.
func declassifyRecursive(prefix string, provider SecretStore, value reflect.Value) {
switch value.Kind() {
case reflect.Ptr:
if value.Elem().IsValid() {
declassifyRecursive(prefix, provider, value.Elem())
}
// If it is a struct we translate each field
case reflect.Struct:
for i := 0; i < value.NumField(); i++ {
name := getFieldName(value.Type().Field(i))
declassifyRecursive(prefix+"/"+name, provider, value.Field(i))
}
// This is a integer, we need to fetch the secret
case reflect.Int:
if v, ok := provider.GetSecret(prefix); ok {
if iv, err := strconv.ParseInt(v, 10, 64); err == nil {
value.SetInt(iv)
}
}
// This is a string, we need to fetch the secret
case reflect.String:
if v, ok := provider.GetSecret(prefix); ok {
value.SetString(v)
}
}
}
func getFieldName(f reflect.StructField) string {
return strings.Replace(string(f.Tag.Get("json")), ",omitempty", "", -1)
}