You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@httpd.apache.org by Adam Cécile <ac...@le-vert.net> on 2024/01/25 18:13:01 UTC

[users@httpd] If statement against AUTHENTICATE_memberOf variable created by authnz_ldap

Hello,


I'm struggling with authnz_ldap configuration. What I'm trying to 
achieve is the following:

1. Authentication is done against Active Directory

2. Groups of user (memberOf) are retreived and X-PCC-Profile header is 
set depending on groups:

- If ADMIN is listed in groups, profile is set to admin

- If OPERATOR is listed in groups, profile is set to operator

- If VIEWER is listed in groups, profile is set to viewer

- If none of the above group is found, X-PCC-Profile header is not set


I came up with a configuration that works, but is ugly (imho) and I have 
to perform some fuzzy regex matching and I do not understand why, so I'm 
requesting your advice.

Configuration is the following:


Require ldap-group CN=ALLOWED,OU=Groups,DC=domain,DC=internal

RequestHeader add X-PCC-User "%{AUTHENTICATE_sAMAccountName}e"

# For debugging purpose, dump AUTHENTICATE_memberOf variable to unused 
X-PCC-Groups header

RequestHeader add X-PCC-Groups "%{AUTHENTICATE_memberOf}e"

RewriteEngine on
RewriteCond %{ENV:AUTHENTICATE_memberOf} "(^|; 
)CN=VIEWER,OU=Groups,DC=domain,DC=internal" [NC]
RewriteRule ".*" - [E=PCC_PROFILE:viewer,NE,NS]
RewriteCond %{ENV:AUTHENTICATE_memberOf} "(^|; 
)CN=OPERATOR,OU=Groups,DC=domain,DC=internal" [NC]
RewriteRule ".*" - [E=PCC_PROFILE:operator,NE,NS]
RewriteCond %{ENV:AUTHENTICATE_memberOf} "(^|; 
)CN=VIEWER,OU=Groups,DC=domain,DC=internal" [NC]
RewriteRule ".*" - [E=PCC_PROFILE:admin,NE,NS]

RequestHeader add X-PCC-Profile "%{PCC_PROFILE}e" "expr=-n 
%{ENV:PCC_PROFILE}"


For the record, here is how the debugging X-PCC-Groups header is seen by 
next "hop" (in this POC, apache is proxy-passing to NGINX which is 
configured to log all headers):

2024/01/25 17:46:14 [debug] 10006#10006: *147 http header: 
"X-PCC-Groups: CN=ANOTHERGROUP,OU=Groups,DC=domain,DC=internal; 
CN=ALLOWED,OU=Groups,DC=domain,DC=internal; 
CN=VIEWER,OU=Groups,DC=domain,DC=internal"



So the first question is: Is it normal that I have to use mod_rewrite to 
check for group membership ? I tried hundred of syntaxes with SetEnvIf 
or SetEnvIfExpr but I never managed to get it working. I'm not sure why 
but I guess it's somehow related to "race condition" (lazy evaluation) 
while evaluating environment variable, does it makes sense ?

Second question is: I cannot use "$" to make a proper regex matcher. If 
the group is not the last one, I can match it with ;.*$, if it is the 
last one, I should be able to match [...]DC=internal$, however that does 
not work. There's is one unknown character and I have no idea what it 
is. Matching with DC=internal.?$ works, so that's one SINGLE char... Any 
idea ?


Thanks in advance,

Best regards, Adam.


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
For additional commands, e-mail: users-help@httpd.apache.org


Re: [users@httpd] If statement against AUTHENTICATE_memberOf variable created by authnz_ldap

Posted by Adam Cecile <ac...@le-vert.net>.
On 1/26/24 18:13, Eric Covener wrote:
>> So the first question is: Is it normal that I have to use mod_rewrite to
>> check for group membership ? I tried hundred of syntaxes with SetEnvIf
>> or SetEnvIfExpr but I never managed to get it working. I'm not sure why
>> but I guess it's somehow related to "race condition" (lazy evaluation)
>> while evaluating environment variable, does it makes sense ?
> SetEnvIf[expr] is evaluated very early, long before authn/authz occurs
> and those environment variables can be filled out.
>
>> Second question is: I cannot use "$" to make a proper regex matcher. If
>> the group is not the last one, I can match it with ;.*$, if it is the
>> last one, I should be able to match [...]DC=internal$, however that does
>> not work. There's is one unknown character and I have no idea what it
>> is. Matching with DC=internal.?$ works, so that's one SINGLE char... Any
>> idea ?
>>
Hello,


Thanks a lot for your help. I ended up going for a completely different 
way because I found it was too messy... So here is the LUA script I use 
instead:

require 'apache2'

local admin_profiles = {
   'CN=Group1,OU=Groups,DC=domain,DC=com',
   'CG=Group2,DC=domain,DC=com',
}
local manager_profiles = {
   'CN=Group1,OU=Groups,DC=domain,DC=com',
   'CN=Group3,OU=Groups,DC=domain,DC=com',
}
local operator_profiles = {
}
local viewer_profiles = {
   'CN=Group4,OU=Groups,DC=domain,DC=com',
   'CN=Group5,OU=Groups,DC=domain,DC=com',
}

local function has_value_case_insensitive(tab, val)
     for index, value in ipairs(tab) do
         if value:lower() == val:lower() then
             return true
         end
     end
     return false
end

