package storage import ( "crypto/x509" "encoding/pem" "fmt" "io/ioutil" "net/http" "net/url" "time" "github.com/AdRoll/goamz/cloudfront" "github.com/docker/distribution" storagedriver "github.com/docker/distribution/registry/storage/driver" ) // cloudFrontLayerHandler provides an simple implementation of layerHandler that // constructs temporary signed CloudFront URLs from the storagedriver layer URL, // then issues HTTP Temporary Redirects to this CloudFront content URL. type cloudFrontLayerHandler struct { cloudfront *cloudfront.CloudFront delegateLayerHandler *delegateLayerHandler duration time.Duration } var _ LayerHandler = &cloudFrontLayerHandler{} // newCloudFrontLayerHandler constructs and returns a new CloudFront // LayerHandler implementation. // Required options: baseurl, privatekey, keypairid func newCloudFrontLayerHandler(storageDriver storagedriver.StorageDriver, options map[string]interface{}) (LayerHandler, error) { base, ok := options["baseurl"] if !ok { return nil, fmt.Errorf("No baseurl provided") } baseURL, ok := base.(string) if !ok { return nil, fmt.Errorf("baseurl must be a string") } pk, ok := options["privatekey"] if !ok { return nil, fmt.Errorf("No privatekey provided") } pkPath, ok := pk.(string) if !ok { return nil, fmt.Errorf("privatekey must be a string") } kpid, ok := options["keypairid"] if !ok { return nil, fmt.Errorf("No keypairid provided") } keypairID, ok := kpid.(string) if !ok { return nil, fmt.Errorf("keypairid must be a string") } pkBytes, err := ioutil.ReadFile(pkPath) if err != nil { return nil, fmt.Errorf("Failed to read privatekey file: %s", err) } block, _ := pem.Decode([]byte(pkBytes)) if block == nil { return nil, fmt.Errorf("Failed to decode private key as an rsa private key") } privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } lh, err := newDelegateLayerHandler(storageDriver, options) if err != nil { return nil, err } dlh := lh.(*delegateLayerHandler) cf := cloudfront.New(baseURL, privateKey, keypairID) duration := 20 * time.Minute d, ok := options["duration"] if ok { switch d := d.(type) { case time.Duration: duration = d case string: dur, err := time.ParseDuration(d) if err != nil { return nil, fmt.Errorf("Invalid duration: %s", err) } duration = dur } } return &cloudFrontLayerHandler{cloudfront: cf, delegateLayerHandler: dlh, duration: duration}, nil } // Resolve returns an http.Handler which can serve the contents of the given // Layer, or an error if not supported by the storagedriver. func (lh *cloudFrontLayerHandler) Resolve(layer distribution.Layer) (http.Handler, error) { layerURLStr, err := lh.delegateLayerHandler.urlFor(layer, nil) if err != nil { return nil, err } layerURL, err := url.Parse(layerURLStr) if err != nil { return nil, err } cfURL, err := lh.cloudfront.CannedSignedURL(layerURL.Path, "", time.Now().Add(lh.duration)) if err != nil { return nil, err } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, cfURL, http.StatusTemporaryRedirect) }), nil } // init registers the cloudfront layerHandler backend. func init() { RegisterLayerHandler("cloudfront", LayerHandlerInitFunc(newCloudFrontLayerHandler)) }