Skip to content

Allow data protection without writable storage #3272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
MatthewLymer opened this issue Jun 27, 2018 · 6 comments
Closed

Allow data protection without writable storage #3272

MatthewLymer opened this issue Jun 27, 2018 · 6 comments
Labels
area-dataprotection Includes: DataProtection ✔️ Resolution: Won't Fix Resolved because we decided not to change the behavior reported in this issue.

Comments

@MatthewLymer
Copy link

I have a server farm in where I have no writable storage (no redis, fileshare, or database) but I would like to use the IDataProtectionProvider interfaces. One node will be sending data to another transitively through a 3rd-party.

Is there an implementation of IDataProtectionProvider that does not use the key-rotation logic that requires a writable storage?

I know of the EphemeralDataProtectionProvider which doesn't require a store, but this is useless in a server farm scenario. All other implementations look to require storing it in a filesystem, in azure, in redis, or whatever else.

@MatthewLymer MatthewLymer changed the title Allow DataProtection without writable storage Allow data protection without writable storage Jun 27, 2018
@blowdart
Copy link
Contributor

This isn't a scenario we support.

@blowdart blowdart added the area-dataprotection Includes: DataProtection label Jun 27, 2018
@natemcmaster natemcmaster added the ✔️ Resolution: Won't Fix Resolved because we decided not to change the behavior reported in this issue. label Aug 18, 2018
@kevinlo
Copy link

kevinlo commented Oct 26, 2018

@blowdart Does it mean that we cannot implement our own custom IDataProtectionProvider to create a custom IDataProtector? I think that is the case, but I would like to confirm that.

@blowdart
Copy link
Contributor

In your case, without some way to link the nodes to share a keyring, no, you'd be better off looking at encrypted XML and certificates, or some sort of pre-shared key mechanism.

@kevinlo
Copy link

kevinlo commented Oct 26, 2018

@blowdart

looking at encrypted XML and certificates, or some sort of pre-shared key mechanism

Do you have sample or link so I can read more about? I am in a similar situation that I don't have a fileshare and can't write on DB. I would like to see what possible solution I can have.

Thanks,

@MatthewLymer
Copy link
Author

MatthewLymer commented Oct 26, 2018

@kevinlo I implemented a very bad possibly not cryptographically secure workaround that seems to suit my needs. I pass in the secret key via an envvar (using IConfiguration).

    internal sealed class MyConfigurationKeyManager : IKeyManager
    {
        private readonly CustomKey[] _keys;

        public ConfigurationKeyManager(IOptions<KeyManagementOptions> keyManagement, IOptions<SecretSettings> secretSettings)
        {
            var secret = secretSettings.Value.Secret;

            if (string.IsNullOrEmpty(secret))
            {
                throw new InvalidOperationException("Secret must be set.");
            }

            var factories = keyManagement.Value.AuthenticatedEncryptorFactories;
            var masterKey = new Secret(Encoding.UTF8.GetBytes(secret));

            var key = new CustomKey(factories, masterKey);

            _keys = new[] { key };
        }

        public IKey CreateNewKey(DateTimeOffset activationDate, DateTimeOffset expirationDate)
        {
            return _keys[0];
        }

        public IReadOnlyCollection<IKey> GetAllKeys()
        {
            return _keys;
        }

        public CancellationToken GetCacheExpirationToken()
        {
            return CancellationToken.None;
        }

        public void RevokeKey(Guid keyId, string reason = null)
        {
            throw new NotSupportedException();
        }

        public void RevokeAllKeys(DateTimeOffset revocationDate, string reason = null)
        {
            throw new NotSupportedException();
        }

        private sealed class CustomKey : IKey
        {
            private static readonly Guid DefaultKeyId = Guid.Parse("PUT YOUR OWN GUID HERE");
            private static readonly DateTimeOffset DefaultActivationDate = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
            private static readonly DateTimeOffset DefaultExpirationDate = new DateTimeOffset(2222, 1, 1, 0, 0, 0, TimeSpan.Zero);

            private readonly IEnumerable<IAuthenticatedEncryptorFactory> _encryptorFactories;

            private IAuthenticatedEncryptor _encryptor;

            public CustomKey(IEnumerable<IAuthenticatedEncryptorFactory> encryptorFactories, ISecret masterKey)
            {
                _encryptorFactories = encryptorFactories;

                Descriptor = new ManagedAuthenticatedEncryptorDescriptor(
                    new ManagedAuthenticatedEncryptorConfiguration(), 
                    masterKey);
            }

            public IAuthenticatedEncryptor CreateEncryptor()
            {
                return _encryptor ?? (_encryptor = CreateEncryptorImpl());
            }

            public DateTimeOffset ActivationDate => DefaultActivationDate;

            public DateTimeOffset CreationDate => DefaultActivationDate;

            public DateTimeOffset ExpirationDate => DefaultExpirationDate;

            public bool IsRevoked => false;

            public Guid KeyId => DefaultKeyId;

            public IAuthenticatedEncryptorDescriptor Descriptor { get; }

            private IAuthenticatedEncryptor CreateEncryptorImpl()
            {
                return _encryptorFactories
                    .Select(x => x.CreateEncryptorInstance(this))
                    .FirstOrDefault(x => x != null);
            }
        }
    }
    internal static class DataProtectionServiceCollectionExtensions
    {
        public static IServiceCollection AddBasicDataProtection(
            [NotNull] this IServiceCollection services, 
            Action<SecretSettings> setupAction)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            services
                .Configure(setupAction)
                .AddSingleton<IKeyManager, MyConfigurationKeyManager>()
                .AddDataProtection(x => x.ApplicationDiscriminator = "My Application Name")
                .DisableAutomaticKeyGeneration();

            return services;
        }
    }
    public sealed class SecretSettings
    {
        public string Secret { get; set; }
    }

Basically, I implement my own IKeyManager that creates the same key id that never expires

@kevinlo
Copy link

kevinlo commented Oct 26, 2018

@MatthewLymer So, your solution is, instead of creating a custom IDataProtectionProvider , you use a custom IKeyManager to generate a fixed key using secret that never expire?

@ghost ghost locked as resolved and limited conversation to collaborators Dec 3, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-dataprotection Includes: DataProtection ✔️ Resolution: Won't Fix Resolved because we decided not to change the behavior reported in this issue.
Projects
None yet
Development

No branches or pull requests

4 participants