diff --git a/lib/build/rpc_definitions.py b/lib/build/rpc_definitions.py
index 821d6bdb5cc6be4064f1594d5de79cbe51073f70..499df601d35da4f65983eeb27205d27b1b4efe52 100644
--- a/lib/build/rpc_definitions.py
+++ b/lib/build/rpc_definitions.py
@@ -403,4 +403,12 @@ CALLS = {
     ("master_info", MULTI, TMO_URGENT, [], None, "Query master info"),
     ("version", MULTI, TMO_URGENT, [], None, "Query node version"),
     ],
+  "RpcClientConfig": [
+    ("upload_file", MULTI, TMO_NORMAL, [
+      ("file_name", "self._PrepareFileUpload(%s)", None),
+      ], None, "Upload a file"),
+    ("write_ssconf_files", MULTI, TMO_NORMAL, [
+      ("values", None, None),
+      ], None, "Write ssconf files"),
+    ],
   }
diff --git a/lib/config.py b/lib/config.py
index 268304fdbadb8d06108bb2fff98eb743c4643a13..7ca7c5593ee2eb50bbf016e165adfe8647d0045f 100644
--- a/lib/config.py
+++ b/lib/config.py
@@ -1723,8 +1723,9 @@ class ConfigWriter:
       node_list.append(node_info.name)
       addr_list.append(node_info.primary_ip)
 
-    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
-                                            address_list=addr_list)
+    # TODO: Use dedicated resolver talking to config writer for name resolution
+    result = \
+      rpc.ConfigRunner(addr_list).call_upload_file(node_list, self._cfg_file)
     for to_node, to_result in result.items():
       msg = to_result.fail_msg
       if msg:
@@ -1783,7 +1784,7 @@ class ConfigWriter:
     # Write ssconf files on all nodes (including locally)
     if self._last_cluster_serial < self._config_data.cluster.serial_no:
       if not self._offline:
-        result = rpc.RpcRunner.call_write_ssconf_files(
+        result = rpc.ConfigRunner(None).call_write_ssconf_files(
           self._UnlockedGetOnlineNodeList(),
           self._UnlockedGetSsconfValues())
 
diff --git a/lib/rpc.py b/lib/rpc.py
index 1ecff15c4103fb213d9782d08b58152e869f0bdc..8e235ed86dd20c86a43e669746dcbc7156fd8fa7 100644
--- a/lib/rpc.py
+++ b/lib/rpc.py
@@ -438,7 +438,8 @@ class _RpcProcessor:
 
 
 class RpcRunner(_generated_rpc.RpcClientDefault,
-                _generated_rpc.RpcClientBootstrap):
+                _generated_rpc.RpcClientBootstrap,
+                _generated_rpc.RpcClientConfig):
   """RPC runner class.
 
   """
@@ -453,6 +454,7 @@ class RpcRunner(_generated_rpc.RpcClientDefault,
     # <http://www.logilab.org/ticket/36586> and
     # <http://www.logilab.org/ticket/35642>
     # pylint: disable=W0233
+    _generated_rpc.RpcClientConfig.__init__(self)
     _generated_rpc.RpcClientBootstrap.__init__(self)
     _generated_rpc.RpcClientDefault.__init__(self)
 
@@ -639,47 +641,20 @@ class RpcRunner(_generated_rpc.RpcClientDefault,
 
     return ieioargs
 
-  #
-  # Begin RPC calls
-  #
-
-  @classmethod
-  @_RpcTimeout(_TMO_NORMAL)
-  def call_upload_file(cls, node_list, file_name, address_list=None):
-    """Upload a file.
-
-    The node will refuse the operation in case the file is not on the
-    approved file list.
-
-    This is a multi-node call.
-
-    @type node_list: list
-    @param node_list: the list of node names to upload to
-    @type file_name: str
-    @param file_name: the filename to upload
-    @type address_list: list or None
-    @keyword address_list: an optional list of node addresses, in order
-        to optimize the RPC speed
+  @staticmethod
+  def _PrepareFileUpload(filename):
+    """Loads a file and prepares it for an upload to nodes.
 
     """
-    file_contents = utils.ReadFile(file_name)
-    data = _Compress(file_contents)
-    st = os.stat(file_name)
+    data = _Compress(utils.ReadFile(filename))
+    st = os.stat(filename)
     getents = runtime.GetEnts()
-    params = [file_name, data, st.st_mode, getents.LookupUid(st.st_uid),
-              getents.LookupGid(st.st_gid), st.st_atime, st.st_mtime]
-    return cls._StaticMultiNodeCall(node_list, "upload_file", params,
-                                    address_list=address_list)
-
-  @classmethod
-  @_RpcTimeout(_TMO_NORMAL)
-  def call_write_ssconf_files(cls, node_list, values):
-    """Write ssconf files.
-
-    This is a multi-node call.
+    return [filename, data, st.st_mode, getents.LookupUid(st.st_uid),
+            getents.LookupGid(st.st_gid), st.st_atime, st.st_mtime]
 
-    """
-    return cls._StaticMultiNodeCall(node_list, "write_ssconf_files", [values])
+  #
+  # Begin RPC calls
+  #
 
   def call_test_delay(self, node_list, duration, read_timeout=None):
     """Sleep for a fixed time on given node(s).
@@ -743,3 +718,34 @@ class BootstrapRunner(_generated_rpc.RpcClientBootstrap):
     body = serializer.DumpJson(args, indent=False)
 
     return self._proc(node_list, procedure, body, read_timeout=timeout)
+
+
+class ConfigRunner(_generated_rpc.RpcClientConfig):
+  """RPC wrappers for L{config}.
+
+  """
+  _PrepareFileUpload = \
+    staticmethod(RpcRunner._PrepareFileUpload) # pylint: disable=W0212
+
+  def __init__(self, address_list):
+    """Initializes this class.
+
+    """
+    _generated_rpc.RpcClientConfig.__init__(self)
+
+    if address_list is None:
+      resolver = _SsconfResolver
+    else:
+      # Caller provided an address list
+      resolver = _StaticResolver(address_list)
+
+    self._proc = _RpcProcessor(resolver,
+                               netutils.GetDaemonPort(constants.NODED))
+
+  def _Call(self, node_list, procedure, timeout, args):
+    """Entry point for automatically generated RPC wrappers.
+
+    """
+    body = serializer.DumpJson(args, indent=False)
+
+    return self._proc(node_list, procedure, body, read_timeout=timeout)
diff --git a/lib/server/noded.py b/lib/server/noded.py
index 384807bcecad142213ebf1d023f32d93df430c52..90d879f9ea5ce92e7b32c7c15fb410abc745db0e 100644
--- a/lib/server/noded.py
+++ b/lib/server/noded.py
@@ -770,7 +770,7 @@ class NodeHttpServer(http.server.HttpServer):
     files are accepted.
 
     """
-    return backend.UploadFile(*params)
+    return backend.UploadFile(*(params[0]))
 
   @staticmethod
   def perspective_master_info(params):