added nextcloud_saml

root 2021-05-28 02:29:26 +02:00
parent 42932d55a0
commit a6826ec8c3
8 changed files with 370 additions and 103 deletions

View File

@ -12,7 +12,7 @@ COPY admin/docker/requirements.pip3 /requirements.pip3
RUN pip3 install --no-cache-dir -r requirements.pip3 RUN pip3 install --no-cache-dir -r requirements.pip3
RUN apk del .build_deps RUN apk del .build_deps
RUN apk add --no-cache curl py3-yaml yarn libpq RUN apk add --no-cache curl py3-yaml yarn libpq openssl
# SSH configuration # SSH configuration
ARG SSH_ROOT_PWD ARG SSH_ROOT_PWD

View File

@ -215,101 +215,10 @@ class Keycloak():
return {'name':rsa_key['kid'],'certificate':rsa_key['certificate']} return {'name':rsa_key['kid'],'certificate':rsa_key['certificate']}
## CLIENTS ## CLIENTS
def add_moodle_client(self): def delete_client(self,clientid):
self.connect() self.connect()
demo={ return self.keycloak_admin.delete_client(clientid)
"id" : "a92d5417-92b6-4678-9cb9-51bc0edcee8c",
"clientId" : "https://moodle."+app.config['DOMAIN']+"/auth/saml2/sp/metadata.php", def add_client(self,client):
"surrogateAuthRequired" : False, self.connect()
"enabled" : True, return self.keycloak_admin.create_client(client)
"alwaysDisplayInConsole" : False,
"clientAuthenticatorType" : "client-secret",
"redirectUris" : [ "https://moodle."+app.config['DOMAIN']+"/auth/saml2/sp/saml2-acs.php/moodle."+app.config['DOMAIN']+"" ],
"webOrigins" : [ "https://moodle."+app.config['DOMAIN']+"" ],
"notBefore" : 0,
"bearerOnly" : False,
"consentRequired" : False,
"standardFlowEnabled" : True,
"implicitFlowEnabled" : False,
"directAccessGrantsEnabled" : False,
"serviceAccountsEnabled" : False,
"publicClient" : False,
"frontchannelLogout" : True,
"protocol" : "saml",
"attributes" : {
"saml.force.post.binding" : True,
"saml.encrypt" : True,
"saml_assertion_consumer_url_post" : "https://moodle."+app.config['DOMAIN']+"/auth/saml2/sp/saml2-acs.php/moodle."+app.config['DOMAIN']+"",
"saml.server.signature" : True,
"saml.server.signature.keyinfo.ext" : False,
"saml.signing.certificate" : app.config['SP_CRT'],
"saml_single_logout_service_url_redirect" : "https://moodle."+app.config['DOMAIN']+"/auth/saml2/sp/saml2-logout.php/moodle."+app.config['DOMAIN']+"",
"saml.signature.algorithm" : "RSA_SHA256",
"saml_force_name_id_format" : False,
"saml.client.signature" : True,
"saml.encryption.certificate" : app.config['SP_PEM'],
"saml.authnstatement" : True,
"saml_name_id_format" : "username",
"saml_signature_canonicalization_method" : "http://www.w3.org/2001/10/xml-exc-c14n#"
},
"authenticationFlowBindingOverrides" : { },
"fullScopeAllowed" : True,
"nodeReRegistrationTimeout" : -1,
"protocolMappers" : [ {
"id" : "9296daa3-4fc4-4b80-b007-5070f546ae13",
"name" : "X500 surname",
"protocol" : "saml",
"protocolMapper" : "saml-user-property-mapper",
"consentRequired" : False,
"config" : {
"attribute.nameformat" : "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
"user.attribute" : "lastName",
"friendly.name" : "surname",
"attribute.name" : "urn:oid:2.5.4.4"
}
}, {
"id" : "ccecf6e4-d20a-4211-b67c-40200a6b2c5d",
"name" : "username",
"protocol" : "saml",
"protocolMapper" : "saml-user-property-mapper",
"consentRequired" : False,
"config" : {
"attribute.nameformat" : "Basic",
"user.attribute" : "username",
"friendly.name" : "username",
"attribute.name" : "username"
}
}, {
"id" : "53858403-eba2-4f6d-81d0-cced700b5719",
"name" : "X500 givenName",
"protocol" : "saml",
"protocolMapper" : "saml-user-property-mapper",
"consentRequired" : False,
"config" : {
"attribute.nameformat" : "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
"user.attribute" : "firstName",
"friendly.name" : "givenName",
"attribute.name" : "urn:oid:2.5.4.42"
}
}, {
"id" : "20034db5-1d0e-4e66-b815-fb0440c6d1e2",
"name" : "X500 email",
"protocol" : "saml",
"protocolMapper" : "saml-user-property-mapper",
"consentRequired" : False,
"config" : {
"attribute.nameformat" : "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
"user.attribute" : "email",
"friendly.name" : "email",
"attribute.name" : "urn:oid:1.2.840.113549.1.9.1"
}
} ],
"defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ],
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ],
"access" : {
"view" : True,
"configure" : True,
"manage" : True
}
}
return self.keycloak_admin.create_client(demo)

