diff --git a/samples/NonDISample/NonDISample.csproj b/samples/NonDISample/NonDISample.csproj index b15e9017a6..118eed8588 100644 --- a/samples/NonDISample/NonDISample.csproj +++ b/samples/NonDISample/NonDISample.csproj @@ -1,13 +1,13 @@ - + net461;netcoreapp2.0 + exe - diff --git a/samples/NonDISample/Properties/launchSettings.json b/samples/NonDISample/Properties/launchSettings.json deleted file mode 100644 index 7d36272608..0000000000 --- a/samples/NonDISample/Properties/launchSettings.json +++ /dev/null @@ -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" - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs index 8d910ded82..df010bc683 100644 --- a/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs @@ -15,64 +15,56 @@ namespace Microsoft.AspNetCore.Cryptography.Internal /// internal static string BCryptAlgorithmHandle_ProviderNotFound { - get { return GetString("BCryptAlgorithmHandle_ProviderNotFound"); } + get => GetString("BCryptAlgorithmHandle_ProviderNotFound"); } /// /// A provider could not be found for algorithm '{0}'. /// internal static string FormatBCryptAlgorithmHandle_ProviderNotFound(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("BCryptAlgorithmHandle_ProviderNotFound"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("BCryptAlgorithmHandle_ProviderNotFound"), p0); /// /// The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}). /// internal static string BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength { - get { return GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength"); } + get => GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength"); } /// /// The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}). /// 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); /// /// This operation requires Windows 7 / Windows Server 2008 R2 or later. /// internal static string Platform_Windows7Required { - get { return GetString("Platform_Windows7Required"); } + get => GetString("Platform_Windows7Required"); } /// /// This operation requires Windows 7 / Windows Server 2008 R2 or later. /// internal static string FormatPlatform_Windows7Required() - { - return GetString("Platform_Windows7Required"); - } + => GetString("Platform_Windows7Required"); /// /// This operation requires Windows 8 / Windows Server 2012 or later. /// internal static string Platform_Windows8Required { - get { return GetString("Platform_Windows8Required"); } + get => GetString("Platform_Windows8Required"); } /// /// This operation requires Windows 8 / Windows Server 2012 or later. /// internal static string FormatPlatform_Windows8Required() - { - return GetString("Platform_Windows8Required"); - } + => GetString("Platform_Windows8Required"); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs index b8bfe383ee..7f8422cf6b 100644 --- a/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs @@ -15,64 +15,56 @@ namespace Microsoft.AspNetCore.DataProtection.Abstractions /// internal static string CryptCommon_PayloadInvalid { - get { return GetString("CryptCommon_PayloadInvalid"); } + get => GetString("CryptCommon_PayloadInvalid"); } /// /// The payload was invalid. /// internal static string FormatCryptCommon_PayloadInvalid() - { - return GetString("CryptCommon_PayloadInvalid"); - } + => GetString("CryptCommon_PayloadInvalid"); /// /// The purposes collection cannot be null or empty and cannot contain null elements. /// internal static string DataProtectionExtensions_NullPurposesCollection { - get { return GetString("DataProtectionExtensions_NullPurposesCollection"); } + get => GetString("DataProtectionExtensions_NullPurposesCollection"); } /// /// The purposes collection cannot be null or empty and cannot contain null elements. /// internal static string FormatDataProtectionExtensions_NullPurposesCollection() - { - return GetString("DataProtectionExtensions_NullPurposesCollection"); - } + => GetString("DataProtectionExtensions_NullPurposesCollection"); /// /// An error occurred during a cryptographic operation. /// internal static string CryptCommon_GenericError { - get { return GetString("CryptCommon_GenericError"); } + get => GetString("CryptCommon_GenericError"); } /// /// An error occurred during a cryptographic operation. /// internal static string FormatCryptCommon_GenericError() - { - return GetString("CryptCommon_GenericError"); - } + => GetString("CryptCommon_GenericError"); /// /// No service for type '{0}' has been registered. /// internal static string DataProtectionExtensions_NoService { - get { return GetString("DataProtectionExtensions_NoService"); } + get => GetString("DataProtectionExtensions_NoService"); } /// /// No service for type '{0}' has been registered. /// 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) { diff --git a/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs index 12a8e114f3..8fba5cd9f2 100644 --- a/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs @@ -15,48 +15,42 @@ namespace Microsoft.AspNetCore.DataProtection.Extensions /// internal static string CryptCommon_GenericError { - get { return GetString("CryptCommon_GenericError"); } + get => GetString("CryptCommon_GenericError"); } /// /// An error occurred during a cryptographic operation. /// internal static string FormatCryptCommon_GenericError() - { - return GetString("CryptCommon_GenericError"); - } + => GetString("CryptCommon_GenericError"); /// /// The payload expired at {0}. /// internal static string TimeLimitedDataProtector_PayloadExpired { - get { return GetString("TimeLimitedDataProtector_PayloadExpired"); } + get => GetString("TimeLimitedDataProtector_PayloadExpired"); } /// /// The payload expired at {0}. /// internal static string FormatTimeLimitedDataProtector_PayloadExpired(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TimeLimitedDataProtector_PayloadExpired"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TimeLimitedDataProtector_PayloadExpired"), p0); /// /// The payload is invalid. /// internal static string TimeLimitedDataProtector_PayloadInvalid { - get { return GetString("TimeLimitedDataProtector_PayloadInvalid"); } + get => GetString("TimeLimitedDataProtector_PayloadInvalid"); } /// /// The payload is invalid. /// internal static string FormatTimeLimitedDataProtector_PayloadInvalid() - { - return GetString("TimeLimitedDataProtector_PayloadInvalid"); - } + => GetString("TimeLimitedDataProtector_PayloadInvalid"); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Microsoft.AspNetCore.DataProtection.SystemWeb/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.DataProtection.SystemWeb/Properties/Resources.Designer.cs index 802e2366d7..ddc7e53910 100644 --- a/src/Microsoft.AspNetCore.DataProtection.SystemWeb/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.DataProtection.SystemWeb/Properties/Resources.Designer.cs @@ -15,32 +15,28 @@ namespace Microsoft.AspNetCore.DataProtection.SystemWeb /// internal static string DataProtector_ProtectFailed { - get { return GetString("DataProtector_ProtectFailed"); } + get => GetString("DataProtector_ProtectFailed"); } /// /// A call to Protect failed. This most likely means that the data protection system is misconfigured. See the inner exception for more information. /// internal static string FormatDataProtector_ProtectFailed() - { - return GetString("DataProtector_ProtectFailed"); - } + => GetString("DataProtector_ProtectFailed"); /// /// The CreateDataProtectionProvider method returned null. /// internal static string Startup_CreateProviderReturnedNull { - get { return GetString("Startup_CreateProviderReturnedNull"); } + get => GetString("Startup_CreateProviderReturnedNull"); } /// /// The CreateDataProtectionProvider method returned null. /// internal static string FormatStartup_CreateProviderReturnedNull() - { - return GetString("Startup_CreateProviderReturnedNull"); - } + => GetString("Startup_CreateProviderReturnedNull"); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Microsoft.AspNetCore.DataProtection/Internal/DockerUtils.cs b/src/Microsoft.AspNetCore.DataProtection/Internal/DockerUtils.cs new file mode 100644 index 0000000000..7a1ede17e0 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection/Internal/DockerUtils.cs @@ -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 _isDocker = new Lazy(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 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)); + } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection/LoggingExtensions.cs b/src/Microsoft.AspNetCore.DataProtection/LoggingExtensions.cs index a2cc325f46..7792d48dbe 100644 --- a/src/Microsoft.AspNetCore.DataProtection/LoggingExtensions.cs +++ b/src/Microsoft.AspNetCore.DataProtection/LoggingExtensions.cs @@ -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 _usingAzureAsKeyRepository; + private static Action _usingEphemeralFileSystemLocationInContainer; + static LoggingExtensions() { _usingFallbackKeyWithExpirationAsDefaultKey = LoggerMessage.Define( @@ -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(eventId: 0, + _usingEphemeralFileSystemLocationInContainer = LoggerMessage.Define( + eventId: 60, + logLevel: LogLevel.Warning, + formatString: Resources.FileSystem_EphemeralKeysLocationInContainer); + + _usingRegistryAsKeyRepositoryWithDPAPI = LoggerMessage.Define( + 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(eventId: 0, + _usingProfileAsKeyRepository = LoggerMessage.Define( + 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(eventId: 0, + _usingProfileAsKeyRepositoryWithDPAPI = LoggerMessage.Define( + 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(eventId: 0, + _usingAzureAsKeyRepository = LoggerMessage.Define( + 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( @@ -782,5 +795,10 @@ namespace Microsoft.Extensions.Logging { _keyRingFailedToLoadOnStartup(logger, innerException); } + + public static void UsingEphemeralFileSystemLocationInContainer(this ILogger logger, string path) + { + _usingEphemeralFileSystemLocationInContainer(logger, path, null); + } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.DataProtection/Properties/Resources.Designer.cs index c2db503dab..c570287f84 100644 --- a/src/Microsoft.AspNetCore.DataProtection/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.DataProtection/Properties/Resources.Designer.cs @@ -15,401 +15,365 @@ namespace Microsoft.AspNetCore.DataProtection /// internal static string CryptCommon_GenericError { - get { return GetString("CryptCommon_GenericError"); } + get => GetString("CryptCommon_GenericError"); } /// /// An error occurred during a cryptographic operation. /// internal static string FormatCryptCommon_GenericError() - { - return GetString("CryptCommon_GenericError"); - } + => GetString("CryptCommon_GenericError"); /// /// The provided buffer is of length {0} byte(s). It must instead be exactly {1} byte(s) in length. /// internal static string Common_BufferIncorrectlySized { - get { return GetString("Common_BufferIncorrectlySized"); } + get => GetString("Common_BufferIncorrectlySized"); } /// /// The provided buffer is of length {0} byte(s). It must instead be exactly {1} byte(s) in length. /// 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); /// /// The payload was invalid. /// internal static string CryptCommon_PayloadInvalid { - get { return GetString("CryptCommon_PayloadInvalid"); } + get => GetString("CryptCommon_PayloadInvalid"); } /// /// The payload was invalid. /// internal static string FormatCryptCommon_PayloadInvalid() - { - return GetString("CryptCommon_PayloadInvalid"); - } + => GetString("CryptCommon_PayloadInvalid"); /// /// Property {0} cannot be null or empty. /// internal static string Common_PropertyCannotBeNullOrEmpty { - get { return GetString("Common_PropertyCannotBeNullOrEmpty"); } + get => GetString("Common_PropertyCannotBeNullOrEmpty"); } /// /// Property {0} cannot be null or empty. /// internal static string FormatCommon_PropertyCannotBeNullOrEmpty(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyCannotBeNullOrEmpty"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyCannotBeNullOrEmpty"), p0); /// /// The provided payload could not be decrypted. Refer to the inner exception for more information. /// internal static string Common_DecryptionFailed { - get { return GetString("Common_DecryptionFailed"); } + get => GetString("Common_DecryptionFailed"); } /// /// The provided payload could not be decrypted. Refer to the inner exception for more information. /// internal static string FormatCommon_DecryptionFailed() - { - return GetString("Common_DecryptionFailed"); - } + => GetString("Common_DecryptionFailed"); /// /// An error occurred while trying to encrypt the provided data. Refer to the inner exception for more information. /// internal static string Common_EncryptionFailed { - get { return GetString("Common_EncryptionFailed"); } + get => GetString("Common_EncryptionFailed"); } /// /// An error occurred while trying to encrypt the provided data. Refer to the inner exception for more information. /// internal static string FormatCommon_EncryptionFailed() - { - return GetString("Common_EncryptionFailed"); - } + => GetString("Common_EncryptionFailed"); /// /// The key {0:B} was not found in the key ring. /// internal static string Common_KeyNotFound { - get { return GetString("Common_KeyNotFound"); } + get => GetString("Common_KeyNotFound"); } /// /// The key {0:B} was not found in the key ring. /// internal static string FormatCommon_KeyNotFound() - { - return GetString("Common_KeyNotFound"); - } + => GetString("Common_KeyNotFound"); /// /// The key {0:B} has been revoked. /// internal static string Common_KeyRevoked { - get { return GetString("Common_KeyRevoked"); } + get => GetString("Common_KeyRevoked"); } /// /// The key {0:B} has been revoked. /// internal static string FormatCommon_KeyRevoked() - { - return GetString("Common_KeyRevoked"); - } + => GetString("Common_KeyRevoked"); /// /// The provided payload cannot be decrypted because it was not protected with this protection provider. /// internal static string ProtectionProvider_BadMagicHeader { - get { return GetString("ProtectionProvider_BadMagicHeader"); } + get => GetString("ProtectionProvider_BadMagicHeader"); } /// /// The provided payload cannot be decrypted because it was not protected with this protection provider. /// internal static string FormatProtectionProvider_BadMagicHeader() - { - return GetString("ProtectionProvider_BadMagicHeader"); - } + => GetString("ProtectionProvider_BadMagicHeader"); /// /// The provided payload cannot be decrypted because it was protected with a newer version of the protection provider. /// internal static string ProtectionProvider_BadVersion { - get { return GetString("ProtectionProvider_BadVersion"); } + get => GetString("ProtectionProvider_BadVersion"); } /// /// The provided payload cannot be decrypted because it was protected with a newer version of the protection provider. /// internal static string FormatProtectionProvider_BadVersion() - { - return GetString("ProtectionProvider_BadVersion"); - } + => GetString("ProtectionProvider_BadVersion"); /// /// Value must be non-negative. /// internal static string Common_ValueMustBeNonNegative { - get { return GetString("Common_ValueMustBeNonNegative"); } + get => GetString("Common_ValueMustBeNonNegative"); } /// /// Value must be non-negative. /// internal static string FormatCommon_ValueMustBeNonNegative() - { - return GetString("Common_ValueMustBeNonNegative"); - } + => GetString("Common_ValueMustBeNonNegative"); /// /// The type '{1}' is not assignable to '{0}'. /// internal static string TypeExtensions_BadCast { - get { return GetString("TypeExtensions_BadCast"); } + get => GetString("TypeExtensions_BadCast"); } /// /// The type '{1}' is not assignable to '{0}'. /// 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); /// /// The new key lifetime must be at least one week. /// internal static string KeyManagementOptions_MinNewKeyLifetimeViolated { - get { return GetString("KeyManagementOptions_MinNewKeyLifetimeViolated"); } + get => GetString("KeyManagementOptions_MinNewKeyLifetimeViolated"); } /// /// The new key lifetime must be at least one week. /// internal static string FormatKeyManagementOptions_MinNewKeyLifetimeViolated() - { - return GetString("KeyManagementOptions_MinNewKeyLifetimeViolated"); - } + => GetString("KeyManagementOptions_MinNewKeyLifetimeViolated"); /// /// The key {0:B} already exists in the keyring. /// internal static string XmlKeyManager_DuplicateKey { - get { return GetString("XmlKeyManager_DuplicateKey"); } + get => GetString("XmlKeyManager_DuplicateKey"); } /// /// The key {0:B} already exists in the keyring. /// internal static string FormatXmlKeyManager_DuplicateKey() - { - return GetString("XmlKeyManager_DuplicateKey"); - } + => GetString("XmlKeyManager_DuplicateKey"); /// /// Argument cannot be null or empty. /// internal static string Common_ArgumentCannotBeNullOrEmpty { - get { return GetString("Common_ArgumentCannotBeNullOrEmpty"); } + get => GetString("Common_ArgumentCannotBeNullOrEmpty"); } /// /// Argument cannot be null or empty. /// internal static string FormatCommon_ArgumentCannotBeNullOrEmpty() - { - return GetString("Common_ArgumentCannotBeNullOrEmpty"); - } + => GetString("Common_ArgumentCannotBeNullOrEmpty"); /// /// Property {0} must have a non-negative value. /// internal static string Common_PropertyMustBeNonNegative { - get { return GetString("Common_PropertyMustBeNonNegative"); } + get => GetString("Common_PropertyMustBeNonNegative"); } /// /// Property {0} must have a non-negative value. /// internal static string FormatCommon_PropertyMustBeNonNegative(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyMustBeNonNegative"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyMustBeNonNegative"), p0); /// /// GCM algorithms require the Windows platform. /// internal static string Platform_WindowsRequiredForGcm { - get { return GetString("Platform_WindowsRequiredForGcm"); } + get => GetString("Platform_WindowsRequiredForGcm"); } /// /// GCM algorithms require the Windows platform. /// internal static string FormatPlatform_WindowsRequiredForGcm() - { - return GetString("Platform_WindowsRequiredForGcm"); - } + => GetString("Platform_WindowsRequiredForGcm"); /// /// A certificate with the thumbprint '{0}' could not be found. /// internal static string CertificateXmlEncryptor_CertificateNotFound { - get { return GetString("CertificateXmlEncryptor_CertificateNotFound"); } + get => GetString("CertificateXmlEncryptor_CertificateNotFound"); } /// /// A certificate with the thumbprint '{0}' could not be found. /// internal static string FormatCertificateXmlEncryptor_CertificateNotFound(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("CertificateXmlEncryptor_CertificateNotFound"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("CertificateXmlEncryptor_CertificateNotFound"), p0); /// /// Decrypting EncryptedXml-encapsulated payloads is not yet supported on Core CLR. /// internal static string EncryptedXmlDecryptor_DoesNotWorkOnCoreClr { - get { return GetString("EncryptedXmlDecryptor_DoesNotWorkOnCoreClr"); } + get => GetString("EncryptedXmlDecryptor_DoesNotWorkOnCoreClr"); } /// /// Decrypting EncryptedXml-encapsulated payloads is not yet supported on Core CLR. /// internal static string FormatEncryptedXmlDecryptor_DoesNotWorkOnCoreClr() - { - return GetString("EncryptedXmlDecryptor_DoesNotWorkOnCoreClr"); - } + => GetString("EncryptedXmlDecryptor_DoesNotWorkOnCoreClr"); /// /// 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. /// internal static string AlgorithmAssert_BadBlockSize { - get { return GetString("AlgorithmAssert_BadBlockSize"); } + get => GetString("AlgorithmAssert_BadBlockSize"); } /// /// 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. /// internal static string FormatAlgorithmAssert_BadBlockSize(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadBlockSize"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadBlockSize"), p0); /// /// 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. /// internal static string AlgorithmAssert_BadDigestSize { - get { return GetString("AlgorithmAssert_BadDigestSize"); } + get => GetString("AlgorithmAssert_BadDigestSize"); } /// /// 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. /// internal static string FormatAlgorithmAssert_BadDigestSize(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadDigestSize"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadDigestSize"), p0); /// /// 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. /// internal static string AlgorithmAssert_BadKeySize { - get { return GetString("AlgorithmAssert_BadKeySize"); } + get => GetString("AlgorithmAssert_BadKeySize"); } /// /// 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. /// internal static string FormatAlgorithmAssert_BadKeySize(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadKeySize"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("AlgorithmAssert_BadKeySize"), p0); /// /// 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. /// internal static string KeyRingProvider_NoDefaultKey_AutoGenerateDisabled { - get { return GetString("KeyRingProvider_NoDefaultKey_AutoGenerateDisabled"); } + get => GetString("KeyRingProvider_NoDefaultKey_AutoGenerateDisabled"); } /// /// 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. /// internal static string FormatKeyRingProvider_NoDefaultKey_AutoGenerateDisabled() - { - return GetString("KeyRingProvider_NoDefaultKey_AutoGenerateDisabled"); - } + => GetString("KeyRingProvider_NoDefaultKey_AutoGenerateDisabled"); /// /// {0} must not be negative /// internal static string LifetimeMustNotBeNegative { - get { return GetString("LifetimeMustNotBeNegative"); } + get => GetString("LifetimeMustNotBeNegative"); } /// /// {0} must not be negative /// internal static string FormatLifetimeMustNotBeNegative(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("LifetimeMustNotBeNegative"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("LifetimeMustNotBeNegative"), p0); /// /// The '{0}' instance could not be found. When an '{1}' instance is set, a corresponding '{0}' instance must also be set. /// internal static string XmlKeyManager_IXmlRepositoryNotFound { - get { return GetString("XmlKeyManager_IXmlRepositoryNotFound"); } + get => GetString("XmlKeyManager_IXmlRepositoryNotFound"); } /// /// The '{0}' instance could not be found. When an '{1}' instance is set, a corresponding '{0}' instance must also be set. /// internal static string FormatXmlKeyManager_IXmlRepositoryNotFound(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("XmlKeyManager_IXmlRepositoryNotFound"), p0, p1); + + /// + /// Storing keys in a directory '{path}' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed. + /// + internal static string FileSystem_EphemeralKeysLocationInContainer { - return string.Format(CultureInfo.CurrentCulture, GetString("XmlKeyManager_IXmlRepositoryNotFound"), p0, p1); + get => GetString("FileSystem_EphemeralKeysLocationInContainer"); } + /// + /// Storing keys in a directory '{path}' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed. + /// + 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); diff --git a/src/Microsoft.AspNetCore.DataProtection/Repositories/FileSystemXmlRepository.cs b/src/Microsoft.AspNetCore.DataProtection/Repositories/FileSystemXmlRepository.cs index ce2bb494ff..a980e7f82c 100644 --- a/src/Microsoft.AspNetCore.DataProtection/Repositories/FileSystemXmlRepository.cs +++ b/src/Microsoft.AspNetCore.DataProtection/Repositories/FileSystemXmlRepository.cs @@ -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(); + + 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."); + } } /// diff --git a/src/Microsoft.AspNetCore.DataProtection/Resources.resx b/src/Microsoft.AspNetCore.DataProtection/Resources.resx index 292ec05625..9540aa54fa 100644 --- a/src/Microsoft.AspNetCore.DataProtection/Resources.resx +++ b/src/Microsoft.AspNetCore.DataProtection/Resources.resx @@ -192,4 +192,7 @@ The '{0}' instance could not be found. When an '{1}' instance is set, a corresponding '{0}' instance must also be set. + + Storing keys in a directory '{path}' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/DockerUtilsTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/DockerUtilsTests.cs new file mode 100644 index 0000000000..9ede10426b --- /dev/null +++ b/test/Microsoft.AspNetCore.DataProtection.Test/DockerUtilsTests.cs @@ -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)); + } + } +} diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/FileSystemXmlRepositoryTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/FileSystemXmlRepositoryTests.cs index 95ba4d1945..4bc2e10171 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/FileSystemXmlRepositoryTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/FileSystemXmlRepositoryTests.cs @@ -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()); + }); + } + /// /// Runs a test and cleans up the temp directory afterward. ///