Skip to content

Commit 650be6b

Browse files
committed
Generate mock certs as a dedicated step
1 parent e35cdb1 commit 650be6b

2 files changed

Lines changed: 41 additions & 67 deletions

File tree

.github/workflows/sf_cli_integration.yml

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,23 @@ jobs:
1010
env:
1111
SF_AUTOUPDATE_DISABLE: true
1212
NO_COLOR: '1'
13-
# Path the mock server writes its self-signed TLS cert to. The CA-trust
14-
# env vars (NODE_EXTRA_CA_CERTS / REQUESTS_CA_BUNDLE) are set per-step on
15-
# only the run/deploy steps — setting them job-wide would point pip/poetry
16-
# at a cert file that does not exist yet during setup, breaking installs.
13+
# Mock server's TLS cert/key pair.
1714
MOCK_SF_CERT_FILE: ${{ github.workspace }}/mock_sf_cert.pem
15+
MOCK_SF_KEY_FILE: ${{ github.workspace }}/mock_sf_key.pem
1816

1917
steps:
2018
# ── Setup ─────────────────────────────────────────────────────────────────
2119

2220
- name: Checkout code
2321
uses: actions/checkout@v4
2422

23+
- name: Generate mock server TLS cert
24+
run: |
25+
openssl req -x509 -newkey rsa:2048 -nodes \
26+
-keyout "$MOCK_SF_KEY_FILE" -out "$MOCK_SF_CERT_FILE" \
27+
-days 1 -subj "/CN=localhost" \
28+
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
29+
2530
- name: Set up Python 3.11
2631
uses: actions/setup-python@v5
2732
with:
@@ -61,14 +66,7 @@ jobs:
6166
# ── Mock Salesforce server + fake org auth ────────────────────────────────
6267

6368
- name: Start mock Salesforce server
64-
run: |
65-
python scripts/mock_sf_server.py &
66-
# Wait for the TLS cert the server writes at startup so clients can trust it.
67-
for _ in $(seq 1 30); do
68-
[ -f "$MOCK_SF_CERT_FILE" ] && break
69-
sleep 0.2
70-
done
71-
test -f "$MOCK_SF_CERT_FILE" || { echo "::error::mock server never wrote $MOCK_SF_CERT_FILE"; exit 1; }
69+
run: python scripts/mock_sf_server.py &
7270
env:
7371
MOCK_SF_PORT: '8888'
7472

@@ -177,8 +175,8 @@ jobs:
177175

178176
- name: '[script] run — sf data-code-extension script run --entrypoint testScript/payload/entrypoint.py -o dev1'
179177
env:
180-
NODE_EXTRA_CA_CERTS: ${{ github.workspace }}/mock_sf_cert.pem
181-
REQUESTS_CA_BUNDLE: ${{ github.workspace }}/mock_sf_cert.pem
178+
NODE_EXTRA_CA_CERTS: ${{ env.MOCK_SF_CERT_FILE }}
179+
REQUESTS_CA_BUNDLE: ${{ env.MOCK_SF_CERT_FILE }}
182180
run: |
183181
sf data-code-extension script run \
184182
--entrypoint testScript/payload/entrypoint.py \
@@ -191,8 +189,8 @@ jobs:
191189

192190
- name: '[script] deploy — sf data-code-extension script deploy'
193191
env:
194-
NODE_EXTRA_CA_CERTS: ${{ github.workspace }}/mock_sf_cert.pem
195-
REQUESTS_CA_BUNDLE: ${{ github.workspace }}/mock_sf_cert.pem
192+
NODE_EXTRA_CA_CERTS: ${{ env.MOCK_SF_CERT_FILE }}
193+
REQUESTS_CA_BUNDLE: ${{ env.MOCK_SF_CERT_FILE }}
196194
run: |
197195
sf data-code-extension script deploy \
198196
--name test-script-deploy \
@@ -281,8 +279,8 @@ jobs:
281279

282280
- name: '[function] run — sf data-code-extension function run --entrypoint testFunction/payload/entrypoint.py --test-with testFunction/payload/tests/test.json -o dev1'
283281
env:
284-
NODE_EXTRA_CA_CERTS: ${{ github.workspace }}/mock_sf_cert.pem
285-
REQUESTS_CA_BUNDLE: ${{ github.workspace }}/mock_sf_cert.pem
282+
NODE_EXTRA_CA_CERTS: ${{ env.MOCK_SF_CERT_FILE }}
283+
REQUESTS_CA_BUNDLE: ${{ env.MOCK_SF_CERT_FILE }}
286284
run: |
287285
sf data-code-extension function run \
288286
--entrypoint testFunction/payload/entrypoint.py \
@@ -295,8 +293,8 @@ jobs:
295293

