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
95 changes: 95 additions & 0 deletions DevProxy.Tests/ProxyConfigurationCiDefaultsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using DevProxy.Commands;
using DevProxy.Proxy;
using Microsoft.Extensions.Configuration;
using Xunit;

namespace DevProxy.Tests;

public class ProxyConfigurationCiDefaultsTests
{
private static ProxyConfiguration Create(bool isCi, Dictionary<string, string?>? settings = null)
{
var configurationRoot = new ConfigurationBuilder()
.AddInMemoryCollection(settings ?? [])
.Build();
return new ProxyConfiguration(configurationRoot, isCi);
}

[Fact]
public void NotCi_KeepsDefaults()
{
var config = Create(isCi: false);

Assert.True(config.AsSystemProxy);
Assert.True(config.InstallCert);
Assert.False(config.AsSystemProxyDefaultedForCi);
Assert.False(config.InstallCertDefaultedForCi);
}

[Fact]
public void Ci_NotSetInConfig_DisablesSystemProxyAndCertInstall()
{
var config = Create(isCi: true);

Assert.False(config.AsSystemProxy);
Assert.False(config.InstallCert);
Assert.True(config.AsSystemProxyDefaultedForCi);
Assert.True(config.InstallCertDefaultedForCi);
}

[Theory]
[InlineData("true", true)]
[InlineData("false", false)]
public void Ci_SetInConfig_KeepsConfiguredValues(string value, bool expected)
{
var config = Create(isCi: true, new()
{
["asSystemProxy"] = value,
["installCert"] = value,
});

Assert.Equal(expected, config.AsSystemProxy);
Assert.Equal(expected, config.InstallCert);
Assert.False(config.AsSystemProxyDefaultedForCi);
Assert.False(config.InstallCertDefaultedForCi);
}

[Fact]
public void Ci_OnlyAsSystemProxyInConfig_DefaultsInstallCertOnly()
{
var config = Create(isCi: true, new() { ["asSystemProxy"] = "true" });

Assert.True(config.AsSystemProxy);
Assert.False(config.InstallCert);
Assert.False(config.AsSystemProxyDefaultedForCi);
Assert.True(config.InstallCertDefaultedForCi);
}

[Fact]
public void ShouldLogCiDefaults_NotCi_ReturnsFalse()
{
Assert.False(DevProxyCommand.ShouldLogCiDefaults(Create(isCi: false), null, null));
}

[Fact]
public void ShouldLogCiDefaults_CiDefaultsApplied_ReturnsTrue()
{
Assert.True(DevProxyCommand.ShouldLogCiDefaults(Create(isCi: true), null, null));
}

[Fact]
public void ShouldLogCiDefaults_OneOverriddenOnCommandLine_ReturnsTrue()
{
Assert.True(DevProxyCommand.ShouldLogCiDefaults(Create(isCi: true), true, null));
}

[Fact]
public void ShouldLogCiDefaults_BothOverriddenOnCommandLine_ReturnsFalse()
{
Assert.False(DevProxyCommand.ShouldLogCiDefaults(Create(isCi: true), true, true));
}
}
25 changes: 23 additions & 2 deletions DevProxy/Commands/DevProxyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -490,8 +490,8 @@ private void ConfigureCommand(DevProxyConfigOptions configOptions)
{
try
{
var asSystemProxy = input.GetValue(asSystemProxyOption) ?? true;
var installCert = input.GetValue(installCertOption) ?? true;
var asSystemProxy = input.GetValue(asSystemProxyOption) ?? _proxyConfiguration.AsSystemProxy;
var installCert = input.GetValue(installCertOption) ?? _proxyConfiguration.InstallCert;
if (asSystemProxy && !installCert)
{
input.AddError($"Requires option '{AsSystemProxyOptionName}' to be 'false'");
Expand Down Expand Up @@ -746,6 +746,7 @@ private void ConfigureFromOptions(ParseResult parseResult)
{
_proxyConfiguration.InstallCert = installCert.Value;
}
LogCiDefaults(asSystemProxy, installCert);
var timeout = parseResult.GetValueOrDefault<long?>(TimeoutOptionName);
if (timeout is not null)
{
Expand All @@ -771,6 +772,26 @@ private void ConfigureFromOptions(ParseResult parseResult)
}
}

private void LogCiDefaults(bool? asSystemProxyOption, bool? installCertOption)
{
if (_proxyConfiguration is not ProxyConfiguration proxyConfiguration ||
!ShouldLogCiDefaults(proxyConfiguration, asSystemProxyOption, installCertOption))
{
return;
}

_logger.LogInformation(
"CI environment detected. Using asSystemProxy: {AsSystemProxy}, installCert: {InstallCert}. To change, use {AsSystemProxyOption} and {InstallCertOption} or set asSystemProxy and installCert in the config file.",
proxyConfiguration.AsSystemProxy.ToString().ToLowerInvariant(),
proxyConfiguration.InstallCert.ToString().ToLowerInvariant(),
AsSystemProxyOptionName,
InstallCertOptionName);
}

internal static bool ShouldLogCiDefaults(ProxyConfiguration proxyConfiguration, bool? asSystemProxyOption, bool? installCertOption) =>
(proxyConfiguration.AsSystemProxyDefaultedForCi && asSystemProxyOption is null) ||
(proxyConfiguration.InstallCertDefaultedForCi && installCertOption is null);

private async Task CheckForNewVersionAsync()
{
var newReleaseInfo = await _updateNotification.CheckForNewVersionAsync(_proxyConfiguration.NewVersionNotification);
Expand Down
33 changes: 32 additions & 1 deletion DevProxy/Proxy/ProxyConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,41 @@ public string ConfigFile
public IEnumerable<string> WatchProcessNames { get; set; } = [];
public bool WatchProcessTree { get; set; }

public ProxyConfiguration(IConfigurationRoot configurationRoot)
// True when the value wasn't set in the config file and was turned off
// because Dev Proxy is running in CI.
internal bool AsSystemProxyDefaultedForCi { get; private set; }
internal bool InstallCertDefaultedForCi { get; private set; }

internal static bool IsCi => Environment.GetEnvironmentVariable("CI") is not null;

public ProxyConfiguration(IConfigurationRoot configurationRoot) : this(configurationRoot, IsCi)
{
}

internal ProxyConfiguration(IConfigurationRoot configurationRoot, bool isCi)
{
_configurationRoot = configurationRoot;
_configurationRoot.Bind(this);
if (isCi)
{
ApplyCiDefaults(_configurationRoot);
}
}

// In CI, don't change machine-wide state (system proxy, trusted root
// certificates) unless the config file explicitly asks for it.
private void ApplyCiDefaults(IConfiguration configuration)
{
if (configuration[nameof(AsSystemProxy)] is null)
{
AsSystemProxy = false;
AsSystemProxyDefaultedForCi = true;
}
if (configuration[nameof(InstallCert)] is null)
{
InstallCert = false;
InstallCertDefaultedForCi = true;
}
}

internal static string GetConfigFilePath(IConfigurationRoot configurationRoot)
Expand Down
2 changes: 2 additions & 0 deletions skills/dev-proxy/references/ci-cd-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Dev Proxy runs headlessly in CI/CD pipelines to automate API testing — catchin

With Dev Proxy 3.3.1 or later, every management API request requires a bearer token. Set `CI=true` in the proxy process to suppress automatic token output, and retrieve credentials explicitly with `devproxy api token`. Keep shell tracing disabled around credentials. For multiple instances, add `--pid <PID>` and use the actual API URL from `devproxy status`.

When `CI` is set, Dev Proxy doesn't register itself as the system proxy or install its root certificate, unless you set `asSystemProxy` or `installCert` in the config file or pass `--as-system-proxy` or `--install-cert`. Point your app at Dev Proxy with `HTTPS_PROXY`/`HTTP_PROXY` and trust the root certificate as shown below.

Use action versions that support the authenticated API. For integrations that have not yet added bearer authentication, use the CLI and authenticated HTTP examples below.

## GitHub Actions (Recommended)
Expand Down
3 changes: 2 additions & 1 deletion skills/dev-proxy/references/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ Plugin-specific override:
| `watchPids` | — | `--watch-pids` | Only intercept from these PIDs |
| `watchProcessNames` | — | `--watch-process-names` | Only intercept from these processes |
| `watchProcessTree` | — | `--watch-process-tree` | Also intercept descendants of watched processes |
| `asSystemProxy` | `true` | `--as-system-proxy` | Register as system proxy |
| `asSystemProxy` | `true` (`false` when `CI` is set) | `--as-system-proxy` | Register as system proxy |
| `installCert` | `true` (`false` when `CI` is set) | `--install-cert` | Install and trust the root certificate |
| — | off | `--detach` | Run in detached (background) mode |
| — | `text` | `--output` | Output format: `text` or `json` |

Expand Down
Loading