github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/environs/bootstrap/config.go (about) 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(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 path, ok := attrs[key+"-path"].(string) 195 if ok { 196 userSpecified = true 197 } else { 198 path = defaultPath 199 } 200 absPath, err := utils.NormalizePath(path) 201 if err != nil { 202 return "", userSpecified, errors.Trace(err) 203 } 204 if !filepath.IsAbs(absPath) { 205 absPath = osenv.JujuXDGDataHomePath(absPath) 206 } 207 data, err := ioutil.ReadFile(absPath) 208 if err != nil { 209 return "", userSpecified, errors.Annotatef(err, "%q not set, and could not read from %q", key, path) 210 } 211 if len(data) == 0 { 212 return "", userSpecified, errors.Errorf("file %q is empty", path) 213 } 214 return string(data), userSpecified, nil 215 } 216 217 var configChecker = schema.FieldMap(schema.Fields{ 218 AdminSecretKey: schema.String(), 219 CACertKey: schema.String(), 220 CACertKey + "-path": schema.String(), 221 CAPrivateKeyKey: schema.String(), 222 CAPrivateKeyKey + "-path": schema.String(), 223 BootstrapTimeoutKey: schema.ForceInt(), 224 BootstrapRetryDelayKey: schema.ForceInt(), 225 BootstrapAddressesDelayKey: schema.ForceInt(), 226 }, schema.Defaults{ 227 AdminSecretKey: schema.Omit, 228 CACertKey: schema.Omit, 229 CACertKey + "-path": schema.Omit, 230 CAPrivateKeyKey: schema.Omit, 231 CAPrivateKeyKey + "-path": schema.Omit, 232 BootstrapTimeoutKey: DefaultBootstrapSSHTimeout, 233 BootstrapRetryDelayKey: DefaultBootstrapSSHRetryDelay, 234 BootstrapAddressesDelayKey: DefaultBootstrapSSHAddressesDelay, 235 })