Skip to content

Commit

Permalink
Save password parameter default values to user secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
DamianEdwards committed Jun 14, 2024
1 parent bda3168 commit 03ffc39
Show file tree
Hide file tree
Showing 24 changed files with 234 additions and 157 deletions.
117 changes: 0 additions & 117 deletions playground/TestShop/AppHost/ParameterExtensions.cs

This file was deleted.

3 changes: 2 additions & 1 deletion playground/TestShop/AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var builder = DistributedApplication.CreateBuilder(args);

var catalogDb = builder.AddPostgres("postgres")
.WithDataVolume()
.WithPgAdmin()
.AddDatabase("catalogdb");

Expand All @@ -15,7 +16,7 @@
.WithReference(catalogDb)
.WithReplicas(2);

var messaging = builder.AddRabbitMQ("messaging", password: builder.CreateStablePassword("rabbitmq-password", special: false))
var messaging = builder.AddRabbitMQ("messaging")
.WithDataVolume()
.WithManagementPlugin()
.PublishAsContainer();
Expand Down
8 changes: 7 additions & 1 deletion src/Aspire.Hosting.MySql/MySqlBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ public static class MySqlBuilderExtensions
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<MySqlServerResource> AddMySql(this IDistributedApplicationBuilder builder, string name, IResourceBuilder<ParameterResource>? password = null, int? port = null)
{
var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password");
var passwordParameterName = $"{name}-password";
var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, passwordParameterName);

if (passwordParameter.Default is not null)
{
passwordParameter.Default = new UserSecretsParameterDefault(builder.Environment.ApplicationName, passwordParameterName, passwordParameter.Default);
}

var resource = new MySqlServerResource(name, passwordParameter);
return builder.AddResource(resource)
Expand Down
8 changes: 7 additions & 1 deletion src/Aspire.Hosting.Oracle/OracleDatabaseBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ public static class OracleDatabaseBuilderExtensions
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<OracleDatabaseServerResource> AddOracle(this IDistributedApplicationBuilder builder, string name, IResourceBuilder<ParameterResource>? password = null, int? port = null)
{
var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password");
var passwordParameterName = $"{name}-password";
var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, passwordParameterName);

if (passwordParameter.Default is not null)
{
passwordParameter.Default = new UserSecretsParameterDefault(builder.Environment.ApplicationName, passwordParameterName, passwordParameter.Default);
}

var oracleDatabaseServer = new OracleDatabaseServerResource(name, passwordParameter);
return builder.AddResource(oracleDatabaseServer)
Expand Down
9 changes: 8 additions & 1 deletion src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,16 @@ public static IResourceBuilder<PostgresServerResource> AddPostgres(this IDistrib
IResourceBuilder<ParameterResource>? password = null,
int? port = null)
{
var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password");
var passwordParameterName = $"{name}-password";
var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, passwordParameterName);

if (passwordParameter.Default is not null)
{
passwordParameter.Default = new UserSecretsParameterDefault(builder.Environment.ApplicationName, passwordParameterName, passwordParameter.Default);
}

var postgresServer = new PostgresServerResource(name, userName?.Resource, passwordParameter);

return builder.AddResource(postgresServer)
.WithEndpoint(port: port, targetPort: 5432, name: PostgresServerResource.PrimaryEndpointName) // Internal port is always 5432.
.WithImage(PostgresContainerImageTags.Image, PostgresContainerImageTags.Tag)
Expand Down
9 changes: 8 additions & 1 deletion src/Aspire.Hosting.Qdrant/QdrantBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,15 @@ public static IResourceBuilder<QdrantServerResource> AddQdrant(this IDistributed
int? grpcPort = null,
int? httpPort = null)
{
var apiKeyParameterName = $"{name}-Key";
var apiKeyParameter = apiKey?.Resource ??
ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-Key", special: false);
ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, apiKeyParameterName, special: false);

if (apiKeyParameter.Default is not null)
{
apiKeyParameter.Default = new UserSecretsParameterDefault(builder.Environment.ApplicationName, apiKeyParameterName, apiKeyParameter.Default);
}

var qdrant = new QdrantServerResource(name, apiKeyParameter);
return builder.AddResource(qdrant)
.WithImage(QdrantContainerImageTags.Image, QdrantContainerImageTags.Tag)
Expand Down
8 changes: 7 additions & 1 deletion src/Aspire.Hosting.RabbitMQ/RabbitMQBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@ public static IResourceBuilder<RabbitMQServerResource> AddRabbitMQ(this IDistrib
IResourceBuilder<ParameterResource>? password = null,
int? port = null)
{
var passwordParameterName = $"{name}-password";
// don't use special characters in the password, since it goes into a URI
var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password", special: false);
var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, passwordParameterName, special: false);

if (passwordParameter.Default is not null)
{
passwordParameter.Default = new UserSecretsParameterDefault(builder.Environment.ApplicationName, passwordParameterName, passwordParameter.Default);
}

var rabbitMq = new RabbitMQServerResource(name, userName?.Resource, passwordParameter);
var rabbitmq = builder.AddResource(rabbitMq)
Expand Down
8 changes: 7 additions & 1 deletion src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@ public static class SqlServerBuilderExtensions
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<SqlServerServerResource> AddSqlServer(this IDistributedApplicationBuilder builder, string name, IResourceBuilder<ParameterResource>? password = null, int? port = null)
{
var passwordParameterName = $"{name}-password";
// The password must be at least 8 characters long and contain characters from three of the following four sets: Uppercase letters, Lowercase letters, Base 10 digits, and Symbols
var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password", minLower: 1, minUpper: 1, minNumeric: 1);
var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, passwordParameterName, minLower: 1, minUpper: 1, minNumeric: 1);