View File

@ -15,7 +15,6 @@ class Nextcloud():
url="https://nextcloud."+app.config['DOMAIN'], url="https://nextcloud."+app.config['DOMAIN'],
username=os.environ['NEXTCLOUD_ADMIN_USER'], username=os.environ['NEXTCLOUD_ADMIN_USER'],
password=os.environ['NEXTCLOUD_ADMIN_PASSWORD'], password=os.environ['NEXTCLOUD_ADMIN_PASSWORD'],
realm='master',
verify=True): verify=True):
self.verify_cert=verify self.verify_cert=verify
@ -259,6 +258,8 @@ class Nextcloud():
try: try:
result = json.loads(self._request('GET',url)) result = json.loads(self._request('GET',url))
if result['ocs']['meta']['statuscode'] == 100: return [g for g in result['ocs']['data']['groups']] if result['ocs']['meta']['statuscode'] == 100: return [g for g in result['ocs']['data']['groups']]
import pprint
pprint.pprint(result)
raise ProviderOpError raise ProviderOpError
except: except:
log.error(traceback.format_exc()) log.error(traceback.format_exc())

View File

@ -84,7 +84,8 @@ class MoodleSaml():
self.activate_saml_plugin() self.activate_saml_plugin()
self.set_moodle_saml_plugin() self.set_moodle_saml_plugin()
self.set_keycloak_moodle_saml_plugin() self.delete_keycloak_moodle_saml_plugin()
self.add_keycloak_moodle_saml()
def activate_saml_plugin(self): def activate_saml_plugin(self):
## After you need to purge moodle caches: /var/www/html # php admin/cli/purge_caches.php ## After you need to purge moodle caches: /var/www/html # php admin/cli/purge_caches.php
@ -104,6 +105,11 @@ class MoodleSaml():
keycloak.add_moodle_client() keycloak.add_moodle_client()
keycloak=None keycloak=None
def delete_keycloak_moodle_saml_plugin(self):
keycloak=Keycloak()
keycloak.delete_client('a92d5417-92b6-4678-9cb9-51bc0edcee8c')
keycloak=None
def set_moodle_saml_plugin(self): def set_moodle_saml_plugin(self):
config={'idpmetadata': self.parse_idp_metadata(), config={'idpmetadata': self.parse_idp_metadata(),
'certs_locked': '1', 'certs_locked': '1',
@ -121,5 +127,107 @@ class MoodleSaml():
self.pg.update("""INSERT INTO "mdl_auth_saml2_idps" ("metadataurl", "entityid", "activeidp", "defaultidp", "adminidp", "defaultname", "displayname", "logo", "alias", "whitelist") VALUES self.pg.update("""INSERT INTO "mdl_auth_saml2_idps" ("metadataurl", "entityid", "activeidp", "defaultidp", "adminidp", "defaultname", "displayname", "logo", "alias", "whitelist") VALUES
('xml', 'https://sso.%s/auth/realms/master', 1, 0, 0, 'Login via SAML2', '', NULL, NULL, NULL);""" % (os.environ['DOMAIN'])) ('xml', 'https://sso.%s/auth/realms/master', 1, 0, 0, 'Login via SAML2', '', NULL, NULL, NULL);""" % (os.environ['DOMAIN']))
def add_keycloak_moodle_saml(self):
client={
"id" : "a92d5417-92b6-4678-9cb9-51bc0edcee8c",
"name": "moodle",
"description": "moodle",
"clientId" : "https://moodle."+os.environ['DOMAIN']+"/auth/saml2/sp/metadata.php",
"surrogateAuthRequired" : False,
"enabled" : True,
"alwaysDisplayInConsole" : False,
"clientAuthenticatorType" : "client-secret",
"redirectUris" : [ "https://moodle."+os.environ['DOMAIN']+"/auth/saml2/sp/saml2-acs.php/moodle."+os.environ['DOMAIN']+"" ],
"webOrigins" : [ "https://moodle."+os.environ['DOMAIN']+"" ],
"notBefore" : 0,
"bearerOnly" : False,
"consentRequired" : False,
"standardFlowEnabled" : True,
"implicitFlowEnabled" : False,
"directAccessGrantsEnabled" : False,
"serviceAccountsEnabled" : False,
"publicClient" : False,
"frontchannelLogout" : True,
"protocol" : "saml",
"attributes" : {
"saml.force.post.binding" : True,
"saml.encrypt" : True,
"saml_assertion_consumer_url_post" : "https://moodle."+os.environ['DOMAIN']+"/auth/saml2/sp/saml2-acs.php/moodle."+os.environ['DOMAIN']+"",
"saml.server.signature" : True,
"saml.server.signature.keyinfo.ext" : False,
"saml.signing.certificate" : app['config']['SP_CRT'],
"saml_single_logout_service_url_redirect" : "https://moodle."+os.environ['DOMAIN']+"/auth/saml2/sp/saml2-logout.php/moodle."+os.environ['DOMAIN']+"",
"saml.signature.algorithm" : "RSA_SHA256",
"saml_force_name_id_format" : False,
"saml.client.signature" : False,
"saml.encryption.certificate" : app['config']['SP_PEM'],
"saml.authnstatement" : True,
"saml_name_id_format" : "username",
"saml_signature_canonicalization_method" : "http://www.w3.org/2001/10/xml-exc-c14n#"
},
"authenticationFlowBindingOverrides" : { },
"fullScopeAllowed" : True,
"nodeReRegistrationTimeout" : -1,
"protocolMappers" : [ {
"id" : "9296daa3-4fc4-4b80-b007-5070f546ae13",
"name" : "X500 surname",
"protocol" : "saml",
"protocolMapper" : "saml-user-property-mapper",
"consentRequired" : False,
"config" : {
"attribute.nameformat" : "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
"user.attribute" : "lastName",
"friendly.name" : "surname",
"attribute.name" : "urn:oid:2.5.4.4"
}
}, {
"id" : "ccecf6e4-d20a-4211-b67c-40200a6b2c5d",
"name" : "username",
"protocol" : "saml",
"protocolMapper" : "saml-user-property-mapper",
"consentRequired" : False,
"config" : {
"attribute.nameformat" : "Basic",
"user.attribute" : "username",
"friendly.name" : "username",
"attribute.name" : "username"
}
}, {
"id" : "53858403-eba2-4f6d-81d0-cced700b5719",
"name" : "X500 givenName",
"protocol" : "saml",
"protocolMapper" : "saml-user-property-mapper",
"consentRequired" : False,
"config" : {
"attribute.nameformat" : "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
"user.attribute" : "firstName",
"friendly.name" : "givenName",
"attribute.name" : "urn:oid:2.5.4.42"
}
}, {
"id" : "20034db5-1d0e-4e66-b815-fb0440c6d1e2",
"name" : "X500 email",
"protocol" : "saml",
"protocolMapper" : "saml-user-property-mapper",
"consentRequired" : False,
"config" : {
"attribute.nameformat" : "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
"user.attribute" : "email",
"friendly.name" : "email",
"attribute.name" : "urn:oid:1.2.840.113549.1.9.1"
}
} ],
"defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ],
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ],
"access" : {
"view" : True,
"configure" : True,
"manage" : True
}
}
keycloak=Keycloak()
keycloak.add_client(client)
keycloak=None
m=MoodleSaml() m=MoodleSaml()

244
admin/src/nextcloud_saml.py Normal file
View File

@ -0,0 +1,244 @@
#!/usr/bin/env python
# coding=utf-8
import time, os
from datetime import datetime, timedelta
import pprint
import logging as log
import traceback
import yaml, json
import psycopg2
from admin.lib.postgres import Postgres
from admin.lib.keycloak import Keycloak
import string, random
app={}
app['config']={}
class NextcloudSaml():
def __init__(self):
ready=False
while not ready:
try:
self.pg=Postgres('isard-apps-postgresql','nextcloud',os.environ['NEXTCLOUD_POSTGRES_USER'],os.environ['NEXTCLOUD_POSTGRES_PASSWORD'])
ready=True
except:
log.warning('Could not connect to nextcloud database. Retrying...')
time.sleep(2)
log.info('Connected to nextcloud database.')
ready=False
while not ready:
try:
with open(os.path.join("./saml_certs/public.cert"),"r") as crt:
app['config']['PUBLIC_CERT']=crt.read()
ready=True
except IOError:
log.warning('Could not get public certificate to be used in nextcloud. Retrying...')
log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert')
time.sleep(2)
except:
log.error(traceback.format_exc())
log.info('Got moodle srt certificate to be used in nextcloud.')
ready=False
while not ready:
try:
with open(os.path.join("./saml_certs/private.key"),"r") as pem:
app['config']['PRIVATE_KEY']=pem.read()
ready=True
except IOError:
log.warning('Could not get private key to be used in nextcloud. Retrying...')
log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert')
time.sleep(2)
log.info('Got moodle pem certificate to be used in nextcloud.')
# ## This seems related to the fact that the certificate generated the first time does'nt work.
# ## And when regenerating the certificate de privatekeypass seems not to be used and instead it
# ## will use always this code as filename: 0f635d0e0f3874fff8b581c132e6c7a7
# ## As this bug I'm not able to solve, the process is:
# ## 1.- Bring up moodle and regenerate certificates on saml2 plugin in plugins-authentication
# ## 2.- Execute this script
# ## 3.- Cleanup all caches in moodle (Development tab)
# # with open(os.path.join("./moodledata/saml2/"+os.environ['NEXTCLOUD_SAML_PRIVATEKEYPASS'].replace("moodle."+os.environ['DOMAIN'],'')+'.idp.xml'),"w") as xml:
# # xml.write(self.parse_idp_metadata())
# with open(os.path.join("./moodledata/saml2/0f635d0e0f3874fff8b581c132e6c7a7.idp.xml"),"w") as xml:
# xml.write(self.parse_idp_metadata())
try:
self.reset_saml()
except:
print('Error resetting saml on nextcloud')
try:
self.delete_keycloak_nextcloud_saml_plugin()
except:
print('Error resetting saml on keycloak')
try:
self.set_nextcloud_saml_plugin()
except:
print('Error adding saml on nextcloud')
try:
self.add_keycloak_nextcloud_saml()
except:
print('Error adding saml on keycloak')
# def activate_saml_plugin(self):
# ## After you need to purge moodle caches: /var/www/html # php admin/cli/purge_caches.php
# return self.pg.update("""UPDATE "mdl_config" SET value = 'email,saml2' WHERE "name" = 'auth'""")
# def get_privatekey_pass(self):
# return self.pg.select("""SELECT * FROM "mdl_config" WHERE "name" = 'siteidentifier'""")[0][2]
def parse_idp_cert(self):
keycloak=Keycloak()
rsa=keycloak.get_server_rsa_key()
keycloak=None
return rsa['certificate']
def set_keycloak_nextcloud_saml_plugin(self):
keycloak=Keycloak()
keycloak.add_nextcloud_client()
keycloak=None
def delete_keycloak_nextcloud_saml_plugin(self):
keycloak=Keycloak()
keycloak.delete_client('bef873f0-2079-4876-8657-067de27d01b7')
keycloak=None
def set_nextcloud_saml_plugin(self):
self.pg.update("""INSERT INTO "oc_appconfig" ("appid", "configkey", "configvalue") VALUES
('user_saml', 'general-uid_mapping', 'username'),
('user_saml', 'type', 'saml'),
('user_saml', 'sp-privateKey', '%s'),
('user_saml', 'saml-attribute-mapping-email_mapping', 'email'),
('user_saml', 'saml-attribute-mapping-group_mapping', 'Role'),
('user_saml', 'idp-entityId', 'https://sso.%s/auth/realms/master'),
('user_saml', 'idp-singleSignOnService.url', 'https://sso.%s/auth/realms/master/protocol/saml'),
('user_saml', 'idp-x509cert', '%s'),
('user_saml', 'security-authnRequestsSigned', '1'),
('user_saml', 'security-logoutRequestSigned', '1'),
('user_saml', 'security-logoutResponseSigned', '1'),
('user_saml', 'security-wantMessagesSigned', '1'),
('user_saml', 'security-wantAssertionsSigned', '1'),
('user_saml', 'general-idp0_display_name', 'SAML Login'),
('user_saml', 'sp-x509cert', '%s'),
('user_saml', 'idp-singleLogoutService.url', 'https://sso.%s/auth/realms/master/protocol/saml');""" % (app['config']['PRIVATE_KEY'],os.environ['DOMAIN'],os.environ['DOMAIN'],self.parse_idp_cert(),app['config']['PUBLIC_CERT'],os.environ['DOMAIN']))
def reset_saml(self):
cfg_list=['general-uid_mapping',
'sp-privateKey',
'saml-attribute-mapping-email_mapping',
'saml-attribute-mapping-group_mapping',
'idp-entityId',
'idp-singleSignOnService.url',
'idp-x509cert',
'security-authnRequestsSigned',
'security-logoutRequestSigned',
'security-logoutResponseSigned',
'security-wantMessagesSigned',
'security-wantAssertionsSigned',
'general-idp0_display_name',
'type',
'sp-x509cert',
'idp-singleLogoutService.url']
for cfg in cfg_list:
self.pg.update("""DELETE FROM "oc_appconfig" WHERE appid = 'user_saml' AND configkey = '%s'""" % (cfg))
def add_keycloak_nextcloud_saml(self):
client={"id" : "bef873f0-2079-4876-8657-067de27d01b7",
"name": "nextcloud",
"description": "nextcloud",
"clientId" : "https://nextcloud."+os.environ['DOMAIN']+"/apps/user_saml/saml/metadata",
"surrogateAuthRequired" : False,
"enabled" : True,
"alwaysDisplayInConsole" : False,
"clientAuthenticatorType" : "client-secret",
"redirectUris" : [ "https://nextcloud."+os.environ['DOMAIN']+"/apps/user_saml/saml/acs" ],
"webOrigins" : [ "https://nextcloud."+os.environ['DOMAIN'] ],
"notBefore" : 0,
"bearerOnly" : False,
"consentRequired" : False,
"standardFlowEnabled" : True,
"implicitFlowEnabled" : False,
"directAccessGrantsEnabled" : False,
"serviceAccountsEnabled" : False,
"publicClient" : False,
"frontchannelLogout" : True,
"protocol" : "saml",
"attributes" : {
"saml.assertion.signature" : True,
"saml.force.post.binding" : True,
"saml_assertion_consumer_url_post" : "https://nextcloud."+os.environ['DOMAIN']+"/apps/user_saml/saml/acs",
"saml.server.signature" : True,
"saml.server.signature.keyinfo.ext" : False,
"saml.signing.certificate" : app['config']['PUBLIC_CERT'],
"saml_single_logout_service_url_redirect" : "https://nextcloud."+os.environ['DOMAIN']+"/apps/user_saml/saml/sls",
"saml.signature.algorithm" : "RSA_SHA256",
"saml_force_name_id_format" : False,
"saml.client.signature" : False,
"saml.authnstatement" : True,
"saml_name_id_format" : "username",
"saml_signature_canonicalization_method" : "http://www.w3.org/2001/10/xml-exc-c14n#"
},
"authenticationFlowBindingOverrides" : { },
"fullScopeAllowed" : True,
"nodeReRegistrationTimeout" : -1,
"protocolMappers" : [ {
"id" : "e8e4acff-da2b-46aa-8bdb-ba42171671d6",
"name" : "username",
"protocol" : "saml",
"protocolMapper" : "saml-user-attribute-mapper",
"consentRequired" : False,
"config" : {
"attribute.nameformat" : "Basic",
"user.attribute" : "username",
"friendly.name" : "username",
"attribute.name" : "username"
}
}, {
"id" : "28206b59-757b-4e3c-81cb-0b6053b1fd3d",
"name" : "email",
"protocol" : "saml",
"protocolMapper" : "saml-user-property-mapper",
"consentRequired" : False,
"config" : {
"attribute.nameformat" : "Basic",
"user.attribute" : "email",
"friendly.name" : "email",
"attribute.name" : "email"
}
}, {
"id" : "e51e04b9-f71a-42de-819e-dd9285246ada",
"name" : "Roles",
"protocol" : "saml",
"protocolMapper" : "saml-role-list-mapper",
"consentRequired" : False,
"config" : {
"single" : True,
"attribute.nameformat" : "Basic",
"friendly.name" : "Roles",
"attribute.name" : "Roles"
}
} ],
"defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ],
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ],
"access" : {
"view" : True,
"configure" : True,
"manage" : True
}
}
keycloak=Keycloak()
keycloak.add_client(client)
keycloak=None
n=NextcloudSaml()

