Query Azure SQL Database from local Azure Function using Managed Identities - azure-active-directory

I want to query an Azure SQL Database from an Azure Function executing on my machine in debug using Managed Identities (i.e. the identity of my user connected to Visual Studio instead of providing UserId and Password in my connection string).
I followed this tutorial on Microsoft documentation so my Azure SQL Server has an AD user as admin which allowed me to give rights (db_datareader) to an Azure AD group I created with my Azure Function Identity and my user in it (and also my Function App deployed in Azure).
If I deploy and run in Azure my Azure Function, it is able to query my database and everything is working fine. But when I run my Azure Function locally, I have the following error :
Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON'.
The code of my function is the following:
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "test")] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
using (var connection = new SqlConnection(Environment.GetEnvironmentVariable("sqlConnectionString")))
{
connection.AccessToken = await (new AzureServiceTokenProvider()).GetAccessTokenAsync("https://database.windows.net");
log.LogInformation($"Access token : {connection.AccessToken}");
try
{
await connection.OpenAsync();
var rows = await connection.QueryAsync<Test>("select top 10 * from TestTable");
return new OkObjectResult(rows);
}
catch (Exception e)
{
throw e;
}
}
}
The code retrieves a token correctly, the error occurs on line await connection.OpenAsync().
If I open the database in Azure Data Studio with the same user than the one connected to Visual Studio (which is member of the AD group with the rights on the database), I can connect and query the database without any issue.
Is it a known issue or am I missing something here ?

