Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .docs/terminology.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Terminology
>[!WARNING]
>ROUGH DRAFT!

* Workload Template = Bicep template defining multiple resources that make up a workload/app, maximally leaning on platform templates.
* Platform Template = Bicep template defining multiple resources that are to be used by multiple workloads and other deployments. Configurations should be focused on providing centralized services.
* Resource Template = Bicep template defining an individual resource and how to deploy it. These will typically be included in Mordos, but able to be modified to the MSPs desires. Configurations should be focused on defining best practice and business restrictions for wide-scale (multi-tenant) deployment.
* Module = Functionality add, typically included by Mordos, but able to be expanded and modified. These are code functions, like name generators and helpers.
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
target-branch: "dev"
63 changes: 28 additions & 35 deletions Mordos.API/Functions/BicepTemplatesFunctions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
Expand All @@ -14,22 +15,14 @@
/// <summary>
/// Azure Functions for managing Bicep templates
/// </summary>
public class BicepTemplatesFunctions
public class BicepTemplatesFunctions(IBicepTemplateService bicepTemplateService, ILogger<BicepTemplatesFunctions> logger)
{
private readonly IBicepTemplateService _bicepTemplateService;
private readonly ILogger<BicepTemplatesFunctions> _logger;

public BicepTemplatesFunctions(IBicepTemplateService bicepTemplateService, ILogger<BicepTemplatesFunctions> logger)
{
_bicepTemplateService = bicepTemplateService;
_logger = logger;
}

/// <summary>
/// Get all Bicep templates
/// </summary>
[Function("GetBicepTemplates")]
[OpenApiOperation(operationId: "GetBicepTemplates", tags: new[] { "BicepTemplates" }, Summary = "Get all Bicep templates", Description = "Retrieves a list of all Bicep templates with optional filtering", Visibility = OpenApiVisibilityType.Important)]
[OpenApiOperation(operationId: "GetBicepTemplates", tags: ["BicepTemplates"], Summary = "Get all Bicep templates", Description = "Retrieves a list of all Bicep templates with optional filtering", Visibility = OpenApiVisibilityType.Important)]
[OpenApiParameter(name: "nameFilter", In = ParameterLocation.Query, Required = false, Type = typeof(string), Summary = "Name filter", Description = "Filter templates by name (case-insensitive partial match)", Visibility = OpenApiVisibilityType.Important)]
[OpenApiParameter(name: "tagFilter", In = ParameterLocation.Query, Required = false, Type = typeof(string), Summary = "Tag filter", Description = "Filter templates by tag (case-insensitive partial match)", Visibility = OpenApiVisibilityType.Important)]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(IEnumerable<BicepTemplateResponse>), Summary = "Success", Description = "List of Bicep templates")]
Expand All @@ -41,14 +34,14 @@
var nameFilter = req.Query["nameFilter"].FirstOrDefault();
var tagFilter = req.Query["tagFilter"].FirstOrDefault();

_logger.LogInformation("Getting Bicep templates with filters - Name: {NameFilter}, Tag: {TagFilter}", nameFilter, tagFilter);
logger.LogInformation("Getting Bicep templates with filters - Name: {NameFilter}, Tag: {TagFilter}", nameFilter, tagFilter);

Check failure

Code scanning / CodeQL

Log entries created from user input High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 4 months ago

To fix the log forging vulnerability, we need to sanitize any user input before logging it. Since the log entry is plain text and the risk described involves newlines, we should remove carriage return (\r) and newline (\n) characters from the string before logging. We'll apply this to both nameFilter and tagFilter, since both are coming from user input and logged. The replacement should be for both possible null or empty values. The best way is to sanitize both variables shortly after extraction, before logging (line 36 or before 37). This fix will not alter the filters passed to the main business logic; it only affects what gets logged. No new methods or dependencies are required; a simple .Replace() (or regex if warranted) is enough.


Suggested changeset 1
Mordos.API/Functions/BicepTemplatesFunctions.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Mordos.API/Functions/BicepTemplatesFunctions.cs b/Mordos.API/Functions/BicepTemplatesFunctions.cs
--- a/Mordos.API/Functions/BicepTemplatesFunctions.cs
+++ b/Mordos.API/Functions/BicepTemplatesFunctions.cs
@@ -34,8 +34,11 @@
             var nameFilter = req.Query["nameFilter"].FirstOrDefault();
             var tagFilter = req.Query["tagFilter"].FirstOrDefault();
 
