Merge pull request 'feat(connect): add -SkipCertificateCheck and -AllowInsecureTransport switches' (#11) from dev into main

Reviewed-on: #11
This commit was merged in pull request #11.
This commit is contained in:
2026-06-05 20:49:03 +00:00
8 changed files with 112 additions and 5 deletions
+6
View File
@@ -6,6 +6,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
## Unreleased ## Unreleased
## 2026.06.05.2040
- Build produced from commit 1270c9099cae.
## Unreleased (carried forward)
## 2026.06.05.0240 ## 2026.06.05.0240
- Build produced from commit b438abf18f18. - Build produced from commit b438abf18f18.
+2 -2
View File
@@ -1,6 +1,6 @@
@{ @{
RootModule = 'PSInfisicalAPI.psm1' RootModule = 'PSInfisicalAPI.psm1'
ModuleVersion = '2026.06.05.0240' ModuleVersion = '2026.06.05.2040'
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51' GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
Author = 'Grace Solutions' Author = 'Grace Solutions'
CompanyName = 'Grace Solutions' CompanyName = 'Grace Solutions'
@@ -62,7 +62,7 @@
LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html' LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html'
ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI' ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI'
ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.' ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.'
CommitHash = 'b438abf18f18' CommitHash = '1270c9099cae'
} }
} }
} }
Binary file not shown.
@@ -84,5 +84,38 @@ namespace PSInfisicalAPI.Tests
Assert.Equal("explicit-org", cmdlet.CallResolveOrganizationId(ConnectionWithDefaults(), "explicit-org")); Assert.Equal("explicit-org", cmdlet.CallResolveOrganizationId(ConnectionWithDefaults(), "explicit-org"));
Assert.Empty(logger.VerboseEntries); Assert.Empty(logger.VerboseEntries);
} }
[Fact]
public void InfisicalConnection_Defaults_TransportFlags_To_False()
{
InfisicalConnection connection = new InfisicalConnection();
Assert.False(connection.SkipCertificateCheck);
Assert.False(connection.AllowInsecureTransport);
}
[Fact]
public void ShouldSkipCertificateCheck_Reads_From_Current_Session()
{
InfisicalConnection previous = InfisicalSessionManager.Current;
try
{
TestCmdlet cmdlet = CreateCmdletWith(new RecordingLogger());
MethodInfo virt = typeof(InfisicalCmdletBase).GetMethod("ShouldSkipCertificateCheck", BindingFlags.NonPublic | BindingFlags.Instance);
InfisicalSessionManager.SetCurrent(null);
Assert.False((bool)virt.Invoke(cmdlet, null));
InfisicalConnection session = ConnectionWithDefaults();
session.IsConnected = true;
session.SkipCertificateCheck = true;
InfisicalSessionManager.SetCurrent(session);
Assert.True((bool)virt.Invoke(cmdlet, null));
}
finally
{
InfisicalSessionManager.SetCurrent(previous);
}
}
} }
} }
@@ -62,12 +62,24 @@ namespace PSInfisicalAPI.Cmdlets
[Parameter] [Parameter]
public SwitchParameter PassThru { get; set; } public SwitchParameter PassThru { get; set; }
[Parameter]
public SwitchParameter SkipCertificateCheck { get; set; }
[Parameter]
public SwitchParameter AllowInsecureTransport { get; set; }
protected override bool ShouldSkipCertificateCheck()
{
return SkipCertificateCheck.IsPresent;
}
protected override void ProcessRecord() protected override void ProcessRecord()
{ {
try try
{ {
ResolveMissingParametersFromEnvironment(); ResolveMissingParametersFromEnvironment();
ValidateRequiredParameters(); ValidateRequiredParameters();
ValidateTransportSafety();
IInfisicalAuthProvider provider; IInfisicalAuthProvider provider;
InfisicalAuthenticationRequest request; InfisicalAuthenticationRequest request;
@@ -179,7 +191,9 @@ namespace PSInfisicalAPI.Cmdlets
ConnectedAtUtc = DateTimeOffset.UtcNow, ConnectedAtUtc = DateTimeOffset.UtcNow,
ExpiresAtUtc = authResult.ExpiresAtUtc, ExpiresAtUtc = authResult.ExpiresAtUtc,
IsConnected = true, IsConnected = true,
AccessToken = authResult.AccessToken AccessToken = authResult.AccessToken,
SkipCertificateCheck = SkipCertificateCheck.IsPresent,
AllowInsecureTransport = AllowInsecureTransport.IsPresent
}; };
InfisicalSessionManager.SetCurrent(connection); InfisicalSessionManager.SetCurrent(connection);
@@ -195,6 +209,26 @@ namespace PSInfisicalAPI.Cmdlets
} }
} }
private void ValidateTransportSafety()
{
bool isHttp = BaseUri != null && string.Equals(BaseUri.Scheme, "http", StringComparison.OrdinalIgnoreCase);
if (isHttp && !AllowInsecureTransport.IsPresent)
{
throw new InfisicalConfigurationException("BaseUri '" + BaseUri + "' is not HTTPS. Re-run Connect-Infisical with -AllowInsecureTransport to permit plaintext.");
}
if (SkipCertificateCheck.IsPresent)
{
Logger.Warning(Component, "SkipCertificateCheck is enabled. TLS certificate validation is disabled for this session. Do not use in production.");
}
if (AllowInsecureTransport.IsPresent && isHttp)
{
Logger.Warning(Component, "AllowInsecureTransport is enabled and BaseUri uses HTTP. Credentials and secrets will traverse the network unencrypted. Do not use in production.");
}
}
private void ResolveMissingParametersFromEnvironment() private void ResolveMissingParametersFromEnvironment()
{ {
bool tokenSet = string.Equals(ParameterSetName, ParameterSetToken, StringComparison.Ordinal); bool tokenSet = string.Equals(ParameterSetName, ParameterSetToken, StringComparison.Ordinal);
@@ -31,13 +31,19 @@ namespace PSInfisicalAPI.Cmdlets
{ {
if (_httpClient == null) if (_httpClient == null)
{ {
_httpClient = new InfisicalHttpClient(Logger); _httpClient = new InfisicalHttpClient(Logger, 100, ShouldSkipCertificateCheck());
} }
return _httpClient; return _httpClient;
} }
} }
protected virtual bool ShouldSkipCertificateCheck()
{
InfisicalConnection current = InfisicalSessionManager.Current;
return current != null && current.SkipCertificateCheck;
}
protected void ThrowTerminatingForException(string component, string operation, Exception exception) protected void ThrowTerminatingForException(string component, string operation, Exception exception)
{ {
InfisicalErrorDetails details = InfisicalErrorHandler.BuildDetails(component, operation, exception); InfisicalErrorDetails details = InfisicalErrorHandler.BuildDetails(component, operation, exception);
@@ -15,6 +15,8 @@ namespace PSInfisicalAPI.Connections
public DateTimeOffset ConnectedAtUtc { get; set; } public DateTimeOffset ConnectedAtUtc { get; set; }
public DateTimeOffset? ExpiresAtUtc { get; set; } public DateTimeOffset? ExpiresAtUtc { get; set; }
public bool IsConnected { get; set; } public bool IsConnected { get; set; }
public bool SkipCertificateCheck { get; set; }
public bool AllowInsecureTransport { get; set; }
public Dictionary<string, string> ResolvedEndpointVersions { get; } = new Dictionary<string, string>(StringComparer.Ordinal); public Dictionary<string, string> ResolvedEndpointVersions { get; } = new Dictionary<string, string>(StringComparer.Ordinal);
+27 -1
View File
@@ -2,6 +2,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Security;
using System.Reflection;
using System.Text; using System.Text;
using PSInfisicalAPI.Errors; using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Logging; using PSInfisicalAPI.Logging;
@@ -11,13 +13,18 @@ namespace PSInfisicalAPI.Http
public sealed class InfisicalHttpClient : IInfisicalHttpClient public sealed class InfisicalHttpClient : IInfisicalHttpClient
{ {
private const string Component = "HttpClient"; private const string Component = "HttpClient";
private static readonly PropertyInfo PerRequestCertCallbackProperty =
typeof(HttpWebRequest).GetProperty("ServerCertificateValidationCallback");
private readonly IInfisicalLogger _logger; private readonly IInfisicalLogger _logger;
private readonly int _timeoutSeconds; private readonly int _timeoutSeconds;
private readonly bool _skipCertificateCheck;
public InfisicalHttpClient(IInfisicalLogger logger, int timeoutSeconds = 100) public InfisicalHttpClient(IInfisicalLogger logger, int timeoutSeconds = 100, bool skipCertificateCheck = false)
{ {
_logger = logger ?? NullInfisicalLogger.Instance; _logger = logger ?? NullInfisicalLogger.Instance;
_timeoutSeconds = timeoutSeconds; _timeoutSeconds = timeoutSeconds;
_skipCertificateCheck = skipCertificateCheck;
} }
public InfisicalHttpResponse Send(InfisicalHttpRequest request) public InfisicalHttpResponse Send(InfisicalHttpRequest request)
@@ -44,6 +51,11 @@ namespace PSInfisicalAPI.Http
webRequest.ReadWriteTimeout = _timeoutSeconds * 1000; webRequest.ReadWriteTimeout = _timeoutSeconds * 1000;
webRequest.UseDefaultCredentials = true; webRequest.UseDefaultCredentials = true;
if (_skipCertificateCheck)
{
ApplyInsecureCertificateBypass(webRequest);
}
IWebProxy systemProxy = WebRequest.GetSystemWebProxy(); IWebProxy systemProxy = WebRequest.GetSystemWebProxy();
if (systemProxy != null) if (systemProxy != null)
{ {
@@ -95,6 +107,20 @@ namespace PSInfisicalAPI.Http
} }
} }
private void ApplyInsecureCertificateBypass(HttpWebRequest webRequest)
{
RemoteCertificateValidationCallback callback = (sender, certificate, chain, errors) => true;
if (PerRequestCertCallbackProperty != null && PerRequestCertCallbackProperty.CanWrite)
{
PerRequestCertCallbackProperty.SetValue(webRequest, callback, null);
return;
}
_logger.Warning(Component, "Per-request ServerCertificateValidationCallback unavailable on this runtime; falling back to global ServicePointManager override for this process.");
ServicePointManager.ServerCertificateValidationCallback = callback;
}
private static void ApplyHeaders(HttpWebRequest webRequest, IDictionary<string, string> headers) private static void ApplyHeaders(HttpWebRequest webRequest, IDictionary<string, string> headers)
{ {
if (headers == null) if (headers == null)