Running Pact provider tests against deployed HTTPS provider - pact

I want to set up Pact contract testing for my clients and APIs. My APIs cannot be run locally, so I want to be able to run provider tests against deployed staging version of the API before deploying to production.
Most examples I've seen online of provider tests have used localhost.
When trying to run a provider test against my deployed HTTPS endpoint, the test fails with read server hello A: unknown protocol.
Is the HTTPS protocol not supported, or am I missing something?
func TestTenantProvider(t *testing.T) {
// Create Pact connecting to local Daemon
pact := &dsl.Pact{
Consumer: "TenantConsumer",
Provider: "TenantProvider",
}
// Verify the Provider with local Pact Files
pact.VerifyProvider(t, types.VerifyRequest{
ProviderBaseURL: "https://my-staging-endpoint.com",
PactURLs: []string{filepath.ToSlash(fmt.Sprintf("%s/tenantconsumer-tenantprovider.json", pactDir))},
})
}
Using the pact-provider-verifier cmd line tool works fine.

It should support verification over HTTPS, but currently I don't believe we support SSL with self-signed certificates easily in Pact Go (see https://github.com/pact-foundation/pact-go/issues/66).
In theory, you should be able to set an environment variable to your SSL configuration [1] as per below:
To connect to a Pact Broker that uses custom SSL cerificates, set the environment variable $SSL_CERT_FILE or $SSL_CERT_DIR to a path that contains the appropriate certificate.
[1] https://github.com/pact-foundation/pact-ruby-standalone/releases

Related

How to setup https when developing localy with webpack and hosting on Azure in Docker container running ASP.NET Core

I am hosting on Azure and have it configured to only allow https. The backend is running ASP .NET Core in a Linux container. The webserver (Kestrel) is running without https enabled. I've configured Azure TLS/SSL settings to force https, so when users connect from the public internet, they have to use https. I have a cert that is signed by a cert authority and it's configured in the Azure App Service -> TLS/SSL -> Bindings settings.
However in my local development environment I've been running webpack using http. So when I test I connect to localhost:8080 and this is redirected to localhost:8085 by webpack. localhost:8085 is the port Kestrel is listening on. I've decided I want to develop locally using https so that my environment mimics the production environment closely. To this I've started the webpack-dev-server with the --https command line option, and ammended my redirects in my webpack.config.js
For example:
'/api/*': {
target: 'https://localhost:' + (process.env.SERVER_PROXY_PORT || "8085"),
changeOrigin: true,
secure: false
},
This redirects https requests to port 8085.
I've created a self-signed cert for use by Kestrel when developing locally. I modified my code to use this certificate as shown below:
let configure_host (settings_file : string) (builder : IWebHostBuilder) =
//turns out if you pass an anonymous function to a function that expects an Action<...> or
//Func<...> the type inference will work out the inner types....so you don't need to specify them.
builder.ConfigureAppConfiguration((fun ctx config_builder ->
config_builder
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables()
.AddJsonFile(settings_file, false, true)
.Build() |> ignore))
.ConfigureKestrel(fun ctx opt ->
eprintfn "JWTIssuer = %A" ctx.Configuration.["JWTIssuer"]
eprintfn "CertificateFilename = %A" ctx.Configuration.["CertificateFilename"]
let certificate_file = (ctx.Configuration.["CertificateFilename"])
let certificate_password = (ctx.Configuration.["CertificatePassword"])
let certificate = new X509Certificate2(certificate_file, certificate_password)
opt.AddServerHeader <- false
opt.Listen(IPAddress.Loopback, 8085, (fun opt -> opt.UseHttps(certificate) |> ignore)))
.UseUrls("https://localhost:8085") |> ignore
builder
This all works, and I can connect to webpack locally and it redirects the request to the webserver using https. The browser complains that the cert is insecure because it's self-signed but that was expected.
My question is how should this be setup in the production environment. I don't want to be running the container on azure with the certificates I created locally embeded in the image. In my production environment should I be configuring Kestrel, as I have done with the localhost code, to use the cert in loaded into Azure (as mentioned in the 1st paragraph)? Or is simply binding it to the domain using the portal and forcing https via the Web UI enough?
On Azure, If you have the PFX certificate you can choose to upload the certificate:
see this image
However, this certificate needs to come from a trusted certificate authority.
If the URL is a subdomain, you can choose a Free App Service Managed Certificate.
After, that all you need to do is enable https only in the portal.
If its a naked domain and you really need the certificate to be free, you can get a certificate from sslforfree.com. sslforfree will give you the .cer file and the private key you will need to generate a pfx.

