Docker: add warning when FileSystemXmlRepository saves a key to non-volume mounted folder

This commit is contained in:
Nate McMaster 2017-06-05 16:31:59 -07:00
parent ddd041b0f1
commit abf05e2856
13 changed files with 307 additions and 179 deletions

View File

@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\dependencies.props" />
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<OutputType>exe</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection.Extensions\Microsoft.AspNetCore.DataProtection.Extensions.csproj" />
</ItemGroup>

View File

@ -1,22 +0,0 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:1394/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"NonDISample": {
"commandName": "Project"
}
}
}

View File

@ -15,64 +15,56 @@ namespace Microsoft.AspNetCore.Cryptography.Internal
/// </summary>
internal static string BCryptAlgorithmHandle_ProviderNotFound
{
get { return GetString("BCryptAlgorithmHandle_ProviderNotFound"); }
get => GetString("BCryptAlgorithmHandle_ProviderNotFound");
}
/// <summary>
/// A provider could not be found for algorithm '{0}'.
/// </summary>
internal static string FormatBCryptAlgorithmHandle_ProviderNotFound(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("BCryptAlgorithmHandle_ProviderNotFound"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("BCryptAlgorithmHandle_ProviderNotFound"), p0);
/// <summary>
/// The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).
/// </summary>
internal static string BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength
{
get { return GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength"); }
get => GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength");
}
/// <summary>
/// The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).
/// </summary>
internal static string FormatBCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength(object p0, object p1, object p2, object p3)
{
return string.Format(CultureInfo.CurrentCulture, GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength"), p0, p1, p2, p3);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength"), p0, p1, p2, p3);
/// <summary>
/// This operation requires Windows 7 / Windows Server 2008 R2 or later.
/// </summary>
internal static string Platform_Windows7Required
{
get { return GetString("Platform_Windows7Required"); }
get => GetString("Platform_Windows7Required");
}
/// <summary>
/// This operation requires Windows 7 / Windows Server 2008 R2 or later.
/// </summary>
internal static string FormatPlatform_Windows7Required()
{
return GetString("Platform_Windows7Required");
}
=> GetString("Platform_Windows7Required");
/// <summary>
/// This operation requires Windows 8 / Windows Server 2012 or later.
/// </summary>
internal static string Platform_Windows8Required
{
get { return GetString("Platform_Windows8Required"); }
get => GetString("Platform_Windows8Required");
}
/// <summary>
/// This operation requires Windows 8 / Windows Server 2012 or later.
/// </summary>
internal static string FormatPlatform_Windows8Required()
{
return GetString("Platform_Windows8Required");
}
=> GetString("Platform_Windows8Required");
private static string GetString(string name, params string[] formatterNames)
{

View File

@ -15,64 +15,56 @@ namespace Microsoft.AspNetCore.DataProtection.Abstractions
/// </summary>
internal static string CryptCommon_PayloadInvalid
{
get { return GetString("CryptCommon_PayloadInvalid"); }
get => GetString("CryptCommon_PayloadInvalid");
}
/// <summary>
/// The payload was invalid.
/// </summary>
internal static string FormatCryptCommon_PayloadInvalid()
{
return GetString("CryptCommon_PayloadInvalid");
}
=> GetString("CryptCommon_PayloadInvalid");
/// <summary>
/// The purposes collection cannot be null or empty and cannot contain null elements.
/// </summary>
internal static string DataProtectionExtensions_NullPurposesCollection
{
get { return GetString("DataProtectionExtensions_NullPurposesCollection"); }
get => GetString("DataProtectionExtensions_NullPurposesCollection");
}
/// <summary>
/// The purposes collection cannot be null or empty and cannot contain null elements.
/// </summary>
internal static string FormatDataProtectionExtensions_NullPurposesCollection()
{
return GetString("DataProtectionExtensions_NullPurposesCollection");
}
=> GetString("DataProtectionExtensions_NullPurposesCollection");
/// <summary>
/// An error occurred during a cryptographic operation.
/// </summary>
internal static string CryptCommon_GenericError
{
get { return GetString("CryptCommon_GenericError"); }
get => GetString("CryptCommon_GenericError");
}
/// <summary>
/// An error occurred during a cryptographic operation.
/// </summary>
internal static string FormatCryptCommon_GenericError()
{
return GetString("CryptCommon_GenericError");
}
=> GetString("CryptCommon_GenericError");
/// <summary>
/// No service for type '{0}' has been registered.
/// </summary>
internal static string DataProtectionExtensions_NoService
{
get { return GetString("DataProtectionExtensions_NoService"); }
get => GetString("DataProtectionExtensions_NoService");
}
/// <summary>
/// No service for type '{0}' has been registered.
/// </summary>
internal static string FormatDataProtectionExtensions_NoService(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("DataProtectionExtensions_NoService"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("DataProtectionExtensions_NoService"), p0);
private static string GetString(string name, params string[] formatterNames)
{

View File

@ -15,48 +15,42 @@ namespace Microsoft.AspNetCore.DataProtection.Extensions
/// </summary>
internal static string CryptCommon_GenericError
{
get { return GetString("CryptCommon_GenericError"); }
get => GetString("CryptCommon_GenericError");
}
/// <summary>
/// An error occurred during a cryptographic operation.
/// </summary>
internal static string FormatCryptCommon_GenericError()
{
return GetString("CryptCommon_GenericError");
}
=> GetString("CryptCommon_GenericError");
/// <summary>
/// The payload expired at {0}.
/// </summary>
internal static string TimeLimitedDataProtector_PayloadExpired
{
get { return GetString("TimeLimitedDataProtector_PayloadExpired"); }
get => GetString("TimeLimitedDataProtector_PayloadExpired");
}
/// <summary>
/// The payload expired at {0}.
/// </summary>
internal static string FormatTimeLimitedDataProtector_PayloadExpired(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TimeLimitedDataProtector_PayloadExpired"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("TimeLimitedDataProtector_PayloadExpired"), p0);
/// <summary>
/// The payload is invalid.
/// </summary>
internal static string TimeLimitedDataProtector_PayloadInvalid
{
get { return GetString("TimeLimitedDataProtector_PayloadInvalid"); }
get => GetString("TimeLimitedDataProtector_PayloadInvalid");
}
/// <summary>
/// The payload is invalid.
/// </summary>
internal static string FormatTimeLimitedDataProtector_PayloadInvalid()
{
return GetString("TimeLimitedDataProtector_PayloadInvalid");
}
=> GetString("TimeLimitedDataProtector_PayloadInvalid");
private static string GetString(string name, params string[] formatterNames)
{

View File

@ -15,32 +15,28 @@ namespace Microsoft.AspNetCore.DataProtection.SystemWeb
/// </summary>
internal static string DataProtector_ProtectFailed
{
get { return GetString("DataProtector_ProtectFailed"); }
get => GetString("DataProtector_ProtectFailed");
}
/// <summary>
/// A call to Protect failed. This most likely means that the data protection system is misconfigured. See the inner exception for more information.
/// </summary>
internal static string FormatDataProtector_ProtectFailed()
{
return GetString("DataProtector_ProtectFailed");
}
=> GetString("DataProtector_ProtectFailed");
/// <summary>
/// The CreateDataProtectionProvider method returned null.
/// </summary>
internal static string Startup_CreateProviderReturnedNull
{
get { return GetString("Startup_CreateProviderReturnedNull"); }
get => GetString("Startup_CreateProviderReturnedNull");
}
/// <summary>
/// The CreateDataProtectionProvider method returned null.
/// </summary>
internal static string FormatStartup_CreateProviderReturnedNull()
{
return GetString("Startup_CreateProviderReturnedNull");
}
=> GetString("Startup_CreateProviderReturnedNull");
private static string GetString(string name, params string[] formatterNames)
{

View File

@ -0,0 +1,99 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.DataProtection.Internal
{
internal static class DockerUtils
{
private static Lazy<bool> _isDocker = new Lazy<bool>(IsProcessRunningInDocker);
public static bool IsDocker => _isDocker.Value;
public static bool IsVolumeMountedFolder(DirectoryInfo directory)
{
if (!IsDocker)
{
return false;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// we currently don't have a good way to detect mounted file systems within Windows ctonainers
return false;
}
const string mountsFile = "/proc/self/mounts";
if (!File.Exists(mountsFile))
{
return false;
}
var lines = File.ReadAllLines(mountsFile);
return IsDirectoryMounted(directory, lines);
}
// internal for testing. Don't use directly
internal static bool IsDirectoryMounted(DirectoryInfo directory, IEnumerable<string> fstab)
{
// Expected file format: http://man7.org/linux/man-pages/man5/fstab.5.html
foreach (var line in fstab)
{
if (line == null || line.Length == 0 || line[0] == '#')
{
// skip empty and commented-out lines
continue;
}
var fields = line.Split(new[] { '\t', ' ' });
if (fields.Length < 2 // line had too few fields
|| fields[1].Length <= 1 // fs_file empty or is the root directory '/'
|| fields[1][0] != '/') // fs_file was not a file path
{
continue;
}
// check if directory is a subdirectory of this location
var fs_file = new DirectoryInfo(fields[1].TrimEnd(Path.DirectorySeparatorChar)).FullName;
var dir = directory;
while (dir != null)
{
// filesystems on Linux are case sensitive
if (fs_file.Equals(dir.FullName.TrimEnd(Path.DirectorySeparatorChar), StringComparison.Ordinal))
{
return true;
}
dir = dir.Parent;
}
}
return false;
}
private static bool IsProcessRunningInDocker()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// we currently don't have a good way to detect if running in a Windows container
return false;
}
const string procFile = "/proc/1/cgroup";
if (!File.Exists(procFile))
{
return false;
}
var lines = File.ReadAllLines(procFile);
// typically the last line in the file is "1:name=openrc:/docker"
return lines.Reverse().Any(l => l.EndsWith("name=openrc:/docker", StringComparison.Ordinal));
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Win32;
namespace Microsoft.Extensions.Logging
@ -143,6 +144,8 @@ namespace Microsoft.Extensions.Logging
private static Action<ILogger, string, Exception> _usingAzureAsKeyRepository;
private static Action<ILogger, string, Exception> _usingEphemeralFileSystemLocationInContainer;
static LoggingExtensions()
{
_usingFallbackKeyWithExpirationAsDefaultKey = LoggerMessage.Define<Guid, DateTimeOffset>(
@ -377,19 +380,29 @@ namespace Microsoft.Extensions.Logging
eventId: 58,
logLevel: LogLevel.Information,
formatString: "Creating key {KeyId:B} with creation date {CreationDate:u}, activation date {ActivationDate:u}, and expiration date {ExpirationDate:u}.");
_usingEphemeralKeyRepository = LoggerMessage.Define(eventId: 59,
_usingEphemeralKeyRepository = LoggerMessage.Define(
eventId: 59,
logLevel: LogLevel.Warning,
formatString: "Neither user profile nor HKLM registry available. Using an ephemeral key repository. Protected data will be unavailable when application exits.");
_usingRegistryAsKeyRepositoryWithDPAPI = LoggerMessage.Define<string>(eventId: 0,
_usingEphemeralFileSystemLocationInContainer = LoggerMessage.Define<string>(
eventId: 60,
logLevel: LogLevel.Warning,
formatString: Resources.FileSystem_EphemeralKeysLocationInContainer);
_usingRegistryAsKeyRepositoryWithDPAPI = LoggerMessage.Define<string>(
eventId: 0,
logLevel: LogLevel.Information,
formatString: "User profile not available. Using '{Name}' as key repository and Windows DPAPI to encrypt keys at rest.");
_usingProfileAsKeyRepository = LoggerMessage.Define<string>(eventId: 0,
_usingProfileAsKeyRepository = LoggerMessage.Define<string>(
eventId: 0,
logLevel: LogLevel.Information,
formatString: "User profile is available. Using '{FullName}' as key repository; keys will not be encrypted at rest.");
_usingProfileAsKeyRepositoryWithDPAPI = LoggerMessage.Define<string>(eventId: 0,
_usingProfileAsKeyRepositoryWithDPAPI = LoggerMessage.Define<string>(
eventId: 0,
logLevel: LogLevel.Information,
formatString: "User profile is available. Using '{FullName}' as key repository and Windows DPAPI to encrypt keys at rest.");
_usingAzureAsKeyRepository = LoggerMessage.Define<string>(eventId: 0,
_usingAzureAsKeyRepository = LoggerMessage.Define<string>(
eventId: 0,
logLevel: LogLevel.Information,
formatString: "Azure Web Sites environment detected. Using '{FullName}' as key repository; keys will not be encrypted at rest.");
_keyRingWasLoadedOnStartup = LoggerMessage.Define<Guid>(
@ -782,5 +795,10 @@ namespace Microsoft.Extensions.Logging
{
_keyRingFailedToLoadOnStartup(logger, innerException);
}
public static void UsingEphemeralFileSystemLocationInContainer(this ILogger logger, string path)
{
_usingEphemeralFileSystemLocationInContainer(logger, path, null);
}
}
}

View File

@ -15,401 +15,365 @@ namespace Microsoft.AspNetCore.DataProtection
/// </summary>
internal static string CryptCommon_GenericError
{
get { return GetString("CryptCommon_GenericError"); }
get => GetString("CryptCommon_GenericError");
}
/// <summary>
/// An error occurred during a cryptographic operation.
/// </summary>
internal static string FormatCryptCommon_GenericError()
{
return GetString("CryptCommon_GenericError");
}
=> GetString("CryptCommon_GenericError");
/// <summary>
/// The provided buffer is of length {0} byte(s). It must instead be exactly {1} byte(s) in length.
/// </summary>
internal static string Common_BufferIncorrectlySized
{
get { return GetString("Common_BufferIncorrectlySized"); }
get => GetString("Common_BufferIncorrectlySized");
}
/// <summary>
/// The provided buffer is of length {0} byte(s). It must instead be exactly {1} byte(s) in length.
/// </summary>
internal static string FormatCommon_BufferIncorrectlySized(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Common_BufferIncorrectlySized"), p0, p1);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("Common_BufferIncorrectlySized"), p0, p1);
/// <summary>
/// The payload was invalid.
/// </summary>
internal static string CryptCommon_PayloadInvalid
{
get { return GetString("CryptCommon_PayloadInvalid"); }
get => GetString("CryptCommon_PayloadInvalid");
}
/// <summary>
/// The payload was invalid.
/// </summary>
internal static string FormatCryptCommon_PayloadInvalid()
{
return GetString("CryptCommon_PayloadInvalid");
}
=> GetString("CryptCommon_PayloadInvalid");
/// <summary>
/// Property {0} cannot be null or empty.
/// </summary>
internal static string Common_PropertyCannotBeNullOrEmpty
{
get { return GetString("Common_PropertyCannotBeNullOrEmpty"); }
get => GetString("Common_PropertyCannotBeNullOrEmpty");
}
/// <summary>
/// Property {0} cannot be null or empty.
/// </summary>
internal static string FormatCommon_PropertyCannotBeNullOrEmpty(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyCannotBeNullOrEmpty"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyCannotBeNullOrEmpty"), p0);
/// <summary>
/// The provided payload could not be decrypted. Refer to the inner exception for more information.
/// </summary>
internal static string Common_DecryptionFailed
{
get { return GetString("Common_DecryptionFailed"); }
get => GetString("Common_DecryptionFailed");
}
/// <summary>
/// The provided payload could not be decrypted. Refer to the inner exception for more information.
/// </summary>
internal static string FormatCommon_DecryptionFailed()
{
return GetString("Common_DecryptionFailed");
}
=> GetString("Common_DecryptionFailed");
/// <summary>
/// An error occurred while trying to encrypt the provided data. Refer to the inner exception for more information.
/// </summary>
internal static string Common_EncryptionFailed
{
get { return GetString("Common_EncryptionFailed"); }
get => GetString("Common_EncryptionFailed");
}
/// <summary>
/// An error occurred while trying to encrypt the provided data. Refer to the inner exception for more information.
/// </summary>
internal static string FormatCommon_EncryptionFailed()
{
return GetString("Common_EncryptionFailed");
}
=> GetString("Common_EncryptionFailed");
/// <summary>
/// The key {0:B} was not found in the key ring.
/// </summary>
internal static string Common_KeyNotFound
{
get { return GetString("Common_KeyNotFound"); }
get => GetString("Common_KeyNotFound");
}
/// <summary>
/// The key {0:B} was not found in the key ring.
/// </summary>
internal static string FormatCommon_KeyNotFound()
{
return GetString("Common_KeyNotFound");
}
=> GetString("Common_KeyNotFound");
/// <summary>
/// The key {0:B} has been revoked.
/// </summary>
internal static string Common_KeyRevoked
{
get { return GetString("Common_KeyRevoked"); }
get => GetString("Common_KeyRevoked");
}
/// <summary>
/// The key {0:B} has been revoked.
/// </summary>
internal static string FormatCommon_KeyRevoked()
{
return GetString("Common_KeyRevoked");
}
=> GetString("Common_KeyRevoked");
/// <summary>
/// The provided payload cannot be decrypted because it was not protected with this protection provider.
/// </summary>
internal static string ProtectionProvider_BadMagicHeader
{
get { return GetString("ProtectionProvider_BadMagicHeader"); }
get => GetString("ProtectionProvider_BadMagicHeader");
}
/// <summary>
/// The provided payload cannot be decrypted because it was not protected with this protection provider.
/// </summary>
internal static string FormatProtectionProvider_BadMagicHeader()
{
return GetString("ProtectionProvider_BadMagicHeader");
}
=> GetString("ProtectionProvider_BadMagicHeader");
/// <summary>
/// The provided payload cannot be decrypted because it was protected with a newer version of the protection provider.
/// </summary>
internal static string ProtectionProvider_BadVersion
{
get { return GetString("ProtectionProvider_BadVersion"); }
get => GetString("ProtectionProvider_BadVersion");
}
/// <summary>
/// The provided payload cannot be decrypted because it was protected with a newer version of the protection provider.
/// </summary>
internal static string FormatProtectionProvider_BadVersion()
{
return GetString("ProtectionProvider_BadVersion");
}
=> GetString("ProtectionProvider_BadVersion");
/// <summary>
/// Value must be non-negative.
/// </summary>
internal static string Common_ValueMustBeNonNegative
{
get { return GetString("Common_ValueMustBeNonNegative"); }
get => GetString("Common_ValueMustBeNonNegative");
}
/// <summary>
/// Value must be non-negative.
/// </summary>
internal static string FormatCommon_ValueMustBeNonNegative()
{
return GetString("Common_ValueMustBeNonNegative");
}
=> GetString("Common_ValueMustBeNonNegative");
/// <summary>
/// The type '{1}' is not assignable to '{0}'.
/// </summary>
internal static string TypeExtensions_BadCast
{
get { return GetString("TypeExtensions_BadCast"); }
get => GetString("TypeExtensions_BadCast");
}
/// <summary>
/// The type '{1}' is not assignable to '{0}'.
/// </summary>
internal static string FormatTypeExtensions_BadCast(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TypeExtensions_BadCast"), p0, p1);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("TypeExtensions_BadCast"), p0, p1);
/// <summary>
/// The new key lifetime must be at least one week.
/// </summary>
internal static string KeyManagementOptions_MinNewKeyLifetimeViolated
{
get { return GetString("KeyManagementOptions_MinNewKeyLifetimeViolated"); }
get => GetString("KeyManagementOptions_MinNewKeyLifetimeViolated");
}
/// <summary>
/// The new key lifetime must be at least one week.
/// </summary>
internal static string FormatKeyManagementOptions_MinNewKeyLifetimeViolated()
{
return GetString("KeyManagementOptions_MinNewKeyLifetimeViolated");
}
=> GetString("KeyManagementOptions_MinNewKeyLifetimeViolated");
/// <summary>
/// The key {0:B} already exists in the keyring.
/// </summary>
internal static string XmlKeyManager_DuplicateKey
{
get { return GetString("XmlKeyManager_DuplicateKey"); }
get => GetString("XmlKeyManager_DuplicateKey");
}
/// <summary>
/// The key {0:B} already exists in the keyring.
/// </summary>
internal static string FormatXmlKeyManager_DuplicateKey()
{
return GetString("XmlKeyManager_DuplicateKey");
}
=> GetString("XmlKeyManager_DuplicateKey");
/// <summary>
/// Argument cannot be null or empty.
/// </summary>
internal static string Common_ArgumentCannotBeNullOrEmpty
{
get { return GetString("Common_ArgumentCannotBeNullOrEmpty"); }
get => GetString("Common_ArgumentCannotBeNullOrEmpty");
}
/// <summary>
/// Argument cannot be null or empty.
/// </summary>
internal static string FormatCommon_ArgumentCannotBeNullOrEmpty()
{
return GetString("Common_ArgumentCannotBeNullOrEmpty");
}
=> GetString("Common_ArgumentCannotBeNullOrEmpty");
/// <summary>
/// Property {0} must have a non-negative value.
/// </summary>
internal static string Common_PropertyMustBeNonNegative
{
get { return GetString("Common_PropertyMustBeNonNegative"); }
get => GetString("Common_PropertyMustBeNonNegative");
}
/// <summary>
/// Property {0} must have a non-negative value.
/// </summary>
internal static string FormatCommon_PropertyMustBeNonNegative(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyMustBeNonNegative"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyMustBeNonNegative"), p0);
/// <summary>
/// GCM algorithms require the Windows platform.
/// </summary>
internal static string Platform_WindowsRequiredForGcm
{
get { return GetString("Platform_WindowsRequiredForGcm"); }
get => GetString("Platform_WindowsRequiredForGcm");
}
/// <summary>
/// GCM algorithms require the Windows platform.
/// </summary>
internal static string FormatPlatform_WindowsRequiredForGcm()
{
return GetString("Platform_WindowsRequiredForGcm");
}
=> GetString("Platform_WindowsRequiredForGcm");
/// <summary>
/// A certificate with the thumbprint '{0}' could not be found.
/// </summary>
internal static string CertificateXmlEncryptor_CertificateNotFound
{
get { return GetString("CertificateXmlEncryptor_CertificateNotFound"); }
get => GetString("CertificateXmlEncryptor_CertificateNotFound");
}
/// <summary>
/// A certificate with the thumbprint '{0}' could not be found.
/// </summary>
internal static string FormatCertificateXmlEncryptor_CertificateNotFound(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("CertificateXmlEncryptor_CertificateNotFound"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("CertificateXmlEncryptor_CertificateNotFound"), p0);
/// <summary>
/// Decrypting EncryptedXml-encapsulated payloads is not yet supported on Core CLR.
/// </summary>
internal static string EncryptedXmlDecryptor_DoesNotWorkOnCoreClr
{
get { return GetString("EncryptedXmlDecryptor_DoesNotWorkOnCoreClr"); }
get => GetString("EncryptedXmlDecryptor_DoesNotWorkOnCoreClr");
}
/// <summary>
/// Decrypting EncryptedXml-encapsulated payloads is not yet supported on Core CLR.
/// </summary>
internal static string FormatEncryptedXmlDecryptor_DoesNotWorkOnCoreClr()
{
return GetString("EncryptedXmlDecryptor_DoesNotWorkOnCoreClr");
}
=> GetString("EncryptedXmlDecryptor_DoesNotWorkOnCoreClr");
/// <summary>
/// The symmetric algorithm block size of {0} bits is invalid. The block size must be between 64 and 2048 bits, inclusive, and it must be a multiple of 8 bits.
/// </summary>
internal static string AlgorithmAssert_BadBlockSize
{
get { return GetString("AlgorithmAssert_BadBlockSize"); }
get => GetString("AlgorithmAssert_BadBlockSize");
}
/// <summary>
/// The symmetric algorithm block size of {0} bits is invalid. The block size must be between 64 and 2048 bits, inclusive, and it must be a multiple of 8 bits.
/// </summary>
internal static string FormatAlgorithmAssert_BadBlockSize(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadBlockSize"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadBlockSize"), p0);
/// <summary>
/// The validation algorithm digest size of {0} bits is invalid. The digest size must be between 128 and 2048 bits, inclusive, and it must be a multiple of 8 bits.
/// </summary>
internal static string AlgorithmAssert_BadDigestSize
{
get { return GetString("AlgorithmAssert_BadDigestSize"); }
get => GetString("AlgorithmAssert_BadDigestSize");
}
/// <summary>
/// The validation algorithm digest size of {0} bits is invalid. The digest size must be between 128 and 2048 bits, inclusive, and it must be a multiple of 8 bits.
/// </summary>
internal static string FormatAlgorithmAssert_BadDigestSize(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadDigestSize"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadDigestSize"), p0);
/// <summary>
/// The symmetric algorithm key size of {0} bits is invalid. The key size must be between 128 and 2048 bits, inclusive, and it must be a multiple of 8 bits.
/// </summary>
internal static string AlgorithmAssert_BadKeySize
{
get { return GetString("AlgorithmAssert_BadKeySize"); }
get => GetString("AlgorithmAssert_BadKeySize");
}
/// <summary>
/// The symmetric algorithm key size of {0} bits is invalid. The key size must be between 128 and 2048 bits, inclusive, and it must be a multiple of 8 bits.
/// </summary>
internal static string FormatAlgorithmAssert_BadKeySize(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadKeySize"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadKeySize"), p0);
/// <summary>
/// The key ring does not contain a valid default protection key. The data protection system cannot create a new key because auto-generation of keys is disabled.
/// </summary>
internal static string KeyRingProvider_NoDefaultKey_AutoGenerateDisabled
{
get { return GetString("KeyRingProvider_NoDefaultKey_AutoGenerateDisabled"); }
get => GetString("KeyRingProvider_NoDefaultKey_AutoGenerateDisabled");
}
/// <summary>
/// The key ring does not contain a valid default protection key. The data protection system cannot create a new key because auto-generation of keys is disabled.
/// </summary>
internal static string FormatKeyRingProvider_NoDefaultKey_AutoGenerateDisabled()
{
return GetString("KeyRingProvider_NoDefaultKey_AutoGenerateDisabled");
}
=> GetString("KeyRingProvider_NoDefaultKey_AutoGenerateDisabled");
/// <summary>
/// {0} must not be negative
/// </summary>
internal static string LifetimeMustNotBeNegative
{
get { return GetString("LifetimeMustNotBeNegative"); }
get => GetString("LifetimeMustNotBeNegative");
}
/// <summary>
/// {0} must not be negative
/// </summary>
internal static string FormatLifetimeMustNotBeNegative(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("LifetimeMustNotBeNegative"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("LifetimeMustNotBeNegative"), p0);
/// <summary>
/// The '{0}' instance could not be found. When an '{1}' instance is set, a corresponding '{0}' instance must also be set.
/// </summary>
internal static string XmlKeyManager_IXmlRepositoryNotFound
{
get { return GetString("XmlKeyManager_IXmlRepositoryNotFound"); }
get => GetString("XmlKeyManager_IXmlRepositoryNotFound");
}
/// <summary>
/// The '{0}' instance could not be found. When an '{1}' instance is set, a corresponding '{0}' instance must also be set.
/// </summary>
internal static string FormatXmlKeyManager_IXmlRepositoryNotFound(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("XmlKeyManager_IXmlRepositoryNotFound"), p0, p1);
/// <summary>
/// Storing keys in a directory '{path}' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
/// </summary>
internal static string FileSystem_EphemeralKeysLocationInContainer
{
return string.Format(CultureInfo.CurrentCulture, GetString("XmlKeyManager_IXmlRepositoryNotFound"), p0, p1);
get => GetString("FileSystem_EphemeralKeysLocationInContainer");
}
/// <summary>
/// Storing keys in a directory '{path}' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
/// </summary>
internal static string FormatFileSystem_EphemeralKeysLocationInContainer(object path)
=> string.Format(CultureInfo.CurrentCulture, GetString("FileSystem_EphemeralKeysLocationInContainer", "path"), path);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -8,6 +8,7 @@ using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using Microsoft.AspNetCore.DataProtection.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.DataProtection.Repositories
@ -35,6 +36,22 @@ namespace Microsoft.AspNetCore.DataProtection.Repositories
Directory = directory;
_logger = loggerFactory.CreateLogger<FileSystemXmlRepository>();
try
{
if (DockerUtils.IsDocker && !DockerUtils.IsVolumeMountedFolder(Directory))
{
// warn users that keys may be lost when running in docker without a volume mounted folder
_logger.UsingEphemeralFileSystemLocationInContainer(Directory.FullName);
}
}
catch (Exception ex)
{
// Treat exceptions as non-fatal when attempting to detect docker.
// These might occur if fstab is an unrecognized format, or if there are other unusual
// file IO errors.
_logger.LogTrace(ex, "Failure occurred while attempting to detect docker.");
}
}
/// <summary>

View File

@ -192,4 +192,7 @@
<data name="XmlKeyManager_IXmlRepositoryNotFound" xml:space="preserve">
<value>The '{0}' instance could not be found. When an '{1}' instance is set, a corresponding '{0}' instance must also be set.</value>
</data>
<data name="FileSystem_EphemeralKeysLocationInContainer" xml:space="preserve">
<value>Storing keys in a directory '{path}' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.</value>
</data>
</root>

View File

@ -0,0 +1,56 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Microsoft.AspNetCore.DataProtection.Internal;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.DataProtection.Test
{
public class DockerUtilsTests
{
// example of content from /proc/self/mounts
private static readonly string[] fstab = new []
{
"none / aufs rw,relatime,si=f9bfcf896de3f6c2,dio,dirperm1 0 0",
"# comments",
"",
"proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0",
"tmpfs /dev tmpfs rw,nosuid,mode=755 0 0",
"devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0",
"/dev/vda2 /etc/resolv.conf ext4 rw,relatime,data=ordered 0 0",
"/dev/vda2 /etc/hostname ext4 rw,relatime,data=ordered 0 0",
"/dev/vda2 /etc/hosts ext4 rw,relatime,data=ordered 0 0",
"shm /dev/shm tmpfs rw,nosuid,nodev,noexec,relatime,size=65536k 0 0",
// the mounted directory
"osxfs /app fuse.osxfs rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other,max_read=1048576 0 0",
};
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.Windows)]
[InlineData("/")]
[InlineData("/home")]
[InlineData("/home/")]
[InlineData("/home/root")]
[InlineData("./dir")]
[InlineData("../dir")]
public void DeterminesFolderIsNotMounted(string directory)
{
Assert.False(DockerUtils.IsDirectoryMounted(new DirectoryInfo(directory), fstab));
}
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.Windows)]
[InlineData("/app")]
[InlineData("/app/")]
[InlineData("/app/subdir")]
[InlineData("/app/subdir/")]
[InlineData("/app/subdir/two")]
[InlineData("/app/subdir/two/")]
public void DeterminesFolderIsMounted(string directory)
{
Assert.True(DockerUtils.IsDirectoryMounted(new DirectoryInfo(directory), fstab));
}
}
}

View File

@ -6,6 +6,8 @@ using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
@ -139,6 +141,23 @@ namespace Microsoft.AspNetCore.DataProtection.Repositories
});
}
[ConditionalFact]
[DockerOnly]
[Trait("Docker", "true")]
public void Logs_DockerEphemeralFolders()
{
// Arrange
var loggerFactory = new StringLoggerFactory(LogLevel.Warning);
WithUniqueTempDirectory(dirInfo =>
{
// Act
var repo = new FileSystemXmlRepository(dirInfo, loggerFactory);
// Assert
Assert.Contains(Resources.FormatFileSystem_EphemeralKeysLocationInContainer(dirInfo.FullName), loggerFactory.ToString());
});
}
/// <summary>
/// Runs a test and cleans up the temp directory afterward.
/// </summary>