From 6328afd5fbb33a53148bf2cbc3c907f6d3bee382 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Wed, 5 Jun 2024 21:54:10 +0200 Subject: [PATCH] (feature): sever side agent implementation --- README.md | 62 ++- src/Agent/Agent.csproj | 23 + src/Agent/Configuration.cs | 6 + src/Agent/Controllers/CertsController.cs | 41 ++ src/Agent/Controllers/HelloWorldController.cs | 14 + src/Agent/Controllers/ServiceController.cs | 61 +++ src/Agent/Program.cs | 34 ++ src/Agent/Properties/launchSettings.json | 31 ++ src/Agent/ServiceReloader.http | 6 + src/Agent/appsettings.Development.json | 8 + src/Agent/appsettings.json | 14 + src/Agent/build_and_deploy.sh | 77 +++ src/LetsEncrypt.sln | 14 +- src/LetsEncryptServer/Configuration.cs | 15 +- .../Controllers/CertsFlowController.cs | 6 +- .../LetsEncryptServer.csproj | 6 +- src/LetsEncryptServer/Program.cs | 1 + .../Services/AgentService.cs | 72 +++ .../Services/CertsFlowService.cs | 148 +----- src/LetsEncryptServer/appsettings.json | 14 +- .../Agent/Requests/CertsUploadRequest.cs | 7 + .../Agent/Requests/ServiceReloadRequest.cs | 11 + .../Requests/GetCerificatesRequest.cs | 2 +- .../Requests/GetOrderRequest.cs | 2 +- .../Requests/InitRequest.cs | 2 +- .../Requests/NewOrderRequest.cs | 2 +- src/Models/Models.csproj | 14 + ...Encrypt Production.postman_collection.json | 479 ++++++++++++++++++ ...etsEncrypt Staging.postman_collection.json | 25 +- .../Maks-IT Agent.postman_collection.json | 77 +++ 30 files changed, 1084 insertions(+), 190 deletions(-) create mode 100644 src/Agent/Agent.csproj create mode 100644 src/Agent/Configuration.cs create mode 100644 src/Agent/Controllers/CertsController.cs create mode 100644 src/Agent/Controllers/HelloWorldController.cs create mode 100644 src/Agent/Controllers/ServiceController.cs create mode 100644 src/Agent/Program.cs create mode 100644 src/Agent/Properties/launchSettings.json create mode 100644 src/Agent/ServiceReloader.http create mode 100644 src/Agent/appsettings.Development.json create mode 100644 src/Agent/appsettings.json create mode 100644 src/Agent/build_and_deploy.sh create mode 100644 src/LetsEncryptServer/Services/AgentService.cs create mode 100644 src/Models/Agent/Requests/CertsUploadRequest.cs create mode 100644 src/Models/Agent/Requests/ServiceReloadRequest.cs rename src/{LetsEncryptServer/Models => Models/LetsEncryptServer}/Requests/GetCerificatesRequest.cs (61%) rename src/{LetsEncryptServer/Models => Models/LetsEncryptServer}/Requests/GetOrderRequest.cs (59%) rename src/{LetsEncryptServer/Models => Models/LetsEncryptServer}/Requests/InitRequest.cs (78%) rename src/{LetsEncryptServer/Models => Models/LetsEncryptServer}/Requests/NewOrderRequest.cs (70%) create mode 100644 src/Models/Models.csproj create mode 100644 src/Postman/LetsEncrypt Production.postman_collection.json rename LetsEncrypt.postman_collection.json => src/Postman/LetsEncrypt Staging.postman_collection.json (96%) create mode 100644 src/Postman/Maks-IT Agent.postman_collection.json diff --git a/README.md b/README.md index ae7d6c7..030102b 100644 --- a/README.md +++ b/README.md @@ -122,4 +122,64 @@ frontend web #--------------------------------------------------------------------- backend acme_challenge_backend server acme_challenge 127.0.0.1:8080 -``` \ No newline at end of file +``` + + + + +## MaksIT agent + +```bash +openssl rand -base64 32 +``` + +```bash +sudo rpm -Uvh https://packages.microsoft.com/config/centos/8/packages-microsoft-prod.rpm +sudo dnf install -y dotnet-sdk-8.0 +``` + + +Copy sources to + +```bash +sudo mkdir -p /opt/maks-it-agent +``` + + +```bash +dotnet build --configuration Release +dotnet publish -c Release -o /opt/maks-it-agent +``` + + + + +```bash +sudo nano /etc/systemd/system/maks-it-agent.service +``` + +```bash +[Unit] +Description=Maks-IT Agent +After=network.target + +[Service] +WorkingDirectory=/opt/maks-it-agent +ExecStart=/usr/bin/dotnet /opt/maks-it-agent/Agent.dll --urls "http://*:5000" +Restart=always +# Restart service after 10 seconds if the dotnet service crashes: +RestartSec=10 +KillSignal=SIGINT +SyslogIdentifier=dotnet-servicereloader +User=root +Environment=ASPNETCORE_ENVIRONMENT=Production + +[Install] +WantedBy=multi-user.target +``` + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now maks-it-agent.service +sudo systemctl status maks-it-agent.service +``` diff --git a/src/Agent/Agent.csproj b/src/Agent/Agent.csproj new file mode 100644 index 0000000..6726b4e --- /dev/null +++ b/src/Agent/Agent.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + Never + + + + diff --git a/src/Agent/Configuration.cs b/src/Agent/Configuration.cs new file mode 100644 index 0000000..b90bc89 --- /dev/null +++ b/src/Agent/Configuration.cs @@ -0,0 +1,6 @@ +namespace MaksIT.Agent { + public class Configuration { + public required string ApiKey { get; set; } + public required string CertsPath { get; set; } + } +} diff --git a/src/Agent/Controllers/CertsController.cs b/src/Agent/Controllers/CertsController.cs new file mode 100644 index 0000000..5578991 --- /dev/null +++ b/src/Agent/Controllers/CertsController.cs @@ -0,0 +1,41 @@ + +using System.Diagnostics; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +using MaksIT.Models.Agent.Requests; + +namespace MaksIT.Agent.Controllers; + +[ApiController] +[Route("[controller]")] +public class CertsController : ControllerBase { + + private readonly Configuration _appSettings; + + public CertsController( + IOptions appSettings + ) { + _appSettings = appSettings.Value; + } + + [HttpPost("[action]")] + public IActionResult Upload([FromBody] CertsUploadRequest requestData) { + if (!Request.Headers.TryGetValue("X-API-KEY", out var extractedApiKey)) { + return Unauthorized("API Key is missing"); + } + + if (!_appSettings.ApiKey.Equals(extractedApiKey)) { + return Unauthorized("Unauthorized client"); + } + + foreach (var (fileName, fileContent) in requestData.Certs) { + System.IO.File.WriteAllText(Path.Combine(_appSettings.CertsPath, fileName), fileContent); + } + + return Ok("Certificates uploaded successfully"); + } + +} + diff --git a/src/Agent/Controllers/HelloWorldController.cs b/src/Agent/Controllers/HelloWorldController.cs new file mode 100644 index 0000000..d3d3dc0 --- /dev/null +++ b/src/Agent/Controllers/HelloWorldController.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Agent.Controllers { + + [ApiController] + [Route("[controller]")] + public class HelloWorldController : ControllerBase { + + [HttpGet] + public IActionResult Get() { + return Ok("Hello, World!"); + } + } +} diff --git a/src/Agent/Controllers/ServiceController.cs b/src/Agent/Controllers/ServiceController.cs new file mode 100644 index 0000000..d849b6a --- /dev/null +++ b/src/Agent/Controllers/ServiceController.cs @@ -0,0 +1,61 @@ +using System.Diagnostics; +using MaksIT.Models.Agent.Requests; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace MaksIT.Agent.Controllers; + +[ApiController] +[Route("[controller]")] +public class ServiceController : ControllerBase { + + private readonly Configuration _appSettings; + + public ServiceController( + IOptions appSettings + ) { + _appSettings = appSettings.Value; + } + + [HttpPost("[action]")] + public IActionResult Reload([FromBody] ServiceReloadRequest requestData) { + var serviceName = requestData.ServiceName; + + if (!Request.Headers.TryGetValue("X-API-KEY", out var extractedApiKey)) { + return Unauthorized("API Key is missing"); + } + + if (!_appSettings.ApiKey.Equals(extractedApiKey)) { + return Unauthorized("Unauthorized client"); + } + + try { + var processStartInfo = new ProcessStartInfo { + FileName = "/bin/systemctl", + Arguments = $"reload {serviceName}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using (var process = new Process { StartInfo = processStartInfo }) { + process.Start(); + process.WaitForExit(); + + var output = process.StandardOutput.ReadToEnd(); + var error = process.StandardError.ReadToEnd(); + + if (process.ExitCode != 0) { + return StatusCode(500, $"Error reloading service: {error}"); + } + + return Ok($"Service {serviceName} reloaded successfully: {output}"); + } + } + catch (Exception ex) { + return StatusCode(500, $"Exception: {ex.Message}"); + } + } +} + diff --git a/src/Agent/Program.cs b/src/Agent/Program.cs new file mode 100644 index 0000000..af450c9 --- /dev/null +++ b/src/Agent/Program.cs @@ -0,0 +1,34 @@ +using MaksIT.Agent; + +var builder = WebApplication.CreateBuilder(args); + +// Extract configuration +var configuration = builder.Configuration; + +// Configure strongly typed settings objects +var configurationSection = configuration.GetSection("Configuration"); +var appSettings = configurationSection.Get() ?? throw new ArgumentNullException(); + +// Allow configurations to be available through IOptions +builder.Services.Configure(configurationSection); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) { + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/src/Agent/Properties/launchSettings.json b/src/Agent/Properties/launchSettings.json new file mode 100644 index 0000000..8747336 --- /dev/null +++ b/src/Agent/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:7748", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Agent/ServiceReloader.http b/src/Agent/ServiceReloader.http new file mode 100644 index 0000000..3f65e8d --- /dev/null +++ b/src/Agent/ServiceReloader.http @@ -0,0 +1,6 @@ +@ServiceReloader_HostAddress = http://localhost:5186 + +GET {{ServiceReloader_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/Agent/appsettings.Development.json b/src/Agent/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/Agent/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Agent/appsettings.json b/src/Agent/appsettings.json new file mode 100644 index 0000000..c2941ed --- /dev/null +++ b/src/Agent/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + + "Configuration": { + "ApiKey": "UGnCaElLLJClHgUeet/yr7vNvPf13b1WkDJQMfsiP6I=", + "CertsPath": "/etc/haproxy/certs" + } +} \ No newline at end of file diff --git a/src/Agent/build_and_deploy.sh b/src/Agent/build_and_deploy.sh new file mode 100644 index 0000000..39d9d4e --- /dev/null +++ b/src/Agent/build_and_deploy.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# Variables +SERVICE_NAME="maks-it-agent" +SERVICE_PORT="5000" +SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME.service" +INSTALL_DIR="/opt/$SERVICE_NAME" +DOTNET_EXEC="/usr/bin/dotnet" +EXEC_CMD="$DOTNET_EXEC $INSTALL_DIR/Agent.dll --urls \"http://*:$SERVICE_PORT\"" +APPSETTINGS_FILE="appsettings.json" +NO_NEW_KEY_FLAG="--no-new-key" + +# Update package index and install the Microsoft package repository +sudo rpm -Uvh https://packages.microsoft.com/config/centos/8/packages-microsoft-prod.rpm +sudo dnf install -y dotnet-sdk-8.0 + +# Check if the service exists and stop it if it does +if systemctl list-units --full -all | grep -Fq "$SERVICE_NAME.service"; then + sudo systemctl stop $SERVICE_NAME.service + sudo systemctl disable $SERVICE_NAME.service + sudo rm -f $SERVICE_FILE +fi + +# Clean up the old files if they exist +sudo rm -rf $INSTALL_DIR + +# Create the application directory +sudo mkdir -p $INSTALL_DIR + +# Update appsettings.json if --no-new-key flag is not provided +if [[ "$1" != "$NO_NEW_KEY_FLAG" ]]; then + NEW_API_KEY=$(openssl rand -base64 32) + jq --arg newApiKey "$NEW_API_KEY" '.Configuration.ApiKey = $newApiKey' $APPSETTINGS_FILE > tmp.$$.json && mv tmp.$$.json $APPSETTINGS_FILE +fi + +# Build and publish the .NET application +sudo dotnet build --configuration Release +sudo dotnet publish -c Release -o $INSTALL_DIR + +# Create the systemd service unit file +sudo bash -c "cat > $SERVICE_FILE < + + Maks-IT Agent + +' > /etc/firewalld/services/maks-it-agent.xml + +sleep 10 + +# Add the services to the firewall +firewall-cmd --permanent --add-service=maks-it-agent + +# Reload the firewall +firewall-cmd --reload diff --git a/src/LetsEncrypt.sln b/src/LetsEncrypt.sln index 62d0dbf..42180a2 100644 --- a/src/LetsEncrypt.sln +++ b/src/LetsEncrypt.sln @@ -15,10 +15,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3374FDB1 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SSHProviderTests", "Tests\SSHSerivceTests\SSHProviderTests.csproj", "{3937760A-FFB3-4A8C-ABD1-CDDCE1D977C4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LetsEncryptServer", "LetsEncryptServer\LetsEncryptServer.csproj", "{B5F39E04-C2E3-49BF-82C2-9DEBAA949E3D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LetsEncryptServer", "LetsEncryptServer\LetsEncryptServer.csproj", "{B5F39E04-C2E3-49BF-82C2-9DEBAA949E3D}" EndProject Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{0233E43F-435D-4309-B20C-ECD4BFBD2E63}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Agent", "Agent\Agent.csproj", "{871BDED3-C6AE-437D-9B45-3AA3F184D002}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Models", "Models\Models.csproj", "{6814169B-D4D0-40B2-9FA9-89997DD44C30}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -53,6 +57,14 @@ Global {0233E43F-435D-4309-B20C-ECD4BFBD2E63}.Debug|Any CPU.Build.0 = Debug|Any CPU {0233E43F-435D-4309-B20C-ECD4BFBD2E63}.Release|Any CPU.ActiveCfg = Release|Any CPU {0233E43F-435D-4309-B20C-ECD4BFBD2E63}.Release|Any CPU.Build.0 = Release|Any CPU + {871BDED3-C6AE-437D-9B45-3AA3F184D002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {871BDED3-C6AE-437D-9B45-3AA3F184D002}.Debug|Any CPU.Build.0 = Debug|Any CPU + {871BDED3-C6AE-437D-9B45-3AA3F184D002}.Release|Any CPU.ActiveCfg = Release|Any CPU + {871BDED3-C6AE-437D-9B45-3AA3F184D002}.Release|Any CPU.Build.0 = Release|Any CPU + {6814169B-D4D0-40B2-9FA9-89997DD44C30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6814169B-D4D0-40B2-9FA9-89997DD44C30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6814169B-D4D0-40B2-9FA9-89997DD44C30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6814169B-D4D0-40B2-9FA9-89997DD44C30}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/LetsEncryptServer/Configuration.cs b/src/LetsEncryptServer/Configuration.cs index 445b624..ced1dd4 100644 --- a/src/LetsEncryptServer/Configuration.cs +++ b/src/LetsEncryptServer/Configuration.cs @@ -1,20 +1,17 @@ namespace MaksIT.LetsEncryptServer { - public class Server { - public required string Ip { get; set; } - public required int SocketPort { get; set; } - public required int SSHPort { get; set; } - public required string Path { get; set; } + public class Agent { + public required string AgentHostname { get; set; } + public required int AgentPort { get; set; } + public required string AgentKey { get; set; } - public required string Username { get; set; } - public string? Password { get; set; } - public string[]? PrivateKeys { get; set; } + public required string ServiceToReload { get; set; } } public class Configuration { public required string Production { get; set; } public required string Staging { get; set; } public required bool DevMode { get; set; } - public required Server Server { get; set; } + public required Agent Agent { get; set; } } } diff --git a/src/LetsEncryptServer/Controllers/CertsFlowController.cs b/src/LetsEncryptServer/Controllers/CertsFlowController.cs index 222bda6..d6efd49 100644 --- a/src/LetsEncryptServer/Controllers/CertsFlowController.cs +++ b/src/LetsEncryptServer/Controllers/CertsFlowController.cs @@ -3,8 +3,8 @@ using Microsoft.Extensions.Options; using DomainResults.Mvc; -using MaksIT.LetsEncryptServer.Models.Requests; using MaksIT.LetsEncryptServer.Services; +using MaksIT.Models.LetsEncryptServer.Requests; namespace MaksIT.LetsEncryptServer.Controllers; @@ -107,8 +107,8 @@ public class CertsFlowController : ControllerBase { /// /// [HttpPost("[action]/{sessionId}")] - public IActionResult ApplyCertificates(Guid sessionId, [FromBody] GetCertificatesRequest requestData) { - var result = _certsFlowService.ApplyCertificates(sessionId, requestData); + public async Task ApplyCertificates(Guid sessionId, [FromBody] GetCertificatesRequest requestData) { + var result = await _certsFlowService.ApplyCertificates(sessionId, requestData); return result.ToActionResult(); } } diff --git a/src/LetsEncryptServer/LetsEncryptServer.csproj b/src/LetsEncryptServer/LetsEncryptServer.csproj index 2c55ec6..5b4d566 100644 --- a/src/LetsEncryptServer/LetsEncryptServer.csproj +++ b/src/LetsEncryptServer/LetsEncryptServer.csproj @@ -17,11 +17,7 @@ - - - - - + diff --git a/src/LetsEncryptServer/Program.cs b/src/LetsEncryptServer/Program.cs index 756ac93..0bb7cf3 100644 --- a/src/LetsEncryptServer/Program.cs +++ b/src/LetsEncryptServer/Program.cs @@ -28,6 +28,7 @@ builder.Services.AddMemoryCache(); builder.Services.AddHttpClient(); builder.Services.AddScoped(); builder.Services.AddSingleton(); +builder.Services.AddHttpClient(); var app = builder.Build(); diff --git a/src/LetsEncryptServer/Services/AgentService.cs b/src/LetsEncryptServer/Services/AgentService.cs new file mode 100644 index 0000000..8d98f34 --- /dev/null +++ b/src/LetsEncryptServer/Services/AgentService.cs @@ -0,0 +1,72 @@ +using DomainResults.Common; +using MaksIT.Models.Agent.Requests; +using Microsoft.Extensions.Options; +using System.Text; +using System.Text.Json; + +namespace MaksIT.LetsEncryptServer.Services { + + public interface IAgentService { + Task GetHelloWorld(); + Task UploadCerts(Dictionary certs); + Task ReloadService(string serviceName); + } + + public class AgentService : IAgentService { + + private readonly Configuration _appSettings; + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public AgentService( + IOptions appSettings, + ILogger logger, + HttpClient httpClient + ) { + _appSettings = appSettings.Value; + _logger = logger; + _httpClient = httpClient; + } + + public Task GetHelloWorld() { + throw new NotImplementedException(); + } + + public async Task ReloadService(string serviceName) { + var requestBody = new ServiceReloadRequest { ServiceName = serviceName }; + var endpoint = $"/Service/Reload"; + return await SendHttpRequest(requestBody, endpoint); + } + + public async Task UploadCerts(Dictionary certs) { + var requestBody = new CertsUploadRequest { Certs = certs }; + var endpoint = $"/Certs/Upload"; + return await SendHttpRequest(requestBody, endpoint); + } + + private async Task SendHttpRequest(T requestBody, string endpoint) { + try { + var request = new HttpRequestMessage(HttpMethod.Post, $"{_appSettings.Agent.AgentHostname}:{_appSettings.Agent.AgentPort}{endpoint}") { + Content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json") + }; + + request.Headers.Add("x-api-key", _appSettings.Agent.AgentKey); + request.Headers.Add("accept", "application/json"); + + var response = await _httpClient.SendAsync(request); + + if (response.IsSuccessStatusCode) { + return IDomainResult.Success(); + } + else { + _logger.LogError($"Request to {endpoint} failed with status code: {response.StatusCode}"); + return IDomainResult.Failed($"Request to {endpoint} failed with status code: {response.StatusCode}"); + } + } + catch (Exception ex) { + _logger.LogError(ex, "Something went wrong"); + return IDomainResult.Failed("Something went wrong"); + } + } + } +} diff --git a/src/LetsEncryptServer/Services/CertsFlowService.cs b/src/LetsEncryptServer/Services/CertsFlowService.cs index 26185db..ddb3be9 100644 --- a/src/LetsEncryptServer/Services/CertsFlowService.cs +++ b/src/LetsEncryptServer/Services/CertsFlowService.cs @@ -6,8 +6,7 @@ using DomainResults.Common; using MaksIT.LetsEncrypt.Entities; using MaksIT.LetsEncrypt.Services; -using MaksIT.LetsEncryptServer.Models.Requests; -using MaksIT.SSHProvider; +using MaksIT.Models.LetsEncryptServer.Requests; namespace MaksIT.LetsEncryptServer.Services; @@ -24,7 +23,7 @@ public interface ICertsFlowService : ICertsFlowServiceBase { Task CompleteChallengesAsync(Guid sessionId); Task GetOrderAsync(Guid sessionId, GetOrderRequest requestData); Task GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData); - (Dictionary?, IDomainResult) ApplyCertificates(Guid sessionId, GetCertificatesRequest requestData); + Task<(Dictionary?, IDomainResult)> ApplyCertificates(Guid sessionId, GetCertificatesRequest requestData); } public class CertsFlowService : ICertsFlowService { @@ -33,6 +32,7 @@ public class CertsFlowService : ICertsFlowService { private readonly ILogger _logger; private readonly ILetsEncryptService _letsEncryptService; private readonly ICacheService _cacheService; + private readonly IAgentService _agentService; private readonly string _acmePath; @@ -40,12 +40,14 @@ public class CertsFlowService : ICertsFlowService { IOptions appSettings, ILogger logger, ILetsEncryptService letsEncryptService, - ICacheService cashService + ICacheService cashService, + IAgentService agentService ) { _appSettings = appSettings.Value; _logger = logger; _letsEncryptService = letsEncryptService; _cacheService = cashService; + _agentService = agentService; _acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme"); if (!Directory.Exists(_acmePath)) @@ -138,7 +140,7 @@ public class CertsFlowService : ICertsFlowService { return IDomainResult.Success(); } - public (Dictionary?, IDomainResult) ApplyCertificates(Guid sessionId, GetCertificatesRequest requestData) { + public async Task<(Dictionary?, IDomainResult)> ApplyCertificates(Guid sessionId, GetCertificatesRequest requestData) { var results = new Dictionary(); foreach (var subject in requestData.Hostnames) { @@ -150,16 +152,13 @@ public class CertsFlowService : ICertsFlowService { results.Add(subject, content); } - var uploadResult = UploadToServer(results); - if (!uploadResult.IsSuccess) + // TODO: send the certificates to the server + var uploadResult = await _agentService.UploadCerts(results); + if(!uploadResult.IsSuccess) return (null, uploadResult); - //var notifyResult = NotifyHaproxy(results); - //if (!notifyResult.IsSuccess) - // return (null, notifyResult); - - var reloadResult = ReloadServer(); - if (!reloadResult.IsSuccess) + var reloadResult = await _agentService.ReloadService(_appSettings.Agent.ServiceToReload); + if(!reloadResult.IsSuccess) return (null, reloadResult); return IDomainResult.Success(results); @@ -175,129 +174,6 @@ public class CertsFlowService : ICertsFlowService { return IDomainResult.Success(fileContent); } - private IDomainResult UploadToServer(Dictionary results) { - var server = _appSettings.Server; - - try { - using (SSHService sshClient = (server.PrivateKeys != null && server.PrivateKeys.Any(x => !string.IsNullOrWhiteSpace(x))) - ? new SSHService(_logger, server.Ip, server.SSHPort, server.Username, server.PrivateKeys) - : !string.IsNullOrWhiteSpace(server.Password) - ? new SSHService(_logger, server.Ip, server.SSHPort, server.Username, server.Password) - : throw new ArgumentNullException("Neither private keys nor password was provided")) { - - var sshConnectResult = sshClient.Connect(); - if (!sshConnectResult.IsSuccess) - return sshConnectResult; - - foreach (var result in results) { - var uploadResult = sshClient.Upload(server.Path, result.Key, Encoding.UTF8.GetBytes(result.Value)); - if (!uploadResult.IsSuccess) - return uploadResult; - } - } - } - catch (Exception ex) { - var message = "Unable to upload files to remote server"; - _logger.LogError(ex, message); - - return IDomainResult.CriticalDependencyError(message); - } - - return IDomainResult.Success(); - } - private IDomainResult ReloadServer() { - var server = _appSettings.Server; - - try { - using (SSHService sshClient = (server.PrivateKeys != null && server.PrivateKeys.Any(x => !string.IsNullOrWhiteSpace(x))) - ? new SSHService(_logger, server.Ip, server.SSHPort, server.Username, server.PrivateKeys) - : !string.IsNullOrWhiteSpace(server.Password) - ? new SSHService(_logger, server.Ip, server.SSHPort, server.Username, server.Password) - : throw new ArgumentNullException("Neither private keys nor password was provided")) { - - var sshConnectResult = sshClient.Connect(); - if (!sshConnectResult.IsSuccess) - return sshConnectResult; - - // TODO: Prefer to create the native linux service which can receive the signal to reload the services - return sshClient.RunSudoCommand("", "systemctl reload haproxy"); - } - } - catch (Exception ex) { - var message = "Unable to upload files to remote server"; - _logger.LogError(ex, message); - - return IDomainResult.CriticalDependencyError(message); - } - - return IDomainResult.Success(); - } - - /// - /// Currently not working - /// - /// - /// - private IDomainResult NotifyHaproxy(Dictionary results) { - var server = _appSettings.Server; - - try { - using (var client = new TcpClient(server.Ip, server.SocketPort)) - using (var networkStream = client.GetStream()) - using (var writer = new StreamWriter(networkStream, Encoding.ASCII)) - using (var reader = new StreamReader(networkStream, Encoding.ASCII)) { - writer.AutoFlush = true; - - foreach (var result in results) { - var certFile = result.Key; - - // Prepare the certificate - string prepareCommand = $"new ssl cert {server.Path}/{certFile}"; - writer.WriteLine(prepareCommand); - writer.Flush(); - string prepareResponse = reader.ReadLine(); - //if (prepareResponse.Contains("error", StringComparison.OrdinalIgnoreCase)) { - // _logger.LogError($"Error while preparing certificate {certFile}: {prepareResponse}"); - // return IDomainResult.CriticalDependencyError($"Error while preparing certificate {certFile}"); - //} - - // Set the certificate - string setCommand = $"set ssl cert {server.Path}/{certFile} <<\n{result.Value}\n"; - writer.WriteLine(setCommand); - writer.Flush(); - string setResponse = reader.ReadLine(); - //if (setResponse.Contains("error", StringComparison.OrdinalIgnoreCase)) { - // _logger.LogError($"Error while setting certificate {certFile}: {setResponse}"); - // return IDomainResult.CriticalDependencyError($"Error while setting certificate {certFile}"); - //} - - // Commit the certificate - string commitCommand = $"commit ssl cert {server.Path}/{certFile}"; - writer.WriteLine(commitCommand); - writer.Flush(); - string commitResponse = reader.ReadLine(); - //if (commitResponse.Contains("error", StringComparison.OrdinalIgnoreCase)) { - // _logger.LogError($"Error while committing certificate {certFile}: {commitResponse}"); - // return IDomainResult.CriticalDependencyError($"Error while committing certificate {certFile}"); - //} - } - - _logger.LogInformation("Certificates committed successfully."); - } - } - catch (Exception ex) { - var message = "An error occurred while committing certificates"; - _logger.LogError(ex, message); - - return IDomainResult.CriticalDependencyError(message); - } - - return IDomainResult.Success(); - } - - - - private void DeleteExporedChallenges() { var currentDate = DateTime.Now; diff --git a/src/LetsEncryptServer/appsettings.json b/src/LetsEncryptServer/appsettings.json index a9fcddf..cff203a 100644 --- a/src/LetsEncryptServer/appsettings.json +++ b/src/LetsEncryptServer/appsettings.json @@ -13,14 +13,12 @@ "DevMode": true, - "Server": { - "Ip": "192.168.1.4", - "SocketPort": 9999, - "SSHPort": 22, - "Path": "/etc/haproxy/certs", - "Username": "acme", - "PrivateKeys": [], - "Password": "acme" + "Agent": { + "AgentHostname": "http://lblsrv0001.corp.maks-it.com", + "AgentPort": 5000, + "AgentKey": "UGnCaElLLJClHgUeet/yr7vNvPf13b1WkDJQMfsiP6I=", + + "ServiceToReload": "haproxy" } } } diff --git a/src/Models/Agent/Requests/CertsUploadRequest.cs b/src/Models/Agent/Requests/CertsUploadRequest.cs new file mode 100644 index 0000000..d2dbb7c --- /dev/null +++ b/src/Models/Agent/Requests/CertsUploadRequest.cs @@ -0,0 +1,7 @@ +namespace MaksIT.Models.Agent.Requests { + public class CertsUploadRequest { + + public Dictionary Certs { get; set; } + + } +} diff --git a/src/Models/Agent/Requests/ServiceReloadRequest.cs b/src/Models/Agent/Requests/ServiceReloadRequest.cs new file mode 100644 index 0000000..9921ba9 --- /dev/null +++ b/src/Models/Agent/Requests/ServiceReloadRequest.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MaksIT.Models.Agent.Requests { + public class ServiceReloadRequest { + public string ServiceName { get; set; } + } +} diff --git a/src/LetsEncryptServer/Models/Requests/GetCerificatesRequest.cs b/src/Models/LetsEncryptServer/Requests/GetCerificatesRequest.cs similarity index 61% rename from src/LetsEncryptServer/Models/Requests/GetCerificatesRequest.cs rename to src/Models/LetsEncryptServer/Requests/GetCerificatesRequest.cs index a93b758..cddb599 100644 --- a/src/LetsEncryptServer/Models/Requests/GetCerificatesRequest.cs +++ b/src/Models/LetsEncryptServer/Requests/GetCerificatesRequest.cs @@ -1,4 +1,4 @@ -namespace MaksIT.LetsEncryptServer.Models.Requests { +namespace MaksIT.Models.LetsEncryptServer.Requests { public class GetCertificatesRequest { public string[] Hostnames { get; set; } } diff --git a/src/LetsEncryptServer/Models/Requests/GetOrderRequest.cs b/src/Models/LetsEncryptServer/Requests/GetOrderRequest.cs similarity index 59% rename from src/LetsEncryptServer/Models/Requests/GetOrderRequest.cs rename to src/Models/LetsEncryptServer/Requests/GetOrderRequest.cs index e4f76fc..8cde81e 100644 --- a/src/LetsEncryptServer/Models/Requests/GetOrderRequest.cs +++ b/src/Models/LetsEncryptServer/Requests/GetOrderRequest.cs @@ -1,4 +1,4 @@ -namespace MaksIT.LetsEncryptServer.Models.Requests { +namespace MaksIT.Models.LetsEncryptServer.Requests { public class GetOrderRequest { public string[] Hostnames { get; set; } } diff --git a/src/LetsEncryptServer/Models/Requests/InitRequest.cs b/src/Models/LetsEncryptServer/Requests/InitRequest.cs similarity index 78% rename from src/LetsEncryptServer/Models/Requests/InitRequest.cs rename to src/Models/LetsEncryptServer/Requests/InitRequest.cs index 459bf25..0ae1e9e 100644 --- a/src/LetsEncryptServer/Models/Requests/InitRequest.cs +++ b/src/Models/LetsEncryptServer/Requests/InitRequest.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace MaksIT.LetsEncryptServer.Models.Requests { +namespace MaksIT.Models.LetsEncryptServer.Requests { public class InitRequest { public string[] Contacts { get; set; } } diff --git a/src/LetsEncryptServer/Models/Requests/NewOrderRequest.cs b/src/Models/LetsEncryptServer/Requests/NewOrderRequest.cs similarity index 70% rename from src/LetsEncryptServer/Models/Requests/NewOrderRequest.cs rename to src/Models/LetsEncryptServer/Requests/NewOrderRequest.cs index 71d2899..f1f8820 100644 --- a/src/LetsEncryptServer/Models/Requests/NewOrderRequest.cs +++ b/src/Models/LetsEncryptServer/Requests/NewOrderRequest.cs @@ -1,4 +1,4 @@ -namespace MaksIT.LetsEncryptServer.Models.Requests { +namespace MaksIT.Models.LetsEncryptServer.Requests { public class NewOrderRequest { public string[] Hostnames { get; set; } diff --git a/src/Models/Models.csproj b/src/Models/Models.csproj new file mode 100644 index 0000000..a5e4d8f --- /dev/null +++ b/src/Models/Models.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/src/Postman/LetsEncrypt Production.postman_collection.json b/src/Postman/LetsEncrypt Production.postman_collection.json new file mode 100644 index 0000000..609661c --- /dev/null +++ b/src/Postman/LetsEncrypt Production.postman_collection.json @@ -0,0 +1,479 @@ +{ + "info": { + "_postman_id": "728f64b6-893b-43fa-802e-ee836d1dc372", + "name": "LetsEncrypt Production", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "33635244" + }, + "item": [ + { + "name": "letsencrypt production", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://acme-v02.api.letsencrypt.org/directory", + "protocol": "https", + "host": [ + "acme-v02", + "api", + "letsencrypt", + "org" + ], + "path": [ + "directory" + ] + } + }, + "response": [] + }, + { + "name": "configure client", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Ensure the response status code is 200 (OK)\r", + "if (pm.response.code === 200) {\r", + " // Get the plain text response\r", + " let responseBody = pm.response.text();\r", + " \r", + " // Remove the surrounding quotes if present\r", + " responseBody = responseBody.replace(/^\"|\"$/g, '');\r", + " \r", + " // Check if the response body is a valid GUID\r", + " if (/^[0-9a-fA-F-]{36}$/.test(responseBody)) {\r", + " // Set the environment variable sessionId with the response\r", + " pm.environment.set(\"sessionId\", responseBody);\r", + " console.log(`sessionId set to: ${responseBody}`);\r", + " } else {\r", + " console.log(\"Response body is not a valid GUID\");\r", + " }\r", + "} else {\r", + " console.log(`Request failed with status code: ${pm.response.code}`);\r", + "}\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "http://localhost:8080/CertsFlow/ConfigureClient", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "CertsFlow", + "ConfigureClient" + ] + } + }, + "response": [] + }, + { + "name": "terms of service", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/CertsFlow/TermsOfService/{{sessionId}}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "CertsFlow", + "TermsOfService", + "{{sessionId}}" + ] + } + }, + "response": [] + }, + { + "name": "init", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Ensure the response status code is 200 (OK)\r", + "if (pm.response.code === 200) {\r", + " // Get the plain text response\r", + " let responseBody = pm.response.text();\r", + " \r", + " // Remove the surrounding quotes if present\r", + " responseBody = responseBody.replace(/^\"|\"$/g, '');\r", + " \r", + " // Check if the response body is a valid GUID\r", + " if (/^[0-9a-fA-F-]{36}$/.test(responseBody)) {\r", + " // Set the environment variable accountId with the response\r", + " pm.environment.set(\"accountId\", responseBody);\r", + " console.log(`accountId set to: ${responseBody}`);\r", + " } else {\r", + " console.log(\"Response body is not a valid GUID\");\r", + " }\r", + "} else {\r", + " console.log(`Request failed with status code: ${pm.response.code}`);\r", + "}\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "// Retrieve sessionId and accountId from environment variables or global variables\r", + "var sessionId = pm.environment.get(\"sessionId\") || pm.globals.get(\"sessionId\");\r", + "var accountId = pm.environment.get(\"accountId\") || pm.globals.get(\"accountId\");\r", + "\r", + "// Base URL without the optional accountId parameter\r", + "var baseUrl = `http://localhost:8080/CertsFlow/Init/${sessionId}`;\r", + "\r", + "// Append the accountId if it is provided\r", + "if (accountId) {\r", + " pm.request.url = `${baseUrl}/${accountId}`;\r", + "} else {\r", + " pm.request.url = baseUrl;\r", + "}" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"contacts\": [\r\n \"maksym.sadovnychyy@gmail.com\"\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/CertsFlow/Init/{{sessionId}}/{{accountId}}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "CertsFlow", + "Init", + "{{sessionId}}", + "{{accountId}}" + ] + } + }, + "response": [] + }, + { + "name": "new order", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Ensure the response status code is 200 (OK)\r", + "if (pm.response.code === 200) {\r", + " // Parse the JSON response\r", + " let responseBody;\r", + " try {\r", + " responseBody = pm.response.json();\r", + " } catch (e) {\r", + " console.error(\"Failed to parse JSON response:\", e);\r", + " return;\r", + " }\r", + "\r", + " // Check if the response is an array and has at least one element\r", + " if (Array.isArray(responseBody) && responseBody.length > 0) {\r", + " // Get the first element of the array\r", + " const firstElement = responseBody[0];\r", + " \r", + " // Set the environment variable challenge with the first element\r", + " pm.environment.set(\"challenge\", firstElement);\r", + " console.log(`challenge set to: ${firstElement}`);\r", + " } else {\r", + " console.log(\"Response body is not an array or is empty\");\r", + " }\r", + "} else {\r", + " console.log(`Request failed with status code: ${pm.response.code}`);\r", + "}\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"hostnames\": [\r\n \"maks-it.com\",\r\n \"auth.maks-it.com\"\r\n ],\r\n \"challengeType\": \"http-01\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/CertsFlow/NewOrder/{{sessionId}}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "CertsFlow", + "NewOrder", + "{{sessionId}}" + ] + } + }, + "response": [] + }, + { + "name": "acme-challenge local", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/.well-known/acme-challenge/{{challenge}}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + ".well-known", + "acme-challenge", + "{{challenge}}" + ] + } + }, + "response": [] + }, + { + "name": "acme-challenge", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://maks-it.com/.well-known/acme-challenge/{{challenge}}", + "protocol": "http", + "host": [ + "maks-it", + "com" + ], + "path": [ + ".well-known", + "acme-challenge", + "{{challenge}}" + ] + } + }, + "response": [] + }, + { + "name": "complete challenges", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/CertsFlow/CompleteChallenges/{{sessionId}}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "CertsFlow", + "CompleteChallenges", + "{{sessionId}}" + ] + } + }, + "response": [] + }, + { + "name": "get order", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"hostnames\": [\r\n \"maks-it.com\",\r\n \"auth.maks-it.com\"\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/CertsFlow/GetOrder/{{sessionId}}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "CertsFlow", + "GetOrder", + "{{sessionId}}" + ] + } + }, + "response": [] + }, + { + "name": "get certificates", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"hostnames\": [\r\n \"maks-it.com\",\r\n \"auth.maks-it.com\"\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/CertsFlow/GetCertificates/{{sessionId}}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "CertsFlow", + "GetCertificates", + "{{sessionId}}" + ] + } + }, + "response": [] + }, + { + "name": "apply certificates", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"hostnames\": [\r\n \"maks-it.com\",\r\n \"auth.maks-it.com\"\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/CertsFlow/ApplyCertificates/{{sessionId}}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "CertsFlow", + "ApplyCertificates", + "{{sessionId}}" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/LetsEncrypt.postman_collection.json b/src/Postman/LetsEncrypt Staging.postman_collection.json similarity index 96% rename from LetsEncrypt.postman_collection.json rename to src/Postman/LetsEncrypt Staging.postman_collection.json index ae8aaab..51962ce 100644 --- a/LetsEncrypt.postman_collection.json +++ b/src/Postman/LetsEncrypt Staging.postman_collection.json @@ -1,7 +1,7 @@ { "info": { - "_postman_id": "728f64b6-893b-43fa-802e-ee836d1dc372", - "name": "LetsEncrypt", + "_postman_id": "95186b61-1197-4a6e-a90f-d97223528d90", + "name": "LetsEncrypt Staging", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "33635244" }, @@ -28,27 +28,6 @@ }, "response": [] }, - { - "name": "letsencrypt production", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "https://acme-v02.api.letsencrypt.org/directory", - "protocol": "https", - "host": [ - "acme-v02", - "api", - "letsencrypt", - "org" - ], - "path": [ - "directory" - ] - } - }, - "response": [] - }, { "name": "configure client", "event": [ diff --git a/src/Postman/Maks-IT Agent.postman_collection.json b/src/Postman/Maks-IT Agent.postman_collection.json new file mode 100644 index 0000000..9624c74 --- /dev/null +++ b/src/Postman/Maks-IT Agent.postman_collection.json @@ -0,0 +1,77 @@ +{ + "info": { + "_postman_id": "1e13f461-ccaa-436a-92e4-e14c05131b96", + "name": "Maks-IT Agent", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "33635244" + }, + "item": [ + { + "name": "reload service", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{agentKey}}" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"serviceName\": {{serviceName}}\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://lblsrv0001.corp.maks-it.com:5000/Service/Reload", + "protocol": "http", + "host": [ + "lblsrv0001", + "corp", + "maks-it", + "com" + ], + "port": "5000", + "path": [ + "Service", + "Reload" + ] + } + }, + "response": [] + }, + { + "name": "hello world", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://lblsrv0001.corp.maks-it.com:5000/HelloWorld", + "protocol": "http", + "host": [ + "lblsrv0001", + "corp", + "maks-it", + "com" + ], + "port": "5000", + "path": [ + "HelloWorld" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file