Mary đȘđș đ·đŽ đ«đ·
Posted on August 5, 2024
C'est parti, c'est le moment. Bienvenue dans la nouvelle version de PandApache3, version 3.3 ! (Techniquement, c'est la 3.3.1 car une correction mineure a été ajoutée dÚs le premier jour, similaire aux mises à jour récentes des jeux AAA). J'espÚre que vous avez apprécié cette série d'articles approfondis sur PandApache3 jusqu'à présent.
Chaque version majeure sera dĂ©sormais accompagnĂ©e d'un article technique discutant des fonctionnalitĂ©s et de leur mise en Ćuvre.
Nous vivons des moments si excitants ! Si vous n'avez pas lu ou si vous avez besoin d'un rappel sur ce dont nous avons discuté la derniÚre fois, vous pouvez consulter nos précédent articles :
- PandApache3, le tueur d'Apache
- Deep Dive into PandApache3: Code de lancement
- Deep Dive into PandApache3: Comprendre la gestion des connexions et la génération de réponses
- Deep Dive into PandApache3: Implementation d'authentification et de la securité
Je ferai de mon mieux pour tout expliquer ici, donc aucun prérequis n'est forcement nécessaire.
PrĂȘt ? Allons-y !
La release
Tout d'abord, discutons de ce que nous avons voulu accomplir avec cette version. L'objectif était d'étendre les capacités administratives de PandApache3. Par capacités administratives, nous entendons des fonctions telles que :
- Changer et mettre Ă jour la configuration du serveur
- RĂ©cupĂ©rer le statut, redĂ©marrer ou arrĂȘter les services
- Exécuter diverses opérations pour maintenir la santé du service
Habituellement, ces actions sont effectuées directement sur le serveur, nécessitant qu'un administrateur se connecte et exécute des commandes. Bien que cela soit faisable pour quelques services et serveurs, cela devient difficile à grande échelle. Nous ne pouvons pas nous connecter à 100 serveurs pour mettre à jour une configuration.
Pour rĂ©soudre ce problĂšme, nous avons ajoutĂ© des endpoint administratifs Ă PandApache3. Ces endpoint vous permettent d'exĂ©cuter les mĂȘmes actions sur l'ensemble de votre parc de maniĂšre simple. Explorons les diffĂ©rents endpoint disponibles :
- /admin/status : Obtenez le statut actuel de votre serveur
- /admin/reload : Rechargez le fichier de configuration
- /admin/config : Obtenez ou mettez à jour des champs de configuration spécifiques
- /admin/stop : ArrĂȘtez le service
- /admin/restart : Redémarrez le service
- /admin/script : Obtenez, téléchargez ou exécutez des scripts
Prenez un croissant et un café, et plongeons dans la maniÚre dont certains de ces endpoint ont été implémentés !
Entre Nous
Nous avons déjà des outils comme Ansible pour effectuer des actions identiques sur plusieurs serveurs. Alors pourquoi fournir des une API à un service ? Les outils comme Ansible sont en effet puissants, mais avoir des endpoint administratifs disponibles pour effectuer des tùches d'administration est plus simple à intégrer avec d'autres services. C'est aussi plus flexible et léger à intégrer dans des scripts que les playbooks Ansible.
Cette API peut Ă©galement ĂȘtre utilisĂ©e pour construire une interface utilisateur, ce qui n'est pas possible avec Ansible.
Obtenir le statut du service
Nous commencerons par cet endpoint car il est assez simple et nous permettra de nous concentrer sur l'architecture plutĂŽt que sur le rĂ©sultat de l'endpoint lui-mĂȘme.
Comme vous l'avez probablement devinĂ©, pour implĂ©menter ce nouvel endpoint d'administration dans notre serveur web, nous utiliserons et nous appuierons Ă nouveau sur le middleware. Au cas oĂč vous auriez manquĂ© certains articles prĂ©cĂ©dents, un middleware est un composant de notre architecture qui se charge d'exĂ©cuter un certain traitement sur une requĂȘte.
Toutes les requĂȘtes admin, comme toutes les requĂȘtes, doivent passer par le middleware d'authentification et de rĂ©pertoire pour accĂ©der Ă l'endpoint. Ensuite, le middleware admin sera responsable de gĂ©nĂ©rer la rĂ©ponse.
public async Task InvokeAsync(HttpContext context)
{
Logger.LogDebug("Admin middleware");
Request request = context.Request;
if (request.Verb == "GET")
{
string adminURL = ServerConfiguration.Instance.AdminDirectory.URL;
if (request.Path.ToLower().Equals(adminURL + "/status"))
{
context.Response = new HttpResponse(200)
{
Body = new MemoryStream(Encoding.UTF8.GetBytes(Server.STATUS))
};
}
else
{
context.Response = new HttpResponse(404);
}
}
else
{
context.Response = new HttpResponse(404);
}
await _next(context);
}
Si vous ĂȘtes visuel :
ArrĂȘter ou redĂ©marrer le service
Ces deux endpoints sont plus intĂ©ressants Ă examiner. ArrĂȘter, et surtout redĂ©marrer, le service est une fonctionnalitĂ© importante. La plupart des services nĂ©cessitent un orchestrateur pour redĂ©marrer. Mais avant d'examiner ce scĂ©nario complexe, simplifions-le. Un redĂ©marrage est une action d'arrĂȘt suivie d'un dĂ©marrage. Nous avons dĂ©jĂ vu l'action de dĂ©marrage de PandApache3 - Deep Dive into PandApache3: Launch Code ; garder cela Ă l'esprit nous aidera Ă comprendre la fonction d'arrĂȘt.
Voici la fonction appelée lorsque l'endpoint admin/stop est activé et comment la réponse est générée :
Task.Run(() => Server.StoppingServerAsync(false));
response = new HttpResponse(200)
{
Body = new MemoryStream(Encoding.UTF8.GetBytes("Stopping...."))
};
Vous pouvez dĂ©jĂ remarquer quelque chose de diffĂ©rent par rapport Ă l'endpoint status. ArrĂȘter le serveur n'est pas un appel synchrone. Contrairement Ă l'endpoint status, oĂč le serveur exĂ©cute toutes les opĂ©rations nĂ©cessaires avant de renvoyer la rĂ©ponse, cette fois une nouvelle tĂąche indĂ©pendante est crĂ©Ă©e, et la rĂ©ponse OK est envoyĂ©e. Cela a du sens, car si vous arrĂȘtez le serveur, il ne pourra pas rĂ©pondre aux clients. La rĂ©ponse ici est une retour disant que la tĂąche demandĂ©e sera effectuĂ©e, mais elle n'est pas terminĂ©e lorsque la rĂ©ponse arrive au client.
Une autre partie du code de l'action d'arrĂȘt du serveur est unique Ă PandApache3. VĂ©rifions directement la fonction StoppingServerAsync
appelée par notre nouveau thread :
public static async Task StoppingServerAsync()
{
if (!Monitor.TryEnter(_lock))
{
Logger.LogDebug($"Thread (Thread ID: {Thread.CurrentThread.ManagedThreadId}) could not acquire the lock and will exit.");
Logger.LogInfo("Server is already restarting");
return;
}
lock (_lock)
{
List<CancellationTokenSource> cancellationTokens = new List<CancellationTokenSource>();
cancellationTokens.Add(_ConnectionManager._cancellationTokenSource);
StopServerAsync(cancellationTokens).Wait();
}
Logger.LogDebug("Get out of the lock");
Monitor.Exit(_lock);
}
La vĂ©ritable fonction qui arrĂȘtera le serveur est StopServerAsync
, mais vous avez probablement remarquĂ© que cette fonction est Ă l'intĂ©rieur d'un bloc de verrouillage (lock). Un verrouillage garantit que votre code ne sera exĂ©cutĂ© que dans un seul thread. Certaines actions, comme l'arrĂȘt du serveur, ne peuvent ĂȘtre exĂ©cutĂ©es qu'une seule fois. Il n'aurait aucun sens de demander au serveur de s'arrĂȘter deux fois, et cela pourrait gĂ©nĂ©rer de nombreux problĂšmes. En gĂ©nĂ©ral, les threads peuvent gĂ©nĂ©rer de nombreux problĂšmes de concurrence. Utiliser le verrouillage garantit que la fonction ne sera pas appelĂ©e plusieurs fois par diffĂ©rents clients.
Dans cette dĂ©claration de verrouillage, nous n'arrĂȘtons pas seulement le serveur. Vous pouvez Ă©galement voir que nous avons des jetons d'annulation. Voyons l'objectif de ce jeton que nous avons enregistrĂ© dans une liste.
Vous vous souvenez de la façon dont la boucle en charge d'accepter et de gérer les nouvelles connexions a été présentée dans l'article - Deep Dive into PandApache3: Understanding Connection Management and Response Generation
Je vous ai dit que le serveur est exĂ©cutĂ© dans une boucle infinie car nous acceptons toujours des connexions. Eh bien, c'Ă©tait une simplification, car bien sĂ»r, le serveur ne peut pas accepter continuellement des connexions dans certaines situations, comme ĂȘtre arretĂ©. Nous devons ĂȘtre sĂ»rs qu'aucune nouvelle connexion ne sera tentĂ©e. Et une façon d'arrĂȘter cette boucle âinfinieâ Ă distance est le jeton. Voici la boucle correcte :
do
{
if (listener.Pending())
{
ISocketWrapper client = new SocketWrapper(listener.AcceptSocket());
await connectionManager.AcceptConnectionsAsync(client);
}
} while (connectionManager._cancellationTokenSource.IsCancellationRequested == false);
Parlon de l'éléphant au milieu de la piÚce. Oui, c'est une boucle do while et non une boucle while. Mais vous pouvez voir dans la condition que pour sortir de la boucle, le cancellationTokenSource
, une propriété de notre connectionManager
, doit ĂȘtre annulĂ©. C'est ainsi que nous arrĂȘterons d'accepter de nouvelles connexions.
Maintenant, nous pouvons vraiment arrĂȘter le serveur. Voici la derniĂšre fonction pour ce cas d'utilisation, StopServerAsync
:
public static async Task StopServerAsync(List<CancellationTokenSource> cancellationTokens = null)
{
int retry = 5;
Server.STATUS = "PandApache3 is stopping";
Logger.LogInfo($"{Server.STATUS}");
if (cancellationTokens != null)
{
foreach (CancellationTokenSource token in cancellationTokens)
{
token.Cancel();
}
}
_ConnectionManager.Listener.Stop();
for (int i = 0; i < retry; retry--)
{
if (_ConnectionManager.clients.Count > 0)
{
Thread.Sleep(1000);
Logger.LogDebug("There are still active connections...");
}
else
{
break;
}
}
if (retry == 0)
{
Logger.LogInfo("Force server to stop");
}
else
{
Logger.LogInfo("Server stopped");
}
Server.STATUS = "PandApache3 is stopped";
Logger.LogInfo($"{Server.STATUS}");
}
Nous commençons par annuler tous les jetons détenus par le connectionManager :
token.Cancel();
Ensuite, nous arrĂȘtons le listener:
_ConnectionManager.Listener.Stop();
Ă ce stade, mĂȘme si nous bloquons toutes les nouvelles connexions, nous pourrions encore avoir quelques connexions ouvertes et actives en attente d'une rĂ©ponse du serveur. Par souci de considĂ©ration, nous leur donnons 5 secondes avant de les terminer (actuellement, nous ne le faisons pas, mais nous devrions).
Enfin, quand plus aucun thread enfant ne sera en cours d'exécution. Une de nos premiÚres fonctions de serveur, RunServerAsync
, se terminera maintenant que la boucle infinie est terminée. Le programme continuera jusqu'à la fin de son code principal et dira au revoir :
Logger.LogInfo("La revedere !");
Between Us
Pourquoi avons-nous une liste de jetons ? Nous avons un seul
connectionManager
, non ? C'est correct. La liste est pour le moment optionnelle, et le code pourrait fonctionner sans elle en passant simplement notre jeton. Mais, attendez... Nous aborderons ce point plus tard.
Redémarrer le Serveur
Maintenant que nous avons couvert la fonction d'arrĂȘt, examinons le processus de redĂ©marrage. Le redĂ©marrage utilise les mĂȘmes fonctions que l'arrĂȘt, avec un paramĂštre boolĂ©en supplĂ©mentaire pour indiquer un redĂ©marrage. Pour redĂ©marrer le serveur, nous devons l'arrĂȘter puis le redĂ©marrer depuis le dĂ©but. Comment y parvenir ? Avec une boucle et un jeton, bien sĂ»r !
Voici la fonction principale :
while (_cancellationTokenSourceServer.IsCancellationRequested == false)
{
await StartServerAsync();
await RunServerAsync();
}
Logger.LogInfo("La revedere !");
Comme discuté dans l'article précédent, nous démarrons le serveur, qui fonctionne ensuite en continu. La fonction RunServerAsync
ne se termine jamais jusqu'Ă ce que nous accĂ©dions au endpoint d'administration pour arrĂȘter le serveur. Lorsque le serveur s'arrĂȘte, le processus se termine.
Cependant, pour effectuer un redĂ©marrage, nous devons arrĂȘter le serveur puis revenir au dĂ©but de la fonction principale pour exĂ©cuter StartServerAsync
et RunServerAsync
Ă nouveau.
Pour y parvenir, nous utilisons un jeton, mais cette fois au niveau du serveur plutĂŽt qu'au niveau du connectionManager
. Lorsque nous voulons redémarrer le serveur, nous exécutons la fonction décrite dans la section précédente. Notre fonction StopServer
devient une fonction RestartServer
. Pour arrĂȘter efficacement le serveur, nous utilisons un indicateur pour dĂ©terminer s'il faut annuler le jeton du serveur.
Server.STATUS = "PandApache3 est arrĂȘtĂ©";
Logger.LogInfo($"{Server.STATUS}");
if (isRestart == false)
{
_cancellationTokenSourceServer.Cancel();
}
C'est tout ! Bien que la section prĂ©cĂ©dente ait Ă©tĂ© dĂ©taillĂ©e pour expliquer comment le serveur s'arrĂȘte, cet effort n'a pas Ă©tĂ© vain car comprendre comment le serveur redĂ©marre est maintenant beaucoup plus facile.â
Le queryParameters
Nous avons encore un endpoint intéressant à examiner : /admin/script, qui vous permet d'obtenir, de charger ou d'exécuter des scripts. Ce endpoint a quelque chose de différent par rapport aux autres ; il accepte ce que nous appelons un queryParameters .
Voici un exemple d'URL avec un queryParameters : http://pandapache3/admin/script?name=hello_world.ps1
Le queryParameters est ce qui suit le ?
. Dans cet exemple, nous avons un paramĂštre, name
, associé à la valeur hello_world.ps1
. Cette requĂȘte ne contient qu'un paramĂštre, mais il pourrait y en avoir beaucoup plus.
Comme vous pouvez l'imaginer, cela signifie que notre objet de requĂȘte continuera d'Ă©voluer pour stocker ces queryParameters . Notre nouvel attribut sera un dictionnaire de string:
public Dictionary<string, string> queryParameters { get; }
Voici une simple fonction pour analyser et stocker les valeurs :
private Dictionary<string, string> GetQueryParameters()
{
var parameters = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(QueryString))
{
var keyValuePairs = QueryString.Split('&');
foreach (var pair in keyValuePairs)
{
var keyValue = pair.Split('=');
Logger.LogDebug($"KeyValue: {keyValue}");
if (keyValue.Length == 2)
{
var key = Uri.UnescapeDataString(keyValue[0]);
var value = Uri.UnescapeDataString(keyValue[1]);
parameters[key] = value;
}
}
}
return parameters;
}
Voyons maintenant ce qui se passe lorsque notre endpoint est atteint. Il y a trois cas d'utilisation.
Vous appelez le point de terminaison avec une requĂȘte POST: Cela signifie que vous voulez charger un script sur le serveur. Le serveur gĂšre dĂ©jĂ le chargement depuis la version 3.0, donc rien de nouveau ici.
private HttpResponse postAdmin(HttpContext context)
{
if (context.Request.Headers["Content-Type"] != null && context.Request.Headers["Content-Type"].StartsWith("multipart/form-data"))
{
string adminURL = ServerConfiguration.Instance.AdminDirectory.URL;
string scriptsDirectory = ServerConfiguration.Instance.AdminDirectory.Path;
if (context.Request.Path.ToLower().StartsWith(adminURL + "/script"))
{
if (ServerConfiguration.Instance.AdminScript)
return RequestParser.UploadHandler(context.Request, true);
else
return new HttpResponse(403);
}
}
return new HttpResponse(404);
}
Vous appelez le endpoint avec une requĂȘte GET sans queryParameters: Dans ce cas, votre URL pourrait ressembler Ă http://pandapache3/admin/script
. Nous considĂ©rerons que vous voulez savoir quels scripts sont prĂ©sents sur le serveur que vous pouvez exĂ©cuter. Cette requĂȘte est simple Ă gĂ©rer :
string bodyScript = "Voici la liste des scripts sur le serveur PandApache3 :\n";
foreach (string script in Directory.GetFiles(scriptsDirectory))
{
FileInfo fileInfo = new FileInfo(script);
bodyScript += $"\t- {fileInfo.Name}\n";
}
response = new HttpResponse(200)
{
Body = new MemoryStream(Encoding.UTF8.GetBytes(bodyScript))
};
Vous utilisez un queryParameters : Cela signifie que vous voulez exécuter un script spécifique sur le serveur. Voyons ce qui se passe :
private HttpResponse RunScript(string scriptDirectory, Dictionary<string, string> queryParameters)
{
HttpResponse response = null;
string terminal = string.Empty;
if (ServerConfiguration.Instance.Platform.Equals("WIN"))
terminal = "powershell.exe";
else
terminal = "/bin/bash";
string argumentList = $"{Path.Combine(scriptDirectory, queryParameters["name"])}";
foreach (var item in queryParameters)
{
if (item.Key != "name")
{
argumentList += $" {item.Value}";
}
}
var processInfo = new ProcessStartInfo
{
FileName = terminal,
Arguments = argumentList,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
UseShellExecute = false,
};
try
{
using (var process = new Process { StartInfo = processInfo })
{
process.Start();
process.WaitForExit();
string standardOutput = process.StandardOutput.ReadToEnd();
string standardError = process.StandardError.ReadToEnd();
ScriptResult scriptResult = new ScriptResult
{
ExitCode = process.ExitCode,
StandardOutput = standardOutput,
ErrorOutput = standardError
};
response = new HttpResponse(200)
{
Body = new MemoryStream(Encoding.UTF8.GetBytes(Newtonsoft.Json.JsonConvert.SerializeObject(scriptResult)))
};
}
}
catch (Exception ex)
{
Logger.LogError($"Erreur lors de l'exécution du script {ex.Message}");
response = new HttpResponse(500);
}
return response;
}
Passons en revue cette fonction. Tout d'abord, les paramĂštres sont le rĂ©pertoire des scripts (qui est aussi le rĂ©pertoire d'administration) et la chaĂźne de requĂȘte qui contient le script que nous voulons exĂ©cuter.
L'étape suivante consiste à déterminer la plateforme actuelle pour exécuter notre script avec le programme approprié (PowerShell pour Windows et Bash pour Linux).
string terminal = string.Empty;
if (ServerConfiguration.Instance.Platform.Equals("WIN"))
terminal = "powershell.exe";
else
terminal = "/bin/bash";
Avec toutes les informations, nous construisons la commande que nous voulons exécuter, y compris le chemin complet du script et les arguments potentiels :
string argumentList = $"{Path.Combine(scriptDirectory, queryParameters["name"])}";
foreach (var item in queryParameters)
{
if (item.Key != "name")
{
argumentList += $" {item.Value}";
}
}
Pour exécuter le script, nous avons besoin d'un ProcessStartInfo
pour spécifier les options pour l'exécution :
var processInfo = new ProcessStartInfo
{
FileName = terminal,
Arguments = argumentList,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
UseShellExecute = false,
};
Nous pouvons maintenant l'exécuter et enregistrer sa sortie pour générer une réponse pour notre client :
using (var process = new Process { StartInfo = processInfo })
{
process.Start();
process.WaitForExit();
string standardOutput = process.StandardOutput.ReadToEnd();
string standardError = process.StandardError.ReadToEnd();
ScriptResult scriptResult = new ScriptResult
{
ExitCode = process.ExitCode,
StandardOutput = standardOutput,
ErrorOutput = standardError
};
response = new HttpResponse(200)
{
Body = new MemoryStream(Encoding.UTF8.GetBytes(Newtonsoft.Json.JsonConvert.SerializeObject(scriptResult)))
};
}
La rĂ©ponse sera structurĂ©e en format JSON avec le code de sortie, la sortie standard et la sortie d'erreur. Ce format est conçu pour ĂȘtre facilement utilisĂ© par d'autres programmes plutĂŽt que par des humains.
Entre Nous
Le endpoint /admin/config peut Ă©galement fonctionner avec des paramĂštres de requĂȘte pour modifier un paramĂštre spĂ©cifique dans la configuration. Cette partie fonctionne exactement comme le point de terminaison de script Ă ce niveau, donc nous ne le couvrirons pas dans cet article. Cependant, vous avez maintenant toutes les connaissances nĂ©cessaires pour explorer le code vous-mĂȘme !
Toujours Entre Nous
Vous pourriez remarquer que cette fois, la requĂȘte HTTP est synchrone pour le client. C'est assez diffĂ©rent par rapport Ă l'action d'arrĂȘter le serveur, qui renvoie une rĂ©ponse avant la fin de l'action. Ici, nous avons choisi de bloquer le client jusqu'Ă ce qu'il reçoive une rĂ©ponse. Cela pourrait poser un problĂšme si votre script effectue un traitement lourd. Nous aborderons ce point Ă l'avenir en proposant des options d'exĂ©cution de script synchrones et asynchrones sur le serveur.
Protéger Notre Administration
Il est temps de discuter du dernier aspect de cette version. Nous avons vu que les points de terminaison d'administration sont assez puissants et peuvent ĂȘtre utilisĂ©s pour crĂ©er des outils CLI ou une console d'administration web. Leur puissance est telle que, si une action d'administration n'existe pas, vous pouvez la crĂ©er vous-mĂȘme via des scripts.
Cependant, si vous ĂȘtes un bon administrateur systĂšme, vous reconnaĂźtrez que la possibilitĂ© de charger et d'exĂ©cuter du code Ă distance sur un serveur peut ĂȘtre extrĂȘmement dangereuse ! Et vous avez raison !
Cette fonctionnalitĂ©, bien que trĂšs utile pour la gestion d'un PaaS, est Ă©galement risquĂ©e si elle n'est pas configurĂ©e correctement. C'est pourquoi vos endpoint d'administration ne fonctionnent pas sur le mĂȘme site que votre site PandApache !
Actuellement, mon site fonctionne sur http://127.0.0.1:8080/. Lorsque j'accĂšde Ă cette adresse, mon gestionnaire de connexions gĂšre la connexion, passe la requĂȘte Ă travers le pipeline de middleware et affiche mes ressources. Cependant, mon endpoint d'administration n'est pas situĂ© Ă cette adresse. Vous pouvez le configurer pour qu'il fonctionne sur un port diffĂ©rent ou mĂȘme sur une autre interface rĂ©seau ! Ce site a son propre gestionnaire de connexions, ce qui signifie qu'il a aussi son propre pipeline de middleware.
C'est pourquoi, lors du redĂ©marrage du serveur, nous utilisons une liste de jetons pour annuler les connexions. En effet, nous arrĂȘtons non pas seulement un ConnectionManager
avec son TCPListener
, mais deux !
La possibilité de séparer les deux sites au niveau du réseau est un bon moyen de protéger votre serveur contre les attaques. Cela permet à vos clients d'accéder au contenu par un chemin et à votre équipe d'administration de gérer votre service par un autre.
Entre Nous
Si vous n'ĂȘtes toujours pas convaincu par les mesures de sĂ©curitĂ©, il existe des Ă©tapes supplĂ©mentaires que vous pouvez suivre. Tout d'abord, vous pouvez dĂ©sactiver l'exĂ©cution de scripts. Votre endpoint d'administration restera disponible, mais vous ne pourrez pas exĂ©cuter des actions qui n'ont pas dĂ©jĂ Ă©tĂ© implĂ©mentĂ©es via l'API. Vous pouvez Ă©galement dĂ©sactiver complĂštement le site d'administration et continuer Ă gĂ©rer votre service comme vous le faites actuellement. Nous continuerons Ă amĂ©liorer la sĂ©curitĂ© Ă l'avenir, en offrant une isolation encore plus grande entre les parties publique et administrative de PandApache3.
Dans cet article, nous avons explorĂ© les nouvelles fonctionnalitĂ©s de PandApache3, en dĂ©taillant comment les endpoint on Ă©tĂ© implĂ©mentĂ© pour ĂȘtre sĂ©curisĂ© et utile.
Le prochain sujet que nous aborderons dans cette série d'articles est une partie importante pour les administrateurs systÚmes : la journalisation, la télémétrie et la surveillance.
Restez Ă l'Ă©coute !
Merci beaucoup d'avoir explorĂ© les rouages de PandApache3 avec moi ! Vos pensĂ©es et votre soutien sont cruciaux pour faire avancer ce projet. đ
N'hésitez pas à partager vos idées et impressions dans les commentaires ci-dessous. J'ai hùte d'avoir de vos nouvelles !
Suivez mes aventures sur Twitter @pykpyky pour rester informé de toutes les nouvelles.
Vous pouvez Ă©galement explorer le projet complet sur GitHub et me rejoindre pour des sessions de codage en direct sur Twitch pour des sessions excitantes et interactives. Ă bientĂŽt derriĂšre l'Ă©cran !
Posted on August 5, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.