Skip to content

Commit

Permalink
Hiding KeyVault secrets on Environment page (#155)
Browse files Browse the repository at this point in the history
* Hiding KeyVault secret environment variables with checkbox

* Adding key vault hiding secrets to NewUI

Co-authored-by: Kar Shin Lin <karslin@microsoft.com>
  • Loading branch information
karshinlin and karshinlin authored Dec 9, 2020
1 parent 1904bd3 commit a906dbe
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 36 deletions.
99 changes: 99 additions & 0 deletions Kudu.Core/Helpers/KeyVaultReferenceHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;

namespace Kudu.Core.Helpers
{
public static class KeyVaultReferenceHelper
{
private const string AppSettingPrefix = "APPSETTING_";
private const string KeyVaultReferenceInfoEnvVar = "WEBSITE_KEYVAULT_REFERENCES";

/// Example dictionary:
/// {
/// "secret1":
/// {
/// { "rawReference", "@Microsoft.KeyVault(SecretUri=)" },
/// { "status", "ValueNotFound" }
/// }
/// }
/// </summary>
private static Dictionary<string, Dictionary<string, string>> KeyVaultReferencesInformation = GetKeyVaultReferencesInformation();

public static int NumKeyVaultReferences
{
get
{
return KeyVaultReferencesInformation.Count();
}
}

/// <summary>
/// Simple filter to hide secrets from KeyVault references.
/// </summary>
/// <param name="environmentVariables">All variables for the site</param>
/// <param name="hideKeyVaultSecrets">Whether to hide KeyVault secrets</param>
/// <returns>Filtered environment variables</returns>
public static IDictionary<object, object> KeyVaultReferencesFilter(IDictionary variables, bool hideKeyVaultSecrets)
{
IDictionary<object, object> filteredEnvironmentVariables = new Dictionary<object, object>();
foreach (var entry in variables.Keys)
{
filteredEnvironmentVariables.Add(entry, HideKeyVaultSecret(entry, variables[entry], hideKeyVaultSecrets));
}
return filteredEnvironmentVariables;
}

/// <summary>
/// Deserializes KeyVault References information in the form of a Dictionary<string, Dictionary<string, string>>
/// </summary>
/// <param name="serializedInformationBlob">Serialized dictionary containing KeyVault reference information</param>
public static Dictionary<string, Dictionary<string, string>> GetKeyVaultReferencesInformation()
{
try
{
var serializedInformationBlob = System.Environment.GetEnvironmentVariable(KeyVaultReferenceInfoEnvVar);
if (serializedInformationBlob != null)
{
var result = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string>>>(serializedInformationBlob);
if (result != null)
{
return result;
}
}
}
catch (Exception)
{
}

return new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
}

/// <summary>
/// Hides a KeyVault secret if enabled, or just returns the original value if disabled
/// </summary>
/// <param name="key">Environment variable key</param>
/// <param name="value">Environment variable original value</param>
/// <param name="hideValue">Whether to hide</param>
/// <returns>Hidden value (or original)</returns>
public static object HideKeyVaultSecret(object key, object value, bool hideValue)
{
var keyString = (string)key;
if (hideValue && KeyVaultReferencesInformation.ContainsKey(keyString))
{
try
{
return "[Hidden - " + KeyVaultReferencesInformation[keyString]["status"] + ": " + KeyVaultReferencesInformation[keyString]["rawReference"] + "]";
}
catch (Exception)
{
return "[Hidden KeyVault secret]";
}
}

return value;
}
}
}
71 changes: 56 additions & 15 deletions Kudu.Services.Web/Pages/Env.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@

<div class="container">
<h3>Index</h3>

@{
bool isHidden = String.IsNullOrEmpty(httpContextAccessor.HttpContext.Request.Query["hideSecrets"]) || ("true").Equals(httpContextAccessor.HttpContext.Request.Query["hideSecrets"]); // Hide by default
}
@if (Kudu.Core.Helpers.KeyVaultReferenceHelper.NumKeyVaultReferences > 0)
{
<div class="checkbox span5">
<label class="checkbox" style="padding:0px">
@Html.CheckBox("hideKeyVaultSecrets", isHidden, new { id = "hideKeyVaultSecrets", style = "padding:0px", onchange = "keyVaultCheckbox()" }) Hide Values of KeyVault References
</label>
</div>
}
<ul>
<li><a href="#sysInfo">System Info</a></li>
<li><a href="#appSettings">App Settings</a></li>
Expand Down Expand Up @@ -50,29 +62,34 @@
</ul>

<h3 id="appSettings">AppSettings</h3>
@{
var appSettingsInEnvironment = new Dictionary<string, string>(_settingsManager.GetValues());
var appSettingVariablesDict = Kudu.Core.Helpers.KeyVaultReferenceHelper.KeyVaultReferencesFilter(appSettingsInEnvironment, isHidden);
}
<ul class="fixed-width">
@using System.Configuration;
@using System.Linq
@using Core.Infrastructure
@using Kudu.Contracts.Settings
@using System.Linq
@using Core.Infrastructure
@using Kudu.Contracts.Settings
@using Microsoft.AspNetCore.Http
@foreach (string name in System.Configuration.ConfigurationManager.AppSettings)
{
<li>
@name = @System.Configuration.ConfigurationManager.AppSettings[name]
</li>
}

@foreach (KeyValuePair<string, string> kv in _settingsManager.GetValues())
{

if (kv.Value != null)
{
<li>@kv.Key = @kv.Value</li>
}

}
</ul>

@foreach (KeyValuePair<object, object> kv in appSettingVariablesDict)
{

if (kv.Value != null)
{
<li>@kv.Key = @kv.Value</li>

}

}
</ul>

<h3 id="connectionStrings">Connection Strings</h3>
<ul>
Expand All @@ -89,8 +106,12 @@
</ul>

<h3 id="envVariables">Environment variables</h3>
@{
IDictionary environmentVariables = Environment.GetEnvironmentVariables();
var environmentVariablesDict = Kudu.Core.Helpers.KeyVaultReferenceHelper.KeyVaultReferencesFilter(environmentVariables, isHidden);
}
<ul class="fixed-width">
@foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables().OfType<DictionaryEntry>().OrderBy(e => e.Key))
@foreach (KeyValuePair<object, object> entry in environmentVariablesDict.OfType<KeyValuePair<object, object>>().OrderBy(e => e.Key))
{
<li>@entry.Key = @entry.Value</li>
}
Expand Down Expand Up @@ -122,3 +143,23 @@
</ul>
*@
</div>

