Skip to content

Commit c74372e

Browse files
committed
fix(e2e): sign the https proxy fixture listener cert with a CA
The corporate-proxy E2E fixture served a single `openssl req -x509` certificate as its TLS listener identity. OpenSSL marks that certificate `basicConstraints: critical, CA:TRUE`, and rustls refuses a CA certificate presented as an end-entity certificate (CaUsedAsEndEntity). The supervisor's TLS handshake with the proxy therefore failed, the upstream dial errored, and the workload's CONNECT was dropped without a response, so podman_corporate_proxy_trusts_ca_bundle_for_https_proxy failed on the approved destination while policy denial still worked. Generate a corporate CA and a separate listener leaf signed by it, serve the leaf chain, and publish only the CA as the bundle the supervisor trusts. This is what an intercepting proxy actually presents, and it exercises the corporate-CA trust path rather than pinning the listener certificate itself. Refs #1792 Signed-off-by: Philippe Martin <phmartin@redhat.com>
1 parent 2713f54 commit c74372e

1 file changed

Lines changed: 46 additions & 19 deletions

File tree

e2e/rust/tests/podman_corporate_proxy.rs

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -137,34 +137,61 @@ while True:
137137
)
138138
}
139139

140-
// Delimits the proxy's self-signed CA certificate in its stdout, so the test
141-
// can recover it and hand it back as the corporate CA bundle.
140+
// Delimits the proxy's CA certificate in its stdout, so the test can recover
141+
// it and hand it back as the corporate CA bundle.
142142
const CA_BEGIN: &str = "---PROXY-CA-BEGIN---";
143143
const CA_END: &str = "---PROXY-CA-END---";
144144

145-
/// A forward proxy that terminates TLS with a self-signed certificate and logs
145+
/// A forward proxy that terminates TLS with a CA-signed certificate and logs
146146
/// every CONNECT it sees.
147147
///
148-
/// The certificate is generated in-container (SAN = the proxy alias, which is
149-
/// the name the supervisor uses for SNI) and printed to stdout between
150-
/// [`CA_BEGIN`]/[`CA_END`] so the test can trust it via `proxy_ca_bundle`. No
151-
/// Basic auth: this test isolates the `https://` proxy + corporate-CA path;
152-
/// credential delivery is covered by the plaintext-proxy test.
148+
/// The container generates a corporate CA and a separate listener leaf signed
149+
/// by it (SAN = the proxy alias, which is the name the supervisor uses for
150+
/// SNI), serves the leaf, and prints the CA between [`CA_BEGIN`]/[`CA_END`] so
151+
/// the test can trust it via `proxy_ca_bundle`. The listener certificate must
152+
/// be a leaf: rustls rejects a `CA:TRUE` certificate presented as an
153+
/// end-entity certificate (`CaUsedAsEndEntity`), so a single self-signed
154+
/// `openssl req -x509` certificate — which is a CA by default — cannot serve
155+
/// as both the anchor and the listener identity. Signing a leaf also matches
156+
/// what a real intercepting proxy does. No Basic auth: this test isolates the
157+
/// `https://` proxy + corporate-CA path; credential delivery is covered by the
158+
/// plaintext-proxy test.
153159
fn tls_proxy_script() -> String {
154160
format!(
155161
r"
156162
import os, select, socket, ssl, subprocess, tempfile, threading
157163
158164
workdir = tempfile.mkdtemp()
159-
key = os.path.join(workdir, 'key.pem')
160-
crt = os.path.join(workdir, 'cert.pem')
161-
subprocess.run(
162-
['openssl', 'req', '-x509', '-newkey', 'rsa:2048', '-nodes',
163-
'-keyout', key, '-out', crt, '-days', '1', '-subj', '/CN={PROXY_ALIAS}',
164-
'-addext', 'subjectAltName=DNS:{PROXY_ALIAS}'],
165-
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
166-
167-
with open(crt) as fh:
165+
ca_key = os.path.join(workdir, 'ca-key.pem')
166+
ca_crt = os.path.join(workdir, 'ca.pem')
167+
leaf_key = os.path.join(workdir, 'leaf-key.pem')
168+
leaf_csr = os.path.join(workdir, 'leaf.csr')
169+
leaf_crt = os.path.join(workdir, 'leaf.pem')
170+
chain = os.path.join(workdir, 'chain.pem')
171+
ext = os.path.join(workdir, 'leaf.ext')
172+
173+
def run(*args):
174+
subprocess.run(args, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
175+
176+
run('openssl', 'req', '-x509', '-newkey', 'rsa:2048', '-nodes',
177+
'-keyout', ca_key, '-out', ca_crt, '-days', '1',
178+
'-subj', '/CN=corp-proxy-e2e-ca')
179+
180+
with open(ext, 'w') as fh:
181+
fh.write('basicConstraints=critical,CA:FALSE\n'
182+
'subjectAltName=DNS:{PROXY_ALIAS}\n'
183+
'extendedKeyUsage=serverAuth\n')
184+
run('openssl', 'req', '-newkey', 'rsa:2048', '-nodes',
185+
'-keyout', leaf_key, '-out', leaf_csr, '-subj', '/CN={PROXY_ALIAS}')
186+
run('openssl', 'x509', '-req', '-in', leaf_csr, '-CA', ca_crt, '-CAkey', ca_key,
187+
'-CAcreateserial', '-out', leaf_crt, '-days', '1', '-extfile', ext)
188+
189+
with open(chain, 'w') as out:
190+
for part in (leaf_crt, ca_crt):
191+
with open(part) as fh:
192+
out.write(fh.read())
193+
194+
with open(ca_crt) as fh:
168195
print('{CA_BEGIN}\n' + fh.read() + '{CA_END}', flush=True)
169196
170197
def log(msg):
@@ -223,7 +250,7 @@ def handle(conn):
223250
conn.close()
224251
225252
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
226-
ctx.load_cert_chain(crt, key)
253+
ctx.load_cert_chain(chain, leaf_key)
227254
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
228255
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
229256
server.bind(('0.0.0.0', {PROXY_PORT}))
@@ -243,7 +270,7 @@ while True:
243270
)
244271
}
245272

246-
/// Extract the proxy's self-signed CA certificate PEM from its stdout.
273+
/// Extract the proxy's CA certificate PEM from its stdout.
247274
fn ca_cert_from_logs(logs: &str) -> Result<String, String> {
248275
let start = logs
249276
.find(CA_BEGIN)

0 commit comments

Comments
 (0)