if (passwordParameter.Default is not null)
{
passwordParameter.Default = new UserSecretsParameterDefault(builder.Environment.ApplicationName, passwordParameterName, passwordParameter.Default);
}

var sqlServer = new SqlServerServerResource(name, passwordParameter);
return builder.AddResource(sqlServer)
Expand Down
56 changes: 56 additions & 0 deletions src/Aspire.Hosting/ApplicationModel/UserSecretsParameterDefault.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using Aspire.Hosting.Publishing;
using Microsoft.Extensions.Configuration.UserSecrets;
using Microsoft.Extensions.SecretManager.Tools.Internal;

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Wraps a <see cref="ParameterDefault"/> such that the default value is saved to the project's user secrets store.
/// </summary>
/// <param name="applicationName">The application name.</param>
/// <param name="parameterName">The parameter name.</param>
/// <param name="parameterDefault">The parameter with default value.</param>
public sealed class UserSecretsParameterDefault(string applicationName, string parameterName, ParameterDefault parameterDefault)
: ParameterDefault
{
/// <inheritdoc/>
public override string GetDefaultValue()
{
var value = parameterDefault.GetDefaultValue();
var configurationKey = $"{ParameterResourceBuilderExtensions.ConfigurationSectionKey}:{parameterName}";
if (!TrySetUserSecret(applicationName, configurationKey, value))
{
throw new DistributedApplicationException($"Failed to set value for parameter '{parameterName}' in application '{applicationName}' to user secrets.");
}
return value;
}

/// <inheritdoc/>
public override void WriteToManifest(ManifestPublishingContext context) => parameterDefault.WriteToManifest(context);

private static bool TrySetUserSecret(string applicationName, string name, string value)
{
if (!string.IsNullOrEmpty(applicationName))
{
var appAssembly = Assembly.Load(new AssemblyName(applicationName));
if (appAssembly is not null && appAssembly.GetCustomAttribute<UserSecretsIdAttribute>()?.UserSecretsId is { } userSecretsId)
{
// Save the value to the secret store
try
{
var secretsStore = new SecretsStore(userSecretsId);
secretsStore.Set(name, value);
secretsStore.Save();
return true;
}
catch (Exception) { }
}
}

return false;
}
}
1 change: 1 addition & 0 deletions src/Aspire.Hosting/Aspire.Hosting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<Compile Include="$(SharedDir)LaunchSettings.cs" Link="LaunchSettings.cs" />
<Compile Include="$(SharedDir)LaunchProfile.cs" Link="LaunchProfile.cs" />
<Compile Include="$(SharedDir)LaunchSettingsSerializerContext.cs" Link="LaunchSettingsSerializerContext.cs" />
<Compile Include="$(SharedDir)SecretsStore.cs" Link="Utils\SecretsStore.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
9 changes: 7 additions & 2 deletions src/Aspire.Hosting/ParameterResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ namespace Aspire.Hosting;
/// </summary>
public static class ParameterResourceBuilderExtensions
{
/// <summary>
/// The configuration section that parameter values are read from.
/// </summary>
public const string ConfigurationSectionKey = "Parameters";

/// <summary>
/// Adds a parameter resource to the application.
/// </summary>
Expand All @@ -29,7 +34,7 @@ public static IResourceBuilder<ParameterResource> AddParameter(this IDistributed

private static string GetParameterValue(IConfiguration configuration, string name, ParameterDefault? parameterDefault)
{
var configurationKey = $"Parameters:{name}";
var configurationKey = $"{ConfigurationSectionKey}:{name}";
return configuration[configurationKey]
?? parameterDefault?.GetDefaultValue()
?? throw new DistributedApplicationException($"Parameter resource could not be used because configuration key '{configurationKey}' is missing and the Parameter has no default value."); ;
Expand All @@ -51,7 +56,7 @@ internal static IResourceBuilder<ParameterResource> AddParameter(this IDistribut
State = KnownResourceStates.Hidden,
Properties = [
new("parameter.secret", secret.ToString()),
new(CustomResourceKnownProperties.Source, connectionString ? $"ConnectionStrings:{name}" : $"Parameters:{name}")
new(CustomResourceKnownProperties.Source, connectionString ? $"ConnectionStrings:{name}" : $"{ParameterResourceBuilderExtensions.ConfigurationSectionKey}:{name}")
]
};

Expand Down
6 changes: 5 additions & 1 deletion src/Aspire.Hosting/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
#nullable enable

Aspire.Hosting.ApplicationModel.UserSecretsParameterDefault
Aspire.Hosting.ApplicationModel.UserSecretsParameterDefault.UserSecretsParameterDefault(string! applicationName, string! parameterName, Aspire.Hosting.ApplicationModel.ParameterDefault! parameterDefault) -> void
const Aspire.Hosting.ParameterResourceBuilderExtensions.ConfigurationSectionKey = "Parameters" -> string!
override Aspire.Hosting.ApplicationModel.UserSecretsParameterDefault.GetDefaultValue() -> string!
override Aspire.Hosting.ApplicationModel.UserSecretsParameterDefault.WriteToManifest(Aspire.Hosting.Publishing.ManifestPublishingContext! context) -> void
Loading

0 comments on commit 03ffc39

Please sign in to comment.