-            logger.LogInformation("Getting Bicep templates with filters - Name: {NameFilter}, Tag: {TagFilter}", nameFilter, tagFilter);
+            var safeNameFilter = nameFilter?.Replace("\r", "").Replace("\n", "");
+            var safeTagFilter = tagFilter?.Replace("\r", "").Replace("\n", "");
 
+            logger.LogInformation("Getting Bicep templates with filters - Name: {NameFilter}, Tag: {TagFilter}", safeNameFilter, safeTagFilter);
+
             var templates = await bicepTemplateService.GetAllTemplatesAsync(nameFilter, tagFilter);
             return new OkObjectResult(templates);
         }
EOF
@@ -34,8 +34,11 @@
var nameFilter = req.Query["nameFilter"].FirstOrDefault();
var tagFilter = req.Query["tagFilter"].FirstOrDefault();

logger.LogInformation("Getting Bicep templates with filters - Name: {NameFilter}, Tag: {TagFilter}", nameFilter, tagFilter);
var safeNameFilter = nameFilter?.Replace("\r", "").Replace("\n", "");
var safeTagFilter = tagFilter?.Replace("\r", "").Replace("\n", "");

logger.LogInformation("Getting Bicep templates with filters - Name: {NameFilter}, Tag: {TagFilter}", safeNameFilter, safeTagFilter);

var templates = await bicepTemplateService.GetAllTemplatesAsync(nameFilter, tagFilter);
return new OkObjectResult(templates);
}
Copilot is powered by AI and may make mistakes. Always verify output.

Check failure

Code scanning / CodeQL

Log entries created from user input High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 4 months ago

The best fix is to sanitize the user-provided input before logging it. For plain text logs, this means stripping or replacing newline (\n) and carriage return (\r) characters from nameFilter and tagFilter before passing them to the logger. This should be done right before logging, ensuring any future changes to log format or usage are covered. We only edit the logging line (line 37) to use sanitized variants of both filters. No new dependencies are required; use standard string replacement.


Suggested changeset 1
Mordos.API/Functions/BicepTemplatesFunctions.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Mordos.API/Functions/BicepTemplatesFunctions.cs b/Mordos.API/Functions/BicepTemplatesFunctions.cs
--- a/Mordos.API/Functions/BicepTemplatesFunctions.cs
+++ b/Mordos.API/Functions/BicepTemplatesFunctions.cs
@@ -34,7 +34,9 @@
             var nameFilter = req.Query["nameFilter"].FirstOrDefault();
             var tagFilter = req.Query["tagFilter"].FirstOrDefault();
 
-            logger.LogInformation("Getting Bicep templates with filters - Name: {NameFilter}, Tag: {TagFilter}", nameFilter, tagFilter);
+            var safeNameFilter = nameFilter?.Replace("\r", "").Replace("\n", "");
+            var safeTagFilter = tagFilter?.Replace("\r", "").Replace("\n", "");
+            logger.LogInformation("Getting Bicep templates with filters - Name: {NameFilter}, Tag: {TagFilter}", safeNameFilter, safeTagFilter);
 
             var templates = await bicepTemplateService.GetAllTemplatesAsync(nameFilter, tagFilter);
             return new OkObjectResult(templates);
EOF
@@ -34,7 +34,9 @@
var nameFilter = req.Query["nameFilter"].FirstOrDefault();
var tagFilter = req.Query["tagFilter"].FirstOrDefault();

logger.LogInformation("Getting Bicep templates with filters - Name: {NameFilter}, Tag: {TagFilter}", nameFilter, tagFilter);
var safeNameFilter = nameFilter?.Replace("\r", "").Replace("\n", "");
var safeTagFilter = tagFilter?.Replace("\r", "").Replace("\n", "");
logger.LogInformation("Getting Bicep templates with filters - Name: {NameFilter}, Tag: {TagFilter}", safeNameFilter, safeTagFilter);

var templates = await bicepTemplateService.GetAllTemplatesAsync(nameFilter, tagFilter);
return new OkObjectResult(templates);
Copilot is powered by AI and may make mistakes. Always verify output.

