From 1270c9099cae7106796668af3292a813c7beaf0a Mon Sep 17 00:00:00 2001 From: GraceSolutions Date: Fri, 5 Jun 2026 16:39:56 -0400 Subject: [PATCH] feat(connect): add -SkipCertificateCheck and -AllowInsecureTransport switches Adds opt-in insecure-transport controls for pre-production / self-signed scenarios. Configured once on Connect-Infisical and persisted on the InfisicalConnection; every downstream cmdlet inherits via the base class. Connect-Infisical: - [switch] SkipCertificateCheck Disable TLS chain validation per request. - [switch] AllowInsecureTransport Permit http:// BaseUri (else throw). - Logs explicit Warning records when either is enabled. InfisicalConnection: - New SkipCertificateCheck / AllowInsecureTransport bool properties (default false). Persisted on the session for downstream cmdlets. InfisicalCmdletBase: - HttpClient getter now constructs InfisicalHttpClient with the flag derived from a new virtual ShouldSkipCertificateCheck(), which reads the current session. Connect-Infisical overrides it to use its own switch since the session does not yet exist during auth. InfisicalHttpClient: - New skipCertificateCheck ctor parameter; when on, sets HttpWebRequest.ServerCertificateValidationCallback per request via reflection (property is available at runtime on PS 5.1/7 but not surfaced by netstandard2.0). Falls back to ServicePointManager with a warning if reflection is unavailable. Tests: - InfisicalConnection defaults both flags to false. - ShouldSkipCertificateCheck reads from InfisicalSessionManager.Current. --- .../CmdletBaseInheritanceTests.cs | 33 +++++++++++++++++ .../Cmdlets/ConnectInfisicalCmdlet.cs | 36 ++++++++++++++++++- .../Cmdlets/InfisicalCmdletBase.cs | 8 ++++- .../Connections/InfisicalConnection.cs | 2 ++ .../Http/InfisicalHttpClient.cs | 28 ++++++++++++++- 5 files changed, 104 insertions(+), 3 deletions(-) diff --git a/src/PSInfisicalAPI.Tests/CmdletBaseInheritanceTests.cs b/src/PSInfisicalAPI.Tests/CmdletBaseInheritanceTests.cs index 92840be..a3791c6 100644 --- a/src/PSInfisicalAPI.Tests/CmdletBaseInheritanceTests.cs +++ b/src/PSInfisicalAPI.Tests/CmdletBaseInheritanceTests.cs @@ -84,5 +84,38 @@ namespace PSInfisicalAPI.Tests Assert.Equal("explicit-org", cmdlet.CallResolveOrganizationId(ConnectionWithDefaults(), "explicit-org")); 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); + } + } } } diff --git a/src/PSInfisicalAPI/Cmdlets/ConnectInfisicalCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/ConnectInfisicalCmdlet.cs index d199fb9..dd80ace 100644 --- a/src/PSInfisicalAPI/Cmdlets/ConnectInfisicalCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/ConnectInfisicalCmdlet.cs @@ -62,12 +62,24 @@ namespace PSInfisicalAPI.Cmdlets [Parameter] 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() { try { ResolveMissingParametersFromEnvironment(); ValidateRequiredParameters(); + ValidateTransportSafety(); IInfisicalAuthProvider provider; InfisicalAuthenticationRequest request; @@ -179,7 +191,9 @@ namespace PSInfisicalAPI.Cmdlets ConnectedAtUtc = DateTimeOffset.UtcNow, ExpiresAtUtc = authResult.ExpiresAtUtc, IsConnected = true, - AccessToken = authResult.AccessToken + AccessToken = authResult.AccessToken, + SkipCertificateCheck = SkipCertificateCheck.IsPresent, + AllowInsecureTransport = AllowInsecureTransport.IsPresent }; 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() { bool tokenSet = string.Equals(ParameterSetName, ParameterSetToken, StringComparison.Ordinal); diff --git a/src/PSInfisicalAPI/Cmdlets/InfisicalCmdletBase.cs b/src/PSInfisicalAPI/Cmdlets/InfisicalCmdletBase.cs index 4e35b17..bdc69ce 100644 --- a/src/PSInfisicalAPI/Cmdlets/InfisicalCmdletBase.cs +++ b/src/PSInfisicalAPI/Cmdlets/InfisicalCmdletBase.cs @@ -31,13 +31,19 @@ namespace PSInfisicalAPI.Cmdlets { if (_httpClient == null) { - _httpClient = new InfisicalHttpClient(Logger); + _httpClient = new InfisicalHttpClient(Logger, 100, ShouldSkipCertificateCheck()); } return _httpClient; } } + protected virtual bool ShouldSkipCertificateCheck() + { + InfisicalConnection current = InfisicalSessionManager.Current; + return current != null && current.SkipCertificateCheck; + } + protected void ThrowTerminatingForException(string component, string operation, Exception exception) { InfisicalErrorDetails details = InfisicalErrorHandler.BuildDetails(component, operation, exception); diff --git a/src/PSInfisicalAPI/Connections/InfisicalConnection.cs b/src/PSInfisicalAPI/Connections/InfisicalConnection.cs index 7e6362d..0eabef2 100644 --- a/src/PSInfisicalAPI/Connections/InfisicalConnection.cs +++ b/src/PSInfisicalAPI/Connections/InfisicalConnection.cs @@ -15,6 +15,8 @@ namespace PSInfisicalAPI.Connections public DateTimeOffset ConnectedAtUtc { get; set; } public DateTimeOffset? ExpiresAtUtc { get; set; } public bool IsConnected { get; set; } + public bool SkipCertificateCheck { get; set; } + public bool AllowInsecureTransport { get; set; } public Dictionary ResolvedEndpointVersions { get; } = new Dictionary(StringComparer.Ordinal); diff --git a/src/PSInfisicalAPI/Http/InfisicalHttpClient.cs b/src/PSInfisicalAPI/Http/InfisicalHttpClient.cs index 55fe767..654c8d9 100644 --- a/src/PSInfisicalAPI/Http/InfisicalHttpClient.cs +++ b/src/PSInfisicalAPI/Http/InfisicalHttpClient.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.Security; +using System.Reflection; using System.Text; using PSInfisicalAPI.Errors; using PSInfisicalAPI.Logging; @@ -11,13 +13,18 @@ namespace PSInfisicalAPI.Http public sealed class InfisicalHttpClient : IInfisicalHttpClient { private const string Component = "HttpClient"; + private static readonly PropertyInfo PerRequestCertCallbackProperty = + typeof(HttpWebRequest).GetProperty("ServerCertificateValidationCallback"); + private readonly IInfisicalLogger _logger; 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; _timeoutSeconds = timeoutSeconds; + _skipCertificateCheck = skipCertificateCheck; } public InfisicalHttpResponse Send(InfisicalHttpRequest request) @@ -44,6 +51,11 @@ namespace PSInfisicalAPI.Http webRequest.ReadWriteTimeout = _timeoutSeconds * 1000; webRequest.UseDefaultCredentials = true; + if (_skipCertificateCheck) + { + ApplyInsecureCertificateBypass(webRequest); + } + IWebProxy systemProxy = WebRequest.GetSystemWebProxy(); 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 headers) { if (headers == null)