Last active
June 15, 2021 07:45
-
-
Save t2ym/9b80d6c41a99f3dee6136751b463e90d to your computer and use it in GitHub Desktop.
Git patch to nghttp2-v1.43.0 to add const SSL *nghttp2::asio_http2::server::request::ssl() to associate client certificates with requests
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/asio_server.cc b/src/asio_server.cc | |
index 74c92276..5a79061e 100644 | |
--- a/src/asio_server.cc | |
+++ b/src/asio_server.cc | |
@@ -154,7 +154,7 @@ void server::start_accept(boost::asio::ssl::context &tls_context, | |
return; | |
} | |
- new_connection->start(); | |
+ new_connection->start(new_connection->socket().native_handle()); | |
}); | |
} | |
diff --git a/src/asio_server_connection.h b/src/asio_server_connection.h | |
index daf9a664..c9570c04 100644 | |
--- a/src/asio_server_connection.h | |
+++ b/src/asio_server_connection.h | |
@@ -85,12 +85,15 @@ public: | |
stopped_(false) {} | |
/// Start the first asynchronous operation for the connection. | |
- void start() { | |
+ void start(SSL *ssl = nullptr) { | |
boost::system::error_code ec; | |
handler_ = std::make_shared<http2_handler>( | |
GET_IO_SERVICE(socket_), socket_.lowest_layer().remote_endpoint(ec), | |
[this]() { do_write(); }, mux_); | |
+ if (ssl) { | |
+ handler_->ssl(ssl); | |
+ } | |
if (handler_->start() != 0) { | |
stop(); | |
return; | |
diff --git a/src/asio_server_http2_handler.cc b/src/asio_server_http2_handler.cc | |
index c1fc195f..78186bd7 100644 | |
--- a/src/asio_server_http2_handler.cc | |
+++ b/src/asio_server_http2_handler.cc | |
@@ -241,6 +241,7 @@ http2_handler::http2_handler(boost::asio::io_service &io_service, | |
mux_(mux), | |
io_service_(io_service), | |
remote_ep_(ep), | |
+ ssl_(nullptr), | |
session_(nullptr), | |
buf_(nullptr), | |
buflen_(0), | |
@@ -484,6 +485,14 @@ const boost::asio::ip::tcp::endpoint &http2_handler::remote_endpoint() { | |
return remote_ep_; | |
} | |
+const SSL *http2_handler::ssl() const { | |
+ return ssl_; | |
+} | |
+ | |
+void http2_handler::ssl(SSL *ssl) { | |
+ ssl_ = ssl; | |
+} | |
+ | |
callback_guard::callback_guard(http2_handler &h) : handler(h) { | |
handler.enter_callback(); | |
} | |
diff --git a/src/asio_server_http2_handler.h b/src/asio_server_http2_handler.h | |
index 12064499..7bf5cf43 100644 | |
--- a/src/asio_server_http2_handler.h | |
+++ b/src/asio_server_http2_handler.h | |
@@ -92,6 +92,9 @@ public: | |
const boost::asio::ip::tcp::endpoint &remote_endpoint(); | |
+ const SSL *ssl() const; | |
+ void ssl(SSL *ssl); | |
+ | |
const std::string &http_date(); | |
template <size_t N> | |
@@ -156,6 +159,7 @@ private: | |
serve_mux &mux_; | |
boost::asio::io_service &io_service_; | |
boost::asio::ip::tcp::endpoint remote_ep_; | |
+ SSL *ssl_; | |
nghttp2_session *session_; | |
const uint8_t *buf_; | |
std::size_t buflen_; | |
diff --git a/src/asio_server_request.cc b/src/asio_server_request.cc | |
index 36669a52..8083a516 100644 | |
--- a/src/asio_server_request.cc | |
+++ b/src/asio_server_request.cc | |
@@ -54,6 +54,10 @@ const boost::asio::ip::tcp::endpoint &request::remote_endpoint() const { | |
return impl_->remote_endpoint(); | |
} | |
+const SSL *request::ssl() const { | |
+ return impl_->ssl(); | |
+} | |
+ | |
} // namespace server | |
} // namespace asio_http2 | |
} // namespace nghttp2 | |
diff --git a/src/asio_server_request_impl.cc b/src/asio_server_request_impl.cc | |
index 8442ad05..5cc6ab78 100644 | |
--- a/src/asio_server_request_impl.cc | |
+++ b/src/asio_server_request_impl.cc | |
@@ -62,6 +62,14 @@ void request_impl::remote_endpoint(boost::asio::ip::tcp::endpoint ep) { | |
remote_ep_ = std::move(ep); | |
} | |
+const SSL *request_impl::ssl() const { | |
+ return ssl_; | |
+} | |
+ | |
+void request_impl::ssl(const SSL *ssl) { | |
+ ssl_ = ssl; | |
+} | |
+ | |
size_t request_impl::header_buffer_size() const { return header_buffer_size_; } | |
void request_impl::update_header_buffer_size(size_t len) { | |
diff --git a/src/asio_server_request_impl.h b/src/asio_server_request_impl.h | |
index 05de98a8..3f29299d 100644 | |
--- a/src/asio_server_request_impl.h | |
+++ b/src/asio_server_request_impl.h | |
@@ -58,6 +58,9 @@ public: | |
const boost::asio::ip::tcp::endpoint &remote_endpoint() const; | |
void remote_endpoint(boost::asio::ip::tcp::endpoint ep); | |
+ const SSL *ssl() const; | |
+ void ssl(const SSL *ssl); | |
+ | |
size_t header_buffer_size() const; | |
void update_header_buffer_size(size_t len); | |
@@ -68,6 +71,7 @@ private: | |
uri_ref uri_; | |
data_cb on_data_cb_; | |
boost::asio::ip::tcp::endpoint remote_ep_; | |
+ const SSL *ssl_; | |
size_t header_buffer_size_; | |
}; | |
diff --git a/src/asio_server_stream.cc b/src/asio_server_stream.cc | |
index f763c1e0..05ba88e5 100644 | |
--- a/src/asio_server_stream.cc | |
+++ b/src/asio_server_stream.cc | |
@@ -35,6 +35,7 @@ namespace server { | |
stream::stream(http2_handler *h, int32_t stream_id) | |
: handler_(h), stream_id_(stream_id) { | |
request_.impl().stream(this); | |
+ request_.impl().ssl(h->ssl()); | |
response_.impl().stream(this); | |
} | |
diff --git a/src/includes/nghttp2/asio_http2_server.h b/src/includes/nghttp2/asio_http2_server.h | |
index d4ec489a..ae6cb65d 100644 | |
--- a/src/includes/nghttp2/asio_http2_server.h | |
+++ b/src/includes/nghttp2/asio_http2_server.h | |
@@ -62,6 +62,8 @@ public: | |
// Returns the remote endpoint of the request | |
const boost::asio::ip::tcp::endpoint &remote_endpoint() const; | |
+ const SSL *ssl() const; | |
+ | |
private: | |
std::unique_ptr<request_impl> impl_; | |
}; |
Get Fingerprints for Roles mapped from AD group and subgroups
- Pseudo-role
"*"
to show the root group DN - Other meta-infomration such as timestamp may be useful
$group = "CN=WebAppX_Users,OU=SectionA,OU=DevDepartment,DC=example,DC=local"; $outfile = "roles.json"; $jsonBase = @{"*"=$group}; Get-ADGroupMember $group |
Select distinguishedName |
ForEach-Object { $jsonBase.Add(($_.distinguishedName -replace "CN=([^,]*),.*", "`$1"),
@(,(Get-ADGroupMember $_.distinguishedName -recursive |
Get-ADUser -Properties userCertificate |
Select userCertificate |
ForEach-Object { if ($_.userCertificate) {
Get-FileHash -Algorithm SHA1 -InputStream (new-object System.IO.MemoryStream(,$_.userCertificate[0]))} } |
ForEach-Object { $_.Hash }) |
ForEach-Object { $_ })); }; $jsonBase | ConvertTo-Json | Out-File $outfile; Get-Content $outfile;
{
"Admin": [
"C94F88A3E4B9CC73D919F74620F821D28782B8AF"
],
"*": "CN=WebAppX_Users,OU=SectionA,OU=DevDepartment,DC=example,DC=local",
"User": [
"C94F88A3E4B9CC73D919F74620F821D28782B8AF",
"020C1FCEA19A398DD4E544A3263E0A6EC6357CFD",
"E92C3F99D1ADF24B4895C4A7C96214056CEDF23F",
"DEC457FBC9C1828B7654ED36343E5AD85008E99C",
"BD639B17FAFA564DF2F7E8729781A38E9B4A6024"
]
}
On UN*X-like systems
- LDAP configuration at
~/.ldaprc
TLS_CACERT {PATH_TO_TRUSTED_CA_FILE_IN_CONCATENATED_PEM}
- LDAP Bind User password at
./passwdfile
ldif2json
at./ldif2json
- Depends on
bash
,jq
,awk
,sha1sum
,base64
,cut
,egrep
,sed
,ldapsearch
- Limitations by Active Directory policy
get_roles() { local GROUP_DN="$1";local BIND_USER="$2";local BIND="$3";local SERVERURI="$4";local PASSWDFILE="$5";local LDIF2JSON="$6"; local OUTFILE="$7";\
get_fingerprints () { local SUBGROUP_DN=$1; jq -nc '$ARGS.positional' \
--args `for i in \`ldapsearch -H ${SERVERURI} -x -W -D ${BIND_USER} -y passwdfile -b ${BIND} \
"(&(objectCategory=person)(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=${SUBGROUP_DN}))" |\
awk -f ${LDIF2JSON} | jq '.[].userCertificate' | sed -e 's/^"\(.*\)"$/\1/' | sed -e 's/^null//' | egrep -e .\`; \
do { echo -n ${i} | base64 -d | sha1sum | cut -c1-40 | awk '{print toupper($0)}'; } done` | jq .;
}; \
echo \{ `ldapsearch -H ${SERVERURI} -x -W -D "${BIND_USER}" -y ${PASSWDFILE} -b "${BIND}" "(distinguishedName=${GROUP_DN})" | awk -f ${LDIF2JSON} | jq .[].member | jq -r .[] |\
while read line; do { echo \"\`echo -n \"${line}\" | sed -e 's/^"CN=\([^,]*\),.*$/\\1/'\`\": \`get_fingerprints ${line}\`, ; } done` \} \{\"*\": \"${GROUP_DN}\" \} |\
sed -e 's/, *}/}/' | jq -s add >${OUTFILE}; \
cat ${OUTFILE}; unset get_fingerprints; unset get_roles;\
}; \
get_roles "CN=WebAppX_Users,OU=SectionA,OU=DevDepartment,DC=example,DC=local" "bind.user@example.local" "dc=example,dc=local" "ldaps://winserver.example.local" "./passwdfile" "./ldif2json" "./roles.json"
{
"User": [
"C94F88A3E4B9CC73D919F74620F821D28782B8AF",
"020C1FCEA19A398DD4E544A3263E0A6EC6357CFD",
"DEC457FBC9C1828B7654ED36343E5AD85008E99C",
"BD639B17FAFA564DF2F7E8729781A38E9B4A6024",
"E92C3F99D1ADF24B4895C4A7C96214056CEDF23F"
],
"Admin": [
"C94F88A3E4B9CC73D919F74620F821D28782B8AF"
],
"*": "CN=WebAppX_Users,OU=SectionA,OU=DevDepartment,DC=example,DC=local"
}
Group and User configurations in the above scripts
- Subgroups mapped to roles can be located in any LDAP paths including outside of the container OU to avoid role name conflicts with other web applications
function Get-ADTree {
param( $group )
if ($group.GetType().FullName -eq "System.String") {
Get-ADTree (Get-ADGroup $group);
}
elseif ($group.ObjectClass -eq "group") {
@{$group.distinguishedName=(Get-ADGroupMember $group | ForEach-Object { Get-ADTree $_ })}
}
else {
$group.distinguishedName
}
}
Get-ADTree "CN=WebAppX_Users,OU=SectionA,OU=DevDepartment,DC=example,DC=local" | ConvertTo-Json -Depth 5
- Output reformatted with
jq .
- To show a single element array,
ConvertTo-Json -Depth 5 -AsArray
should be used in PowerShell 7.1
{
"CN=WebAppX_Users,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
{
"CN=Admin,OU=SectionA,OU=DevDepartment,DC=example,DC=local": "CN=SecA User1,OU=SectionA,OU=DevDepartment,DC=example,DC=local"
},
{
"CN=User,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
{
"CN=SecA_Users,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
"CN=SecA User1,OU=SectionA,OU=DevDepartment,DC=example,DC=local",
"CN=SecA User2,OU=SectionA,OU=DevDepartment,DC=example,DC=local",
"CN=SecA User3,OU=SectionA,OU=DevDepartment,DC=example,DC=local"
]
},
{
"CN=SecB_Users,OU=SectionB,OU=DevDepartment,DC=example,DC=local": [
"CN=SecB User1,OU=SectionB,OU=DevDepartment,DC=example,DC=local",
"CN=SecB User2,OU=SectionB,OU=DevDepartment,DC=example,DC=local"
]
}
]
}
]
}
Map groups to roles generated from AD groups and subgroups
- If only groups of which a user is a direct member is known, the user's roles can be determined by this map from groups to roles
- Searching only for groups should be faster than exhaustive search for
userCertificate
attributes userCertificate
attributes are not populated until users sign in to their computers- Web apps for all users should be able to authenticate and authorize such users at their first sign-in operations, where intranet web services must be of their focus
- Retrieval of their group membership attributes from Active Directory should be done just on their first access to web apps without delay
- For intranet web apps that are expected to be registered on requests, all the expected
userCertificate
attributes should be existent before their access attempts. Fingerprints can be pre-fetched for such applications
- Web apps for all users should be able to authenticate and authorize such users at their first sign-in operations, where intranet web services must be of their focus
- Option # 1: Direct groups can be fetched from Active Directory on each user access to the target web app
- Option # 2: Find a way to fetch all the fingerprints of target users of relevant groups in a reasonable query load on Active Directory
- Option # 3: Hybrid solution with fingerprints mapped to roles for normal users and roles from Active Directory groups on demand as a fallback to achieve complete service levels
- Authenticate on verify callback via trusted certificate issuer(s)
- Authorize on request middleware via fingerprints assigned to role(s)
- Authorize as a fallback on request middleware via Active Directory group membership mapped to role(s)
- Prototyping LDAP accessor class: ldap_member_of.cc
- Basic synchronous search for
memberOf
attributes - Primitive fallback mechanism for synchronous search
- Basic asynchronous search for
memberOf
attributes- Note: In .net core, polling is used for async LDAP operations as callback interface is unavailable in OpenLDAP
- Basic synchronous search for
Underlying native libraries don't support callback-based function, so we will instead use polling and use a Stopwatch to track the timeout manually.
dummy item for indentationdummy item for indentation- Fallback mechanism for asynchronous search
- Caching of roles derived from group membership in Active Directory is essential for performance
- On Web Apps, it may NOT be trivial to map a user's
subject
in user certificate to Active DirectorydistinguishedName
- If
emailAddress
is included in thesubject
, it should be trivial - Is this simple conversion feasible?
subject
:CN=${Name},OU...
==distinguishedName
- If
Format-Json
came from https://stackoverflow.com/questions/56322993/proper-formating-of-json-using-powershell/56324939
function Get-ADGroupRoles {
param( $group, $roles, $result, $root )
if ($group.GetType().FullName -eq "System.String") {
[void]($result = @{});
[void]($roles = @());
[void]($root = Get-ADGroup $group);
[void](Get-ADGroupRoles $root $roles $result $root);
$result
}
else {
if ($root.distinguishedName -ne $group.distinguishedName) {
if ($roles.Count -eq 0) {
[void]($private:roles = @(($group.distinguishedName -replace "CN=([^,]*),.*", "`$1")));
[void]($result.add($group.distinguishedName,$private:roles.psobject.copy()));
}
else {
[void]($private:existingRoles = $result[$group.distinguishedName]);
if ($private:existingRoles) {
[void]($roles | ForEach-Object { $private:existingRoles += $_; });
[void]($result[$group.distinguishedName] = $private:existingRoles);
}
else {
[void]($result.add($group.distinguishedName, $roles.psobject.copy()));
}
[void]($private:roles = $roles.psobject.copy());
}
}
Get-ADGroupMember $group |
Where-Object { $_.objectClass -eq "group" } |
ForEach-Object { Get-ADGroupRoles $_ $private:roles $result $root }
}
}
Get-ADGroupRoles WebAppX_Users | ConvertTo-Json -Depth 5 | Format-Json
{
"CN=Interim_Group3,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
"Admin",
"User"
],
"CN=Admin,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
"Admin"
],
"CN=Interim_Group2,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
"User"
],
"CN=User,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
"User"
],
"CN=Interim_Group1,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
"User"
],
"CN=SecB_Users,OU=SectionB,OU=DevDepartment,DC=example,DC=local": [
"User"
],
"CN=SecA_Users,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
"User"
]
}
Group and User configurations in the above script
function Get-ADTree {
param( $group )
if ($group.GetType().FullName -eq "System.String") {
Get-ADTree (Get-ADGroup $group);
}
elseif ($group.ObjectClass -eq "group") {
@{$group.distinguishedName=(Get-ADGroupMember $group | ForEach-Object { Get-ADTree $_ })}
}
else {
$group.distinguishedName
}
}
Get-ADTree WebAppX_Users | ConvertTo-Json -Depth 7 | Format-Json
{
"CN=WebAppX_Users,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
{
"CN=Admin,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
"CN=SecA User1,OU=SectionA,OU=DevDepartment,DC=example,DC=local",
{
"CN=Interim_Group3,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
"CN=SecA User4,OU=SectionA,OU=DevDepartment,DC=example,DC=local",
"CN=SecA User5,OU=SectionA,OU=DevDepartment,DC=example,DC=local"
]
}
]
},
{
"CN=User,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
{
"CN=SecA_Users,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
"CN=SecA User1,OU=SectionA,OU=DevDepartment,DC=example,DC=local",
"CN=SecA User2,OU=SectionA,OU=DevDepartment,DC=example,DC=local",
"CN=SecA User3,OU=SectionA,OU=DevDepartment,DC=example,DC=local",
"CN=SecA User4,OU=SectionA,OU=DevDepartment,DC=example,DC=local",
"CN=SecA User5,OU=SectionA,OU=DevDepartment,DC=example,DC=local"
]
},
{
"CN=SecB_Users,OU=SectionB,OU=DevDepartment,DC=example,DC=local": [
"CN=SecB User1,OU=SectionB,OU=DevDepartment,DC=example,DC=local",
"CN=SecB User2,OU=SectionB,OU=DevDepartment,DC=example,DC=local"
]
},
{
"CN=Interim_Group1,OU=SectionA,OU=DevDepartment,DC=example,DC=local": {
"CN=Interim_Group2,OU=SectionA,OU=DevDepartment,DC=example,DC=local": {
"CN=Interim_Group3,OU=SectionA,OU=DevDepartment,DC=example,DC=local": [
"CN=SecA User4,OU=SectionA,OU=DevDepartment,DC=example,DC=local",
"CN=SecA User5,OU=SectionA,OU=DevDepartment,DC=example,DC=local"
]
}
}
}
]
}
]
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
List SHA1 fingerprints for JSON
userCertificate
propsPowershell to get SHA1 hash list of
userCertificate
for group members who have the attribute