Skip to content

Instantly share code, notes, and snippets.

@t2ym
Last active June 15, 2021 07:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save t2ym/9b80d6c41a99f3dee6136751b463e90d to your computer and use it in GitHub Desktop.
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
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_;
};
@t2ym
Copy link
Author

t2ym commented May 25, 2021

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
  • 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
    1. Authenticate on verify callback via trusted certificate issuer(s)
    2. Authorize on request middleware via fingerprints assigned to role(s)
    3. 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

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 indentation
    • dummy 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 Directory distinguishedName
    • If emailAddress is included in the subject, it should be trivial
    • Is this simple conversion feasible? subject: CN=${Name},OU... == distinguishedName
  • 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