<script type="text/javascript">
// Flip whether to hide KeyVault secrets based on current request; Reload page with new parameter
function keyVaultCheckbox() {
var isHidden = '@isHidden' == 'True';
var flipHidden = (!isHidden).toString();
var paramName = "hideSecrets";
// Replace hideSecrets param in URL
var str = location.search;
if (new RegExp("[&?]" + paramName + "([=&].+)?$").test(str)) {
str = str.replace(new RegExp("(?:[&?])" + paramName + "[^&]*", "g"), "")
}
str += "&";
str += paramName + "=" + flipHidden;
str = "?" + str.slice(1);
location.assign(location.origin + location.pathname + str + location.hash);
}
</script>
83 changes: 62 additions & 21 deletions Kudu.Services.Web/Pages/NewUI/Env.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@

<div class="container">
<h3>Index</h3>

@{
bool isHidden = String.IsNullOrEmpty(httpContextAccessor.HttpContext.Request.Query["hideSecrets"]) || ("true").Equals(httpContextAccessor.HttpContext.Request.Query["hideSecrets"]); // Hide by default
}
@if (Kudu.Core.Helpers.KeyVaultReferenceHelper.NumKeyVaultReferences > 0)
{
<div class="checkbox span5">
<label class="checkbox" style="padding:0px">
@Html.CheckBox("hideKeyVaultSecrets", isHidden, new { id = "hideKeyVaultSecrets", style = "padding:0px", onchange = "keyVaultCheckbox()" }) Hide Values of KeyVault References
</label>
</div>
}
<ul>
<li><a href="#sysInfo">System Info</a></li>
<li><a href="#appSettings">App Settings</a></li>
Expand Down Expand Up @@ -50,29 +62,34 @@
</ul>

