KNOX-3214 - Add integration tests (#1116)

diff --git a/.github/workflows/build/Dockerfile b/.github/workflows/build/Dockerfile
new file mode 100644
index 0000000..5d6056a
--- /dev/null
+++ b/.github/workflows/build/Dockerfile
@@ -0,0 +1,47 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you 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.
+
+FROM eclipse-temurin:17-jre
+
+MAINTAINER moresandeep
+
+RUN useradd -ms /bin/bash gateway
+
+# Create directories
+RUN mkdir /knox-runtime
+RUN mkdir /knox-runtime/knoxshell
+
+# Copy artifacts
+COPY knox-temp-artifacts /knox-temp-artifacts-staging
+COPY knoxshell-temp-artifacts /knoxshell-temp-artifacts-staging
+
+# Move runtime to new location
+RUN mv /knox-temp-artifacts-staging/*/* /knox-runtime/ && \
+    mv /knoxshell-temp-artifacts-staging/*/* /knox-runtime/knoxshell/ && \
+    rm -rf /knox-temp-artifacts-staging /knoxshell-temp-artifacts-staging
+
+# Add configuration
+ADD master /knox-runtime/data/security/master
+ADD gateway-site.xml /knox-runtime/conf/gateway-site.xml
+ADD conf/topologies/knoxtoken.xml /knox-runtime/conf/topologies/knoxtoken.xml
+ADD conf/topologies/knoxldap.xml /knox-runtime/conf/topologies/knoxldap.xml
+ADD conf/topologies/health.xml /knox-runtime/conf/topologies/health.xml
+
+RUN chown -R gateway /knox-runtime/
+
+ADD ldap.sh /ldap.sh
+ADD gateway.sh /gateway.sh
+
+RUN chmod +x /ldap.sh /gateway.sh
diff --git a/.github/workflows/build/Dockerfile.local b/.github/workflows/build/Dockerfile.local
new file mode 100644
index 0000000..ba95ae1
--- /dev/null
+++ b/.github/workflows/build/Dockerfile.local
@@ -0,0 +1,73 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you 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.
+
+FROM maven:3.9.9-eclipse-temurin-17
+
+MAINTAINER moresandeep
+
+# Install dependencies
+RUN apt-get update
+
+RUN apt-get install -y git
+
+RUN useradd -ms /bin/bash gateway
+
+# Clone our dev branch
+ARG knoxurl
+ARG branch
+
+RUN git clone -b $branch $knoxurl knox
+
+RUN mkdir /knox/knox-temp-artifacts
+RUN mkdir /knox/knoxshell-temp-artifacts
+
+# Update maven settings to ignore jcenter repo
+#ADD settings.xml /home/gateway/.m2/settings.xml
+#RUN mv /home/gateway/.m2/settings.xml ~/.m2/settings.xml
+ADD settings.xml /usr/share/maven/ref/
+
+# Skipping tests here for faster turnaround.
+RUN cd knox && mvn -settings /usr/share/maven/ref/settings.xml clean -Ppackage,release install -Dforbiddenapis.skip=true -Denforcer.skip -Dpmd.failOnViolation=false -DskipTests=true -Dcheckstyle.skip=true -Dspotbugs.skip=true -Dpmd.skip=true -Drat.skip -DskipTests && tar -xvzf target/*/knox-*.tar.gz -C knox-temp-artifacts && tar -xvzf /knox/target/*/knoxshell-*.tar.gz -C knoxshell-temp-artifacts
+
+# move runtime to new location
+# See https://github.com/docker/compose/issues/4581#issuecomment-321386605
+# for KnoxShell dance
+RUN mkdir /knox-runtime
+RUN mkdir /knoxshell
+RUN mkdir /knox-runtime/knoxshell
+RUN mv /knox/knox-temp-artifacts/*/* /knox-runtime
+RUN mv /knox/knoxshell-temp-artifacts/*/* /knox-runtime/knoxshell
+
+# delete build artifacts
+RUN rm -rf /knox
+
+ADD master /knox-runtime/data/security/master
+# Enable websockets
+ADD gateway-site.xml /knox-runtime/conf/gateway-site.xml
+ADD conf/topologies/knoxtoken.xml /knox-runtime/conf/topologies/knoxtoken.xml
+ADD conf/topologies/health.xml /knox-runtime/conf/topologies/health.xml
+ADD conf/topologies/knoxldap.xml /knox-runtime/conf/topologies/knoxldap.xml
+
+RUN chown -R gateway /knox-runtime/
+
+# Cleanup 
+RUN rm -rf /home/gateway/.m2/repository
+
+ADD ldap.sh /ldap.sh
+ADD gateway.sh /gateway.sh
+
+RUN chmod +x /ldap.sh
+RUN chmod +x /gateway.sh
+
diff --git a/.github/workflows/build/conf/topologies/health.xml b/.github/workflows/build/conf/topologies/health.xml
new file mode 100644
index 0000000..0117d9f
--- /dev/null
+++ b/.github/workflows/build/conf/topologies/health.xml
@@ -0,0 +1,29 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+<topology>
+    <gateway>
+        <provider>
+            <role>authentication</role>
+            <name>Anonymous</name>
+            <enabled>true</enabled>
+        </provider>
+    </gateway>
+    <service>
+        <role>HEALTH</role>
+    </service>
+</topology>
\ No newline at end of file
diff --git a/.github/workflows/build/conf/topologies/knoxldap.xml b/.github/workflows/build/conf/topologies/knoxldap.xml
new file mode 100644
index 0000000..1027976
--- /dev/null
+++ b/.github/workflows/build/conf/topologies/knoxldap.xml
@@ -0,0 +1,82 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+<topology>
+	<gateway>
+		<provider>
+			<role>authentication</role>
+			<name>ShiroProvider</name>
+			<enabled>true</enabled>
+			<param>
+				<name>sessionTimeout</name>
+				<value>30</value>
+			</param>
+			<param>
+				<name>main.ldapRealm</name>
+				<value>org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm</value>
+			</param>
+			<param>
+				<name>main.ldapRealm.userDnTemplate</name>
+				<value>uid={0},ou=people,dc=hadoop,dc=apache,dc=org</value>
+			</param>
+			<param>
+				<name>main.ldapRealm.contextFactory.url</name>
+				<value>ldap://ldap:33389</value>
+			</param>
+			<param>
+				<name>main.ldapRealm.contextFactory.authenticationMechanism</name>
+				<value>simple</value>
+			</param>
+			<param>
+				<name>urls./**</name>
+				<value>authcBasic</value>
+			</param>
+		</provider>
+		<provider>
+			<role>identity-assertion</role>
+			<name>Default</name>
+			<enabled>true</enabled>
+			<param>
+				<name>group.principal.mapping</name>
+				<value>admin=longGroupName1,longGroupName2,longGroupName3,longGroupName4</value>
+			</param>
+		</provider>
+	</gateway>
+	<service>
+		<role>KNOXTOKEN</role>
+		<param>
+			<name>knoxsso.token.ttl</name>
+			<value>86400000</value>
+		</param>
+	</service>
+	<service>
+		<role>KNOX-AUTH-SERVICE</role>
+		<param>
+			<name>preauth.auth.header.actor.id.name</name>
+			<value>x-knox-actor-username</value>
+		</param>
+		<param>
+			<name>preauth.auth.header.actor.groups.prefix</name>
+			<value>x-knox-actor-groups</value>
+		</param>
+		<param>
+			<name>preauth.group.filter.pattern</name>
+			<value>[^\s]+</value>
+		</param>
+	</service>
+</topology>
+
diff --git a/.github/workflows/build/conf/topologies/knoxtoken.xml b/.github/workflows/build/conf/topologies/knoxtoken.xml
new file mode 100644
index 0000000..d103603
--- /dev/null
+++ b/.github/workflows/build/conf/topologies/knoxtoken.xml
@@ -0,0 +1,56 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+<topology>
+	<gateway>
+		<provider>
+			<role>federation</role>
+			<name>JWTProvider</name>
+			<enabled>true</enabled>
+			<param>
+				<name>knox.token.use.cookie</name>
+				<value>false</value>
+			</param>
+			<param>
+				<name>jwt.expected.sigalg</name>
+				<value>RS256</value>
+			</param>
+		</provider>
+	</gateway>
+	<service>
+		<role>KNOXTOKEN</role>
+		<param>
+			<name>knoxsso.token.ttl</name>
+			<value>86400000</value>
+		</param>
+	</service>
+	<service>
+		<role>KNOX-AUTH-SERVICE</role>
+		<param>
+			<name>preauth.auth.header.actor.id.name</name>
+			<value>x-knox-actor-username</value>
+		</param>
+		<param>
+			<name>preauth.auth.header.actor.groups.prefix</name>
+			<value>x-knox-actor-groups</value>
+		</param>
+		<param>
+			<name>preauth.group.filter.pattern</name>
+			<value>[^\s]+</value>
+		</param>
+	</service>
+</topology>
\ No newline at end of file
diff --git a/.github/workflows/build/gateway-site.xml b/.github/workflows/build/gateway-site.xml
new file mode 100644
index 0000000..add31b8
--- /dev/null
+++ b/.github/workflows/build/gateway-site.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+<configuration>
+
+    <property>
+        <name>gateway.service.alias.impl</name>
+        <value>org.apache.knox.gateway.services.security.impl.DefaultAliasService</value>
+    </property>
+    <property>
+        <name>gateway.port</name>
+        <value>8443</value>
+        <description>The HTTP port for the Gateway.</description>
+    </property>
+
+    <property>
+        <name>gateway.path</name>
+        <value>gateway</value>
+        <description>The default context path for the gateway.</description>
+    </property>
+
+    <property>
+        <name>gateway.gateway.conf.dir</name>
+        <value>deployments</value>
+        <description>The directory within GATEWAY_HOME that contains gateway topology files and deployments.</description>
+    </property>
+
+    <!-- @since 0.10 Websocket configs -->
+    <property>
+        <name>gateway.websocket.feature.enabled</name>
+        <value>true</value>
+        <description>Enable/Disable websocket feature.</description>
+    </property>
+
+    <property>
+        <name>gateway.scope.cookies.feature.enabled</name>
+        <value>false</value>
+        <description>Enable/Disable cookie scoping feature.</description>
+    </property>
+
+    <!-- @since 2.0.0 WebShell configs -->
+    <!-- must have websocket enabled to use webshell --> 
+    <property>
+        <name>gateway.webshell.feature.enabled</name>
+        <value>true</value>
+        <description>Enable/Disable webshell feature.</description>
+    </property>
+    <property>
+        <name>gateway.webshell.max.concurrent.sessions</name>
+        <value>20</value>
+        <description>Maximum number of total concurrent webshell sessions</description>
+    </property>
+    <property>
+        <name>gateway.webshell.read.buffer.size</name>
+        <value>1024</value>
+        <description>Web Shell buffer size for reading</description>
+    </property>
+
+    <!-- @since 2.0.0 websocket JWT validation configs -->
+    <property>
+        <name>gateway.websocket.JWT.validation.feature.enabled</name>
+        <value>true</value>
+        <description>Enable/Disable websocket JWT validation at websocket layer.</description>
+    </property>
+
+    <!-- @since 1.5.0 homepage logout -->
+    <property>
+        <name>knox.homepage.logout.enabled</name>
+        <value>true</value>
+        <description>Enable/disable logout from the Knox Homepage.</description>
+    </property>
+
+    <!-- @since 1.6.0 token management related properties -->
+    <property>
+        <name>gateway.knox.token.eviction.grace.period</name>
+        <value>0</value>
+        <description>A duration (in seconds) beyond a token’s expiration to wait before evicting its state. This configuration only applies when server-managed token state is enabled either in gateway-site or at the topology level.</description>
+    </property>
+
+    <!-- Knox Admin related config -->
+    <property>
+        <name>gateway.knox.admin.groups</name>
+        <value>admin</value>
+    </property>
+
+    <!-- DEMO LDAP config for Hadoop Group Provider -->
+    <property>
+        <name>gateway.group.config.hadoop.security.group.mapping</name>
+        <value>org.apache.hadoop.security.LdapGroupsMapping</value>
+    </property>
+    <property>
+        <name>gateway.group.config.hadoop.security.group.mapping.ldap.bind.user</name>
+        <value>uid=guest,ou=people,dc=hadoop,dc=apache,dc=org</value>
+    </property>
+    <property>
+        <name>gateway.group.config.hadoop.security.group.mapping.ldap.bind.password</name>
+        <value>guest-password</value>
+    </property>
+    <property>
+        <name>gateway.group.config.hadoop.security.group.mapping.ldap.url</name>
+        <value>ldap://localhost:33389</value>
+    </property>
+    <property>
+        <name>gateway.group.config.hadoop.security.group.mapping.ldap.base</name>
+        <value></value>
+    </property>
+    <property>
+        <name>gateway.group.config.hadoop.security.group.mapping.ldap.search.filter.user</name>
+        <value>(&amp;(|(objectclass=person)(objectclass=applicationProcess))(cn={0}))</value>
+    </property>
+    <property>
+        <name>gateway.group.config.hadoop.security.group.mapping.ldap.search.filter.group</name>
+        <value>(objectclass=groupOfNames)</value>
+    </property>
+    <property>
+        <name>gateway.group.config.hadoop.security.group.mapping.ldap.search.attr.member</name>
+        <value>member</value>
+    </property>
+    <property>
+        <name>gateway.group.config.hadoop.security.group.mapping.ldap.search.attr.group.name</name>
+        <value>cn</value>
+    </property>
+    <property>
+        <name>gateway.dispatch.whitelist.services</name>
+        <value>DATANODE,HBASEUI,HDFSUI,JOBHISTORYUI,NODEUI,YARNUI,knoxauth</value>
+        <description>The comma-delimited list of service roles for which the gateway.dispatch.whitelist should be applied.</description>
+    </property>
+    <property>
+        <name>gateway.dispatch.whitelist</name>
+        <value>^https?:\/\/(www\.local\.com|localhost|127\.0\.0\.1|0:0:0:0:0:0:0:1|::1):[0-9].*$</value>
+        <description>The whitelist to be applied for dispatches associated with the service roles specified by gateway.dispatch.whitelist.services.
+        If the value is DEFAULT, a domain-based whitelist will be derived from the Knox host.</description>
+    </property>
+    <property>
+        <name>gateway.xforwarded.header.context.append.servicename</name>
+        <value>LIVYSERVER</value>
+        <description>Add service name to x-forward-context header for the list of services defined above.</description>
+    </property>
+
+</configuration>
diff --git a/.github/workflows/build/gateway.sh b/.github/workflows/build/gateway.sh
new file mode 100755
index 0000000..f31dd44
--- /dev/null
+++ b/.github/workflows/build/gateway.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you 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.
+
+
+# Start Knox
+java -jar /knox-runtime/bin/gateway.jar
\ No newline at end of file
diff --git a/.github/workflows/build/ldap.sh b/.github/workflows/build/ldap.sh
new file mode 100755
index 0000000..ad3d9c8
--- /dev/null
+++ b/.github/workflows/build/ldap.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you 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.
+java -jar /knox-runtime/bin/ldap.jar /knox-runtime/conf
\ No newline at end of file
diff --git a/.github/workflows/build/master b/.github/workflows/build/master
new file mode 100644
index 0000000..3b3d9ed
--- /dev/null
+++ b/.github/workflows/build/master
@@ -0,0 +1,2 @@
+#1.0# Tue, Sep 23 2025 12:12:59.057
+TXpTZkVGM0VTYk09OjpGUncvV1JHOGIrVll5dGRWeXFKR1pRPT06Om9nV2x6UVdSM0UvbWhuQ0VnSlhuUWc9PQ==
diff --git a/.github/workflows/build/settings.xml b/.github/workflows/build/settings.xml
new file mode 100644
index 0000000..ea1f0de
--- /dev/null
+++ b/.github/workflows/build/settings.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 
+                              https://maven.apache.org/xsd/settings-1.0.0.xsd">
+  <mirrors>
+    <mirror>
+      <id>replace-jcenter-with-central</id>
+      <mirrorOf>jcenter</mirrorOf>
+      <url>https://repo.maven.apache.org/maven2</url>
+    </mirror>
+  </mirrors>
+</settings>
diff --git a/.github/workflows/compose/docker-compose.yml b/.github/workflows/compose/docker-compose.yml
new file mode 100644
index 0000000..b114d3a
--- /dev/null
+++ b/.github/workflows/compose/docker-compose.yml
@@ -0,0 +1,63 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with this
+# work for additional information regarding copyright ownership. The ASF
+# licenses this file to you 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
+# <p>
+# http://www.apache.org/licenses/LICENSE-2.0
+# <p>
+# 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.
+
+services:
+  knox-dev:
+    build:
+      context: ../build
+      dockerfile: Dockerfile
+    image: apache/knox-dev:master
+
+  knox-dev-local:
+    build: &build_local
+      context: ../build
+      dockerfile: Dockerfile.local
+      args:
+        knoxurl: ${knoxurl:-https://github.com/apache/knox.git}
+        branch: ${branch:-master}
+    image: apache/knox-dev:local
+
+  ldap:
+    build: *build_local # Build LDAP service if not already built
+    image: apache/knox-dev:local
+    command: /ldap.sh
+
+  knox:
+    build: *build_local # Build Knox service if not already built
+    image: apache/knox-dev:local
+    command: /gateway.sh
+    volumes:
+#      - ./topologies:/knox-runtime/conf/topologies
+      - ./logs:/knox-runtime/logs
+#      - ./knoxshell:/knoxshell
+    depends_on:
+      - ldap
+
+  tests:
+    image: python:3.9-slim
+    working_dir: /tests
+    volumes:
+      - ../tests:/tests
+    environment:
+      - KNOX_GATEWAY_URL=https://knox:8443/
+    command: >
+      bash -c "pip install -r requirements.txt 
+      && echo 'Waiting for knox...' 
+      && sleep 30 
+      && python -m unittest discover -p 'test_*.py'"
+    depends_on:
+      - knox
+
+
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..1e7fd42
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,83 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you 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.
+
+name: Apache Knox Docker Compose Tests
+
+on:
+  pull_request:
+    branches:
+      - '**'  # triggers for all PRs
+  workflow_dispatch:
+
+jobs:
+  build-and-test:
+    if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'skip-tests')
+    runs-on: ubuntu-latest
+
+    env:
+      KNOX_URL: "https://github.com/apache/knox.git"
+      BRANCH: "${{ github.head_ref }}"  # PR branch being tested
+
+    steps:
+      - name: Checkout Repository
+        uses: actions/checkout@v4
+
+      - name: Set up JDK 17
+        uses: actions/setup-java@v4
+        with:
+          java-version: '17'
+          distribution: 'temurin'
+          cache: 'maven'
+
+      - name: Build with Maven
+        run: |
+          mvn clean -Ppackage,release install -T 1C \
+          -Dmaven.test.skip=true -Dpmd.skip=true -Dcpd.skip=true -Dcheckstyle.skip=true \
+          -Dspotbugs.skip=true -Drat.skip=true -Dforbiddenapis.skip=true -Denforcer.skip=true \
+          -Djacoco.skip=true -Dmaven.javadoc.skip=true -Dmaven.source.skip=true \
+          -Dshellcheck.skip=true -Dxml.skip=true \
+          -s .github/workflows/build/settings.xml
+
+      - name: Extract Artifacts
+        run: |
+          mkdir -p .github/workflows/build/knox-temp-artifacts .github/workflows/build/knoxshell-temp-artifacts
+          # Extract artifacts to the build directory where Dockerfile expects them
+          tar -xvzf target/*/knox-*.tar.gz -C .github/workflows/build/knox-temp-artifacts
+          tar -xvzf target/*/knoxshell-*.tar.gz -C .github/workflows/build/knoxshell-temp-artifacts
+
+      - name: Set up Docker Compose
+        run: docker compose version
+
+      - name: Build Docker Images
+        run: |
+          export knoxurl=${KNOX_URL}
+          export branch=${BRANCH:-master}
+          # Build only knox-dev which is the runtime image using artifacts
+          docker compose -f ./.github/workflows/compose/docker-compose.yml build knox-dev
+
+      - name: Start Knox and LDAP Services
+        run: docker compose -f ./.github/workflows/compose/docker-compose.yml up -d
+
+      - name: Wait for services to stabilize
+        run: sleep 30  # Adjust as needed for services startup time
+
+      - name: Run Knox Integration Tests
+        run: |
+          # Run the tests service defined in docker-compose.yml
+          docker compose -f ./.github/workflows/compose/docker-compose.yml up --exit-code-from tests tests
+
+      - name: Tear Down Docker Compose
+        if: always()
+        run: docker compose -f ./.github/workflows/compose/docker-compose.yml down --volumes
diff --git a/.github/workflows/tests/README.md b/.github/workflows/tests/README.md
new file mode 100644
index 0000000..c15672d
--- /dev/null
+++ b/.github/workflows/tests/README.md
@@ -0,0 +1,118 @@
+# Adding Test Cases to GitHub Workflow
+
+This directory contains Python integration tests that run as part of the GitHub workflow.
+
+## Directory Structure
+
+- `test_*.py`: Python test files containing test cases.
+- `requirements.txt`: Python dependencies required for running the tests.
+
+## How to Add a New Test Case
+
+1. **Create a New Test File**:
+   Create a new Python file in this directory (`.github/workflows/tests/`). The filename **must** start with `test_` (e.g., `test_auth.py`) to be automatically discovered by the test runner.
+
+2. **Implement Test Logic**:
+   Use the `unittest` framework to structure your tests. You can include multiple test methods in a single class, and multiple classes in a single file. Each method starting with `test_` will be executed as a separate test case.
+
+   ```python
+   # Licensed to the Apache Software Foundation (ASF) under one or more
+   # contributor license agreements.  See the NOTICE file distributed with
+   # this work for additional information regarding copyright ownership.
+   # The ASF licenses this file to you 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.
+   
+   import unittest
+   import requests
+   import os
+   import urllib3
+
+   # Suppress InsecureRequestWarning since we use verify=False for self-signed certs
+   urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
+   class TestMyFeature(unittest.TestCase):
+       def setUp(self):
+           # Get the Knox Gateway URL from environment variables
+           # Default to localhost for local debugging outside Docker
+           self.base_url = os.environ.get("KNOX_GATEWAY_URL", "https://localhost:8443/")
+
+       def test_my_endpoint(self):
+           """
+           Description of what this test checks.
+           """
+           url = f"{self.base_url}gateway/sandbox/webhdfs/v1/?op=LISTSTATUS"
+           
+           print(f"Testing URL: {url}")
+           
+           # Make the request
+           # verify=False is needed for the dev environment's self-signed certs
+           response = requests.get(url, verify=False)
+           
+           # Assertions
+           self.assertEqual(response.status_code, 200)
+           # Add more assertions as needed
+
+       def test_another_endpoint(self):
+           """
+           Another test case in the same class.
+           """
+           # ... implementation ...
+           pass
+   ```
+
+3. **Add Dependencies**:
+   If your test requires additional Python libraries (other than `requests`), add them to `requirements.txt` in this directory.
+
+## Organizing Tests in Subdirectories
+
+You can organize tests into subdirectories (e.g., `tests/auth/`, `tests/proxy/`). For the test runner to discover them:
+
+1. The subdirectory **must** contain an `__init__.py` file (it can be empty).
+2. The test files inside must still match the `test_*.py` pattern.
+
+**Example structure:**
+
+```text
+tests/
+├── test_health.py
+├── auth/
+│   ├── __init__.py
+│   └── test_auth.py
+└── proxy/
+    ├── __init__.py
+    └── test_proxy.py
+```
+
+## How It Works
+
+The tests run in a dedicated Docker container defined in `../compose/docker-compose.yml`.
+
+1. The `tests` service mounts this directory (`.github/workflows/tests/`) to `/tests` inside the container.
+2. It installs dependencies from `requirements.txt`.
+3. It waits for the `knox` service to be ready.
+4. It runs `python -m unittest discover -p 'test_*.py'` to find and execute all test files.
+
+## Skipping Tests on Pull Requests
+
+If you want to skip the integration tests for a specific Pull Request (e.g., documentation changes), add the label **`skip-tests`** to the PR.
+
+## Running Tests Locally
+
+You can run these tests locally using Docker Compose from the `.github/workflows/compose` directory:
+
+```bash
+cd .github/workflows/compose
+docker-compose up --build --abort-on-container-exit
+```
+
+This will start the Knox environment and run the tests. The `tests` container will exit once tests are complete.
+
diff --git a/.github/workflows/tests/requirements.txt b/.github/workflows/tests/requirements.txt
new file mode 100644
index 0000000..3288e92
--- /dev/null
+++ b/.github/workflows/tests/requirements.txt
@@ -0,0 +1,2 @@
+requests
+
diff --git a/.github/workflows/tests/test_health.py b/.github/workflows/tests/test_health.py
new file mode 100644
index 0000000..421dc52
--- /dev/null
+++ b/.github/workflows/tests/test_health.py
@@ -0,0 +1,42 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you 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.
+import os
+import unittest
+import requests
+import urllib3
+
+# Suppress InsecureRequestWarning
+urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
+class TestKnoxHealth(unittest.TestCase):
+    def test_admin_api_health(self):
+        """
+        Basic health check to ensure Knox is up and running.
+        We expect a response 200 to indicate the server is up.
+        """
+        url = os.environ.get("KNOX_GATEWAY_URL", "https://localhost:8443/")
+        print(f"Checking connectivity to {url}...")
+        try:
+            response = requests.get(url + "health/v1/ping", verify=False, timeout=30)
+            print(f"Received status code: {response.status_code}")
+            self.assertEqual(response.status_code, 200)
+        except requests.exceptions.ConnectionError:
+            self.fail("Failed to connect to Knox on port 8443 - Connection refused")
+        except Exception as e:
+            self.fail(f"Health check failed with unexpected error: {e}")
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/.github/workflows/tests/test_knox_auth_service_and_LDAP.py b/.github/workflows/tests/test_knox_auth_service_and_LDAP.py
new file mode 100644
index 0000000..dd1b344
--- /dev/null
+++ b/.github/workflows/tests/test_knox_auth_service_and_LDAP.py
@@ -0,0 +1,107 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you 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.
+import os
+import unittest
+import requests
+import urllib3
+from requests.auth import HTTPBasicAuth
+
+########################################################
+# This test is verifying the behavior of the Knox Auth Service + LDAP authentication.
+# It is using the 'auth/api/v1/pre' endpoint to get the actor ID and group headers.
+# It is using the 'guest' user to get the guest user headers.
+# It is using the 'admin' user to get the admin user headers.
+# It is verifying that the actor ID and group headers are correct.
+# It is verifying that the actor ID and group headers are not empty.
+# It is verifying that the actor ID and group headers are not None.
+########################################################
+
+# Suppress InsecureRequestWarning
+urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
+class TestKnoxAuthService(unittest.TestCase):
+    def setUp(self):
+        self.base_url = os.environ.get("KNOX_GATEWAY_URL", "https://localhost:8443/")
+        if not self.base_url.endswith("/"):
+            self.base_url += "/"
+        # The topology name is based on the filename knoxldap.xml
+        self.topology_url = self.base_url + "gateway/knoxldap/auth/api/v1/extauthz"
+
+    def test_auth_service_guest(self):
+        """
+        Verify that guest user gets the correct actor ID header.
+        """
+        print(f"\nTesting guest authentication against {self.topology_url}")
+        response = requests.get(
+            self.topology_url, 
+            auth=HTTPBasicAuth('guest', 'guest-password'),
+            verify=False, 
+            timeout=30
+        )
+        
+        print(f"Status Code: {response.status_code}")
+        self.assertEqual(response.status_code, 200)
+        
+        # Check for Actor ID header
+        # The config in knoxtoken.xml sets 'preauth.auth.header.actor.id.name' to 'x-knox-actor-username'
+        actor_id_header = 'x-knox-actor-username'
+        self.assertIn(actor_id_header, response.headers)
+        self.assertEqual(response.headers[actor_id_header], 'guest')
+        print(f"Verified {actor_id_header}: {response.headers[actor_id_header]}")
+
+    def test_auth_service_admin_groups(self):
+        """
+        Verify that admin user gets actor ID and group headers.
+        """
+        print(f"\nTesting admin authentication against {self.topology_url}")
+        response = requests.get(
+            self.topology_url, 
+            auth=HTTPBasicAuth('admin', 'admin-password'),
+            verify=False, 
+            timeout=30
+        )
+        
+        print(f"Status Code: {response.status_code}")
+        self.assertEqual(response.status_code, 200)
+        
+        # Check for Actor ID header
+        actor_id_header = 'x-knox-actor-username'
+        self.assertIn(actor_id_header, response.headers)
+        self.assertEqual(response.headers[actor_id_header], 'admin')
+        print(f"Verified {actor_id_header}: {response.headers[actor_id_header]}")
+        
+        # Check for Group headers
+        # Config: 'preauth.auth.header.actor.groups.prefix' = 'x-knox-actor-groups'
+        # We mapped admin to 'longGroupName1,longGroupName2,longGroupName3,longGroupName4'
+        
+        # We just verify that at least one header starting with the prefix exists
+        prefix = 'x-knox-actor-groups'
+        group_headers = [h for h in response.headers.keys() if h.lower().startswith(prefix.lower())]
+        
+        self.assertTrue(len(group_headers) > 0, f"No headers found starting with {prefix}")
+        
+        # Verify content of groups
+        all_groups = []
+        for h in group_headers:
+            all_groups.extend(response.headers[h].split(','))
+            print(f"Found group header {h}: {response.headers[h]}")
+            
+        expected_groups = ['longGroupName1', 'longGroupName2', 'longGroupName3', 'longGroupName4']
+        for group in expected_groups:
+            self.assertIn(group, all_groups)
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/.gitignore b/.gitignore
index 11e463d..89404ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,7 @@
 *.patch
 .checkstyle
 **/package-lock.json
+.github/workflows/compose/logs/*
 
 # other IDEs and editors
 .vscode
diff --git a/pom.xml b/pom.xml
index 8551319..9f3bae9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -494,6 +494,7 @@
                         <exclude>.git/**</exclude>
                         <exclude>.pc/**</exclude>
                         <exclude>debian/**</exclude>
+                        <exclude>.github/**</exclude>
                         <exclude>.gitignore/**</exclude>
                         <exclude>.svn/**</exclude>
                         <exclude>.idea/**</exclude>