Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite core proxy, TLS MITM support, and refactor all the things. #13

Merged
merged 16 commits into from Jul 31, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
@@ -1,4 +1,4 @@
language: go

go:
- 1.4
- tip
50 changes: 43 additions & 7 deletions README.md
Expand Up @@ -24,7 +24,7 @@ Running an instance of Martian is as simple as
If you want to see system logs as Martian is running, pass in the verbosity
flag:

go run examples/main.go -- -v=0
go run examples/main.go -- -v

For logging of requests and responses a [logging modifier](https://github.com/google/martian/wiki/Modifier-Reference#logging) is available

Expand Down Expand Up @@ -70,7 +70,7 @@ Let's break down the parts of this message.
This is a simple configuration, for more complex configurations, modifiers are
combined with groups and filters to compose the desired behavior.

To configure Martian, `POST` the JSON to `host:port/martian/modifiers`. You'll
To configure Martian, `POST` the JSON to `http://martian.proxy/modifiers`. You'll
want to use whatever mechanism your language of choice provides you to make
HTTP requests, but for demo purposes, curl works (assuming your configuration
is in a file called `modifier.json`).
Expand All @@ -79,7 +79,43 @@ is in a file called `modifier.json`).
-X POST \
-H "Content-Type: application/json" \
-d @modifier.json \
"http://localhost:8080/martian/modifiers"
"http://martian.proxy/configure"

### Intercepting HTTPS Requests and Responses
Martian supports modifying HTTPS requests and responses if configured to do so.

In order for Martian to intercept HTTPS traffic a custom CA certificate must be
installed in the browser so that connection warnings are not shown.

The easiest way to install the CA certificate is to start the proxy with the
necessary flags to use a custom CA certificate and private key using the `-cert`
and `-key` flags, or to have the proxy generate one using the `-generate-ca-cert`
flag.

After the proxy has started, visit http://martian.proxy/authority.cer in the
browser configured to use the proxy and a prompt will be displayed to install
the certificate.

Several flags are available in `examples/main.go` to help configure MITM
functionality:

-key=""
PEM encoded private key file of the CA certificate provided in -cert; used
to sign certificates that are generated on-the-fly
-cert=""
PEM encoded CA certificate file used to generate certificates
-generate-ca-cert=false
generates a CA certificate and private key to use for man-in-the-middle;
most users choosing this option will immediately visit
http://martian.proxy/authority.cer in the browser whose traffic is to be
intercepted to install the newly generated CA certificate
-organization="Martian Proxy"
organization name set on the dynamically-generated certificates during
man-in-the-middle
-validity="1h"
window of time around the time of request that the dynamically-generated
certificate is valid for; the duration is set such that the total valid
timeframe is double the value of validity (1h before & 1h after)

### Check Verifiers
Let's assume that you've configured Martian to verify the presence a specific
Expand Down Expand Up @@ -107,7 +143,7 @@ got back `200 OK` responses.

To check verifications, perform

GET host:port/martian/verify
GET http://martian.proxy/verify

Failed expectations are tracked as errors, and the list of errors are retrieved
by making a `GET` request to `host:port/martian/verify`, which will return
Expand All @@ -126,7 +162,7 @@ a list of errors:

Verification errors are held in memory until they are explicitly cleared by

POST host:port/martian/verify/reset
POST http://martian.proxy/verify/reset

