소스 검색

feat: add opt-in TLS certificate verification skip for http.request

The runner's HTTP primitive (smartbotic.http.request, backing http-request
and rss-reader) always verified certificates with no way to relax that,
so a feed like https://35photo.ru/rss/photo_new.xml with an expired cert
had no route to being read even deliberately.

Adds a skipTlsVerify option, off by default, threaded per-request from the
node config through js_http_request down to the curl call. It disables
only CURLOPT_SSL_VERIFYPEER (chain and expiry); CURLOPT_SSL_VERIFYHOST
stays on, so a certificate for a different host is still rejected - this
matches the failure being fixed (expired cert, correct host), not a
broader bypass. A warning naming the host is logged every time it fires.

Exposed on the http-request and rss-reader node config schemas as "Skip
TLS certificate verification", with a description that says what is given
up and who should turn it on. Off on every existing workflow, including
the one that prompted this.
fszontagh 1 개월 전
부모
커밋
4fed3f29f5
3개의 변경된 파일과 55개의 추가작업 그리고 2개의 파일을 삭제
  1. 12 0
      nodes/core/http-request.js
  2. 10 0
      nodes/rss/rss-reader.js
  3. 33 2
      src/runner/engine/script_engine.cpp

+ 12 - 0
nodes/core/http-request.js

@@ -125,6 +125,17 @@ const configSchema = {
       title: 'Follow Redirects',
       default: true
     },
+    skipTlsVerify: {
+      type: 'boolean',
+      title: 'Skip TLS certificate verification',
+      default: false,
+      description: 'Accept the response even if the certificate has expired or does not chain to a ' +
+                   'trusted root. The hostname is still checked, so this cannot be tricked into accepting ' +
+                   'a certificate for a different site - only a broken one for the right site. ' +
+                   'An attacker who can intercept this connection can impersonate the host while this is ' +
+                   'on. Use it only for a host you control, or a certificate failure you have personally ' +
+                   'checked - not as a standing setting.'
+    },
     failOnErrorStatus: {
       type: 'boolean',
       title: 'Fail on error status',
@@ -395,6 +406,7 @@ async function execute(config, input, context) {
       bodyBase64: bodyBase64 || undefined,
       timeout: config.timeout,
       followRedirects: config.followRedirects,
+      skipTlsVerify: config.skipTlsVerify === true,
       // Retrying is off unless asked for, and the waiting is done by the HTTP
       // helper rather than a loop here.
       retries: Number(config.retries) || 0,

+ 10 - 0
nodes/rss/rss-reader.js

@@ -58,6 +58,15 @@ const configSchema = {
             type: 'boolean', title: 'Skip On Error', default: false,
             description: 'Return success false and no items instead of failing the workflow. For a workflow reading several feeds, this is what lets the others carry on when one is unreachable - without it the first bad feed ends the run'
         },
+        skipTlsVerify: {
+            type: 'boolean', title: 'Skip TLS certificate verification', default: false,
+            description: 'Accept the feed even if its certificate has expired or does not chain to a ' +
+                         'trusted root. The hostname is still checked, so this cannot be tricked into ' +
+                         'accepting a certificate for a different site - only a broken one for the right ' +
+                         'site. An attacker who can intercept this connection can impersonate the feed ' +
+                         'host while this is on. Use it only for a feed you control, or a certificate ' +
+                         'failure you have personally checked - not as a standing setting.'
+        },
         detectNewItems: {
             type: 'boolean',
             title: 'Detect New Items',
@@ -730,6 +739,7 @@ async function readFeed(config, input) {
             ? Number(config.retries) : 2,
         retryDelayMs: Number(config.retryDelayMs) || 2000,
         retryMaxDelayMs: Number(config.retryMaxDelayMs) || 30000,
+        skipTlsVerify: config.skipTlsVerify === true,
         headers: {
             'Accept': 'application/rss+xml, application/atom+xml, application/xml, text/xml, */*',
             'User-Agent': userAgent

+ 33 - 2
src/runner/engine/script_engine.cpp

@@ -835,7 +835,8 @@ static CurlResponse performHttpRequest(
     const nlohmann::json& headers,
     const std::string& body,
     long timeout_ms,
-    bool follow_redirects
+    bool follow_redirects,
+    bool skip_tls_verify
 ) {
     CurlResponse response;
 
@@ -850,6 +851,18 @@ static CurlResponse performHttpRequest(
     // Set URL
     curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
 
+    // Certificate verification. Verify by default: CURLOPT_SSL_VERIFYPEER
+    // checks the chain and expiry, CURLOPT_SSL_VERIFYHOST checks the hostname
+    // in the certificate against the one being connected to. When a caller
+    // opts in to skipping verification we turn off only VERIFYPEER - the
+    // failure this exists for (an expired certificate) is a chain/expiry
+    // problem, not a wrong-host problem, and the hostname still being checked
+    // means the option cannot be used to quietly accept a certificate for a
+    // completely different site. Turning off VERIFYHOST as well would be the
+    // blunt instrument the request is deliberately not building.
+    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, skip_tls_verify ? 0L : 1L);
+    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
+
     // Set method
     if (method == "POST") {
         curl_easy_setopt(curl, CURLOPT_POST, 1L);
@@ -1189,6 +1202,24 @@ static JSValue js_http_request(JSContext* ctx, JSValue this_val, int argc, JSVal
     }
     JS_FreeValue(ctx, follow_val);
 
+    // Skip TLS certificate verification. Off unless a node deliberately turns
+    // it on: doing so means anyone able to intercept the connection can
+    // impersonate the host, so this belongs on a specific request against a
+    // host the caller controls or a failure they have personally checked, not
+    // left on as a habit. A warning is logged every time it fires, naming the
+    // host, so it shows up in the log even for someone who never opens the
+    // workflow that set it.
+    bool skip_tls_verify = false;
+    JSValue skip_tls_val = JS_GetPropertyStr(ctx, options, "skipTlsVerify");
+    if (JS_IsBool(skip_tls_val)) {
+        skip_tls_verify = JS_ToBool(ctx, skip_tls_val);
+    }
+    JS_FreeValue(ctx, skip_tls_val);
+    if (skip_tls_verify) {
+        LOG_WARN("http.request {} {} - TLS certificate verification is disabled for this request, "
+                 "host is not authenticated", method, url);
+    }
+
     // Retry settings. Off unless asked for: a request that is not safe to repeat
     // - a payment, a post - must not start retrying itself because a helper
     // gained the ability. A node that wants it says so.
@@ -1251,7 +1282,7 @@ static JSValue js_http_request(JSContext* ctx, JSValue this_val, int argc, JSVal
         for (int attempt = 1; attempt <= attempts; ++attempt) {
             bool retryable = false;
             try {
-                response = performHttpRequest(method, url, headers, body, timeout_ms, follow_redirects);
+                response = performHttpRequest(method, url, headers, body, timeout_ms, follow_redirects, skip_tls_verify);
                 last_transport_error.clear();
                 retryable = isRetryableStatus(response.status_code, retry_statuses);
                 if (!retryable) {