Use LDAP auth during integration tests
Gerrit instances started during tests were not secured by authentication.
This was insecure, especially if tests were run in a public accessible
cluster.
Now LDAP-based authentication is used. A minimal LDAP server deplyoment
is provided as a supplement. The initial admin account in Gerrit is
created using selenium to log in via the Chrome browser once.
Change-Id: I07ea1592240cec7d1059016ca4d8597a78bffb32
diff --git a/LICENSE b/LICENSE
index 4149bfa..5e173a3 100644
--- a/LICENSE
+++ b/LICENSE
@@ -240,11 +240,21 @@
Copyright (c) 2004-2017 Holger Krekel and others \
MIT License (https://github.com/pytest-dev/pytest/blob/master/LICENSE)
+python-chromedriver-autoinstaller \
+https://github.com/yeongbin-jo/python-chromedriver-autoinstaller \
+Copyright (c) 2022 Yeongbin Jo \
+MIT License (https://github.com/yeongbin-jo/python-chromedriver-autoinstaller/blob/master/LICENSE)
+
Requests \
https://github.com/requests/requests \
Copyright 2018 Kenneth Reitz \
Apache 2 license (https://github.com/requests/requests/blob/master/LICENSE)
+Selenium \
+https://github.com/SeleniumHQ/selenium \
+Copyright 2022 Software Freedom Conservancy (SFC) \
+Apache 2 license (https://github.com/SeleniumHQ/selenium/blob/trunk/LICENSE)
+
---
## The MIT License (MIT)
diff --git a/Pipfile b/Pipfile
index 56db413..10150a8 100644
--- a/Pipfile
+++ b/Pipfile
@@ -16,6 +16,8 @@
pytest-timeout = "~=2.1.0"
kubernetes = "~=21.7.0"
pygit2 = "~=1.9.1"
+selenium = "~=4.2.0"
+chromedriver-autoinstaller = "==0.3.1"
[requires]
python_version = "3.9"
diff --git a/Pipfile.lock b/Pipfile.lock
index 9b67cb5..ba28e5f 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "73e9de2399926646cf8380820f2c22edf4cf093f8d005dacd49f6ca045ac2072"
+ "sha256": "a0f6041cdd2972a528a6d9b74675991e47130e94c14808b70a50a4e663a5ff99"
},
"pipfile-spec": 6,
"requires": {
@@ -16,6 +16,14 @@
]
},
"default": {
+ "async-generator": {
+ "hashes": [
+ "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b",
+ "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==1.10"
+ },
"attrs": {
"hashes": [
"sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4",
@@ -117,6 +125,14 @@
"markers": "python_version >= '3'",
"version": "==2.0.12"
},
+ "chromedriver-autoinstaller": {
+ "hashes": [
+ "sha256:7bafc2c1730fc044d078a9e2e31c59c8015077c7e4de3903f7f14693af03bfbe",
+ "sha256:d2d934fed5a8c27352e279c8c398ecbc570f5aebb4a877add05dc4703ce91bc8"
+ ],
+ "index": "pypi",
+ "version": "==0.3.1"
+ },
"cryptography": {
"hashes": [
"sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804",
@@ -161,6 +177,14 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==2.9.0"
},
+ "h11": {
+ "hashes": [
+ "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06",
+ "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==0.13.0"
+ },
"idna": {
"hashes": [
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
@@ -192,6 +216,14 @@
"markers": "python_version >= '3.6'",
"version": "==3.2.0"
},
+ "outcome": {
+ "hashes": [
+ "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672",
+ "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==1.2.0"
+ },
"packaging": {
"hashes": [
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
@@ -322,6 +354,14 @@
"markers": "python_full_version >= '3.6.8'",
"version": "==3.0.9"
},
+ "pysocks": {
+ "hashes": [
+ "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299",
+ "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5",
+ "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"
+ ],
+ "version": "==1.7.1"
+ },
"pytest": {
"hashes": [
"sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
@@ -409,6 +449,13 @@
"markers": "python_version >= '3.6'",
"version": "==4.8"
},
+ "selenium": {
+ "hashes": [
+ "sha256:ba5b2633f43cf6fe9d308fa4a6996e00a101ab9cb1aad6fd91ae1f3dbe57f56f"
+ ],
+ "index": "pypi",
+ "version": "==4.2.0"
+ },
"setuptools": {
"hashes": [
"sha256:16923d366ced322712c71ccb97164d07472abeecd13f3a6c283f6d5d26722793",
@@ -425,6 +472,21 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.16.0"
},
+ "sniffio": {
+ "hashes": [
+ "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
+ "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==1.2.0"
+ },
+ "sortedcontainers": {
+ "hashes": [
+ "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88",
+ "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"
+ ],
+ "version": "==2.4.0"
+ },
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
@@ -433,6 +495,22 @@
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
"version": "==0.10.2"
},
+ "trio": {
+ "hashes": [
+ "sha256:4dc0bf9d5cc78767fc4516325b6d80cc0968705a31d0eec2ecd7cdda466265b0",
+ "sha256:523f39b7b69eef73501cebfe1aafd400a9aad5b03543a0eded52952488ff1c13"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==0.21.0"
+ },
+ "trio-websocket": {
+ "hashes": [
+ "sha256:5b558f6e83cc20a37c3b61202476c5295d1addf57bd65543364e0337e37ed2bc",
+ "sha256:a3d34de8fac26023eee701ed1e7bf4da9a8326b61a62934ec9e53b64970fd8fe"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==0.9.2"
+ },
"urllib3": {
"hashes": [
"sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14",
@@ -448,6 +526,14 @@
],
"markers": "python_version >= '3.7'",
"version": "==1.3.3"
+ },
+ "wsproto": {
+ "hashes": [
+ "sha256:2218cb57952d90b9fca325c0dcfb08c3bda93e8fd8070b0a17f048e2e47a521b",
+ "sha256:a2e56bfd5c7cd83c1369d83b5feccd6d37798b74872866e62616e0ecf111bda8"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==1.1.0"
}
},
"develop": {
diff --git a/supplements/test-cluster/ldap/openldap.deployment.yaml b/supplements/test-cluster/ldap/openldap.deployment.yaml
new file mode 100644
index 0000000..f311479
--- /dev/null
+++ b/supplements/test-cluster/ldap/openldap.deployment.yaml
@@ -0,0 +1,42 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: openldap
+ namespace: openldap
+ labels:
+ app.kubernetes.io/name: openldap
+spec:
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: openldap
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: openldap
+ spec:
+ containers:
+ - name: openldap
+ image: docker.io/bitnami/openldap:latest
+ imagePullPolicy: "IfNotPresent"
+ env:
+ - name: LDAP_ADMIN_USERNAME
+ value: "admin"
+ - name: LDAP_ADMIN_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ key: adminpassword
+ name: openldap-admin
+ - name: LDAP_USERS
+ valueFrom:
+ secretKeyRef:
+ key: users
+ name: openldap-users
+ - name: LDAP_PASSWORDS
+ valueFrom:
+ secretKeyRef:
+ key: passwords
+ name: openldap-users
+ ports:
+ - name: tcp-ldap
+ containerPort: 1389
diff --git a/supplements/test-cluster/ldap/openldap.namespace.yaml b/supplements/test-cluster/ldap/openldap.namespace.yaml
new file mode 100644
index 0000000..cc9e999
--- /dev/null
+++ b/supplements/test-cluster/ldap/openldap.namespace.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: openldap
diff --git a/supplements/test-cluster/ldap/openldap.secret.yaml b/supplements/test-cluster/ldap/openldap.secret.yaml
new file mode 100644
index 0000000..8ceb912
--- /dev/null
+++ b/supplements/test-cluster/ldap/openldap.secret.yaml
@@ -0,0 +1,20 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: openldap-admin
+ namespace: openldap
+ labels:
+ app: gerrit
+data:
+ adminpassword: #TODO
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: openldap-users
+ namespace: openldap
+ labels:
+ app: gerrit
+data:
+ users: gerrit-admin,gerrit-user
+ passwords: #TODO
diff --git a/supplements/test-cluster/ldap/openldap.service.yaml b/supplements/test-cluster/ldap/openldap.service.yaml
new file mode 100644
index 0000000..1a8f274
--- /dev/null
+++ b/supplements/test-cluster/ldap/openldap.service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: openldap
+ namespace: openldap
+ labels:
+ app.kubernetes.io/name: openldap
+spec:
+ type: ClusterIP
+ ports:
+ - name: tcp-ldap
+ port: 1389
+ targetPort: tcp-ldap
+ selector:
+ app.kubernetes.io/name: openldap
diff --git a/tests/fixtures/cluster.py b/tests/fixtures/cluster.py
index 8f936d6..eb94968 100644
--- a/tests/fixtures/cluster.py
+++ b/tests/fixtures/cluster.py
@@ -116,3 +116,29 @@
yield test_cluster
test_cluster.cleanup()
+
+
+@pytest.fixture(scope="session")
+def ldap_credentials(test_cluster):
+ ldap_secret = client.CoreV1Api().read_namespaced_secret(
+ "openldap-users", namespace="openldap"
+ )
+ users = base64.b64decode(ldap_secret.data["users"]).decode("utf-8").split(",")
+ passwords = (
+ base64.b64decode(ldap_secret.data["passwords"]).decode("utf-8").split(",")
+ )
+ credentials = {}
+ for i, user in enumerate(users):
+ credentials[user] = passwords[i]
+
+ yield credentials
+
+
+@pytest.fixture(scope="session")
+def ldap_admin_credentials(test_cluster):
+ ldap_secret = client.CoreV1Api().read_namespaced_secret(
+ "openldap-admin", namespace="openldap"
+ )
+ password = base64.b64decode(ldap_secret.data["adminpassword"]).decode("utf-8")
+
+ yield ("admin", password)
diff --git a/tests/fixtures/helm/gerrit.py b/tests/fixtures/helm/gerrit.py
index edf7e0a..908081d 100644
--- a/tests/fixtures/helm/gerrit.py
+++ b/tests/fixtures/helm/gerrit.py
@@ -21,7 +21,10 @@
import yaml
import pygit2 as git
+import chromedriver_autoinstaller
from kubernetes import client
+from selenium import webdriver
+from selenium.webdriver.common.by import By
from .abstract_deployment import AbstractDeployment
@@ -48,7 +51,9 @@
GERRIT_STARTUP_TIMEOUT = 240
DEFAULT_GERRIT_CONFIG = {
- "auth": {"type": "DEVELOPMENT_BECOME_ANY_ACCOUNT"},
+ "auth": {
+ "type": "LDAP",
+ },
"container": {
"user": "gerrit",
"javaHome": "/usr/lib/jvm/java-11-openjdk",
@@ -69,6 +74,11 @@
"gracefulStopTimeout": "1m",
},
"index": {"type": "LUCENE", "onlineUpgrade": False},
+ "ldap": {
+ "server": "ldap://openldap.openldap.svc.cluster.local:1389",
+ "accountbase": "dc=example,dc=org",
+ "username": "cn=admin,dc=example,dc=org",
+ },
"sshd": {"listenAddress": "off"},
}
@@ -81,6 +91,7 @@
}
+# pylint: disable=R0902
class GerritDeployment(AbstractDeployment):
def __init__(
self,
@@ -91,10 +102,13 @@
container_org,
container_version,
ingress_url,
+ ldap_admin_credentials,
+ ldap_credentials,
):
super().__init__(tmp_dir)
self.cluster = cluster
self.storageclass = storageclass
+ self.ldap_credentials = ldap_credentials
self.chart_name = "gerrit-" + self.namespace
self.chart_path = os.path.join(
@@ -112,6 +126,14 @@
)
self.hostname = f"{self.namespace}.{ingress_url}"
self._configure_ingress()
+ self.set_gerrit_config_value(
+ "gerrit", "canonicalWebUrl", f"http://{self.hostname}"
+ )
+ # pylint: disable=W1401
+ self.set_helm_value(
+ "gerrit.etc.secret.secure\.config",
+ dict_to_git_config({"ldap": {"password": ldap_admin_credentials[1]}}),
+ )
def install(self, wait=True):
if self.cluster.helm.is_installed(self.namespace, self.chart_name):
@@ -133,6 +155,33 @@
wait=wait,
)
+ def create_admin_account(self):
+ self.wait_until_ready()
+ chromedriver_autoinstaller.install()
+ options = webdriver.ChromeOptions()
+ options.add_argument("--headless")
+ options.add_argument("--no-sandbox")
+ options.add_argument("--ignore-certificate-errors")
+ capabilities = webdriver.DesiredCapabilities.CHROME.copy()
+ capabilities["acceptInsecureCerts"] = True
+ driver = webdriver.Chrome(
+ chrome_options=options,
+ desired_capabilities=capabilities,
+ )
+ print(driver.capabilities)
+ driver.get(f"http://{self.hostname}/login")
+ print(self.ldap_credentials)
+ user_input = driver.find_element(By.ID, "f_user")
+ user_input.send_keys("gerrit-admin")
+
+ pwd_input = driver.find_element(By.ID, "f_pass")
+ pwd_input.send_keys(self.ldap_credentials["gerrit-admin"])
+
+ submit_btn = driver.find_element(By.ID, "b_signin")
+ submit_btn.click()
+
+ driver.close()
+
def update(self):
with open(self.values_file, "w", encoding="UTF-8") as f:
yaml.dump(self.chart_opts, f)
@@ -206,7 +255,9 @@
@pytest.fixture(scope="class")
-def gerrit_deployment(request, tmp_path_factory, test_cluster):
+def gerrit_deployment(
+ request, tmp_path_factory, test_cluster, ldap_admin_credentials, ldap_credentials
+):
deployment = GerritDeployment(
tmp_path_factory.mktemp("gerrit_deployment"),
test_cluster,
@@ -215,6 +266,8 @@
request.config.getoption("--org"),
request.config.getoption("--tag"),
request.config.getoption("--ingress-url"),
+ ldap_admin_credentials,
+ ldap_credentials,
)
yield deployment
@@ -225,5 +278,6 @@
@pytest.fixture(scope="class")
def default_gerrit_deployment(gerrit_deployment):
gerrit_deployment.install()
+ gerrit_deployment.create_admin_account()
yield gerrit_deployment
diff --git a/tests/helm-charts/gerrit/test_chart_gerrit_plugins.py b/tests/helm-charts/gerrit/test_chart_gerrit_plugins.py
index f9fd067..9e8c58c 100644
--- a/tests/helm-charts/gerrit/test_chart_gerrit_plugins.py
+++ b/tests/helm-charts/gerrit/test_chart_gerrit_plugins.py
@@ -51,6 +51,7 @@
def gerrit_deployment_with_packaged_plugins(request, gerrit_deployment):
gerrit_deployment.set_helm_value("gerrit.plugins.packaged", request.param)
gerrit_deployment.install()
+ gerrit_deployment.create_admin_account()
yield gerrit_deployment, request.param
@@ -69,6 +70,7 @@
gerrit_deployment.set_helm_value("gerrit.plugins.packaged", ["healthcheck"])
gerrit_deployment.install()
+ gerrit_deployment.create_admin_account()
yield gerrit_deployment, selected_plugins
@@ -106,14 +108,16 @@
@pytest.mark.timeout(300)
def test_install_packaged_plugins(
- self, request, gerrit_deployment_with_packaged_plugins
+ self, request, gerrit_deployment_with_packaged_plugins, ldap_credentials
):
gerrit_deployment, expected_plugins = gerrit_deployment_with_packaged_plugins
response = None
while not response:
try:
response = get_gerrit_plugin_list(
- f"http://{gerrit_deployment.hostname}"
+ f"http://{gerrit_deployment.hostname}",
+ "gerrit-admin",
+ ldap_credentials["gerrit-admin"],
)
except requests.exceptions.ConnectionError:
time.sleep(1)
@@ -122,7 +126,11 @@
@pytest.mark.timeout(300)
def test_install_packaged_plugins_are_removed_with_update(
- self, request, test_cluster, gerrit_deployment_with_packaged_plugins
+ self,
+ request,
+ test_cluster,
+ gerrit_deployment_with_packaged_plugins,
+ ldap_credentials,
):
gerrit_deployment, expected_plugins = gerrit_deployment_with_packaged_plugins
removed_plugin = expected_plugins.pop()
@@ -134,7 +142,9 @@
while True:
try:
response = get_gerrit_plugin_list(
- f"http://{gerrit_deployment.hostname}"
+ f"http://{gerrit_deployment.hostname}",
+ "gerrit-admin",
+ ldap_credentials["gerrit-admin"],
)
if response is not None and removed_plugin not in response:
break
@@ -158,13 +168,17 @@
)
@pytest.mark.timeout(300)
- def test_install_other_plugins(self, request, gerrit_deployment_with_other_plugins):
+ def test_install_other_plugins(
+ self, gerrit_deployment_with_other_plugins, ldap_credentials
+ ):
gerrit_deployment, expected_plugins = gerrit_deployment_with_other_plugins
response = None
while not response:
try:
response = get_gerrit_plugin_list(
- f"http://{gerrit_deployment.hostname}"
+ f"http://{gerrit_deployment.hostname}",
+ "gerrit-admin",
+ ldap_credentials["gerrit-admin"],
)
except requests.exceptions.ConnectionError:
continue
@@ -172,7 +186,7 @@
@pytest.mark.timeout(300)
def test_install_other_plugins_are_removed_with_update(
- self, request, gerrit_deployment_with_other_plugins
+ self, gerrit_deployment_with_other_plugins, ldap_credentials
):
gerrit_deployment, installed_plugins = gerrit_deployment_with_other_plugins
removed_plugin = installed_plugins.pop()
@@ -183,7 +197,9 @@
while True:
try:
response = get_gerrit_plugin_list(
- f"http://{gerrit_deployment.hostname}"
+ f"http://{gerrit_deployment.hostname}",
+ "gerrit-admin",
+ ldap_credentials["gerrit-admin"],
)
if response is not None and removed_plugin["name"] not in response:
break
diff --git a/tests/helm-charts/gerrit/test_chart_gerrit_ssl.py b/tests/helm-charts/gerrit/test_chart_gerrit_ssl.py
index 539c88f..0eee0f4 100644
--- a/tests/helm-charts/gerrit/test_chart_gerrit_ssl.py
+++ b/tests/helm-charts/gerrit/test_chart_gerrit_ssl.py
@@ -56,6 +56,7 @@
)
gerrit_deployment.install()
+ gerrit_deployment.create_admin_account()
yield gerrit_deployment
@@ -66,22 +67,20 @@
@pytest.mark.slow
class TestgerritChartSetup:
# pylint: disable=W0613
- def test_create_project_rest(self, request, cert_dir, gerrit_deployment_with_ssl):
+ def test_create_project_rest(
+ self, cert_dir, gerrit_deployment_with_ssl, ldap_credentials
+ ):
create_project_url = (
f"https://{gerrit_deployment_with_ssl.hostname}/a/projects/test"
)
response = requests.put(
create_project_url,
- auth=("admin", "secret"),
+ auth=("gerrit-admin", ldap_credentials["gerrit-admin"]),
verify=os.path.join(cert_dir, "server.crt"),
)
assert response.status_code == 201
- def test_cloning_project(
- self,
- tmp_path_factory,
- gerrit_deployment_with_ssl,
- ):
+ def test_cloning_project(self, tmp_path_factory, gerrit_deployment_with_ssl):
clone_dest = tmp_path_factory.mktemp("gerrit_chart_clone_test")
repo_url = f"https://{gerrit_deployment_with_ssl.hostname}/test.git"
repo = git.clone_repository(
diff --git a/tests/helm-charts/gerrit/test_chart_gerrit_usage.py b/tests/helm-charts/gerrit/test_chart_gerrit_usage.py
index fae691d..f63d209 100644
--- a/tests/helm-charts/gerrit/test_chart_gerrit_usage.py
+++ b/tests/helm-charts/gerrit/test_chart_gerrit_usage.py
@@ -27,7 +27,7 @@
@pytest.mark.kubernetes
class TestGerritChartSetup:
@pytest.mark.timeout(240)
- def test_create_project_rest(self, default_gerrit_deployment):
+ def test_create_project_rest(self, default_gerrit_deployment, ldap_credentials):
create_project_url = (
f"http://{default_gerrit_deployment.hostname}/a/projects/test"
)
@@ -35,7 +35,10 @@
while not response:
try:
- response = requests.put(create_project_url, auth=("admin", "secret"))
+ response = requests.put(
+ create_project_url,
+ auth=("gerrit-admin", ldap_credentials["gerrit-admin"]),
+ )
except requests.exceptions.ConnectionError:
break