From 070dbf815c26666e59c84433927b8ded557b7c89 Mon Sep 17 00:00:00 2001 From: btoews Date: Mon, 22 Jul 2024 10:26:30 -0600 Subject: [PATCH 1/2] reject requetss with no tokenized data --- tokenizer.go | 9 ++++++++- tokenizer_test.go | 25 +++++++++++-------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/tokenizer.go b/tokenizer.go index 3184633..36496f3 100644 --- a/tokenizer.go +++ b/tokenizer.go @@ -59,7 +59,7 @@ func NewTokenizer(openKey string) *tokenizer { proxy := goproxy.NewProxyHttpServer() tkz := &tokenizer{ProxyHttpServer: proxy, priv: priv, pub: pub} - tkz.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "I'm not that kind of server") }) @@ -156,6 +156,13 @@ func (t *tokenizer) HandleRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*ht processors = append(processors, reqProcessors...) } + if len(processors) == 0 { + pud.reqLog.Warn("no processors") + return nil, errorResponse(ErrBadRequest) + } + + pud.reqLog = pud.reqLog.WithField("processors", len(processors)) + for _, processor := range processors { if err := processor(req); err != nil { pud.reqLog.WithError(err).Warn("run processor") diff --git a/tokenizer_test.go b/tokenizer_test.go index 9e100a1..4b341a8 100644 --- a/tokenizer_test.go +++ b/tokenizer_test.go @@ -47,13 +47,17 @@ func TestTokenizer(t *testing.T) { tkzServer := httptest.NewServer(tkz) defer tkzServer.Close() - client, err := Client(tkzServer.URL) + req, err := http.NewRequest(http.MethodPost, appURL+"/foo", nil) assert.NoError(t, err) - req, err := http.NewRequest(http.MethodPost, appURL+"/foo", nil) + auth := "trustno1" + token := "supersecret" + secret, err := (&Secret{AuthConfig: NewBearerAuthConfig(auth), ProcessorConfig: &InjectProcessorConfig{Token: token}}).Seal(sealKey) assert.NoError(t, err) // TLS error (proxy doesn't trust upstream) + client, err := Client(tkzServer.URL, WithAuth(auth), WithSecret(secret, nil)) + assert.NoError(t, err) resp, err := client.Get(appURL) assert.NoError(t, err) assert.Equal(t, http.StatusBadGateway, resp.StatusCode) @@ -61,19 +65,12 @@ func TestTokenizer(t *testing.T) { // make proxy trust upstream UpstreamTrust.AddCert(appServer.Certificate()) - // no proxy auth or secrets (good) - assert.Equal(t, &echoResponse{ - Headers: http.Header{}, - Body: "", - }, doEcho(t, client, req)) - - // bad proxy auth doesn't matter if no secrets are used - client, err = Client(tkzServer.URL, WithAuth("bogus")) + // error if no secrets + client, err = Client(tkzServer.URL, WithAuth(auth)) + assert.NoError(t, err) + resp, err = client.Get(appURL) assert.NoError(t, err) - assert.Equal(t, &echoResponse{ - Headers: http.Header{}, // proxy auth isn't leaked downstream - Body: "", - }, doEcho(t, client, req)) + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) t.Run("inject processor", func(t *testing.T) { auth := "trustno1" From f1c4afa1a678e459ef36aad79decf85d7f4582c3 Mon Sep 17 00:00:00 2001 From: btoews Date: Fri, 26 Jul 2024 12:38:48 -0600 Subject: [PATCH 2/2] make open-proxy configurable --- README.md | 9 +++++---- cmd/tokenizer/main.go | 5 +++++ tokenizer.go | 6 +++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5dda009..e547ad9 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The client configures their HTTP library to use the tokenizer service as it's HT ```ruby conn = Faraday.new( - proxy: "http://tokenizer.flycast", + proxy: "http://tokenizer.flycast", headers: { proxy_tokenizer: Base64.encode64(sealed_secret), proxy_authorization: "Bearer trustno1" @@ -99,7 +99,7 @@ seal_key = ENV["TOKENIZER_PUBLIC_KEY"] sealed_secret = RbNaCl::Boxes::Sealed.new(seal_key).box(secret.to_json) processor_params = { - dst: "X-Stripe-Token", + dst: "X-Stripe-Token", fmt: "token=%s" } @@ -110,7 +110,7 @@ conn.get("http://api.stripe.com") ## Host allowlist -If a client is fully compromised, the attacker could send encrypted secrets via tokenizer to a service that simply echoes back the request. This way, the attacker could learn the plaintext value of the secret. To mitigate against this, secrets can specify which hosts they may be used against. +If a client is fully compromised, the attacker could send encrypted secrets via tokenizer to a service that simply echoes back the request. This way, the attacker could learn the plaintext value of the secret. To mitigate against this, secrets can specify which hosts they may be used against. ```ruby secret = { @@ -193,4 +193,5 @@ Tokenizer is configured with the following environment variables: - `OPEN_KEY` - The hex encoded 32 byte private key is used for decrypting secrets. - `LISTEN_ADDRESS` - The address (`ip:port`) to listen on. -- `FILTERED_HEADERS` - A comma separated list of request headers to strip from client requests. \ No newline at end of file +- `FILTERED_HEADERS` - A comma separated list of request headers to strip from client requests. +- `OPEN_PROXY` - Setting `1` or `true` will allow requests that don't contain sealed secrets to be proxied. Such requests are blocked by default. diff --git a/cmd/tokenizer/main.go b/cmd/tokenizer/main.go index 87f6fb8..3cb6cae 100644 --- a/cmd/tokenizer/main.go +++ b/cmd/tokenizer/main.go @@ -16,6 +16,7 @@ import ( "github.com/sirupsen/logrus" "github.com/superfly/tokenizer" + "golang.org/x/exp/slices" ) // Package variables can be overridden at build time: @@ -85,6 +86,10 @@ func runServe() { tkz.ProxyHttpServer.Logger = logrus.StandardLogger() } + if slices.Contains([]string{"1", "true"}, os.Getenv("OPEN_PROXY")) { + tkz.OpenProxy = true + } + server := &http.Server{Handler: tkz} go func() { diff --git a/tokenizer.go b/tokenizer.go index 36496f3..6cfd677 100644 --- a/tokenizer.go +++ b/tokenizer.go @@ -40,6 +40,10 @@ const headerProxyTokenizer = "Proxy-Tokenizer" type tokenizer struct { *goproxy.ProxyHttpServer + + // OpenProxy dictates whether requests without any sealed secrets are allowed. + OpenProxy bool + priv *[32]byte pub *[32]byte } @@ -156,7 +160,7 @@ func (t *tokenizer) HandleRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*ht processors = append(processors, reqProcessors...) } - if len(processors) == 0 { + if len(processors) == 0 && !t.OpenProxy { pud.reqLog.Warn("no processors") return nil, errorResponse(ErrBadRequest) }