Skip to content
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
2.6.0
Features:
- Improve management job performance by converting device group commits to template stack commits.
- Add validation to the certificate alias length, ensuring that alias length does not exceed 31 characters for Panorama and does not exceed 63 characters for Firewall.

2.5.0
Features:
- Add support for multiple Device Groups. You can now specify a comma-delimited list of Device Groups for your Certificate Store. i.e. `Group 1;Group 2;Group 3`.
Expand Down
18 changes: 17 additions & 1 deletion PaloAlto.IntegrationTests/BaseIntegrationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,32 @@
using Keyfactor.Orchestrators.Common.Enums;
using Keyfactor.Orchestrators.Extensions;
using Keyfactor.Orchestrators.Extensions.Interfaces;
using Microsoft.Extensions.Logging;
using Moq;
using Newtonsoft.Json;
using PaloAlto.IntegrationTests.Models;
using PaloAlto.Tests.Common.TestUtilities;
using Xunit;
using Xunit.Abstractions;

namespace PaloAlto.IntegrationTests;

public abstract class BaseIntegrationTest
{
protected readonly string MockCertificatePassword = "sldfklsdfsldjfk";
protected readonly ILogger Logger;

public BaseIntegrationTest(ITestOutputHelper output)
{
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.SetMinimumLevel(LogLevel.Trace)
.AddProvider(new XunitLoggerProvider(output));
});

Logger = loggerFactory.CreateLogger<BaseIntegrationTest>();
}

protected void AssertJobSuccess(JobResult result, string context)
{
Expand Down Expand Up @@ -75,7 +91,7 @@ private JobResult ProcessManagementJob(ManagementJobConfiguration config)
mgmtSecretResolver
.Setup(m => m.Resolve(It.Is<string>(s => s == config.ServerPassword)))
.Returns(() => config.ServerPassword);
var mgmt = new Management(mgmtSecretResolver.Object);
var mgmt = new Management(mgmtSecretResolver.Object, Logger);
return mgmt.ProcessJob(config);
}

Expand Down
6 changes: 6 additions & 0 deletions PaloAlto.IntegrationTests/InventoryIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@

using PaloAlto.IntegrationTests.Models;
using Xunit;
using Xunit.Abstractions;

namespace PaloAlto.IntegrationTests;