## Martian as a Library
Martian can also be included into any Go program and used as a library.
Expand All @@ -149,9 +185,9 @@ Modifiers, filters and groups all implement `RequestModifer`,
`ResponseModifier` or `RequestResponseModifier` (defined in
[`martian.go`](https://github.com/google/martian/martian.go)).

ModifyRequest(ctx *martian.Context, req *http.Request) error
ModifyRequest(req *http.Request) error

ModifyResponse(ctx *martian.Context, res *http.Response) error
ModifyResponse(res *http.Response) error

Throughout the code (and this documentation) you'll see the word "modifier"
used as a term that encompasses modifiers, groups and filters. Even though a
Expand Down
69 changes: 35 additions & 34 deletions auth/auth_filter.go
Expand Up @@ -12,19 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package auth provides filtering support for a martian.Proxy based on ctx.Auth.ID.
// Package auth provides filtering support for a martian.Proxy based on auth
// ID.
package auth

import (
"errors"
"fmt"
"net/http"
"sync"

"github.com/google/martian"
)

// Filter filters RequestModifiers and ResponseModifiers by ctx.Auth.ID.
// Filter filters RequestModifiers and ResponseModifiers by auth ID.
type Filter struct {
authRequired bool

Expand All @@ -33,9 +33,6 @@ type Filter struct {
resmods map[string]martian.ResponseModifier
}

// ErrIDRequired indicates that the filter must have an ID.
var ErrIDRequired = errors.New("ID required")

// NewFilter returns a new auth.Filter.
func NewFilter() *Filter {
return &Filter{
Expand All @@ -44,20 +41,15 @@ func NewFilter() *Filter {
}
}

// SetAuthRequired determines whether the ctx.Auth.ID must have an associated
// RequestModifier or ResponseModifier. If true, it will set ctx.Auth.Error.
// SetAuthRequired determines whether the auth ID must have an associated
// RequestModifier or ResponseModifier. If true, it will set auth error.
func (f *Filter) SetAuthRequired(required bool) {
f.authRequired = required
}

// SetRequestModifier sets the RequestModifier for the given ID. It will
// overwrite any existing modifier with the same ID.
// Returns ErrIDRequired if id is empty.
func (f *Filter) SetRequestModifier(id string, reqmod martian.RequestModifier) error {
if id == "" {
return ErrIDRequired
}

f.mu.Lock()
defer f.mu.Unlock()

Expand All @@ -72,12 +64,7 @@ func (f *Filter) SetRequestModifier(id string, reqmod martian.RequestModifier) e

// SetResponseModifier sets the ResponseModifier for the given ID. It will
// overwrite any existing modifier with the same ID.
// Returns ErrIDRequired if id is empty.
func (f *Filter) SetResponseModifier(id string, resmod martian.ResponseModifier) error {
if id == "" {
return ErrIDRequired
}

f.mu.Lock()
defer f.mu.Unlock()

Expand Down Expand Up @@ -108,32 +95,46 @@ func (f *Filter) ResponseModifier(id string) martian.ResponseModifier {
return f.resmods[id]
}

// ModifyRequest runs the RequestModifier for the associated ctx.Auth.ID. If no
// modifier is found for ctx.Auth.ID then ctx.Auth.Error is set.
func (f *Filter) ModifyRequest(ctx *martian.Context, req *http.Request) error {
if reqmod := f.reqmods[ctx.Auth.ID]; reqmod != nil {
return reqmod.ModifyRequest(ctx, req)
// ModifyRequest runs the RequestModifier for the associated auth ID. If no
// modifier is found for auth ID then auth error is set.
func (f *Filter) ModifyRequest(req *http.Request) error {
ctx := martian.Context(req)
actx := FromContext(ctx)

if reqmod, ok := f.reqmods[actx.ID()]; ok {
return reqmod.ModifyRequest(req)
}

return f.requireKnownAuth(ctx)
if err := f.requireKnownAuth(actx.ID()); err != nil {
actx.SetError(err)
}

return nil
}

// ModifyResponse runs the ResponseModifier for the associated ctx.Auth.ID. If
// no modifier is found for ctx.Auth.ID then ctx.Auth.Error is set.
func (f *Filter) ModifyResponse(ctx *martian.Context, res *http.Response) error {
if resmod := f.resmods[ctx.Auth.ID]; resmod != nil {
return resmod.ModifyResponse(ctx, res)
// ModifyResponse runs the ResponseModifier for the associated auth ID. If no
// modifier is found for the auth ID then the auth error is set.
func (f *Filter) ModifyResponse(res *http.Response) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update comment - no longer ctx.Auth.ID, it's actx.ID()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

ctx := martian.Context(res.Request)
actx := FromContext(ctx)

if resmod, ok := f.resmods[actx.ID()]; ok {
return resmod.ModifyResponse(res)
}

return f.requireKnownAuth(ctx)
if err := f.requireKnownAuth(actx.ID()); err != nil {
actx.SetError(err)
}

return nil
}

func (f *Filter) requireKnownAuth(ctx *martian.Context) error {
_, reqok := f.reqmods[ctx.Auth.ID]
_, resok := f.resmods[ctx.Auth.ID]
func (f *Filter) requireKnownAuth(id string) error {
_, reqok := f.reqmods[id]
_, resok := f.resmods[id]

if !reqok && !resok && f.authRequired {
ctx.Auth.Error = fmt.Errorf("no modifiers found for %s", ctx.Auth.ID)
return fmt.Errorf("auth: unrecognized credentials: %s", id)
}

return nil
Expand Down