github.com/lusis/distribution@v2.0.1+incompatible/registry/storage/driver/middleware/cloudfront/middleware.go (about) 1 // Package middleware - cloudfront wrapper for storage libs 2 // N.B. currently only works with S3, not arbitrary sites 3 // 4 package middleware 5 6 import ( 7 "crypto/x509" 8 "encoding/pem" 9 "fmt" 10 "io/ioutil" 11 "time" 12 13 "github.com/AdRoll/goamz/cloudfront" 14 "github.com/docker/distribution/context" 15 storagedriver "github.com/docker/distribution/registry/storage/driver" 16 storagemiddleware "github.com/docker/distribution/registry/storage/driver/middleware" 17 ) 18 19 // cloudFrontStorageMiddleware provides an simple implementation of layerHandler that 20 // constructs temporary signed CloudFront URLs from the storagedriver layer URL, 21 // then issues HTTP Temporary Redirects to this CloudFront content URL. 22 type cloudFrontStorageMiddleware struct { 23 storagedriver.StorageDriver 24 cloudfront *cloudfront.CloudFront 25 duration time.Duration 26 } 27 28 var _ storagedriver.StorageDriver = &cloudFrontStorageMiddleware{} 29 30 // newCloudFrontLayerHandler constructs and returns a new CloudFront 31 // LayerHandler implementation. 32 // Required options: baseurl, privatekey, keypairid 33 func newCloudFrontStorageMiddleware(storageDriver storagedriver.StorageDriver, options map[string]interface{}) (storagedriver.StorageDriver, error) { 34 base, ok := options["baseurl"] 35 if !ok { 36 return nil, fmt.Errorf("No baseurl provided") 37 } 38 baseURL, ok := base.(string) 39 if !ok { 40 return nil, fmt.Errorf("baseurl must be a string") 41 } 42 pk, ok := options["privatekey"] 43 if !ok { 44 return nil, fmt.Errorf("No privatekey provided") 45 } 46 pkPath, ok := pk.(string) 47 if !ok { 48 return nil, fmt.Errorf("privatekey must be a string") 49 } 50 kpid, ok := options["keypairid"] 51 if !ok { 52 return nil, fmt.Errorf("No keypairid provided") 53 } 54 keypairID, ok := kpid.(string) 55 if !ok { 56 return nil, fmt.Errorf("keypairid must be a string") 57 } 58 59 pkBytes, err := ioutil.ReadFile(pkPath) 60 if err != nil { 61 return nil, fmt.Errorf("Failed to read privatekey file: %s", err) 62 } 63 64 block, _ := pem.Decode([]byte(pkBytes)) 65 if block == nil { 66 return nil, fmt.Errorf("Failed to decode private key as an rsa private key") 67 } 68 privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) 69 if err != nil { 70 return nil, err 71 } 72 73 cf := cloudfront.New(baseURL, privateKey, keypairID) 74 75 duration := 20 * time.Minute 76 d, ok := options["duration"] 77 if ok { 78 switch d := d.(type) { 79 case time.Duration: 80 duration = d 81 case string: 82 dur, err := time.ParseDuration(d) 83 if err != nil { 84 return nil, fmt.Errorf("Invalid duration: %s", err) 85 } 86 duration = dur 87 } 88 } 89 90 return &cloudFrontStorageMiddleware{StorageDriver: storageDriver, cloudfront: cf, duration: duration}, nil 91 } 92 93 // S3BucketKeyer is any type that is capable of returning the S3 bucket key 94 // which should be cached by AWS CloudFront. 95 type S3BucketKeyer interface { 96 S3BucketKey(path string) string 97 } 98 99 // Resolve returns an http.Handler which can serve the contents of the given 100 // Layer, or an error if not supported by the storagedriver. 101 func (lh *cloudFrontStorageMiddleware) URLFor(path string, options map[string]interface{}) (string, error) { 102 // TODO(endophage): currently only supports S3 103 keyer, ok := lh.StorageDriver.(S3BucketKeyer) 104 if !ok { 105 context.GetLogger(context.Background()).Warn("the CloudFront middleware does not support this backend storage driver") 106 return lh.StorageDriver.URLFor(path, options) 107 } 108 109 cfURL, err := lh.cloudfront.CannedSignedURL(keyer.S3BucketKey(path), "", time.Now().Add(lh.duration)) 110 if err != nil { 111 return "", err 112 } 113 return cfURL, nil 114 } 115 116 // init registers the cloudfront layerHandler backend. 117 func init() { 118 storagemiddleware.Register("cloudfront", storagemiddleware.InitFunc(newCloudFrontStorageMiddleware)) 119 }