mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(feature): sever side agent implementation
This commit is contained in:
parent
5ecc436ddf
commit
6328afd5fb
62
README.md
62
README.md
@ -122,4 +122,64 @@ frontend web
|
|||||||
#---------------------------------------------------------------------
|
#---------------------------------------------------------------------
|
||||||
backend acme_challenge_backend
|
backend acme_challenge_backend
|
||||||
server acme_challenge 127.0.0.1:8080
|
server acme_challenge 127.0.0.1:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|||||||
23
src/Agent/Agent.csproj
Normal file
23
src/Agent/Agent.csproj
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Models\Models.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="build_and_deploy.sh">
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
6
src/Agent/Configuration.cs
Normal file
6
src/Agent/Configuration.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace MaksIT.Agent {
|
||||||
|
public class Configuration {
|
||||||
|
public required string ApiKey { get; set; }
|
||||||
|
public required string CertsPath { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/Agent/Controllers/CertsController.cs
Normal file
41
src/Agent/Controllers/CertsController.cs
Normal file
@ -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<Configuration> 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
14
src/Agent/Controllers/HelloWorldController.cs
Normal file
14
src/Agent/Controllers/HelloWorldController.cs
Normal file
@ -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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/Agent/Controllers/ServiceController.cs
Normal file
61
src/Agent/Controllers/ServiceController.cs
Normal file
@ -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<Configuration> 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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
34
src/Agent/Program.cs
Normal file
34
src/Agent/Program.cs
Normal file
@ -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<Configuration>() ?? throw new ArgumentNullException();
|
||||||
|
|
||||||
|
// Allow configurations to be available through IOptions<Configuration>
|
||||||
|
builder.Services.Configure<Configuration>(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();
|
||||||
31
src/Agent/Properties/launchSettings.json
Normal file
31
src/Agent/Properties/launchSettings.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/Agent/ServiceReloader.http
Normal file
6
src/Agent/ServiceReloader.http
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@ServiceReloader_HostAddress = http://localhost:5186
|
||||||
|
|
||||||
|
GET {{ServiceReloader_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
8
src/Agent/appsettings.Development.json
Normal file
8
src/Agent/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/Agent/appsettings.json
Normal file
14
src/Agent/appsettings.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
|
||||||
|
"Configuration": {
|
||||||
|
"ApiKey": "UGnCaElLLJClHgUeet/yr7vNvPf13b1WkDJQMfsiP6I=",
|
||||||
|
"CertsPath": "/etc/haproxy/certs"
|
||||||
|
}
|
||||||
|
}
|
||||||
77
src/Agent/build_and_deploy.sh
Normal file
77
src/Agent/build_and_deploy.sh
Normal file
@ -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 <<EOL
|
||||||
|
[Unit]
|
||||||
|
Description=Maks-IT Agent
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
WorkingDirectory=$INSTALL_DIR
|
||||||
|
ExecStart=$EXEC_CMD
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
KillSignal=SIGINT
|
||||||
|
SyslogIdentifier=dotnet-servicereloader
|
||||||
|
User=root
|
||||||
|
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOL"
|
||||||
|
|
||||||
|
# Reload systemd to recognize the new service, enable it to start on boot, and start the service now
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now $SERVICE_NAME.service
|
||||||
|
|
||||||
|
# Create the firewall service rule
|
||||||
|
echo '<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<service>
|
||||||
|
<short>Maks-IT Agent</short>
|
||||||
|
<port protocol="tcp" port="'$SERVICE_PORT'"/>
|
||||||
|
</service>' > /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
|
||||||
@ -15,10 +15,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3374FDB1
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SSHProviderTests", "Tests\SSHSerivceTests\SSHProviderTests.csproj", "{3937760A-FFB3-4A8C-ABD1-CDDCE1D977C4}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SSHProviderTests", "Tests\SSHSerivceTests\SSHProviderTests.csproj", "{3937760A-FFB3-4A8C-ABD1-CDDCE1D977C4}"
|
||||||
EndProject
|
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
|
EndProject
|
||||||
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{0233E43F-435D-4309-B20C-ECD4BFBD2E63}"
|
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{0233E43F-435D-4309-B20C-ECD4BFBD2E63}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{0233E43F-435D-4309-B20C-ECD4BFBD2E63}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@ -1,20 +1,17 @@
|
|||||||
namespace MaksIT.LetsEncryptServer {
|
namespace MaksIT.LetsEncryptServer {
|
||||||
|
|
||||||
public class Server {
|
public class Agent {
|
||||||
public required string Ip { get; set; }
|
public required string AgentHostname { get; set; }
|
||||||
public required int SocketPort { get; set; }
|
public required int AgentPort { get; set; }
|
||||||
public required int SSHPort { get; set; }
|
public required string AgentKey { get; set; }
|
||||||
public required string Path { get; set; }
|
|
||||||
|
|
||||||
public required string Username { get; set; }
|
public required string ServiceToReload { get; set; }
|
||||||
public string? Password { get; set; }
|
|
||||||
public string[]? PrivateKeys { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Configuration {
|
public class Configuration {
|
||||||
public required string Production { get; set; }
|
public required string Production { get; set; }
|
||||||
public required string Staging { get; set; }
|
public required string Staging { get; set; }
|
||||||
public required bool DevMode { get; set; }
|
public required bool DevMode { get; set; }
|
||||||
public required Server Server { get; set; }
|
public required Agent Agent { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@ using Microsoft.Extensions.Options;
|
|||||||
|
|
||||||
using DomainResults.Mvc;
|
using DomainResults.Mvc;
|
||||||
|
|
||||||
using MaksIT.LetsEncryptServer.Models.Requests;
|
|
||||||
using MaksIT.LetsEncryptServer.Services;
|
using MaksIT.LetsEncryptServer.Services;
|
||||||
|
using MaksIT.Models.LetsEncryptServer.Requests;
|
||||||
|
|
||||||
|
|
||||||
namespace MaksIT.LetsEncryptServer.Controllers;
|
namespace MaksIT.LetsEncryptServer.Controllers;
|
||||||
@ -107,8 +107,8 @@ public class CertsFlowController : ControllerBase {
|
|||||||
/// <param name="requestData"></param>
|
/// <param name="requestData"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost("[action]/{sessionId}")]
|
[HttpPost("[action]/{sessionId}")]
|
||||||
public IActionResult ApplyCertificates(Guid sessionId, [FromBody] GetCertificatesRequest requestData) {
|
public async Task<IActionResult> ApplyCertificates(Guid sessionId, [FromBody] GetCertificatesRequest requestData) {
|
||||||
var result = _certsFlowService.ApplyCertificates(sessionId, requestData);
|
var result = await _certsFlowService.ApplyCertificates(sessionId, requestData);
|
||||||
return result.ToActionResult();
|
return result.ToActionResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,11 +17,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\LetsEncrypt\LetsEncrypt.csproj" />
|
<ProjectReference Include="..\LetsEncrypt\LetsEncrypt.csproj" />
|
||||||
<ProjectReference Include="..\SSHProvider\SSHProvider.csproj" />
|
<ProjectReference Include="..\Models\Models.csproj" />
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Models\Responses\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -28,6 +28,7 @@ builder.Services.AddMemoryCache();
|
|||||||
builder.Services.AddHttpClient<ILetsEncryptService, LetsEncryptService>();
|
builder.Services.AddHttpClient<ILetsEncryptService, LetsEncryptService>();
|
||||||
builder.Services.AddScoped<ICertsFlowService, CertsFlowService>();
|
builder.Services.AddScoped<ICertsFlowService, CertsFlowService>();
|
||||||
builder.Services.AddSingleton<ICacheService, CacheService>();
|
builder.Services.AddSingleton<ICacheService, CacheService>();
|
||||||
|
builder.Services.AddHttpClient<IAgentService, AgentService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|||||||
72
src/LetsEncryptServer/Services/AgentService.cs
Normal file
72
src/LetsEncryptServer/Services/AgentService.cs
Normal file
@ -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<IDomainResult> GetHelloWorld();
|
||||||
|
Task<IDomainResult> UploadCerts(Dictionary<string, string> certs);
|
||||||
|
Task<IDomainResult> ReloadService(string serviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AgentService : IAgentService {
|
||||||
|
|
||||||
|
private readonly Configuration _appSettings;
|
||||||
|
private readonly ILogger<AgentService> _logger;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
|
public AgentService(
|
||||||
|
IOptions<Configuration> appSettings,
|
||||||
|
ILogger<AgentService> logger,
|
||||||
|
HttpClient httpClient
|
||||||
|
) {
|
||||||
|
_appSettings = appSettings.Value;
|
||||||
|
_logger = logger;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IDomainResult> GetHelloWorld() {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IDomainResult> ReloadService(string serviceName) {
|
||||||
|
var requestBody = new ServiceReloadRequest { ServiceName = serviceName };
|
||||||
|
var endpoint = $"/Service/Reload";
|
||||||
|
return await SendHttpRequest(requestBody, endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IDomainResult> UploadCerts(Dictionary<string, string> certs) {
|
||||||
|
var requestBody = new CertsUploadRequest { Certs = certs };
|
||||||
|
var endpoint = $"/Certs/Upload";
|
||||||
|
return await SendHttpRequest(requestBody, endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IDomainResult> SendHttpRequest<T>(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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,8 +6,7 @@ using DomainResults.Common;
|
|||||||
|
|
||||||
using MaksIT.LetsEncrypt.Entities;
|
using MaksIT.LetsEncrypt.Entities;
|
||||||
using MaksIT.LetsEncrypt.Services;
|
using MaksIT.LetsEncrypt.Services;
|
||||||
using MaksIT.LetsEncryptServer.Models.Requests;
|
using MaksIT.Models.LetsEncryptServer.Requests;
|
||||||
using MaksIT.SSHProvider;
|
|
||||||
|
|
||||||
|
|
||||||
namespace MaksIT.LetsEncryptServer.Services;
|
namespace MaksIT.LetsEncryptServer.Services;
|
||||||
@ -24,7 +23,7 @@ public interface ICertsFlowService : ICertsFlowServiceBase {
|
|||||||
Task<IDomainResult> CompleteChallengesAsync(Guid sessionId);
|
Task<IDomainResult> CompleteChallengesAsync(Guid sessionId);
|
||||||
Task<IDomainResult> GetOrderAsync(Guid sessionId, GetOrderRequest requestData);
|
Task<IDomainResult> GetOrderAsync(Guid sessionId, GetOrderRequest requestData);
|
||||||
Task<IDomainResult> GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData);
|
Task<IDomainResult> GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData);
|
||||||
(Dictionary<string, string>?, IDomainResult) ApplyCertificates(Guid sessionId, GetCertificatesRequest requestData);
|
Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificates(Guid sessionId, GetCertificatesRequest requestData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CertsFlowService : ICertsFlowService {
|
public class CertsFlowService : ICertsFlowService {
|
||||||
@ -33,6 +32,7 @@ public class CertsFlowService : ICertsFlowService {
|
|||||||
private readonly ILogger<CertsFlowService> _logger;
|
private readonly ILogger<CertsFlowService> _logger;
|
||||||
private readonly ILetsEncryptService _letsEncryptService;
|
private readonly ILetsEncryptService _letsEncryptService;
|
||||||
private readonly ICacheService _cacheService;
|
private readonly ICacheService _cacheService;
|
||||||
|
private readonly IAgentService _agentService;
|
||||||
|
|
||||||
private readonly string _acmePath;
|
private readonly string _acmePath;
|
||||||
|
|
||||||
@ -40,12 +40,14 @@ public class CertsFlowService : ICertsFlowService {
|
|||||||
IOptions<Configuration> appSettings,
|
IOptions<Configuration> appSettings,
|
||||||
ILogger<CertsFlowService> logger,
|
ILogger<CertsFlowService> logger,
|
||||||
ILetsEncryptService letsEncryptService,
|
ILetsEncryptService letsEncryptService,
|
||||||
ICacheService cashService
|
ICacheService cashService,
|
||||||
|
IAgentService agentService
|
||||||
) {
|
) {
|
||||||
_appSettings = appSettings.Value;
|
_appSettings = appSettings.Value;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_letsEncryptService = letsEncryptService;
|
_letsEncryptService = letsEncryptService;
|
||||||
_cacheService = cashService;
|
_cacheService = cashService;
|
||||||
|
_agentService = agentService;
|
||||||
|
|
||||||
_acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme");
|
_acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme");
|
||||||
if (!Directory.Exists(_acmePath))
|
if (!Directory.Exists(_acmePath))
|
||||||
@ -138,7 +140,7 @@ public class CertsFlowService : ICertsFlowService {
|
|||||||
return IDomainResult.Success();
|
return IDomainResult.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Dictionary<string, string>?, IDomainResult) ApplyCertificates(Guid sessionId, GetCertificatesRequest requestData) {
|
public async Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificates(Guid sessionId, GetCertificatesRequest requestData) {
|
||||||
var results = new Dictionary<string, string>();
|
var results = new Dictionary<string, string>();
|
||||||
|
|
||||||
foreach (var subject in requestData.Hostnames) {
|
foreach (var subject in requestData.Hostnames) {
|
||||||
@ -150,16 +152,13 @@ public class CertsFlowService : ICertsFlowService {
|
|||||||
results.Add(subject, content);
|
results.Add(subject, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
var uploadResult = UploadToServer(results);
|
// TODO: send the certificates to the server
|
||||||
if (!uploadResult.IsSuccess)
|
var uploadResult = await _agentService.UploadCerts(results);
|
||||||
|
if(!uploadResult.IsSuccess)
|
||||||
return (null, uploadResult);
|
return (null, uploadResult);
|
||||||
|
|
||||||
//var notifyResult = NotifyHaproxy(results);
|
var reloadResult = await _agentService.ReloadService(_appSettings.Agent.ServiceToReload);
|
||||||
//if (!notifyResult.IsSuccess)
|
if(!reloadResult.IsSuccess)
|
||||||
// return (null, notifyResult);
|
|
||||||
|
|
||||||
var reloadResult = ReloadServer();
|
|
||||||
if (!reloadResult.IsSuccess)
|
|
||||||
return (null, reloadResult);
|
return (null, reloadResult);
|
||||||
|
|
||||||
return IDomainResult.Success(results);
|
return IDomainResult.Success(results);
|
||||||
@ -175,129 +174,6 @@ public class CertsFlowService : ICertsFlowService {
|
|||||||
return IDomainResult.Success(fileContent);
|
return IDomainResult.Success(fileContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IDomainResult UploadToServer(Dictionary<string, string> 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Currently not working
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="results"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private IDomainResult NotifyHaproxy(Dictionary<string, string> 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() {
|
private void DeleteExporedChallenges() {
|
||||||
var currentDate = DateTime.Now;
|
var currentDate = DateTime.Now;
|
||||||
|
|
||||||
|
|||||||
@ -13,14 +13,12 @@
|
|||||||
|
|
||||||
"DevMode": true,
|
"DevMode": true,
|
||||||
|
|
||||||
"Server": {
|
"Agent": {
|
||||||
"Ip": "192.168.1.4",
|
"AgentHostname": "http://lblsrv0001.corp.maks-it.com",
|
||||||
"SocketPort": 9999,
|
"AgentPort": 5000,
|
||||||
"SSHPort": 22,
|
"AgentKey": "UGnCaElLLJClHgUeet/yr7vNvPf13b1WkDJQMfsiP6I=",
|
||||||
"Path": "/etc/haproxy/certs",
|
|
||||||
"Username": "acme",
|
"ServiceToReload": "haproxy"
|
||||||
"PrivateKeys": [],
|
|
||||||
"Password": "acme"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/Models/Agent/Requests/CertsUploadRequest.cs
Normal file
7
src/Models/Agent/Requests/CertsUploadRequest.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace MaksIT.Models.Agent.Requests {
|
||||||
|
public class CertsUploadRequest {
|
||||||
|
|
||||||
|
public Dictionary<string, string> Certs { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/Models/Agent/Requests/ServiceReloadRequest.cs
Normal file
11
src/Models/Agent/Requests/ServiceReloadRequest.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
namespace MaksIT.LetsEncryptServer.Models.Requests {
|
namespace MaksIT.Models.LetsEncryptServer.Requests {
|
||||||
public class GetCertificatesRequest {
|
public class GetCertificatesRequest {
|
||||||
public string[] Hostnames { get; set; }
|
public string[] Hostnames { get; set; }
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
namespace MaksIT.LetsEncryptServer.Models.Requests {
|
namespace MaksIT.Models.LetsEncryptServer.Requests {
|
||||||
public class GetOrderRequest {
|
public class GetOrderRequest {
|
||||||
public string[] Hostnames { get; set; }
|
public string[] Hostnames { get; set; }
|
||||||
}
|
}
|
||||||
@ -4,7 +4,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MaksIT.LetsEncryptServer.Models.Requests {
|
namespace MaksIT.Models.LetsEncryptServer.Requests {
|
||||||
public class InitRequest {
|
public class InitRequest {
|
||||||
public string[] Contacts { get; set; }
|
public string[] Contacts { get; set; }
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
namespace MaksIT.LetsEncryptServer.Models.Requests {
|
namespace MaksIT.Models.LetsEncryptServer.Requests {
|
||||||
public class NewOrderRequest {
|
public class NewOrderRequest {
|
||||||
public string[] Hostnames { get; set; }
|
public string[] Hostnames { get; set; }
|
||||||
|
|
||||||
14
src/Models/Models.csproj
Normal file
14
src/Models/Models.csproj
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Agent\Responses\" />
|
||||||
|
<Folder Include="LetsEncryptServer\Responses\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
479
src/Postman/LetsEncrypt Production.postman_collection.json
Normal file
479
src/Postman/LetsEncrypt Production.postman_collection.json
Normal file
@ -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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"info": {
|
"info": {
|
||||||
"_postman_id": "728f64b6-893b-43fa-802e-ee836d1dc372",
|
"_postman_id": "95186b61-1197-4a6e-a90f-d97223528d90",
|
||||||
"name": "LetsEncrypt",
|
"name": "LetsEncrypt Staging",
|
||||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||||
"_exporter_id": "33635244"
|
"_exporter_id": "33635244"
|
||||||
},
|
},
|
||||||
@ -28,27 +28,6 @@
|
|||||||
},
|
},
|
||||||
"response": []
|
"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",
|
"name": "configure client",
|
||||||
"event": [
|
"event": [
|
||||||
77
src/Postman/Maks-IT Agent.postman_collection.json
Normal file
77
src/Postman/Maks-IT Agent.postman_collection.json
Normal file
@ -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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user