181 lines
5.3 KiB
Go
181 lines
5.3 KiB
Go
/*
|
|
Copyright 2021 The Dapr 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 opa_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/dapr/components-contrib/middleware"
|
|
opaMw "github.com/dapr/components-contrib/middleware/http/opa"
|
|
"github.com/dapr/components-contrib/tests/certification/embedded"
|
|
"github.com/dapr/components-contrib/tests/certification/flow"
|
|
"github.com/dapr/components-contrib/tests/certification/flow/app"
|
|
"github.com/dapr/components-contrib/tests/certification/flow/sidecar"
|
|
httpMiddlewareLoader "github.com/dapr/dapr/pkg/components/middleware/http"
|
|
"github.com/dapr/dapr/pkg/config/protocol"
|
|
httpMiddleware "github.com/dapr/dapr/pkg/middleware/http"
|
|
dapr_testing "github.com/dapr/dapr/pkg/testing"
|
|
"github.com/dapr/go-sdk/service/common"
|
|
"github.com/dapr/kit/logger"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
const (
|
|
appID = "myapp"
|
|
invokeMethod = "mymethod"
|
|
responseHeaderName = "x-custom-header"
|
|
)
|
|
|
|
var (
|
|
// Logger
|
|
log = logger.NewLogger("dapr.components")
|
|
)
|
|
|
|
func TestHTTPMiddlewareOpa(t *testing.T) {
|
|
|
|
ports, err := dapr_testing.GetFreePorts(6)
|
|
require.NoError(t, err)
|
|
|
|
grpcPorts := [2]int{ports[0], ports[3]}
|
|
httpPorts := [2]int{ports[1], ports[4]}
|
|
appPorts := [2]int{ports[2], ports[5]}
|
|
|
|
client := http.Client{}
|
|
|
|
// Application setup code
|
|
application := func(ctx flow.Context, s common.Service) error {
|
|
s.AddServiceInvocationHandler(invokeMethod, func(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {
|
|
return &common.Content{}, nil
|
|
})
|
|
return nil
|
|
}
|
|
|
|
type sendRequestOpts struct {
|
|
Body string
|
|
}
|
|
|
|
sendRequest := func(parentCtx context.Context, port int, opts *sendRequestOpts) (int, string, error) {
|
|
invokeUrl := fmt.Sprintf("http://localhost:%d/v1.0/invoke/%s/method/%s", port, appID, invokeMethod)
|
|
|
|
reqCtx, reqCancel := context.WithTimeout(parentCtx, 150*time.Second)
|
|
// reqCtx, reqCancel := context.WithTimeout(parentCtx, 5*time.Second)
|
|
defer reqCancel()
|
|
|
|
req, err := http.NewRequestWithContext(reqCtx, http.MethodPost, invokeUrl, bytes.NewBufferString(opts.Body))
|
|
if err != nil {
|
|
return 0, "", fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
return 0, "", fmt.Errorf("request error: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
// Drain before closing
|
|
_, _ = io.Copy(io.Discard, res.Body)
|
|
res.Body.Close()
|
|
}()
|
|
|
|
return res.StatusCode, res.Header.Get(responseHeaderName), nil
|
|
}
|
|
|
|
// Run tests to check if the bearer token is validated correctly
|
|
opaTests := func(ctx flow.Context) error {
|
|
tests := []struct {
|
|
name string
|
|
body string
|
|
statusCode int
|
|
expectedHeaderValue string
|
|
}{
|
|
{
|
|
name: "default response",
|
|
body: "",
|
|
statusCode: http.StatusTeapot,
|
|
expectedHeaderValue: "",
|
|
}, {
|
|
name: "allowed request",
|
|
body: "allow-ok",
|
|
statusCode: http.StatusOK,
|
|
expectedHeaderValue: "",
|
|
}, {
|
|
name: "redirected",
|
|
body: "redirect",
|
|
statusCode: http.StatusMovedPermanently,
|
|
expectedHeaderValue: "redirected",
|
|
},
|
|
}
|
|
|
|
ctx.T.Run("bearer token validation", func(t *testing.T) {
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
// Invoke sidecar
|
|
resStatus, resHeaderValue, err := sendRequest(ctx.Context, httpPorts[0], &sendRequestOpts{
|
|
Body: tt.body,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.statusCode, resStatus)
|
|
assert.Equal(t, tt.expectedHeaderValue, resHeaderValue)
|
|
})
|
|
}
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
flow.New(t, "OPA HTTP Middleware").
|
|
// Start app and sidecar
|
|
Step(app.Run("Start application", fmt.Sprintf(":%d", appPorts[0]), application)).
|
|
Step(sidecar.Run(appID,
|
|
append(componentRuntimeOptions(),
|
|
embedded.WithAppProtocol(protocol.HTTPProtocol, strconv.Itoa(appPorts[0])),
|
|
embedded.WithDaprGRPCPort(strconv.Itoa(grpcPorts[0])),
|
|
embedded.WithDaprHTTPPort(strconv.Itoa(httpPorts[0])),
|
|
embedded.WithResourcesPath("./resources"),
|
|
embedded.WithAPILoggingEnabled(true),
|
|
embedded.WithProfilingEnabled(false),
|
|
)...,
|
|
)).
|
|
// Tests
|
|
Step("opa validation", opaTests).
|
|
// Run
|
|
Run()
|
|
|
|
}
|
|
|
|
func componentRuntimeOptions() []embedded.Option {
|
|
middlewareRegistry := httpMiddlewareLoader.NewRegistry()
|
|
middlewareRegistry.Logger = log
|
|
middlewareRegistry.RegisterComponent(func(log logger.Logger) httpMiddlewareLoader.FactoryMethod {
|
|
return func(metadata middleware.Metadata) (httpMiddleware.Middleware, error) {
|
|
return opaMw.NewMiddleware(log).GetHandler(context.Background(), metadata)
|
|
}
|
|
}, "opa")
|
|
|
|
return []embedded.Option{
|
|
embedded.WithHTTPMiddlewares(middlewareRegistry),
|
|
}
|
|
}
|