feat(connect): add -SkipCertificateCheck and -AllowInsecureTransport switches #11
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user