ory / oathkeeper

A cloud native Identity & Access Proxy / API (IAP) and Access Control Decision API that authenticates, authorizes, and mutates incoming HTTP(s) requests. Inspired by the BeyondCorp / Zero Trust white paper. Written in Go.

Home Page:https://www.ory.sh/?utm_source=github&utm_medium=banner&utm_campaign=hydra

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Authenticator: Bearer_token w. "query_parameter" selector consumes request body

marbergq opened this issue · comments

Preflight checklist

Describe the bug

GIven this rule

      authenticators:
      - handler: bearer_token
        config:
          check_session_url: http://check-session-host/...
          token_from:
            query_parameter: token

The upstream service will never see the body when the request is of Content-type: application/x-www-form-urlencoded.

This is because return r.FormValue(*tokenLocation.QueryParameter) (in helper/bearer.go) will consume the body

Reproducing the bug

config.yaml:

  api:
    port: 6061
  proxy:
    port: 6060

access_rules:
  repositories:
    - file://./rules.1.json

authenticators:
  noop:
    enabled: true
  bearer_token:
    enabled: true
    config:
      check_session_url: http://localhost:6662/session

authorizers:
  allow:
    enabled: true
  deny:
    enabled: true

mutators:
  noop:
    enabled: true

rules.1.yaml

[
  {
    "id": "test",
    "upstream": {
      "url": "http://127.0.0.1:6662"
    },
    "match": {
      "url": "http://127.0.0.1:6060/test",
      "methods": ["POST"]
    },
    "authenticators": [
      {
        "handler": "bearer_token",
        "config": {
          "check_session_url": "http://127.0.0.1:6662/session",
          "preserve_path": true,
          "preserve_query": false,
          "force_method": "GET",
          "token_from": {
            "query_parameter": "token"
          }
        }
      }
    ],
    "authorizer": {
      "handler": "allow"
    },
    "mutators": [
      {
        "handler": "noop"
      }
    ]
  }
]

run.sh

#!/bin/bash

set -euo pipefail

waitport() {
  i=0
  while ! nc -z localhost "$1" ; do
    sleep 1
    if [ $i -gt 10 ]; then
      cat ./config.yaml
      cat ./oathkeeper.log
      exit 1
    fi
    i=$((i+1))
  done
}

cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"


run_oathkekeper() {
  killall oathkeeper || true

  export OATHKEEPER_PROXY=http://127.0.0.1:6060
  export OATHKEEPER_API=http://127.0.0.1:6061

  go build -o . ../..
  LOG_LEVEL=debug ./oathkeeper --config ./config.yaml serve > ./oathkeeper.log 2>&1 &

  waitport 6060
  waitport 6061
}

run_api(){
  killall okapi || true

  PORT=6662 go run ./okapi > ./api.log 2>&1 &

  waitport 6662
}

SUCCESS_TEST=()
FAILED_TEST=()

run_test() {
  label=$1
  shift 1

  result="0"
  "$@" || result="1"

  if [[ "$result" -eq "0" ]]; then
    SUCCESS_TEST+=("$label")
  else
    FAILED_TEST+=("$label")
  fi
}

function finish {
  echo ::group::Config
  cat ./config.yaml
  cat ./rules.1.json
  echo ::endgroup::
  echo ::group::Log
  cat ./oathkeeper.log
  echo ::endgroup::
}
trap finish EXIT

run_oathkekeper
run_api

curl -X POST -f http://127.0.0.1:6662/test?token=token -F fk=fv -H "Content-Type: application/x-www-form-urlencoded" -i

kill %1 || true

trap - EXIT

okapi/main.go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"

	"github.com/julienschmidt/httprouter"
)

func main() {
	router := httprouter.New()

	router.POST("/test", test)
	router.GET("/session", session)

	port := os.Getenv("PORT")
	if port == "" {
		port = "6662"
	}

	server := http.Server{
		Addr:    fmt.Sprintf(":%s", port),
		Handler: router,
	}

	if err := server.ListenAndServe(); err != nil {
		panic(err)
	}
}

func test(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
	}
	fmt.Printf("body was: %s", string(b))
	_, _ = w.Write(b)
}

func session(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	w.WriteHeader(http.StatusOK)
}

$> ./run.sh

time=2023-05-24T14:07:31+02:00 level=warning msg=Access request denied because roundtrip failed audience=application error=map[message:net/http: HTTP/1.x transport connection broken: http: ContentLength=140 with Body length 0 stack_trace: 
....

Relevant log output

time=2023-05-24T14:07:31+02:00 level=warning msg=Access request denied because roundtrip failed audience=application error=map[message:net/http: HTTP/1.x transport connection broken: http: ContentLength=140 with Body length 0 stack_trace:

Relevant configuration

[
  {
    "id": "test",
    "upstream": {
      "url": "http://127.0.0.1:6662"
    },
    "match": {
      "url": "http://127.0.0.1:6060/test",
      "methods": ["POST"]
    },
    "authenticators": [
      {
        "handler": "bearer_token",
        "config": {
          "check_session_url": "http://127.0.0.1:6662/session",
          "preserve_path": true,
          "preserve_query": false,
          "token_from": {
            "query_parameter": "token"
          }
        }
      }
    ],
    "authorizer": {
      "handler": "allow"
    },
    "mutators": [
      {
        "handler": "noop"
      }
    ]
  }
]

Version

0.40.3

On which operating system are you observing this issue?

None

In which environment are you deploying?

Kubernetes

Additional Context

No response