Skip to content

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
abutaha committed Oct 31, 2016
0 parents commit beea83e
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*~
vendor
glide.lock
dist
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# How to Contribute

*aws-es-proxy* is [Apache 2.0 licensed](LICENSE) and accept contributions via
GitHub pull requests.

## Getting Started

- Fork the repository on GitHub
- Read the [README](README.md) for build and test instructions
- Play with the project, submit bugs, submit patches!
114 changes: 114 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#aws-auth-proxy

**aws-es-proxy** is a small web server application sitting between your HTTP client (browser, curl, etc...) and Amazon Elasticsearch service. It will sign your requests using latest [AWS Signature Version 4](http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) before sending the request to Amazon Elasticsearch. When response is back from Amazon Elasticsearch, this response will be sent back to your HTTP client.

Kibana requests are also signed automatically.

##Installation

### Download binary executable:

**aws-es-proxy** has single executable binaries for Linux, Mac and Windows.

Download the latest [aws-es-proxy release](http://google.com).


### Build from Source

#### Dependencies:
* go1.5
* [glide package manager](https://github.com/Masterminds/glide)


```sh
#requires go1.5
export GO15VENDOREXPERIMENT=1
mkdir -p $GOPATH/src/github.com/abutaha
cd $GOPATH/src/github.com/abutaha
git clone https://github.com/abutaha/aws-es-proxy
cd aws-es-proxy
glide install
go build github.com/abutaha/aws-es-proxy
```

##Configuring Credentials

Before using **aws-es-proxy**, ensure that you've configured your AWS IAM user credentials. The best way to configure credentials on a development machine is to use the `~/.aws/credentials` file, which might look like:

```
[default]
aws_access_key_id = AKID1234567890
aws_secret_access_key = MY-SECRET-KEY
```

Alternatively, you can set the following environment variables:

```
export AWS_ACCESS_KEY_ID=AKID1234567890
export AWS_SECRET_ACCESS_KEY=MY-SECRET-KEY
```

**aws-es-proxy** also supports `IAM roles`. To use IAM roles, you need to modify your Amazon Elasticsearch access policy to allow access from that role. Below is an Amazon Elasticsearch `access policy` example allowing access from any EC2 instance with an IAM role called `ec2-aws-elasticsearch`.

```
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::012345678910:role/ec2-aws-elasticsearch"
},
"Action": "es:*",
"Resource": "arn:aws:es:eu-west-1:012345678910:domain/test-es-domain/*"
}
]
}
```



##Usage example:

```sh
./aws-es-proxy -endpoint https://test-es-somerandomvalue.eu-west-1.es.amazonaws.com
Listening on 127.0.0.1:9200
```

*aws-es-proxy* listens on 127.0.0.1:9200 if no additional argument is provided. You can change the IP and Port passing the argument `-listen`

```
./aws-es-proxy -listen :8080 -endpoint ...
./aws-es-proxy -listen 10.0.0.1:9200 -endpoint ...
```

By default, *aws-es-proxy* will not display any message in the console. However, it has the ability to print requests being sent to Amazon Elasticsearch, and the duration it takes to receive the request back. This can be enabled using the option `-verbose`

```
./aws-es-proxy -verbose ...
Listening on 127.0.0.1:9200
2016/10/31 19:48:23 -> GET / 200 1.054s
2016/10/31 19:48:30 -> GET /_cat/indices?v 200 0.199s
2016/10/31 19:48:37 -> GET /_cat/shards?v 200 0.196s
2016/10/31 19:48:49 -> GET /_cat/allocation?v 200 0.179s
2016/10/31 19:49:10 -> PUT /my-test-index 200 0.347s
```

For a full list of available options, use `-h`:

```
./aws-es-proxy -h
Usage of ./aws-es-proxy:
-endpoint string
Amazon ElasticSearch Endpoint (e.g: https://dummy-host.eu-west-1.es.amazonaws.com)
-listen string
Local TCP port to listen on (default "127.0.0.1:9200")
-verbose
Print user requests
```

##Using HTTP Clients

After you run *aws-es-proxy*, you can now open your Web browser on [http://localhost:9200](http://localhost:9200). Everything should be working as you have your own instance of ElasticSearch running on port 9200.

To access Kibana, use [http://localhost:9200/_plugin/kibana/](http://localhost:9200/_plugin/kibana/)
165 changes: 165 additions & 0 deletions aws-es-proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package main

import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/aws/signer/v4"
)

type proxy struct {
Scheme string
Host string
Region string
Service string
Verbose bool
}

func copyHeaders(dst, src http.Header) {
for k, vals := range src {
for _, v := range vals {
dst.Add(k, v)
}
}
}

func replaceBody(req *http.Request) []byte {
if req.Body == nil {
return []byte{}
}
payload, _ := ioutil.ReadAll(req.Body)
req.Body = ioutil.NopCloser(bytes.NewReader(payload))
return payload
}

func parseEndpoint(endpoint string, p *proxy) {
link, err := url.Parse(endpoint)
if err != nil {
log.Fatalf("ERROR: Failed parsing endpoint: %s\n", endpoint)
}

// Only http/https are supported schemes
scheme := func(x string) string {
switch x {
case "http", "https":
return x
}
return "https"
}
link.Scheme = scheme(link.Scheme)

// Unkown schemes sometimes result in empty host value
if link.Host == "" {
log.Fatalf("ERROR: Empty host information in submitted endpoint (%s)\n", endpoint)
}

// Extract region and service from link
parts := strings.Split(link.Host, ".")
var region, service string

if len(parts) == 5 {
region, service = parts[1], parts[2]
} else {
log.Fatalln("ERROR: Submitted endpoint is not a valid Amazon ElasticSearch Endpoint")
}

// Build proxy struct
p.Scheme = link.Scheme
p.Host = link.Host
p.Region = region
p.Service = service

}

func (p *proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
requestStarted := time.Now()
defer r.Body.Close()

respondError := func(err error) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
}

endpoint := *r.URL
endpoint.Host = p.Host
endpoint.Scheme = p.Scheme

req, err := http.NewRequest(r.Method, endpoint.String(), r.Body)
if err != nil {
respondError(err)
return
}

// Start AWS session from ENV, Shared Creds or EC2Role
sess, err := session.NewSession()
if err != nil {
log.Fatalln(err)
}

// Sign the request with AWSv4
signer := v4.NewSigner(sess.Config.Credentials)
payload := bytes.NewReader(replaceBody(req))
signer.Sign(req, payload, p.Service, p.Region, time.Now())

resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Println(err)
respondError(err)
return
}

defer resp.Body.Close()

// Write back received headers
copyHeaders(w.Header(), resp.Header)

buf := bytes.Buffer{}
if _, err := io.Copy(&buf, resp.Body); err != nil {
log.Fatal(err)
}

// Send response back
w.WriteHeader(resp.StatusCode)
w.Write(buf.Bytes())

// Log everything
if p.Verbose {
requestEnded := time.Since(requestStarted)
log.Printf(" -> %s %s %d %.3fs\n",
r.Method, endpoint.RequestURI(), resp.StatusCode, requestEnded.Seconds())
}

}

func main() {
var endpoint, listenAddress string
var verbose bool

// TODO: Use a more sophisticated args parser that can enforce arguments
flag.StringVar(&endpoint, "endpoint", "", "Amazon ElasticSearch Endpoint (e.g: https://dummy-host.eu-west-1.es.amazonaws.com)")
flag.StringVar(&listenAddress, "listen", "127.0.0.1:9200", "Local TCP port to listen on")
flag.BoolVar(&verbose, "verbose", false, "Print user requests")
flag.Parse()

if len(os.Args) < 3 {
fmt.Println("You need to specify Amazon ElasticSearch endpoint.")
fmt.Println("Please run with '-h' for a list of available arguments.")
os.Exit(1)
}

mux := &proxy{Verbose: verbose}
parseEndpoint(endpoint, mux)

fmt.Printf("Listening on %s\n", listenAddress)
log.Fatal(http.ListenAndServe(listenAddress, mux))
}
9 changes: 9 additions & 0 deletions cross-compile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

mkdir -p dist
for GOOS in darwin linux windows; do
for GOARCH in 386 amd64; do
echo "Building $GOOS-$GOARCH"
env GOOS=$GOOS GOARCH=$GOARCH go build -o dist/aws-es-proxy-$GOOS-$GOARCH
done
done
11 changes: 11 additions & 0 deletions glide.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package: github.com/abutaha/aws-es-proxy
license: Apache
owners:
- name: Muslim AbuTaha
email: muslim.adel@gmail.com
import:
- package: github.com/aws/aws-sdk-go
version: ^1.4.22
subpackages:
- aws/session
- aws/signer/v4

0 comments on commit beea83e

Please sign in to comment.