296294
- name: '[function] deploy — sf data-code-extension function deploy'
297295
env:
298-
NODE_EXTRA_CA_CERTS: ${{ github.workspace }}/mock_sf_cert.pem
299-
REQUESTS_CA_BUNDLE: ${{ github.workspace }}/mock_sf_cert.pem
296+
NODE_EXTRA_CA_CERTS: ${{ env.MOCK_SF_CERT_FILE }}
297+
REQUESTS_CA_BUNDLE: ${{ env.MOCK_SF_CERT_FILE }}
300298
run: |
301299
sf data-code-extension function deploy \
302300
--name test-function-deploy \

scripts/mock_sf_server.py

Lines changed: 23 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,16 @@
4141
MOCK_SF_PORT=9000 python scripts/mock_sf_server.py
4242
python scripts/mock_sf_server.py 9000
4343
44-
Serves TLS with a throwaway self-signed cert (the deploy path requires an HTTPS
45-
upload URL). Set ``MOCK_SF_CERT_FILE`` to a path the clients can trust via
46-
``NODE_EXTRA_CA_CERTS`` (CLI) and ``REQUESTS_CA_BUNDLE`` (SDK).
44+
Serves TLS (the deploy path requires an HTTPS upload URL) using a pre-generated
45+
cert/key pair — this script does not generate one. Set ``MOCK_SF_CERT_FILE`` /
46+
``MOCK_SF_KEY_FILE`` to the pair's paths; generate a throwaway one with:
47+
48+
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem \\
49+
-days 1 -subj "/CN=localhost" \\
50+
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
51+
52+
Point clients at the cert so they trust it: ``NODE_EXTRA_CA_CERTS`` (CLI) and
53+
``REQUESTS_CA_BUNDLE`` (SDK).
4754
"""
4855

4956
from __future__ import annotations
@@ -52,51 +59,14 @@
5259
import json
5360
import os
5461
import ssl
55-
import subprocess
5662
import sys
57-
import tempfile
5863

5964
PORT = (
6065
int(sys.argv[1])
6166
if len(sys.argv) > 1
6267
else int(os.environ.get("MOCK_SF_PORT", "8888"))
6368
)
6469

65-
66-
def _self_signed_cert(dirpath: str) -> tuple[str, str]:
67-
"""Generate a throwaway self-signed cert for localhost via openssl.
68-
69-
The plugin's deploy path requires an HTTPS upload URL, so the server must
70-
speak TLS. Set ``MOCK_SF_CERT_FILE`` to write the cert to a known path so
71-
clients can trust it (``REQUESTS_CA_BUNDLE`` / ``NODE_EXTRA_CA_CERTS``).
72-
"""
73-
cert_path = os.environ.get("MOCK_SF_CERT_FILE") or os.path.join(dirpath, "cert.pem")
74-
key_path = os.path.join(dirpath, "key.pem")
75-
subprocess.run(
76-
[
77-
"openssl",
78-
"req",
79-
"-x509",
80-
"-newkey",
81-
"rsa:2048",
82-
"-nodes",
83-
"-keyout",
84-
key_path,
85-
"-out",
86-
cert_path,
87-
"-days",
88-
"1",
89-
"-subj",
90-
"/CN=localhost",
91-
"-addext",
92-
"subjectAltName=DNS:localhost,IP:127.0.0.1",
93-
],
94-
check=True,
95-
capture_output=True,
96-
)
97-
return cert_path, key_path
98-
99-
10070
_USERINFO = {
10171
"sub": "https://test.salesforce.com/id/00D000000000001AAA/005000000000001AAA",
10272
"user_id": "005000000000001AAA",
@@ -194,12 +164,18 @@ def do_PUT(self) -> None:
194164

195165

196166
if __name__ == "__main__":
167+
cert_path = os.environ.get("MOCK_SF_CERT_FILE")
168+
key_path = os.environ.get("MOCK_SF_KEY_FILE")
169+
if not cert_path or not key_path:
170+
sys.exit(
171+
"MOCK_SF_CERT_FILE and MOCK_SF_KEY_FILE must both be set to an "
172+
"existing TLS cert/key pair — see the module docstring."
173+
)
174+
197175
server = HTTPServer(("localhost", PORT), MockSFHandler)
198176
server.allow_reuse_address = True
199-
with tempfile.TemporaryDirectory() as certdir:
200-
cert_path, key_path = _self_signed_cert(certdir)
201-
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
202-
ctx.load_cert_chain(cert_path, key_path)
203-
server.socket = ctx.wrap_socket(server.socket, server_side=True)
204-
print(f"[MOCK SF] Listening on https://localhost:{PORT}", flush=True)
205-
server.serve_forever()
177+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
178+
ctx.load_cert_chain(cert_path, key_path)
179+
server.socket = ctx.wrap_socket(server.socket, server_side=True)
180+
print(f"[MOCK SF] Listening on https://localhost:{PORT}", flush=True)
181+
server.serve_forever()

0 commit comments

Comments
 (0)