Skip to content

Commit

Permalink
E2E test for elasticsearch token propagation on Openshift
Browse files Browse the repository at this point in the history
Signed-off-by: Ruben Vargas <ruben.vp8510@gmail.com>
  • Loading branch information
rubenvp8510 committed Jul 24, 2019
1 parent 966dd7e commit 2f1ea29
Show file tree
Hide file tree
Showing 8 changed files with 520 additions and 29 deletions.
38 changes: 11 additions & 27 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,7 @@ matrix:
include:
- go: "1.12.1"
env:
- TESTS=true
- COVERAGE=true
- go: "1.12.1"
env:
- PROTO_GEN_TEST=true
- go: "1.12.1"
env:
- ALL_IN_ONE=true
- go: "1.12.1"
env:
- CROSSDOCK=true
- go: "1.12.1"
env:
- DOCKER=true
- DEPLOY=true
- go: "1.12.1"
env:
- ES_INTEGRATION_TEST=true
- go: "1.12.1"
env:
- KAFKA_INTEGRATION_TEST=true
- go: "1.12.1"
env:
- CASSANDRA_INTEGRATION_TEST=true
- go: "1.12.1"
env:
- HOTROD=true
- ES_TOKEN_PROPAGATION=true

services:
- docker
Expand All @@ -51,6 +25,12 @@ env:
# override default yarn
- PATH=$HOME/.yarn/bin:$PATH

before_install:
# add 172.16.0.0/12 as insecure registry (OpenShift internal registry)
- sudo sed -i "s/\DOCKER_OPTS=\"/DOCKER_OPTS=\"--insecure-registry=172.16.0.0\/12 /g" /etc/default/docker
- sudo cat /etc/default/docker
- sudo service docker restart

install:
- docker rmi $(docker images -q) || true
- make install-ci
Expand All @@ -63,6 +43,9 @@ install:
curl -L https://github.com/google/protobuf/releases/download/v3.6.1/protoc-3.6.1-linux-x86_64.zip -o /tmp/protoc.zip
unzip /tmp/protoc.zip -d "$HOME"/protoc
fi
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install 8.16.0
- npm install
- curl -o- -L https://yarnpkg.com/install.sh | bash

script:
- export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi)
Expand All @@ -71,6 +54,7 @@ script:
- if [ "$CROSSDOCK" == true ]; then bash ./scripts/travis/build-crossdock.sh ; else echo 'skipping crossdock'; fi
- if [ "$DOCKER" == true ]; then bash ./scripts/travis/build-docker-images.sh ; else echo 'skipping docker images'; fi
- if [ "$ES_INTEGRATION_TEST" == true ]; then bash ./scripts/travis/es-integration-test.sh ; else echo 'skipping elastic search integration test'; fi
- if [ "$ES_TOKEN_PROPAGATION" == true ]; then bash ./scripts/travis/es-token-propagation-test.sh ; else echo 'skipping elastic search token propagation integration test'; fi
- if [ "$KAFKA_INTEGRATION_TEST" == true ]; then bash ./scripts/travis/kafka-integration-test.sh ; else echo 'skipping kafka integration test'; fi
- if [ "$CASSANDRA_INTEGRATION_TEST" == true ]; then bash ./scripts/travis/cassandra-integration-test.sh ; else echo 'skipping cassandra integration test'; fi
- if [ "$HOTROD" == true ]; then bash ./scripts/travis/hotrod-integration-test.sh ; else echo 'skipping hotrod example'; fi
Expand Down
5 changes: 4 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ storage-integration-test: go-gen
go clean -testcache
bash -c "set -e; set -o pipefail; $(GOTEST) $(STORAGE_PKGS) | $(COLORIZE)"

.PHONY: token-propagation-test
token-propagation-test: go-gen
go clean -testcache
bash -c "set -e; set -o pipefail; $(GOTEST) ./cmd/query/app/integration/... | $(COLORIZE)"

all-pkgs:
@echo $(ALL_PKGS) | tr ' ' '\n' | sort

Expand Down
24 changes: 24 additions & 0 deletions cmd/query/app/integration/token_propagation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package integration

import (
"github.com/stretchr/testify/assert"
"net/http"
"testing"
)

func Test_bearTokenPropagationHandler(t *testing.T) {
hostName := "query.127.0.0.1.nip.io"
authClient, err := newOauthAuthenticatedClient(hostName, "developer")
if err != nil {
print(err.Error())
}
assert.Nil(t, err)
// Do a request for search service, should not return forbidden.
resp, err := getResponse("https://"+ hostName + "/api/services", authClient)
if err != nil {
print(err.Error())
}
assert.Nil(t, err)
assert.Equal(t, http.StatusOK,resp.StatusCode)
}

214 changes: 214 additions & 0 deletions cmd/query/app/integration/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package integration

import (
"bytes"
"crypto/tls"
"fmt"
"golang.org/x/net/html"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"time"
)
/* based on: https://github.com/openshift/oauth-proxy/blob/master/test/e2e/proxy_test.go */

func submitOAuthForm(client *http.Client, response *http.Response, user string) (*http.Response, error) {
responseBytes, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}

responseBuffer := bytes.NewBuffer(responseBytes)

body, err := html.Parse(responseBuffer)
if err != nil {
return nil, err
}

