launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/environs/httpstorage/backend.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package httpstorage
     5  
     6  import (
     7  	"crypto/tls"
     8  	"crypto/x509"
     9  	"errors"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"net"
    13  	"net/http"
    14  	"strings"
    15  	"time"
    16  
    17  	"launchpad.net/juju-core/cert"
    18  	"launchpad.net/juju-core/environs/storage"
    19  )
    20  
    21  // storageBackend provides HTTP access to a storage object.
    22  type storageBackend struct {
    23  	backend storage.Storage
    24  
    25  	// httpsPort is the port to send to clients
    26  	// if they perform a HEAD request.
    27  	httpsPort int
    28  
    29  	// authkey is non-empty if modifying requests
    30  	// require an auth key.
    31  	authkey string
    32  }
    33  
    34  // ServeHTTP handles the HTTP requests to the container.
    35  func (s *storageBackend) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    36  	switch req.Method {
    37  	case "PUT", "DELETE":
    38  		// Don't allow modifying operations if there's an HTTPS backend
    39  		// to handle that, and ensure the user is authorized/authenticated.
    40  		if s.httpsPort != 0 || !s.authorized(req) {
    41  			http.Error(w, "unauthorized access", http.StatusUnauthorized)
    42  			return
    43  		}
    44  	}
    45  	switch req.Method {
    46  	case "GET":
    47  		if strings.HasSuffix(req.URL.Path, "*") {
    48  			s.handleList(w, req)
    49  		} else {
    50  			s.handleGet(w, req)
    51  		}
    52  	case "HEAD":
    53  		s.handleHead(w, req)
    54  	case "PUT":
    55  		s.handlePut(w, req)
    56  	case "DELETE":
    57  		s.handleDelete(w, req)
    58  	default:
    59  		http.Error(w, "method "+req.Method+" is not supported", http.StatusMethodNotAllowed)
    60  	}
    61  }
    62  
    63  // authorized checks that either the storage does not require
    64  // authorization, or the user has specified the correct auth key.
    65  func (s *storageBackend) authorized(req *http.Request) bool {
    66  	if s.authkey == "" {
    67  		return true
    68  	}
    69  	return req.URL.Query().Get("authkey") == s.authkey
    70  }
    71  
    72  // hostOnly splits a host of the form host, or host:port,
    73  // into its host and port parts, and returns the host part.
    74  func hostOnly(host string) (string, error) {
    75  	hostonly, _, err := net.SplitHostPort(host)
    76  	if err != nil {
    77  		// err may be because of missing :port. Checking
    78  		// the error message is brittle, so let's try
    79  		// again with ":0" tacked on the end.
    80  		var err2 error
    81  		hostonly, _, err = net.SplitHostPort(host + ":0")
    82  		if err2 != nil {
    83  			// something heinous, return the original error
    84  			return "", err
    85  		}
    86  	}
    87  	return hostonly, nil
    88  }
    89  
    90  // handleHead returns the HTTPS URL for the specified
    91  // path in the Location header.
    92  func (s *storageBackend) handleHead(w http.ResponseWriter, req *http.Request) {
    93  	if s.httpsPort != 0 {
    94  		host, err := hostOnly(req.Host)
    95  		if err != nil {
    96  			http.Error(w, fmt.Sprintf("failed to split host: %v", err), http.StatusBadRequest)
    97  			return
    98  		}
    99  		url := fmt.Sprintf("https://%s:%d%s", host, s.httpsPort, req.URL.Path)
   100  		w.Header().Set("Location", url)
   101  	} else {
   102  		http.Error(w, "method HEAD is not supported", http.StatusMethodNotAllowed)
   103  		return
   104  	}
   105  	w.WriteHeader(http.StatusOK)
   106  }
   107  
   108  // handleGet returns a storage file to the client.
   109  func (s *storageBackend) handleGet(w http.ResponseWriter, req *http.Request) {
   110  	readcloser, err := s.backend.Get(req.URL.Path[1:])
   111  	if err != nil {
   112  		http.Error(w, fmt.Sprint(err), http.StatusNotFound)
   113  		return
   114  	}
   115  	defer readcloser.Close()
   116  	data, err := ioutil.ReadAll(readcloser)
   117  	if err != nil {
   118  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   119  		return
   120  	}
   121  	w.Header().Set("Content-Type", "application/octet-stream")
   122  	w.Write(data)
   123  }
   124  
   125  // handleList returns the file names in the storage to the client.
   126  func (s *storageBackend) handleList(w http.ResponseWriter, req *http.Request) {
   127  	prefix := req.URL.Path
   128  	prefix = prefix[1 : len(prefix)-1] // drop the leading '/' and trailing '*'
   129  	names, err := s.backend.List(prefix)
   130  	if err != nil {
   131  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   132  		return
   133  	}
   134  	data := []byte(strings.Join(names, "\n"))
   135  	w.Header().Set("Content-Type", "application/octet-stream")
   136  	w.Write(data)
   137  }
   138  
   139  // handlePut stores data from the client in the storage.
   140  func (s *storageBackend) handlePut(w http.ResponseWriter, req *http.Request) {
   141  	if req.ContentLength < 0 {
   142  		http.Error(w, "missing or invalid Content-Length header", http.StatusInternalServerError)
   143  		return
   144  	}
   145  	err := s.backend.Put(req.URL.Path[1:], req.Body, req.ContentLength)
   146  	if err != nil {
   147  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   148  		return
   149  	}
   150  	w.WriteHeader(http.StatusCreated)
   151  }
   152  
   153  // handleDelete removes a file from the storage.
   154  func (s *storageBackend) handleDelete(w http.ResponseWriter, req *http.Request) {
   155  	if !s.authorized(req) {
   156  		http.Error(w, "unauthorized access", http.StatusUnauthorized)
   157  		return
   158  	}
   159  	err := s.backend.Remove(req.URL.Path[1:])
   160  	if err != nil {
   161  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   162  		return
   163  	}
   164  	w.WriteHeader(http.StatusOK)
   165  }
   166  
   167  // Serve runs a storage server on the given network address, relaying
   168  // requests to the given storage implementation. It returns the network
   169  // listener. This can then be attached to with Client.
   170  func Serve(addr string, stor storage.Storage) (net.Listener, error) {
   171  	return serve(addr, stor, nil, "")
   172  }
   173  
   174  // ServeTLS runs a storage server on the given network address, relaying
   175  // requests to the given storage implementation. The server runs a TLS
   176  // listener, and verifies client certificates (if given) against the
   177  // specified CA certificate. A client certificate is only required for
   178  // PUT and DELETE methods.
   179  //
   180  // This method returns the network listener, which can then be attached
   181  // to with ClientTLS.
   182  func ServeTLS(addr string, stor storage.Storage, caCertPEM, caKeyPEM []byte, hostnames []string, authkey string) (net.Listener, error) {
   183  	expiry := time.Now().UTC().AddDate(10, 0, 0)
   184  	certPEM, keyPEM, err := cert.NewServer(caCertPEM, caKeyPEM, expiry, hostnames)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	serverCert, err := tls.X509KeyPair(certPEM, keyPEM)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	caCerts := x509.NewCertPool()
   193  	if !caCerts.AppendCertsFromPEM(caCertPEM) {
   194  		return nil, errors.New("error adding CA certificate to pool")
   195  	}
   196  	config := &tls.Config{
   197  		NextProtos:   []string{"http/1.1"},
   198  		Certificates: []tls.Certificate{serverCert},
   199  		ClientAuth:   tls.VerifyClientCertIfGiven,
   200  		ClientCAs:    caCerts,
   201  	}
   202  	return serve(addr, stor, config, authkey)
   203  }
   204  
   205  func serve(addr string, stor storage.Storage, tlsConfig *tls.Config, authkey string) (net.Listener, error) {
   206  	listener, err := net.Listen("tcp", addr)
   207  	if err != nil {
   208  		return nil, fmt.Errorf("cannot start listener: %v", err)
   209  	}
   210  	backend := &storageBackend{backend: stor}
   211  	if tlsConfig != nil {
   212  		tlsBackend := &storageBackend{backend: stor, authkey: authkey}
   213  		tcpAddr := listener.Addr().(*net.TCPAddr)
   214  		tlsListener, err := tls.Listen("tcp", fmt.Sprintf("[%s]:0", tcpAddr.IP), tlsConfig)
   215  		if err != nil {
   216  			listener.Close()
   217  			return nil, fmt.Errorf("cannot start TLS listener: %v", err)
   218  		}
   219  		backend.httpsPort = tlsListener.Addr().(*net.TCPAddr).Port
   220  		goServe(tlsListener, tlsBackend)
   221  	}
   222  	goServe(listener, backend)
   223  	return listener, nil
   224  }
   225  
   226  func goServe(listener net.Listener, backend *storageBackend) {
   227  	// Construct a NewServeMux to sanitise request paths.
   228  	mux := http.NewServeMux()
   229  	mux.Handle("/", backend)
   230  	go http.Serve(listener, mux)
   231  }