Add param to forward headers from the auth server to the origin (#9271)
diff --git a/doc/admin-guide/plugins/authproxy.en.rst b/doc/admin-guide/plugins/authproxy.en.rst
index b17984a..c788c63 100644
--- a/doc/admin-guide/plugins/authproxy.en.rst
+++ b/doc/admin-guide/plugins/authproxy.en.rst
@@ -80,6 +80,11 @@
The TCP port of the authorization host. This is only used by the
``redirect`` transform.
+--forward-header-prefix=PREFIX
+ If the option is enabled, authentication response header fields, which
+ contain the specified prefix, will be sent to the original server in
+ the original request.
+
--force-cacheability
If this options is set, the plugin will allow Traffic Server to
cache the result of authorized requests. In the normal case, requests
@@ -106,10 +111,11 @@
In this example, the request is directed to a local authentication server
-that authorizes the request based on internal policy rules::
+that authorizes the request based on internal policy rules. Authentication response
+headers with the prefix will be proxied to the original server::
map http://cache.example.com http://origin.internal.com/ \
- @plugin=authproxy.so @pparam=--auth-transform=redirect @pparam=--auth-host=127.0.0.1 @pparam=--auth-port=9000
+ @plugin=authproxy.so @pparam=--auth-transform=redirect @pparam=--auth-host=127.0.0.1 @pparam=--auth-port=9000 @pparam=--forward-header-prefix=x-requested
map http://origin.internal.com/ http://origin.internal.com/ \
- @plugin=authproxy.so @pparam=--auth-transform=redirect @pparam=--auth-host=127.0.0.1 @pparam=--auth-port=9000
+ @plugin=authproxy.so @pparam=--auth-transform=redirect @pparam=--auth-host=127.0.0.1 @pparam=--auth-port=9000 @pparam=--forward-header-prefix=x-requested
diff --git a/plugins/authproxy/Makefile.inc b/plugins/authproxy/Makefile.inc
index a46a242..9e2e569 100644
--- a/plugins/authproxy/Makefile.inc
+++ b/plugins/authproxy/Makefile.inc
@@ -19,3 +19,7 @@
authproxy/authproxy.cc \
authproxy/utils.cc \
authproxy/utils.h
+
+check_PROGRAMS += authproxy/authproxy_test
+authproxy_authproxy_test_CPPFLAGS = $(AM_CPPFLAGS) -I$(abs_top_srcdir)/tests/include
+authproxy_authproxy_test_SOURCES = authproxy/tests/authproxy_test.cc
diff --git a/plugins/authproxy/authproxy.cc b/plugins/authproxy/authproxy.cc
index 74f20ce..a8da078 100644
--- a/plugins/authproxy/authproxy.cc
+++ b/plugins/authproxy/authproxy.cc
@@ -39,6 +39,7 @@
#include <ts/remap.h>
using std::strlen;
+using std::string_view;
struct AuthRequestContext;
@@ -55,10 +56,12 @@
struct AuthOptions {
std::string hostname;
+ std::string forward_header_prefix;
int hostport = -1;
AuthRequestTransform transform = nullptr;
bool force = false;
bool cache_internal_requests = false;
+ string_view forwardHeaderPrefix;
AuthOptions() = default;
~AuthOptions() = default;
@@ -622,6 +625,38 @@
TSHttpTxnConfigIntSet(auth->txn, TS_CONFIG_HTTP_CACHE_IGNORE_AUTHENTICATION, 1);
}
+ if (!options->forward_header_prefix.empty()) {
+ // Copy headers with configured prefix in the authentication response to the original request
+ TSMLoc field_loc;
+ TSMLoc next_field_loc;
+ TSMBuffer request_bufp;
+ TSMLoc request_hdr;
+
+ TSReleaseAssert(TSHttpTxnClientReqGet(auth->txn, &request_bufp, &request_hdr) == TS_SUCCESS);
+ field_loc = TSMimeHdrFieldGet(auth->rheader.buffer, auth->rheader.header, 0);
+ TSReleaseAssert(field_loc != TS_NULL_MLOC);
+
+ while (field_loc) {
+ int key_len = 0;
+ int val_len = 0;
+
+ char *key = const_cast<char *>(TSMimeHdrFieldNameGet(auth->rheader.buffer, auth->rheader.header, field_loc, &key_len));
+ char *val =
+ const_cast<char *>(TSMimeHdrFieldValueStringGet(auth->rheader.buffer, auth->rheader.header, field_loc, -1, &val_len));
+
+ if (key && val && ContainsPrefix(string_view(key, key_len), options->forward_header_prefix)) {
+ // Append the matched header key/val to the original request
+ HttpSetMimeHeader(request_bufp, request_hdr, string_view(key, key_len), string_view(val, val_len));
+ }
+
+ // Validate the next header field
+ next_field_loc = TSMimeHdrFieldNext(auth->rheader.buffer, auth->rheader.header, field_loc);
+ TSHandleMLocRelease(auth->rheader.buffer, auth->rheader.header, field_loc);
+ field_loc = next_field_loc;
+ }
+ }
+
+ // Proceed with the modified request
TSHttpTxnReenable(auth->txn, TS_EVENT_HTTP_CONTINUE);
return TS_EVENT_CONTINUE;
}
@@ -693,6 +728,7 @@
{const_cast<char *>("auth-transform"), required_argument, nullptr, 't'},
{const_cast<char *>("force-cacheability"), no_argument, nullptr, 'c'},
{const_cast<char *>("cache-internal"), no_argument, nullptr, 'i'},
+ {const_cast<char *>("forward-header-prefix"), required_argument, nullptr, 'f'},
{nullptr, 0, nullptr, 0},
};
@@ -717,6 +753,9 @@
case 'i':
options->cache_internal_requests = true;
break;
+ case 'f':
+ options->forward_header_prefix = optarg;
+ break;
case 't':
if (strcasecmp(optarg, "redirect") == 0) {
options->transform = AuthWriteRedirectedRequest;
diff --git a/plugins/authproxy/tests/authproxy_test.cc b/plugins/authproxy/tests/authproxy_test.cc
new file mode 100644
index 0000000..c9026cb
--- /dev/null
+++ b/plugins/authproxy/tests/authproxy_test.cc
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#include <string_view>
+#define CATCH_CONFIG_MAIN /* include main function */
+#include <catch.hpp> /* catch unit-test framework */
+#include "../utils.h"
+
+using std::string_view;
+TEST_CASE("Util methods", "[authproxy][utility]")
+{
+ SECTION("ContainsPrefix()")
+ {
+ CHECK(ContainsPrefix(string_view{"abcdef"}, "abc") == true);
+ CHECK(ContainsPrefix(string_view{"abc"}, "abcdef") == false);
+ CHECK(ContainsPrefix(string_view{"abcdef"}, "abd") == false);
+ CHECK(ContainsPrefix(string_view{"abc"}, "abc") == true);
+ CHECK(ContainsPrefix(string_view{""}, "") == true);
+ CHECK(ContainsPrefix(string_view{"abc"}, "") == true);
+ CHECK(ContainsPrefix(string_view{""}, "abc") == false);
+ CHECK(ContainsPrefix(string_view{"abcdef"}, "abc\0") == true);
+ CHECK(ContainsPrefix(string_view{"abcdef\0"}, "abc\0") == true);
+ }
+}
diff --git a/plugins/authproxy/utils.cc b/plugins/authproxy/utils.cc
index bbc1ac1..e9a9a75 100644
--- a/plugins/authproxy/utils.cc
+++ b/plugins/authproxy/utils.cc
@@ -26,6 +26,7 @@
#include <getopt.h>
#include <netinet/in.h>
#include <arpa/inet.h>
+#include <string_view>
void
HttpDebugHeader(TSMBuffer mbuf, TSMLoc mhdr)
@@ -81,6 +82,23 @@
TSHandleMLocRelease(mbuf, mhdr, mloc);
}
+void
+HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const std::string_view name, const std::string_view value)
+{
+ TSMLoc mloc;
+ mloc = TSMimeHdrFieldFind(mbuf, mhdr, name.data(), name.size());
+ if (mloc == TS_NULL_MLOC) {
+ TSReleaseAssert(TSMimeHdrFieldCreateNamed(mbuf, mhdr, name.data(), name.size(), &mloc) == TS_SUCCESS);
+ } else {
+ TSReleaseAssert(TSMimeHdrFieldValuesClear(mbuf, mhdr, mloc) == TS_SUCCESS);
+ }
+
+ TSReleaseAssert(TSMimeHdrFieldValueStringInsert(mbuf, mhdr, mloc, 0 /* index */, value.data(), value.size()) == TS_SUCCESS);
+ TSReleaseAssert(TSMimeHdrFieldAppend(mbuf, mhdr, mloc) == TS_SUCCESS);
+
+ TSHandleMLocRelease(mbuf, mhdr, mloc);
+}
+
unsigned
HttpGetContentLength(TSMBuffer mbuf, TSMLoc mhdr)
{
diff --git a/plugins/authproxy/utils.h b/plugins/authproxy/utils.h
index 51cf01c..bd672ee 100644
--- a/plugins/authproxy/utils.h
+++ b/plugins/authproxy/utils.h
@@ -18,6 +18,8 @@
#pragma once
+#include <string>
+#include <string_view>
#include <ts/ts.h>
#include <netinet/in.h>
#include <memory>
@@ -102,8 +104,15 @@
// Set the value of an arbitrary HTTP header.
void HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const char *name, const char *value);
void HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const char *name, unsigned value);
+void HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const std::string_view name, const std::string_view value);
// Dump the given HTTP header to the debug log.
void HttpDebugHeader(TSMBuffer mbuf, TSMLoc mhdr);
+// Check if the string contains the prefix
+inline bool
+ContainsPrefix(const std::string_view str, const std::string &prefix)
+{
+ return str.size() < prefix.size() ? false : (strncmp(str.data(), prefix.data(), prefix.size()) == 0);
+}
// vim: set ts=4 sw=4 et :