code4it-dev / blog-comments

https://www.code4it.dev/
1 stars 0 forks source link

blog/my-2-secret-endpoints #40

Open utterances-bot opened 1 year ago

utterances-bot commented 1 year ago

The 2 secret endpoints I create in my .NET APIs - Code4IT

In this article, I will show you two simple tricks that help me understand the deployment status of my .NET APIs

https://www.code4it.dev/blog/my-2-secret-endpoints

erwin-beckers commented 1 year ago

Seriously ? Let's create public (anonymous) endpoints which expose sensitive data and use security-by-obscurity to fix that ?? That's like the worst advise ever ?

Those endpoints should not be anonymous, but require proper authorization imo

bellons91 commented 1 year ago

Well, actually they should not even be there - they're the last resort for understanding what's going on.

I totally agree with preferring authentication over obfuscation, but not every project has an auth layer.

So, yes, if it's something that you're planning to leave there, especially on production, you should consider authentication. But if it's something used for quick checks - may be exposed only if a specific flag is set - then pure obfuscation can be ok.

vjacquet commented 1 year ago

Thanks for this nice post. I often add environment and version number in my web pages but I never though of doing it for an API.

The /conf is more controversial. First there is the security issue. I would never ever expose the settings as such, even with proper authentication. If I need to know what happens, I log it. But there is also the problem of accuracy: some settings are retrieved as IOptions, others as IOptionsSnapshot. They have different lifetime. If the settings have changed since your application started, what your endpoint returns may not be what some of your services are using.

bellons91 commented 1 year ago

Good catch, I haven't thought about that.

I'm used to IOptions, therefore I haven't tested it with IOptionsSnapshot.

Is there a way to catch all the configs regardless of when they were set? My only guess is by wrapping all the application config within a root key, such as

{ "MyApplication":{ "SomeRealConfig": ... }

Now you can use IOptionsSnapshot to see all the application configs.

Any other ideas?

vjacquet commented 1 year ago

Disclaimer: for security reason, I do not think an endpoint returning all the settings is a good idea, you should at least use a whitelist for the keys you want to return

If you want an endpojnt to return your configuration, your code is fine. If you want an endpoint to return the options your services are running with, then there are 4 issues

  1. IOptions<TOptions> is defined with the values of the configuration at the time of its instanciation. IOptionsSnapshot<TOptions> is instanciated for each request, so it reflects the state of the configuration now.
  2. Options can be configured also by using an Action<TOptions>, which is not in the configuration but in the code
  3. Options can be named (IOptions<TOptions> cannot but IOptionsSnapshot<TOptions> and IOptionsMonitor<TOptions> can), and, as far as I now, you cannot enumerate the names
  4. Options can be passed in as argument without being resolved at all (see for instance https://github.com/dotnet/aspnetcore/blob/25ffef8fe5ad0ed67e0ad33b02379c2d3c1a890f/src/Middleware/StaticFiles/src/DefaultFilesExtensions.cs#L64)

That being said, there is a way to get all the IOptions<TOptions> that will be resolved by the service provider, or, to be more precise, there is a way to get all the options's type and then IOptions<TOptions> can be requested from the service provider. As you may know, only IOptions<> is registered in the service collection so you cannot get them by looking for them. Instead we could relying on the fact that the options have to be configured and so an interface with a concrete type is registered.

var query = from d in builder.Services
            where d.ServiceType.IsGenericType && !d.ServiceType.IsGenericTypeDefinition
            let g = d.ServiceType.GetGenericTypeDefinition()
            where g == typeof(IConfigureOptions<>) || g == typeof(IPostConfigureOptions<>)
            select d.ServiceType.GenericTypeArguments[0];
var optionTypes = query.Distinct().ToList();

Now, in you endpoint you can retrieve all options like this

var options = optionTypes.Select(t => context.RequestServices.GetService(typeof(IOptions<>).MakeGenericType(t))).ToList();

Keep in mind that the options may not be serializable by defaut. Some use Func<..> as properties.

Also, you still won't know if you are using the singleton value or the snapshot value, and you will not be able to handle named options either.

Most of this is contextual, so logging the options when needed seems easier, safer and more accurate.

For more information on what is registered when you add the options: https://github.com/dotnet/runtime/blob/20d4e309ec4f081e4b1d47a8f937abfbb763a4d4/src/libraries/Microsoft.Extensions.Options/src/OptionsServiceCollectionExtensions.cs#L26-L30

bellons91 commented 1 year ago

Here's a nice comment from David Osolkowski on Twitter:

For the configuration data, if you get IConfigurationRoot instead of IConfiguration you can use the built-in GetDebugView method that not only shows everything but also which config source it came from.

vjacquet commented 1 year ago

I was not aware of this. Thanks for the information.

abatishchev commented 1 year ago

I'd suggest using the ASP.NET Core built-in health checks with custom IHealthCheck.

ComradeLV commented 1 year ago

If there's any sensitive information, it shouldn't appear in your public endpoints. If you need to have an endpoints to launch some actions, they must be secured with query key at least. And also, if you just need to expose some environmental data, for example, to collect status or statistics from group of services, you can use custom healthchecks.

bellons91 commented 1 year ago

I'd suggest using the ASP.NET Core built-in health checks with custom IHealthCheck.

Well, Health Checks have a meaning - they tell you that the system is healthy.

The Env endpoint has a totally different meaning: it tells you which is the current ASPNETCORE_ENVIRONMENT variable (and you can use it to print whichever info you like)

ComradeLV commented 1 year ago

Well, Health Checks have a meaning - they tell you that the system is healthy.

You're right in general, but environmental data (such as env, build id, commit and so) is usually a part of diagnostic data, which you probably will want to receive together with "healthy" or "unhealthy" status update. So having these values in healthchecks is a natural way how to store and access em.

salem84 commented 1 year ago

Hi, I have created AspNetCore.VersionInfo library to expose environment and custom information, pluggable by ad hoc providers. I have read also your interesting comments about authentication issues and in the next version surely I will improve this aspect! Any contribution or suggestion is welcome!