diff --git a/cmd/query/app/token_propagation_test.go b/cmd/query/app/token_propagation_test.go new file mode 100644 index 00000000000..76172eb99a1 --- /dev/null +++ b/cmd/query/app/token_propagation_test.go @@ -0,0 +1,138 @@ +// Copyright (c) 2019 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/olivere/elastic" + "github.com/opentracing/opentracing-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" + + "github.com/jaegertracing/jaeger/cmd/flags" + "github.com/jaegertracing/jaeger/cmd/query/app/querysvc" + "github.com/jaegertracing/jaeger/pkg/bearertoken" + "github.com/jaegertracing/jaeger/pkg/config" + "github.com/jaegertracing/jaeger/pkg/metrics" + "github.com/jaegertracing/jaeger/pkg/tenancy" + "github.com/jaegertracing/jaeger/plugin/storage/es" + "github.com/jaegertracing/jaeger/ports" +) + +const ( + bearerToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI" + bearerHeader = "Bearer " + bearerToken +) + +type elasticsearchHandlerMock struct { + test *testing.T +} + +func (h *elasticsearchHandlerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if token, ok := bearertoken.GetBearerToken(r.Context()); ok && token == bearerToken { + // Return empty results, we don't care about the result here. + // we just need to make sure the token was propagated to the storage and the query-service returns 200 + ret := new(elastic.SearchResult) + json_ret, _ := json.Marshal(ret) + w.Header().Add("Content-Type", "application/json; charset=UTF-8") + w.Write(json_ret) + return + } + + // No token, return error! + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) +} + +func runMockElasticsearchServer(t *testing.T) *httptest.Server { + handler := &elasticsearchHandlerMock{ + test: t, + } + return httptest.NewServer( + bearertoken.PropagationHandler(zaptest.NewLogger(t), handler), + ) +} + +func runQueryService(t *testing.T, esURL string) *Server { + flagsSvc := flags.NewService(ports.QueryAdminHTTP) + flagsSvc.Logger = zaptest.NewLogger(t) + + f := es.NewFactory() + v, command := config.Viperize(f.AddFlags) + require.NoError(t, command.ParseFlags([]string{ + "--es.tls.enabled=false", + "--es.version=7", + "--es.server-urls=" + esURL, + })) + f.InitFromViper(v, flagsSvc.Logger) + // set AllowTokenFromContext manually because we don't register the respective CLI flag from query svc + f.Options.Primary.AllowTokenFromContext = true + require.NoError(t, f.Initialize(metrics.NullFactory, flagsSvc.Logger)) + + spanReader, err := f.CreateSpanReader() + require.NoError(t, err) + + querySvc := querysvc.NewQueryService(spanReader, nil, querysvc.QueryServiceOptions{}) + server, err := NewServer(flagsSvc.Logger, querySvc, nil, + &QueryOptions{GRPCHostPort: ":0", HTTPHostPort: ":0", BearerTokenPropagation: true}, + tenancy.NewManager(&tenancy.Options{}), + opentracing.NoopTracer{}, + ) + require.NoError(t, err) + require.NoError(t, server.Start()) + return server +} + +func TestBearerTokenPropagation(t *testing.T) { + testCases := []struct { + name string + headerValue string + headerName string + }{ + {name: "Bearer token", headerName: "Authorization", headerValue: bearerHeader}, + {name: "Raw Bearer token", headerName: "Authorization", headerValue: bearerToken}, + {name: "X-Forwarded-Access-Token", headerName: "X-Forwarded-Access-Token", headerValue: bearerHeader}, + } + + esSrv := runMockElasticsearchServer(t) + defer esSrv.Close() + t.Logf("mock ES server started on %s", esSrv.URL) + + querySrv := runQueryService(t, esSrv.URL) + defer querySrv.Close() + queryAddr := querySrv.httpConn.Addr().String() + // Will try to load service names, this should return 200. + url := fmt.Sprintf("http://%s/api/services", queryAddr) + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + req, err := http.NewRequest("GET", url, nil) + require.NoError(t, err) + req.Header.Add(testCase.headerName, testCase.headerValue) + + client := &http.Client{} + resp, err := client.Do(req) + require.NoError(t, err) + require.NotNil(t, resp) + + assert.Equal(t, resp.StatusCode, http.StatusOK) + }) + } +} diff --git a/plugin/storage/integration/token_propagation_test.go b/plugin/storage/integration/token_propagation_test.go deleted file mode 100644 index d1ced9fea9e..00000000000 --- a/plugin/storage/integration/token_propagation_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2019 The Jaeger Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build token_propagation -// +build token_propagation - -package integration - -import ( - "context" - "encoding/json" - "net/http" - "strings" - "testing" - - "github.com/olivere/elastic" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - bearerToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI" - bearerHeader = "Bearer " + bearerToken - queryUrl = "http://127.0.0.1:16686/api/services" -) - -type esTokenPropagationTestHandler struct { - test *testing.T -} - -func (h *esTokenPropagationTestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - authValue := r.Header.Get("Authorization") - if authValue != "" { - headerValue := strings.Split(authValue, " ") - token := "" - if len(headerValue) == 2 { - if headerValue[0] == "Bearer" { - token = headerValue[1] - } - } else if len(headerValue) == 1 { - token = authValue - } - assert.Equal(h.test, bearerToken, token) - if token == bearerToken { - // Return empty results, we don't care about the result here. - // we just need to make sure the token was propagated to the storage and the query-service returns 200 - ret := new(elastic.SearchResult) - json_ret, _ := json.Marshal(ret) - w.Header().Add("Content-Type", "application/json; charset=UTF-8") - w.Write(json_ret) - return - } - } - // No token, return error! - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) -} - -func createElasticSearchMock(srv *http.Server, t *testing.T) { - srv.Handler = &esTokenPropagationTestHandler{ - test: t, - } - if err := srv.ListenAndServe(); err != http.ErrServerClosed { - assert.Error(t, err) - } -} - -func TestBearTokenPropagation(t *testing.T) { - testCases := []struct { - name string - headerValue string - headerName string - }{ - {name: "Bearer token", headerName: "Authorization", headerValue: bearerHeader}, - {name: "Raw Bearer token", headerName: "Authorization", headerValue: bearerToken}, - {name: "X-Forwarded-Access-Token", headerName: "X-Forwarded-Access-Token", headerValue: bearerHeader}, - } - - // Run elastic search mocked server in background.. - // is not a full server, just mocked the necessary stuff for this test. - srv := &http.Server{Addr: ":19200"} - defer srv.Shutdown(context.Background()) - - go createElasticSearchMock(srv, t) - - // Test cases. - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - // Ask for services query, this should return 200 - req, err := http.NewRequest("GET", queryUrl, nil) - require.NoError(t, err) - req.Header.Add(testCase.headerName, testCase.headerValue) - client := &http.Client{} - resp, err := client.Do(req) - assert.NoError(t, err) - assert.NotNil(t, resp) - if err == nil && resp != nil { - assert.Equal(t, resp.StatusCode, http.StatusOK) - } - }) - } -} diff --git a/scripts/es-integration-test.sh b/scripts/es-integration-test.sh index a60370dee3e..ce3b4c30f0b 100755 --- a/scripts/es-integration-test.sh +++ b/scripts/es-integration-test.sh @@ -5,18 +5,6 @@ set -euxf -o pipefail # use global variables to reflect status of db db_is_up= -db_cid= - -exit_trap_command="echo executing exit traps" -function cleanup { - eval "$exit_trap_command" -} -trap cleanup EXIT - -function add_exit_trap { - local to_add=$1 - exit_trap_command="$exit_trap_command; $to_add" -} usage() { echo $"Usage: $0 " @@ -59,20 +47,6 @@ setup_opensearch() { echo ${cid} } -# bearer token propagaton test uses real query service, but starts a fake DB at 19200 -setup_query() { - local distro=$1 - local os=$(go env GOOS) - local arch=$(go env GOARCH) - local params=( - --es.tls.enabled=false - --es.version=7 - --es.server-urls=http://127.0.0.1:19200 - --query.bearer-token-propagation=true - ) - SPAN_STORAGE_TYPE=${distro} ./cmd/query/query-${os}-${arch} ${params[@]} -} - wait_for_storage() { local distro=$1 local url=$2 @@ -108,6 +82,8 @@ bring_up_storage() { local distro=$1 local version=$2 local cid + + echo "starting ${distro} ${version}" for retry in 1 2 3 do if [ ${distro} = "elasticsearch" ]; then @@ -121,12 +97,14 @@ bring_up_storage() { wait_for_storage ${distro} "http://localhost:9200" ${cid} if [ ${db_is_up} = "1" ]; then break - else - echo "ERROR: unable to start ${distro}" - exit 1 fi done - db_cid=${cid} + if [ ${db_is_up} = "1" ]; then + trap "teardown_storage ${cid}" EXIT + else + echo "ERROR: unable to start ${distro}" + exit 1 + fi } teardown_storage() { @@ -134,44 +112,15 @@ teardown_storage() { docker kill ${cid} } -teardown_query() { - local pid=$1 - kill -9 ${pid} -} - -build_query() { - make build-crossdock-ui-placeholder - make build-query -} - -run_integration_test() { +main() { + check_arg "$@" local distro=$1 + local version=$2 + + bring_up_storage ${distro} ${version} STORAGE=${distro} make storage-integration-test make index-cleaner-integration-test make index-rollover-integration-test } -run_token_propagation_test() { - local distro=$1 - build_query - setup_query ${distro} & - local pid=$! - add_exit_trap "teardown_query ${pid}" - make token-propagation-integration-test -} - -main() { - check_arg "$@" - - echo "Preparing $1 $2" - bring_up_storage "$1" "$2" - add_exit_trap "teardown_storage ${db_cid}" - - echo "Executing main integration tests" - run_integration_test "$1" - - echo "Executing token propagation test" - run_token_propagation_test "$1" -} - main "$@"