From 7c2ef04d47a23a8c42e102e50d68d9d327a3753a Mon Sep 17 00:00:00 2001 From: Ryan Blundon Date: Wed, 25 Feb 2026 20:36:50 -0600 Subject: [PATCH] Sync before Archive --- ansible/group_vars/all/vault | 330 +++++++++--------- ansible/host_vars/lightning_lane/vars | 8 +- ansible/inventory.yml | 57 +-- .../playbooks/deploy-haproxy-cloudflare.yml | 177 ++++++++++ ansible/playbooks/roles/haproxy/README.md | 0 .../playbooks/roles/haproxy/defaults/main.yml | 147 ++++++++ .../playbooks/roles/haproxy/handlers/main.yml | 26 ++ .../haproxy/tasks/certbot-cloudflare.yml | 202 +++++++++++ .../roles/haproxy/tasks/certificates.yml | 78 +++++ .../roles/haproxy/tasks/configure.yml | 94 +++++ .../playbooks/roles/haproxy/tasks/install.yml | 128 +++++++ .../playbooks/roles/haproxy/tasks/main.yml | 40 +++ .../roles/haproxy/tasks/validate.yml | 47 +++ .../haproxy/templates/certbot-post-hook.sh.j2 | 107 ++++++ .../templates/cloudflare-credentials.ini.j2 | 12 + .../roles/haproxy/templates/haproxy.cfg.j2 | 188 ++++++++++ .../roles/haproxy/templates/logrotate.j2 | 14 + 17 files changed, 1464 insertions(+), 191 deletions(-) create mode 100644 ansible/playbooks/deploy-haproxy-cloudflare.yml create mode 100644 ansible/playbooks/roles/haproxy/README.md create mode 100644 ansible/playbooks/roles/haproxy/defaults/main.yml create mode 100644 ansible/playbooks/roles/haproxy/handlers/main.yml create mode 100644 ansible/playbooks/roles/haproxy/tasks/certbot-cloudflare.yml create mode 100644 ansible/playbooks/roles/haproxy/tasks/certificates.yml create mode 100644 ansible/playbooks/roles/haproxy/tasks/configure.yml create mode 100644 ansible/playbooks/roles/haproxy/tasks/install.yml create mode 100644 ansible/playbooks/roles/haproxy/tasks/main.yml create mode 100644 ansible/playbooks/roles/haproxy/tasks/validate.yml create mode 100644 ansible/playbooks/roles/haproxy/templates/certbot-post-hook.sh.j2 create mode 100644 ansible/playbooks/roles/haproxy/templates/cloudflare-credentials.ini.j2 create mode 100644 ansible/playbooks/roles/haproxy/templates/haproxy.cfg.j2 create mode 100644 ansible/playbooks/roles/haproxy/templates/logrotate.j2 diff --git a/ansible/group_vars/all/vault b/ansible/group_vars/all/vault index b4e6886..bab6c32 100644 --- a/ansible/group_vars/all/vault +++ b/ansible/group_vars/all/vault @@ -1,162 +1,170 @@ $ANSIBLE_VAULT;1.1;AES256 -35616466656362396235346561343838343634666539663537353635316361353566383531623266 -3934346664633933666162376430626165626132376637630a356261613130306538633933653931 -34383032376664306663353836356165373861383932333737336633366362373962663533366235 -6663393939396563660a373031353531646165336239656534613330663266666361613834326532 -64313632643565666139303464356339383834303262323933326133663163386465323431633332 -38616266353430316133313135643164366464346134316462343536636532323432356362623366 -63383331663535663739386338376561383962316462613432323938373437646136613564393464 -64633530633635633634366135353730343037306437383861363230333036396164656637373864 -39383963623865303630376437663861303935363234383539323263303031373865386539346431 -64323335663134333738653732613761353938373830376261353737613865653537326561303964 -34613134323963663362303235613962303966323163316535386139366161363831656638383665 -31376561303733393464303039636565393537616363333431633863356135353038346562323930 -35376462623632633632303533313138373630393331616239656365626164613166663837373938 -37393064323761303434333265323039356265356532636133383362656634313431333262633566 -31363439656433333562316636636431326564346633623862383463613735396563343164326339 -39356337623264353363336538646366356631303438353236656234383836356363303436393137 -32633665636266623035616264393463363036373530386431653536306431366234353936373464 -64666133303734636135616636393165373530663031396139636561306336366636663335363139 -65356130666164353564343733623337393137326463306333323338386536333363313337363234 -39383666336535386331316162326264343966646562303030363766623936616263646364316432 -31326138323031356235316366313361353235346132383965623431353364623331326162663437 -37373630323232343662376437653337663166663562343066623363343064336230373964326132 -64383965626533663036616261303039663838623030396633393730336334373236366638633339 -61613561626664383063373530393130376539316637363964316532313034313065353530373761 -30386338386333323633333961636434316330613065643662356131623536323865393933326537 -61633231636638396164373635313434376533613334346238396462656334363036643235333839 -36643962353662373439366236333363343432643666363561633931323564316664316538363733 -30326632393638313665643562616230373335363831656136633633343234336465616361363537 -35623666303636303837623430373832663532323430383330313530646636316535336434613466 -35373666363665643536363863663161653330646462633239623466346265376231653363393366 -61343233326638386633343135316563653234336538613939623938366164326561323861373166 -39323462646530333737663039336530656534333332616661373037636336346137643861613930 -35613066346262346232643362373835333337626361316339636564663235323939633234363866 -38616662623665373139323031343462393135653463306333373665666431613264666661643434 -33393666633464636562393335323064636332656336373661353461323830386166656231383639 -33373631396439313265383063613638643633343861323733306335366163653332373865663863 -61653532643838636137353031323663613933656462333364656331373763323366316339653038 -36323831643566323039653566643635303232313137653130393165613633343364646366366532 -31386565366438613866346263653166343934323863303839303531346363663234343265393162 -31613936333138616237393065656430303436653466613632633335613136616534396434653235 -66373834363764333366343933333362363138303663333932643531363863313136633630343063 -36326336313733636564356235303664336131653639393964383132663230613264626131353037 -36363866636238353430396461616530356435386265393864343164613434376139333038336339 -35313964336462626365343135656466316562613935353431326332313461663937326338393536 -61663732313464383734323664663566313732323536666237653662393738333835613831623434 -38306430616333356436633330626537356265343936343764633638393262356266313130623632 -31616131356461656636396539636134616138306239653232613166333765326364666165623334 -62663533313064396635333830616362303332643163646138393761366139626363346537333235 -61616130366334366437633039343533626664343538396638633833336136336662643937366636 -66356665623361386362643237646464326566323865343437336130353031373663643132333630 -65656461643733643534363362336364613164383266393136343830663037313365343632363934 -32386335303638313734623633323564373163666465336563653934346435353766303331333032 -34303037626266366638663435663532633232343361306636303166373437376439653437626537 -65373532386332366530616537363334383362653434333162323533376165376533353263396338 -31653338383363316537366334613137666365646237303235303731633362383536353564663535 -65653864396465333438323532363731643431373666633830316566343537633337366465376138 -35663139303761383935316261636461353936653966303738313130626130613130326332386137 -38326464356637333933316264663032303161643034303363326665373163656238663364643233 -61373461393330316366373135383532323436313535333535623338353332346364613262616539 -32383866636233313461323466336538613165316565646431633632353331383237303437306636 -35653566386437333536656630323761636262333333396563303264653166656663636264633933 -65343435373038366163386464353562303161613162633330633630306566646430613736396266 -38396432646637623837356662626639623361356163346564353238353263363434326465306637 -62343464623365633133633539383933313633333664316438313830346335326161393138333139 -32363237343133386436396537333639623537656362643134356566326439353237313939376637 -35336134383035396637383637356536353064636163333639393763616538313264373930366563 -32333933373466336465643030663765373164363732656231626261336665633238623830313163 -62343438303936383738303737363338333864666135356336313964626131653663393933653130 -33313435653364623531323038393962616432636265666437356365323963636431613136633463 -64376235393462633162386366666266353234383936306637663136383830663438636637376532 -61313134623233626230653931653131356265353836303064326563303936363734656166386133 -38303230366562376566376337376164373335303566386638613839353637323563366463396661 -31623134666231316337373539356332616136663761303834373831663433316339363630396131 -64303066643063666165326439633133383966663265653133346635636634313135636463343835 -36383733383561626239363561653034373636373134633934376437316231323433623733393537 -32323330366262383463316564373930663634363231636635353137636330393630333930653439 -38663066613139343761353164636537356331656534613637356463633561336663393033333331 -38636436646162343630393431316664303738373834326165666366396330316366663838376133 -61386631646338653761646432613432383763326537393136616662633135323336643330663938 -65373234336136643462363939653734613464393830333566643764313834363836356531386363 -39386536323864373863666135633039653966333531633030633362343938313138373764643865 -31353437363830633363623861643164333631343431663832663564613161363165373561383533 -65373234356337373732303038393364326430646131653536646536393139326138373533663761 -34626464663563396631393565626431363066303632306561316336373737313937396563396534 -65366337353232616361323963303263343235383432323261373564356261663632623836313266 -34326163363562343064623265373562316432663839303662613661616265386263336462633163 -63643264303062643264396362353864306562323364323361366463333061366439373339363262 -35353331393265316631323039623334616638356439346335333864616132363562373334323039 -38363161613261363562623032383263303930383538653365393864663266306130613638333131 -32666135643831666531343266386365663563393639656635306338366431313563616632653739 -61643263616530383466656265316630386537356438653063643832373464376162643831323836 -38376338326239326632616262356366396538656666393365383366393237633361333536326434 -31366538396337306632663937343064383836653831383538353037343366393534386437643530 -30356237393739353437323438623664376338323865656439313231313062313766383363616530 -30336363633437643539663037666331346335373639633539613466666231653131316535326439 -32353066393064636262633166643832653938633432633762376435616466336363643131383532 -62353337336133666530383865326537623932373163643131353034623964396462633936613630 -30353566316531313135353965353130646463343539303031353130633536613835383565643932 -33383236656433363762653663383961613164633533303061386334656165366337386235656632 -36353537383635633631323161636535643733313366613435313632373230326337616165323838 -63383038396663303230666337333264613762393066336134653063663562313130303966643238 -37336431666265643231376139386138306261636638616466366461373666393531316139373564 -61363631633866396531653336363365656236613266373933376535656565623461653465346333 -62376566343530366466613536393938343263303437383334373863626138383764656561333662 -37613764353639653838633533316462653165663037376134613730323661383364343438353035 -39363634623034633263383063653437613339313762333234643062613035373264326233383935 -66643134373735383135353763306165303063383161313135343435386163356466656163636366 -35323037303562633863346138616530333932313633343162666662333930663535633661633934 -36376637396532313265323066626637333061353165633036333739663632346337643137343165 -34653561303864613335396233626130363436353764666365356230316534316365613436323639 -32613662353438333362363565346432303333356361653065303236626133353961343834333661 -31633238666165633561303639626630323235346635356264323934353034316361636331333461 -35356138616332656161343434653638653861373962386436323061356263353064613934656663 -34653062383434666462623732386337393038313339626463376631623336666133633663643835 -31613438376431613862333564396330346635656231333835383433343832613663643163396334 -35653739386264326530393261633864656462636264663832303537333833373037323039373731 -65393837656234383530353335343730353535393561346137313436383966396632326437343562 -31316237396336666233656637383461303737363565356136396336393864383238396164333163 -32646238303430626261646236306664396533313136623764303962333836623933666562663439 -35666465613738326165626365343734623131356238616162386233376333663764363038636635 -63643531323164323634316130326265623966323033356338663261636136373035303665346537 -64663065373366636637303237333666396266636535326563653130613237363833626365623331 -66363264353831343264306336313532646637343631656230393466396633336639356431313839 -39643630626462616336333166333062373231613039313661646231393834386561373431643561 -64313933636430343132613263663936313732343233646631616163366633386164653763313333 -62383563303434303830363338666465306463643262623637363939623136366639306330636331 -33633462643234656636656634663664616338636332393231343932383363643331396635303466 -31353935626461396431626336343466623163323730656131313432313635366365653063623537 -61306466393938636261363032353330353134383430363164353230323630363864393234356238 -32333865663063353566663038316633666464636433626236343632643864656635373833386338 -61366136303863343430393661653738626331343366333965616135663339633334623762373266 -36383564663337353935626534366365626238306232633534326266656333613664643736356637 -64323030626361356338356165326336626365636666343131306236366630653534376333373037 -35623062653032666431643936393337623830646134653034336139363863373931653132663165 -65333536343535343463373662373937663133616437636137373632373332323632363465613663 -36326636666435353062383433373562643464376365623530663836356663336338346565386535 -35643136396130326634386632333739663632616538356434376336393664396332623562356164 -39636262323563313862303266663035346132323765613732316434353631383234323036323630 -34336165306436633434306333313534333238623963306338616363303632613934393732653962 -38323331396664336163346236396536333361376431313461383032363733316133343437376637 -30376230366666346363383235333836616539626465636539616131373437336332633633316437 -31626538326137656262633332623631643737656632653036376432653333643031663266316338 -38353734343761656533383133386634343331383338343333363438386231393332653330303631 -30643363346538393532373533376537313831633764366566373539346337623135323161323039 -36326139306364326663353233343133356165326362666535336138653663613131363835376533 -30366566613265346232323062636435373733646461646639306539323762613932313166666331 -37643431363961643832336464646433656664613137303062613037346335323230626464383461 -61363535616266626364333633333061633234646638383335616232373166346233306139363239 -62373363343836623162313638383434363530343134666666346630363630643633616639356231 -35336365376437353935353630333835633033306235653964656163613166643136363165396263 -35333732373665346362336333356538313161633032653931393465306430396536333735383237 -62313361616530623630653762613430393837366431656139393437643837623135333666393231 -31323566663366376666663362656630366135633336323462643936343433613938316432653638 -64343637383039373265643963653037343530386465363936343438346538633266663762366536 -39393732653637343166336663323363653131653035376464643934353963353935386364376336 -35633533346632396237346534303665383365633565663939623438343463613938633863323864 -36633538643830373964373864323235393865366364303166653739376563616364303936623733 -32333935313537643231653232643463303634366366386534363562333635623730316638346661 -37663936366330666463623263326134353331613661363863383361643965316635353663396639 -38323538333165623666333933393966343763323536663163383133663362383665616237633239 -6665 +64393362643437353731353234643535366431613861636131613139636435633465306333656161 +3365306632386330373862363637363435633235643336610a663736396537313032336162633334 +32626233346233323565383030393130316532653062373730376163326134616239663865363862 +3365316365643534610a343932623566356637663136336338386534373532383863363662326438 +37393331356632313163643138613330366631643465623633386433663236636334666264383033 +34333935633131363364626333313336353733373562326636343633653838616532643737366435 +36366330633263613938323566393063373233363538643531636131316335316461656631616362 +30343965356363383139366534363664313831363132383337636266316538393139383232386230 +65353364313534333632306465393264386466346365303232356263653563653338306131376131 +66343764643639396663623330623332333466633737343738386230626463386137373536343430 +36636430616132363161303338353033343963643338656639643662636664613965636263366135 +64353134376162633162316166316239333963653930363830646165636537653238623537323238 +39376338636335336338323935333439306534386539336331613961663261623962383237303632 +34326139626366303331363337353432623636626231373731646665623831353865303130663533 +37326639626237653361316666616161363531303961613538636663633063396363346337663239 +64313333653766383736303161636530316538633033323063323662336335666264646564643465 +64396436366131373061326233643562626335376263623031376532313730653363343038356135 +30336131646635636638353934636139343662623265336338386535663536393935343132306335 +33626632613663353661313061623732643539323637336433666330313463666661623836303462 +61383732613139343434363662616539373837633139393764353864613062346237346661633033 +61393639326535366266643664653439383238646132306636656130333733316638363830346433 +38643430346263663639653762393935356662363533323766343362366532626532633530323630 +30643436653130363066373665666434383732663837643839356539366432316539613863623839 +35323930383437383535373330326239663966383234663233643337623330353236626464636530 +36393735646664366235363831656262383333626265383066393363346165656265363438306135 +34633530653837346638636665666162363062663333623263356131646661373234333939333536 +30643639333836373439613561346130623239363737626238663436356365633562366465643561 +32643039376234323963653733353138643138626430383639313765353035646539653466623539 +32366135366266323261333938373837386636626232323661393035613638643335383564663462 +31383663663063373935396439653231353962303630346338623938343064643037396562626637 +63363363343739626639633934393734396434653262373137373539316536306663336466626639 +34646465306439313239323365333761663161313461613766343463666134303036376338366663 +64356364653462383534343438343933623766396232336434653765613864376235363735343032 +32313133383731333366626534363065333738346230393636393938366633363561346436363338 +33393932626333636166313431633339386564616466653165383962656633303161393935366635 +65396464326333396433303966316231376432363335643732303438663131653932636564363264 +36356663623436643835373538363662643134313030656236326335393334626634663136333137 +38363432346663393435366536636335636465343131666562396334636530346235366365396431 +64623739373130623436356161666139656330646661396430633264393632663366353037643130 +35373839373430643065616630613433646536356563353133633361623235623733313234306435 +64356635343361633462376131636362386433373339383365313436333332613333316237323632 +33633362376331323930376130393838633333316330363362396131306230643161373636303034 +37303738306233363937316332313830396336323639333634666364633566386261373031383563 +36633865643634363234613463636437306236376435613034623537356162353235396665636338 +31613733363430313532643235343438393137313136653730643364623662336236323464656465 +65636431633464613437396561643638346565313937303731633837623930333538383335633938 +62666461323033303937653366646432623764653566626333333766656235633662623631636636 +35626161653736303261303233643236393038303063386532643563663065303331616637386661 +33326637393633616430643665623431616336336666383236326538383535633364613135653131 +38383961383264313130383263613763326666663033626338313762376336353866646634323537 +39393565643265633437396535643165626165646537373039613061613936356434366238636562 +61393639316531316564646334663863383836656261623230646461323566316664653965336136 +62353839363664396632306231363031303265343131343739633835353338386335343737326633 +36656433616135653663353166636461623337653138613435646663663939323631373063373136 +63396331313538636437383635643831643935303932313964336532643366623434316662376632 +63666464333736333162373461333563393138366638643039393865613238616531356165346236 +65313432383533353738396530356465386263313939393962333637306138653939646638353235 +37393733333032623763333632353866633866613162663833346534666237383430663838383965 +66663332616164353539323261383364363237383064306132633530326134383561346536346636 +32393365316435626464386631663037663934313533663030313466323134336439303362626365 +30613764346362326130353333353663316539306437376465306332353464343530663933363562 +39646333323631353835336461326233623963353535316633353236353236636462313163633638 +35366538366336663534353739353231306164626664383737333565666337636164386539316632 +64323966336637626538303830323535373533306362636236303065663161353734653633393064 +66353938306238646238313037323163343935643863653632336462363834326634363433383338 +35646364353834376439633136313939393064633539643462623562333832303261386331636533 +37373837396461373064353565353438633938616162363138323561343662383330346633323133 +36626665343636343739353732366133313434623230353732323037633236303237656364323763 +30363530306131303630386131623866323861633636313431376161336536373436656334303036 +61316639666136313338396233326234386634343863633632626264363336376531363461626564 +35336335396561626339616234373265663032353432363832306666353339376338396366383839 +62633063326135323539653133626538613331613334353430623232376336633334666433316638 +61326563366432333632666436363761376237636434356231613061626633303737626531626236 +62373166363432616666616630616565373934356564363266396662323963636434313735626161 +61373738623966363032313632376465356235363834633639663733653131636531363563326662 +62663365323934363761653536356162336463646566393838346134356536303264323065396534 +63346531303764613634646336313530326663373433396436663162343530306131386265303137 +32333432383966363735343331646233613631373165346131323632366165346235656330323939 +34393030313466666265346163323561353734613434386239333630623762616566616562316265 +38633263323339396462336166646431363336616462366463383663313839613638383663303133 +32333835623531366236643530623961656631336139313037353962323937666465343430666336 +33383464363930663630333164636361326334396163376661666564663538623732663839613237 +35623237613761356235383130633733333766333236363537383862306539373665383461386534 +32646432363339306433646139393732366464336339346161323736363937383566656663356230 +65316562383562366366653466646461613235353061623363386331636139386136306131633232 +32326238353861613236373964383131363537653632663532376364336166303662663336623161 +37623838616664376561663763393534323931343163376535396662626333383730363232653036 +39626266623666346635373630343135333532353866363234396265356631333265663432326235 +37346439613363313334633964386632336138316662616661356637363730306164646439643765 +62343137663137633031316665363362643165643731326464323962616330383164353861366462 +31663965626338636534356461366362333831396464376134613331393664306639386632396236 +66633066663831646361333730356361646133383834376230356336613038303665323732663162 +37643130313739306638636262656632353431316531336438363636363833626237346363326461 +31636262396632666335303566633161333937626330373835613236656130383132663863636364 +34383661353966346362386461303062303138626536323163376362326639356336396437333530 +66623765313638616430383838396164336565646338636430363262396636663965383531383139 +35356663363437383132333566336131666139643639663662633363616133323031353032663438 +39393266633639306363313530643239303132303264323063306335346233363233373865653863 +65396430346630626566383963356264313630623336316236393730343437656435656664663266 +32643639363536396266626435373363303234623230383035326536633631633737653739366563 +65653932663539353436366139313635663962663330343461353864323166323733366163653162 +66353866376134633534323366656265633930356132316137616235343866653265666161363266 +30386638343739353965643466613639303666316432323634383464633431363566333831643134 +65363138313439366430613837396337323436636530313664303964353966306565663630323662 +33643639623935336235373332633739306539656235323439343331376632373538613534386430 +30633832656532623262326662376237393262333764623937343232383861636563633730393765 +39663032373132323735633361343933333332623465343863636263363161303165656234313338 +65383462626465376162306639626531366161396531643364656539366233306166386234313761 +30633666303832636561323236373363373137353032613637353637386238353234646431333661 +32313834666237626333626437663562646438363561393364373565313536363535323536363537 +35633834616136656261643130656131306264333131326431626439343765393031313964616639 +33643933653033336436616635346335383335333334343836343638613264396462633530623866 +30303462653130353430663862623261343866383335343239393435633962383864396430313536 +66643535303437383232323564326266356636376464623563636139633237313661356330633931 +66376631373366613731633863666132626366393532666331343361323764626539666238656262 +66356332383235353864303562356638636130343938383530376432663866316238333434616233 +32316432333064663764363831343464326334353737346632383565643737366233396139333639 +35656435346335323030636363356633633332376138653930613037313537373038353433363532 +39346161336261353463656430663033623036396365346638323832313361633866363134396165 +65356632366463393435386366643232383162623536653965643235323934633464373061333237 +32636564386662626365613235333664323563633466383238343336343063376531396161343530 +61306632646133626133343132666534666337336239306137663931623637643164326538646138 +63313037336235316164316232636262316331356363356438653235346539643065636537366661 +37313437386432316336663366633366666434346135356462623635313531333530653866663863 +31646438316435373637303733646564316261336233623035353564363664303437373132353166 +33646534663130356330663565656663333765653961663835623235633665623464353862616138 +37343531366462616439333539306265626164636233616262616563386566396536386264343137 +30396434303231366664656334656635616334633066323062313738363431646335643132626331 +33663038316165626365346366363731323639653837616163653561623034393064303563353232 +30613939353336396365613662393938663933363662656634663465303134393964623738633765 +37633832626631363736383436666263356531366237356261666538303863343033356333656138 +64366537363939346535313732643965623332396265386636353364316630333130643833383331 +34643563386539383235386130323065353835376564613835313934333636316464636463376537 +32363565333038643230656266393761313064616537393830343163643535646436616230626166 +36373531646536363664663330353864636533656261353331316364633361633934326436323933 +39636631623430313934393033373630656132623734363835353735353935393063356261323764 +66346265376339623265376666343865343763393833323564643161613861303065346137653339 +34326432383230656638303963373832316438666131623539613464636631653365353331346262 +37626130343831636133646135323564306538353439636265313239326235343232633136383663 +62306561616564623038393631373862346535366135633836353363323330346430613466393463 +39313766613064306566316561303761626135613063326536363761626635646134303732356130 +38333266363639636565393737323938663938633662653231366164316262666538356162323665 +38383263366133326333636466343835373737613333623464383139343863336234373932393239 +36653736393732353466656334613364626339356533343363353731353033363730376565353236 +66343332306330303662373363326236393333663565383661643763363662396265313037643432 +63343336623663326237326365333565346335346630356439303534363561626362363833313239 +62366436313466633534343430306461313266376363336536666162306135363434643266373338 +65323866323537636465306636646533316466303735386336663234386538303939343661653562 +34353564313738643665636337336536343735336436626438653335376532343137613837356363 +31353861363733373232363865323164663532646235623533336536333932363133613830373937 +38616137646332303934303839633336313239653064626331383432633935353538636132633134 +61393230663536633335393330386363333162636661373938343837323736663539393636366532 +35623637623262373332373335303833373239323430393766633939633665643236623139363834 +30636135303634643765393433393962303138666630343936653234333730336234356234396566 +35346331336639333237323136616337623938666561343861393563393633313261633333333434 +38633064633538396566373730636632346234653339303533343431643266373266633836633139 +33313234366236626338636331346565336138313136623632343735363132623964353066346233 +66363833383432316238353364363963323762666562303030663939303162656261663664616632 +66396466643763393130313831313438356635346230646335306237363262366132323133646535 +30313338653262636136393133306663653331333830626534356536303532633031356435663734 +32383035643637313863653537656665366138333933333364646561353333653661306133646361 +32326535393333383033313765383031353233323138386365623132646636383037633537386361 +65306166613030353033666438343530333330306532323135306363653764613638383461633964 +63666239366638663537643636373432303164623735626634383636643836343436346161393365 +62336462636534383766656632653833333832393263316633356235373335643436363333393335 +62643838303131643265613861373964653464623766363664383330343138653632396365633533 +39383535616630663564326536623537323966616436353439343630363738626131326365656132 +32383737303733623830353366383062313134396531303831323336326137396335353763353631 +36663336353865303562316233643762643563386665393664623138643836633165 diff --git a/ansible/host_vars/lightning_lane/vars b/ansible/host_vars/lightning_lane/vars index 48d822d..23a58ae 100644 --- a/ansible/host_vars/lightning_lane/vars +++ b/ansible/host_vars/lightning_lane/vars @@ -13,7 +13,7 @@ proxmox_clone_node: "fantasyland" # VM Configuration vm_os_distribution: "ubuntu" vm_os_version: "24.04" -vm_size: "medium" +vm_size: "small" vm_storage: "general" # Proxmox HA Configuration @@ -22,12 +22,12 @@ proxmox_host_target: "pve02" # Network Configuration # IP address for the Traefik load balancer -ip_address: 10.1.71.80 -vm_mac_address: 'BC:24:11:50:00:80' +ip_address: 10.1.71.24 +#vm_mac_address: 'BC:24:11:50:00:80' # Software Configuration # Traefik will be installed and configured on this host # VM Template Configuration -vm_clone_source: "{{ vm_os_distribution }}-{{ vm_os_version }}-{{ vm_size }}" +#vm_clone_source: "{{ vm_os_distribution }}-{{ vm_os_version }}-{{ vm_size }}" hostname: "{{ inventory_hostname }}.{{ base_domain }}" \ No newline at end of file diff --git a/ansible/inventory.yml b/ansible/inventory.yml index 20cba88..48fc02c 100755 --- a/ansible/inventory.yml +++ b/ansible/inventory.yml @@ -25,6 +25,11 @@ prometheus_nodes: hosts: cinderella-castle: +load_balancers: + hosts: + lightning-lane: + ansible_host: 10.1.71.24 + ansible_become: true # node_explorer: # hosts: # cinderella-castle: @@ -78,37 +83,37 @@ terraform_server: hosts: infra01: -hub_cluster: - hosts: - ocp-hub: +# hub_cluster: +# hosts: +# ocp-hub: -internal_cluster: - hosts: - int-master01: - int-master02: - int-master03: -# int-worker01: - int-worker02: +# internal_cluster: +# hosts: +# int-master01: +# int-master02: +# int-master03: +# # int-worker01: +# int-worker02: -talos_cluster: - hosts: - splash: +# talos_cluster: +# hosts: +# splash: -fastpass_control_plane: - hosts: - space-mountain: - big-thunder-mountain: - splash-mountain: +# fastpass_control_plane: +# hosts: +# space-mountain: +# big-thunder-mountain: +# splash-mountain: -fastpass_workers: - hosts: - haunted-mansion: # backstage - peter-pans-flight: # backstage +# fastpass_workers: +# hosts: +# haunted-mansion: # backstage +# peter-pans-flight: # backstage -fastpass: - children: - fastpass_control_plane: - fastpass_workers: +# fastpass: +# children: +# fastpass_control_plane: +# fastpass_workers: # sql_servers: # hosts: diff --git a/ansible/playbooks/deploy-haproxy-cloudflare.yml b/ansible/playbooks/deploy-haproxy-cloudflare.yml new file mode 100644 index 0000000..30e86bc --- /dev/null +++ b/ansible/playbooks/deploy-haproxy-cloudflare.yml @@ -0,0 +1,177 @@ +--- +# ansible/playbooks/deploy-haproxy-cloudflare.yml + +- name: Deploy HAProxy with Cloudflare DNS certificates + hosts: lightning-lane + become: true + + vars: + # Certbot Cloudflare configuration + haproxy_certbot_enable: true + haproxy_certbot_challenge_method: "dns-cloudflare" + haproxy_certbot_email: "ryan@mk-labs.cloud" + haproxy_certbot_staging: false # Set to true for testing + haproxy_certbot_cloudflare_api_token: "{{ vault_cloudflare_api_token }}" + + # Request wildcard certificate + haproxy_certbot_domains: + - domain: "*.local.mk-labs.cloud" + include_base: true # Also include local.mk-labs.cloud + + # HAProxy Stats + haproxy_stats_password: "{{ vault_haproxy_stats_password }}" + + # Frontend configuration + haproxy_frontends: + - name: https_front + bind: "*:443 ssl crt {{ haproxy_ssl_cert_dir }}/live crt {{ haproxy_ssl_default_crt }} alpn h2,http/1.1" + mode: http + options: + - "http-server-close" + - "forwardfor" + acls: + - "netbox hdr(host) -i fire-station.local.mk-labs.cloud" + - "wordpress hdr(host) -i be-our-guest.local.mk-labs.cloud" + - "plane hdr(host) -i people-mover.local.mk-labs.cloud" + - "minecraft hdr(host) -i arcade.local.mk-labs.cloud" + use_backends: + - "netbox_back if netbox" + - "wordpress_back if wordpress" + - "plane_back if plane" + - "minecraft_back if minecraft" + + # Backend configuration + haproxy_backends: + - name: netbox_back + mode: http + balance: roundrobin + options: + - "httpchk GET /api/" + servers: + - name: fire-station + address: 10.1.71.102:8000 + check: true + + - name: wordpress_back + mode: http + balance: roundrobin + servers: + - name: be-our-guest + address: 10.1.71.101:80 + check: true + + - name: plane_back + mode: http + balance: roundrobin + servers: + - name: people-mover + address: 10.1.71.112:8080 + check: true + + - name: minecraft_back + mode: http + balance: roundrobin + servers: + - name: arcade + address: 10.1.71.111:25565 + check: true + + # TCP services + haproxy_tcp_services: + - name: postgres + frontend_port: 5432 + backend_port: 5432 + balance: leastconn + timeout_client: 1h + timeout_server: 1h + servers: + - name: netbox-db + address: 10.1.71.102 + check: true + + - name: ssh_jump + frontend_port: 2222 + backend_port: 22 + balance: source + servers: + - name: main-street-usa + address: 10.1.71.11 + check: true + - name: tomorrowland + address: 10.1.71.12 + check: true + - name: fantasyland + address: 10.1.71.13 + check: true + + - name: minecraft_java + frontend_port: 25565 + backend_port: 25565 + balance: roundrobin + servers: + - name: arcade + address: 10.1.71.111 + check: true + + # Firewall configuration + haproxy_firewall_allowed_ports: + - 80/tcp + - 443/tcp + - 2222/tcp + - 5432/tcp + - 8404/tcp + - 25565/tcp + + pre_tasks: + - name: Ensure required groups exist + ansible.builtin.group: + name: "{{ item }}" + state: present + loop: + - haproxy + + roles: + - haproxy + + post_tasks: + - name: Wait for HAProxy to be ready + ansible.builtin.wait_for: + host: "{{ ansible_default_ipv4.address }}" + port: 443 + timeout: 60 + + - name: Display deployment summary + ansible.builtin.debug: + msg: | + ╔════════════════════════════════════════════════════════════╗ + ║ HAProxy Deployment Complete - lightning-lane ║ + ╚════════════════════════════════════════════════════════════╝ + + Stats Interface: http://{{ ansible_default_ipv4.address }}:8404/stats + Username: {{ haproxy_stats_username }} + + HTTPS Services: + {% for frontend in haproxy_frontends %} + {% if frontend.acls is defined %} + {% for acl in frontend.acls %} + - {{ acl.split()[1] | regex_replace('hdr\\(host\\)', '') | regex_replace('-i', '') | trim }} + {% endfor %} + {% endif %} + {% endfor %} + + TCP Services: + {% for tcp in haproxy_tcp_services %} + - {{ tcp.name }}: {{ ansible_default_ipv4.address }}:{{ tcp.frontend_port }} + {% endfor %} + + Certificates: + {% for domain in haproxy_certbot_domains %} + - {{ domain.domain }}{% if domain.include_base | default(false) %} + base domain{% endif %} + + {% endfor %} + + Next steps: + 1. Verify certificate: certbot certificates + 2. Test renewal: certbot renew --dry-run + 3. Check HAProxy stats interface + 4. Test HTTPS endpoints diff --git a/ansible/playbooks/roles/haproxy/README.md b/ansible/playbooks/roles/haproxy/README.md new file mode 100644 index 0000000..e69de29 diff --git a/ansible/playbooks/roles/haproxy/defaults/main.yml b/ansible/playbooks/roles/haproxy/defaults/main.yml new file mode 100644 index 0000000..af4a816 --- /dev/null +++ b/ansible/playbooks/roles/haproxy/defaults/main.yml @@ -0,0 +1,147 @@ +--- +# ansible/roles/haproxy/defaults/main.yml + +# HAProxy version and installation +haproxy_version: "3.2" +haproxy_install_method: "package" # package or source + +# System configuration +haproxy_user: haproxy +haproxy_group: haproxy +haproxy_chroot_dir: /var/lib/haproxy +haproxy_config_dir: /etc/haproxy +haproxy_log_dir: /var/log/haproxy + +# Global settings +haproxy_global_maxconn: 4096 +haproxy_global_log_facility: local0 +haproxy_global_log_level: info +haproxy_global_nbthread: "{{ ansible_processor_vcpus }}" + +# Default timeouts (milliseconds) +haproxy_timeout_connect: 5000 +haproxy_timeout_client: 50000 +haproxy_timeout_server: 50000 + +# Stats interface +haproxy_stats_enable: true +haproxy_stats_port: 8404 +haproxy_stats_uri: /stats +haproxy_stats_refresh: 30s +haproxy_stats_username: admin +haproxy_stats_password: "{{ vault_haproxy_stats_password | default('changeme') }}" + +# SSL/TLS configuration +haproxy_ssl_cert_dir: "{{ haproxy_config_dir }}/certs" +haproxy_ssl_default_crt: "{{ haproxy_ssl_cert_dir }}/default.pem" +haproxy_ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384" +haproxy_ssl_options: "ssl-min-ver TLSv1.2 no-tls-tickets" + +# Health check defaults +haproxy_health_check_interval: 2000 +haproxy_health_check_timeout: 1000 +haproxy_health_check_rise: 2 +haproxy_health_check_fall: 3 + +# Service discovery integration +haproxy_netbox_integration: false # true +haproxy_netbox_url: "http://fire-station.local.mk-labs.cloud:8000" +haproxy_netbox_token: "{{ vault_netbox_token | default('') }}" + +# Frontends configuration +haproxy_frontends: [] +# - name: http_front +# bind: "*:80" +# mode: http +# redirect: "scheme https code 301" +# +# - name: https_front +# bind: "*:443 ssl crt {{ haproxy_ssl_cert_dir }}" +# mode: http +# options: +# - "http-server-close" +# - "forwardfor" +# acls: +# - "netbox hdr(host) -i fire-station.local.mk-labs.cloud" +# - "wordpress hdr(host) -i be-our-guest.local.mk-labs.cloud" +# use_backends: +# - "netbox_back if netbox" +# - "wordpress_back if wordpress" + +# Backends configuration +haproxy_backends: [] +# - name: netbox_back +# mode: http +# balance: roundrobin +# options: +# - "httpchk GET /" +# servers: +# - name: fire-station +# address: 10.1.71.102:8000 +# check: true + +# TCP services configuration +haproxy_tcp_services: [] +# - name: postgres +# frontend_port: 5432 +# backend_port: 5432 +# balance: leastconn +# servers: +# - name: netbox-db +# address: 10.1.71.102 + +# Firewall configuration +haproxy_configure_firewall: true +haproxy_firewall_allowed_ports: + - 80/tcp + - 443/tcp + - 8404/tcp + +# Monitoring and observability +haproxy_prometheus_exporter: false +haproxy_prometheus_port: 9101 + +# Logging +haproxy_rsyslog_config: true +haproxy_log_rotation: true + +# Certificate management - Certbot with Cloudflare DNS +haproxy_certbot_enable: true +haproxy_certbot_challenge_method: "dns-cloudflare" # or "webroot" for HTTP-01 +haproxy_certbot_email: "ryan.blundon@protonmail.com" +haproxy_certbot_staging: false # Set to true for testing with Let's Encrypt staging + +# Cloudflare DNS configuration +haproxy_certbot_cloudflare_api_token: "{{ vault_cloudflare_api_token }}" +haproxy_certbot_cloudflare_credentials_file: "/etc/letsencrypt/cloudflare.ini" + +# Certificate domains - DNS-01 supports wildcards +haproxy_certbot_domains: + - domain: "*.mk-labs.cloud" + include_base: true + - domain: "*.local.mk-labs.cloud" + include_base: true # Also include local.mk-labs.cloud + - domain: "lightning-lane.local.mk-labs.cloud" + include_base: false + +# Alternative: specific domains without wildcard +# haproxy_certbot_domains: +# - domain: "lightning-lane.local.mk-labs.cloud" +# - domain: "fire-station.local.mk-labs.cloud" +# - domain: "be-our-guest.local.mk-labs.cloud" + +# Certbot renewal settings +haproxy_certbot_renewal_cron_minute: "0" +haproxy_certbot_renewal_cron_hour: "3" # 3 AM daily +haproxy_certbot_renewal_cron_day: "*" +haproxy_certbot_post_hook: "{{ haproxy_config_dir }}/certbot-post-hook.sh" + +# Certificate storage +haproxy_certbot_cert_dir: "/etc/letsencrypt/live" +haproxy_certbot_archive_dir: "/etc/letsencrypt/archive" + +# DNS propagation wait time (seconds) +haproxy_certbot_dns_propagation_seconds: 60 + +# Fallback to self-signed if Certbot fails +haproxy_certbot_fallback_selfsigned: true diff --git a/ansible/playbooks/roles/haproxy/handlers/main.yml b/ansible/playbooks/roles/haproxy/handlers/main.yml new file mode 100644 index 0000000..c08ec65 --- /dev/null +++ b/ansible/playbooks/roles/haproxy/handlers/main.yml @@ -0,0 +1,26 @@ +--- +# ansible/roles/haproxy/handlers/main.yml + +- name: Reload haproxy + ansible.builtin.systemd: + name: haproxy + state: reloaded + listen: reload haproxy + +- name: Restart haproxy + ansible.builtin.systemd: + name: haproxy + state: restarted + daemon_reload: true + listen: restart haproxy + +- name: Reload systemd + ansible.builtin.systemd: + daemon_reload: true + listen: reload systemd + +- name: Restart rsyslog + ansible.builtin.systemd: + name: rsyslog + state: restarted + listen: restart rsyslog diff --git a/ansible/playbooks/roles/haproxy/tasks/certbot-cloudflare.yml b/ansible/playbooks/roles/haproxy/tasks/certbot-cloudflare.yml new file mode 100644 index 0000000..339ae70 --- /dev/null +++ b/ansible/playbooks/roles/haproxy/tasks/certbot-cloudflare.yml @@ -0,0 +1,202 @@ +--- +# ansible/roles/haproxy/tasks/certbot-cloudflare.yml + +- name: Install Certbot and Cloudflare DNS plugin + ansible.builtin.package: + name: + - certbot + - python3-certbot-dns-cloudflare + state: present + +- name: Create Let's Encrypt configuration directory + ansible.builtin.file: + path: /etc/letsencrypt + state: directory + owner: root + group: root + mode: '0755' + +- name: Create Cloudflare credentials file + ansible.builtin.template: + src: cloudflare-credentials.ini.j2 + dest: "{{ haproxy_certbot_cloudflare_credentials_file }}" + owner: root + group: root + mode: '0600' + no_log: true + +- name: Create Certbot post-renewal hook script + ansible.builtin.template: + src: certbot-post-hook.sh.j2 + dest: "{{ haproxy_certbot_post_hook }}" + owner: root + group: root + mode: '0755' + +- name: Create Certbot renewal hooks directory + ansible.builtin.file: + path: /etc/letsencrypt/renewal-hooks/deploy + state: directory + mode: '0755' + +- name: Link post-hook to renewal hooks directory + ansible.builtin.file: + src: "{{ haproxy_certbot_post_hook }}" + dest: /etc/letsencrypt/renewal-hooks/deploy/haproxy-reload.sh + state: link + force: true + +- name: Check for existing certificates + ansible.builtin.stat: + path: "{{ haproxy_certbot_cert_dir }}/{{ item.domain | regex_replace('\\*\\.', '') }}" + register: existing_certs + loop: "{{ haproxy_certbot_domains }}" + loop_control: + label: "{{ item.domain }}" + +- name: Build certificate request commands + ansible.builtin.set_fact: + certbot_commands: "{{ certbot_commands | default([]) + [command_item] }}" + vars: + cert_name: "{{ item.item.domain | regex_replace('\\*\\.', '') }}" + domain_list: >- + {{ [item.item.domain] + + (item.item.include_base | default(false) | ternary( + [item.item.domain | regex_replace('\\*\\.', '')], + [] + )) + }} + command_item: + domain: "{{ item.item.domain }}" + cert_name: "{{ cert_name }}" + domains: "{{ domain_list }}" + exists: "{{ item.stat.exists }}" + loop: "{{ existing_certs.results }}" + loop_control: + label: "{{ item.item.domain }}" + +- name: Display certificate request plan + ansible.builtin.debug: + msg: | + Will request certificates for: + {% for cmd in certbot_commands %} + - {{ cmd.cert_name }} ({{ cmd.domains | join(', ') }}) - {{ 'EXISTS' if cmd.exists else 'NEW' }} + {% endfor %} + +- name: Request certificates from Let's Encrypt via Cloudflare DNS + ansible.builtin.command: > + certbot certonly + --non-interactive + --agree-tos + --email {{ haproxy_certbot_email }} + --dns-cloudflare + --dns-cloudflare-credentials {{ haproxy_certbot_cloudflare_credentials_file }} + --dns-cloudflare-propagation-seconds {{ haproxy_certbot_dns_propagation_seconds }} + --cert-name {{ item.cert_name }} + {% for domain in item.domains %} + --domain {{ domain }} + {% endfor %} + {% if haproxy_certbot_staging %} + --staging + {% endif %} + --deploy-hook {{ haproxy_certbot_post_hook }} + loop: "{{ certbot_commands }}" + loop_control: + label: "{{ item.cert_name }}" + when: not item.exists + register: certbot_result + failed_when: + - certbot_result.rc != 0 + - "'Certificate not yet due for renewal' not in certbot_result.stdout" + - "'too many certificates already issued' not in certbot_result.stderr" + +- name: Display certificate request results + ansible.builtin.debug: + msg: "Certificate for {{ item.item.cert_name }}: {{ 'SUCCESS' if item.rc == 0 else 'SKIPPED/FAILED' }}" + loop: "{{ certbot_result.results }}" + loop_control: + label: "{{ item.item.cert_name }}" + when: certbot_result.results is defined + +- name: Run post-hook to combine certificates for HAProxy + ansible.builtin.command: "{{ haproxy_certbot_post_hook }}" + changed_when: true + when: certbot_result.changed + +- name: Setup automatic certificate renewal via cron + ansible.builtin.cron: + name: "Certbot certificate renewal" + minute: "{{ haproxy_certbot_renewal_cron_minute }}" + hour: "{{ haproxy_certbot_renewal_cron_hour }}" + day: "{{ haproxy_certbot_renewal_cron_day }}" + job: "/usr/bin/certbot renew --quiet --deploy-hook {{ haproxy_certbot_post_hook }} 2>&1 | logger -t certbot" + user: root + state: present + +- name: Create systemd timer for certificate renewal (alternative to cron) + when: ansible_service_mgr == "systemd" + block: + - name: Create certbot renewal service + ansible.builtin.copy: + content: | + [Unit] + Description=Certbot Renewal + After=network-online.target + Wants=network-online.target + + [Service] + Type=oneshot + ExecStart=/usr/bin/certbot renew --quiet --deploy-hook {{ haproxy_certbot_post_hook }} + StandardOutput=journal + StandardError=journal + dest: /etc/systemd/system/certbot-renewal.service + mode: '0644' + + - name: Create certbot renewal timer + ansible.builtin.copy: + content: | + [Unit] + Description=Certbot Renewal Timer + + [Timer] + OnCalendar=daily + RandomizedDelaySec=1h + Persistent=true + + [Install] + WantedBy=timers.target + dest: /etc/systemd/system/certbot-renewal.timer + mode: '0644' + + - name: Enable and start certbot renewal timer + ansible.builtin.systemd: + name: certbot-renewal.timer + enabled: true + state: started + daemon_reload: true + +- name: Test certificate renewal (dry-run) + ansible.builtin.command: certbot renew --dry-run --dns-cloudflare --dns-cloudflare-credentials {{ haproxy_certbot_cloudflare_credentials_file }} + register: renewal_test + changed_when: false + failed_when: false + +- name: Display renewal test results + ansible.builtin.debug: + msg: | + Certificate renewal dry-run: {{ 'SUCCESS ✓' if renewal_test.rc == 0 else 'FAILED ✗' }} + + {% if renewal_test.rc == 0 %} + Your certificates will renew automatically. + {% else %} + Please check the output above for errors. + {% endif %} + +- name: List all certificates + ansible.builtin.command: certbot certificates + register: cert_list + changed_when: false + +- name: Display certificate information + ansible.builtin.debug: + msg: "{{ cert_list.stdout_lines }}" diff --git a/ansible/playbooks/roles/haproxy/tasks/certificates.yml b/ansible/playbooks/roles/haproxy/tasks/certificates.yml new file mode 100644 index 0000000..815573d --- /dev/null +++ b/ansible/playbooks/roles/haproxy/tasks/certificates.yml @@ -0,0 +1,78 @@ +--- +# ansible/roles/haproxy/tasks/certificates.yml + +- name: Check if default certificate exists + ansible.builtin.stat: + path: "{{ haproxy_ssl_default_crt }}" + register: default_cert + +- name: Generate self-signed certificate for development + when: not default_cert.stat.exists + block: + - name: Generate private key + openssl_privatekey: + path: "{{ haproxy_ssl_cert_dir }}/default.key" + size: 2048 + mode: '0600' + owner: "{{ haproxy_user }}" + group: "{{ haproxy_group }}" + + - name: Generate certificate signing request + openssl_csr: + path: "{{ haproxy_ssl_cert_dir }}/default.csr" + privatekey_path: "{{ haproxy_ssl_cert_dir }}/default.key" + common_name: "lightning-lane.local.mk-labs.cloud" + subject_alt_name: + - "DNS:lightning-lane.local.mk-labs.cloud" + - "DNS:*.local.mk-labs.cloud" + owner: "{{ haproxy_user }}" + group: "{{ haproxy_group }}" + + - name: Generate self-signed certificate + openssl_certificate: + path: "{{ haproxy_ssl_cert_dir }}/default.crt" + privatekey_path: "{{ haproxy_ssl_cert_dir }}/default.key" + csr_path: "{{ haproxy_ssl_cert_dir }}/default.csr" + provider: selfsigned + selfsigned_not_after: "+3650d" + owner: "{{ haproxy_user }}" + group: "{{ haproxy_group }}" + + - name: Combine certificate and key for HAProxy + ansible.builtin.shell: | + cat {{ haproxy_ssl_cert_dir }}/default.crt \ + {{ haproxy_ssl_cert_dir }}/default.key \ + > {{ haproxy_ssl_default_crt }} + args: + creates: "{{ haproxy_ssl_default_crt }}" + + - name: Set permissions on combined certificate + ansible.builtin.file: + path: "{{ haproxy_ssl_default_crt }}" + owner: "{{ haproxy_user }}" + group: "{{ haproxy_group }}" + mode: '0600' + +- name: Create certificate directory structure + ansible.builtin.file: + path: "{{ haproxy_ssl_cert_dir }}/{{ item }}" + state: directory + owner: "{{ haproxy_user }}" + group: "{{ haproxy_group }}" + mode: '0755' + loop: + - live + - archive + +- name: Deploy additional certificates + when: haproxy_ssl_certificates is defined + block: + - name: Copy certificate files + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "{{ haproxy_ssl_cert_dir }}/live/{{ item.name }}.pem" + owner: "{{ haproxy_user }}" + group: "{{ haproxy_group }}" + mode: '0600' + loop: "{{ haproxy_ssl_certificates }}" + notify: Reload haproxy diff --git a/ansible/playbooks/roles/haproxy/tasks/configure.yml b/ansible/playbooks/roles/haproxy/tasks/configure.yml new file mode 100644 index 0000000..de35028 --- /dev/null +++ b/ansible/playbooks/roles/haproxy/tasks/configure.yml @@ -0,0 +1,94 @@ +--- +# ansible/roles/haproxy/tasks/configure.yml + +- name: Fetch services from NetBox + when: haproxy_netbox_integration + block: + - name: Query NetBox for service configurations + ansible.builtin.uri: + url: "{{ haproxy_netbox_url }}/api/ipam/services/" + method: GET + headers: + Authorization: "Token {{ haproxy_netbox_token }}" + Accept: "application/json" + return_content: true + register: netbox_services + changed_when: false + + - name: Parse NetBox services + ansible.builtin.set_fact: + haproxy_frontends: "{{ haproxy_frontends | default([]) + netbox_parsed_frontends }}" + haproxy_backends: "{{ haproxy_backends | default([]) + netbox_parsed_backends }}" + vars: + netbox_parsed_frontends: "{{ netbox_services.json.results | map(attribute='custom_fields') | list }}" + netbox_parsed_backends: "{{ netbox_services.json.results | map(attribute='custom_fields') | list }}" + when: netbox_services.json.results is defined + +- name: Generate HAProxy main configuration + ansible.builtin.template: + src: haproxy.cfg.j2 + dest: "{{ haproxy_config_dir }}/haproxy.cfg" + owner: root + group: root + mode: '0644' + backup: true + validate: 'haproxy -c -f %s' + notify: reload haproxy + +- name: Configure rsyslog for HAProxy + when: haproxy_rsyslog_config + block: + - name: Create rsyslog configuration for HAProxy + ansible.builtin.copy: + content: | + # HAProxy logging configuration + $ModLoad imudp + $UDPServerRun 514 + + # Create separate log files for HAProxy + local0.* {{ haproxy_log_dir }}/haproxy.log + local0.info {{ haproxy_log_dir }}/haproxy-info.log + local0.notice {{ haproxy_log_dir }}/haproxy-notice.log + local0.err {{ haproxy_log_dir }}/haproxy-error.log + + # Stop processing HAProxy logs + & stop + dest: /etc/rsyslog.d/49-haproxy.conf + mode: '0644' + notify: restart rsyslog + +- name: Configure logrotate for HAProxy + when: haproxy_log_rotation + ansible.builtin.template: + src: logrotate.j2 + dest: /etc/logrotate.d/haproxy + mode: '0644' + +- name: Create HAProxy systemd override directory + ansible.builtin.file: + path: /etc/systemd/system/haproxy.service.d + state: directory + mode: '0755' + +- name: Configure HAProxy systemd service overrides + ansible.builtin.copy: + content: | + [Service] + # Increase file descriptor limits + LimitNOFILE=65536 + + # Runtime directory + RuntimeDirectory=haproxy + RuntimeDirectoryMode=0755 + + # Security hardening + NoNewPrivileges=true + ProtectSystem=strict + ProtectHome=true + PrivateTmp=true + ReadWritePaths={{ haproxy_log_dir }} + dest: /etc/systemd/system/haproxy.service.d/override.conf + mode: '0644' + notify: + - Reload systemd + - Restart haproxy diff --git a/ansible/playbooks/roles/haproxy/tasks/install.yml b/ansible/playbooks/roles/haproxy/tasks/install.yml new file mode 100644 index 0000000..710656b --- /dev/null +++ b/ansible/playbooks/roles/haproxy/tasks/install.yml @@ -0,0 +1,128 @@ +--- +# ansible/roles/haproxy/tasks/install.yml + +- name: Install HAProxy repository (Ubuntu) + when: ansible_os_family == "Debian" + block: + - name: Add HAProxy PPA + ansible.builtin.apt_repository: + repo: "ppa:vbernat/haproxy-{{ haproxy_version }}" + state: present + update_cache: true + + - name: Install HAProxy + ansible.builtin.apt: + name: haproxy + state: present + update_cache: true + +- name: Install HAProxy (Fedora/RHEL) + when: ansible_os_family == "RedHat" + block: + - name: Install HAProxy + ansible.builtin.dnf: + name: haproxy + state: present + +- name: Install additional packages + ansible.builtin.package: + name: + - rsyslog + - logrotate + - socat # For HAProxy socket communication + state: present + +- name: Create HAProxy system user + ansible.builtin.user: + name: "{{ haproxy_user }}" + group: "{{ haproxy_group }}" + system: true + shell: /usr/sbin/nologin + home: "{{ haproxy_chroot_dir }}" + create_home: true + +- name: Create required directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ haproxy_user }}" + group: "{{ haproxy_group }}" + mode: '0755' + loop: + - "{{ haproxy_config_dir }}" + - "{{ haproxy_ssl_cert_dir }}" + - "{{ haproxy_log_dir }}" + - "{{ haproxy_chroot_dir }}" + - /var/lib/haproxy/dev + +- name: Create HAProxy socket directory + ansible.builtin.file: + path: /run/haproxy + state: directory + owner: "{{ haproxy_user }}" + group: "{{ haproxy_group }}" + mode: '0755' + +- name: Configure firewall rules + when: haproxy_configure_firewall + block: + - name: Open HAProxy ports (UFW) + when: ansible_os_family == "Debian" + community.general.ufw: + rule: allow + port: "{{ item.split('/')[0] }}" + proto: "{{ item.split('/')[1] }}" + loop: "{{ haproxy_firewall_allowed_ports }}" + + - name: Open HAProxy ports (firewalld) + when: ansible_os_family == "RedHat" + ansible.posix.firewalld: + port: "{{ item }}" + permanent: true + state: enabled + immediate: true + loop: "{{ haproxy_firewall_allowed_ports }}" + +- name: Install Prometheus HAProxy exporter + when: haproxy_prometheus_exporter + block: + - name: Download HAProxy exporter + ansible.builtin.get_url: + url: "https://github.com/prometheus/haproxy_exporter/releases/download/v0.15.0/haproxy_exporter-0.15.0.linux-amd64.tar.gz" + dest: /tmp/haproxy_exporter.tar.gz + mode: '0644' + + - name: Extract HAProxy exporter + ansible.builtin.unarchive: + src: /tmp/haproxy_exporter.tar.gz + dest: /usr/local/bin/ + remote_src: true + extra_opts: + - --strip-components=1 + - --wildcards + - '*/haproxy_exporter' + + - name: Create HAProxy exporter systemd service + ansible.builtin.copy: + content: | + [Unit] + Description=HAProxy Exporter + After=network.target + + [Service] + Type=simple + User={{ haproxy_user }} + ExecStart=/usr/local/bin/haproxy_exporter --haproxy.scrape-uri=unix:/run/haproxy/admin.sock + Restart=on-failure + + [Install] + WantedBy=multi-user.target + dest: /etc/systemd/system/haproxy-exporter.service + mode: '0644' + + - name: Enable and start HAProxy exporter + ansible.builtin.systemd: + name: haproxy-exporter + state: started + enabled: true + daemon_reload: true diff --git a/ansible/playbooks/roles/haproxy/tasks/main.yml b/ansible/playbooks/roles/haproxy/tasks/main.yml new file mode 100644 index 0000000..49032b4 --- /dev/null +++ b/ansible/playbooks/roles/haproxy/tasks/main.yml @@ -0,0 +1,40 @@ +--- +# ansible/roles/haproxy/tasks/main.yml + +# - name: Include OS-specific variables +# include_vars: "{{ ansible_os_family }}.yml" +# tags: [haproxy, always] + +- name: Include installation tasks + ansible.builtin.include_tasks: install.yml + tags: [haproxy, install] + +- name: Include Certbot with Cloudflare DNS tasks + ansible.builtin.include_tasks: certbot-cloudflare.yml + when: + - haproxy_certbot_enable + - haproxy_certbot_challenge_method == "dns-cloudflare" + tags: [haproxy, certificates, certbot, cloudflare] + +- name: Include fallback self-signed certificate tasks + ansible.builtin.include_tasks: certificates.yml + when: + - not haproxy_certbot_enable + - haproxy_certbot_fallback_selfsigned | default(false) + tags: [haproxy, certificates, selfsigned] + +- name: Include configuration tasks + ansible.builtin.include_tasks: configure.yml + tags: [haproxy, configure] + +- name: Include validation tasks + ansible.builtin.include_tasks: validate.yml + tags: [haproxy, validate] + +- name: Ensure HAProxy is started and enabled + ansible.builtin.systemd: + name: haproxy + state: started + enabled: true + daemon_reload: true + tags: [haproxy, service] diff --git a/ansible/playbooks/roles/haproxy/tasks/validate.yml b/ansible/playbooks/roles/haproxy/tasks/validate.yml new file mode 100644 index 0000000..e92ca1a --- /dev/null +++ b/ansible/playbooks/roles/haproxy/tasks/validate.yml @@ -0,0 +1,47 @@ +--- +# ansible/roles/haproxy/tasks/validate.yml + +- name: Validate HAProxy configuration syntax + command: haproxy -c -f {{ haproxy_config_dir }}/haproxy.cfg + register: haproxy_validation + changed_when: false + failed_when: haproxy_validation.rc != 0 + +- name: Display validation results + debug: + msg: "{{ haproxy_validation.stdout_lines }}" + when: haproxy_validation.stdout_lines is defined + +- name: Check HAProxy service status + systemd: + name: haproxy + state: started + register: haproxy_status + changed_when: false + +- name: Verify HAProxy is listening on configured ports + wait_for: + host: "{{ ansible_default_ipv4.address }}" + port: "{{ item }}" + timeout: 10 + loop: + - 80 + - 443 + - "{{ haproxy_stats_port }}" + when: haproxy_status.status.ActiveState == "active" + +- name: Test stats interface accessibility + uri: + url: "http://localhost:{{ haproxy_stats_port }}{{ haproxy_stats_uri }}" + user: "{{ haproxy_stats_username }}" + password: "{{ haproxy_stats_password }}" + force_basic_auth: true + status_code: 200 + register: stats_test + changed_when: false + failed_when: false + +- name: Display stats interface status + debug: + msg: "Stats interface is {{ 'accessible' if stats_test.status == 200 else 'not accessible' }}" + \ No newline at end of file diff --git a/ansible/playbooks/roles/haproxy/templates/certbot-post-hook.sh.j2 b/ansible/playbooks/roles/haproxy/templates/certbot-post-hook.sh.j2 new file mode 100644 index 0000000..3855663 --- /dev/null +++ b/ansible/playbooks/roles/haproxy/templates/certbot-post-hook.sh.j2 @@ -0,0 +1,107 @@ +#!/bin/bash +# {{ ansible_managed }} +# Certbot post-renewal hook for HAProxy +# Combines certificate and private key for HAProxy consumption + +set -euo pipefail + +# Configuration +LETSENCRYPT_LIVE="/etc/letsencrypt/live" +HAPROXY_CERT_DIR="{{ haproxy_ssl_cert_dir }}/live" +HAPROXY_USER="{{ haproxy_user }}" +HAPROXY_GROUP="{{ haproxy_group }}" +LOG_FILE="{{ haproxy_log_dir }}/certbot-hook.log" + +# Ensure HAProxy cert directory exists +mkdir -p "$HAPROXY_CERT_DIR" + +# Logging function +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" +} + +log "Starting certificate deployment for HAProxy" + +# Track if any certificates were updated +CERTS_UPDATED=false + +# Process each certificate in Let's Encrypt live directory +for cert_dir in "$LETSENCRYPT_LIVE"/*; do + if [ ! -d "$cert_dir" ] || [ "$(basename "$cert_dir")" = "README" ]; then + continue + fi + + CERT_NAME=$(basename "$cert_dir") + + # Check if certificate files exist + if [ ! -f "$cert_dir/fullchain.pem" ] || [ ! -f "$cert_dir/privkey.pem" ]; then + log "WARNING: Missing certificate files for $CERT_NAME, skipping" + continue + fi + + log "Processing certificate: $CERT_NAME" + + # Create combined PEM file (fullchain + privkey) + # HAProxy requires the certificate chain and private key in a single file + COMBINED_PEM="$HAPROXY_CERT_DIR/${CERT_NAME}.pem" + + # Combine certificates + cat "$cert_dir/fullchain.pem" "$cert_dir/privkey.pem" > "$COMBINED_PEM.tmp" + + # Check if certificate actually changed + if [ -f "$COMBINED_PEM" ] && cmp -s "$COMBINED_PEM" "$COMBINED_PEM.tmp"; then + log "Certificate $CERT_NAME unchanged, skipping" + rm "$COMBINED_PEM.tmp" + continue + fi + + # Move new certificate into place + mv "$COMBINED_PEM.tmp" "$COMBINED_PEM" + + # Set proper permissions + chown "$HAPROXY_USER:$HAPROXY_GROUP" "$COMBINED_PEM" + chmod 600 "$COMBINED_PEM" + + log "Created/Updated: $COMBINED_PEM" + CERTS_UPDATED=true +done + +# Create/update default certificate symlink +# Use the first wildcard cert or the first available cert as default +DEFAULT_CERT="{{ haproxy_ssl_default_crt }}" +WILDCARD_CERT="$HAPROXY_CERT_DIR/local.mk-labs.cloud.pem" + +if [ -f "$WILDCARD_CERT" ]; then + ln -sf "$WILDCARD_CERT" "$DEFAULT_CERT" + log "Updated default certificate symlink to wildcard cert" +elif [ -n "$(ls -A "$HAPROXY_CERT_DIR" 2>/dev/null)" ]; then + FIRST_CERT=$(ls "$HAPROXY_CERT_DIR"/*.pem 2>/dev/null | head -n1) + ln -sf "$FIRST_CERT" "$DEFAULT_CERT" + log "Updated default certificate symlink to $FIRST_CERT" +fi + +# Only reload HAProxy if certificates were actually updated +if [ "$CERTS_UPDATED" = true ]; then + log "Certificates updated, validating HAProxy configuration" + + # Validate HAProxy configuration + if haproxy -c -f {{ haproxy_config_dir }}/haproxy.cfg >/dev/null 2>&1; then + log "HAProxy configuration valid, reloading service" + + if systemctl reload haproxy; then + log "HAProxy reloaded successfully" + else + log "ERROR: Failed to reload HAProxy" + exit 1 + fi + else + log "ERROR: HAProxy configuration validation failed" + haproxy -c -f {{ haproxy_config_dir }}/haproxy.cfg 2>&1 | tee -a "$LOG_FILE" + exit 1 + fi +else + log "No certificates were updated, skipping HAProxy reload" +fi + +log "Certificate deployment complete" +exit 0 diff --git a/ansible/playbooks/roles/haproxy/templates/cloudflare-credentials.ini.j2 b/ansible/playbooks/roles/haproxy/templates/cloudflare-credentials.ini.j2 new file mode 100644 index 0000000..1e3b09d --- /dev/null +++ b/ansible/playbooks/roles/haproxy/templates/cloudflare-credentials.ini.j2 @@ -0,0 +1,12 @@ +# {{ ansible_managed }} +# Cloudflare API credentials for Certbot DNS-01 challenge +# This file contains sensitive information and should be protected + +# Cloudflare API Token (RECOMMENDED) +# Create at: https://dash.cloudflare.com/profile/api-tokens +# Required permissions: Zone:DNS:Edit for specific zone +dns_cloudflare_api_token = {{ haproxy_certbot_cloudflare_api_token }} + +# Alternative: Global API Key (NOT RECOMMENDED - too permissive) +# dns_cloudflare_email = your-email@example.com +# dns_cloudflare_api_key = your-global-api-key diff --git a/ansible/playbooks/roles/haproxy/templates/haproxy.cfg.j2 b/ansible/playbooks/roles/haproxy/templates/haproxy.cfg.j2 new file mode 100644 index 0000000..986cef2 --- /dev/null +++ b/ansible/playbooks/roles/haproxy/templates/haproxy.cfg.j2 @@ -0,0 +1,188 @@ +{# ansible/roles/haproxy/templates/haproxy.cfg.j2 #} +#--------------------------------------------------------------------- +# HAProxy Configuration for {{ inventory_hostname }} +# Generated by Ansible - DO NOT EDIT MANUALLY +# Magic Kingdom Labs - lightning-lane load balancer +#--------------------------------------------------------------------- + +#--------------------------------------------------------------------- +# Global settings +#--------------------------------------------------------------------- +global + log 127.0.0.1 {{ haproxy_global_log_facility }} {{ haproxy_global_log_level }} + chroot {{ haproxy_chroot_dir }} + pidfile /var/run/haproxy.pid + maxconn {{ haproxy_global_maxconn }} + user {{ haproxy_user }} + group {{ haproxy_group }} + daemon + nbthread {{ haproxy_global_nbthread }} + + # Runtime socket for management + stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners + stats timeout 30s + + # SSL/TLS configuration + ssl-default-bind-ciphers {{ haproxy_ssl_ciphers }} + ssl-default-bind-options {{ haproxy_ssl_options }} + ssl-default-server-ciphers {{ haproxy_ssl_ciphers }} + ssl-default-server-options {{ haproxy_ssl_options }} + + # Performance tuning + tune.ssl.default-dh-param 2048 + tune.ssl.cachesize 100000 + tune.ssl.lifetime 600 + +#--------------------------------------------------------------------- +# Common defaults +#--------------------------------------------------------------------- +defaults + mode http + log global + option httplog + option dontlognull + option http-server-close + option redispatch + retries 3 + timeout connect {{ haproxy_timeout_connect }} + timeout client {{ haproxy_timeout_client }} + timeout server {{ haproxy_timeout_server }} + timeout http-request 10s + timeout http-keep-alive 10s + timeout check 10s + maxconn 3000 + + # Error pages + errorfile 400 /etc/haproxy/errors/400.http + errorfile 403 /etc/haproxy/errors/403.http + errorfile 408 /etc/haproxy/errors/408.http + errorfile 500 /etc/haproxy/errors/500.http + errorfile 502 /etc/haproxy/errors/502.http + errorfile 503 /etc/haproxy/errors/503.http + errorfile 504 /etc/haproxy/errors/504.http + +#--------------------------------------------------------------------- +# Stats interface +#--------------------------------------------------------------------- +{% if haproxy_stats_enable %} +listen stats + bind *:{{ haproxy_stats_port }} + mode http + stats enable + stats uri {{ haproxy_stats_uri }} + stats refresh {{ haproxy_stats_refresh }} + stats realm Magic\ Kingdom\ Labs\ -\ HAProxy\ Statistics + stats auth {{ haproxy_stats_username }}:{{ haproxy_stats_password }} + stats admin if TRUE +{% endif %} + +#--------------------------------------------------------------------- +# HTTP Redirect Frontend (HTTP -> HTTPS) +#--------------------------------------------------------------------- +frontend http_front + bind *:80 + mode http + + # Redirect all HTTP traffic to HTTPS + redirect scheme https code 301 + +#--------------------------------------------------------------------- +# HTTPS Frontend with SNI +#--------------------------------------------------------------------- +frontend https_front + bind *:443 ssl crt {{ haproxy_ssl_cert_dir }}/live crt {{ haproxy_ssl_default_crt }} alpn h2,http/1.1 + mode http + + # Security headers + http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + http-response set-header X-Frame-Options "SAMEORIGIN" + http-response set-header X-Content-Type-Options "nosniff" + http-response set-header X-XSS-Protection "1; mode=block" + + # Logging + option httplog + option forwardfor + +{% if haproxy_frontends is defined and haproxy_frontends | length > 0 %} +{% for frontend in haproxy_frontends %} +{% if frontend.acls is defined %} + # ACLs for {{ frontend.name }} +{% for acl in frontend.acls %} + acl {{ acl }} +{% endfor %} +{% endif %} +{% endfor %} + +{% for frontend in haproxy_frontends %} +{% if frontend.use_backends is defined %} + # Backend routing for {{ frontend.name }} +{% for use_backend in frontend.use_backends %} + use_backend {{ use_backend }} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} + + # Default backend + default_backend no_match_back + +#--------------------------------------------------------------------- +# Backends +#--------------------------------------------------------------------- +{% for backend in haproxy_backends %} +backend {{ backend.name }} + mode {{ backend.mode | default('http') }} + balance {{ backend.balance | default('roundrobin') }} +{% if backend.options is defined %} +{% for option in backend.options %} + option {{ option }} +{% endfor %} +{% endif %} +{% if backend.http_check is defined %} + option httpchk {{ backend.http_check }} +{% endif %} +{% if backend.servers is defined %} +{% for server in backend.servers %} + server {{ server.name }} {{ server.address }}{% if server.check | default(false) %} check{% endif %}{% if server.backup | default(false) %} backup{% endif %}{% if server.weight is defined %} weight {{ server.weight }}{% endif %} + +{% endfor %} +{% endif %} + +{% endfor %} + +# Default backend for unmatched requests +backend no_match_back + mode http + http-request deny deny_status 404 + +#--------------------------------------------------------------------- +# TCP Services +#--------------------------------------------------------------------- +{% for tcp_service in haproxy_tcp_services %} +# {{ tcp_service.name | upper }} Service (TCP) +frontend {{ tcp_service.name }}_front + bind *:{{ tcp_service.frontend_port }} + mode tcp + option tcplog +{% if tcp_service.timeout_client is defined %} + timeout client {{ tcp_service.timeout_client }} +{% endif %} + default_backend {{ tcp_service.name }}_back + +backend {{ tcp_service.name }}_back + mode tcp + balance {{ tcp_service.balance | default('roundrobin') }} +{% if tcp_service.timeout_server is defined %} + timeout server {{ tcp_service.timeout_server }} +{% endif %} +{% if tcp_service.timeout_connect is defined %} + timeout connect {{ tcp_service.timeout_connect }} +{% endif %} +{% if tcp_service.servers is defined %} +{% for server in tcp_service.servers %} + server {{ server.name }} {{ server.address }}:{{ tcp_service.backend_port }}{% if server.check | default(true) %} check{% endif %} + +{% endfor %} +{% endif %} + +{% endfor %} diff --git a/ansible/playbooks/roles/haproxy/templates/logrotate.j2 b/ansible/playbooks/roles/haproxy/templates/logrotate.j2 new file mode 100644 index 0000000..593bb4d --- /dev/null +++ b/ansible/playbooks/roles/haproxy/templates/logrotate.j2 @@ -0,0 +1,14 @@ +{# ansible/roles/haproxy/templates/logrotate.j2 #} +{{ haproxy_log_dir }}/*.log { + daily + rotate 14 + missingok + notifempty + compress + delaycompress + sharedscripts + postrotate + /bin/kill -HUP `cat /var/run/rsyslog.pid 2> /dev/null` 2> /dev/null || true + /bin/kill -HUP `cat /var/run/haproxy.pid 2> /dev/null` 2> /dev/null || true + endscript +}