]> Raphaël G. Git Repositories - blogbundle/blob - Fixture/BlogFixture.php
Add doctrine dbal default charset and collate parameters
[blogbundle] / Fixture / BlogFixture.php
1 <?php declare(strict_types=1);
2
3 /*
4 * This file is part of the Rapsys BlogBundle package.
5 *
6 * (c) Raphaël Gertz <symfony@rapsys.eu>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Rapsys\BlogBundle\Fixture;
13
14 use Doctrine\Bundle\FixturesBundle\Fixture;
15 use Doctrine\Persistence\ObjectManager;
16
17 use Rapsys\PackBundle\Util\SluggerUtil;
18
19 use Rapsys\BlogBundle\Entity\Civility;
20 use Rapsys\BlogBundle\Entity\Group;
21 use Rapsys\BlogBundle\Entity\User;
22 use Rapsys\BlogBundle\Entity\UserTranslation;
23 use Rapsys\BlogBundle\Entity\Keyword;
24 use Rapsys\BlogBundle\Entity\KeywordTranslation;
25 use Rapsys\BlogBundle\Entity\Article;
26 use Rapsys\BlogBundle\Entity\ArticleTranslation;
27
28 /**
29 * {@inheritDoc}
30 */
31 class BlogFixture extends Fixture {
32 /**
33 * @var Rapsys\PackBundle\Util\SluggerUtil
34 */
35 private SluggerUtil $slugger;
36
37 /**
38 * Constructor
39 */
40 public function __construct(SluggerUtil $slugger) {
41 //Set slugger
42 $this->slugger = $slugger;
43 }
44
45 /**
46 * {@inheritDoc}
47 */
48 public function load(ObjectManager $manager) {
49 //Civility tree
50 $civilityTree = [
51 'Mister',
52 'Madam',
53 'Miss'
54 ];
55
56 //Create titles
57 $civilitys = [];
58 foreach($civilityTree as $civilityData) {
59 $civility = new Civility($civilityData);
60 $manager->persist($civility);
61 $civilitys[$civilityData] = $civility;
62 unset($civility);
63 }
64
65 //Group tree
66 //XXX: ROLE_XXX is required by
67 $groupTree = [
68 'User',
69 'Admin'
70 ];
71
72 //Create groups
73 $groups = [];
74 foreach($groupTree as $groupData) {
75 $group = new Group($groupData);
76 $manager->persist($group);
77 $groups[$groupData] = $group;
78 unset($group);
79 }
80
81 //Flush to get the ids
82 $manager->flush();
83
84 //User tree
85 $userTree = [
86 [
87 'civility' => 'Mister',
88 'group' => 'Admin',
89 'mail' => 'blog@rapsys.eu',
90 'password' => 'test',
91 'forename' => 'Raphaël',
92 'surname' => 'Gertz',
93 'active' => true,
94 'enable' => false,
95 'pseudonym' => 'Rapsys',
96 'slug' => $this->slugger->slug('Raphaël Gertz (rapsys)'),
97 'translations' => [
98 'en_gb' => 'Raphaël Gertz, born in 1984, is a web developper since 2007. Interested in free software, since 2004, when he begin contributing to a linux distribution, known now as Mageia, some path has been traveled since then.',
99 'fr_fr' => 'Raphaël Gertz, né en 1984, est développeur web depuis 2007. Passionné par le monde du logiciel libre, depuis 2004, où il commence à contribuer à une distribution linux, connue maintenant sous le nom Mageia, un certain chemin a été parcouru dès lors.'
100 ]
101 ],
102 ];
103
104 //Create users
105 $users = [];
106 foreach($userTree as $userData) {
107 $user = new User($userData['mail'], $userData['password'], $civilitys[$userData['civility']], $userData['forename'], $userData['surname'], $userData['active'], $userData['enable'], $userData['pseudonym'], $userData['slug']);
108 $user->addGroup($groups[$userData['group']]);
109 $manager->persist($user);
110 //Flush to get the id
111 $manager->flush();
112 $users[$userData['mail']] = $user;
113 foreach($userData['translations'] as $locale => $description) {
114 $userTranslation = new UserTranslation($users[$userData['mail']], $locale, $description);
115 $manager->persist($userTranslation);
116 unset($userTranslation);
117 }
118 unset($user);
119 }
120
121 //Flush to get the ids
122 $manager->flush();
123
124 //Keyword tree
125 $keywordTree = [
126 'png' => [
127 'en_gb' => [
128 'title' => 'PNG',
129 'description' => 'Portable Network Graphics (PNG) is an raster graphics file open format that supports lossless data compression'
130 ],
131 'fr_fr' => [
132 'title' => 'PNG',
133 'description' => 'Le Portable Network Graphics (PNG) est un format ouvert d’images numériques, qui a été créé pour remplacer le format GIF, à l’époque propriétaire et dont la compression était soumise à un brevet'
134 ]
135 ],
136 'imagick' => [
137 'en_gb' => [
138 'title' => 'Imagick',
139 'description' => 'ImageMagick is a free and open-source software suite for displaying, converting, and editing raster image and vector image files'
140 ],
141 'fr_fr' => [
142 'title' => 'Imagick',
143 'description' => 'Image Magick est une collection de logiciels libres pour afficher, convertir et modifier des images numériques matricielles ou vectorielles dans de nombreux formats'
144 ]
145 ],
146 'image' => [
147 'en_gb' => [
148 'title' => 'Image',
149 'description' => 'An image is an artifact that depicts visual perception, for example, a photo or a two-dimensional picture, that has a similar appearance to some subject'
150 ],
151 'fr_fr' => [
152 'title' => 'Image',
153 'description' => 'Une image est une représentation visuelle, voire mentale, de quelque chose'
154 ]
155 ],
156 'varnish' => [
157 'en_gb' => [
158 'title' => 'Varnish',
159 'description' => 'Varnish is an HTTP cache server deployed as a reverse proxy between applications servers and clients'
160 ],
161 'fr_fr' => [
162 'title' => 'Varnish',
163 'description' => 'Varnish est un serveur de cache HTTP déployé en tant que proxy inverse entre les serveurs d\'application et les clients'
164 ]
165 ],
166 'webservice' => [
167 'en_gb' => [
168 'title' => 'Web service',
169 'description' => 'A web service is a service offered by an electronic device to another electronic device, communicating with each other via the World Wide Web'
170 ],
171 'fr_fr' => [
172 'title' => 'Service web',
173 'description' => 'Un service web, ou service de la toile, est un protocole d\'interface informatique de la famille des technologies web permettant la communication et l\'échange de données entre applications et systèmes hétérogènes'
174 ]
175 ],
176 'rest' => [
177 'en_gb' => [
178 'title' => 'REST',
179 'description' => 'Representational state transfer (REST) or RESTful web services are a way of providing interoperability between computer systems on the Internet'
180 ],
181 'fr_fr' => [
182 'title' => 'REST',
183 'description' => 'Representational state transfer (REST) ou services web RESTful est une manière de fournir de l\'intéropérabilité entre systèmes d\'information sur Internet'
184 ]
185 ],
186 'hateoas' => [
187 'en_gb' => [
188 'title' => 'HATEOAS',
189 'description' => 'HATEOAS, abbreviation of Hypermedia As The Engine Of Application State, is a constraint of the REST application architecture that distinguishes it from other network application architectures'
190 ],
191 'fr_fr' => [
192 'title' => 'HATEOAS',
193 'description' => 'HATEOAS, abréviation de Hypermedia As Engine of Application State, Hypermédia en tant que moteur de l\'état d\'application, constitue une contrainte de l\'architecture d\'application REST qui la distingue de la plupart des autres architectures d\'applications réseau'
194 ]
195 ],
196 'uri' => [
197 'en_gb' => [
198 'title' => 'URI',
199 'description' => 'In information technology, a Uniform Resource Identifier (URI) is a string of characters used to identify a resource'
200 ],
201 'fr_fr' => [
202 'title' => 'URI',
203 'description' => 'En technologie de l\'information, une URI, abréviation d\'Uniform Resource Identifier, Identifiant uniforme de ressource, est une chaine de caractères utilisée pour identifier une ressource'
204 ]
205 ],
206 'cidr' => [
207 'en_gb' => [
208 'title' => 'CIDR',
209 'description' => 'Classless Inter-Domain Routing, CIDR, is a method for aggregating IP addresses and route them'
210 ],
211 'fr_fr' => [
212 'title' => 'CIDR',
213 'description' => 'Routage inter-domaine sans classe, de l\'anglais Classless Inter-Domain Routing, CIDR, est une méthode pour agréger des adresses IP et les router'
214 ]
215 ],
216 'amazon' => [
217 'en_gb' => [
218 'title' => 'Amazon',
219 'description' => 'Amazon Elastic Compute Cloud or EC2 is an Amazon server renting service allowing third party to run their own web application'
220 ],
221 'fr_fr' => [
222 'title' => 'Amazon',
223 'description' => 'Amazon Elastic Compute Cloud ou EC2 est un service proposé par Amazon permettant à des tiers de louer des serveurs sur lesquels exécuter leurs propres applications web'
224 ]
225 ],
226 'php' => [
227 'en_gb' => [
228 'title' => 'PHP',
229 'description' => 'PHP: Hypertext Preprocessor, better known as PHP, is an open programming language, used mostly to produce dynamic web pages through an HTTP server'
230 ],
231 'fr_fr' => [
232 'title' => 'PHP',
233 'description' => 'PHP : Hypertext Preprocessor, plus connu sous son sigle PHP, est un langage de programmation libre, principalement utilisé pour produire des pages Web dynamiques via un serveur HTTP'
234 ]
235 ],
236 'mysql' => [
237 'en_gb' => [
238 'title' => 'MySQL',
239 'description' => 'MySQL is a free and open-source relational database management system'
240 ],
241 'fr_fr' => [
242 'title' => 'MySQL',
243 'description' => 'MySQL est un logiciel libre de gestion de bases de données relationnelles'
244 ]
245 ],
246 'azure' => [
247 'en_gb' => [
248 'title' => 'Azure',
249 'description' => 'Microsoft Azure, formerly Windows Azure, is a cloud computing service created by Microsoft for building, testing, deploying, and managing applications and services through a global network of Microsoft-managed data centers'
250 ],
251 'fr_fr' => [
252 'title' => 'Azure',
253 'description' => 'Microsoft Azure, anciennement Windows Azure, est une plateforme applicative en nuage crée par Microsoft pour construire, tester, déployer et gérer des applications et services sur un réseau global de centres de données opéré par Microsoft'
254 ]
255 ],
256 'microsoft' => [
257 'en_gb' => [
258 'title' => 'Microsoft',
259 'description' => 'Microsoft Corporation is an american multinational technology company, founded in 1975 by Bill Gates and Paul Allen'
260 ],
261 'fr_fr' => [
262 'title' => 'Microsoft',
263 'description' => 'Microsoft Corporation est une multinationale informatique et micro-informatique américaine, fondée en 1975 par Bill Gates et Paul Allen'
264 ]
265 ],
266 'apache' => [
267 'en_gb' => [
268 'title' => 'Apache',
269 'description' => 'Apache is a free and open-source HTTP server for modern operating systems'
270 ],
271 'fr_fr' => [
272 'title' => 'Apache',
273 'description' => 'Apache est un logiciel libre serveur HTTP pour les systèmes d\'exploitation modernes'
274 ]
275 ],
276 'haproxy' => [
277 'en_gb' => [
278 'title' => 'HAProxy',
279 'description' => 'HAProxy is a reliable high performance TCP/HTTP free and open-source load balancer'
280 ],
281 'fr_fr' => [
282 'title' => 'HAProxy',
283 'description' => 'HAProxy est un logiciel libre équilibreur de charge TCP/HTTP fiable et haute performance'
284 ]
285 ],
286 'http' => [
287 'en_gb' => [
288 'title' => 'HTTP',
289 'description' => 'HTTP, abbreviation of HyperText Transfer Protocol, is a network client-server communications protocol developed for the World Wide Web'
290 ],
291 'fr_fr' => [
292 'title' => 'HTTP',
293 'description' => 'HTTP, abréviation de HyperText Transfer Protocol, est un protocole de communication client-serveur réseau développé pour le World Wide Web'
294 ]
295 ],
296 'proxy' => [
297 'en_gb' => [
298 'title' => 'Proxy',
299 'description' => 'Intermediate server application between two hosts to improve privacy, security and performance'
300 ],
301 'fr_fr' => [
302 'title' => 'Proxy',
303 'description' => 'Application serveur intermédiaire entre deux hôtes pour améliorer la confidentialité, la sécurité et les performances'
304 ]
305 ],
306 'quic' => [
307 'en_gb' => [
308 'title' => 'QUIC',
309 'description' => 'QUIC, abbreviation of Quick UDP Internet Connections, is a fast network client-server communications protocol over UDP developed for Google'
310 ],
311 'fr_fr' => [
312 'title' => 'QUIC',
313 'description' => 'QUIC, abréviation de Quick UDP Internet Connections, est un protocole de communication client-serveur réseau rapide sur UDP développé pour Google'
314 ]
315 ],
316 'mageia' => [
317 'en_gb' => [
318 'title' => 'Mageia',
319 'description' => 'Mageia is a free operating system community project, based on GNU/Linux, supported by a French 1901 law association made up of elected contributors'
320 ],
321 'fr_fr' => [
322 'title' => 'Mageia',
323 'description' => 'Mageia est un projet communautaire de système d\'exploitation libre, basé sur GNU/Linux, soutenu par une association loi 1901 française constituée de contributeurs élus'
324 ]
325 ],
326 'google' => [
327 'en_gb' => [
328 'title' => 'Google',
329 'description' => 'Google organize the world\'s information and make it universally accessible and useful'
330 ],
331 'fr_fr' => [
332 'title' => 'Google',
333 'description' => 'Google organise les informations à l\'échelle mondiale pour les rendre accessibles et utiles à tous'
334 ]
335 ],
336 ];
337
338 //Create 3 keywords
339 $keywords = [];
340 foreach($keywordTree as $name => $data) {
341 $keyword = new Keyword();
342 $manager->persist($keyword);
343 //Flush to get the id
344 $manager->flush();
345 $keywords[$name] = $keyword;
346 foreach($data as $locale => $translation) {
347 $keywordTranslation = new KeywordTranslation($keywords[$name], $locale, $translation['description'], $this->slugger->slug($translation['title']), $translation['title']);
348 $manager->persist($keywordTranslation);
349 unset($keywordTranslation);
350 }
351 unset($keyword);
352 }
353
354 //Flush to get the ids
355 $manager->flush();
356
357 //Article tree
358 $articleTree = [
359 [
360 'mail' => 'blog@rapsys.eu',
361 'keywords' => ['image', 'imagick', 'png'],
362 'translations' => [
363 'en_gb' => [
364 'title' => 'How to reliably detect transparency in PNG image with PHP',
365 'description' => 'Reliable PHP function to detect PNG images with transparency supporting all the variants to process.',
366 'body' => 'The primary need is to find out if a PNG has transparency using PHP.
367
368 All the code I found didn\'t seemed to work correctly for the collection of PNG I had to convert.
369
370 I finished using the following function:
371 ```php
372 function png_has_transparency($im) {
373 //Retrieve content from imagick object
374 $content = $im->getImageBlob();
375
376 //Detect 32-bit png (each pixel has tranparency level)
377 if (ord(substr($content, 25, 1)) & 4) {
378 //Fetch iterator
379 $p = $im->getPixelIterator();
380
381 //Loop on each row
382 foreach($p as $r) {
383 //Loop on each row pixel
384 foreach($r as $pix) {
385 //Check if pixel has partial transparency
386 if ($pix->getColorValue(Imagick::COLOR_ALPHA) != 1) {
387 return true;
388 }
389 }
390 }
391 //Check 8-bit png transparency
392 } elseif (stripos($content, \'PLTE\') !== false || stripos($content, \'tRNS\') !== false) {
393 return true;
394 }
395
396 //Didn\'t found clue of transparency
397 return false;
398 }
399 ```
400
401 This function works with the only two transparency possibilities: 8 and 32-bit PNG.
402
403 The first case is a 32-bit PNG with transparency enabled, we have then to check every pixel to detect if it has transparent part or not.
404
405 The second case is a 8-bit PNG, then we only have to look the file content for transparency markers.
406
407 In this function configuration, we only read part of the file in 32-bit PNG until we detect one transparent pixel or parse content until transparency marker is detected in 8-bit PNG.
408
409 The worst case scenario will be 32-bit PNG with transparency flag without transparency or 8-bit PNG without transparency flag.
410
411 Depending on how likely you are to have transparency in each cases you might want to reverse the flow of this function.
412
413 Big thanks to these articles which expains how these parts work in a bit more detail:
414 - <https://www.jonefox.com/blog/2011/04/15/how-to-detect-transparency-in-png-images>
415 - <http://camendesign.com/code/uth1_is-png-32bit>
416 - <https://stackoverflow.com/questions/5495275/how-to-check-if-an-image-has-transparency-using-gd>
417
418 Hope this helps someone else out there.'
419 ],
420 'fr_fr' => [
421 'title' => 'Comment détecter la tranparence dans des images PNG en PHP de manière fiable',
422 'description' => 'Fonction PHP fiable pour détecter les images PNG avec transparence prenant en charge toutes les variantes à traiter.',
423 'body' => 'Le besoin principal est de savoir si un PNG a de la transparence en utilisant PHP.
424
425 Les codes trouvés ne semblaient pas fonctionner de manière satisfaisante pour les différents types de PNG à contrôler.
426
427 J\'ai fini par utiliser la fonction suivante:
428 ```php
429 function png_has_transparency($im) {
430 //Retrieve content from imagick object
431 $content = $im->getImageBlob();
432
433 //Detect 32bit png (each pixel has tranparency level)
434 if (ord(substr($content, 25, 1)) & 4) {
435 //Fetch iterator
436 $p = $im->getPixelIterator();
437
438 //Loop on each row
439 foreach($p as $r) {
440 //Loop on each row pixel
441 foreach($r as $pix) {
442 //Check if pixel has partial transparency
443 if ($pix->getColorValue(Imagick::COLOR_ALPHA) != 1) {
444 return true;
445 }
446 }
447 }
448 //Check 8bit png transparency
449 } elseif (stripos($content, \'PLTE\') !== false || stripos($content, \'tRNS\') !== false) {
450 return true;
451 }
452
453 //Didn\'t found clue of transparency
454 return false;
455 }
456 ```
457
458 Cette fonction fonctionne avec les deux seules possibilités : PNG 8 et 32 bits.
459
460 Le premier cas est un PNG 32 bits avec transparence activée, on doit alors vérifier l\'opacité de chaque pixel savoir si l\'image a de la transparence ou non.
461
462 Le second cas est un PNG 8 bits, on a simplement à détecter un marqueur de transparence dans le contenu du fichier.
463
464 Dans cette configuration de fonction, on lit seulement une partie du PNG 32 bits jusqu\'à détection d\'un pixel transparent où on analyse le contenu jusqu\'à trouver un marqueur de transparence dans un PNG 8 bits.
465
466 Les pires cas seront un PNG 32 bits avec marqueur de transparence sans pixel transparent ou PNG 8 bits sans marqueur de transparence.
467
468 Selon les probabilités de rencontrer les différents cas de transparence vous pouvez être intéressé pour renverser l\'ordre des tests de cette fonction.
469
470 Un grand merci à ces articles qui expliquent plus en détail comment fonctionnent les différentes parties de ce code:
471 - <https://www.jonefox.com/blog/2011/04/15/how-to-detect-transparency-in-png-images>
472 - <http://camendesign.com/code/uth1_is-png-32bit>
473 - <https://stackoverflow.com/questions/5495275/how-to-check-if-an-image-has-transparency-using-gd>
474
475 En espérant que cela puisse aider quelques personnes.'
476 ]
477 ]
478 ],
479 [
480 'mail' => 'blog@rapsys.eu',
481 'keywords' => ['hateoas', 'http', 'rest', 'uri', 'varnish', 'webservice'],
482 'translations' => [
483 'en_gb' => [
484 'title' => 'Caching webservice with Varnish',
485 'description' => 'Cache a webservice responses by ignoring the Authorization header using Varnish.',
486 'body' => 'The primary goal is to find a way to reduce the load of a webservice by caching its responses.
487
488 The webservice is a RESTfull API serving as a gateway between a private HATEOAS API and a client generating more than 500 000 requests a day.
489
490 The first surprise is that if your well educated client, sending you a header Authorization: Bearer, will not be cached by default by Varnish !
491
492 Let\'s force back the standard behaviour with this header for our webservice uri prefix:
493
494 ```varnish
495 sub vcl_recv {
496 # Force cache response even with req.http.Authorization set
497 if (req.http.Authorization) {
498 if (req.url ~ "^/webservice/uri/prefix/") {
499 return (lookup);
500 }
501 }
502 }
503 ```
504
505 This has security implication, because anyone allowed to request varnish will be able to retrieve a cached result without authentification.
506
507 It is important to validate the Authorization header value before serving the result from cache.
508
509 Now, our webservice has three possibles answers :
510 - 200: the data in JSON
511 - 404: data was not found
512 - 410: data is not available anymore
513
514 Let\'s cache our results depending on the reponse code:
515
516 ```varnish
517 sub vcl_fetch {
518 if (req.url ~ "^/webservice/uri/prefix/") {
519 if (beresp.status == 404) {
520 set beresp.ttl = 7d;
521 }
522 if (beresp.status == 410) {
523 set beresp.ttl = 7d;
524 }
525 if (beresp.status == 200) {
526 set beresp.ttl = 24h;
527 }
528 }
529 }
530 ```
531
532 With this configuration, we divided by 5 the quantity of request on our gateway from the client who was not able to cache our result himself.'
533 ],
534 'fr_fr' => [
535 'title' => 'Mise en cache de webservice avec Varnish',
536 'description' => 'Mettre en cache les réponses d\'un service web en ignorant l’en-tête Authorization à l’aide de Varnish.',
537 'body' => 'Le but premier est de trouver une solution pour réduire la charge d\'un service web en mettant en cache ses réponses.
538
539 L\'API RESTfull du webservice sert de passerelle entre un API privé HATEOAS et un client générant plus de 500 000 requêtes par jour.
540
541 La première surprise est qu\'un client bien élevé, envoyant un en-tête Authorization: Bearer, ne sera pas mis en cache par Varnish par défaut !
542
543 Forçons le fonctionnement standard avec l\'en-tête pour le préfixe de l\'uri de notre webservice:
544
545 ```varnish
546 sub vcl_recv {
547 # Force la mise en cache de la réponse même avec req.http.Authorization présent
548 if (req.http.Authorization) {
549 if (req.url ~ "^/webservice/uri/prefix/") {
550 return (lookup);
551 }
552 }
553 }
554 ```
555
556 Ce changement a des conséquences sur la sécurité, puisque n\'importe quelle personne autorisée à interroger Varnish sera en mesure de récupérer un résultat en cache sans s\'identifier.
557
558 Il est important de valider la valeur de l\'en-tête Authorization avant de fournir le résultat depuis le cache.
559
560 Notre webservice a trois réponses possibles :
561 - 200: les données en JSON
562 - 404: données non trouvées
563 - 410: données plus jamais disponibles
564
565 Mettons en cache les résultats selon le code de retour :
566
567 ```varnish
568 sub vcl_fetch {
569 if (req.url ~ "^/webservice/uri/prefix/") {
570 if (beresp.status == 404) {
571 set beresp.ttl = 7d;
572 }
573 if (beresp.status == 410) {
574 set beresp.ttl = 7d;
575 }
576 if (beresp.status == 200) {
577 set beresp.ttl = 24h;
578 }
579 }
580 }
581 ```
582
583 Avec cette configuration, on a divisé par 5 la quantité de demandes sur notre passerelle pour le client qui n\'était pas en mesure de mettre en cache lui-même nos résultats.'
584 ]
585 ]
586 ],
587 [
588 'mail' => 'blog@rapsys.eu',
589 'keywords' => ['amazon', 'azure', 'cidr', 'http', 'microsoft', 'mysql', 'php', 'webservice', 'google'],
590 'translations' => [
591 'en_gb' => [
592 'title' => 'Dealing with IP range in PHP/MySQL',
593 'description' => 'Secure a webservice by granting access only to remote IP address included in a CIDR blocks set.',
594 'body' => 'The goal is to process some CIDR blocks to tighten a webservice security.
595
596 First let\'s see how to compute the first and last address of an IP range with just the block base IP and mask:
597
598 ```php
599 $range = [\'127.0.0.1\', 8];
600 function rangeBegin($range) {
601 return $range[0];
602 }
603 function rangeEnd($range) {
604 return long2ip(ip2long($range[0]) | ((1 << (32 - $range[1])) - 1));
605 }
606 ```
607
608 How to detect if an IP is present in a CIDR block:
609
610 ```php
611 $ip = \'127.0.0.1\';
612 $range = [\'127.0.0.1\', 8];
613 function ipInRange($ip, $range) {
614 if (ip2long($range[0]) <= ip2long($ip) && ip2long($ip) <= (ip2long($range[0]) | ((1 << (32 - $range[1])) - 1))) {
615 return true;
616 }
617 return false;
618 }
619 ```
620
621 As a first bonus, how to retrieve amazon IP ranges:
622
623 ```php
624 function fetchAmazonRange() {
625 //Init array
626 $amazonRanges = [];
627
628 $ctx = stream_context_create(
629 [
630 \'http\' => [
631 \'method\' => \'GET\',
632 \'max_redirects\' => 0,
633 \'timeout\' => 5,
634 \'ignore_errors\' => false,
635 \'header\' => [
636 \'Connection: close\',
637 \'Accept: application/json\'
638 ]
639 ]
640 ]
641 ];
642
643 //Fetch json
644 if (($json = file_get_contents(\'https://ip-ranges.amazonaws.com/ip-ranges.json\', false, $ctx)) === false) {
645 return null;
646 }
647
648 //Decode it
649 if (($json = json_decode($json)) === null || empty($json->prefixes)) {
650 return false;
651 }
652
653 //Deal with prefixes
654 foreach($json->prefixes as $range) {
655 //Skip ipv6 and invalid ranges
656 if (empty($range->ip_prefix)||!preg_match(\'/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\/([0-9]+)$/\', $range->ip_prefix, $matche
657 s)) {
658 continue;
659 }
660 //Remove whole match
661 array_shift($matches);
662 //Add ip and mask
663 $amazonRanges[] = $matches;
664 }
665
666 //Send back result
667 return $amazonRanges;
668 }
669 ```
670
671 Microsoft Azure IP ranges urls:
672 - <https://www.microsoft.com/en-us/download/details.aspx?id=41653>
673 - <https://msdn.microsoft.com/library/mt757330.aspx>
674
675 Google IP ranges urls:
676 - <https://support.google.com/a/answer/10026322?hl=en>'
677 ],
678 'fr_fr' => [
679 'title' => 'Gestion des plages d\'IP en PHP/MySQL',
680 'description' => 'Sécuriser un service web en accordant l\'accès uniquement à l\'adresse IP distante incluse dans un jeu de blocs CIDR.',
681 'body' => 'L\'objectif est de traiter des blocs CIDR pour renforcer la sécurité d’un service web.
682
683 Premièrement, voyons comment calculer la première et la dernière adresse IP d\'une plage (bloc CIDR) avec sa base et son masque :
684
685 ```php
686 $range = [\'127.0.0.1\', 8];
687 function rangeBegin($range) {
688 return $range[0];
689 }
690 function rangeEnd($range) {
691 return long2ip(ip2long($range[0]) | ((1 << (32 - $range[1])) - 1));
692 }
693 ```
694
695 Maintenant comment vérifier si une IP est présente dans une plage (bloc CIDR) :
696
697 ```php
698 $ip = \'127.0.0.1\';
699 $range = [\'127.0.0.1\', 8];
700 function ipInRange($ip, $range) {
701 if (ip2long($range[0]) <= ip2long($ip) && ip2long($ip) <= (ip2long($range[0]) | ((1 << (32 - $range[1])) - 1))) {
702 return true;
703 }
704 return false;
705 }
706 ```
707
708 En premier bonus, comment récupérer les plages d\'IP d\'amazon :
709
710 ```php
711 function fetchAmazonRange() {
712 //Init array
713 $amazonRanges = [];
714
715 $ctx = stream_context_create(
716 [
717 \'http\' => [
718 \'method\' => \'GET\',
719 \'max_redirects\' => 0,
720 \'timeout\' => 5,
721 \'ignore_errors\' => false,
722 \'header\' => [
723 \'Connection: close\',
724 \'Accept: application/json\'
725 ]
726 ]
727 ]
728 ];
729
730 //Fetch json
731 if (($json = file_get_contents(\'https://ip-ranges.amazonaws.com/ip-ranges.json\', false, $ctx)) === false) {
732 return null;
733 }
734
735 //Decode it
736 if (($json = json_decode($json)) === null || empty($json->prefixes)) {
737 return false;
738 }
739
740 //Deal with prefixes
741 foreach($json->prefixes as $range) {
742 //Skip ipv6 and invalid ranges
743 if (empty($range->ip_prefix)||!preg_match(\'/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\/([0-9]+)$/\', $range->ip_prefix, $matche
744 s)) {
745 continue;
746 }
747 //Remove whole match
748 array_shift($matches);
749 //Add ip and mask
750 $amazonRanges[] = $matches;
751 }
752
753 //Send back result
754 return $amazonRanges;
755 }
756 ```
757
758 Urls pour les plages d\'IP de Microsoft Azure :
759 - <https://www.microsoft.com/en-us/download/details.aspx?id=41653>
760 - <https://msdn.microsoft.com/library/mt757330.aspx>
761
762 Urls pour les plages d\'IP de Google :
763 - <https://support.google.com/a/answer/10026322?hl=en>'
764 ]
765 ]
766 ],
767 [
768 'mail' => 'blog@rapsys.eu',
769 'keywords' => ['apache', 'haproxy', 'http', 'mageia', 'proxy', 'quic'],
770 'translations' => [
771 'en_gb' => [
772 'title' => 'A load balancer to standardize access to heterogeneous web services',
773 'description' => 'Setup a single entry point for various web services including Apache and a few others available on exotic ports.',
774 'body' => 'The primary need is to set up a single entry point for different web services including Apache and a few others on exotic ports.
775
776 Like any new project, implementation, as usual, took longer than initially expected.
777
778 To this end, I packaged the HAProxy service in the Mageia Linux distribution with a transparent proxy configuration by default.
779
780 HAProxy does not support the certificate path layout used on Red Hat-derived distributions, where public and private keys are separated into two separate files in two different directories.
781
782 HAProxy suffers from a critical bug when using standard output for logging which resumes at the beginning of the file after a restart.
783
784 HAProxy is penalized by disabling the buffer on its standard output when used for logging.
785
786 Three fixes were made by me for this purpose to resolve these problems:
787 - support for certificate path layout for Red Hat distribution
788 - no longer starts at the beginning of the log file after a restart
789 - no longer deactivate the standard output buffer
790
791 New features are being developed for the next version of HAProxy that could make my choices and developments on logging obsolete.
792
793 To make support of the HTTP/3 protocol possible by HAProxy, it was necessary to integrate the quictls library, a branch of OpenSSL with support for the QUIC protocol.
794
795 To install the server:
796 ```bash
797 # urpmi haproxy haproxy-quic
798 ```
799
800 Activate the service:
801 ```bash
802 # systemctl enable haproxy.service
803 ```
804
805 Start the service:
806 ```bash
807 # systemctl start haproxy.service
808 ```
809
810 In order to avoid a waltz of ports when integrating into an existing architecture, port indirection in Shorewall is proposed:
811 ```shorewall
812 # Redirect tcp traffic from net on port 80 to 8000
813 REDIRECT net 8000 tcp 80
814 # Redirect tcp traffic from net on port 443 to 8000
815 REDIRECT net 8000 tcp 443
816 # Redirect udp traffic from net on port 443 to 8443
817 REDIRECT net 8443 udp 443
818 ```
819
820 This allows HAProxy to capture HTTP and HTTPS traffic on port 8000/TCP and QUIC on port 8443/UDP.
821
822 The default configuration captures HTTP and HTTPS traffic on the tcp_default front end, then switches it to the tcp_http and tcp_https back ends, the latter will send the flows to the http_default and https_default front ends, taking care to add a proxy header to transmit the original IP address and port. This configuration allows other TCP services (SSH, VPN, etc.) to be captured and redistributed as needed.
823
824 The traffic is then received by the http_default and https_default front-ends respectively, the original IP address and port is preserved via the proxy header received from the TCP back-ends.
825
826 They can advertise HTTP/3 support, clean up some headers, deny access to bots, forward original IP addresses and ports via a Forwarded header, and distribute to different backends based on hosts and requested paths.
827
828 Configuring Apache to receive the proxy header is problematic, it\'s simpler to pass the Forwarded header than to configure the freshly released mod_remoteip.
829
830 Support for the HTTP/3 protocol is announced by sending an alt-svc: h3="<host>:<port>"; ma=<timeout> where host can be empty, port=443 and timeout=3600 for example. The certificate served must match the initial host name and not the offload server for security reasons.
831
832 Configuring the different http backends will come down to declaring the offload servers, the additional headers to add, the compression according to the content types and the method to test their health.
833
834 Without further ado, the configuration to adapt to your needs:
835 ```haproxy
836 # HAProxy configuration file
837
838 # Global config
839 global
840 # Log to systemd
841 log stdout format short daemon
842 # Number of threads
843 nbthread 8
844 # Pid file
845 pidfile /run/haproxy/haproxy.pid
846
847 # Stat socket
848 stats socket /run/haproxy/haproxy.sock mode 0660 level admin
849 # Stat timeout
850 stats timeout 10s
851 # Max connections
852 stats maxconn 10
853
854 # Certificate base dir
855 crt-base /etc/pki/tls/certs
856 # Private key base dir
857 key-base /etc/pki/tls/private
858 # Don\'t load extra files
859 ssl-load-extra-files none
860 # Disable SSL-v3 TLSv1.0 TLSv1.1 and TLS tickets
861 ssl-default-server-options ssl-min-ver TLSv1.2 no-tls-tickets
862 # Do not verify certificate
863 ssl-server-verify none
864 # Supported bind ciphers
865 #XXX: https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended-configurations
866 ssl-default-bind-ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
867
868 # SSL/TLS session cache size
869 tune.ssl.cachesize 20000
870 # SSL/TLS session life time in cache
871 tune.ssl.lifetime 300
872 # SSL/TLS layer maximum passed bytes at a time
873 tune.ssl.maxrecord 0
874 # Diffie-Hellman ephemeral keys max size
875 tune.ssl.default-dh-param 2048
876 # Buffer size
877 tune.bufsize 16384
878 # Reserved buffer size
879 tune.maxrewrite 1024
880 # Max number of headers
881 tune.http.maxhdr 101
882
883
884 # Default config
885 defaults
886 # Use global log
887 log global
888
889 # Set balance mode
890 balance random
891 # Set http mode
892 mode http
893 # Set http keep alive mode
894 #XXX: https://cbonte.github.io/haproxy-dconv/2.3/configuration.html#4
895 option http-keep-alive
896 # Dont log empty line
897 option dontlognull
898 # Dissociate client from dead server
899 option redispatch
900
901 # Number of retries on connection failure
902 retries 3
903 # Max concurrent connections
904 maxconn 30000
905 # Max pending connections
906 backlog 30000
907
908 # Max time for a connection attempt
909 timeout connect 5s
910 # Max inactivitiy time on client side
911 timeout client 60s
912 # Max inactivitiy time on server side
913 timeout server 60s
914 # Max inactivitiy time on client and server for tunnels
915 timeout tunnel 3600s
916 # Max time for new request
917 timeout http-keep-alive 1s
918 # Max time for a complete http request
919 timeout http-request 15s
920 # Max time for a free slot
921 timeout queue 5s
922 # Duration for tarpitted connections
923 timeout tarpit 5s
924
925 # Error documents
926 errorfile 400 /usr/share/doc/haproxy/error/400.http
927 errorfile 403 /usr/share/doc/haproxy/error/403.http
928 errorfile 408 /usr/share/doc/haproxy/error/408.http
929 errorfile 500 /usr/share/doc/haproxy/error/500.http
930 errorfile 502 /usr/share/doc/haproxy/error/502.http
931 errorfile 503 /usr/share/doc/haproxy/error/503.http
932 errorfile 504 /usr/share/doc/haproxy/error/504.http
933
934
935 # Default tcp frontend
936 frontend tcp_default
937 # Set tcp mode
938 mode tcp
939 # Bind to 8000 port
940 bind :::8000
941 # Log disabled
942 no log
943 # Set tcp log
944 #option tcplog
945 # Set inspect delay
946 tcp-request inspect-delay 5s
947 # Wait for extension detection
948 #tcp-request content accept if { req.proto_http } or { req.ssl_hello_type 1 } or { req.ssl_ec_ext 1 }
949 tcp-request content accept if { req.proto_http } or { req.ssl_hello_type 1 }
950
951 # Send to https tcp backend
952 use_backend tcp_https if { req.ssl_hello_type 1 }
953
954 # Send to ec tcp backend
955 #use_backend tcp_ec if { req.ssl_ec_ext 1 }
956
957 # Send to OpenVPN backend
958 #acl openvpn payload(0,2) -m bin 003c
959 #tcp-request content accept if openvpn
960 #use_backend tcp_openvpn if openvpn
961
962 # Send to OpenSSH backend
963 #XXX: https://jonnyzzz.com/blog/2017/05/24/ssh-haproxy/
964 #XXX: https://issues.apache.org/jira/browse/SSHD-656
965 #acl ssh payload(0,7) -m str SSH-2.0
966 #tcp-request content accept if ssh
967 #use_backend tcp_ssh if ssh
968
969 # Send to http tcp backend (if { req.proto_http })
970 default_backend tcp_http
971
972
973 # Http tcp backend
974 backend tcp_http
975 # Set tcp mode
976 mode tcp
977 # Send to localhost without ssl with v2 proxy header
978 server haproxy 127.0.0.1:8080 no-ssl verify none send-proxy-v2
979
980
981 # Https tcp backend
982 backend tcp_https
983 # Set tcp mode
984 mode tcp
985 # Send to localhost without ssl with v2 proxy header
986 server haproxy 127.0.0.1:8443 no-ssl verify none send-proxy-v2-ssl
987
988
989 # Default http frontend
990 frontend http_default
991 # Bind to 8080 port
992 bind :::8080 accept-proxy
993 # Insert X-Forwarded-For header
994 option forwardfor
995 # Set http log format
996 option httplog
997 # Log enabled
998 log global
999
1000 # Check if acme challenge
1001 acl acme_challenge path_beg /.well-known/acme-challenge/
1002
1003 # Add X-Backend header
1004 #http-response add-header X-Backend %[haproxy.backend_name]
1005
1006 # Advertise QUIC
1007 #http-response add-header alt-svc \'h3=":443"; ma=3600\'
1008 #http-after-response add-header alt-svc \'h3=":443"; ma=3600\'
1009
1010 # Remove server and x-powered-by headers
1011 #http-after-response del-header server
1012 #http-after-response del-header x-powered-by
1013
1014 # Redirect to https scheme when unsecure and not acme challenge
1015 http-request redirect scheme https code 302 unless { ssl_fc } || acme_challenge
1016
1017 # Check if denied path
1018 #XXX: use ,url_dec like in https://serverfault.com/questions/754752/block-specific-url-in-haproxy-url-encoding
1019 #acl denied_path path_reg ^/(\.env|login|admin/|wp-login\.php|\.git/config)$
1020 # Deny access on denied path
1021 #http-request deny if denied_path
1022
1023 # Check if protected path
1024 #acl protected_path path_reg ^/(contact|register)$
1025 # Deny access on protected path
1026 #http-request deny deny_status 503 if protected_path { method \'POST\' } { req.ver \'1.0\' }
1027
1028 # Store origin variable as txn
1029 http-request set-var(txn.origin) req.hdr(Origin)
1030 # Store host variable as txn
1031 http-request set-var(txn.host) req.hdr(Host),field(1,:),lower
1032 # Store proto variable as txn
1033 http-request set-var(txn.proto) ssl_fc,iif(https,http)
1034
1035 # Set forwarded proto
1036 http-request set-header X-Forwarded-Proto %[var(txn.proto)]
1037 # Set forwarded port
1038 http-request set-header X-Forwarded-Port %[dst_port]
1039 # Set forwarded for
1040 #http-request set-header X-Forwarded-For %[src]
1041 # Set forwarded by
1042 http-request set-header X-Forwarded-By %[dst]
1043
1044 # Set forwarded
1045 #http-request set-header Forwarded by=%[dst]:%[dst_port];for=%[src]:%[src_port];host=%[var(txn.host)];proto=%[var(txn.proto)]
1046 http-request set-header Forwarded by=%[dst]:%[dst_port];for=%[src]:%[src_port];proto=%[var(txn.proto)]
1047
1048 # Check if host is cdn.example.com
1049 acl cdn var(txn.host) -m str cdn.example.com
1050 # Check if cdn css path
1051 acl cdn_css path_beg /css
1052 # Check if cdn js path
1053 acl cdn_js path_beg /js
1054 # Check if haproxy status path
1055 acl haproxy_status path_beg /haproxy-status
1056 # Check if debug path
1057 acl debug path_beg /debug
1058
1059 # Send to css backend if path start with /css
1060 use_backend http_css if cdn cdn_css
1061 # Send to js backend if path start with /js
1062 use_backend http_js if cdn cdn_js
1063 # Send to status backend if path start with /haproxy-status
1064 use_backend http_status if haproxy_status
1065 # Send to debug backend if path start with /debug
1066 use_backend http_debug if debug
1067
1068 # Send to https backend
1069 use_backend https_default if { ssl_fc }
1070
1071 # Send to default backend
1072 default_backend http_default
1073
1074 # Default https frontend
1075 #XXX: copy of upper one, just done to skip logs here
1076 frontend https_default
1077 # Bind to 8443 tcp port as ssl
1078 bind :::8443 ssl crt haproxy.pem alpn h2,http/1.1,http/1.0 accept-proxy
1079 # Bind to 8443 udp port as ssl
1080 #bind quic6@:::8443 ssl crt haproxy.pem alpn h3
1081 # Insert X-Forwarded-For header
1082 option forwardfor
1083 # Set http log format
1084 option httplog
1085 # Log enabled
1086 log global
1087
1088 # Check if acme challenge
1089 acl acme_challenge path_beg /.well-known/acme-challenge/
1090
1091 # Add X-Backend header
1092 #http-response add-header X-Backend %[haproxy.backend_name]
1093
1094 # Advertise QUIC
1095 #http-response add-header alt-svc \'h3=":443"; ma=3600\'
1096 #http-after-response add-header alt-svc \'h3=":443"; ma=3600\'
1097
1098 # Remove server and x-powered-by headers
1099 #http-after-response del-header server
1100 #http-after-response del-header x-powered-by
1101
1102 # Redirect to https scheme when unsecure and not acme challenge
1103 http-request redirect scheme https code 302 unless { ssl_fc } || acme_challenge
1104
1105 # Check if denied path
1106 #XXX: use ,url_dec like in https://serverfault.com/questions/754752/block-specific-url-in-haproxy-url-encoding
1107 #acl denied_path path_reg ^/(\.env|login|admin/|wp-login\.php|\.git/config)$
1108 # Deny access on denied path
1109 #http-request deny if denied_path
1110
1111 # Check if protected path
1112 #acl protected_path path_reg ^/(contact|register)$
1113 # Deny access on protected path
1114 #http-request deny deny_status 503 if protected_path { method \'POST\' } { req.ver \'1.0\' }
1115
1116 # Store origin variable as txn
1117 http-request set-var(txn.origin) req.hdr(Origin)
1118 # Store host variable as txn
1119 http-request set-var(txn.host) req.hdr(Host),field(1,:),lower
1120 # Store proto variable as txn
1121 http-request set-var(txn.proto) ssl_fc,iif(https,http)
1122
1123 # Set forwarded proto
1124 http-request set-header X-Forwarded-Proto %[var(txn.proto)]
1125 # Set forwarded port
1126 http-request set-header X-Forwarded-Port %[dst_port]
1127 # Set forwarded for
1128 #http-request set-header X-Forwarded-For %[src]
1129 # Set forwarded by
1130 http-request set-header X-Forwarded-By %[dst]
1131
1132 # Set forwarded
1133 #http-request set-header Forwarded by=%[dst]:%[dst_port];for=%[src]:%[src_port];host=%[var(txn.host)];proto=%[var(txn.proto)]
1134 http-request set-header Forwarded by=%[dst]:%[dst_port];for=%[src]:%[src_port];proto=%[var(txn.proto)]
1135
1136 # Check if host is cdn.example.com
1137 acl cdn var(txn.host) -m str cdn.example.com
1138 # Check if cdn css path
1139 acl cdn_css path_beg /css
1140 # Check if cdn js path
1141 acl cdn_js path_beg /js
1142 # Check if haproxy status path
1143 acl haproxy_status path_beg /haproxy-status
1144 # Check if debug path
1145 acl debug path_beg /debug
1146
1147 # Send to css backend if path start with /css
1148 use_backend http_css if cdn cdn_css
1149 # Send to js backend if path start with /js
1150 use_backend http_js if cdn cdn_js
1151 # Send to status backend if path start with /haproxy-status
1152 use_backend http_status if haproxy_status
1153 # Send to debug backend if path start with /debug
1154 use_backend http_debug if debug
1155
1156 # Send to https backend
1157 use_backend https_default if { ssl_fc }
1158
1159 # Send to default backend
1160 default_backend http_default
1161
1162
1163 # Debug http backend
1164 backend http_debug
1165 # Check if trusted
1166 acl trusted src 127.0.0.0/8 ::1
1167 # Allow access from trusted only
1168 http-request deny unless trusted
1169 # Server without ssl or check
1170 server debug 127.0.0.1:8090 no-ssl verify none
1171
1172
1173 # Default http backend
1174 backend http_default
1175 # Enable check
1176 option httpchk
1177 # User server default
1178 http-check connect default
1179 # Send HEAD on / with protocol HTTP/1.1 for host example.com
1180 http-check send meth HEAD uri / ver HTTP/1.1 hdr Host example.com
1181 # Expect return code between 200 and 399
1182 http-check expect status 200-399
1183
1184 # Insert header X-Server: apache
1185 #http-response add-header X-Server apache
1186
1187 # Set compression algorithm
1188 #compression algo gzip
1189 # Enable compression for html, plain and css text types
1190 #compression type text/html text/plain text/css
1191
1192 # Server with ssl and check without certificate verification
1193 server apache 127.0.0.1:80 no-ssl verify none check #cookie apache
1194
1195
1196 # Default https backend
1197 backend https_default
1198 # Enable check
1199 option httpchk
1200 # User server default
1201 http-check connect default
1202 # Send HEAD on / with protocol HTTP/1.1 for host example.com
1203 http-check send meth HEAD uri / ver HTTP/1.1 hdr Host example.com
1204 # Expect return code between 200 and 399
1205 http-check expect status 200-399
1206
1207 # Insert header X-Server: apache
1208 #http-response add-header X-Server apache
1209
1210 # Force HSTS for 5 minutes on domain and all subdomains
1211 #http-response set-header Strict-Transport-Security max-age=300#;\ includeSubDomains#;\ preload
1212
1213 # Set compression algorithm
1214 #compression algo gzip
1215 # Enable compression for html, plain and css text types
1216 #compression type text/html text/plain text/css
1217
1218 # Server with ssl and check without certificate verification
1219 server apache 127.0.0.1:443 ssl verify none check #cookie apache
1220
1221
1222 # Css http backend
1223 backend http_css
1224 # Enable check
1225 option httpchk
1226 # User server default
1227 http-check connect default
1228 # Send GET on /css/empty.css with protocol HTTP/1.1 for host cdn.example.com
1229 http-check send meth GET uri /css/empty.css ver HTTP/1.1 hdr Host cdn.example.com
1230 # Expect return code between 200 and 399
1231 http-check expect status 200-399
1232
1233 # Server with check without ssl and certificate verification
1234 server css 127.0.0.1:80 no-ssl verify none check
1235
1236
1237 # Js http backend
1238 backend http_js
1239 # Enable check
1240 option httpchk
1241 # User server default
1242 http-check connect default
1243 # Send HEAD on /js/missing.js with protocol HTTP/1.1 for host cdn.example.com
1244 http-check send meth HEAD uri /js/missing.js ver HTTP/1.1 hdr Host cdn.example.com
1245 # Expect return code 404
1246 http-check expect status 404
1247
1248 # Check if txn.origin start with https://cdn.example.com
1249 acl cdn_origin var(txn.origin) -m beg https://cdn.example.com
1250 # Send origin as ACAO
1251 http-response set-header Access-Control-Allow-Origin %[var(txn.origin)] if cdn_origin
1252 # Set ACMA for one day
1253 http-response set-header Access-Control-Max-Age 86400 if cdn_origin
1254
1255 # Server with check without ssl and certificate verification
1256 server js 127.0.0.1:80 no-ssl verify none check
1257
1258
1259 # Status user list
1260 userlist status
1261 # Add user admin
1262 user admin insecure-password ADMINPASSWORD
1263 # Add user operator
1264 user operator insecure-password OPERATORPASSWORD
1265 # Assign admin in admin group
1266 group admin users admin
1267 # Assign operator and admin in operator group
1268 group operator users operator,admin
1269
1270
1271 # Status http backend
1272 backend http_status
1273 # Add operator acl
1274 acl is_operator http_auth(status)
1275 # Add admin acl
1276 acl is_admin http_auth_group(status) admin
1277 # Check if trusted
1278 acl trusted src 127.0.0.0/8 ::1
1279 # Enable stats
1280 stats enable
1281 # Set stats hook on /haproxy-status
1282 stats uri /haproxy-status
1283 # Set refresh time
1284 stats refresh 10s
1285 # Display legends
1286 stats show-legends
1287 # Display node
1288 stats show-node
1289 # Allow access from trusted or authentified operator only
1290 #stats http-request auth unless trusted or is_operator
1291 stats http-request auth unless trusted
1292 # Activate admin interface from trusted or authentified admin only
1293 #stats admin if is_admin
1294 ```
1295
1296 Hope this article was helpful to you.'
1297 ],
1298 'fr_fr' => [
1299 'title' => 'Un équilibreur de charge pour standardiser l\'accès aux services web hétérogènes',
1300 'description' => 'Configurer un point d\'entrée unique pour différents services web dont Apache et quelques autres disponibles sur des ports exotiques.',
1301 'body' => 'Le besoin principal est de mettre en place un point d\'entrée unique pour différents services web dont Apache et quelques autres sur des ports exotiques.
1302
1303 Comme tout nouveau projet, la mise en œuvre, comme d\'habitude, a nécessité plus de temps que prévu initialement.
1304
1305 À cet effet, j\'ai réalisé la mise en paquet du service HAProxy dans la distribution linux Mageia avec une configuration de proxy transparent par défaut.
1306
1307 HAProxy ne prend pas en charge la disposition de chemin des certificats utilisée sur les distributions dérivées de Red Hat, où clef publique et privée sont séparées en deux fichiers distincts dans deux répertoires différents.
1308
1309 HAProxy souffre d\'un bug critique lors de l\'usage de la sortie standard pour la journalisation qui reprend en début de fichier après un redémarage.
1310
1311 HAProxy est pénalisé par la désactivation du tampon sur sa sortie standard lorsqu\'elle est utilisée pour la journalisation.
1312
1313 Trois correctifs ont été réalisé par mes soins à cet effet pour résoudre ces problèmes sur le chemin de mes besoins :
1314 - prise en charge de la disposition de chemin des certificats pour distribution Red Hat
1315 - ne recommence plus au début du fichier journal après un redémarage
1316 - ne plus désactiver le tampon de la sortie standard
1317
1318 De nouvelles fonctionnalités sont en cours de développement pour la prochaine version d\'HAProxy qui pourraient rendre obsolètes mes choix et développements sur la journalisation.
1319
1320 Pour rendre possible la prise en charge du protocole HTTP/3 par HAProxy, il a été nécessaire d\'intégrer la librairie quictls, une branche d\'OpenSSL avec support du protocole QUIC.
1321
1322 Pour installer le serveur :
1323 ```bash
1324 # urpmi haproxy haproxy-quic
1325 ```
1326
1327 Activer le service :
1328 ```bash
1329 # systemctl enable haproxy.service
1330 ```
1331
1332 Démarrer le service :
1333 ```bash
1334 # systemctl start haproxy.service
1335 ```
1336
1337 Afin d\'éviter une valse de ports lors de l\'intégration dans une architecture existante, l\'indirection de ports dans Shorewall est proposée :
1338 ```shorewall
1339 # Redirect tcp traffic from net on port 80 to 8000
1340 REDIRECT net 8000 tcp 80
1341 # Redirect tcp traffic from net on port 443 to 8000
1342 REDIRECT net 8000 tcp 443
1343 # Redirect udp traffic from net on port 443 to 8443
1344 REDIRECT net 8443 udp 443
1345 ```
1346
1347 Cela permet à HAProxy de capturer le trafic HTTP et HTTPS sur le port 8000/TCP et QUIC sur le port 8443/UDP.
1348
1349 La configuration par défaut capture le trafic HTTP et HTTPS sur le frontal tcp_default, puis le bascule sur les dorsaux tcp_http et tcp_https, ces derniers enverront les flux sur les frontaux http_default et https_default, en prenant soin d\'ajouter un en-tête proxy pour transmettre l\'adresse IP et le port d\'origine. Cette configuration permet à d\'autre services TCP (SSH, VPN, etc) d\'être capturés et redistribués au besoin.
1350
1351 Le trafic est ensuite reçu respectivement par les frontaux http_default et https_default, l\'adresse IP et le port d\'origine est conservée via l\'en-tête proxy reçu des dorsaux TCP.
1352
1353 Ils peuvent annoncer la prise en charge de HTTP/3, nettoyer certains en-têtes, refuser l\'accès aux robots, transférer les adresses IP et ports d\'origine via un en-tête Forwarded et distribuer à différents dorsaux en fonction des hôtes et chemins demandés.
1354
1355 Configurer Apache pour recevoir l\'en-tête proxy est problématique, il est plus simple de transmettre l\'en-tête Forwarded que de configurer le fraîchement publié mod_remoteip.
1356
1357 La prise en charge du protocole HTTP/3 est annoncée par l\'envoi d\'un en-tête alt-svc: h3="<host>:<port>"; ma=<timeout> où host peut être vide, port=443 et timeout=3600 par exemple. Le certificat servi doit correspondre au nom d\'hôte initial et non à celui du serveur de déchargement pour des raisons de sécurité.
1358
1359 Configurer les différents backends http se résumera à déclarer les serveurs de déchargement, les en-têtes supplémentaires à ajouter, la compression selon les types de contenus et la méthode pour tester leur santé.
1360
1361 Sans plus attendre la configuration à adapter à vos besoins :
1362 ```haproxy
1363 # HAProxy configuration file
1364
1365 # Global config
1366 global
1367 # Log to systemd
1368 log stdout format short daemon
1369 # Number of threads
1370 nbthread 8
1371 # Pid file
1372 pidfile /run/haproxy/haproxy.pid
1373
1374 # Stat socket
1375 stats socket /run/haproxy/haproxy.sock mode 0660 level admin
1376 # Stat timeout
1377 stats timeout 10s
1378 # Max connections
1379 stats maxconn 10
1380
1381 # Certificate base dir
1382 crt-base /etc/pki/tls/certs
1383 # Private key base dir
1384 key-base /etc/pki/tls/private
1385 # Don\'t load extra files
1386 ssl-load-extra-files none
1387 # Disable SSL-v3 TLSv1.0 TLSv1.1 and TLS tickets
1388 ssl-default-server-options ssl-min-ver TLSv1.2 no-tls-tickets
1389 # Do not verify certificate
1390 ssl-server-verify none
1391 # Supported bind ciphers
1392 #XXX: https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended-configurations
1393 ssl-default-bind-ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
1394
1395 # SSL/TLS session cache size
1396 tune.ssl.cachesize 20000
1397 # SSL/TLS session life time in cache
1398 tune.ssl.lifetime 300
1399 # SSL/TLS layer maximum passed bytes at a time
1400 tune.ssl.maxrecord 0
1401 # Diffie-Hellman ephemeral keys max size
1402 tune.ssl.default-dh-param 2048
1403 # Buffer size
1404 tune.bufsize 16384
1405 # Reserved buffer size
1406 tune.maxrewrite 1024
1407 # Max number of headers
1408 tune.http.maxhdr 101
1409
1410
1411 # Default config
1412 defaults
1413 # Use global log
1414 log global
1415
1416 # Set balance mode
1417 balance random
1418 # Set http mode
1419 mode http
1420 # Set http keep alive mode
1421 #XXX: https://cbonte.github.io/haproxy-dconv/2.3/configuration.html#4
1422 option http-keep-alive
1423 # Dont log empty line
1424 option dontlognull
1425 # Dissociate client from dead server
1426 option redispatch
1427
1428 # Number of retries on connection failure
1429 retries 3
1430 # Max concurrent connections
1431 maxconn 30000
1432 # Max pending connections
1433 backlog 30000
1434
1435 # Max time for a connection attempt
1436 timeout connect 5s
1437 # Max inactivitiy time on client side
1438 timeout client 60s
1439 # Max inactivitiy time on server side
1440 timeout server 60s
1441 # Max inactivitiy time on client and server for tunnels
1442 timeout tunnel 3600s
1443 # Max time for new request
1444 timeout http-keep-alive 1s
1445 # Max time for a complete http request
1446 timeout http-request 15s
1447 # Max time for a free slot
1448 timeout queue 5s
1449 # Duration for tarpitted connections
1450 timeout tarpit 5s
1451
1452 # Error documents
1453 errorfile 400 /usr/share/doc/haproxy/error/400.http
1454 errorfile 403 /usr/share/doc/haproxy/error/403.http
1455 errorfile 408 /usr/share/doc/haproxy/error/408.http
1456 errorfile 500 /usr/share/doc/haproxy/error/500.http
1457 errorfile 502 /usr/share/doc/haproxy/error/502.http
1458 errorfile 503 /usr/share/doc/haproxy/error/503.http
1459 errorfile 504 /usr/share/doc/haproxy/error/504.http
1460
1461
1462 # Default tcp frontend
1463 frontend tcp_default
1464 # Set tcp mode
1465 mode tcp
1466 # Bind to 8000 port
1467 bind :::8000
1468 # Log disabled
1469 no log
1470 # Set tcp log
1471 #option tcplog
1472 # Set inspect delay
1473 tcp-request inspect-delay 5s
1474 # Wait for extension detection
1475 #tcp-request content accept if { req.proto_http } or { req.ssl_hello_type 1 } or { req.ssl_ec_ext 1 }
1476 tcp-request content accept if { req.proto_http } or { req.ssl_hello_type 1 }
1477
1478 # Send to https tcp backend
1479 use_backend tcp_https if { req.ssl_hello_type 1 }
1480
1481 # Send to ec tcp backend
1482 #use_backend tcp_ec if { req.ssl_ec_ext 1 }
1483
1484 # Send to OpenVPN backend
1485 #acl openvpn payload(0,2) -m bin 003c
1486 #tcp-request content accept if openvpn
1487 #use_backend tcp_openvpn if openvpn
1488
1489 # Send to OpenSSH backend
1490 #XXX: https://jonnyzzz.com/blog/2017/05/24/ssh-haproxy/
1491 #XXX: https://issues.apache.org/jira/browse/SSHD-656
1492 #acl ssh payload(0,7) -m str SSH-2.0
1493 #tcp-request content accept if ssh
1494 #use_backend tcp_ssh if ssh
1495
1496 # Send to http tcp backend (if { req.proto_http })
1497 default_backend tcp_http
1498
1499
1500 # Http tcp backend
1501 backend tcp_http
1502 # Set tcp mode
1503 mode tcp
1504 # Send to localhost without ssl with v2 proxy header
1505 server haproxy 127.0.0.1:8080 no-ssl verify none send-proxy-v2
1506
1507
1508 # Https tcp backend
1509 backend tcp_https
1510 # Set tcp mode
1511 mode tcp
1512 # Send to localhost without ssl with v2 proxy header
1513 server haproxy 127.0.0.1:8443 no-ssl verify none send-proxy-v2-ssl
1514
1515
1516 # Default http frontend
1517 frontend http_default
1518 # Bind to 8080 port
1519 bind :::8080 accept-proxy
1520 # Insert X-Forwarded-For header
1521 option forwardfor
1522 # Set http log format
1523 option httplog
1524 # Log enabled
1525 log global
1526
1527 # Check if acme challenge
1528 acl acme_challenge path_beg /.well-known/acme-challenge/
1529
1530 # Add X-Backend header
1531 #http-response add-header X-Backend %[haproxy.backend_name]
1532
1533 # Advertise QUIC
1534 #http-response add-header alt-svc \'h3=":443"; ma=3600\'
1535 #http-after-response add-header alt-svc \'h3=":443"; ma=3600\'
1536
1537 # Remove server and x-powered-by headers
1538 #http-after-response del-header server
1539 #http-after-response del-header x-powered-by
1540
1541 # Redirect to https scheme when unsecure and not acme challenge
1542 http-request redirect scheme https code 302 unless { ssl_fc } || acme_challenge
1543
1544 # Check if denied path
1545 #XXX: use ,url_dec like in https://serverfault.com/questions/754752/block-specific-url-in-haproxy-url-encoding
1546 #acl denied_path path_reg ^/(\.env|login|admin/|wp-login\.php|\.git/config)$
1547 # Deny access on denied path
1548 #http-request deny if denied_path
1549
1550 # Check if protected path
1551 #acl protected_path path_reg ^/(contact|register)$
1552 # Deny access on protected path
1553 #http-request deny deny_status 503 if protected_path { method \'POST\' } { req.ver \'1.0\' }
1554
1555 # Store origin variable as txn
1556 http-request set-var(txn.origin) req.hdr(Origin)
1557 # Store host variable as txn
1558 http-request set-var(txn.host) req.hdr(Host),field(1,:),lower
1559 # Store proto variable as txn
1560 http-request set-var(txn.proto) ssl_fc,iif(https,http)
1561
1562 # Set forwarded proto
1563 http-request set-header X-Forwarded-Proto %[var(txn.proto)]
1564 # Set forwarded port
1565 http-request set-header X-Forwarded-Port %[dst_port]
1566 # Set forwarded for
1567 #http-request set-header X-Forwarded-For %[src]
1568 # Set forwarded by
1569 http-request set-header X-Forwarded-By %[dst]
1570
1571 # Set forwarded
1572 #http-request set-header Forwarded by=%[dst]:%[dst_port];for=%[src]:%[src_port];host=%[var(txn.host)];proto=%[var(txn.proto)]
1573 http-request set-header Forwarded by=%[dst]:%[dst_port];for=%[src]:%[src_port];proto=%[var(txn.proto)]
1574
1575 # Check if host is cdn.example.com
1576 acl cdn var(txn.host) -m str cdn.example.com
1577 # Check if cdn css path
1578 acl cdn_css path_beg /css
1579 # Check if cdn js path
1580 acl cdn_js path_beg /js
1581 # Check if haproxy status path
1582 acl haproxy_status path_beg /haproxy-status
1583 # Check if debug path
1584 acl debug path_beg /debug
1585
1586 # Send to css backend if path start with /css
1587 use_backend http_css if cdn cdn_css
1588 # Send to js backend if path start with /js
1589 use_backend http_js if cdn cdn_js
1590 # Send to status backend if path start with /haproxy-status
1591 use_backend http_status if haproxy_status
1592 # Send to debug backend if path start with /debug
1593 use_backend http_debug if debug
1594
1595 # Send to https backend
1596 use_backend https_default if { ssl_fc }
1597
1598 # Send to default backend
1599 default_backend http_default
1600
1601 # Default https frontend
1602 #XXX: copy of upper one, just done to skip logs here
1603 frontend https_default
1604 # Bind to 8443 tcp port as ssl
1605 bind :::8443 ssl crt haproxy.pem alpn h2,http/1.1,http/1.0 accept-proxy
1606 # Bind to 8443 udp port as ssl
1607 #bind quic6@:::8443 ssl crt haproxy.pem alpn h3
1608 # Insert X-Forwarded-For header
1609 option forwardfor
1610 # Set http log format
1611 option httplog
1612 # Log enabled
1613 log global
1614
1615 # Check if acme challenge
1616 acl acme_challenge path_beg /.well-known/acme-challenge/
1617
1618 # Add X-Backend header
1619 #http-response add-header X-Backend %[haproxy.backend_name]
1620
1621 # Advertise QUIC
1622 #http-response add-header alt-svc \'h3=":443"; ma=3600\'
1623 #http-after-response add-header alt-svc \'h3=":443"; ma=3600\'
1624
1625 # Remove server and x-powered-by headers
1626 #http-after-response del-header server
1627 #http-after-response del-header x-powered-by
1628
1629 # Redirect to https scheme when unsecure and not acme challenge
1630 http-request redirect scheme https code 302 unless { ssl_fc } || acme_challenge
1631
1632 # Check if denied path
1633 #XXX: use ,url_dec like in https://serverfault.com/questions/754752/block-specific-url-in-haproxy-url-encoding
1634 #acl denied_path path_reg ^/(\.env|login|admin/|wp-login\.php|\.git/config)$
1635 # Deny access on denied path
1636 #http-request deny if denied_path
1637
1638 # Check if protected path
1639 #acl protected_path path_reg ^/(contact|register)$
1640 # Deny access on protected path
1641 #http-request deny deny_status 503 if protected_path { method \'POST\' } { req.ver \'1.0\' }
1642
1643 # Store origin variable as txn
1644 http-request set-var(txn.origin) req.hdr(Origin)
1645 # Store host variable as txn
1646 http-request set-var(txn.host) req.hdr(Host),field(1,:),lower
1647 # Store proto variable as txn
1648 http-request set-var(txn.proto) ssl_fc,iif(https,http)
1649
1650 # Set forwarded proto
1651 http-request set-header X-Forwarded-Proto %[var(txn.proto)]
1652 # Set forwarded port
1653 http-request set-header X-Forwarded-Port %[dst_port]
1654 # Set forwarded for
1655 #http-request set-header X-Forwarded-For %[src]
1656 # Set forwarded by
1657 http-request set-header X-Forwarded-By %[dst]
1658
1659 # Set forwarded
1660 #http-request set-header Forwarded by=%[dst]:%[dst_port];for=%[src]:%[src_port];host=%[var(txn.host)];proto=%[var(txn.proto)]
1661 http-request set-header Forwarded by=%[dst]:%[dst_port];for=%[src]:%[src_port];proto=%[var(txn.proto)]
1662
1663 # Check if host is cdn.example.com
1664 acl cdn var(txn.host) -m str cdn.example.com
1665 # Check if cdn css path
1666 acl cdn_css path_beg /css
1667 # Check if cdn js path
1668 acl cdn_js path_beg /js
1669 # Check if haproxy status path
1670 acl haproxy_status path_beg /haproxy-status
1671 # Check if debug path
1672 acl debug path_beg /debug
1673
1674 # Send to css backend if path start with /css
1675 use_backend http_css if cdn cdn_css
1676 # Send to js backend if path start with /js
1677 use_backend http_js if cdn cdn_js
1678 # Send to status backend if path start with /haproxy-status
1679 use_backend http_status if haproxy_status
1680 # Send to debug backend if path start with /debug
1681 use_backend http_debug if debug
1682
1683 # Send to https backend
1684 use_backend https_default if { ssl_fc }
1685
1686 # Send to default backend
1687 default_backend http_default
1688
1689
1690 # Debug http backend
1691 backend http_debug
1692 # Check if trusted
1693 acl trusted src 127.0.0.0/8 ::1
1694 # Allow access from trusted only
1695 http-request deny unless trusted
1696 # Server without ssl or check
1697 server debug 127.0.0.1:8090 no-ssl verify none
1698
1699
1700 # Default http backend
1701 backend http_default
1702 # Enable check
1703 option httpchk
1704 # User server default
1705 http-check connect default
1706 # Send HEAD on / with protocol HTTP/1.1 for host example.com
1707 http-check send meth HEAD uri / ver HTTP/1.1 hdr Host example.com
1708 # Expect return code between 200 and 399
1709 http-check expect status 200-399
1710
1711 # Insert header X-Server: apache
1712 #http-response add-header X-Server apache
1713
1714 # Set compression algorithm
1715 #compression algo gzip
1716 # Enable compression for html, plain and css text types
1717 #compression type text/html text/plain text/css
1718
1719 # Server with ssl and check without certificate verification
1720 server apache 127.0.0.1:80 no-ssl verify none check #cookie apache
1721
1722
1723 # Default https backend
1724 backend https_default
1725 # Enable check
1726 option httpchk
1727 # User server default
1728 http-check connect default
1729 # Send HEAD on / with protocol HTTP/1.1 for host example.com
1730 http-check send meth HEAD uri / ver HTTP/1.1 hdr Host example.com
1731 # Expect return code between 200 and 399
1732 http-check expect status 200-399
1733
1734 # Insert header X-Server: apache
1735 #http-response add-header X-Server apache
1736
1737 # Force HSTS for 5 minutes on domain and all subdomains
1738 #http-response set-header Strict-Transport-Security max-age=300#;\ includeSubDomains#;\ preload
1739
1740 # Set compression algorithm
1741 #compression algo gzip
1742 # Enable compression for html, plain and css text types
1743 #compression type text/html text/plain text/css
1744
1745 # Server with ssl and check without certificate verification
1746 server apache 127.0.0.1:443 ssl verify none check #cookie apache
1747
1748
1749 # Css http backend
1750 backend http_css
1751 # Enable check
1752 option httpchk
1753 # User server default
1754 http-check connect default
1755 # Send GET on /css/empty.css with protocol HTTP/1.1 for host cdn.example.com
1756 http-check send meth GET uri /css/empty.css ver HTTP/1.1 hdr Host cdn.example.com
1757 # Expect return code between 200 and 399
1758 http-check expect status 200-399
1759
1760 # Server with check without ssl and certificate verification
1761 server css 127.0.0.1:80 no-ssl verify none check
1762
1763
1764 # Js http backend
1765 backend http_js
1766 # Enable check
1767 option httpchk
1768 # User server default
1769 http-check connect default
1770 # Send HEAD on /js/missing.js with protocol HTTP/1.1 for host cdn.example.com
1771 http-check send meth HEAD uri /js/missing.js ver HTTP/1.1 hdr Host cdn.example.com
1772 # Expect return code 404
1773 http-check expect status 404
1774
1775 # Check if txn.origin start with https://cdn.example.com
1776 acl cdn_origin var(txn.origin) -m beg https://cdn.example.com
1777 # Send origin as ACAO
1778 http-response set-header Access-Control-Allow-Origin %[var(txn.origin)] if cdn_origin
1779 # Set ACMA for one day
1780 http-response set-header Access-Control-Max-Age 86400 if cdn_origin
1781
1782 # Server with check without ssl and certificate verification
1783 server js 127.0.0.1:80 no-ssl verify none check
1784
1785
1786 # Status user list
1787 userlist status
1788 # Add user admin
1789 user admin insecure-password ADMINPASSWORD
1790 # Add user operator
1791 user operator insecure-password OPERATORPASSWORD
1792 # Assign admin in admin group
1793 group admin users admin
1794 # Assign operator and admin in operator group
1795 group operator users operator,admin
1796
1797
1798 # Status http backend
1799 backend http_status
1800 # Add operator acl
1801 acl is_operator http_auth(status)
1802 # Add admin acl
1803 acl is_admin http_auth_group(status) admin
1804 # Check if trusted
1805 acl trusted src 127.0.0.0/8 ::1
1806 # Enable stats
1807 stats enable
1808 # Set stats hook on /haproxy-status
1809 stats uri /haproxy-status
1810 # Set refresh time
1811 stats refresh 10s
1812 # Display legends
1813 stats show-legends
1814 # Display node
1815 stats show-node
1816 # Allow access from trusted or authentified operator only
1817 #stats http-request auth unless trusted or is_operator
1818 stats http-request auth unless trusted
1819 # Activate admin interface from trusted or authentified admin only
1820 #stats admin if is_admin
1821 ```
1822
1823 Espérons que cet article vous a été utile.'
1824 ]
1825 ]
1826 ],
1827 ];
1828
1829 //Create 3 articles
1830 foreach($articleTree as $i => $data) {
1831 $article = new Article($users[$data['mail']]);
1832 foreach($data['keywords'] as $keyword) {
1833 $article->addKeyword($keywords[$keyword]);
1834 }
1835 $manager->persist($article);
1836 //Flush to get the id
1837 $manager->flush();
1838 $articles[$i] = $article;
1839 foreach($data['translations'] as $locale => $translation) {
1840 $articleTranslation = new ArticleTranslation($articles[$i], $locale, $translation['body'], $translation['description'], $this->slugger->slug($translation['title']), $translation['title']);
1841 $manager->persist($articleTranslation);
1842 }
1843 unset($article);
1844 }
1845
1846 //Flush to get the ids
1847 $manager->flush();
1848 }
1849 }