<h3 id="appSettings">AppSettings</h3>
@{
var appSettingsInEnvironment = new Dictionary<string, string>(_settingsManager.GetValues());
var appSettingVariablesDict = Kudu.Core.Helpers.KeyVaultReferenceHelper.KeyVaultReferencesFilter(appSettingsInEnvironment, isHidden);
}
<ul class="fixed-width">
@using System.Configuration;
@using System.Linq
@using Core.Infrastructure
@using Kudu.Contracts.Settings
@using System.Linq
@using Core.Infrastructure
@using Kudu.Contracts.Settings
@using Microsoft.AspNetCore.Http
@foreach (string name in System.Configuration.ConfigurationManager.AppSettings)
{
<li>
@name = @System.Configuration.ConfigurationManager.AppSettings[name]
</li>
}
@foreach (KeyValuePair<string, string> kv in _settingsManager.GetValues())

@foreach (KeyValuePair<object, object> kv in appSettingVariablesDict)
{

if (kv.Value != null)
{
<li>@kv.Key = @kv.Value</li>
}


if (kv.Value != null)
{
<li>@kv.Key = @kv.Value</li>

}

}
</ul>
</ul>

<h3 id="connectionStrings">Connection Strings</h3>
<ul>
Expand All @@ -89,8 +106,12 @@
</ul>

<h3 id="envVariables">Environment variables</h3>
@{
IDictionary environmentVariables = Environment.GetEnvironmentVariables();
var environmentVariablesDict = Kudu.Core.Helpers.KeyVaultReferenceHelper.KeyVaultReferencesFilter(environmentVariables, isHidden);
}
<ul class="fixed-width">
@foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables().OfType<DictionaryEntry>().OrderBy(e => e.Key))
@foreach (KeyValuePair<object, object> entry in environmentVariablesDict.OfType<KeyValuePair<object, object>>().OrderBy(e => e.Key))
{
<li>@entry.Key = @entry.Value</li>
}
Expand All @@ -113,12 +134,32 @@
</ul>

@*
<h3 id="serverVar">Server variables</h3>
<ul class="fixed-width">
@foreach (string name in HttpContext.Connection.OfType<string>().OrderBy(s => s))
{
<li>@name=@HttpContext.Connection[name]</li>
}
</ul>
*@
<h3 id="serverVar">Server variables</h3>
<ul class="fixed-width">
@foreach (string name in HttpContext.Connection.OfType<string>().OrderBy(s => s))
{
<li>@name=@HttpContext.Connection[name]</li>
}
</ul>
*@
</div>

<script type="text/javascript">
// Flip whether to hide KeyVault secrets based on current request; Reload page with new parameter
function keyVaultCheckbox() {
var isHidden = '@isHidden' == 'True';
var flipHidden = (!isHidden).toString();
var paramName = "hideSecrets";
// Replace hideSecrets param in URL
var str = location.search;
if (new RegExp("[&?]" + paramName + "([=&].+)?$").test(str)) {
str = str.replace(new RegExp("(?:[&?])" + paramName + "[^&]*", "g"), "")
}
str += "&";
str += paramName + "=" + flipHidden;
str = "?" + str.slice(1);
location.assign(location.origin + location.pathname + str + location.hash);
}
</script>

0 comments on commit a906dbe

Please sign in to comment.