diff --git a/man/journal-upload.conf.xml b/man/journal-upload.conf.xml
index 3de7044fd66..d4dfa11f2b9 100644
--- a/man/journal-upload.conf.xml
+++ b/man/journal-upload.conf.xml
@@ -129,6 +129,31 @@
Takes a boolean value, enforces using compression without content encoding negotiation.
Defaults to false.
+
+
+
+ Header=
+
+ HTTP header which should be added to each request to URL.
+
+ Header consists of name and value, which are separated by colon.
+
+ Header name can contains alphanumeric values, "_" and "-" symbols additionally.
+
+ This option may be specified more than once, in which case all listed headers will be set. If
+ the same header name is listed more than once, all its unique values will be concatenated with comma.
+ Setting Header to empty string clears all previous assignments.
+
+
+ Example:
+ Header=HeaderName: HeaderValue
+Header=HeaderName: NewValue
+Header=HeaderName: HeaderValue
+
+
+ adds HeaderName header with HeaderValue, NewValue to each HTTP request.
+
+
diff --git a/src/journal-remote/journal-header-util.c b/src/journal-remote/journal-header-util.c
new file mode 100644
index 00000000000..d25a9d09568
--- /dev/null
+++ b/src/journal-remote/journal-header-util.c
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "escape.h"
+#include "journal-header-util.h"
+#include "string-util.h"
+#include "strv.h"
+
+/* HTTP header name can contains:
+- Alphanumeric characters: a-z, A-Z, and 0-9
+- The following special characters: - and _ */
+#define VALID_HEADER_NAME_CHARS \
+ ALPHANUMERICAL "_-"
+
+#define VALID_HEADER_NAME_LENGTH 40
+
+#define VALID_HEADER_VALUE_CHARS \
+ ALPHANUMERICAL "_ :;.,\\/'\"?!(){}[]@<>=-+*#$&`|~^%"
+
+static bool header_name_is_valid(const char *e) {
+ if (!e)
+ return false;
+
+ if (strlen(e) == 0 || strlen(e) > VALID_HEADER_NAME_LENGTH)
+ return false;
+
+ return in_charset(e, VALID_HEADER_NAME_CHARS);
+}
+
+static bool header_value_is_valid(const char *e) {
+ if (!e)
+ return false;
+
+ return in_charset(e, VALID_HEADER_VALUE_CHARS);
+}
+
+int header_put(OrderedHashmap **headers, const char *name, const char *value) {
+ assert(headers);
+
+ if (!header_value_is_valid(value))
+ return -EINVAL;
+
+ if (!header_name_is_valid(name))
+ return -EINVAL;
+
+ return string_strv_ordered_hashmap_put(headers, name, value);
+}
+
+int config_parse_header(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ OrderedHashmap **headers = ASSERT_PTR(data);
+ char *unescaped, *t;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ /* an empty string clears the previous assignments. */
+ *headers = ordered_hashmap_free(*headers);
+ return 1;
+ }
+
+ r = cunescape(rvalue, 0, &unescaped);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to unescape headers: %s", rvalue);
+ return 0;
+ }
+
+ t = strchr(unescaped, ':');
+ if (!t) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Failed to parse header, name: value separator was not found, ignoring: %s", unescaped);
+ return 0;
+ }
+
+ *t++ = '\0';
+
+ r = header_put(headers, strstrip(unescaped), skip_leading_chars(t, WHITESPACE));
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to update headers: %s", rvalue);
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/src/journal-remote/journal-header-util.h b/src/journal-remote/journal-header-util.h
new file mode 100644
index 00000000000..b0d9bb49f57
--- /dev/null
+++ b/src/journal-remote/journal-header-util.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "conf-parser.h"
+#include "hashmap.h"
+
+int header_put(OrderedHashmap **headers, const char *name, const char *value);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_header);
diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c
index f0af903d8a5..a1d2be8b17e 100644
--- a/src/journal-remote/journal-upload.c
+++ b/src/journal-remote/journal-upload.c
@@ -15,11 +15,13 @@
#include "constants.h"
#include "daemon-util.h"
#include "env-file.h"
+#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
#include "glob-util.h"
+#include "journal-header-util.h"
#include "journal-upload.h"
#include "journal-util.h"
#include "log.h"
@@ -59,6 +61,7 @@ static int arg_follow = -1;
static char *arg_save_state = NULL;
static usec_t arg_network_timeout_usec = USEC_INFINITY;
static OrderedHashmap *arg_compression = NULL;
+static OrderedHashmap *arg_headers = NULL;
static bool arg_force_compression = false;
STATIC_DESTRUCTOR_REGISTER(arg_url, freep);
@@ -72,6 +75,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_machine, freep);
STATIC_DESTRUCTOR_REGISTER(arg_namespace, freep);
STATIC_DESTRUCTOR_REGISTER(arg_save_state, freep);
STATIC_DESTRUCTOR_REGISTER(arg_compression, ordered_hashmap_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_headers, ordered_hashmap_freep);
static void close_fd_input(Uploader *u);
@@ -226,6 +230,23 @@ int start_upload(Uploader *u,
h = l;
}
+ char **values;
+ const char *name;
+ ORDERED_HASHMAP_FOREACH_KEY(values, name, arg_headers) {
+ _cleanup_free_ char *joined = strv_join(values, ", ");
+ if (!joined)
+ return log_oom();
+
+ _cleanup_free_ char *header = strjoin(name, ": ", joined);
+ if (!header)
+ return log_oom();
+
+ l = curl_slist_append(h, header);
+ if (!l)
+ return log_oom();
+ h = l;
+ }
+
u->header = TAKE_PTR(h);
}
@@ -657,6 +678,7 @@ static int parse_config(void) {
{ "Upload", "ServerCertificateFile", config_parse_path_or_ignore, 0, &arg_cert },
{ "Upload", "TrustedCertificateFile", config_parse_path_or_ignore, 0, &arg_trust },
{ "Upload", "NetworkTimeoutSec", config_parse_sec, 0, &arg_network_timeout_usec },
+ { "Upload", "Header", config_parse_header, 0, &arg_headers },
{ "Upload", "Compression", config_parse_compression, /* with_level */ true, &arg_compression },
{ "Upload", "ForceCompression", config_parse_bool, 0, &arg_force_compression },
{}
diff --git a/src/journal-remote/meson.build b/src/journal-remote/meson.build
index 0f3a91a621d..f133e6bbe43 100644
--- a/src/journal-remote/meson.build
+++ b/src/journal-remote/meson.build
@@ -4,6 +4,7 @@ systemd_journal_upload_sources = files(
'journal-compression-util.c',
'journal-upload-journal.c',
'journal-upload.c',
+ 'journal-header-util.c',
)
libsystemd_journal_remote_sources = files(
@@ -90,6 +91,12 @@ executables += [
},
]
+executables += [
+ test_template + {
+ 'sources' : files('test-journal-header-util.c', 'journal-header-util.c'),
+ },
+]
+
in_files = [
['journal-upload.conf',
conf.get('ENABLE_REMOTE') == 1 and conf.get('HAVE_LIBCURL') == 1 and install_sysconfdir_samples],
diff --git a/src/journal-remote/test-journal-header-util.c b/src/journal-remote/test-journal-header-util.c
new file mode 100644
index 00000000000..e88bb164ef7
--- /dev/null
+++ b/src/journal-remote/test-journal-header-util.c
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "hashmap.h"
+#include "journal-header-util.h"
+#include "tests.h"
+
+TEST(header_put) {
+ _cleanup_ordered_hashmap_free_ OrderedHashmap *headers = NULL;
+
+ ASSERT_OK_POSITIVE(header_put(&headers, "NewName", "Val"));
+ ASSERT_OK_POSITIVE(header_put(&headers, "Name", "FirstName"));
+ ASSERT_OK_POSITIVE(header_put(&headers, "Name", "Override"));
+ ASSERT_OK_ZERO(header_put(&headers, "Name", "FirstName"));
+ ASSERT_ERROR(header_put(&headers, "InvalidN@me", "test"), EINVAL);
+ ASSERT_ERROR(header_put(&headers, "Name", NULL), EINVAL);
+ ASSERT_ERROR(header_put(&headers, NULL, "Value"), EINVAL);
+ ASSERT_OK_POSITIVE(header_put(&headers, "Name", ""));
+ ASSERT_ERROR(header_put(&headers, "", "Value"), EINVAL);
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
diff --git a/test/units/TEST-04-JOURNAL.journal-remote.sh b/test/units/TEST-04-JOURNAL.journal-remote.sh
index df39a50b049..094fb3f4411 100755
--- a/test/units/TEST-04-JOURNAL.journal-remote.sh
+++ b/test/units/TEST-04-JOURNAL.journal-remote.sh
@@ -272,3 +272,41 @@ EOF
rm /run/systemd/journal-upload.conf.d/99-test.conf
rm /run/systemd/journal-remote.conf.d/99-test.conf
done
+
+# Let's test sending data with custom headers
+echo "$TEST_MESSAGE" | systemd-cat -t "$TEST_TAG"
+journalctl --sync
+
+cat >/run/systemd/journal-remote.conf.d/99-test.conf </run/systemd/journal-upload.conf.d/99-test.conf <