Merge lp:~axwalk/juju-core/null-provider-storage-auth into lp:~go-bot/juju-core/trunk
- null-provider-storage-auth
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Andrew Wilkins |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1921 |
Proposed branch: | lp:~axwalk/juju-core/null-provider-storage-auth |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
1012 lines (+525/-98) 12 files modified
environs/httpstorage/backend.go (+56/-19) environs/httpstorage/backend_test.go (+27/-7) environs/httpstorage/storage.go (+55/-26) environs/httpstorage/storage_test.go (+35/-14) environs/manual/bootstrap.go (+3/-6) provider/null/config.go (+6/-0) provider/null/config_test.go (+4/-3) provider/null/environ.go (+51/-1) provider/null/provider.go (+9/-2) worker/localstorage/config.go (+112/-0) worker/localstorage/config_test.go (+132/-0) worker/localstorage/worker.go (+35/-20) |
To merge this branch: | bzr merge lp:~axwalk/juju-core/null-provider-storage-auth |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+187964@code.launchpad.net |
Commit message
Wire up authenticating httpstorage
There are two things happening here:
- httpstorage authentication changes
- wiring up to null provider/
The httpstorage authentication mechanism has
changed to use an auth key (randomly generated
as part of the boilerplate), rather than client
certificates. This gives us more options for
using the storage, since now you don't need the
client to share the CA key.
Also, when serving HTTPS, HTTP is *also* provided,
but only for non-modifying operations. This allows
wget et al. to fetch files from the storage without
bypassing certificate validation. The HTTP interface
disallows modifying operations if there is an HTTPS
interface backing it. To get an HTTPS URL, a HEAD
request may be performed on the HTTP interface.
Description of the change
Wire up authenticating httpstorage
There are two things happening here:
- httpstorage authentication changes
- wiring up to null provider/
The httpstorage authentication mechanism has
changed to use an auth key (randomly generated
as part of the boilerplate), rather than client
certificates. This gives us more options for
using the storage, since now you don't need the
client to share the CA key.
Also, when serving HTTPS, HTTP is *also* provided,
but only for non-modifying operations. This allows
wget et al. to fetch files from the storage without
bypassing certificate validation. The HTTP interface
disallows modifying operations if there is an HTTPS
interface backing it. To get an HTTPS URL, a HEAD
request may be performed on the HTTP interface.
Andrew Wilkins (axwalk) wrote : | # |
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
Tim Penhey (thumper) wrote : | # |
LGTM, few comments
https:/
File environs/
https:/
environs/
supported", http.StatusMeth
shouldn't we return here? So as not to change the header to 200 OK?
https:/
File provider/
https:/
provider/
httpstorage.
If this is an error condition, shouldn't we log an error (which the
users now see) and return nil rather than http storage?
Andrew Wilkins (axwalk) wrote : | # |
https:/
File environs/
https:/
environs/
supported", http.StatusMeth
On 2013/10/02 05:07:25, thumper wrote:
> shouldn't we return here? So as not to change the header to 200 OK?
It doesn't actually change in practice, as the first header wins. But
this was an oversight and I've fixed it. I added a test while I was at
it. Thanks.
https:/
File provider/
https:/
provider/
httpstorage.
On 2013/10/02 05:07:25, thumper wrote:
> If this is an error condition, shouldn't we log an error (which the
users now
> see) and return nil rather than http storage?
Done.
Preview Diff
1 | === modified file 'environs/httpstorage/backend.go' |
2 | --- environs/httpstorage/backend.go 2013-09-24 05:42:43 +0000 |
3 | +++ environs/httpstorage/backend.go 2013-10-02 05:48:33 +0000 |
4 | @@ -18,24 +18,39 @@ |
5 | "launchpad.net/juju-core/environs/storage" |
6 | ) |
7 | |
8 | -// TODO(axw) 2013-09-16 bug #1225916 |
9 | -// Implement authentication for modifying storage. |
10 | - |
11 | // storageBackend provides HTTP access to a storage object. |
12 | type storageBackend struct { |
13 | backend storage.Storage |
14 | - tls bool |
15 | + |
16 | + // httpsBaseURL is the base URL to send to clients |
17 | + // if they perform a HEAD request. |
18 | + httpsBaseURL string |
19 | + |
20 | + // authkey is non-empty if modifying requests |
21 | + // require an auth key. |
22 | + authkey string |
23 | } |
24 | |
25 | // ServeHTTP handles the HTTP requests to the container. |
26 | func (s *storageBackend) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
27 | switch req.Method { |
28 | + case "PUT", "DELETE": |
29 | + // Don't allow modifying operations if there's an HTTPS backend |
30 | + // to handle that, and ensure the user is authorised/authenticated. |
31 | + if s.httpsBaseURL != "" || !s.authorised(req) { |
32 | + http.Error(w, "unauthorised access", http.StatusUnauthorized) |
33 | + return |
34 | + } |
35 | + } |
36 | + switch req.Method { |
37 | case "GET": |
38 | if strings.HasSuffix(req.URL.Path, "*") { |
39 | s.handleList(w, req) |
40 | } else { |
41 | s.handleGet(w, req) |
42 | } |
43 | + case "HEAD": |
44 | + s.handleHead(w, req) |
45 | case "PUT": |
46 | s.handlePut(w, req) |
47 | case "DELETE": |
48 | @@ -45,11 +60,25 @@ |
49 | } |
50 | } |
51 | |
52 | -// authorised checks that either the storage does not require authorisation, |
53 | -// or the user has negotiated TLS with a valid certificate. Authentication |
54 | -// implies authorisation. |
55 | +// authorised checks that either the storage does not require |
56 | +// authorisation, or the user has specified the correct auth key. |
57 | func (s *storageBackend) authorised(req *http.Request) bool { |
58 | - return !s.tls || len(req.TLS.PeerCertificates) > 0 |
59 | + if s.authkey == "" { |
60 | + return true |
61 | + } |
62 | + return req.URL.Query().Get("authkey") == s.authkey |
63 | +} |
64 | + |
65 | +// handleHead returns the HTTPS URL for the specified |
66 | +// path in the Location header. |
67 | +func (s *storageBackend) handleHead(w http.ResponseWriter, req *http.Request) { |
68 | + if s.httpsBaseURL != "" { |
69 | + w.Header().Set("Location", s.httpsBaseURL+req.URL.Path) |
70 | + } else { |
71 | + http.Error(w, "method HEAD is not supported", http.StatusMethodNotAllowed) |
72 | + return |
73 | + } |
74 | + w.WriteHeader(http.StatusOK) |
75 | } |
76 | |
77 | // handleGet returns a storage file to the client. |
78 | @@ -85,10 +114,6 @@ |
79 | |
80 | // handlePut stores data from the client in the storage. |
81 | func (s *storageBackend) handlePut(w http.ResponseWriter, req *http.Request) { |
82 | - if !s.authorised(req) { |
83 | - http.Error(w, "unauthorised access", http.StatusUnauthorized) |
84 | - return |
85 | - } |
86 | if req.ContentLength < 0 { |
87 | http.Error(w, "missing or invalid Content-Length header", http.StatusInternalServerError) |
88 | return |
89 | @@ -119,7 +144,7 @@ |
90 | // requests to the given storage implementation. It returns the network |
91 | // listener. This can then be attached to with Client. |
92 | func Serve(addr string, stor storage.Storage) (net.Listener, error) { |
93 | - return serve(addr, stor, nil) |
94 | + return serve(addr, stor, nil, "") |
95 | } |
96 | |
97 | // ServeTLS runs a storage server on the given network address, relaying |
98 | @@ -130,7 +155,7 @@ |
99 | // |
100 | // This method returns the network listener, which can then be attached |
101 | // to with ClientTLS. |
102 | -func ServeTLS(addr string, stor storage.Storage, caCertPEM, caKeyPEM []byte, hostnames []string) (net.Listener, error) { |
103 | +func ServeTLS(addr string, stor storage.Storage, caCertPEM, caKeyPEM []byte, hostnames []string, authkey string) (net.Listener, error) { |
104 | expiry := time.Now().UTC().AddDate(10, 0, 0) |
105 | certPEM, keyPEM, err := cert.NewServer(caCertPEM, caKeyPEM, expiry, hostnames) |
106 | if err != nil { |
107 | @@ -150,21 +175,33 @@ |
108 | ClientAuth: tls.VerifyClientCertIfGiven, |
109 | ClientCAs: caCerts, |
110 | } |
111 | - return serve(addr, stor, config) |
112 | + return serve(addr, stor, config, authkey) |
113 | } |
114 | |
115 | -func serve(addr string, stor storage.Storage, tlsConfig *tls.Config) (net.Listener, error) { |
116 | +func serve(addr string, stor storage.Storage, tlsConfig *tls.Config, authkey string) (net.Listener, error) { |
117 | listener, err := net.Listen("tcp", addr) |
118 | if err != nil { |
119 | return nil, fmt.Errorf("cannot start listener: %v", err) |
120 | } |
121 | backend := &storageBackend{backend: stor} |
122 | if tlsConfig != nil { |
123 | - listener = tls.NewListener(listener, tlsConfig) |
124 | - backend.tls = true |
125 | + tlsBackend := &storageBackend{backend: stor, authkey: authkey} |
126 | + tcpAddr := listener.Addr().(*net.TCPAddr) |
127 | + tlsListener, err := tls.Listen("tcp", fmt.Sprintf("[%s]:0", tcpAddr.IP), tlsConfig) |
128 | + if err != nil { |
129 | + listener.Close() |
130 | + return nil, fmt.Errorf("cannot start TLS listener: %v", err) |
131 | + } |
132 | + backend.httpsBaseURL = fmt.Sprintf("https://%s", tlsListener.Addr()) |
133 | + goServe(tlsListener, tlsBackend) |
134 | } |
135 | + goServe(listener, backend) |
136 | + return listener, nil |
137 | +} |
138 | + |
139 | +func goServe(listener net.Listener, backend *storageBackend) { |
140 | + // Construct a NewServeMux to sanitise request paths. |
141 | mux := http.NewServeMux() |
142 | mux.Handle("/", backend) |
143 | go http.Serve(listener, mux) |
144 | - return listener, nil |
145 | } |
146 | |
147 | === modified file 'environs/httpstorage/backend_test.go' |
148 | --- environs/httpstorage/backend_test.go 2013-09-24 05:42:43 +0000 |
149 | +++ environs/httpstorage/backend_test.go 2013-10-02 05:48:33 +0000 |
150 | @@ -25,6 +25,8 @@ |
151 | "launchpad.net/juju-core/testing/testbase" |
152 | ) |
153 | |
154 | +const testAuthkey = "jabberwocky" |
155 | + |
156 | func TestLocal(t *stdtesting.T) { |
157 | gc.TestingT(t) |
158 | } |
159 | @@ -50,14 +52,16 @@ |
160 | // startServerTLS starts a new TLS-based local storage server |
161 | // using a temporary directory and returns the listener, |
162 | // a base URL for the server and the directory path. |
163 | -func startServerTLS(c *gc.C, caCertPEM, caKeyPEM []byte) (listener net.Listener, url, dataDir string) { |
164 | +func startServerTLS(c *gc.C) (listener net.Listener, url, dataDir string) { |
165 | dataDir = c.MkDir() |
166 | embedded, err := filestorage.NewFileStorageWriter(dataDir, filestorage.UseDefaultTmpDir) |
167 | c.Assert(err, gc.IsNil) |
168 | hostnames := []string{"127.0.0.1"} |
169 | - listener, err = httpstorage.ServeTLS("127.0.0.1:0", embedded, caCertPEM, caKeyPEM, hostnames) |
170 | + caCertPEM := []byte(coretesting.CACert) |
171 | + caKeyPEM := []byte(coretesting.CAKey) |
172 | + listener, err = httpstorage.ServeTLS("127.0.0.1:0", embedded, caCertPEM, caKeyPEM, hostnames, testAuthkey) |
173 | c.Assert(err, gc.IsNil) |
174 | - return listener, fmt.Sprintf("https://%s/", listener.Addr()), dataDir |
175 | + return listener, fmt.Sprintf("http://%s/", listener.Addr()), dataDir |
176 | } |
177 | |
178 | type testCase struct { |
179 | @@ -130,6 +134,24 @@ |
180 | }, |
181 | } |
182 | |
183 | +func (s *backendSuite) TestHead(c *gc.C) { |
184 | + // HEAD is unsupported for non-authenticating servers. |
185 | + listener, url, _ := startServer(c) |
186 | + defer listener.Close() |
187 | + resp, err := http.Head(url) |
188 | + c.Assert(err, gc.IsNil) |
189 | + c.Assert(resp.StatusCode, gc.Equals, http.StatusMethodNotAllowed) |
190 | + |
191 | + // HEAD on an authenticating server will return the HTTPS counterpart URL. |
192 | + client, url, _ := s.tlsServerAndClient(c) |
193 | + resp, err = client.Head(url + "arbitrary") |
194 | + c.Assert(err, gc.IsNil) |
195 | + c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) |
196 | + location, err := resp.Location() |
197 | + c.Assert(err, gc.IsNil) |
198 | + c.Assert(location.String(), gc.Matches, "https://127.0.0.1:[0-9]{5}/arbitrary") |
199 | +} |
200 | + |
201 | func (s *backendSuite) TestGet(c *gc.C) { |
202 | // Test retrieving a file from a storage. |
203 | listener, url, dataDir := startServer(c) |
204 | @@ -373,12 +395,10 @@ |
205 | } |
206 | |
207 | func (b *backendSuite) tlsServerAndClient(c *gc.C) (client *http.Client, url, dataDir string) { |
208 | - caCertPEM := []byte(coretesting.CACert) |
209 | - caKeyPEM := []byte(coretesting.CAKey) |
210 | - listener, url, dataDir := startServerTLS(c, caCertPEM, caKeyPEM) |
211 | + listener, url, dataDir := startServerTLS(c) |
212 | b.AddCleanup(func(*gc.C) { listener.Close() }) |
213 | caCerts := x509.NewCertPool() |
214 | - c.Assert(caCerts.AppendCertsFromPEM(caCertPEM), jc.IsTrue) |
215 | + c.Assert(caCerts.AppendCertsFromPEM([]byte(coretesting.CACert)), jc.IsTrue) |
216 | client = &http.Client{ |
217 | Transport: &http.Transport{ |
218 | TLSClientConfig: &tls.Config{RootCAs: caCerts}, |
219 | |
220 | === modified file 'environs/httpstorage/storage.go' |
221 | --- environs/httpstorage/storage.go 2013-09-27 05:19:30 +0000 |
222 | +++ environs/httpstorage/storage.go 2013-10-02 05:48:33 +0000 |
223 | @@ -11,11 +11,11 @@ |
224 | "io" |
225 | "io/ioutil" |
226 | "net/http" |
227 | + "net/url" |
228 | "sort" |
229 | "strings" |
230 | - "time" |
231 | + "sync" |
232 | |
233 | - "launchpad.net/juju-core/cert" |
234 | "launchpad.net/juju-core/environs/storage" |
235 | coreerrors "launchpad.net/juju-core/errors" |
236 | "launchpad.net/juju-core/utils" |
237 | @@ -23,47 +23,60 @@ |
238 | |
239 | // storage implements the storage.Storage interface. |
240 | type localStorage struct { |
241 | - baseURL string |
242 | - client *http.Client |
243 | + addr string |
244 | + client *http.Client |
245 | + |
246 | + authkey string |
247 | + httpsBaseURL string |
248 | + httpsBaseURLError error |
249 | + httpsBaseURLOnce sync.Once |
250 | } |
251 | |
252 | // Client returns a storage object that will talk to the |
253 | // storage server at the given network address (see Serve) |
254 | func Client(addr string) storage.Storage { |
255 | return &localStorage{ |
256 | - baseURL: fmt.Sprintf("http://%s/", addr), |
257 | - client: http.DefaultClient, |
258 | + addr: addr, |
259 | + client: http.DefaultClient, |
260 | } |
261 | } |
262 | |
263 | // ClientTLS returns a storage object that will talk to the |
264 | // storage server at the given network address (see Serve), |
265 | -// using TLS. The client will generate a client certificate, |
266 | -// signed with the given CA certificate/key, to authenticate. |
267 | -func ClientTLS(addr string, caCertPEM, caKeyPEM []byte) (storage.Storage, error) { |
268 | +// using TLS. The client is given an authentication key, |
269 | +// which the server will verify for Put and Remove* operations. |
270 | +func ClientTLS(addr string, caCertPEM []byte, authkey string) (storage.Storage, error) { |
271 | caCerts := x509.NewCertPool() |
272 | - if !caCerts.AppendCertsFromPEM(caCertPEM) { |
273 | - return nil, errors.New("error adding CA certificate to pool") |
274 | - } |
275 | - expiry := time.Now().UTC().AddDate(10, 0, 0) |
276 | - clientCertPEM, clientKeyPEM, err := cert.NewClient(caCertPEM, caKeyPEM, expiry) |
277 | - clientCert, err := tls.X509KeyPair(clientCertPEM, clientKeyPEM) |
278 | - if err != nil { |
279 | - return nil, err |
280 | + if caCertPEM != nil { |
281 | + if !caCerts.AppendCertsFromPEM(caCertPEM) { |
282 | + return nil, errors.New("error adding CA certificate to pool") |
283 | + } |
284 | } |
285 | return &localStorage{ |
286 | - baseURL: fmt.Sprintf("https://%s/", addr), |
287 | + addr: addr, |
288 | + authkey: authkey, |
289 | client: &http.Client{ |
290 | Transport: &http.Transport{ |
291 | - TLSClientConfig: &tls.Config{ |
292 | - Certificates: []tls.Certificate{clientCert}, |
293 | - RootCAs: caCerts, |
294 | - }, |
295 | + TLSClientConfig: &tls.Config{RootCAs: caCerts}, |
296 | }, |
297 | }, |
298 | }, nil |
299 | } |
300 | |
301 | +func (s *localStorage) getHTTPSBaseURL() (string, error) { |
302 | + url, _ := s.URL("/") // never fails |
303 | + resp, err := s.client.Head(url) |
304 | + if err != nil { |
305 | + return "", err |
306 | + } |
307 | + resp.Body.Close() |
308 | + httpsURL, err := resp.Location() |
309 | + if err != nil { |
310 | + return "", err |
311 | + } |
312 | + return httpsURL.String(), nil |
313 | +} |
314 | + |
315 | // Get opens the given storage file and returns a ReadCloser |
316 | // that can be used to read its contents. It is the caller's |
317 | // responsibility to close it after use. If the name does not |
318 | @@ -119,9 +132,25 @@ |
319 | return names, nil |
320 | } |
321 | |
322 | -// URL returns an URL that can be used to access the given storage file. |
323 | +// URL returns a URL that can be used to access the given storage file. |
324 | func (s *localStorage) URL(name string) (string, error) { |
325 | - return s.baseURL + name, nil |
326 | + return fmt.Sprintf("http://%s/%s", s.addr, name), nil |
327 | +} |
328 | + |
329 | +// modURL returns a URL that can be used to modify the given storage file. |
330 | +func (s *localStorage) modURL(name string) (string, error) { |
331 | + if s.client == http.DefaultClient { |
332 | + return s.URL(name) |
333 | + } |
334 | + s.httpsBaseURLOnce.Do(func() { |
335 | + s.httpsBaseURL, s.httpsBaseURLError = s.getHTTPSBaseURL() |
336 | + }) |
337 | + if s.httpsBaseURLError != nil { |
338 | + return "", s.httpsBaseURLError |
339 | + } |
340 | + v := url.Values{} |
341 | + v.Set("authkey", s.authkey) |
342 | + return fmt.Sprintf("%s%s?%s", s.httpsBaseURL, name, v.Encode()), nil |
343 | } |
344 | |
345 | // ConsistencyStrategy is specified in the StorageReader interface. |
346 | @@ -137,7 +166,7 @@ |
347 | // Put reads from r and writes to the given storage file. |
348 | // The length must be set to the total length of the file. |
349 | func (s *localStorage) Put(name string, r io.Reader, length int64) error { |
350 | - url, err := s.URL(name) |
351 | + url, err := s.modURL(name) |
352 | if err != nil { |
353 | return err |
354 | } |
355 | @@ -168,7 +197,7 @@ |
356 | // storage. It should not return an error if the file does |
357 | // not exist. |
358 | func (s *localStorage) Remove(name string) error { |
359 | - url, err := s.URL(name) |
360 | + url, err := s.modURL(name) |
361 | if err != nil { |
362 | return err |
363 | } |
364 | |
365 | === modified file 'environs/httpstorage/storage_test.go' |
366 | --- environs/httpstorage/storage_test.go 2013-09-23 10:05:53 +0000 |
367 | +++ environs/httpstorage/storage_test.go 2013-10-02 05:48:33 +0000 |
368 | @@ -25,12 +25,9 @@ |
369 | var _ = gc.Suite(&storageSuite{}) |
370 | |
371 | func (s *storageSuite) TestClientTLS(c *gc.C) { |
372 | - caCertPEM := []byte(coretesting.CACert) |
373 | - caKeyPEM := []byte(coretesting.CAKey) |
374 | - |
375 | - listener, _, storageDir := startServerTLS(c, caCertPEM, caKeyPEM) |
376 | + listener, _, storageDir := startServerTLS(c) |
377 | defer listener.Close() |
378 | - stor, err := httpstorage.ClientTLS(listener.Addr().String(), caCertPEM, caKeyPEM) |
379 | + stor, err := httpstorage.ClientTLS(listener.Addr().String(), []byte(coretesting.CACert), testAuthkey) |
380 | c.Assert(err, gc.IsNil) |
381 | |
382 | data := []byte("hello") |
383 | @@ -41,13 +38,37 @@ |
384 | c.Assert(names, gc.DeepEquals, []string{"filename"}) |
385 | checkFileHasContents(c, stor, "filename", data) |
386 | |
387 | - // Now try Put, Remove and RemoveAll. |
388 | + // Put, Remove and RemoveAll should all succeed. |
389 | checkPutFile(c, stor, "filenamethesecond", data) |
390 | checkFileHasContents(c, stor, "filenamethesecond", data) |
391 | c.Assert(stor.Remove("filenamethesecond"), gc.IsNil) |
392 | c.Assert(stor.RemoveAll(), gc.IsNil) |
393 | } |
394 | |
395 | +func (s *storageSuite) TestClientTLSInvalidAuth(c *gc.C) { |
396 | + listener, _, storageDir := startServerTLS(c) |
397 | + defer listener.Close() |
398 | + const invalidAuthkey = testAuthkey + "!" |
399 | + stor, err := httpstorage.ClientTLS(listener.Addr().String(), []byte(coretesting.CACert), invalidAuthkey) |
400 | + c.Assert(err, gc.IsNil) |
401 | + |
402 | + // Get and List should succeed. |
403 | + data := []byte("hello") |
404 | + err = ioutil.WriteFile(filepath.Join(storageDir, "filename"), data, 0644) |
405 | + c.Assert(err, gc.IsNil) |
406 | + names, err := storage.List(stor, "filename") |
407 | + c.Assert(err, gc.IsNil) |
408 | + c.Assert(names, gc.DeepEquals, []string{"filename"}) |
409 | + checkFileHasContents(c, stor, "filename", data) |
410 | + |
411 | + // Put, Remove and RemoveAll should all fail. |
412 | + const authErrorPattern = ".*401 Unauthorized" |
413 | + err = putFile(c, stor, "filenamethesecond", data) |
414 | + c.Assert(err, gc.ErrorMatches, authErrorPattern) |
415 | + c.Assert(stor.Remove("filenamethesecond"), gc.ErrorMatches, authErrorPattern) |
416 | + c.Assert(stor.RemoveAll(), gc.ErrorMatches, authErrorPattern) |
417 | +} |
418 | + |
419 | func (s *storageSuite) TestList(c *gc.C) { |
420 | listener, _, _ := startServer(c) |
421 | defer listener.Close() |
422 | @@ -80,7 +101,6 @@ |
423 | storage2 := httpstorage.Client(listener.Addr().String()) |
424 | for _, name := range names { |
425 | checkFileHasContents(c, storage2, name, []byte(name)) |
426 | - checkFileURLHasContents(c, storage2, name, []byte(name)) |
427 | } |
428 | |
429 | // remove the first file and check that the others remain. |
430 | @@ -128,12 +148,17 @@ |
431 | return nil |
432 | } |
433 | |
434 | -func checkPutFile(c *gc.C, stor storage.StorageWriter, name string, contents []byte) { |
435 | +func putFile(c *gc.C, stor storage.StorageWriter, name string, contents []byte) error { |
436 | c.Logf("check putting file %s ...", name) |
437 | reader := &readerWithClose{bytes.NewBuffer(contents), false} |
438 | err := stor.Put(name, reader, int64(len(contents))) |
439 | - c.Assert(err, gc.IsNil) |
440 | c.Assert(reader.closeCalled, jc.IsFalse) |
441 | + return err |
442 | +} |
443 | + |
444 | +func checkPutFile(c *gc.C, stor storage.StorageWriter, name string, contents []byte) { |
445 | + err := putFile(c, stor, name, contents) |
446 | + c.Assert(err, gc.IsNil) |
447 | } |
448 | |
449 | func checkFileDoesNotExist(c *gc.C, stor storage.StorageReader, name string) { |
450 | @@ -147,19 +172,15 @@ |
451 | c.Assert(err, gc.IsNil) |
452 | c.Check(r, gc.NotNil) |
453 | defer r.Close() |
454 | - |
455 | data, err := ioutil.ReadAll(r) |
456 | c.Check(err, gc.IsNil) |
457 | c.Check(data, gc.DeepEquals, contents) |
458 | -} |
459 | |
460 | -func checkFileURLHasContents(c *gc.C, stor storage.StorageReader, name string, contents []byte) { |
461 | url, err := stor.URL(name) |
462 | c.Assert(err, gc.IsNil) |
463 | - |
464 | resp, err := http.Get(url) |
465 | c.Assert(err, gc.IsNil) |
466 | - data, err := ioutil.ReadAll(resp.Body) |
467 | + data, err = ioutil.ReadAll(resp.Body) |
468 | c.Assert(err, gc.IsNil) |
469 | defer resp.Body.Close() |
470 | c.Assert(resp.StatusCode, gc.Equals, http.StatusOK, gc.Commentf("error response: %s", data)) |
471 | |
472 | === modified file 'environs/manual/bootstrap.go' |
473 | --- environs/manual/bootstrap.go 2013-10-02 05:18:02 +0000 |
474 | +++ environs/manual/bootstrap.go 2013-10-02 05:48:33 +0000 |
475 | @@ -7,7 +7,6 @@ |
476 | "errors" |
477 | "fmt" |
478 | |
479 | - "launchpad.net/juju-core/agent" |
480 | "launchpad.net/juju-core/environs" |
481 | envtools "launchpad.net/juju-core/environs/tools" |
482 | "launchpad.net/juju-core/instance" |
483 | @@ -107,11 +106,9 @@ |
484 | tools.URL = fmt.Sprintf("file://%s/%s", storageDir, toolsStorageName) |
485 | |
486 | // Add the local storage configuration. |
487 | - agentEnv := map[string]string{ |
488 | - agent.StorageAddr: args.Environ.StorageAddr(), |
489 | - agent.StorageDir: storageDir, |
490 | - agent.SharedStorageAddr: args.Environ.SharedStorageAddr(), |
491 | - agent.SharedStorageDir: args.Environ.SharedStorageDir(), |
492 | + agentEnv, err := localstorage.StoreConfig(args.Environ) |
493 | + if err != nil { |
494 | + return err |
495 | } |
496 | |
497 | // Finally, provision the machine agent. |
498 | |
499 | === modified file 'provider/null/config.go' |
500 | --- provider/null/config.go 2013-09-24 02:27:12 +0000 |
501 | +++ provider/null/config.go 2013-10-02 05:48:33 +0000 |
502 | @@ -16,11 +16,13 @@ |
503 | "bootstrap-user": schema.String(), |
504 | "storage-listen-ip": schema.String(), |
505 | "storage-port": schema.Int(), |
506 | + "storage-auth-key": schema.String(), |
507 | } |
508 | configDefaults = schema.Defaults{ |
509 | "bootstrap-user": "", |
510 | "storage-listen-ip": "", |
511 | "storage-port": 8040, |
512 | + "storage-auth-key": schema.Omit, |
513 | } |
514 | ) |
515 | |
516 | @@ -57,6 +59,10 @@ |
517 | return int(c.attrs["storage-port"].(int64)) |
518 | } |
519 | |
520 | +func (c *environConfig) storageAuthKey() string { |
521 | + return c.attrs["storage-auth-key"].(string) |
522 | +} |
523 | + |
524 | // storageAddr returns an address for connecting to the |
525 | // bootstrap machine's localstorage. |
526 | func (c *environConfig) storageAddr() string { |
527 | |
528 | === modified file 'provider/null/config_test.go' |
529 | --- provider/null/config_test.go 2013-09-24 03:02:54 +0000 |
530 | +++ provider/null/config_test.go 2013-10-02 05:48:33 +0000 |
531 | @@ -28,9 +28,10 @@ |
532 | |
533 | func minimalConfigValues() map[string]interface{} { |
534 | return map[string]interface{}{ |
535 | - "name": "test", |
536 | - "type": provider.Null, |
537 | - "bootstrap-host": "hostname", |
538 | + "name": "test", |
539 | + "type": provider.Null, |
540 | + "bootstrap-host": "hostname", |
541 | + "storage-auth-key": "whatever", |
542 | // While the ca-cert bits aren't entirely minimal, they avoid the need |
543 | // to set up a fake home. |
544 | "ca-cert": coretesting.CACert, |
545 | |
546 | === modified file 'provider/null/environ.go' |
547 | --- provider/null/environ.go 2013-10-02 05:18:02 +0000 |
548 | +++ provider/null/environ.go 2013-10-02 05:48:33 +0000 |
549 | @@ -5,9 +5,12 @@ |
550 | |
551 | import ( |
552 | "errors" |
553 | + "net" |
554 | "path" |
555 | "sync" |
556 | |
557 | + "launchpad.net/loggo" |
558 | + |
559 | "launchpad.net/juju-core/constraints" |
560 | "launchpad.net/juju-core/environs" |
561 | "launchpad.net/juju-core/environs/cloudinit" |
562 | @@ -23,6 +26,7 @@ |
563 | "launchpad.net/juju-core/state" |
564 | "launchpad.net/juju-core/state/api" |
565 | "launchpad.net/juju-core/tools" |
566 | + "launchpad.net/juju-core/worker/localstorage" |
567 | ) |
568 | |
569 | const ( |
570 | @@ -39,6 +43,8 @@ |
571 | storageTmpSubdir = "storage-tmp" |
572 | ) |
573 | |
574 | +var logger = loggo.GetLogger("juju.provider.null") |
575 | + |
576 | type nullEnviron struct { |
577 | cfg *environConfig |
578 | cfgmutex sync.Mutex |
579 | @@ -160,7 +166,20 @@ |
580 | if e.bootstrapStorage != nil { |
581 | return e.bootstrapStorage |
582 | } |
583 | - return httpstorage.Client(e.envConfig().storageAddr()) |
584 | + caCertPEM, caKeyPEM := e.StorageCACert(), e.StorageCAKey() |
585 | + if caCertPEM != nil && caKeyPEM != nil { |
586 | + authkey := e.StorageAuthKey() |
587 | + storage, err := httpstorage.ClientTLS(e.envConfig().storageAddr(), caCertPEM, authkey) |
588 | + if err != nil { |
589 | + // Should be impossible, since ca-cert will always be validated. |
590 | + logger.Errorf("initialising HTTPS storage failed: %v", err) |
591 | + } else { |
592 | + return storage |
593 | + } |
594 | + } else { |
595 | + logger.Errorf("missing CA cert or private key") |
596 | + } |
597 | + return nil |
598 | } |
599 | |
600 | func (e *nullEnviron) PublicStorage() storage.StorageReader { |
601 | @@ -202,3 +221,34 @@ |
602 | func (e *nullEnviron) SharedStorageDir() string { |
603 | return "" |
604 | } |
605 | + |
606 | +func (e *nullEnviron) StorageCACert() []byte { |
607 | + if bytes, ok := e.envConfig().CACert(); ok { |
608 | + return bytes |
609 | + } |
610 | + return nil |
611 | +} |
612 | + |
613 | +func (e *nullEnviron) StorageCAKey() []byte { |
614 | + if bytes, ok := e.envConfig().CAPrivateKey(); ok { |
615 | + return bytes |
616 | + } |
617 | + return nil |
618 | +} |
619 | + |
620 | +func (e *nullEnviron) StorageHostnames() []string { |
621 | + cfg := e.envConfig() |
622 | + hostnames := []string{cfg.bootstrapHost()} |
623 | + if ip := net.ParseIP(cfg.storageListenIPAddress()); ip != nil { |
624 | + if !ip.IsUnspecified() { |
625 | + hostnames = append(hostnames, ip.String()) |
626 | + } |
627 | + } |
628 | + return hostnames |
629 | +} |
630 | + |
631 | +func (e *nullEnviron) StorageAuthKey() string { |
632 | + return e.envConfig().storageAuthKey() |
633 | +} |
634 | + |
635 | +var _ localstorage.LocalTLSStorageConfig = (*nullEnviron)(nil) |
636 | |
637 | === modified file 'provider/null/provider.go' |
638 | --- provider/null/provider.go 2013-09-26 11:21:52 +0000 |
639 | +++ provider/null/provider.go 2013-10-02 05:48:33 +0000 |
640 | @@ -99,12 +99,19 @@ |
641 | ## network interfaces. |
642 | # storage-listen-ip: |
643 | # storage-port: 8040 |
644 | + storage-auth-key: {{rand}} |
645 | |
646 | ` |
647 | } |
648 | |
649 | -func (_ nullProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) { |
650 | - return make(map[string]string), nil |
651 | +func (p nullProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) { |
652 | + envConfig, err := p.validate(cfg, nil) |
653 | + if err != nil { |
654 | + return nil, err |
655 | + } |
656 | + attrs := make(map[string]string) |
657 | + attrs["storage-auth-key"] = envConfig.storageAuthKey() |
658 | + return attrs, nil |
659 | } |
660 | |
661 | func (_ nullProvider) PublicAddress() (string, error) { |
662 | |
663 | === modified file 'worker/localstorage/config.go' |
664 | --- worker/localstorage/config.go 2013-09-16 02:03:12 +0000 |
665 | +++ worker/localstorage/config.go 2013-10-02 05:48:33 +0000 |
666 | @@ -3,6 +3,27 @@ |
667 | |
668 | package localstorage |
669 | |
670 | +import ( |
671 | + "launchpad.net/goyaml" |
672 | + |
673 | + "launchpad.net/juju-core/agent" |
674 | +) |
675 | + |
676 | +const ( |
677 | + // TODO(axw) 2013-09-25 bug #1230131 |
678 | + // Move these variables out of agent when we can do upgrades in |
679 | + // the right place. In this case, the local provider should do |
680 | + // the envvar-to-agent.conf migration. |
681 | + StorageDir = agent.StorageDir |
682 | + StorageAddr = agent.StorageAddr |
683 | + SharedStorageDir = agent.SharedStorageDir |
684 | + SharedStorageAddr = agent.SharedStorageAddr |
685 | + StorageCACert = "StorageCACert" |
686 | + StorageCAKey = "StorageCAKey" |
687 | + StorageHostnames = "StorageHostnames" |
688 | + StorageAuthKey = "StorageAuthKey" |
689 | +) |
690 | + |
691 | // LocalStorageConfig is an interface that, if implemented, may be used |
692 | // to configure a machine agent for use with the localstorage worker in |
693 | // this package. |
694 | @@ -12,3 +33,94 @@ |
695 | SharedStorageDir() string |
696 | SharedStorageAddr() string |
697 | } |
698 | + |
699 | +// LocalTLSStorageConfig is an interface that extends LocalStorageConfig |
700 | +// to support serving storage over TLS. |
701 | +type LocalTLSStorageConfig interface { |
702 | + LocalStorageConfig |
703 | + |
704 | + // StorageCACert is the CA certificate in PEM format. |
705 | + StorageCACert() []byte |
706 | + |
707 | + // StorageCAKey is the CA private key in PEM format. |
708 | + StorageCAKey() []byte |
709 | + |
710 | + // StorageHostnames is the set of hostnames that will |
711 | + // be assigned to the storage server's certificate. |
712 | + StorageHostnames() []string |
713 | + |
714 | + // StorageAuthKey is the key that clients must present |
715 | + // to perform modifying operations. |
716 | + StorageAuthKey() string |
717 | +} |
718 | + |
719 | +type config struct { |
720 | + storageDir string |
721 | + storageAddr string |
722 | + sharedStorageDir string |
723 | + sharedStorageAddr string |
724 | + caCertPEM []byte |
725 | + caKeyPEM []byte |
726 | + hostnames []string |
727 | + authkey string |
728 | +} |
729 | + |
730 | +// StoreConfig takes a LocalStorageConfig (or derivative interface), |
731 | +// and stores it in a map[string]string suitable for updating an |
732 | +// agent.Config's key/value map. |
733 | +func StoreConfig(storageConfig LocalStorageConfig) (map[string]string, error) { |
734 | + kv := make(map[string]string) |
735 | + kv[StorageDir] = storageConfig.StorageDir() |
736 | + kv[StorageAddr] = storageConfig.StorageAddr() |
737 | + kv[SharedStorageDir] = storageConfig.SharedStorageDir() |
738 | + kv[SharedStorageAddr] = storageConfig.SharedStorageAddr() |
739 | + if tlsConfig, ok := storageConfig.(LocalTLSStorageConfig); ok { |
740 | + if authkey := tlsConfig.StorageAuthKey(); authkey != "" { |
741 | + kv[StorageAuthKey] = authkey |
742 | + } |
743 | + if cert := tlsConfig.StorageCACert(); cert != nil { |
744 | + kv[StorageCACert] = string(cert) |
745 | + } |
746 | + if key := tlsConfig.StorageCAKey(); key != nil { |
747 | + kv[StorageCAKey] = string(key) |
748 | + } |
749 | + if hostnames := tlsConfig.StorageHostnames(); len(hostnames) > 0 { |
750 | + data, err := goyaml.Marshal(hostnames) |
751 | + if err != nil { |
752 | + return nil, err |
753 | + } |
754 | + kv[StorageHostnames] = string(data) |
755 | + } |
756 | + } |
757 | + return kv, nil |
758 | +} |
759 | + |
760 | +func loadConfig(agentConfig agent.Config) (*config, error) { |
761 | + config := &config{ |
762 | + storageDir: agentConfig.Value(StorageDir), |
763 | + storageAddr: agentConfig.Value(StorageAddr), |
764 | + sharedStorageDir: agentConfig.Value(SharedStorageDir), |
765 | + sharedStorageAddr: agentConfig.Value(SharedStorageAddr), |
766 | + authkey: agentConfig.Value(StorageAuthKey), |
767 | + } |
768 | + |
769 | + caCertPEM := agentConfig.Value(StorageCACert) |
770 | + if len(caCertPEM) > 0 { |
771 | + config.caCertPEM = []byte(caCertPEM) |
772 | + } |
773 | + |
774 | + caKeyPEM := agentConfig.Value(StorageCAKey) |
775 | + if len(caKeyPEM) > 0 { |
776 | + config.caKeyPEM = []byte(caKeyPEM) |
777 | + } |
778 | + |
779 | + hostnames := agentConfig.Value(StorageHostnames) |
780 | + if len(hostnames) > 0 { |
781 | + err := goyaml.Unmarshal([]byte(hostnames), &config.hostnames) |
782 | + if err != nil { |
783 | + return nil, err |
784 | + } |
785 | + } |
786 | + |
787 | + return config, nil |
788 | +} |
789 | |
790 | === added file 'worker/localstorage/config_test.go' |
791 | --- worker/localstorage/config_test.go 1970-01-01 00:00:00 +0000 |
792 | +++ worker/localstorage/config_test.go 2013-10-02 05:48:33 +0000 |
793 | @@ -0,0 +1,132 @@ |
794 | +// Copyright 2013 Canonical Ltd. |
795 | +// Licensed under the AGPLv3, see LICENCE file for details. |
796 | + |
797 | +package localstorage_test |
798 | + |
799 | +import ( |
800 | + stdtesting "testing" |
801 | + |
802 | + gc "launchpad.net/gocheck" |
803 | + "launchpad.net/goyaml" |
804 | + |
805 | + "launchpad.net/juju-core/worker/localstorage" |
806 | +) |
807 | + |
808 | +type configSuite struct{} |
809 | + |
810 | +var _ = gc.Suite(&configSuite{}) |
811 | + |
812 | +func TestPackage(t *stdtesting.T) { |
813 | + gc.TestingT(t) |
814 | +} |
815 | + |
816 | +type localStorageConfig struct { |
817 | + storageDir string |
818 | + storageAddr string |
819 | + sharedStorageDir string |
820 | + sharedStorageAddr string |
821 | +} |
822 | + |
823 | +func (c *localStorageConfig) StorageDir() string { |
824 | + return c.storageDir |
825 | +} |
826 | + |
827 | +func (c *localStorageConfig) StorageAddr() string { |
828 | + return c.storageAddr |
829 | +} |
830 | + |
831 | +func (c *localStorageConfig) SharedStorageDir() string { |
832 | + return c.sharedStorageDir |
833 | +} |
834 | + |
835 | +func (c *localStorageConfig) SharedStorageAddr() string { |
836 | + return c.sharedStorageAddr |
837 | +} |
838 | + |
839 | +type localTLSStorageConfig struct { |
840 | + localStorageConfig |
841 | + caCertPEM []byte |
842 | + caKeyPEM []byte |
843 | + hostnames []string |
844 | + authkey string |
845 | +} |
846 | + |
847 | +func (c *localTLSStorageConfig) StorageCACert() []byte { |
848 | + return c.caCertPEM |
849 | +} |
850 | + |
851 | +func (c *localTLSStorageConfig) StorageCAKey() []byte { |
852 | + return c.caKeyPEM |
853 | +} |
854 | + |
855 | +func (c *localTLSStorageConfig) StorageHostnames() []string { |
856 | + return c.hostnames |
857 | +} |
858 | + |
859 | +func (c *localTLSStorageConfig) StorageAuthKey() string { |
860 | + return c.authkey |
861 | +} |
862 | + |
863 | +func (*configSuite) TestStoreConfig(c *gc.C) { |
864 | + var config localStorageConfig |
865 | + m, err := localstorage.StoreConfig(&config) |
866 | + c.Assert(err, gc.IsNil) |
867 | + c.Assert(m, gc.DeepEquals, map[string]string{ |
868 | + localstorage.StorageDir: "", |
869 | + localstorage.StorageAddr: "", |
870 | + localstorage.SharedStorageDir: "", |
871 | + localstorage.SharedStorageAddr: "", |
872 | + }) |
873 | + |
874 | + config.storageDir = "a" |
875 | + config.storageAddr = "b" |
876 | + config.sharedStorageDir = "c" |
877 | + config.sharedStorageAddr = "d" |
878 | + m, err = localstorage.StoreConfig(&config) |
879 | + c.Assert(err, gc.IsNil) |
880 | + c.Assert(m, gc.DeepEquals, map[string]string{ |
881 | + localstorage.StorageDir: config.storageDir, |
882 | + localstorage.StorageAddr: config.storageAddr, |
883 | + localstorage.SharedStorageDir: config.sharedStorageDir, |
884 | + localstorage.SharedStorageAddr: config.sharedStorageAddr, |
885 | + }) |
886 | +} |
887 | + |
888 | +func (*configSuite) TestStoreConfigTLS(c *gc.C) { |
889 | + var config localTLSStorageConfig |
890 | + m, err := localstorage.StoreConfig(&config) |
891 | + c.Assert(err, gc.IsNil) |
892 | + c.Assert(m, gc.DeepEquals, map[string]string{ |
893 | + localstorage.StorageDir: "", |
894 | + localstorage.StorageAddr: "", |
895 | + localstorage.SharedStorageDir: "", |
896 | + localstorage.SharedStorageAddr: "", |
897 | + }) |
898 | + |
899 | + config.storageDir = "a" |
900 | + config.storageAddr = "b" |
901 | + config.sharedStorageDir = "c" |
902 | + config.sharedStorageAddr = "d" |
903 | + config.caCertPEM = []byte("heyhey") |
904 | + config.caKeyPEM = []byte("hoho") |
905 | + config.hostnames = []string{"easy", "as", "1.2.3"} |
906 | + config.authkey = "password" |
907 | + m, err = localstorage.StoreConfig(&config) |
908 | + c.Assert(err, gc.IsNil) |
909 | + c.Assert(m, gc.DeepEquals, map[string]string{ |
910 | + localstorage.StorageDir: config.storageDir, |
911 | + localstorage.StorageAddr: config.storageAddr, |
912 | + localstorage.SharedStorageDir: config.sharedStorageDir, |
913 | + localstorage.SharedStorageAddr: config.sharedStorageAddr, |
914 | + localstorage.StorageCACert: string(config.caCertPEM), |
915 | + localstorage.StorageCAKey: string(config.caKeyPEM), |
916 | + localstorage.StorageHostnames: mustMarshalYAML(c, config.hostnames), |
917 | + localstorage.StorageAuthKey: config.authkey, |
918 | + }) |
919 | +} |
920 | + |
921 | +func mustMarshalYAML(c *gc.C, v interface{}) string { |
922 | + data, err := goyaml.Marshal(v) |
923 | + c.Assert(err, gc.IsNil) |
924 | + return string(data) |
925 | +} |
926 | |
927 | === modified file 'worker/localstorage/worker.go' |
928 | --- worker/localstorage/worker.go 2013-09-20 04:41:12 +0000 |
929 | +++ worker/localstorage/worker.go 2013-10-02 05:48:33 +0000 |
930 | @@ -4,6 +4,8 @@ |
931 | package localstorage |
932 | |
933 | import ( |
934 | + "net" |
935 | + |
936 | "launchpad.net/loggo" |
937 | "launchpad.net/tomb" |
938 | |
939 | @@ -39,40 +41,53 @@ |
940 | return s.tomb.Wait() |
941 | } |
942 | |
943 | +func (s *storageWorker) serveStorage(storageAddr, storageDir string, config *config) (net.Listener, error) { |
944 | + authenticated := len(config.caCertPEM) > 0 && len(config.caKeyPEM) > 0 |
945 | + scheme := "http://" |
946 | + if authenticated { |
947 | + scheme = "https://" |
948 | + } |
949 | + logger.Infof("serving storage from %s to %s%s", storageDir, scheme, storageAddr) |
950 | + storage, err := filestorage.NewFileStorageWriter(storageDir, filestorage.UseDefaultTmpDir) |
951 | + if err != nil { |
952 | + return nil, err |
953 | + } |
954 | + if authenticated { |
955 | + return httpstorage.ServeTLS( |
956 | + storageAddr, |
957 | + storage, |
958 | + config.caCertPEM, |
959 | + config.caKeyPEM, |
960 | + config.hostnames, |
961 | + config.authkey, |
962 | + ) |
963 | + } |
964 | + return httpstorage.Serve(storageAddr, storage) |
965 | +} |
966 | + |
967 | func (s *storageWorker) waitForDeath() error { |
968 | - storageDir := s.config.Value(agent.StorageDir) |
969 | - storageAddr := s.config.Value(agent.StorageAddr) |
970 | - logger.Infof("serving %s on %s", storageDir, storageAddr) |
971 | + config, err := loadConfig(s.config) |
972 | + if err != nil { |
973 | + logger.Errorf("error loading config: %v", err) |
974 | + return err |
975 | + } |
976 | |
977 | - storage, err := filestorage.NewFileStorageWriter(storageDir, filestorage.UseDefaultTmpDir) |
978 | - if err != nil { |
979 | - logger.Errorf("error with local storage: %v", err) |
980 | - return err |
981 | - } |
982 | - storageListener, err := httpstorage.Serve(storageAddr, storage) |
983 | + storageListener, err := s.serveStorage(config.storageAddr, config.storageDir, config) |
984 | if err != nil { |
985 | logger.Errorf("error with local storage: %v", err) |
986 | return err |
987 | } |
988 | defer storageListener.Close() |
989 | |
990 | - sharedStorageDir := s.config.Value(agent.SharedStorageDir) |
991 | - sharedStorageAddr := s.config.Value(agent.SharedStorageAddr) |
992 | - if sharedStorageAddr != "" && sharedStorageDir != "" { |
993 | - logger.Infof("serving %s on %s", sharedStorageDir, sharedStorageAddr) |
994 | - sharedStorage, err := filestorage.NewFileStorageWriter(sharedStorageDir, filestorage.UseDefaultTmpDir) |
995 | - if err != nil { |
996 | - logger.Errorf("error with local storage: %v", err) |
997 | - return err |
998 | - } |
999 | - sharedStorageListener, err := httpstorage.Serve(sharedStorageAddr, sharedStorage) |
1000 | + if config.sharedStorageAddr != "" && config.sharedStorageDir != "" { |
1001 | + sharedStorageListener, err := s.serveStorage(config.sharedStorageAddr, config.sharedStorageDir, config) |
1002 | if err != nil { |
1003 | logger.Errorf("error with local storage: %v", err) |
1004 | return err |
1005 | } |
1006 | defer sharedStorageListener.Close() |
1007 | } else { |
1008 | - logger.Infof("no shared storage: dir=%q addr=%q", sharedStorageDir, sharedStorageAddr) |
1009 | + logger.Infof("no shared storage: dir=%q addr=%q", config.sharedStorageDir, config.sharedStorageAddr) |
1010 | } |
1011 | |
1012 | logger.Infof("storage routines started, awaiting death") |
Reviewers: mp+187964_ code.launchpad. net,
Message:
Please take a look.
Description:
Wire up authenticating httpstorage
There are two things happening here: localstorage worker.
- httpstorage authentication changes
- wiring up to null provider/
The httpstorage authentication mechanism has
changed to use an auth key (randomly generated
as part of the boilerplate), rather than client
certificates. This gives us more options for
using the storage, since now you don't need the
client to share the CA key.
Also, when serving HTTPS, HTTP is *also* provided,
but only for non-modifying operations. This allows
wget et al. to fetch files from the storage without
bypassing certificate validation. The HTTP interface
disallows modifying operations if there is an HTTPS
interface backing it. To get an HTTPS URL, a HEAD
request may be performed on the HTTP interface.
https:/ /code.launchpad .net/~axwalk/ juju-core/ null-provider- storage- auth/+merge/ 187964
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/14011044/
Affected files (+507, -108 lines): httpstorage/ backend. go httpstorage/ backend_ test.go httpstorage/ storage. go httpstorage/ storage_ test.go manual/ bootstrap. go null/config. go null/config_ test.go null/environ. go null/environ_ test.go null/provider. go localstorage/ config. go localstorage/ config_ test.go localstorage/ worker. go
A [revision details]
M environs/
M environs/
M environs/
M environs/
M environs/
M provider/
M provider/
M provider/
M provider/
M provider/
M worker/
A worker/
M worker/