allthingssecurity commented on code in PR #27678:
URL: https://github.com/apache/camel/pull/27678#discussion_r4240515837


##########
components/camel-wasm/src/main/java/org/apache/camel/wasm/WasiCommand.java:
##########
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.wasm;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import run.endive.runtime.ImportValues;
+import run.endive.runtime.Instance;
+import run.endive.runtime.TrapException;
+import run.endive.wasi.WasiExitException;
+import run.endive.wasi.WasiOptions;
+import run.endive.wasi.WasiPreview1;
+import run.endive.wasm.WasmModule;
+import run.endive.wasm.types.Export;
+import run.endive.wasm.types.ExportSection;
+import run.endive.wasm.types.ExternalType;
+import run.endive.wasm.types.MemoryLimits;
+import run.endive.wasm.types.MemorySection;
+
+/**
+ * Runs a WASI Preview 1 command module (a program with a {@code _start} 
function, such as a Rust program built for
+ * {@code wasm32-wasip1} or a Go program built with {@code GOOS=wasip1 
GOARCH=wasm}) to completion.
+ * <p/>
+ * The module is parsed once by the caller and every {@link #run} creates a 
new instance of it, so runs never share
+ * guest memory or globals and need no lock. A run gets exactly the standard 
streams, arguments and environment
+ * variables it is given: no preopened directories (so no files) and no 
sockets. Clock and random numbers are the
+ * host's.
+ */
+public final class WasiCommand {
+
+    private static final String START = "_start";
+    private static final String WASI_MODULE = "wasi_snapshot_preview1";
+
+    private final WasmModule module;
+    private final List<String> arguments;
+    private final MemoryLimits memoryLimits;
+
+    /**
+     * @param  module                   the parsed module
+     * @param  arguments                the program's arguments, the program 
name first
+     * @param  maxMemoryPages           the maximum number of 64 KiB pages the 
module's memory may grow to, or 0 to keep
+     *                                  the maximum the module declares
+     * @throws IllegalArgumentException if the module is not a WASI Preview 1 
command module, or maxMemoryPages is lower
+     *                                  than the initial memory the module 
declares
+     */
+    public WasiCommand(WasmModule module, List<String> arguments, int 
maxMemoryPages) {
+        this.module = Objects.requireNonNull(module);
+        this.arguments = List.copyOf(arguments);
+        validate(module);
+        this.memoryLimits = memoryLimits(module, maxMemoryPages);
+    }
+
+    /**
+     * Runs the program once, on a new instance, on the calling thread.
+     * <p/>
+     * A {@link RuntimeException} thrown by one of the given streams stops the 
program and is rethrown.
+     *
+     * @param  stdin                                       the program's 
standard input
+     * @param  stdout                                      receives the 
program's standard output
+     * @param  stderr                                      receives the 
program's standard error
+     * @param  environment                                 the program's 
complete environment
+     * @return                                             the exit code: 0 
when {@code _start} returned, otherwise the
+     *                                                     value passed to 
{@code proc_exit}
+     * @throws TrapException                               if the program 
trapped (for example a Rust panic)
+     * @throws run.endive.runtime.WasmInterruptedException if the calling 
thread was interrupted
+     */
+    public int run(InputStream stdin, OutputStream stdout, OutputStream 
stderr, Map<String, String> environment) {
+        // built on the thread that runs the program: the default random 
number generator is the thread's own.
+        // Endive reports the standard streams as terminals unless told 
otherwise; they are not, and a program that
+        // sees a terminal may add terminal output, such as colours, to the 
message body
+        WasiOptions.Builder options = WasiOptions.builder()

Review Comment:
   Confirmed in the Endive 1.1.0 jar: `WasiOptions$Builder.<init>` sets 
`random` to `ThreadLocalRandom.current()` and
   `WasiPreview1.randomGet` calls `random.nextBytes`. Fixed in cd87fa0fb8d8: 
`WasiCommand` now passes one shared
   `SecureRandom` with `withRandom(...)`. Tests: 
`WasiCommandTest.randomGetIsServedByASecureRandom` asserts that the
   WASI options of a run carry a `SecureRandom`, and 
`WasmComponentTest.randomGetReturnsBytesOfTheHost` runs a module
   that calls `random_get` (32 bytes, different on each run). The misleading 
comment is gone and the doc line now says
   that `random_get` returns bytes from a `java.security.SecureRandom`.
   
   _Claude Code on behalf of allthingssecurity_



##########
components/camel-wasm/src/main/java/org/apache/camel/component/wasm/WasmProducer.java:
##########
@@ -16,76 +16,335 @@
  */
 package org.apache.camel.component.wasm;
 
+import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
-import org.apache.camel.Endpoint;
+import org.apache.camel.CamelExchangeException;
 import org.apache.camel.Exchange;
-import org.apache.camel.spi.Resource;
-import org.apache.camel.spi.ResourceLoader;
+import org.apache.camel.ExchangeTimedOutException;
+import org.apache.camel.Message;
 import org.apache.camel.support.DefaultProducer;
-import org.apache.camel.support.PluginHelper;
-import org.apache.camel.wasm.WasmFunction;
-import org.apache.camel.wasm.WasmSupport;
+import org.apache.camel.support.ResourceHelper;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.URISupport;
+import org.apache.camel.wasm.WasiCommand;
+import org.apache.camel.wasm.Wasm;
+import run.endive.runtime.TrapException;
 import run.endive.wasm.Parser;
 import run.endive.wasm.WasmModule;
 
+/**
+ * Runs a WASI Preview 1 command module once per exchange: the message body is 
the standard input of the module and its
+ * standard output becomes the message body.
+ * <p/>
+ * The module is parsed once, when the producer is initialized, and every 
exchange runs on a new instance of it, so
+ * exchanges never share guest memory or globals and need no lock. The 
instance is dropped after its run, whatever the
+ * outcome.
+ */
 public class WasmProducer extends DefaultProducer {
 
-    private final String functionModule;
-    private final String functionName;
+    private static final int MAX_STDERR_IN_MESSAGE = 256;
 
-    private WasmModule module;
-    private WasmFunction function;
+    private WasiCommand command;
+    private Map<String, String> environment = Collections.emptyMap();
+    private List<String> environmentHeaders = Collections.emptyList();
+    private volatile ExecutorService executor;
 
-    public WasmProducer(Endpoint endpoint, String functionModule, String 
functionName) throws Exception {
+    public WasmProducer(WasmEndpoint endpoint) {
         super(endpoint);
+    }
 
-        this.functionModule = functionModule;
-        this.functionName = functionName;
+    @Override
+    public WasmEndpoint getEndpoint() {
+        return (WasmEndpoint) super.getEndpoint();
     }
 
     @Override
-    public void doInit() throws Exception {
-        final ResourceLoader rl = 
PluginHelper.getResourceLoader(getEndpoint().getCamelContext());
-        final Resource res = rl.resolveResource(this.functionModule);
+    protected void doInit() throws Exception {
+        super.doInit();
+
+        WasmEndpoint endpoint = getEndpoint();
+        WasmConfiguration conf = endpoint.getConfiguration();
+
+        Map<String, String> env = new LinkedHashMap<>();
+        if (conf.getEnvironment() != null) {
+            for (Map.Entry<String, Object> e : 
conf.getEnvironment().entrySet()) {
+                String value = e.getValue() != null ? e.getValue().toString() 
: "";
+                env.put(checkName(e.getKey()), checkNoNul("environment 
variable " + e.getKey(), value));
+            }
+        }
+        List<String> headers = new ArrayList<>();
+        if (ObjectHelper.isNotEmpty(conf.getEnvironmentHeaders())) {
+            for (String header : conf.getEnvironmentHeaders().split(",")) {
+                String name = checkName(header.trim());
+                if (env.containsKey(name)) {
+                    throw new IllegalArgumentException(
+                            "Environment variable " + name + " is configured 
both in environment and environmentHeaders");
+                }
+                headers.add(name);
+            }
+        }
+        List<String> arguments = arguments(endpoint.getModule(), 
conf.getArgs());

Review Comment:
   Confirmed: argument zero was `endpoint.getModule()` unchanged. Changed in 
cd87fa0fb8d8 to the file name of the
   module (`upper.wasm` for `file:/opt/modules/upper.wasm`), without scheme, 
directories, host, user info or query.
   I chose the file name over `URISupport.sanitizeUri`: sanitizing only masks 
user info and the query keys it knows as
   secrets, so a token under another key, the host and the host directories 
would still reach the module, and a WASI
   program expects its program name in argv[0] (it shows up in usage and error 
messages). `base64:` and `gzip:`
   locations, and a location without a file name, get `module.wasm`. The `args` 
description, the component doc and the
   generated catalog/DSL copies are updated. Tests: `argumentsFromTheRoute` now 
expects `args.wasm`, and
   `argumentZeroIsTheFileNameOfTheModule` covers 11 locations, including
   `https://user:[email protected]/mods/upper.wasm?token=abc123`.
   
   _Claude Code on behalf of allthingssecurity_



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to