forms := getElementsByTagName(body, "form")
if len(forms) != 1 {
errMsg := "expected OpenShift form"
return nil, fmt.Errorf(errMsg)
}

formReq, err := newRequestFromForm(forms[0], response.Request.URL, user)
if err != nil {
return nil, err
}

postResp, err := client.Do(formReq)
if err != nil {
return nil, err
}

return postResp, nil
}

func visit(n *html.Node, visitor func(*html.Node)) {
visitor(n)
for c := n.FirstChild; c != nil; c = c.NextSibling {
visit(c, visitor)
}
}

func getElementsByTagName(root *html.Node, tagName string) []*html.Node {
elements := []*html.Node{}
visit(root, func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == tagName {
elements = append(elements, n)
}
})
return elements
}

func getAttr(element *html.Node, attrName string) (string, bool) {
for _, attr := range element.Attr {
if attr.Key == attrName {
return attr.Val, true
}
}
return "", false
}

func newRequestFromForm(form *html.Node, currentURL *url.URL, user string) (*http.Request, error) {
var (
reqMethod string
reqURL *url.URL
reqBody io.Reader
reqHeader = http.Header{}
err error
)

// Method defaults to GET if empty
if method, _ := getAttr(form, "method"); len(method) > 0 {
reqMethod = strings.ToUpper(method)
} else {
reqMethod = "GET"
}

// URL defaults to current URL if empty
action, _ := getAttr(form, "action")
reqURL, err = currentURL.Parse(action)
if err != nil {
return nil, err
}

formData := url.Values{}
if reqMethod == "GET" {
// Start with any existing query params when we're submitting via GET
formData = reqURL.Query()
}
addedSubmit := false
for _, input := range getElementsByTagName(form, "input") {
if name, ok := getAttr(input, "name"); ok {
if value, ok := getAttr(input, "value"); ok {
inputType, _ := getAttr(input, "type")

switch inputType {
case "text":
if name == "username" {
formData.Add(name, user)
}
case "password":
if name == "password" {
formData.Add(name, "foo")
}
case "submit":
// If this is a submit input, only add the value of the first one.
// We're simulating submitting the form.
if !addedSubmit {
formData.Add(name, value)
addedSubmit = true
}
case "radio", "checkbox":
if _, checked := getAttr(input, "checked"); checked {
formData.Add(name, value)
}
default:
formData.Add(name, value)
}
}
}
}

switch reqMethod {
case "GET":
reqURL.RawQuery = formData.Encode()
case "POST":
reqHeader.Set("Content-Type", "application/x-www-form-urlencoded")
reqBody = strings.NewReader(formData.Encode())
default:
return nil, fmt.Errorf("unknown method: %s", reqMethod)
}

req, err := http.NewRequest(reqMethod, reqURL.String(), reqBody)
if err != nil {
return nil, err
}

req.Header = reqHeader
return req, nil
}

func getResponse(host string, client *http.Client) (*http.Response, error) {
req, err := http.NewRequest("GET", host, nil)
if err != nil {
return nil, err
}

req.Header.Set("Accept", "*/*")

resp, err := client.Do(req)
if err != nil {
return nil, err
}

return resp, nil
}

func closeResponseBody(resp *http.Response) {
if resp == nil {
return
}
err := resp.Body.Close()
if err != nil {
log.Print(err)
}
}

func newOauthAuthenticatedClient(route string, user string) (*http.Client, error) {
host := "https://" + route + "/oauth/start"
jar, _ := cookiejar.New(nil)
tr := &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
client := &http.Client{Transport: tr, Jar: jar}
startUrl := host
resp, err := getResponse(startUrl, client)
if err != nil {
return client, err
}
defer closeResponseBody(resp)
// OpenShift login
loginResp, err := submitOAuthForm(client, resp, user)
if err != nil {
return client,err
}
defer closeResponseBody(loginResp)

// authorization grant form
grantResp, err := submitOAuthForm(client, loginResp, user)
/*if err != nil {
return client, err
}*/
defer closeResponseBody(grantResp)
return client, nil
}
11 changes: 10 additions & 1 deletion cmd/query/app/token_propagation_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package app

import (
"log"
"net/http"
"strings"

Expand All @@ -26,11 +27,15 @@ import (
func bearerTokenPropagationHandler(logger *zap.Logger, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
logger.Info("Propagating bearer token")
log.Print(r)
authHeaderValue := r.Header.Get("Authorization")
// If no Authorization header is present, try with X-Forwarded-Access-Token
if authHeaderValue == "" {
authHeaderValue = r.Header.Get("X-Forwarded-Access-Token")
}
logger.Info("Token: " + authHeaderValue)

if authHeaderValue != "" {
headerValue := strings.Split(authHeaderValue, " ")
token := ""
Expand All @@ -39,7 +44,11 @@ func bearerTokenPropagationHandler(logger *zap.Logger, h http.Handler) http.Hand
if headerValue[0] == "Bearer" {
token = headerValue[1]
}
} else {
} else if len(headerValue) == 1 {
// Tread all value as a token
logger.Info("Token schema does not specified in header, treating all value as a token")
token = authHeaderValue
} else {
logger.Warn("Invalid authorization header, skipping bearer token propagation")
}
h.ServeHTTP(w, r.WithContext(spanstore.ContextWithBearerToken(ctx, token)))
Expand Down
Loading

0 comments on commit 2f1ea29

Please sign in to comment.