├── README.md └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # AWS Metadata Proxy 2 | 3 | Example AWS Metadata proxy to protect against attack vectors targetting AWS Credentials 4 | 5 | ## Getting Started 6 | 7 | Clone the repo 8 | 9 | ``` 10 | git clone https://github.com/Netflix-Skunkworks/aws-metadata-proxy.git 11 | cd aws-metadata-proxy 12 | ``` 13 | 14 | Build the proxy 15 | 16 | ```golang 17 | go get 18 | go build 19 | ``` 20 | 21 | ## Network Setup 22 | 23 | Create an `iptable` rule that prevents talking directly to the AWS Metadata Service **except** for a particular user, `proxy_user` in the example below. This is the user you run the proxy as on your server. 24 | 25 | ``` 26 | /sbin/iptables -t nat -A OUTPUT -m owner ! --uid-owner proxy_user -d 169.254.169.254 -p tcp -m tcp --dport 80 -j DNAT --to-destination 127.0.0.1:9090 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "net/http/httputil" 7 | "net/url" 8 | "os" 9 | "os/signal" 10 | "strings" 11 | "syscall" 12 | 13 | "github.com/gorilla/mux" 14 | ) 15 | 16 | var ( 17 | whitelist [5]string 18 | ) 19 | 20 | func init() { 21 | whitelist[0] = "aws-sdk-" 22 | whitelist[1] = "Botocore/" 23 | whitelist[2] = "Boto3/" 24 | whitelist[3] = "aws-cli/" 25 | whitelist[4] = "aws-chalice/" 26 | } 27 | 28 | func checkUserAgent(ua string) bool { 29 | for i := range whitelist { 30 | if strings.HasPrefix(ua, whitelist[i]) { 31 | return true 32 | } 33 | } 34 | return false 35 | } 36 | 37 | func ProxyHandler(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) { 38 | return func(w http.ResponseWriter, r *http.Request) { 39 | ua := string(r.Header.Get("User-Agent")) 40 | 41 | if !checkUserAgent(ua) { 42 | w.WriteHeader(http.StatusUnauthorized) 43 | w.Write([]byte("401 - Unauthorized")) 44 | return 45 | } 46 | p.ServeHTTP(w, r) 47 | } 48 | } 49 | 50 | func main() { 51 | 52 | log.Println("Starting proxy...") 53 | 54 | target := "http://169.254.169.254" 55 | remote, err := url.Parse(target) 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | proxy := httputil.NewSingleHostReverseProxy(remote) 61 | router := mux.NewRouter() 62 | 63 | router.HandleFunc("/{path:.*}", ProxyHandler(proxy)) 64 | 65 | go func() { 66 | log.Fatal(http.ListenAndServe("127.0.0.1:9090", router)) 67 | }() 68 | 69 | // Check for interrupt signal and exit cleanly 70 | quit := make(chan os.Signal, 1) 71 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 72 | <-quit 73 | log.Println("Shutdown signal received, exiting proxy...") 74 | 75 | } 76 | --------------------------------------------------------------------------------