View File

@ -18,9 +18,10 @@ services:
volumes: volumes:
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- ${BUILD_ROOT_PATH}/admin/src:/admin # Revome in production - ${BUILD_ROOT_PATH}/admin/src:/admin # Revome in production
- ${BUILD_ROOT_PATH}/custom:/admin/custom #:ro in production - ${BUILD_ROOT_PATH}/../custom:/admin/custom #:ro in production
- ${DATA_FOLDER}/avatars:/admin/avatars:ro - ${DATA_FOLDER}/avatars:/admin/avatars:rw
- ${DATA_FOLDER}/moodle/saml2:/admin/moodledata/saml2:rw - ${DATA_FOLDER}/moodle/saml2:/admin/moodledata/saml2:rw
- ${DATA_FOLDER}/saml_certs:/admin/saml_certs:rw
env_file: env_file:
- .env - .env
environment: environment:

View File

@ -2,7 +2,7 @@
set -e set -e
# Set debug path password # Set debug path password
PASSWD=$(python3 -c 'import os,crypt,getpass; print(crypt.crypt(os.environ["IPA_ADMIN_PWD"], crypt.mksalt(crypt.METHOD_SHA512)))') PASSWD=$(python3 -c 'import os,crypt,getpass; print(crypt.crypt(os.environ["ADMINAPP_PASSWORD"], crypt.mksalt(crypt.METHOD_SHA512)))')
sed -i "/^ user admin password/c\ user admin password $PASSWD" /usr/local/etc/haproxy/haproxy.cfg sed -i "/^ user admin password/c\ user admin password $PASSWD" /usr/local/etc/haproxy/haproxy.cfg
LETSENCRYPT_DOMAIN="$DOMAIN" letsencrypt.sh LETSENCRYPT_DOMAIN="$DOMAIN" letsencrypt.sh

View File

@ -7,6 +7,8 @@ global
log 127.0.0.1 local0 log 127.0.0.1 local0
tune.ssl.default-dh-param 2048 tune.ssl.default-dh-param 2048
h1-case-adjust content-type Content-Type h1-case-adjust content-type Content-Type
h1-case-adjust content-encoding Content-Encoding
h1-case-adjust transfer-encoding Transfer-Encoding
defaults defaults
mode http mode http
@ -85,6 +87,8 @@ backend be_sso
backend be_admin backend be_admin
mode http mode http
acl authorized http_auth(AuthUsers)
http-request auth realm AuthUsers unless authorized
acl existing-x-forwarded-host req.hdr(X-Forwarded-Host) -m found acl existing-x-forwarded-host req.hdr(X-Forwarded-Host) -m found
acl existing-x-forwarded-proto req.hdr(X-Forwarded-Proto) -m found acl existing-x-forwarded-proto req.hdr(X-Forwarded-Proto) -m found
http-request add-header X-Forwarded-Host %[req.hdr(Host)] unless existing-x-forwarded-host http-request add-header X-Forwarded-Host %[req.hdr(Host)] unless existing-x-forwarded-host