function authcheck_hook(r)
   -- Extract username and group list from Active Directory authentication
   local sam_account_name = r.subprocess_env['AUTHENTICATE_SAMACCOUNTNAME']
   local member_of = r.subprocess_env['AUTHENTICATE_MEMBEROF']

   -- Check if Active Directory variables are not set, if so, return 403 
and log error
   if (sam_account_name == nil or sam_account_name == '') then
     r:err("ActiveDirectory group to PCC profile LUA script called but 
AUTHENTICATE_SAMACCOUNTNAME was not set")
     return 403
   end
   if (member_of == nil or member_of == '') then
     r:err("ActiveDirectory group to PCC profile LUA script called but 
AUTHENTICATE_MEMBEROF was not set")
     return 403
   end

   -- Split group member ship into an array and match corresponding profile
   local member_of_split_regex = '([^;%s*]+)'
   local member_of_profiles = {}
   for member_of_entry in member_of:gmatch(member_of_split_regex) do
     local member_of_entry_profiles = {}
     if has_value_case_insensitive(admin_profiles, member_of_entry) then
       if not has_value_case_insensitive(member_of_entry_profiles, 
"admin") then
         table.insert(member_of_entry_profiles, "admin")
       end
       if not has_value_case_insensitive(member_of_profiles, "admin") then
         table.insert(member_of_profiles, "admin")
       end
     end
     if has_value_case_insensitive(manager_profiles, member_of_entry) then
       if not has_value_case_insensitive(member_of_entry_profiles, 
"manager") then
         table.insert(member_of_entry_profiles, "manager")
       end
       if not has_value_case_insensitive(member_of_profiles, "manager") then
         table.insert(member_of_profiles, "manager")
       end
     end
     if has_value_case_insensitive(operator_profiles, member_of_entry) then
       if not has_value_case_insensitive(member_of_entry_profiles, 
"operator") then
         table.insert(member_of_entry_profiles, "operator")
       end
       if not has_value_case_insensitive(member_of_profiles, "operator") 
then
         table.insert(member_of_profiles, "operator")
       end
     end
     if has_value_case_insensitive(viewer_profiles, member_of_entry) then
       if not has_value_case_insensitive(member_of_entry_profiles, 
"viewer") then
         table.insert(member_of_entry_profiles, "viewer")
       end
       if not has_value_case_insensitive(member_of_profiles, "viewer") then
         table.insert(member_of_profiles, "viewer")
       end
     end
     if next(member_of_entry_profiles) == nil then
       r:debug(("Username %s ActiveDirectory group %s gives no PCC 
privileges"):format(sam_account_name, member_of_entry))
     else
       r:debug(("Username %s ActiveDirectory group %s gives PCC 
privileges: %s"):format(sam_account_name, member_of_entry, 
table.concat(member_of_entry_profiles, ', ')))
     end
   end

   -- Compare all matched group resulting profile and get the one with 
highest privileges
   if has_value_case_insensitive(member_of_profiles, "admin") then
     r:info(("Username %s authorized with PCC admin 
privileges"):format(sam_account_name, member_of_entry))
     r.headers_in['X-PCC-Group'] = "admin"
   elseif has_value_case_insensitive(member_of_profiles, "manager") then
     r:info(("Username %s authorized with PCC manager 
privileges"):format(sam_account_name, member_of_entry))
     r.headers_in['X-PCC-Group'] = "manager"
   elseif has_value_case_insensitive(member_of_profiles, "operator") then
     r:err(("Username %s authorized with PCC operator 
privileges"):format(sam_account_name, member_of_entry))
     r.headers_in['X-PCC-Group'] = "operator"
   elseif has_value_case_insensitive(member_of_profiles, "viewer") then
     r:err(("Username %s authorized with PCC viewer 
privileges"):format(sam_account_name, member_of_entry))
     r.headers_in['X-PCC-Group'] = "viewer"
   else
     r:err(("Username %s ActiveDirectory groups did not give ANY PCC 
privileges, it should have NOT been authorized to 
login"):format(sam_account_name))
     return 403
   end

   return apache2.OK
end

Which is called from config using:

LuaHookFixups /etc/apache2/snippets/active_directory_to_pcc_profile.lua 
authcheck_hook


It seems to be working as expected, and is also working with my 
auth_form / cookie_session setup, which is great !

Any comment is welcome is you see something borked...


Regards, Adam.

Re: [users@httpd] If statement against AUTHENTICATE_memberOf variable created by authnz_ldap

Posted by Eric Covener <co...@gmail.com>.
> So the first question is: Is it normal that I have to use mod_rewrite to
> check for group membership ? I tried hundred of syntaxes with SetEnvIf
> or SetEnvIfExpr but I never managed to get it working. I'm not sure why
> but I guess it's somehow related to "race condition" (lazy evaluation)
> while evaluating environment variable, does it makes sense ?

SetEnvIf[expr] is evaluated very early, long before authn/authz occurs
and those environment variables can be filled out.

>
> Second question is: I cannot use "$" to make a proper regex matcher. If
> the group is not the last one, I can match it with ;.*$, if it is the
> last one, I should be able to match [...]DC=internal$, however that does
> not work. There's is one unknown character and I have no idea what it
> is. Matching with DC=internal.?$ works, so that's one SINGLE char... Any
> idea ?
>

You could send it out as a response header for debugging, or maybe
observe it with rewrite:trace8 in the error_log

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
For additional commands, e-mail: users-help@httpd.apache.org