Skip to content

Commit

Permalink
acme implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ansibleguy committed May 3, 2024
1 parent b530c69 commit f53d381
Show file tree
Hide file tree
Showing 22 changed files with 694 additions and 73 deletions.
54 changes: 34 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

# Ansible Role - HAProxy Community

**WARNING**: The role is still in early development! **DO NOT TRY TO USE IN PRODUCTION**!

Role to deploy HAProxy (*Focus on the Community Version*)

<a href='https://ko-fi.com/ansible0guy' target='_blank'><img height='35' style='border:0px;height:46px;' src='https://az743702.vo.msecnd.net/cdn/kofi3.png?v=0' border='0' alt='Buy me a coffee' />
Expand Down Expand Up @@ -60,14 +58,12 @@ ansible-galaxy install -r requirements.yml
* Redirect non SSL traffic to SSL if in HTTP mode
* Logging User-Agent
* Setting basic security-headers
* Backend
* Basic Check (*httpchk if in http mode*)


* **Default opt-outs**:
* Stats http listener
* Frontend
* [ACME/LetsEncrypt](https://www.haproxy.com/blog/haproxy-and-let-s-encrypt) (*yet to be implemented*)
* [ACME/LetsEncrypt](https://github.com/dehydrated-io/dehydrated)
* [GeoIP Lookups](https://github.com/superstes/haproxy-geoip)


Expand Down Expand Up @@ -95,15 +91,15 @@ ansible-galaxy install -r requirements.yml
**Attribution**: `This product includes GeoLite2 data created by MaxMind, available from <a href="https://www.maxmind.com">https://www.maxmind.com</a>.`


* **Info**: If you want to self-manage the databases - the role will assume they are placed at `/var/local/lib/geoip` and be named `asn.mmdb` & `country.mmdb`.


* **Info**: For GeoIP Tokens you will have to create a free account:

* **IPInfo**: [Login/Register](https://ipinfo.io/login)
* **MaxMind**: [Login/Register](https://www.maxmind.com/en/account/login) - Set the `token` to `<ACCOUNT>:<LICENSE>`


* **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` & `country.mmdb`.


* **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&ip=1.1.1.1'`


Expand All @@ -120,13 +116,19 @@ ansible-galaxy install -r requirements.yml

```yaml
haproxy:
acme:
enable: true
email: '[email protected]'

frontends:
fe_web:
bind: ['[::]:80 v4v6', '[::]:443 v4v6 ssl']
acme: true
acme_domains: ['app.template.ansibleguy.net']
acme:
enable: true
domains: ['app.template.ansibleguy.net']

route:

routes:
be_intern:
filter_ip: '10.0.0.0/8'
filter_not_ip: '10.100.0.0/24'
Expand All @@ -135,7 +137,9 @@ haproxy:

backends:
be_intern:

servers:
- 'srv-1 192.168.10.11:80'
- 'srv-2 192.168.10.12:80'

be_fallback:
lines: 'http-request redirect code 301 location https://github.com/ansibleguy'
Expand All @@ -146,15 +150,19 @@ Define the config as needed:
```yaml
haproxy:
version: '2.8'
acme:
enable: true
email: '[email protected]'

# FRONTENDS
frontends:
fe_web:
bind: ['[::]:80 v4v6', '[::]:443 v4v6 ssl']
acme: true
acme_domains: ['app.template.ansibleguy.net'] # domains from routes will also be added
acme:
enable: true
domains: ['app.template.ansibleguy.net'] # domains from routes will also be added

route:
routes:
be_app01:
domains: ['app01.template.ansibleguy.net', 'hello.template.ansibleguy.net']

Expand All @@ -170,14 +178,13 @@ haproxy:
default_backend: 'be_db'

fe_restricted:
bind: ['[::]:80 v4v6', '[::]:443 v4v6 ssl']
acme: true
bind: ['[::]:8080 v4v6', '[::]:8443 v4v6 ssl /etc/myapp/mycert.pem']

geoip:
enable: true

backends:
be_app01:
routes:
be_app02:
filter_country: ['AT', 'DE', 'CH']
# filter_ip: ['10.0.0.0/8']
domains: ['app01.template.ansibleguy.net', 'hello.template.ansibleguy.net']
Expand All @@ -199,6 +206,13 @@ haproxy:
check_uri: '/health'
check_expect: 'status 200'

be_app02:
ssl: true
ssl_verify: 'none' # default; example: 'required ca-file /etc/ssl/certs/my_ca.crt verifyhost host01.intern'
servers:
- 'app02-1 10.0.1.1:443'
- 'app02-2 10.0.1.2:443'

be_db:
mode: 'tcp'
balance: 'roundrobin'
Expand Down Expand Up @@ -253,7 +267,7 @@ ansible-playbook -K -D -i inventory/hosts.yml playbook.yml
There are also some useful **tags** available:
* install
* config
* ssl
* ssl or acme
* geoip

To debug errors - you can set the 'debug' variable at runtime:
Expand Down
21 changes: 19 additions & 2 deletions defaults/main/0_hardcoded.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
---

version_geoip_lookup: '1.0'
version_dehydrated: '0.7.1'

cpu_arch: "{{ 'amd64' if ansible_architecture == 'x86_64' else ansible_architecture }}"

HAPROXY_HC:
valid_versions: ['2.6', '2.7', '2.8', '2.9']
path:
config: '/etc/haproxy/conf.d'
acme: '/etc/ssl/haproxy_acme'
map: '/etc/haproxy/map'
lua: '/etc/haproxy/lua'
geoip_bin: '/usr/local/bin/geoip-lookup'
Expand All @@ -16,16 +18,25 @@ HAPROXY_HC:
map_geoip_country: '/etc/haproxy/map/geoip_country.map'
map_geoip_asn: '/etc/haproxy/map/geoip_asn.map'
map_geoip_as_name: '/etc/haproxy/map/geoip_as_name.map'
acme_script: '/usr/local/bin/dehydrated.sh'
acme_script_hook: '/usr/local/bin/dehydrated_hook.sh'
acme_script_src: 'dehydrated'
acme: '/etc/ssl/haproxy_acme'
acme_certs: '/etc/ssl/haproxy_acme/certs'
acme_certs_raw: '/etc/ssl/haproxy_acme/certs_raw'
acme_challenges: '/var/www/haproxy_acme'
acme_config: '/etc/dehydrated'

user: 'haproxy'
group: 'haproxy'

url:
geoip_bin: "https://github.com/superstes/geoip-lookup-service/releases/download/1.0/geoip-lookup-linux-{{ cpu_arch }}-CGO0.tar.gz"
geoip_bin: "https://github.com/superstes/geoip-lookup-service/releases/download/{{ version_geoip_lookup }}/geoip-lookup-linux-{{ cpu_arch }}-CGO0.tar.gz"
geoip_ipinfo_country: "https://ipinfo.io/data/free/country.mmdb?token="
geoip_ipinfo_asn: "https://ipinfo.io/data/free/asn.mmdb?token="
geoip_maxmind_country: "https://download.maxmind.com/geoip/databases/GeoLite2-ASN/download?suffix=tar.gz"
geoip_maxmind_asn: "https://download.maxmind.com/geoip/databases/GeoLite2-ASN/download?suffix=tar.gz"
acme_script: "https://github.com/dehydrated-io/dehydrated/releases/download/v{{ version_dehydrated }}/dehydrated-{{ version_dehydrated }}.tar.gz"

valid_geoip_providers: ['ipinfo', 'maxmind']
geoip_lookup_port: 10069
Expand All @@ -43,3 +54,9 @@ HAPROXY_HC:
country: 'country.iso_code'
asn: 'autonomous_system_number'
as_name: 'autonomous_system_organization'

user_acme: 'haproxy-acme'
acme_update_timer: '*-*-* 02:00:00'
acme_reload_timer: '*-*-* 03:00:00'
service_acme: 'haproxy-acme'
service_acme_reload: 'haproxy-acme-reload'
17 changes: 15 additions & 2 deletions defaults/main/1_main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ defaults_haproxy:
pwd: 'monitor'
realm: 'Authorized Personal Only'

acme:
enable: false
email:
ocsp_must_staple: false
ocsp_fetch: false
manage_challenge: true # set-up nginx-light to handle acme challenge-responses
challenge_port: 8405 # port the webserver for challenge-responses listens on
domains: []

geoip:
enable: false
manage_db: true
Expand Down Expand Up @@ -64,7 +73,10 @@ defaults_frontend:
# ipv4 only: ':80'
# ssl with acme enabled: ':443 ssl'
# ssl with custom cert: ':443 ssl crt /etc/ssl/haproxy/site.pem'
acme: false
acme:
enable: false
domains: []

ssl_redirect: true
security_headers: true
log:
Expand All @@ -78,7 +90,7 @@ defaults_frontend:

lines: {} # raw config lines to add - section to lines mapping to make resulting config human-readable

route: {}
routes: {}
# backend_name:
# domains: ['hostname1', 'hostname2']
# filter_country: ['AT', 'DE', 'CH']
Expand Down Expand Up @@ -123,6 +135,7 @@ defaults_backend:
# for health-checks see: https://www.haproxy.com/blog/how-to-enable-health-checks-in-haproxy
# more complex ones should be implemented by supplying the raw config-lines
check: true
check_http: false # httpchk
check_method: 'GET'
check_uri:
check_expect:
48 changes: 48 additions & 0 deletions files/etc/ssl/haproxy_acme/certs/placeholder.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
-----BEGIN CERTIFICATE-----
MIIDMTCCAhkCFCXtQHlUk/7cEzQVuSinmYCO11hBMA0GCSqGSIb3DQEBCwUAMFUx
CzAJBgNVBAYTAkFUMQ8wDQYDVQQIDAZTdHlyaWExEzARBgNVBAoMCkFuc2libGVH
dXkxIDAeBgNVBAMMF1BsYWNlaG9sZGVyIENlcnRpZmljYXRlMB4XDTI0MDUwMzEy
MzM0NloXDTM0MDUwMTEyMzM0NlowVTELMAkGA1UEBhMCQVQxDzANBgNVBAgMBlN0
eXJpYTETMBEGA1UECgwKQW5zaWJsZUd1eTEgMB4GA1UEAwwXUGxhY2Vob2xkZXIg
Q2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6w1bI
9T5etLqi5cRMsLJgfuGXzxI6eT3CrPOZXWsq1+F+wr3TcprUeNniD+8TTZ+YJu1r
E5M5PGjODsNRbYj2uXtSu18TsJJn2qV1X8bvQgC4rswMUttkYO0QsrlCM3VEqI6R
GA534gMfJ4slSf3Fiz/noWBllEM/NpphgHyoRSM5yw2TARRLrVE0Oc41qbzeIN6f
nYritVw64/dsBQbOMtBwHRm57wz3kSrn+zDbXn2VZiDDXVJOAZsXCezsFuk3TlmC
DjfJoRucfgHMxNoVaRoHJR9pPumHzRaqx2eDg2Cav/tE4nO2ANWPtD2736zpqsuh
YqF6+eQage70EJ+vAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAImDt32BDBGohFNC
AKXygBTMfcEYQO3Zp/gycpder7CsWyt+l551F5Rqgd0uHxa0VE4cwtyzXgmKkxh/
lslcLysIzBCyfl3vJBw5cGQ+Ctg0D4i3XDDgJwU15ZXlnk9G3bIhNfHnoKUcxDle
gp7E4Bub48PoE7LHf4yrHGNXUKKOIPaax18rQUxDUiWVCI1g4dMKjp4+yX0tiRUv
PZcejNpw3FoKqkkGQVHvS01PS5r/CU0nw0muRXpoKVRZ8nMvulkvihN4BK4zjhs1
y+xFY4GbTzJqPYTPwt+zkc/P8YB8T/abNYpo42xe+Kf8I9dWFBd6jNe//lg93v1r
4WLf/tg=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC6w1bI9T5etLqi
5cRMsLJgfuGXzxI6eT3CrPOZXWsq1+F+wr3TcprUeNniD+8TTZ+YJu1rE5M5PGjO
DsNRbYj2uXtSu18TsJJn2qV1X8bvQgC4rswMUttkYO0QsrlCM3VEqI6RGA534gMf
J4slSf3Fiz/noWBllEM/NpphgHyoRSM5yw2TARRLrVE0Oc41qbzeIN6fnYritVw6
4/dsBQbOMtBwHRm57wz3kSrn+zDbXn2VZiDDXVJOAZsXCezsFuk3TlmCDjfJoRuc
fgHMxNoVaRoHJR9pPumHzRaqx2eDg2Cav/tE4nO2ANWPtD2736zpqsuhYqF6+eQa
ge70EJ+vAgMBAAECggEABF7Pbq+hrTQSRsv7P/nFv53rrlKMkEAhQqdeO/FRSxT2
+rEyVMeSkyfdnjCi3SzGPOU/gQkmoZw/esyI1ySQPYj+apbMrsVFZ8pYcA3oHDxk
wyDNnZKcZHwjXNSrb4uMuI9izRQAoqvwFSnAhwR1CWTGZg4DF3cH69H3SUWccfbB
27ObKI6AhRApIr2yBTihEL4mCqPTXtWbRQdcAM3kb07V7YxbsJz51Qac0l8ZmEph
MzBzCJMuU65RwgalPS5hTzSwP5xHEIXjAIJ8mPH/QrIrSajReIiJGfXRbO/xljhg
90yGTSX9HwnrC92+mJEszCzwI4nCCJmT2/jQSt/gIQKBgQC7bho/xBM9WExfqGiP
uAghNBzxws8FzFexeLkM1ZNStJTuJv6ax0wnV4FXhwENp6lB38Hd7DoihOV80WLX
gSfmDxf74nw3QXrjKOeEKjwxRB+qLiEsN8r4ThgyeAfGjRLitOKZjGRxfFPgbazJ
e3wRkgBgdMJjaCxaYR6MOWu+QwKBgQD/FsOOe3SJkaVZwjsEC/IE7uuwoxJsmxO7
8jRb8ABi+Sd+MtYtMQXYEghOVxupg2/PR1Pl/cMWEYPoQ3Lbn3quoJnP4qd02MiN
puL1hFIj09pEExaoUU01lTrNkgCkm5HhLGSo4uj/Ygg4I+Z1d9jlXck24UI7z23Y
Y4q8hXdgJQKBgQCf9qcmdvSorXx5Q6UBy+H8XJq7ZzUC0NSjHdJpdrpGouJcoyE2
/hMrnI5CInGussJM+2hdPCidn2iw7495N7zSp10j17eF/TehOh7leJpovah8uOQM
9g0fgJ88K58PQQW2QQUIYX60MJTxfQkz6FUKNd5mdCAXcSgxdqP4r2UaOwKBgFas
aW30TLihoElLUboiRO2gML0n646zcpUdyuSiO79lYSHkLBnW2mF8Xw4fUuraGheX
6M3w12ScNvGoWVJ+cbT8JMcaAEQXlK1s0xkRCMfbqAIRalVuqolWV1CaF1XW9k5I
QzuPPhPoP1qz+A5Z1ny4zTG0gEjKRkyMJgvAXbtxAoGAPSRzPJU81AwxrUMspLEf
J+S5PKeGoicJ4U1QFExcMjLnJwnkBwcs2BUgt8uocOhHVQstyicQRvibXm8nwFZd
SOjUQRPvKrilgO1NVgT/hi51fN1TBN7zncZCdOLCctfAUBzdK9pdO6Qu6uvtFNMd
iKYEFn5fm6rrNIU6GQKdEZo=
-----END PRIVATE KEY-----
9 changes: 9 additions & 0 deletions filter_plugins/utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from re import sub as regex_replace


class FilterModule(object):

def filters(self):
return {
"ensure_list": self.ensure_list,
"is_string": self.is_string,
"is_dict": self.is_dict,
"safe_key": self.safe_key,
}

@staticmethod
Expand All @@ -22,3 +26,8 @@ def is_string(data) -> bool:
@staticmethod
def is_dict(data) -> bool:
return isinstance(data, dict)

@staticmethod
def safe_key(key: str) -> str:
return regex_replace('[^0-9a-zA-Z_]+', '', key.replace(' ', '_'))

Loading

0 comments on commit f53d381

Please sign in to comment.