After trying your specific scenario, I tested quite a few ways to try and get it to work locally. This didn't work, giving the same error message you're getting.
I discussed it with some people, when a possible solution came up. I tested it: it works!
The main issue in my case was that my subscription (and my user) is a Microsoft account (Outlook). Because of this, you need to specify the tenantId in the GetAccessTokenAsync() call.
Apparently, for managed identities you do not have to specify the tenantId. With a user, it's a good idea to explicitly specify it. In case of a personal MS account, specifying it is mandatory.
My code (sort of):
var tokenProvider = new AzureServiceTokenProvider();
using (var connection = new SqlConnection(CONNECTIONSTRING))
using (var command = new SqlCommand(QUERY, connection))
{
connection.AccessToken = await tokenProvider.GetAccessTokenAsync("https://database.windows.net/", "<YOUR_TENANT_ID>");
await connection.OpenAsync();
var result = (await command.ExecuteScalarAsync()).ToString();
return new OkObjectResult(result);
}
This solution has been tested and works both when specifying the tenantId (or Directory ID, the tenant's GUID) and the 'onmicrosoft'-name (xxx.onmicrosoft.com).

Is your local machine's IP Address white-listed?
https://learn.microsoft.com/en-us/azure/sql-database/sql-database-firewall-configure

Related

How To Access logs of the HTTP Requests in IIS or Query logs sent from Entity Framework to SQL Server?

I created a simple website with a login page that requires to authenticate only with an email address.
I did not store any login logs nor I enabled any logging functionalities on IIS.
Now, people are asking for the list of emails who logged in the past two days.
I used EntityFramework to connect to the database. This is the authentication method:
public static bool Authenticate(string email)
{
using (var db = new DatabaseEntities())
{
var user = db.Users.Where(x => x.Email.ToLower() == email.ToLower()).FirstOrDefault();
if (user != null)
return true;
}
return false;
}
I have tired this in SQL but nothing relevant will show:
SELECT t.[text]
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
Is there a way to access the logs in SQL for every time this line of code was executed?
var user = db.Users.Where(x => x.Email.ToLower() == email.ToLower()).FirstOrDefault();
Perhaps there is a way to access the logs for every post request sent to the IIS server?
If you didn't already enable a logging feature in IIS or SQL Server then there is nothing you can do retroactively unfortunately.
Moving forward you can pretty easily enable out of the box features of SQL Server to log User connections via either SQL Server Audit or a Logon Trigger to store the log to a table.
This article lists a few other methodologies (in addition to what I mentioned above) such as using a Trace.
Unfortunately, the queries sent by the application to the database cannot be viewed in IIS. These query records can only be viewed through SQL Server.
The Logging module in IIS records the communication between the client and the server, which includes the URL status code and time.
Even the Fail Request Tracing module cannot capture SQL Server query records, but only the communication records with SQL Server, such as request response time and success and failure.

Connection String generated by VS doesn't work, and any variation of it

I'm deploying a web application using IIS and ASP.NET CORE.
I setup a "appsetting.json" file that include a connection string to sql server.
So far, any variation I've tried didn't work.
The strange thing about it, is that it works perfectly on a my local machine, but when I deploy it and send an HTTPPost, log file says there's an error using connection to database on server.
Well, I tried all variations I could think of.
Current connection string generated by Visual Studio is :
Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Swap;Integrated
Security=True;Connect Timeout=30;Encrypt=False;
TrustServerCertificate=False;
ApplicationIntent=ReadWrite;MultiSubnetFailover=False
I've tried changing the "Integrated Security" to False.
I've tried to replace it with User ID and password (of "sa" user).
I've tried adding a "Initial Catalog" property and set it to my database.
I'm preety sure the startup of the app is fine, because when I try to send a GET request to the main page, everything seems fine.
When I send a POST request and asks the DBcontext to Add and SaveChanges, it shows this error :
fail: Microsoft.EntityFrameworkCore.Database.Connection[20004]
An error occurred using the connection to database 'Swap' on server
'(localdb)\MSSQLLocalDB'.
This is the function that I tried to send a HTTPPost request to:
byte[] j = new byte[(int)Request.ContentLength];
Request.Body.Read(j, 0, (int)Request.ContentLength);
string str = Encoding.ASCII.GetString(j);
TokenSet tokenSet = new TokenSet {Token = str };
sqlTokensetData.Add<TokenSet>(tokenSet);
sqlTokensetData.SaveChanges();
HttpClient notificationSender = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,
"https://fcm.googleapis.com/fcm/send");
request.Headers.TryAddWithoutValidation("Authorization", "key="
<somekey>);
request.Headers.TryAddWithoutValidation("Sender", "id=<id>");
Message pushNotification = new Message(new string[] { str }, "Success!"
<somestring>);
request.Content = new
StringContent(JsonConvert.SerializeObject(pushNotification)
,Encoding.UTF8,"application/json");
HttpResponseMessage output = await notificationSender.SendAsync(request);
Log file error:
https://anotepad.com/notes/9a8jxa
I hope that the "str" string will be inserted to the database.
Thank you.
Well, Basicly, Thanks to the comments above I've managed to solve it.
What you need to do if you come by the same error:
1.Download SQL Server Express -
https://www.microsoft.com/en-us/sql-server/sql-server-editions-express as said above.
and configure the SQL Server however you see fit.
2.Configure sa user, set password and enable it.
3. If SQL Authentication doesn't work, then in Microsoft SQL Server Management Studio -> Right-click the server -> Properties -> Server Authentication -> Change to SQL Server and Windows Authentication
4. Change connection string to :
Data Source=;Initial Catalog=User ID=sa;
Password=;
Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;
ApplicationIntent=ReadWrite;MultiSubnetFailover=False
It worked for me.

SQL Server 2016 Always Encrypted Timeout at Published IIS

I Have strange problem when i tried to publish my asp.net mvc application to my local (pc) iis with "Always Encrypted" Enabled.
My application keep timeout when i tried to access database using EF6 at local IIS (not express) :
But if i tried to access & debug my asp.net mvc app using Visual Studio 2017, database with 'always encrypted enabled' can be accessed perfectly without timeout.
And also i can access it with SQL Management Studio without problem.
Both (SMSS & ASP.NET web config) using this configuration.
Column Encryption Setting=enabled;
Note : I'm using ASP.NET MVC 5 & EF 6, SQL Server 2016 Developer Edition.
Sorry for my bad english.
UPDATED :
I have tried using .NET Framework Data Provider to see if there's any clue that'll help me solving this issue, Using following code :
var context = new TestDevEntities();
StringBuilder sb = new StringBuilder();
string connectionString = context.Database.Connection.ConnectionString;
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
using (SqlCommand cmd = new SqlCommand(#"SELECT [id],[name],[CCno] FROM [TestDev].[dbo].[testEncCol]", connection, null, SqlCommandColumnEncryptionSetting.ResultSetOnly))
{
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
sb.Append(reader[2] + ";");
}
}
}
}
}
above code show me this error :
Now, with this kind of error i know i exactly i must do :)
Change the identity of application pool to the 'user' who previously generated the certificate.
Export currentuser cert (used by always encrypted) and import to the user that you want to use as application pool identity.
Now its worked!
EF should throw some kind of error as clear as .NET Data Providers do, instead of timeout failure that really confuse me #_#
UPDATED (1) :
Now the question is how to use it (Certificate) with default ApplicationPoolIdentity instead of custom account?
UPDATED (2) :
I have done what jakub suggest, but still no luck.
Thanks
One way (could be the only way) to use the DefaultAppPool identity instead of a custom (user) account is to store the certificate in the Local Machine certificate store (not Current User).
Once you create a certificate in the Local Machine certificate store, you need to grant DefaultAppPool access to the cert. You can do that using Microsoft Management Console (and the plugin for Local Computer certs):
Right click on the cert, select All Tasks > Manage Private Keys.
Click Add.
Set location to your computer (not your domain).
Enter IIS AppPool\DefaultAppPool as the object name.
Click OK twice.