How to specify server certificate for a self-contained Kestrel/Blazor application deployed on a production server?

I created a Blazor application to be run using Kestrel (.Net core 3.1).
Import NuGet package Microsoft.AspNetCore.Authentication.Negotiate
Add the following code in ConfigureService() in Startup.cs.
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
services.AddSingleton<ValidateAuthentication>();
Add the following code in Configure() in Startup.cs. They are added between app.UseRouting(); and app.UseEndpoints(...;
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<ValidateAuthentication>();
Add the class
internal class ValidateAuthentication : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (context.User.Identity.IsAuthenticated)
await next(context);
else
await context.ChallengeAsync();
}
}
Add the following code in the Program.cs (To make sure the remote machine can access the website)
webBuilder.UseUrls(new string[] { "https://0.0.0.0:5001", "http://0.0.0.0:5000" });
I published (as self hosted) the application to a local folder and it works fine on my PC. Running .\myApp.exe and then browsing http://localhost.5000 will redirect to https://localhost:5001 and show the page.
Then the published folder was copied to a Windows 2012 Server. However, running the application gets error:
PS C:\Website\Portal> .\MyApp.exe
crit: Microsoft.AspNetCore.Server.Kestrel[0]
Unable to start Kestrel.
System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the defau
lt developer certificate could not be found or is out of date.
To generate a developer certificate run 'dotnet dev-certs https'. To trust the certificate (Windows and macOS only) run
'dotnet dev-certs https --trust'.
For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
at Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(ListenOptions listenOptions, Action`1 configure
Options)
at Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(ListenOptions listenOptions)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext con
text)
How to specify a server certificate for the application?
This Microsoft Docs article explains how to startup an ASP.Net Core app with certificates when it ships as a Docker container. You basically set ennvironment variables, e.g.
ASPNETCORE_URLS
ASPNETCORE_HTTPS_PORT
ASPNETCORE_Kestrel__Certificates__Default__Path
ASPNETCORE_Kestrel__Certificates__Default__Password
I guess you may do the same on the host directly but keep in mind that those settings won't be isolated and may affect other apps. Also note that you would be storing a password in an environment variable. I also found this article explaining how to configure Kestrel via the launch settings json.
I came across this question by accident. Solution is not tested. Just replying since there is no better suggestion yet.

Exception using Azure Managed Service Identity across tenants

I'm building an Azure web app for a client that will be provisioned into many other directories for their customers. This app will call a web API in my client's directory, which will then call back to another web API in the customer's directory. Something like this:
Other Customer AAD1 --------- My client AAD2
App --------------------------------> Web API 2
Web API 1 <-------------------------- Web API 2
We have been able to get the first call to work. This requires a corresponding App Registation for Web API 2 in AAD1. We figure that we could get the callback to work by following the same pattern, with a registration for Web API1 in AAD2. However, that might be a LOT of these 'proxy' registration in my client's AAD, so we're looking at alternatives.
We are exploring using Managed Service Identity, which we think will allow us to get tokens that are valid for resources in other tenants. If there's a better way, I'm certainly interested in knowing about it.
I've followed the code example from here using the Microsoft.Azure.Services.AppAuthentication library: https://learn.microsoft.com/en-us/azure/app-service/app-service-managed-service-identity#obtaining-tokens-for-azure-resources
// In Web API 2
using Microsoft.Azure.Services.AppAuthentication;
// ...
var azureServiceTokenProvider = new AzureServiceTokenProvider();
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync(
"https://<App ID URI for Web API1>");
Web API2 is configured to have a Managed Service Identity.
I'm currently running this on my local machine, and I've installed Azure CLI and I'm logged in. I've tried 'az account get-access-token', and I get a valid token.
When Web API2 tries to get the token to be able to call Web API1, I get an exception:
Parameters: Connectionstring: [No connection string specified], Resource: , Authority: . Exception Message: Tried the following 2 methods to get an access token, but none of them worked.
Parameters: Connectionstring: [No connection string specified], Resource: , Authority: . Exception Message: Tried to get token using Managed Service Identity. Unable to connect to the Managed Service Identity (MSI) endpoint. Please check that you are running on an Azure resource that has MSI setup.
Parameters: Connectionstring: [No connection string specified], Resource: , Authority: . Exception Message: Tried to get token using Azure CLI. Access token could not be acquired. ERROR: Get Token request returned http error: 400 and server response: {"error":"invalid_grant","error_description":"AADSTS65001: The user or administrator has not consented to use the application with ID '04b07795-8ddb-461a-bbee-02f9e1bf7b46' named 'Web API 1'. Send an interactive authorization request for this user and resource.\r\nTrace ID: f5bb0d4d-6f92-4fdd-81b7-e82a78720a00\r\nCorrelation ID: 04f92114-8d9d-40c6-b292-965168d6a919\r\nTimestamp: 2017-10-19 16:39:22Z","error_codes":[65001],"timestamp":"2017-10-19 16:39:22Z","trace_id":"f5bb0d4d-6f92-4fdd-81b7-e82a78720a00","correlation_id":"04f92114-8d9d-40c6-b292-965168d6a919"}
What's interesting is that there's no application with ID '04b07795-8ddb-461a-bbee-02f9e1bf7b46' in either AAD1 or AAD2. Is this a known Azure app? I thought that it might be the Service Management API, but I'm not sure.
In any case, I'm not sure of the proper way to grant permission. I've tried building different content URLs like this into my browser, but none of them seem to have done the trick:
https://login.microsoftonline.com/(AAD1 ID)/adminconsent
?client_id=(App ID)
&redirect_uri=https://localhost:44341
&resource=(App ID URI for Web API1)
&prompt=admin_consent
https://login.microsoftonline.com/(AAD1 ID)/adminconsent
?client_id=04b07795-8ddb-461a-bbee-02f9e1bf7b46
&redirect_uri=https://localhost:44341
&resource=(App ID URI for Web API1)
&prompt=admin_consent
(This last one tells me that the reply URL is incorrect; since it's not one of my apps, I can't find the reply URL)
Note that the tenant is AAD1.
Am I missing something, or am I not using this feature correctly?
Thanks in advance.
AzureServiceTokenProvider uses Azure CLI (among other options) for local development. For a scenario where a service calls an Azure Service, this works using the developer identity from Azure CLI, since Azure services allow access to both users and applications.
For a scenario where a service calls another custom service (like your scenario), you need to use a service principal for local development. For this, you have two options:
Login to Azure CLI using a service principal.
First, create a service principal for local development
https://learn.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest
Then login to Azure CLI using it.
az login --service-principal -u 25922285-eab9-4262-ba61-8083533a929b --password <<pwd>> --tenant 72f988bf-86f1-41af-91ab-2d7cd011db47 --allow-no-subscriptions
Use the --allow-no-subscriptions argument since this service principal may not have access to any subscription.
Now, AzureServiceTokenProvider will get a token using this service principal for local development.
Specify service principal details in an environment variable. AzureServiceTokenProvider will use the specified service principal for local development. Please see the section Running the application using a service principal in local development environment in this sample on how to do that. https://github.com/Azure-Samples/app-service-msi-keyvault-dotnet
Note: Ths is only for local development. AzureServiceTokenProvider will use MSI when deployed to App Service.

ASP.NET Core MVC Health Check failing

I try to run a ASP.NET Core MVC Web application on the Swisscom Appcloud. But when I start the Application I get following Error-Message in the Console:
2017-01-24 14:29:53 [CELL/0] ERR Timed out after 1m0s: health check never passed.
Its looks like the Appcloud cannot check the Health of my Application. Do I need to install a Nuget-Package or something else to get this up and running?
Thanks for your effort
By default, Cloud Foundry makes a health check by trying to connect to the port which the application is exposing.
If your application is not exposing ANY port (e.g., it's not a web service with APIs and so on), then you should add the health-check-type attribute to none, as described here.
If after that you still get errors, then I suggest you to find where your application is listening to a given port. In Cloud Foundry you must listen to $PORT, which is a environment variable. You can check an example of that here.
As gsmachado has already mentioned, you must listen to a specific port.
The .NET Core buildpack configures the app web server automatically so you don’t have to handle this yourself. But you have to prepare your app in a way that allows the buildpack to deliver this information via the command line to your app.
The buildpack will start your app with the following command:
$ dotnet run --server.urls http://0.0.0.0:${PORT}
Therefore you have to add the command line as a configuration provider and then add the UseConfiguration extension to pass the configuration to the WebHostBuilder
e.g.:
var config = new ConfigurationBuilder()
.AddCommandLine(args)
.Build();
var host = new WebHostBuilder()
.UseKestrel()
.UseConfiguration(config)
.UseStartup<Startup>()
.Build();

AspNet.Security.OpenIdConnect.Server (ASP.NET vNext) Authority Configuration in Mixed http/https Environments

I am using Visual Studio 2015 Enterprise and ASP.NET vNext Beta8 to build an endpoint that both issues and consumes JWT tokens as described in detail here. As explained in that article the endpoint uses AspNet.Security.OpenIdConnect.Server (AKA OIDC) to do the heavy lifting.
While standing this prototype up in our internal development environment we have encountered a problem using it with a load balancer. In particular, we think it has to do with the "Authority" setting on app.UseJwtBearerAuthentication and our peculiar mix of http/https. With our load balanced environment, any attempt to call a REST method using the token yields this exception:
WebException: The remote name could not be resolved: 'devapi.contoso.com.well-known'
HttpRequestException: An error occurred while sending the request.
IOException: IDX10804: Unable to retrieve document from: 'https://devapi.contoso.com.well-known/openid-configuration'.
Consider the following steps to reproduce (this is for prototyping and should not be considered production worthy):
We created a beta8 prototype using OIDC as described here.
We deployed the project to 2 identically configured IIS 8.5 servers running on Server 2012 R2. The IIS servers host a beta8 site called "API" with bindings to port 80 and 443 for the host name "devapi.contoso.com" (sanitized for purposes of this post) on all available IP addresses.
Both IIS servers have a host entry that point to themselves:
127.0.0.1 devapi.contoso.com
Our network admin has bound a * certificate (*.contoso.com) with our Kemp load balancer and configured the DNS entry for https://devapi.contoso.com to resolve to the load balancer.
Now this is important, the load balancer has also been configured to proxy https traffic to the IIS servers using http (not, repeat, not on https). It has been explained to me that this is standard operating procedure for our company because they only have to install the certificate in one place. We're not sure why our network admin bound 443 in IIS since it, in theory, never receives any traffic on this port.
We make a secure post via https to https://devapi.contoso.com/authorize/v1 to fetch a token, which works fine (the details of how to make this post are here ):
{
"sub": "todo",
"iss": "https://devapi.contoso.com/",
"aud": "https://devapi.contoso.com/",
"exp": 1446158373,
"nbf": 1446154773
}
We then use this token in another secure get via https to https://devapi.contoso.com/values/v1/5.
OpenIdConnect.OpenIdConnectConfigurationRetriever throws the exception:
WebException: The remote name could not be resolved: 'devapi.contoso.com.well-known'
HttpRequestException: An error occurred while sending the request.
IOException: IDX10804: Unable to retrieve document from: 'https://devapi.contoso.com.well-known/openid-configuration'.
We think this is happening because OIDC is attempting to consult the host specified in "options.Authority", which we set at startup time to https://devapi.contoso.com/. Further we speculate that because our environment has been configured to translate https traffic to non https traffic between the load balancer and IIS something is going wrong when the framework tries to resolve https://devapi.contoso.com/. We have tried many configuration changes including even pointing the authority to non-secure http://devapi.contoso.com to no avail.
Any assistance in helping us understand this problem would be greatly appreciated.
#Pinpoint was right. This exception was caused by the OIDC configuration code path that allows IdentityModel to initiate non-HTTPS calls. In particular the code sample we were using was sensitive to missing trailing slash in the authority URI. Here is a code fragment that uses the Uri class to combine paths in a reliable way, regardless of whether the Authority URI has a trailing slash:
public void Configure(IApplicationBuilder app, IOptions<AppSettings> appSettings)
{
.
.
.
// Add a new middleware validating access tokens issued by the OIDC server.
app.UseJwtBearerAuthentication
(
options =>
{
options.AuthenticationScheme = JwtBearerDefaults.AuthenticationScheme ;
options.AutomaticAuthentication = false ;
options.Authority = new Uri(appSettings.Value.AuthAuthority).ToString() ;
options.Audience = new Uri(appSettings.Value.AuthAuthority).ToString() ;
// Allow IdentityModel to use HTTP
options.ConfigurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>
(
metadataAddress : new Uri(new Uri(options.Authority), ".well-known/openid-configuration").ToString(),
configRetriever : new OpenIdConnectConfigurationRetriever() ,
docRetriever : new HttpDocumentRetriever { RequireHttps = false }
);
}
);
.
.
.
}
In this example we're pulling in the Authority URI from config.json via "appSettings.Value.AuthAuthority" and then sanitizing/combining it using the Uri class.

Resources