diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 6f95adaed7..35ec078c95 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -157,6 +157,7 @@ jobs: - name: Setup Bats and bats libs uses: bats-core/bats-action@42fcc8700f773c075a16a90eb11674c0318ad507 # 3.0.1 - run: test/service-start.bats + - run: test/service-cors.bats - run: test/tdf-roundtrips.bats - run: test/policy-service.bats - name: verify builtin casbin policy @@ -395,7 +396,7 @@ jobs: focus-sdk: go # use commit instead of ref so we can "go get" specific sdk version platform-ref: ${{ github.event.pull_request.head.sha || github.sha }} lts - + tests-bdd: name: Cucumber BDD Tests runs-on: ubuntu-22.04 @@ -423,21 +424,21 @@ jobs: with: go-version-file: ./tests-bdd/go.mod cache: false - + - name: Build local platform-cukes image for testing run: docker build -t platform-cukes . - name: Run BDD Tests run: | CUKES_LOG_HANDLER=console go test ./tests-bdd -v --tags=cukes --godog.random --godog.format="cucumber:$(pwd)/cukes_platform_report.json,pretty:$(pwd)/cukes_platform_report.log,pretty" ./features - + - name: Check for undefined steps run: | if grep -qi "Undefined" cukes_platform_report.log; then echo "❌ Undefined steps found in BDD tests!" exit 1 fi - + - name: Get logs on failure if: failure() || cancelled() run: | diff --git a/opentdf-dev.yaml b/opentdf-dev.yaml index 3361bd6ec4..244ab6ee52 100644 --- a/opentdf-dev.yaml +++ b/opentdf-dev.yaml @@ -72,7 +72,7 @@ server: username_claim: # preferred_username # That claim to access groups (i.e. realm_access.roles) groups_claim: # realm_access.roles - # Claim the represents the idP client ID + # Claim the represents the idP client ID client_id_claim: # azp ## Extends the builtin policy extension: | @@ -119,6 +119,7 @@ server: # insecure: true # If collector is just HTTP, not HTTPS # headers: {} # Add if authentication is needed cors: + enabled: true # "*" to allow any origin or a specific domain like "https://yourdomain.com" allowedorigins: - "*" diff --git a/service/internal/server/server.go b/service/internal/server/server.go index 40e56cba83..c38bd16846 100644 --- a/service/internal/server/server.go +++ b/service/internal/server/server.go @@ -383,7 +383,7 @@ var rpcPathRegex = regexp.MustCompile(`^/[\w\.]+\.[\w\.]+/[\w]+$`) func routeConnectRPCRequests(connectRPC http.Handler, httpHandler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // contentType := r.Header.Get("Content-Type") - if (r.Method == http.MethodPost || r.Method == http.MethodGet) && rpcPathRegex.MatchString(r.URL.Path) { + if (r.Method == http.MethodPost || r.Method == http.MethodGet || r.Method == http.MethodOptions) && rpcPathRegex.MatchString(r.URL.Path) { connectRPC.ServeHTTP(w, r) } else { httpHandler.ServeHTTP(w, r) diff --git a/test/service-cors.bats b/test/service-cors.bats new file mode 100755 index 0000000000..bab764c3b6 --- /dev/null +++ b/test/service-cors.bats @@ -0,0 +1,112 @@ +#!/usr/bin/env bats + +# Tests for validating CORS configuration allows Authorization header + +# Set base URL based on TLS configuration +BASE_URL="http://localhost:8080" +CURL_OPTIONS="" + +# Check if TLS is enabled via environment variable +if [[ "${TLS_ENABLED:-false}" == "true" ]]; then + BASE_URL="https://localhost:8080" + CURL_OPTIONS="-k" # Allow insecure connections for self-signed certs +fi + +@test "CORS: preflight request includes Authorization in allowed headers" { + run curl -i -X OPTIONS $CURL_OPTIONS \ + -H "Origin: http://localhost:3000" \ + -H "Access-Control-Request-Method: POST" \ + -H "Access-Control-Request-Headers: authorization,content-type,connect-protocol-version" \ + ${BASE_URL}/policy.namespaces.NamespaceService/GetNamespace + + echo "$output" + + # Verify 200 OK response (HTTP/1.1 or HTTP/2) + [[ "$output" =~ "HTTP/2 200" ]] || [[ "$output" =~ "HTTP/1.1 200 OK" ]] + + # Verify Access-Control-Allow-Headers includes Authorization + [[ "$output" =~ [Aa]ccess-[Cc]ontrol-[Aa]llow-[Hh]eaders:.*[Aa]uthorization ]] + + # Verify Access-Control-Allow-Origin is set + [[ "$output" =~ [Aa]ccess-[Cc]ontrol-[Aa]llow-[Oo]rigin:\ http://localhost:3000 ]] + + # Verify credentials are allowed + [[ "$output" =~ [Aa]ccess-[Cc]ontrol-[Aa]llow-[Cc]redentials:\ true ]] + + # Verify max-age is set + [[ "$output" =~ [Aa]ccess-[Cc]ontrol-[Mm]ax-[Aa]ge:\ 3600 ]] +} + +@test "CORS: preflight request with different headers" { + run curl -i -X OPTIONS $CURL_OPTIONS \ + -H "Origin: http://localhost:3000" \ + -H "Access-Control-Request-Method: POST" \ + -H "Access-Control-Request-Headers: authorization" \ + ${BASE_URL}/policy.namespaces.NamespaceService/GetNamespace + + echo "$output" + + # Verify 200 OK response (HTTP/1.1 or HTTP/2) + [[ "$output" =~ "HTTP/2 200" ]] || [[ "$output" =~ "HTTP/1.1 200 OK" ]] + + # Verify Authorization is in allowed headers + [[ "$output" =~ [Aa]ccess-[Cc]ontrol-[Aa]llow-[Hh]eaders:.*[Aa]uthorization ]] +} + +@test "CORS: actual request with Authorization header" { + run curl -i -X POST $CURL_OPTIONS \ + -H "Origin: http://localhost:3000" \ + -H "Authorization: Bearer test-token" \ + -H "Content-Type: application/json" \ + -H "Connect-Protocol-Version: 1" \ + ${BASE_URL}/policy.namespaces.NamespaceService/GetNamespace + + echo "$output" + + # Verify CORS headers are in response (status may be 401 due to invalid token, but CORS should work) + [[ "$output" =~ [Aa]ccess-[Cc]ontrol-[Aa]llow-[Oo]rigin:\ http://localhost:3000 ]] + [[ "$output" =~ [Aa]ccess-[Cc]ontrol-[Aa]llow-[Cc]redentials:\ true ]] +} + +@test "CORS: wildcard origin configuration" { + run curl -i -X OPTIONS $CURL_OPTIONS \ + -H "Origin: http://example.com" \ + -H "Access-Control-Request-Method: POST" \ + -H "Access-Control-Request-Headers: authorization,content-type" \ + ${BASE_URL}/policy.namespaces.NamespaceService/GetNamespace + + echo "$output" + + # With wildcard ("*") in config, different origins should work + # Server should return 200 OK (HTTP/1.1 or HTTP/2) + [[ "$output" =~ "HTTP/2 200" ]] || [[ "$output" =~ "HTTP/1.1 200 OK" ]] + + # Origin should be reflected back or wildcard + [[ "$output" =~ [Aa]ccess-[Cc]ontrol-[Aa]llow-[Oo]rigin: ]] +} + +@test "CORS: verify Content-Type in allowed headers" { + run curl -i -X OPTIONS $CURL_OPTIONS \ + -H "Origin: http://localhost:3000" \ + -H "Access-Control-Request-Method: POST" \ + -H "Access-Control-Request-Headers: content-type" \ + ${BASE_URL}/policy.namespaces.NamespaceService/GetNamespace + + echo "$output" + + # Verify Content-Type is in allowed headers + [[ "$output" =~ [Aa]ccess-[Cc]ontrol-[Aa]llow-[Hh]eaders:.*[Cc]ontent-[Tt]ype ]] +} + +@test "CORS: verify Connect-Protocol-Version in allowed headers" { + run curl -i -X OPTIONS $CURL_OPTIONS \ + -H "Origin: http://localhost:3000" \ + -H "Access-Control-Request-Method: POST" \ + -H "Access-Control-Request-Headers: connect-protocol-version" \ + ${BASE_URL}/policy.namespaces.NamespaceService/GetNamespace + + echo "$output" + + # Verify Connect-Protocol-Version is in allowed headers + [[ "$output" =~ [Aa]ccess-[Cc]ontrol-[Aa]llow-[Hh]eaders:.*[Cc]onnect-[Pp]rotocol-[Vv]ersion ]] +}