~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/environs/bootstrap/config.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2016 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package bootstrap
 
5
 
 
6
import (
 
7
        "crypto/rand"
 
8
        "crypto/tls"
 
9
        "fmt"
 
10
        "io"
 
11
        "io/ioutil"
 
12
        "os"
 
13
        "path/filepath"
 
14
        "time"
 
15
 
 
16
        "github.com/juju/errors"
 
17
        "github.com/juju/schema"
 
18
        "github.com/juju/utils"
 
19
 
 
20
        "github.com/juju/juju/cert"
 
21
        "github.com/juju/juju/juju/osenv"
 
22
)
 
23
 
 
24
const (
 
25
        // AdminSecretKey is the attribute key for the administrator password.
 
26
        AdminSecretKey = "admin-secret"
 
27
 
 
28
        // CACertKey is the attribute key for the controller's CA certificate.
 
29
        CACertKey = "ca-cert"
 
30
 
 
31
        // CAPrivateKeyKey is the key for the controller's CA certificate private key.
 
32
        CAPrivateKeyKey = "ca-private-key"
 
33
 
 
34
        // BootstrapTimeoutKey is the attribute key for the amount of time to wait
 
35
        // for bootstrap to complete.
 
36
        BootstrapTimeoutKey = "bootstrap-timeout"
 
37
 
 
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"
 
41
 
 
42
        // BootstrapAddressesDelayKey is the attribute key for the amount of
 
43
        // time in between refreshing the bootstrap machine addresses.
 
44
        BootstrapAddressesDelayKey = "bootstrap-addresses-delay"
 
45
)
 
46
 
 
47
const (
 
48
        // Attribute Defaults
 
49
 
 
50
        // DefaultBootstrapSSHTimeout is the amount of time to wait
 
51
        // contacting a controller, in seconds.
 
52
        DefaultBootstrapSSHTimeout = 1200
 
53
 
 
54
        // DefaultBootstrapSSHRetryDelay is the amount of time between
 
55
        // attempts to connect to an address, in seconds.
 
56
        DefaultBootstrapSSHRetryDelay = 5
 
57
 
 
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
 
62
)
 
63
 
 
64
// BootstrapConfigAttributes are attributes which may be defined by the
 
65
// user at bootstrap time, but should not be present in general controller
 
66
// config.
 
67
var BootstrapConfigAttributes = []string{
 
68
        AdminSecretKey,
 
69
        CACertKey,
 
70
        CAPrivateKeyKey,
 
71
        BootstrapTimeoutKey,
 
72
        BootstrapRetryDelayKey,
 
73
        BootstrapAddressesDelayKey,
 
74
}
 
75
 
 
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 {
 
80
                if attr == a {
 
81
                        return true
 
82
                }
 
83
        }
 
84
        return false
 
85
}
 
86
 
 
87
// Config contains bootstrap-specific configuration.
 
88
type Config struct {
 
89
        AdminSecret             string
 
90
        CACert                  string
 
91
        CAPrivateKey            string
 
92
        BootstrapTimeout        time.Duration
 
93
        BootstrapRetryDelay     time.Duration
 
94
        BootstrapAddressesDelay time.Duration
 
95
}
 
96
 
 
97
// Validate validates the controller configuration.
 
98
func (c Config) Validate() error {
 
99
        if c.AdminSecret == "" {
 
100
                return errors.NotValidf("empty " + AdminSecretKey)
 
101
        }
 
102
        if _, err := tls.X509KeyPair([]byte(c.CACert), []byte(c.CAPrivateKey)); err != nil {
 
103
                return errors.Annotatef(err, "validating %s and %s", CACertKey, CAPrivateKeyKey)
 
104
        }
 
105
        if c.BootstrapTimeout <= 0 {
 
106
                return errors.NotValidf("%s of %s", BootstrapTimeoutKey, c.BootstrapTimeout)
 
107
        }
 
108
        if c.BootstrapRetryDelay <= 0 {
 
109
                return errors.NotValidf("%s of %s", BootstrapRetryDelayKey, c.BootstrapRetryDelay)
 
110
        }
 
111
        if c.BootstrapAddressesDelay <= 0 {
 
112
                return errors.NotValidf("%s of %s", BootstrapAddressesDelayKey, c.BootstrapAddressesDelay)
 
113
        }
 
114
        return nil
 
115
}
 
116
 
 
117
// NewConfig creates a new Config from the supplied attributes.
 
118
// Default values will be used where defaults are available.
 
119
//
 
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)
 
128
        if err != nil {
 
129
                return Config{}, errors.Trace(err)
 
130
        }
 
131
        attrs = coerced.(map[string]interface{})
 
132
        config := Config{
 
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,
 
136
        }
 
137
 
 
138
        if adminSecret, ok := attrs[AdminSecretKey].(string); ok {
 
139
                config.AdminSecret = adminSecret
 
140
        } else {
 
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)
 
145
                }
 
146
                config.AdminSecret = fmt.Sprintf("%x", buf)
 
147
        }
 
148
 
 
149
        if caCert, ok := attrs[CACertKey].(string); ok {
 
150
                config.CACert = caCert
 
151
        } else {
 
152
                var userSpecified bool
 
153
                var err error
 
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)
 
157
                }
 
158
        }
 
159
 
 
160
        if caPrivateKey, ok := attrs[CAPrivateKeyKey].(string); ok {
 
161
                config.CAPrivateKey = caPrivateKey
 
162
        } else {
 
163
                var userSpecified bool
 
164
                var err error
 
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)
 
168
                }
 
169
        }
 
170
 
 
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()
 
176
                if err != nil {
 
177
                        return Config{}, errors.Annotate(err, "generating UUID for CA certificate")
 
178
                }
 
179
                caCert, caKey, err := cert.NewCA("juju-ca", uuid.String(), expiry)
 
180
                if err != nil {
 
181
                        return Config{}, errors.Trace(err)
 
182
                }
 
183
                config.CACert = caCert
 
184
                config.CAPrivateKey = caKey
 
185
        }
 
186
 
 
187
        return config, config.Validate()
 
188
}
 
189
 
 
190
// readFileAttr reads the contents of an attribute from a file, if the
 
191
// corresponding "-path" attribute is set, or otherwise from a default
 
192
// path.
 
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)
 
196
        }
 
197
        path, ok := attrs[key+"-path"].(string)
 
198
        if ok {
 
199
                userSpecified = true
 
200
        } else {
 
201
                path = defaultPath
 
202
        }
 
203
        absPath, err := utils.NormalizePath(path)
 
204
        if err != nil {
 
205
                return "", userSpecified, errors.Trace(err)
 
206
        }
 
207
        if !filepath.IsAbs(absPath) {
 
208
                absPath = osenv.JujuXDGDataHomePath(absPath)
 
209
        }
 
210
        data, err := ioutil.ReadFile(absPath)
 
211
        if err != nil {
 
212
                return "", userSpecified, errors.Annotatef(err, "%q not set, and could not read from %q", key, path)
 
213
        }
 
214
        if len(data) == 0 {
 
215
                return "", userSpecified, errors.Errorf("file %q is empty", path)
 
216
        }
 
217
        return string(data), userSpecified, nil
 
218
}
 
219
 
 
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(),
 
229
}, schema.Defaults{
 
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,
 
238
})