Bluemix connecting to external SQL Server Database

I have an application built using the ASP.NET 5 runtime - I would like to connect it to an on-premise SQL Server Database.
After some research I've already created the user-provided service with the relevant credentials, however I am unsure what to do next (i.e. writing the necessary code connecting it in ASP.NET).
Some further googling suggests to use Secure Gateway? but is this the only way? the cloud I am working on is dedicated and does not have the Secure Gateway service. Is there a workaround for this?
(Note: The application I'm working on is based on the ASP.NET-Cloudant example on IBM Github, if that helps).
https://github.com/IBM-Bluemix/asp.net5-cloudant
The Secure Gateway service isn't required as long as the Bluemix environment can connect to the server running SQL Server. This might require your firewall rules to be a little more relaxed on the SQL Server, or you can contact IBM to create a secure tunnel as Hobert suggested in his answer.
Aside from that issue, if you're planning to use Entity Framework to connect to your SQL Server, it should work similar to the existing tutorials on the asp.net site. The only difference will be in how you access the environment variables to create your connection string.
Assuming that you created your user-provided service with a command similar to this:
cf cups my-sql-server -p '{"server":"127.0.0.1","database":"MyDB","user":"sa","password":"my-password"}'
Your connection string in your Startup.cs file's ConfigureServices method would then look something like this:
string vcapServices = Environment.GetEnvironmentVariable("VCAP_SERVICES");
string connection = "";
if (vcapServices != null)
{
string myServiceName = "my-sql-server";
JArray userServices = (JArray)JObject.Parse(vcapServices)?["user-provided"];
dynamic creds = ((dynamic)userServices
.FirstOrDefault(m => ((dynamic)m).name == myServiceName))?.credentials;
connection = string.Format(#"Server={0};Database={1};User Id={2}; Password={3};",
creds.server, creds.database, creds.user, creds.password);
}
Update
The cloudant boilerplate that you're modifying doesn't use Entity Framework because cloudant is a NoSQL database, so it's a bit different than connecting to SQL Server. The reason that the boilerplate calls .Configure to register the creds class is that it needs to use that class from another location, but when using Entity Framework you simply need to use the credentials when adding EF to the services in the Startup.cs file so you don't need to use .Configure<creds>.
If you follow the guide here, the only part you'll need to change is the line var connection = #"Server=(localdb)\mssqllocaldb;Database=EFGetStarted.AspNet5.NewDb;Trusted_Connection=True;"; replacing it with the code above to create the connection string instead of hard-coding it like they did in the example tutorial.
Eventually, your ConfigureServices method should look something like this, assuming your DbContext class is named BloggingContext like in the example:
public void ConfigureServices(IServiceCollection services)
{
string vcapServices = Environment.GetEnvironmentVariable("VCAP_SERVICES");
string connection = "";
if (vcapServices != null)
{
string myServiceName = "my-sql-server";
JArray userServices = (JArray)JObject.Parse(vcapServices)?["user-provided"];
dynamic creds = ((dynamic)userServices
.FirstOrDefault(m => ((dynamic)m).name == myServiceName))?.credentials;
connection = string.Format(#"Server={0};Database={1};User Id={2}; Password={3};",
creds.server, creds.database, creds.user, creds.password);
}
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<BloggingContext>(options => options.UseSqlServer(connection));
services.AddMvc();
}
And then your Startup method would be simplified to:
public Startup(IHostingEnvironment env)
{
var configBuilder = new ConfigurationBuilder()
.AddJsonFile("config.json", optional: true);
Configuration = configBuilder.Build();
}
Excellent!
In Public Bluemix Regions, you would create and use the Secure Gateway Service to access the On-Premise MS SQL Server DB.
In your case, as a Bluemix Dedicated client, you should engage your IBM Bluemix Administration Team so they can work with your Network Team to create a tunnel between the Dedicated Bluemix Region and your On-Premise MS SQL DB Server.
If you want to connect directly from your Asp.Net Core application to a SQL Server you actually don't need a Secure Gateway.
For example, if you want to use a SQL Azure as your Database you can simply add the given connection string in your application.
But, for pratical and security reasons, you should create a User-Provided Service to store your credentials (and not use statically in your code), and pull your credentials from you VCAP_SERVICES simply adding SteelToe to your Cconfiguration Builder. (Instead of use parse the configuration manually with JObjects and JArrays)
Step-by-step:
In your CloudFoundry console create a User-Provided Service using a Json:
cf cups MySqlServerCredentials -p '{"server":"tcp:example.database.windows.net,1433", "database":"MyExampleDatabase", "user":"admin", "password":"password"}'
Obs.: If you use Windows console/Powershell you should escape you double quotes in Json like:
'{\"server\":\"myserver\",\"database\":\"mydatabase\",\"user\":\"admin\",\"password\":\"password\"}'
After you have created your User-Provided Service you should Connect this Service with your application in Bluemix Console.
Then, In your application add the reference to SteelToe CloudFoundry Steeltoe.Extensions.Configuration.CloudFoundry
In your Startup class add:
using Steeltoe.Extensions.Configuration;
...
var builder = new ConfigurationBuilder()
.SetBasePath(basePath)
.AddJsonFile("appsettings.json")
.AddCloudFoundry();
var config = builder.Build();
Finally, to access your configurations just use:
var mySqlName = config["vcap:services:user-provided:0:name"];
var database = config["vcap:services:user-provided:0:credentials:database"];
var server = config["vcap:services:user-provided:0:credentials:server"];
var password = config["vcap:services:user-provided:0:credentials:password"];
var user = config["vcap:services:user-provided:0:credentials:user"];
OBS.: If you're using Azure, remember to configure your Database firewall to accept the IP of your Bluemis application, but as default Bluemix don't give a static IP address you have some options:
Buy a Bluemix Statica service to you application (expensive)
Update firewall rules with REST put with the current IP of application (workaroud)
Open your Azure Database Firewall to a broad range of IPs. (Just DON'T)
More info about SteelToe CloudFoundry in :
https://github.com/SteeltoeOSS/Configuration/tree/master/src/Steeltoe.Extensions.Configuration.CloudFoundry

add soap webservice to IIS

I made a web service that connects to a database hosted in SQL Server 2008 R2 on my local computer, and makes some operations on it.
This is the connection string in the web service:
conn = new SqlConnection("Data Source=amir-pc\\SQLEXPRESS;User Id=sa;Password=1234; Initial Catalog=Election;Integrated Security=True");
It works well and successfully accesses the database and runs right.
Now I want to add this web service to IIS.
I successfully added it on Windows 7 to the IIS and can run it from the browser.
localhost/election_service/service.asmx
but when I tried to call a function that checks the connection, it failed, and I don't know why.
This is the function:
[WebMethod]
public string Check_conn()
{
try
{
conn.Open();
conn.Close();
return "ok";
}
catch
{
return "failed";
}
}
Is there any modification I must to do to be able to access the database?
Hi can you give more information please try change you code for know what is exception is raising.
public string dbcheck()
{
try
{
conn.Open();
conn.Close();
return "ok";
}
catch (Exception ex)
{
return ex.Message;
}
}
Hi, then .. is a login issue:Open IIS Open Application Pool Select your current application pool are you current working (by default is DefaultAppPool) Go to Advanced Options, and find Identity and change to (NT AUTHORITY\Network Service) Then you has been changed your application user, now you need to add permision to network service to database Open SQlMananger, then go to Node Security, rigth click and properties go to NT AUTHORITY\Network Service go to User mapping and add the database you want to allow access.

Resources