{"id":14984422,"url":"https://github.com/ansibleguy/infra_haproxy","last_synced_at":"2025-04-10T20:32:37.208Z","repository":{"id":237557750,"uuid":"794728195","full_name":"ansibleguy/infra_haproxy","owner":"ansibleguy","description":"Ansible Role to provision HAProxy Community (with ACME, GeoIP and some WAF-Features)","archived":false,"fork":false,"pushed_at":"2025-04-10T09:54:37.000Z","size":244,"stargazers_count":7,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"latest","last_synced_at":"2025-04-10T10:54:17.607Z","etag":null,"topics":["acme","ansible","ansible-role","coraza","coraza-waf","fingerprint","fingerprinting","geoip","geoip-lookup","haproxy","iac","infrastructure-as-code","letsencrypt","letsencrypt-certificates","nac","network-as-code","owasp","waf"],"latest_commit_sha":null,"homepage":"","language":"Jinja","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ansibleguy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"ko_fi":"ansible0guy","github":"ansibleguy"}},"created_at":"2024-05-01T20:32:56.000Z","updated_at":"2025-04-10T09:54:42.000Z","dependencies_parsed_at":"2024-06-01T12:25:01.995Z","dependency_job_id":"b43f075f-4c5b-4926-bbf3-02651ae79930","html_url":"https://github.com/ansibleguy/infra_haproxy","commit_stats":{"total_commits":68,"total_committers":2,"mean_commits":34.0,"dds":"0.014705882352941124","last_synced_commit":"a8f7ea053d2a8b1101a1bfc28ee8a22db43185f2"},"previous_names":["ansibleguy/infra_haproxy"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ansibleguy%2Finfra_haproxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ansibleguy%2Finfra_haproxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ansibleguy%2Finfra_haproxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ansibleguy%2Finfra_haproxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ansibleguy","download_url":"https://codeload.github.com/ansibleguy/infra_haproxy/tar.gz/refs/heads/latest","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248290041,"owners_count":21078923,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acme","ansible","ansible-role","coraza","coraza-waf","fingerprint","fingerprinting","geoip","geoip-lookup","haproxy","iac","infrastructure-as-code","letsencrypt","letsencrypt-certificates","nac","network-as-code","owasp","waf"],"created_at":"2024-09-24T14:09:00.986Z","updated_at":"2025-04-10T20:32:37.170Z","avatar_url":"https://github.com/ansibleguy.png","language":"Jinja","readme":"\u003ca href=\"https://www.haproxy.com\"\u003e\n\u003cimg src=\"https://www.haproxy.com/assets/legal/web-logo.png\" alt=\"HAProxy Logo\" width=\"300\"/\u003e\n\u003c/a\u003e\n\n# Ansible Role - HAProxy Community (with ACME, GeoIP, WAF-Integration)\n\nRole to deploy HAProxy (*Focus on the Community Version*)\n\nI think the `frontend` =\u003e `route` =\u003e `backend` abstraction implemented by this Role is very nice to work with. Please [give me some Feedback](https://github.com/ansibleguy/infra_haproxy/discussions)!\n\nIf you are interested to integrate a full-fledged WAF into HAProxy =\u003e [check out the Coraza-WAF](https://github.com/ansibleguy/haproxy_waf_coraza)!\n\n\n[![Lint](https://github.com/ansibleguy/infra_haproxy/actions/workflows/lint.yml/badge.svg)](https://github.com/ansibleguy/infra_haproxy/actions/workflows/lint.yml)\n[![Ansible Galaxy](https://badges.ansibleguy.net/galaxy.badge.svg)](https://galaxy.ansible.com/ui/standalone/roles/ansibleguy/infra_haproxy)\n\n**Molecule Integration-Tests**:\n\n* Status: [![Molecule Test Status](https://badges.ansibleguy.net/infra_haproxy.molecule.svg)](https://github.com/ansibleguy/_meta_cicd/blob/latest/templates/usr/local/bin/cicd/molecule.sh.j2) |\n[![Functional-Tests](https://github.com/ansibleguy/infra_haproxy/actions/workflows/integration_test_result.yml/badge.svg)](https://github.com/ansibleguy/infra_haproxy/actions/workflows/integration_test_result.yml)\n* Logs: [API](https://ci.ansibleguy.net/api/job/ansible-test-molecule-infra_haproxy/logs?token=2b7bba30-9a37-4b57-be8a-99e23016ce70\u0026lines=1000) | [Short](https://badges.ansibleguy.net/log/molecule_infra_haproxy_test_short.log) | [Full](https://badges.ansibleguy.net/log/molecule_infra_haproxy_test.log)\n\nInternal CI: [Tester Role](https://github.com/ansibleguy/_meta_cicd) | [Jobs API](https://github.com/O-X-L/github-self-hosted-jobs-systemd)\n\n**Tested:**\n* Debian 12\n\n----\n\n## Install\n\n```bash\n# latest\nansible-galaxy role install git+https://github.com/ansibleguy/infra_haproxy\n\n# from galaxy\nansible-galaxy install ansibleguy.infra_haproxy\n\n# or to custom role-path\nansible-galaxy install ansibleguy.infra_haproxy --roles-path ./roles\n```\n\n----\n\n### Roadmap\n\n* Security\n  * Basic rate limit (GET/HEAD and POST/PUT/DELETE separated)\n  * Generic client fingerprint\n* 'Interface' for Dict to Map-File translation/creation\n* Option to easily Download \u0026 Integrate IPLists (*like Tor Exit nodes*)\n* Easy way to override the default error-files\n\n----\n\n## Usage\n\n### Examples\n\nHere some detailed config examples and their results:\n\n* [Example ACME](https://github.com/ansibleguy/infra_haproxy/blob/latest/ExampleAcme.md)\n* [Example GeoIP](https://github.com/ansibleguy/infra_haproxy/blob/latest/ExampleGeoIP.md)\n* [Example WAF](https://github.com/ansibleguy/infra_haproxy/blob/latest/ExampleWAF.md)\n* [Example Coraza-WAF](https://github.com/ansibleguy/infra_haproxy/blob/latest/ExampleCorazaWAF.md)\n* [Example TCP](https://github.com/ansibleguy/infra_haproxy/blob/latest/ExampleTCP.md)\n\n### Config\n\n**Minimal example**\n\n```yaml\nhaproxy:\n  acme:\n    enable: true\n    email: 'webmaster@template.ansibleguy.net'\n\n  frontends:\n    fe_web:\n      bind: ['[::]:80 v4v6', '[::]:443 v4v6 ssl']\n      acme:\n        enable: true\n\n      routes:\n        be_intern:\n          domains: ['app.template.ansibleguy.net']\n\n      default_backend: 'be_fallback'\n\n  backends:\n    be_intern:\n      servers:\n        - 'srv-1 192.168.10.11:80'\n        - 'srv-2 192.168.10.12:80'\n\n    be_fallback:\n      lines: 'http-request redirect code 302 location https://github.com/ansibleguy'\n```\n\n----\n\nDefine the config as needed:\n\n```yaml\nhaproxy:\n  version: '2.8'\n  acme:\n    enable: true\n    email: 'webmaster@template.ansibleguy.net'\n\n  # FRONTENDS\n  frontends:\n    fe_web:\n      bind: ['[::]:80 v4v6', '[::]:443 v4v6 ssl']\n      acme:\n        enable: true\n        domains: ['app.template.ansibleguy.net']  # domains from routes will also be added\n\n      routes:\n        be_app01:\n          domains: ['app01.template.ansibleguy.net', 'hello.template.ansibleguy.net']\n\n      # define raw config sections/lines to add\n      lines:\n        section1:\n          - ...\n\n      default_backend: 'be_fallback'\n\n    fe_dbs:\n      mode: 'tcp'\n      default_backend: 'be_db'\n\n    fe_restricted:\n      bind: ['[::]:8080 v4v6', '[::]:8443 v4v6 ssl crt /etc/myapp/mycert.pem']\n\n      geoip:\n        enable: true\n\n      security:\n        restrict_methods: true\n        allow_only_methods: ['HEAD', 'GET', 'POST']\n        fingerprint_ssl: true  # create and log the JA3 SSL-fingerprint of clients\n        \n        # very basic filtering of bad bots based on user-agent matching\n        block_script_bots: true\n        block_bad_crawler_bots: true\n\n      routes:\n        be_app02:\n          filter_country: ['AT', 'DE', 'CH']\n          # filter_ip: ['10.0.0.0/8']\n          domains: ['app01.template.ansibleguy.net', 'hello.template.ansibleguy.net']\n\n      # define raw config sections/lines to add\n      lines:\n        section1:\n          - ...\n\n      default_backend: 'be_fallback'\n\n  # BACKENDS\n  backends:\n    be_app01:\n      servers:\n        - 'app01-1 10.0.1.1:80'\n        - 'app01-2 10.0.1.2:80'\n\n      check_uri: '/health'\n      check_expect: 'status 200'\n\n    be_app02:\n      security:\n        # very basic filtering of bad bots based on user-agent matching\n        block_script_bots: true\n        block_bad_crawler_bots: true\n\n      ssl: true\n      ssl_verify: 'none'  # default; example: 'required ca-file /etc/ssl/certs/my_ca.crt verifyhost host01.intern'\n      servers:\n        - 'app02-1 10.0.1.1:443'\n        - 'app02-2 10.0.1.2:443'\n\n    be_db:\n      mode: 'tcp'\n      balance: 'roundrobin'\n          \n      # define raw config sections/lines to add\n      lines:\n        section1:\n          - 'option mysql-check user haproxy_check'\n\n      servers:\n        - 'mysql-1 10.0.0.1:3306'\n        - 'mysql-2 10.0.0.2:3306'\n\n    be_fallback:\n      lines:\n        default: 'http-request redirect code 302 location https://github.com/ansibleguy'\n\n  # GENERAL\n  stats:\n    enable: true  # enable stats http listener\n    bind: '127.0.0.1:8404'  # default\n\n  geoip:\n    enable: true\n    provider: 'ipinfo'  # or 'maxmind'\n    token: '\u003cYOUR-TOKEN\u003e'\n\n  # define globals/defaults as key/value pairs (multi-value lists usable)\n  global:\n    ca-base: '/etc/ssl/certs'\n\n  defaults:\n    mode: 'http'\n    'timeout connect': 3000\n    'timeout server': 5000\n    'timeout client': 5000\n\n```\n\nYou might want to use 'ansible-vault' to encrypt your passwords:\n```bash\nansible-vault encrypt_string\n```\n\n----\n\n## Functionality\n\n* **Package installation**\n  * Repository dependencies (_minimal_)\n  * HAProxy\n  * GeoIP\n    * [Lookup Service Binary](https://github.com/superstes/geoip-lookup-service)\n  * ACME\n    * [Dependencies](https://github.com/dehydrated-io/dehydrated/blob/v0.7.1/dehydrated#L261)\n    * Nginx light for challenge-response handling\n\n\n* **Configuration**\n\n  * **Default config**:\n    * Globals/Defaults - as seen in default installations\n\n\n  * **Default opt-ins**:\n    * Frontend\n      * HTTP mode\n        * Redirect non SSL traffic to SSL\n        * Logging User-Agent\n        * Setting basic security-headers\n        * Blocking TRACE \u0026 CONNECT methods\n\n\n  * **Default opt-outs**:\n    * Stats http listener\n    * Frontend\n      * [ACME/LetsEncrypt](https://github.com/dehydrated-io/dehydrated)\n      * [GeoIP Lookups](https://github.com/superstes/haproxy-geoip)\n      * Blocking of well-known Script-Bots\n      * SSL Fingerprinting ([JA3](https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967/?ref=waf.ninja))\n\n    * Backend\n      * Sticky sessions\n      * Blocking TRACE \u0026 CONNECT methods\n\n----\n\n## Info\n\n* **Note:** this role currently only supports debian-based systems\n\n\n* **Note:** Most of the role's functionality can be opted in or out.\n\n  For all available options - see the default-config located in [the main defaults-file](https://github.com/ansibleguy/infra_haproxy/blob/latest/defaults/main/1_main.yml)!\n\n\n* **Warning:** Not every setting/variable you provide will be checked for validity. Bad config might break the role!\n\n\n* **Info:** You can easily filter access to backends by using the `filter` and `filter_not` settings:\n\n    `filter_ip`, `filter_not_ip`, `filter_country`, `filter_not_country`, `filter_asn`, `filter_not_asn`\n\n\n* **Info:** A very basic user-agent based Script- \u0026 Bad-Crawler-Bot blocking can be activated for frontends and backends. Check out the [defaults](https://github.com/ansibleguy/infra_haproxy/blob/latest/defaults/main/2_waf.yml) for the list of bots that are blocked.\n\n\n* **Info:** You can easily restrict the HTTP methods allowed on a specific frontend or backend by setting `security.restrict_methods` to true and specifying `security.allow_only_methods`\n\n\n* **Info:** Check out the [Fingerprinting Docs](https://github.com/ansibleguy/infra_haproxy/blob/latest/Fingerprinting.md) for detailed information on how you might want to track clients.\n\n\n* **Info:** If you are using [Graylog Server](https://graylog.org/products/source-available/) to gather and analyze your logs - make sure to split your HAProxy logs into fields using pipeline rules. Example: [HAProxy Community - Graylog Pipeline Rule](https://gist.github.com/superstes/a2f6c5d855857e1f10dcb51255fe08c6#haproxy-split)\n\n\n* **Tip:** You can increase the amount of available `track-sc`'s by setting the global setting [tune.stick-counters](https://docs.haproxy.org/2.8/configuration.html#3.2-tune.stick-counters). This can be useful in environments with complex rate-limit setups.\n\n\n* **Note:** You can define the `basic_auth.users` in the backend-section to enforce a password-prompt before accessing your application. OAuth-Proxy support will be added later on.\n\n### GeoIP\n\n\n* **Warning**: If you use the auto-provisioned GeoIP databases - make sure your product follows their license agreement:\n\n    * **IPinfo**: [Information](https://ipinfo.io/products/free-ip-database), [CC4 License](https://creativecommons.org/licenses/by-sa/4.0/) (*allows for commercial usage - you need to add an attribution*)\n\n        **Attribution**: `\u003cp\u003eIP address data powered by \u003ca href=\"https://ipinfo.io\"\u003eIPinfo\u003c/a\u003e\u003c/p\u003e`\n\n    * **MaxMind**: [Information](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data), [EULA](https://www.maxmind.com/en/geolite2/eula) (*allows for limited commercial usage - you need to add an attribution*)\n\n        **Attribution**: `This product includes GeoLite2 data created by MaxMind, available from \u003ca href=\"https://www.maxmind.com\"\u003ehttps://www.maxmind.com\u003c/a\u003e.`\n\n\n* **Info**: For GeoIP Tokens you will have to create a free account:\n\n    * **IPInfo**: [Login/Register](https://ipinfo.io/login)\n    * **MaxMind**: [Login/Register](https://www.maxmind.com/en/account/login) - Set the `token` to `\u003cACCOUNT\u003e:\u003cLICENSE\u003e`\n\n\n* **Info**: If you want to self-manage the GeoIP-databases (*not recommended*) - the role will assume they are placed at `/var/local/lib/geoip` and be named `asn.mmdb` \u0026 `country.mmdb`.\n\n\n* **Info**: You can test the [GeoIP Lookup Microservice](https://github.com/superstes/haproxy-geoip) manually by using curl: `curl 'http://127.0.0.1:10069/?lookup=country\u0026ip=1.1.1.1'`\n\n\n### WAF\n\n* **Tip**: If you are interested to integrate a full-fledged WAF into HAProxy =\u003e [check out the Coraza-WAF](https://github.com/ansibleguy/haproxy_waf_coraza)!\n\n\n* **Note**: The WAF/security feature-set this role provides does not come lose to the one [available in HAProxy Enterprise by default](https://www.haproxy.com/solutions/web-application-firewall). If you have the money - go for it.\n\n\n* **Tip**: If you are using `security.flag_bots` you can use this basic boolean flag to harden rules for possible bots.\n\n    Examples:\n\n    * Lower rate-limit for bots: `http-request deny deny_status 429 if !{ var(txn.bot) -m int 0 } { sc_http_req_rate(0) gt 50 }`\n\n    * Hard deny bots to register accounts: `http-request deny deny_status 400 if !{ var(txn.bot) -m int 0 } { method POST } { path_sub -m str -i /register/ }`\n\n    * Pass the flag to your application to show a pretty error: `http-request add-header X-Bot %[var(txn.bot)]`\n\n\n* **Note**: If you want to use `security.block_script_kiddies` make sure you check out the block-list in the [defaults](https://github.com/ansibleguy/infra_haproxy/blob/latest/defaults/main/2_waf.yml) and add excludes as needed.\n\n### TCP\n\n* **Info**: If you want to capture data dynamically, you can use `tcp-request content capture`.\n\n    You have to enable the logging of captured data manually by modifying the log-format: `{% raw %}\u003cDEFAULT LOG FORMAT HERE\u003e {%[capture.req.hdr(0)]|%[capture.req.hdr(1)]}{% endraw %}`\n\n    This can be used to log the SNI or GeoIP information.\n\n----\n\n### Execution\n\nRun the playbook:\n```bash\nansible-playbook -K -D -i inventory/hosts.yml playbook.yml\n```\n\nThere are also some useful **tags** available:\n* install\n* config =\u003e only update config and ssl certs\n* ssl or acme\n* geoip\n* lua\n\nTo debug errors - you can set the 'debug' variable at runtime:\n```bash\nansible-playbook -K -D -i inventory/hosts.yml playbook.yml -e debug=yes\n```\n","funding_links":["https://ko-fi.com/ansible0guy","https://github.com/sponsors/ansibleguy"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fansibleguy%2Finfra_haproxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fansibleguy%2Finfra_haproxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fansibleguy%2Finfra_haproxy/lists"}