var templates = await _bicepTemplateService.GetAllTemplatesAsync(nameFilter, tagFilter);
var templates = await bicepTemplateService.GetAllTemplatesAsync(nameFilter, tagFilter);
return new OkObjectResult(templates);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving Bicep templates");
logger.LogError(ex, "Error retrieving Bicep templates");
return new ObjectResult("Internal server error") { StatusCode = 500 };
}
}
Expand All @@ -57,7 +50,7 @@
/// Get a specific Bicep template by ID
/// </summary>
[Function("GetBicepTemplate")]
[OpenApiOperation(operationId: "GetBicepTemplate", tags: new[] { "BicepTemplates" }, Summary = "Get Bicep template by ID", Description = "Retrieves a specific Bicep template by its unique identifier", Visibility = OpenApiVisibilityType.Important)]
[OpenApiOperation(operationId: "GetBicepTemplate", tags: ["BicepTemplates"], Summary = "Get Bicep template by ID", Description = "Retrieves a specific Bicep template by its unique identifier", Visibility = OpenApiVisibilityType.Important)]
[OpenApiParameter(name: "id", In = ParameterLocation.Path, Required = true, Type = typeof(string), Summary = "Template ID", Description = "The unique identifier of the template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(BicepTemplateResponse), Summary = "Success", Description = "The requested Bicep template")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.NotFound, contentType: "application/json", bodyType: typeof(string), Summary = "Not Found", Description = "Template not found")]
Expand All @@ -67,9 +60,9 @@
{
try
{
_logger.LogInformation("Getting Bicep template with ID: {Id}", id);
logger.LogInformation("Getting Bicep template with ID: {Id}", id);

var template = await _bicepTemplateService.GetTemplateByIdAsync(id);
var template = await bicepTemplateService.GetTemplateByIdAsync(id);
if (template == null)
{
return new NotFoundObjectResult($"Template with ID '{id}' not found");
Expand All @@ -79,7 +72,7 @@
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving Bicep template with ID: {Id}", id);
logger.LogError(ex, "Error retrieving Bicep template with ID: {Id}", id);
return new ObjectResult("Internal server error") { StatusCode = 500 };
}
}
Expand All @@ -88,7 +81,7 @@
/// Create a new Bicep template
/// </summary>
[Function("CreateBicepTemplate")]
[OpenApiOperation(operationId: "CreateBicepTemplate", tags: new[] { "BicepTemplates" }, Summary = "Create new Bicep template", Description = "Creates a new Bicep template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiOperation(operationId: "CreateBicepTemplate", tags: ["BicepTemplates"], Summary = "Create new Bicep template", Description = "Creates a new Bicep template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiRequestBody(contentType: "application/json", bodyType: typeof(CreateBicepTemplateRequest), Required = true, Description = "The template data to create")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.Created, contentType: "application/json", bodyType: typeof(BicepTemplateResponse), Summary = "Created", Description = "The created Bicep template")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(string), Summary = "Bad Request", Description = "Invalid request data")]
Expand All @@ -97,7 +90,7 @@
{
try
{
_logger.LogInformation("Creating new Bicep template");
logger.LogInformation("Creating new Bicep template");

var request = await req.ReadFromJsonAsync<CreateBicepTemplateRequest>();
if (request == null)
Expand All @@ -116,12 +109,12 @@
return new BadRequestObjectResult("Template content is required");
}

var template = await _bicepTemplateService.CreateTemplateAsync(request);
var template = await bicepTemplateService.CreateTemplateAsync(request);
return new ObjectResult(template) { StatusCode = 201 };
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating Bicep template");
logger.LogError(ex, "Error creating Bicep template");
return new ObjectResult("Internal server error") { StatusCode = 500 };
}
}
Expand All @@ -130,7 +123,7 @@
/// Update an existing Bicep template
/// </summary>
[Function("UpdateBicepTemplate")]
[OpenApiOperation(operationId: "UpdateBicepTemplate", tags: new[] { "BicepTemplates" }, Summary = "Update Bicep template", Description = "Updates an existing Bicep template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiOperation(operationId: "UpdateBicepTemplate", tags: ["BicepTemplates"], Summary = "Update Bicep template", Description = "Updates an existing Bicep template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiParameter(name: "id", In = ParameterLocation.Path, Required = true, Type = typeof(string), Summary = "Template ID", Description = "The unique identifier of the template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiRequestBody(contentType: "application/json", bodyType: typeof(UpdateBicepTemplateRequest), Required = true, Description = "The template data to update")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(BicepTemplateResponse), Summary = "Success", Description = "The updated Bicep template")]
Expand All @@ -142,15 +135,15 @@
{
try
{
_logger.LogInformation("Updating Bicep template with ID: {Id}", id);
logger.LogInformation("Updating Bicep template with ID: {Id}", id);

var request = await req.ReadFromJsonAsync<UpdateBicepTemplateRequest>();
if (request == null)
{
return new BadRequestObjectResult("Invalid request body");
}

var template = await _bicepTemplateService.UpdateTemplateAsync(id, request);
var template = await bicepTemplateService.UpdateTemplateAsync(id, request);
if (template == null)
{
return new NotFoundObjectResult($"Template with ID '{id}' not found");
Expand All @@ -160,7 +153,7 @@
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating Bicep template with ID: {Id}", id);
logger.LogError(ex, "Error updating Bicep template with ID: {Id}", id);
return new ObjectResult("Internal server error") { StatusCode = 500 };
}
}
Expand All @@ -169,7 +162,7 @@
/// Delete a Bicep template
/// </summary>
[Function("DeleteBicepTemplate")]
[OpenApiOperation(operationId: "DeleteBicepTemplate", tags: new[] { "BicepTemplates" }, Summary = "Delete Bicep template", Description = "Deletes a Bicep template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiOperation(operationId: "DeleteBicepTemplate", tags: ["BicepTemplates"], Summary = "Delete Bicep template", Description = "Deletes a Bicep template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiParameter(name: "id", In = ParameterLocation.Path, Required = true, Type = typeof(string), Summary = "Template ID", Description = "The unique identifier of the template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.NoContent, Summary = "No Content", Description = "Template deleted successfully")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.NotFound, contentType: "application/json", bodyType: typeof(string), Summary = "Not Found", Description = "Template not found")]
Expand All @@ -179,16 +172,16 @@
{
try
{
_logger.LogInformation("Deleting Bicep template with ID: {Id}", id);
logger.LogInformation("Deleting Bicep template with ID: {Id}", id);

// Check if template exists first
var existingTemplate = await _bicepTemplateService.GetTemplateByIdAsync(id);
var existingTemplate = await bicepTemplateService.GetTemplateByIdAsync(id);
if (existingTemplate == null)
{
return new NotFoundObjectResult($"Template with ID '{id}' not found");
}

var success = await _bicepTemplateService.DeleteTemplateAsync(id);
var success = await bicepTemplateService.DeleteTemplateAsync(id);
if (success)
{
return new NoContentResult();
Expand All @@ -198,7 +191,7 @@
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting Bicep template with ID: {Id}", id);
logger.LogError(ex, "Error deleting Bicep template with ID: {Id}", id);
return new ObjectResult("Internal server error") { StatusCode = 500 };
}
}
Expand All @@ -207,7 +200,7 @@
/// Validate a Bicep template
/// </summary>
[Function("ValidateBicepTemplate")]
[OpenApiOperation(operationId: "ValidateBicepTemplate", tags: new[] { "BicepTemplates" }, Summary = "Validate Bicep template", Description = "Validates a Bicep template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiOperation(operationId: "ValidateBicepTemplate", tags: ["BicepTemplates"], Summary = "Validate Bicep template", Description = "Validates a Bicep template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiParameter(name: "id", In = ParameterLocation.Path, Required = true, Type = typeof(string), Summary = "Template ID", Description = "The unique identifier of the template", Visibility = OpenApiVisibilityType.Important)]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(object), Summary = "Success", Description = "Validation result")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.NotFound, contentType: "application/json", bodyType: typeof(string), Summary = "Not Found", Description = "Template not found")]
Expand All @@ -217,21 +210,21 @@
{
try
{
_logger.LogInformation("Validating Bicep template with ID: {Id}", id);
logger.LogInformation("Validating Bicep template with ID: {Id}", id);

// Check if template exists first
var existingTemplate = await _bicepTemplateService.GetTemplateByIdAsync(id);
var existingTemplate = await bicepTemplateService.GetTemplateByIdAsync(id);
if (existingTemplate == null)
{
return new NotFoundObjectResult($"Template with ID '{id}' not found");
}

var success = await _bicepTemplateService.ValidateTemplateAsync(id);
var success = await bicepTemplateService.ValidateTemplateAsync(id);
return new OkObjectResult(new { Success = success, Message = success ? "Template validation passed" : "Template validation failed" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error validating Bicep template with ID: {Id}", id);
logger.LogError(ex, "Error validating Bicep template with ID: {Id}", id);
return new ObjectResult("Internal server error") { StatusCode = 500 };
}
}
Expand Down
Loading
Loading