/// <inheritdoc /> public Task <RetrievedSecrets> RetrieveSecretsAsync(List <RetrieveSecretsRequest> requests, CancellationToken token) { if (_secretsCommunicationKind == CrossProcessSecretsCommunicationKind.Environment) { // Default mode for the launcher return(RetrieveSecretsCoreAsync(requests, token)); } else if (_secretsCommunicationKind == CrossProcessSecretsCommunicationKind.EnvironmentSingleEntry) { var secretsResult = LazyInitializer.EnsureInitialized(ref _secrets, () => DeserializeFromEnvironmentVariable()); secretsResult.ThrowIfFailure(); return(Task.FromResult(secretsResult.Value)); } else if (_secretsCommunicationKind == CrossProcessSecretsCommunicationKind.MemoryMappedFile) { // 'ReadExposedSecrets' returns a disposable object, but the secrets obtained here are long-lived. RetrievedSecrets secrets = InterProcessSecretsCommunicator.ReadExposedSecrets(new OperationContext(_tracingContext)); return(Task.FromResult(secrets)); } else { throw Contract.AssertFailure($"Unknown {nameof(CrossProcessSecretsCommunicationKind)}: {_secretsCommunicationKind}."); } }
public void TestSerialization() { var secretsMap = new Dictionary <string, Secret> { ["cbcache-test-redis-dm_s1"] = new PlainTextSecret("Fake secret that is quite long to emulate the size of the serialized entry."), ["cbcache-test-redis-secondary-dm_s1"] = new PlainTextSecret("Fake secret that is quite long to emulate the size of the serialized entry."), ["cbcache-test-event-hub-dm_s1"] = new PlainTextSecret("Fake secret that is quite long to emulate the size of the serialized entry."), ["cbcacheteststorage-dm_s1-sas"] = new UpdatingSasToken(new SasToken("token_name", "storage_account", "resource_path")), ["ContentMetadataBlobSecretName-dm_s1"] = new PlainTextSecret( "Fake secret that is quite long to emulate the size of the serialized entry.") }; var secrets = new RetrievedSecrets(secretsMap); var text = RetrievedSecretsSerializer.Serialize(secrets); var deserializedSecretsMap = RetrievedSecretsSerializer.Deserialize(text).ShouldBeSuccess().Value.Secrets; Assert.Equal(secretsMap.Count, deserializedSecretsMap.Count); foreach (var kvp in secretsMap) { Assert.Equal(kvp.Value, deserializedSecretsMap[kvp.Key]); Assert.Equal(kvp.Value, deserializedSecretsMap[kvp.Key]); } }
public static string Serialize(RetrievedSecrets secrets) { var secretsList = secrets.Secrets .Select(kvp => SecretData.FromSecret(kvp.Key, kvp.Value)) .OrderBy(s => s.Name) .ToList(); return(JsonSerializer.Serialize(secretsList)); }
private static void UpdateSecrets(RetrievedSecrets original, RetrievedSecrets @new) { foreach (var(name, secret) in @new.Secrets) { if (secret is UpdatingSasToken updatingToken) { // The secret types can't change. var originalSecret = (UpdatingSasToken)original.Secrets[name]; originalSecret.UpdateToken(updatingToken.Token); } } }
private static void AssertSecretsAreEqual(RetrievedSecrets left, RetrievedSecrets right) { Assert.Equal(left.Secrets.Count, right.Secrets.Count); foreach (var(name, secret) in left.Secrets) { if (right.Secrets.TryGetValue(name, out var rightSecret)) { Assert.Equal(secret, rightSecret); } else { Assert.True(false, $"Can't find a secret with name '{name}' in the 'right' variable."); } } }
public void TestUpdatableTokens() { var updatingToken = new UpdatingSasToken(new SasToken(token: "Token 1", "Storage Account 1", "Resource Path 1")); var originalSecrets = new RetrievedSecrets( new Dictionary <string, Secret>() { ["Secret 1"] = new PlainTextSecret("Secret Value 1"), ["Secret 2"] = updatingToken }); var context = new OperationContext(new Context(Logger)); using var secretsExposer = InterProcessSecretsCommunicator.Expose(context, originalSecrets); using var readSecrets = InterProcessSecretsCommunicator.ReadExposedSecrets(context, pollingIntervalInSeconds: 10_000); AssertSecretsAreEqual(originalSecrets, readSecrets); int tokenUpdated = 0; ((UpdatingSasToken)readSecrets.Secrets["Secret 2"]).TokenUpdated += (sender, token) => { tokenUpdated++; }; // Updating the token updatingToken.UpdateToken(new SasToken("1", "2", "3")); readSecrets.RefreshSecrets(context); AssertSecretsAreEqual(originalSecrets, readSecrets); Assert.Equal(1, tokenUpdated); // An event should be raised // Updating token once again updatingToken.UpdateToken(new SasToken("2", "2", "3")); readSecrets.RefreshSecrets(context); AssertSecretsAreEqual(originalSecrets, readSecrets); Assert.Equal(2, tokenUpdated); // An event should be raised }
public CacheServiceWrapper(CacheServiceWrapperConfiguration configuration, ServiceLifetimeManager serviceLifetimeManager, RetrievedSecrets secrets) { _configuration = configuration; _serviceLifetimeManager = serviceLifetimeManager; _secrets = secrets; }
public static IDisposable Expose(OperationContext context, RetrievedSecrets secrets, string?fileName = null) { string memoryMappedFileName = fileName ?? SecretsFileName; var memoryMappedFile = context.PerformOperation( Tracer, () => { var serializedSecrets = RetrievedSecretsSerializer.Serialize(secrets); return(Result.Success(MemoryMappedFileHelper.CreateMemoryMappedFileWithContent(memoryMappedFileName, serializedSecrets))); }, extraStartMessage: $"Exposing secrets to '{memoryMappedFileName}'", messageFactory: _ => $"Exposed secrets to '{memoryMappedFileName}'" ).ThrowIfFailure(); var disposeActions = new List <Action>(); disposeActions.Add( () => { Tracer.Debug(context, $"Closing memory mapped file '{memoryMappedFileName}'"); memoryMappedFile.Dispose(); }); disposeActions.AddRange(trackSecretsLifetime()); return(new InterProcessSecretsCommunicator(disposeActions)); List <Action> trackSecretsLifetime() { var actions = new List <Action>(); foreach (var(_, secret) in secrets.Secrets) { if (secret is UpdatingSasToken updating) { updating.TokenUpdated += tokenUpdated; actions.Add(() => updating.TokenUpdated -= tokenUpdated); } } if (actions.Count != 0) { // It means that we have at least one 'updatable' token. actions.Insert( 0, () => { Tracer.Debug(context, "Unsubscribing from secret updates."); }); } return(actions); } void tokenUpdated(object?sender, SasToken e) { var newSerializedSecrets = RetrievedSecretsSerializer.Serialize(secrets); context.PerformOperation( Tracer, () => { MemoryMappedFileHelper.UpdateContent(memoryMappedFileName, newSerializedSecrets); return(BoolResult.Success); }, extraStartMessage: $"Updating secrets in '{memoryMappedFileName}'", messageFactory: _ => $"Updated secrets in '{memoryMappedFileName}'", caller: "tokenUpdated") .IgnoreFailure(); // We traced the results } }