While running on an app service in Azure, I would like to use the managed service identity to authenticate against Azure SQL Server with two sets of permissions. Within my service I'd like to have a read/write DBContext and a readonly DBContext. Is there a way to downgrade access after authenticating?
I don't necessarily have access to an AG with a readonly replica so I don't think I can use applicationintent=readonly and EXECUTE AS only seems to apply in a stored procedure or function.
Any other suggestions?
I was wrong about EXECUTE AS not working outside of a stored proc/function (I found this documentation to be misleading, and later found this.
As such, I ended up going with the following solution:
Create a read-only user with no login
CREATE USER [readonly_user] WITHOUT LOGIN
ALTER ROLE [db_datareader] ADD MEMBER [readonly_user]
I create an interceptor that looks for the applicationintent=readonly flag to be set in the connection string
public class SessionContextDbConnectionInterceptor: DbCommandInterceptor {
/// <inheritdoc />
public override Task<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = new CancellationToken()) {
if (eventData.Connection.ConnectionString.Contains("applicationintent=readonly",
StringComparison.InvariantCultureIgnoreCase)) {
command.CommandText = $"EXECUTE AS USER = 'readonly_user';\n{command.CommandText};\nREVERT;";
}
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
}
}
Then register the interceptor with the DB context
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"))
.AddInterceptors(new SessionContextDbConnectionInterceptor()));
Related
I have a Springboot app that connects to Azure Active Directory and getting the properties from there.
I don't know exactly how the Active Directory connection works, but I am trying to write JUnits for the app without having to call the real Azure Active Directory. Whenever I run my JUnit, it keeps calling Azure.
I know I can mock the AzureCredentials class, but I really don't know how to make my JUnit use the mock even if I created a mock. None of the classes explicitly depend on the AzureCredentials class. It's somehow being called inherently.
Please guide me as to how I could JUnit this app without connecting to Azure.
public class AzureCredentials implements AppConfigurationCredentialProvider, KeyVaultCredentialProvider {
#Override
public TokenCredential getKeyVaultCredential(String uri) {
return getCredential();
}
#Override
public TokenCredential getAppConfigCredential(String uri) {
return getCredential();
}
private TokenCredential getCredential() {
return new EnvironmentCredentialBuilder().build();
}
}
EDIT 14/2/2023: I've also tried by using a DUMMY connection string for the tests, but then I get this error:
Suppressed: com.microsoft.aad.msal4j.MsalServiceException: AADSTS500011: The resource principal named https://testEndPoint was not found in the tenant named...
I understand the cause for this error, but I just don't want the test to try to connect at all, if there is a way to do this!
I will buy anyone a beer who can solve my problem!
As a piece of work I need to update our Identity Server to use an implicit login flow, it was currently using Bearer Token access only. As a part of our architecture we are using Multi-tenancy.
For security reaosns we need to check the tenant header to verify that the user is not impersonating another tenant. To do this from a client perspective we use a custom IProfileService. This gets triggered in the middleware of Identity Server, meaning all is good!
However if I was a user and I wanted to use some form of functionality on Identity Server itself and not an actual client of it, then IProfileService will not be triggered. An example of this would be to revoke access to clients, or even log out.
The GetProfileDataAsync Method on IProfileService is Invoked when the client request additional claims for the user.
germansak on Github Issue here had a similar issue and it was never quite answered (https://github.com/IdentityServer/IdentityServer4/issues/1643)
Leading to my question, how has anyone been able to verify a Tenant on Identity Server itself when they are not going through a Client, but instead Identity Server. If I can't trigger IProfileService I feel as if I'm beat!
Both logout and grants functionality is not part of the identity server 4 core package and they are simply implemented as ASP.NET Core razor views outside of the oauth2 flows.
There are few ways to validate headers therefore, I guess the easiest in my opinion would be to add another middleware.
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<TenantHeaderValidationMiddleware>();
...Your other config
}
public class TenantHeaderValidationMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
string tenantId = Request.Headers["YourTenantIdHeader"];
//do the checking
...Validation logic
//pass request further if correct
await _next(context);
}
}
I have a web api application which I allow an access to only authorized user.
I do it by using attribute [Authorize] with controllers
Can I restrict from accessing the application a particular user with a given username even though he/she's in Azure AD?
Can I restrict from accessing the application a particular user with a given username even though he/she's in Azure AD?
What you need is to create a policy and check current user against this policy whenever you want.
There're two ways to do that.
Use a magic string to configure policy (e.g. [Authorize(policy="require_username=name")]), and then create a custom policy provider to provide the policy dynamically. For more details, see https://learn.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-2.2
Create a static policy and use a custom AuthorizeFilter to check whether current user is allowed.
Since this thread is asking "Restricting Azure AD users from accessing web api controller", I prefer to the 2nd way.
Here's an implementation for the 2nd approach. Firstly, let's define a policy of requirename:
services.AddAuthorization(opts =>{
opts.AddPolicy("requirename", pb => {
pb.RequireAssertion(ctx =>{
if(ctx.User==null) return false;
var requiredName = ctx.Resource as string;
return ctx.User.HasClaim("name",requiredName);
});
});
});
And to check against this policy, create a custom AuthorizeFilter as below:
public class RequireNameFilterAttribute : Attribute, IAsyncAuthorizationFilter
{
public string Name{get;set;}
public RequireNameFilterAttribute(string name) { this.Name = name; }
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var user= context.HttpContext.User;
if(user==null){
context.Result = new ChallengeResult();
return;
}
var authZService = context.HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();
var result= await authZService.AuthorizeAsync(user, this.Name, "requirename");
if (!result.Succeeded) {
context.Result = new ForbidResult();
}
}
}
Finally, whenever you want to deny users without required names, simply decorate the action method with a RequireNameFilter(requiredName) attribute:
[RequireNameFilter("amplifier")]
public string Test()
{
return "it works";
}
[Edit]
AAD can restrict Azure AD users from accessing web api controller on an Application level. But cannot disallow an user to access a Controller API (API level).
Here's how-to about restricting Azure AD users on an Application Level
Login your Azure portal:
Choose an Activity Directory (e.g. Default Directory)
Click [Enterprise applications]
Choose the application you want to restrict (e.g. AspNetCore-Quickstart)
Select [Properties], Change the [User assignment required] to Yes
Select [Users and groups], Add/Relete users for this application as you need :
Be aware Azure AD is actually an Indentity Provider. This approach only works for the entire application. It's impossible to allow some user to access the App but disallow him to access a specific controller without coding/configuring the Application. To do that, we have no choice but to authorize uses within the application.
I have two Azure Mobile Services (.NET backend) which share the same Azure Database. Let say Service "X" and "Y". The database is created by service "X" (when it ran for the first time) and created tables "TA" with schema name "X". Then I ran service "Y" which created the same tables "TA" and "TB" in the same database but with schema name "Y".
Now I want to make service "Y" to use schema "X" to make sure both services use the same data. Inside the "TADataController" I changed the code to:
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
// MyContext context = new MyContext(Services.Settings.Name.Replace('-', '_'));
YContext context = new YContext("X");
DomainManager = new EntityDomainManager<ADataController>(context, Request, Services);
}
Also in the "YContext" class
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//string schema = ServiceSettingsDictionary.GetSchemaName();
//if (!string.IsNullOrEmpty(schema))
// modelBuilder.HasDefaultSchema(schema);
modelBuilder.HasDefaultSchema("X");
}
but when I try to access the data inside "TADataController" using Query(), I get a SqlException with message "The specified schema name "X" either does not exist or you do not have permission to use it." I doubt that permission would be the issue as both services use the same Azure Database account and also clearly the schema exists as my other service uses it! so I cannot figure out what the problem is.
I also tried:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var entityConfig = modelBuilder.Entity<UserProfileData>();
entityConfig.ToTable("UserProfileDatas", "X");
}
but the same exception was raised. There are some information on the web for transferring data to another schema which could be helpful if I wanted to transfer my data to service "Y". But I want to use both services "X" and "Y" on a shared database.
UPDATE:
I realized that ServiceSettingsDictionary.GetSchemaName() used in the OnModelCreating returns the name of the schema based on the configuration, so I uploaded the original codes:
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MyContext context = new MyContext(Services.Settings.Name.Replace('-', '_'));
DomainManager = new EntityDomainManager<ADataController>(context, Request, Services);
}
and
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
string schema = ServiceSettingsDictionary.GetSchemaName();
if (!string.IsNullOrEmpty(schema))
modelBuilder.HasDefaultSchema(schema);
}
then in the management portal, I added one item to Configuration\App Settings with Key=MS_TableSchema and Value=X. Then debugged the code again, I realized that ServiceSettingsDictionary.GetSchemaName() returns "X" as I wanted, but again the controllers throw a SqlException with message "The specified schema name "X" either does not exist or you do not have permission to use it." when I use the Query() method.
When a mobile service named 'MSName' is created, a schema with the name 'MSName' is defined in the database, and a user is created with permission to access that schema only. That is the user in the default connection string which the mobile service will use when connecting to the database in the runtime. So if you have a service 'X' and try from it to access the service 'Y', the user it's using will not have permissions to do so.
There are a few options you can do to solve that. One is to find the user for service 'Y' (using one of the SQL server management tools) and grant it access to the schema for 'X'. Another option is to, when creating the context in the mobile service Y not to use the default connection string ("Name=MS_TableConnectionString"), but a connection string which uses the user for 'X'.
Just so that I understand -- you want your two services to access the same set of tables in the same database? Not just using the same database but the same tables?
You can easily reuse the same database but we set each mobile service using it up with a separate schema and permissions to only access that schema so that two services won't inadvertently interfere.
However, it sounds like you want them to access the same tables, right? This means that in addition to changing the schema in your service you also need to set the permissions for the mobile user in the DB to access that schema.
You can get the user using the kudu site under the Environment tab (look for the MS_TableConnectionString connection string):
https://<your service>.scm.azure-mobile.net/Env
And you can set the permissions for that user using the grant command -- you can see an example her:
https://weblogs.asp.net/fredriknormen/database-migration-and-azure-mobile-service-adventure
Hope this helps!
Henrik
I have hosted an Owin WebAPI Server in an Azure Worker Role.
The Owin Authentication middleware seems to use the MachineKey to encrypt and generate Tokens.
This works perfectly when I have only one instance of this role, but as soon as I want to use several instances, the tokens generated by each instance are differents.
This is the same problem as a web farm, Azure automatically solves this for WebRoles using the same .net Machine Key for all instances in Web.config.
But this does not work for Worker Role instances.
Is there a trick to have Azure using the same machine key for all the intsances of a worker Role ?
Seems it would be easier than rewriting code to generate the tokens for Owin.
If your self-hosted application can reference System.Web, then you can use the same MachineKey implementaiton that the Microsoft.Owin.Host.SystemWeb does.
Put the configuration/system.web/machineKey settings in your App.config just like it is in the Web.config.
Reference reference System.Web and add the following class:
public class MachineKeyDataProtector : IDataProtector
{
private readonly string[] purposes;
public MachineKeyDataProtector(params string[] purposes)
{
this.purposes = purposes;
}
public byte[] Protect(byte[] userData)
{
return MachineKey.Protect(userData, this.purposes);
}
public byte[] Unprotect(byte[] protectedData)
{
return MachineKey.Unprotect(protectedData, this.purposes);
}
}
Then set your authentication options using that class:
var authenticationOptions = new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new TicketDataFormat(new MachineKeyDataProtector(
typeof(OAuthBearerAuthenticationMiddleware).Namespace, "Access_Token", "v1")),
AccessTokenProvider = new AuthenticationTokenProvider(),
};