1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
16
"github.com/juju/errors"
17
"github.com/juju/schema"
18
"github.com/juju/utils"
20
"github.com/juju/juju/cert"
21
"github.com/juju/juju/juju/osenv"
25
// AdminSecretKey is the attribute key for the administrator password.
26
AdminSecretKey = "admin-secret"
28
// CACertKey is the attribute key for the controller's CA certificate.
31
// CAPrivateKeyKey is the key for the controller's CA certificate private key.
32
CAPrivateKeyKey = "ca-private-key"
34
// BootstrapTimeoutKey is the attribute key for the amount of time to wait
35
// for bootstrap to complete.
36
BootstrapTimeoutKey = "bootstrap-timeout"
38
// BootstrapRetryDelayKey is the attribute key for the amount of time
39
// in between attempts to connect to a bootstrap machine address.
40
BootstrapRetryDelayKey = "bootstrap-retry-delay"
42
// BootstrapAddressesDelayKey is the attribute key for the amount of
43
// time in between refreshing the bootstrap machine addresses.
44
BootstrapAddressesDelayKey = "bootstrap-addresses-delay"
50
// DefaultBootstrapSSHTimeout is the amount of time to wait
51
// contacting a controller, in seconds.
52
DefaultBootstrapSSHTimeout = 1200
54
// DefaultBootstrapSSHRetryDelay is the amount of time between
55
// attempts to connect to an address, in seconds.
56
DefaultBootstrapSSHRetryDelay = 5
58
// DefaultBootstrapSSHAddressesDelay is the amount of time betwee
59
// refreshing the addresses, in seconds. Not too frequent, as we
60
// refresh addresses from the provider each time.
61
DefaultBootstrapSSHAddressesDelay = 10
64
// BootstrapConfigAttributes are attributes which may be defined by the
65
// user at bootstrap time, but should not be present in general controller
67
var BootstrapConfigAttributes = []string{
72
BootstrapRetryDelayKey,
73
BootstrapAddressesDelayKey,
76
// IsBootstrapAttribute reports whether or not the specified
77
// attribute name is only relevant during bootstrap.
78
func IsBootstrapAttribute(attr string) bool {
79
for _, a := range BootstrapConfigAttributes {
87
// Config contains bootstrap-specific configuration.
92
BootstrapTimeout time.Duration
93
BootstrapRetryDelay time.Duration
94
BootstrapAddressesDelay time.Duration
97
// Validate validates the controller configuration.
98
func (c Config) Validate() error {
99
if c.AdminSecret == "" {
100
return errors.NotValidf("empty " + AdminSecretKey)
102
if _, err := tls.X509KeyPair([]byte(c.CACert), []byte(c.CAPrivateKey)); err != nil {
103
return errors.Annotatef(err, "validating %s and %s", CACertKey, CAPrivateKeyKey)
105
if c.BootstrapTimeout <= 0 {
106
return errors.NotValidf("%s of %s", BootstrapTimeoutKey, c.BootstrapTimeout)
108
if c.BootstrapRetryDelay <= 0 {
109
return errors.NotValidf("%s of %s", BootstrapRetryDelayKey, c.BootstrapRetryDelay)
111
if c.BootstrapAddressesDelay <= 0 {
112
return errors.NotValidf("%s of %s", BootstrapAddressesDelayKey, c.BootstrapAddressesDelay)
117
// NewConfig creates a new Config from the supplied attributes.
118
// Default values will be used where defaults are available.
120
// If ca-cert or ca-private-key are not set, then we will check
121
// if ca-cert-path or ca-private-key-path are set, and read the
122
// contents. If none of those are set, we will look for files
123
// in well-defined locations: $JUJU_DATA/ca-cert.pem, and
124
// $JUJU_DATA/ca-private-key.pem. If none of these are set, an
125
// error is returned.
126
func NewConfig(controllerUUID string, attrs map[string]interface{}) (Config, error) {
127
coerced, err := configChecker.Coerce(attrs, nil)
129
return Config{}, errors.Trace(err)
131
attrs = coerced.(map[string]interface{})
133
BootstrapTimeout: time.Duration(attrs[BootstrapTimeoutKey].(int)) * time.Second,
134
BootstrapRetryDelay: time.Duration(attrs[BootstrapRetryDelayKey].(int)) * time.Second,
135
BootstrapAddressesDelay: time.Duration(attrs[BootstrapAddressesDelayKey].(int)) * time.Second,
138
if adminSecret, ok := attrs[AdminSecretKey].(string); ok {
139
config.AdminSecret = adminSecret
141
// Generate a random admin secret.
142
buf := make([]byte, 16)
143
if _, err := io.ReadFull(rand.Reader, buf); err != nil {
144
return Config{}, errors.Annotate(err, "generating random "+AdminSecretKey)
146
config.AdminSecret = fmt.Sprintf("%x", buf)
149
if caCert, ok := attrs[CACertKey].(string); ok {
150
config.CACert = caCert
152
var userSpecified bool
154
config.CACert, userSpecified, err = readFileAttr(attrs, CACertKey, CACertKey+".pem")
155
if err != nil && (userSpecified || !os.IsNotExist(errors.Cause(err))) {
156
return Config{}, errors.Annotatef(err, "reading %q from file", CACertKey)
160
if caPrivateKey, ok := attrs[CAPrivateKeyKey].(string); ok {
161
config.CAPrivateKey = caPrivateKey
163
var userSpecified bool
165
config.CAPrivateKey, userSpecified, err = readFileAttr(attrs, CAPrivateKeyKey, CAPrivateKeyKey+".pem")
166
if err != nil && (userSpecified || !os.IsNotExist(errors.Cause(err))) {
167
return Config{}, errors.Annotatef(err, "reading %q from file", CAPrivateKeyKey)
171
if config.CACert == "" && config.CAPrivateKey == "" {
172
// Generate a new CA certificate and private key.
173
// TODO(perrito666) 2016-05-02 lp:1558657
174
expiry := time.Now().UTC().AddDate(10, 0, 0)
175
uuid, err := utils.NewUUID()
177
return Config{}, errors.Annotate(err, "generating UUID for CA certificate")
179
caCert, caKey, err := cert.NewCA("juju-ca", uuid.String(), expiry)
181
return Config{}, errors.Trace(err)
183
config.CACert = caCert
184
config.CAPrivateKey = caKey
187
return config, config.Validate()
190
// readFileAttr reads the contents of an attribute from a file, if the
191
// corresponding "-path" attribute is set, or otherwise from a default
193
func readFileAttr(attrs map[string]interface{}, key, defaultPath string) (content string, userSpecified bool, _ error) {
194
if !osenv.IsJujuXDGDataHomeSet() {
195
return "", false, errors.Errorf("$JUJU_DATA is not set, cannot read %q", key)
197
path, ok := attrs[key+"-path"].(string)
203
absPath, err := utils.NormalizePath(path)
205
return "", userSpecified, errors.Trace(err)
207
if !filepath.IsAbs(absPath) {
208
absPath = osenv.JujuXDGDataHomePath(absPath)
210
data, err := ioutil.ReadFile(absPath)
212
return "", userSpecified, errors.Annotatef(err, "%q not set, and could not read from %q", key, path)
215
return "", userSpecified, errors.Errorf("file %q is empty", path)
217
return string(data), userSpecified, nil
220
var configChecker = schema.FieldMap(schema.Fields{
221
AdminSecretKey: schema.String(),
222
CACertKey: schema.String(),
223
CACertKey + "-path": schema.String(),
224
CAPrivateKeyKey: schema.String(),
225
CAPrivateKeyKey + "-path": schema.String(),
226
BootstrapTimeoutKey: schema.ForceInt(),
227
BootstrapRetryDelayKey: schema.ForceInt(),
228
BootstrapAddressesDelayKey: schema.ForceInt(),
230
AdminSecretKey: schema.Omit,
231
CACertKey: schema.Omit,
232
CACertKey + "-path": schema.Omit,
233
CAPrivateKeyKey: schema.Omit,
234
CAPrivateKeyKey + "-path": schema.Omit,
235
BootstrapTimeoutKey: DefaultBootstrapSSHTimeout,
236
BootstrapRetryDelayKey: DefaultBootstrapSSHRetryDelay,
237
BootstrapAddressesDelayKey: DefaultBootstrapSSHAddressesDelay,