public class InventoryIntegrationTests : BaseIntegrationTest
{
public InventoryIntegrationTests(ITestOutputHelper output): base(output)
{

}

#region Firewall Tests

// Test Case 6 repeats across Management + Inventory. Keeping number in place for parity.
Expand Down
109 changes: 107 additions & 2 deletions PaloAlto.IntegrationTests/ManagementIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@
using PaloAlto.IntegrationTests.Generators;
using PaloAlto.IntegrationTests.Models;
using Xunit;
using Xunit.Abstractions;

namespace PaloAlto.IntegrationTests;

public class ManagementIntegrationTests : BaseIntegrationTest
{
public ManagementIntegrationTests(ITestOutputHelper output) : base(output)
{
}

#region Firewall Tests

[Fact(DisplayName = "TC01: Firewall Enroll No Bindings")]
Expand Down Expand Up @@ -550,7 +555,57 @@ public void TestCase16c_PanoramaEnroll_NoOverwrite_MultipleDeviceGroups_AddsToPa
{
StorePath =
"/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='CertificatesTemplate']/config/shared",
DeviceGroup = "Group1;Group1", // This will be treated as separate device groups in the app code.
DeviceGroup = "Group1;Group1;Group1", // This will be treated as separate device groups in the app code.
Alias = alias,
Overwrite = false,

CertificateContents = certificateContent,
CertificatePassword = MockCertificatePassword,
TemplateStack = ""
};
props.AddPanoramaCredentials();

var result = ProcessManagementAddJob(props);

AssertJobSuccess(result, "Add");
}

[Fact(DisplayName = "TC16d: Panorama No Overwrite with No Device Group But TemplateStack Defined Adds to Panorama and Firewalls")]
public void TestCase16d_PanoramaEnroll_NoOverwrite_NoDeviceGroups_WithTemplateStack_AddsToPanoramaAndFirewalls()
{
var alias = AliasGenerator.Generate();
var certificateContent = PfxGenerator.GetBlobWithChain(alias, MockCertificatePassword);

var props = new TestManagementJobConfigurationProperties()
{
StorePath =
"/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='CertificatesTemplate']/config/shared",
DeviceGroup = "",
Alias = alias,
Overwrite = false,

CertificateContents = certificateContent,
CertificatePassword = MockCertificatePassword,
TemplateStack = "CertificatesStack"
};
props.AddPanoramaCredentials();

var result = ProcessManagementAddJob(props);

AssertJobSuccess(result, "Add");
}

[Fact(DisplayName = "TC16e: Panorama No Overwrite with No Device Group And No TemplateStack Defined Adds to Panorama and Firewalls")]
public void TestCase16e_PanoramaEnroll_NoOverwrite_NoDeviceGroups_NoTemplateStack_AddsToPanoramaAndFirewalls()
{
var alias = AliasGenerator.Generate();
var certificateContent = PfxGenerator.GetBlobWithChain(alias, MockCertificatePassword);

var props = new TestManagementJobConfigurationProperties()
{
StorePath =
"/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='CertificatesTemplate']/config/shared",
DeviceGroup = "",
Alias = alias,
Overwrite = false,

Expand Down Expand Up @@ -896,6 +951,56 @@ public void TestCase25_PanoramaAdd_Vsys_BoundCert_WithOverride_ShouldSucceed()
var updateResult = ProcessManagementAddJob(updateProps);
AssertJobSuccess(updateResult, "Update");
}

#endregion

[Fact(DisplayName = "TC26a: Panorama Enroll when alias name is too long, returns error")]
public void TestCase26a_PanoramaEnroll_WhenAliasNameIsTooLong_ReturnsFailure()
{
var alias = new string('a', 32);
var certificateContent = PfxGenerator.GetBlobWithChain(alias, MockCertificatePassword);

var props = new TestManagementJobConfigurationProperties()
{
StorePath =
"/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='CertificatesTemplate']/config/shared",
DeviceGroup = "Group1",
Alias = alias,
Overwrite = false,

CertificateContents = certificateContent,
CertificatePassword = MockCertificatePassword,
TemplateStack = ""
};
props.AddPanoramaCredentials();

var result = ProcessManagementAddJob(props);

AssertJobFailure(result, "Alias name is too long, it must not be more than 31 characters. Current length: 32");
}

[Fact(DisplayName = "TC26b: Firewall Enroll when alias name is too long, returns error")]
public void TestCase26b_FirewallEnroll_WhenAliasNameIsTooLong_ReturnsFailure()
{
var alias = new string('a', 64);
var certificateContent = PfxGenerator.GetBlobWithChain(alias, MockCertificatePassword);

var props = new TestManagementJobConfigurationProperties()
{
StorePath =
"/config/shared",
DeviceGroup = "",
Alias = alias,
Overwrite = false,

CertificateContents = certificateContent,
CertificatePassword = MockCertificatePassword,
TemplateStack = ""
};
props.AddPanoramaCredentials();

var result = ProcessManagementAddJob(props);

AssertJobFailure(result, "Alias name is too long, it must not be more than 63 characters. Current length: 64");
}
}
5 changes: 5 additions & 0 deletions PaloAlto.IntegrationTests/PaloAlto.IntegrationTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\PaloAlto.Tests.Common\PaloAlto.Tests.Common.csproj" />
<ProjectReference Include="..\PaloAlto\PaloAlto.csproj" />
</ItemGroup>

Expand All @@ -32,4 +33,8 @@
</None>
</ItemGroup>

<ItemGroup>
<Folder Include="TestUtilities\" />
</ItemGroup>

</Project>
14 changes: 14 additions & 0 deletions PaloAlto.Tests.Common/PaloAlto.Tests.Common.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
</ItemGroup>

</Project>
46 changes: 46 additions & 0 deletions PaloAlto.Tests.Common/TestUtilities/XunitLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2025 Keyfactor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Microsoft.Extensions.Logging;
using Xunit.Abstractions;

namespace PaloAlto.Tests.Common.TestUtilities;

public class XunitLogger : ILogger
{
private readonly ITestOutputHelper _output;

public XunitLogger(string _, ITestOutputHelper output)
{
_output = output;
}

public IDisposable BeginScope<TState>(TState state) => null!;
public bool IsEnabled(LogLevel logLevel) => true;

public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception? exception,
Func<TState, Exception?, string> formatter)
{
_output.WriteLine($"[{logLevel}] {formatter(state, exception)}");

if (exception != null)
{
_output.WriteLine(exception.ToString());
}
}
}
35 changes: 35 additions & 0 deletions PaloAlto.Tests.Common/TestUtilities/XunitLoggerProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2025 Keyfactor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Microsoft.Extensions.Logging;
using Xunit.Abstractions;

namespace PaloAlto.Tests.Common.TestUtilities;

public class XunitLoggerProvider : ILoggerProvider
{
private readonly ITestOutputHelper _output;

public XunitLoggerProvider(ITestOutputHelper output)
{
_output = output;
}

public ILogger CreateLogger(string categoryName)
{
return new XunitLogger(categoryName, _output);
}

public void Dispose() { }
}
22 changes: 22 additions & 0 deletions PaloAlto.UnitTests/BaseUnitTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.Extensions.Logging;
using PaloAlto.Tests.Common.TestUtilities;
using Xunit.Abstractions;

namespace PaloAlto.UnitTests;

public abstract class BaseUnitTest
{
protected readonly ILogger Logger;

public BaseUnitTest(ITestOutputHelper output)
{
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.SetMinimumLevel(LogLevel.Trace)
.AddProvider(new XunitLoggerProvider(output));
});

Logger = loggerFactory.CreateLogger<BaseUnitTest>();
}
}
Loading
Loading