diff options
author | Stefan Eissing <icing@apache.org> | 2021-11-10 16:54:27 +0100 |
---|---|---|
committer | Stefan Eissing <icing@apache.org> | 2021-11-10 16:54:27 +0100 |
commit | 7840e7d212ce12442c48ce6e7e56414d25e17132 (patch) | |
tree | e448f08be385096fd441ce4df816b85adae8da14 /test/pyhttpd | |
parent | * test HTTP/2: also run core tests and worker mpm (diff) | |
download | apache2-7840e7d212ce12442c48ce6e7e56414d25e17132.tar.xz apache2-7840e7d212ce12442c48ce6e7e56414d25e17132.zip |
* testsuite: possible now to issue client certificates and the chain file for them
* testsuite: handling of cert+key in same file improved
* testsuite: using 'stop' configuration to terminate server in case test cases
leave borked test configs lying around.
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1894919 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'test/pyhttpd')
-rw-r--r-- | test/pyhttpd/certs.py | 50 | ||||
-rw-r--r-- | test/pyhttpd/conf.py | 10 | ||||
-rw-r--r-- | test/pyhttpd/conf/stop.conf.template | 46 | ||||
-rw-r--r-- | test/pyhttpd/env.py | 6 |
4 files changed, 97 insertions, 15 deletions
diff --git a/test/pyhttpd/certs.py b/test/pyhttpd/certs.py index 2ce93c07b2..5519f16188 100644 --- a/test/pyhttpd/certs.py +++ b/test/pyhttpd/certs.py @@ -70,13 +70,24 @@ class CertificateSpec: return self.domains[0] return None + @property + def type(self) -> Optional[str]: + if self.domains and len(self.domains): + return "server" + elif self.client: + return "client" + elif self.name: + return "ca" + return None + class Credentials: - def __init__(self, name: str, cert: Any, pkey: Any): + def __init__(self, name: str, cert: Any, pkey: Any, issuer: 'Credentials' = None): self._name = name self._cert = cert self._pkey = pkey + self._issuer = issuer self._cert_file = None self._pkey_file = None self._store = None @@ -117,6 +128,10 @@ class Credentials: PrivateFormat.TraditionalOpenSSL if self.key_type.startswith('rsa') else PrivateFormat.PKCS8, NoEncryption()) + @property + def issuer(self) -> Optional['Credentials']: + return self._issuer + def set_store(self, store: 'CertStore'): self._store = store @@ -145,13 +160,17 @@ class Credentials: def issue_cert(self, spec: CertificateSpec, chain: List['Credentials'] = None) -> 'Credentials': key_type = spec.key_type if spec.key_type else self.key_type - creds = self._store.load_credentials(name=spec.name, key_type=key_type, single_file=spec.single_file) \ - if self._store else None + creds = None + if self._store: + creds = self._store.load_credentials( + name=spec.name, key_type=key_type, single_file=spec.single_file, issuer=self) if creds is None: creds = HttpdTestCA.create_credentials(spec=spec, issuer=self, key_type=key_type, valid_from=spec.valid_from, valid_to=spec.valid_to) if self._store: self._store.save(creds, single_file=spec.single_file) + if spec.type == "ca": + self._store.save_chain(creds, "ca", with_root=True) if spec.sub_specs: if self._store: @@ -196,6 +215,19 @@ class CertStore: creds.set_files(cert_file, pkey_file) self._add_credentials(name, creds) + def save_chain(self, creds: Credentials, infix: str, with_root=False): + name = creds.name + chain = [creds] + while creds.issuer is not None: + creds = creds.issuer + chain.append(creds) + if not with_root and len(chain) > 1: + chain = chain[:-1] + chain_file = os.path.join(self._store_dir, f'{name}-{infix}.pem') + with open(chain_file, "wb") as fd: + for c in chain: + fd.write(c.cert_pem) + def _add_credentials(self, name: str, creds: Credentials): if name not in self._creds_by_name: self._creds_by_name[name] = [] @@ -220,13 +252,13 @@ class CertStore: with open(fpath) as fd: return load_pem_private_key("".join(fd.readlines()).encode(), password=None) - def load_credentials(self, name: str, key_type=None, single_file: bool = False): + def load_credentials(self, name: str, key_type=None, single_file: bool = False, issuer: Credentials = None): cert_file = self.get_cert_file(name=name, key_type=key_type) pkey_file = cert_file if single_file else self.get_pkey_file(name=name, key_type=key_type) if os.path.isfile(cert_file) and os.path.isfile(pkey_file): cert = self.load_pem_cert(cert_file) pkey = self.load_pem_pkey(pkey_file) - creds = Credentials(name=name, cert=cert, pkey=pkey) + creds = Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) creds.set_store(self) creds.set_files(cert_file, pkey_file) self._add_credentials(name, creds) @@ -239,7 +271,7 @@ class HttpdTestCA: @classmethod def create_root(cls, name: str, store_dir: str, key_type: str = "rsa2048") -> Credentials: store = CertStore(fpath=store_dir) - creds = store.load_credentials(name="ca", key_type=key_type) + creds = store.load_credentials(name="ca", key_type=key_type, issuer=None) if creds is None: creds = HttpdTestCA._make_ca_credentials(name=name, key_type=key_type) store.save(creds, name="ca") @@ -405,7 +437,7 @@ class HttpdTestCA: cert = csr.sign(private_key=issuer_key, algorithm=hashes.SHA256(), backend=default_backend()) - return Credentials(name=name, cert=cert, pkey=pkey) + return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) @staticmethod def _make_server_credentials(name: str, domains: List[str], issuer: Credentials, @@ -423,7 +455,7 @@ class HttpdTestCA: cert = csr.sign(private_key=issuer.private_key, algorithm=hashes.SHA256(), backend=default_backend()) - return Credentials(name=name, cert=cert, pkey=pkey) + return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) @staticmethod def _make_client_credentials(name: str, @@ -441,4 +473,4 @@ class HttpdTestCA: cert = csr.sign(private_key=issuer.private_key, algorithm=hashes.SHA256(), backend=default_backend()) - return Credentials(name=name, cert=cert, pkey=pkey) + return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) diff --git a/test/pyhttpd/conf.py b/test/pyhttpd/conf.py index 383b82d590..3fefffaa8c 100644 --- a/test/pyhttpd/conf.py +++ b/test/pyhttpd/conf.py @@ -40,23 +40,25 @@ class HttpdConf(object): if self.env.ssl_module == "ssl": self.add([ f"SSLCertificateFile {cert_file}", - f"SSLCertificateKeyFile {key_file}", + f"SSLCertificateKeyFile {key_file if key_file else cert_file}", ]) elif self.env.ssl_module == "tls": self.add(f""" TLSCertificate {cert_file} {key_file} """) - def add_vhost(self, domains, port=None, doc_root="htdocs", with_ssl=True): + def add_vhost(self, domains, port=None, doc_root="htdocs", with_ssl=None): self.start_vhost(domains=domains, port=port, doc_root=doc_root, with_ssl=with_ssl) self.end_vhost() return self - def start_vhost(self, domains, port=None, doc_root="htdocs", with_ssl=False): + def start_vhost(self, domains, port=None, doc_root="htdocs", with_ssl=None): if not isinstance(domains, list): domains = [domains] if port is None: port = self.env.https_port + if with_ssl is None: + with_ssl = (self.env.https_port == port) self.add("") self.add(f"<VirtualHost *:{port}>") self._indents += 1 @@ -64,7 +66,7 @@ class HttpdConf(object): for alias in domains[1:]: self.add(f"ServerAlias {alias}") self.add(f"DocumentRoot {doc_root}") - if self.env.https_port == port or with_ssl: + if with_ssl: if self.env.ssl_module == "ssl": self.add("SSLEngine on") for cred in self.env.get_credentials_for_name(domains[0]): diff --git a/test/pyhttpd/conf/stop.conf.template b/test/pyhttpd/conf/stop.conf.template new file mode 100644 index 0000000000..21bae845f8 --- /dev/null +++ b/test/pyhttpd/conf/stop.conf.template @@ -0,0 +1,46 @@ +# a config safe to use for stopping the server +# this allows us to stop the server even when+ +# the config in the file is borked (as test cases may try to do that) +# +ServerName localhost +ServerRoot "${server_dir}" + +Include "conf/modules.conf" + +DocumentRoot "${server_dir}/htdocs" + +<IfModule log_config_module> + LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %k" combined + LogFormat "%h %l %u %t \"%r\" %>s %b" common + CustomLog "logs/access_log" combined + +</IfModule> + +TypesConfig "${gen_dir}/apache/conf/mime.types" + +Listen ${http_port} +Listen ${https_port} + +<IfModule mod_ssl.c> + # provide some default + SSLSessionCache "shmcb:ssl_gcache_data(32000)" +</IfModule> + +<VirtualHost *:${http_port}> + ServerName ${http_tld} + ServerAlias www.${http_tld} + <IfModule ssl_module> + SSLEngine off + </IfModule> + DocumentRoot "${server_dir}/htdocs" +</VirtualHost> + +<Directory "${server_dir}/htdocs/cgi"> + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + AddHandler cgi-script .py + AddHandler cgi-script .cgi + Options +ExecCGI +</Directory> diff --git a/test/pyhttpd/env.py b/test/pyhttpd/env.py index eaee5c497b..73044ae40b 100644 --- a/test/pyhttpd/env.py +++ b/test/pyhttpd/env.py @@ -362,7 +362,7 @@ class HttpdTestEnv: self._cert_specs.extend(specs) def get_credentials_for_name(self, dns_name) -> List['Credentials']: - for spec in self._cert_specs: + for spec in [s for s in self._cert_specs if s.domains is not None]: if dns_name in spec.domains: return self.ca.get_credentials_for_name(spec.domains[0]) return [] @@ -420,6 +420,7 @@ class HttpdTestEnv: def install_test_conf(self, lines: List[str]): with open(self._test_conf, 'w') as fd: fd.write('\n'.join(self._httpd_base_conf)) + fd.write('\n') if self._verbosity >= 2: fd.write(f"LogLevel core:trace5 {self.mpm_module}:trace5\n") if self._log_interesting: @@ -479,9 +480,10 @@ class HttpdTestEnv: return False def _run_apachectl(self, cmd) -> ExecResult: + conf_file = 'stop.conf' if cmd == 'stop' else 'httpd.conf' args = [self._apachectl, "-d", self.server_dir, - "-f", os.path.join(self._server_dir, 'conf/httpd.conf'), + "-f", os.path.join(self._server_dir, f'conf/{conf_file}'), "-k", cmd] r = self.run(args) self._apachectl_stderr = r.stderr |