Create Secure Service Fabric on Azure Portal
Prerequisites
Before provisioning secure service fabric make sure you already have Azure Tenant Active directory configured and it should contain the following:
- Test user to access Service Fabric Explorer (e.g https://<service fabric name>.<region>.cloudapp.azure.com:19080/Explorer)
(Note: Once you successfully deployed Secure service fabric, make sure certificate (.pfx) is installed on your VM. Once you hit explorer URL in the browser, it will ask for user credential. Provide test user credentials)
- (Client/Native app): note down app id (e.g a9a87e8f-b6a7-4c5b-a5a6-835aee7cb269)
- Server app (Web API): note down app id (e.g e46df745-dad4-42a5-9f32-dac6e0e1255a)
Provisioning Secure Service Fabric
- Go to Azure Portal, click on create a resource, and select Service Fabric Cluster and click on Create button.
- Fill out all the information in Basic function:
- Fill in all details in Cluster Configuration:
- In Security section, for key vault create new or select existing one (this is used for securing service fabric with SSL certificate). You need to get SSL Certificate from CA provider which will require details like CNAME, Subject. This will be different for all environments.
- Once you create a new certificate, it will be shown as -
- The below screen is shown once SSL certificate is created in the key vault.
- Click on Add an Azure Active Directory button and insert tenant id, client app id and server app id. (these values will be captured from newly created Azure active directory (CORP))
e.g tenantID : 28ebb319-1ef1-4724-b85b-ada7546d1d7b
clientappID : 426604fe-0b0b-40f9-bbb6-1a857dc0470b
serverappID : 89ba6268-d231-4f74-a3a1-f88996a3e8ca
- Once you insert all values, click on Ok. It will then show you next Summary section:
- Once all validation is passed click on Create button. It will take approximately 20 minutes to provision Secure Service Fabric.
- Once it is successfully created go to Service fabric overview and click on explorer button.
- As you have already installed server certificate downloaded from Azure Key vault, on hitting explorer URL you will be prompted for User credential.
- Enter username and password which are created in linked Azure Active Directory.
- Go to Key vault and download this certificate (in .pfx) to your machine/VM. (this will be required for installing on your local MMC console to access Service fabric explorer. Without this certificate we cannot access Service fabric explorer (e.g https://<service fabric name>.<region>.cloudapp.azure.com:19080/Explorer))
- Install above downloaded certificate on your machine/VM using MMC console. For that, go to Run>> type MMC>> Microsoft Management console will open.
- Go to File>>Add/Remove Snap-In..
- Select Certificate and click “Add>” button and select “My User account” and then click Finish.
- This will open MMC console. To select above SSL certificate, go to Certificate>>Personal>>Certificate folder and right click certificate folder and select “Import”. Click Next with all default settings. This will install the certificate on your local.
Set HTTPS endpoint in Service Fabric code base
Prerequisites
Before we start, here are the prerequisites for this article. These instructions were written and tested against the following versions of the Service Fabric API, though the code and techniques likely apply to other versions as well.
Add an HTTPS endpoint to the Service and Application manifests
Make sure you have a certificate in your Service Fabric cluster
If you followed the Secure a Service Fabric cluster docs then you already have the management certificate that you can reuse for this purpose. If your cluster is insecure, then go back and fix that first!
Add the certificate to the LocalMachine\My store on your dev machine for debugging
According to the docs, the certificate should be added to CurrentUser\My, CurrentUser\TrustedPeopleand LocalMachine\My This is because the Service Fabric local cluster runs as NETWORK SERVICE and not as your user account. The PowerShell command to import the certificate is
Import-PfxCertificate -Exportable -CertStoreLocation Cert:\LocalMachine\My -FilePath C:\path\to\cert.pfx -Password (Read-Host -AsSecureString -Prompt "Enter Certificate Password")
Update ServiceManifest.xml and ApplicationManifest.xml
ServiceManifest.xml
- <Endpoint Protocol="http" Name="ServiceEndpoint" Type="Input" Port="8521" />
- <Endpoint Protocol="https" Name="ServiceEndpointHttps" Type="Input" Port="443" />
- </Endpoints>
- </Resources>
- </ServiceManifest>
ApplicationManifest.xml
- <ServiceManifestImport>
- <ServiceManifestRef ServiceManifestName="SampleWebApiPkg" ServiceManifestVersion="1.0.0" />
- <ConfigOverrides />
- <Policies>
- <EndpointBindingPolicy EndpointRef="ServiceEndpointHttps" CertificateRef="MyCert" />
- </Policies>
- </ServiceManifestImport>
- <DefaultServices>
- <Service Name="SampleWebApi">
- </StatelessService>
- </Service>
- </DefaultServices>
- <Certificates>
- <EndpointCertificate X509FindValue="<Thumbprint of your certificate>" Name="MyCert" />
- </Certificates>
- ;/ApplicationManifest>
Open the Load Balancer port(s)
Navigate to your Azure Load Balancer (it was created automatically in the same resource group as your Service Fabric cluster) and add two new Load balancing rules and Probes if they don't already exist (refer below table and screenshot).
Probe / LB Rule | Name | Port |
Probe | WebApiHttp | HTTP on port 8521 |
Probe | WebApiHttps | TCP on port 443 |
Update OwinCommunicationListener
Now we've got two service endpoints in our manifest. We're done, right? Unfortunately, if you look at the Diagnostic Events for your service you should see logs similar to this
Timestamp | Event Name | Message |
6:47:57 PM | StatelessRunAsyncCompletion | RunAsync has successfully completed for a stateless service instance |
6:47:57 PM | StatelessRunAsyncInvocation | RunAsync has been invoked for a stateless service instance |
6:47:57 PM | ServiceMessage | Listening on Http://10.0.0.4:8521/ |
6:47:56 PM | ServiceMessage | Starting web server on Http://+:8521/ |
6:47:56 PM | ServiceTypeRegistered | Service host process 4472 register service type |
Interestingly, the this.listeningAddress has "http" hardcoded! We can update this code to pull the protocol from the endpoint definition in the manifest, and while we're at it, let's add some additional logging which will come in handy later. The diff should look like this:
OwinCommunicationListener.cs
- public Task<string> OpenAsync(CancellationToken cancellationToken)
- {
- + this.eventSource.ServiceMessage(this.serviceContext, "Calling OpenAsync on endpoint {0}", this.endpointName);
-
- var serviceEndpoint = this.serviceContext.CodePackageActivationContext.GetEndpoint(this.endpointName);
- var protocol = serviceEndpoint.Protocol;
- int port = serviceEndpoint.Port;
-
- + this.eventSource.ServiceMessage(this.serviceContext, "Found endpoint with protocol '{0}' port '{1}'", protocol, port);
-
- if (this.serviceContext is StatefulServiceContext)
- {
- StatefulServiceContext statefulServiceContext = this.serviceContext as StatefulServiceContext;
-
- this.listeningAddress = string.Format(
- CultureInfo.InvariantCulture,
- - "http://+:{0}/{1}{2}/{3}/{4}",
- + "{0}://+:{1}/{2}{3}/{4}/{5}",
- protocol,
- port,
- string.IsNullOrWhiteSpace(this.appRoot)
- ? string.Empty
- {
- this.listeningAddress = string.Format(
- CultureInfo.InvariantCulture,
- - "http://+:{0}/{1}",
- + "{0}://+:{1}/{2}",
- protocol,
- port,
- string.IsNullOrWhiteSpace(this.appRoot)
- ? string.Empty
- }
- catch (Exception ex)
- {
- - this.eventSource.ServiceMessage(this.serviceContext, "Web server failed to open. " + ex.ToString());
- + this.eventSource.ServiceMessage(this.serviceContext, "Web server for endpoint {0} failed to open. {1}", this.endpointName, ex.ToString());
-
- this.StopWebServer();
-
-
- public Task CloseAsync(CancellationToken cancellationToken)
- {
- - this.eventSource.ServiceMessage(this.serviceContext, "Closing web server");
- + this.eventSource.ServiceMessage(this.serviceContext, "Closing web server for endpoint {0}", this.endpointName);
-
- this.StopWebServer();
-
-
- public void Abort()
- {
- - this.eventSource.ServiceMessage(this.serviceContext, "Aborting web server");
- + this.eventSource.ServiceMessage(this.serviceContext, "Aborting web server for endpoint {0}", this.endpointName);
-
- this.StopWebServer();
- }
Update CreateServiceInstanceListeners()
- protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
- {
-
-
-
-
- var endpoints = Context.CodePackageActivationContext.GetEndpoints()
- .Where(endpoint => endpoint.Protocol == EndpointProtocol.Http || endpoint.Protocol == EndpointProtocol.Https)
- .Select(endpoint => endpoint.Name);
-
-
- return endpoints.Select(endpoint => new ServiceInstanceListener(serviceContext => new OwinCommunicationListener(Startup.ConfigureApp, serviceContext, ServiceEventSource.Current, endpoint, "<App Root Name>"), endpoint));
- }
This creates an endpoint for each HTTP / HTTPS endpoint found in the manifest. Additionally, we name the ServiceInstanceListener the name of the endpoint since by default it has a blank name, and each listener must have a unique name.
Deploy Application on Secure Service Fabric
- Right click on Service and select Publish.
- Select the credentials of Azure subscription and then select Service fabric in the dropdown,
- Click on Publish button which will publish your application to Azure Service Fabric.
- To ensure that the application is successfully deployed, hit app URL in the browser –
https://<service fabric name>.<region>.cloudapp.azure.com/<app root name>/api/values
e.g: https://secure-sf.westeurope.cloudapp.azure.com/drsservice/api/values
Configure Service Fabric in Azure API Management
Prerequisites
Assuming you have already created API Management.
Generate password protected SSL certificate for API management
- You already installed SSL certificate on your Machine/VM. Go to Run>>Type “MMC”.
- It will open MMC console, open primary(server) certificate which you have installed from the downloaded key Vault.
- Go to details tab and click on copy to File button.
- It will open Certificate Export Wizard click on Next button.
- Select radio button Yes, export the private key and click Next.
- Select “Personal Information Exchange – PKCS# 12 (.PFX)” and click Next.
- Check password checkbox and enter some password (e.g Pa$$w0rd1234) and enter confirm password.
- Click Next button and path file path where user wants to create this file (e.g c:/certificate/sslcert.pfx)
- Click on Next and then Finish.
- Above steps will create SSL certificate with password at given location. This certificate will be required for uploading in API management to connect Secure Service fabric through SSL certificate.
Upload SSL certificate on API Management
- Go to Azure portal and search for api management.
- Go to Client certificate section and click Add button.
- Upload your SSL certificate and enter your certificate password.
- Click on Create button. SSL certificate will be uploaded successfully.
Add service fabric API in API Management
- Go to api-management and click on APIs
- Fill in all the details – Display name, Name, Web service URL, URI scheme. Product (if any).
- URI scheme should be HTTP. Because api management url should be called on http protocol. (e.g http://<api-management-name>.azure-api.net)
- Make sure you add HTTPS uri in web service URL, so as to connect your secure service fabric.
- Click on create button.
Add API Management backend for your API using powershell
If you are using self-signed certificates, you will need to disable certificate chain validation in order for API Management to communicate with the backend system, otherwise it will return a 500 error code. To configure this, you can use the New-AzureRmApiManagementBackend (for new back end) or Set-AzureRmApiManagementBackend (for existing back end) PowerShell cmdlets and set the -SkipCertificateChainValidation parameter to True. Following steps are used to set backend -
- Go to windows PowerShell and login to your Azure account using below command
Login-AzureRmAccount
- It will prompt login screen. Pass user credential of your Azure account where api management is already created.
- Run the following command to create API Management backend
- $context = New-AzureRmApiManagementContext -resourcegroup <resource group name of api management>' -servicename '<api-mgt name>'
- New-AzureRmApiManagementBackend -Context $context -Url '<url of secure service fabric>’ -Protocol http -SkipCertificateChainValidation $true
e.g $context = New-AzureRmApiManagementContext -resourcegroup 'Horizon-RG-Dev-Env' -servicename 'dev-env-api-mgt'
New-AzureRmApiManagementBackend -Context $context -Url 'https://secure-service-fabric.westeurope.cloudapp.azure.com/drsservice' -Protocol http -SkipCertificateChainValidation $true
Link SSL certificate to service fabric API in API Management
- Go to API management and select APIs
- Click on Design Tab and click Add Operation button
- This will prompt Frontend screen to add Operation
- Fill details – Name, URL (URL of your action (e.g newservice/api/values)
- Click on Create button which will create your Get action for this API. Similarly you can add all the actions for this service.
- Go to all operations and click on pencil mark in the backend section:
- It will open Backend screen.
- Add details:
- Select target as HTTP(s) endpoint, Gateway credentials should be Client cert and select Client certificate which is uploaded in previous steps.
- Click on Save button
- To ensure certificate is linked to your api, click on Code view on top right corner
- Two entries are important which will ensure setting are configured correct.
- Authentication tag
- Backend tag
- For testing - Now go to postman app and hit api-management url (e.g https://<api-management name>/newservice/api)
Add following headers,
- Ocp-Apim-Subscription-Key : < subscription key of specific product linked with your api in api management>
e.g: Ocp-Apim-Subscription-Key: 5dhj952a88144ec08d6942f74bf5a713
- This will return you response of API through API management URL.
Create client and server app in Azure B2C directory
Prerequisites
- Azure B2C directory should already be created and should have user created. If NOT, create it using this link.
Create Client app (Native app):
- Go to Azure portal and browse your Azure B2C directory
- Go to App registration option click on Add button
- Provide client app name, select Native app as ‘Yes’ –
- Click on Create
Create Server app (WEBAPI app)
- Create client and server Application ids (e.g 425504fe-0b0b-40f9-bbb6-1a857dc0470b)
Changes in client and server applications for jwtToken authentication
Client app(WPF) changes to acquire access token
- String apiEndpoint = "http://cb6deb37-b838-4591-9e31-589c71bcbcf4.cloudapp.net/drsservice/api/values"; // URL of Application Gateway or you can use API management URL.
- Uri myUri = new Uri(e.Uri.AbsoluteUri);
- string code = HttpUtility.ParseQueryString(myUri.Query).Get("code");
- if (!string.IsNullOrEmpty(code))
- {
- string postData = "grant_type=authorization_code&client_id=<Client id from client app created in B2C directory>&scope=https://<b2c directory name>.onmicrosoft.com/<server app name>/user_impersonation offline_access openid&code=" + code + "&redirect_uri=urn:ietf:wg:oauth:2.0:oob&session_state";
- string requestedUrl = "https://login.microsoftonline.com/tenantid/oauth2/v2.0/token?p=B2C_1_signin";
-
- WebRequest request = WebRequest.Create(requestedUrl);
- request.Method = "POST";
- byte[] byteArray = Encoding.UTF8.GetBytes(postData);
- request.ContentType = "application/x-www-form-urlencoded";
- request.ContentLength = byteArray.Length;
- Stream dataStream = request.GetRequestStream();
- dataStream.Write(byteArray, 0, byteArray.Length);
- dataStream.Close();
- WebResponse response = request.GetResponse();
- dataStream = response.GetResponseStream();
- StreamReader reader = new StreamReader(dataStream);
- string responseFromServer = reader.ReadToEnd();
- dynamic result = JsonConvert.DeserializeObject(responseFromServer);
- this.accessToken = result.access_token;
- this.idToken = result.id_token;
- this.refreshToken = result.refresh_token;
-
- SignOutButton.Visibility = Visibility.Visible;
-
- ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
-
- System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
-
- HttpClient client = new HttpClient();
- HttpRequestMessage request1 = new HttpRequestMessage(HttpMethod.Get, apiEndpoint);
-
- request1.Headers.Add("Ocp-Apim-Subscription-Key", "<subscription key of product obtained from Developer portal>");
- request1.Headers.Add("Host", "<host key given in API gateway>");
-
-
- request1.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); /Add
Bearer Token
- HttpResponseMessage response1 = client.SendAsync(request1).Result;
-
response1.StatusCode
Server app changes to access token
Before making these changes make sure you already created service fabric application using visual studio and has stateless Web API service.
- Go to startup.cs and make changes (marked in yellow),
- public static string AadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
- public static string Tenant = ConfigurationManager.AppSettings["ida:Tenant"];
- public static string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];
- public static string SignUpSignInPolicy =
- ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
- public static string DefaultPolicy = SignUpSignInPolicy;
-
-
- public static void ConfigureApp(IAppBuilder appBuilder)
- {
-
- HttpConfiguration config = new HttpConfiguration();
-
-
- config.Routes.MapHttpRoute(
- name: "DefaultApi",
- routeTemplate: "api/{controller}/{id}",
- defaults: new { id = RouteParameter.Optional }
- );
-
- TokenValidationParameters tvps = new TokenValidationParameters
- {
-
- ValidAudience = ClientId,
- AuthenticationType = DefaultPolicy,
-
- };
-
- appBuilder.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
- {
- AuthenticationMode = AuthenticationMode.Active,
-
- AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(String.Format(AadInstance, Tenant, DefaultPolicy))),
- });
-
- appBuilder.UseWebApi(config);
- config.EnableSwagger(c => c.SingleApiVersion("v1", "valueService")).EnableSwaggerUi();
-
- }
- Go to App.config and add following lines keys,
- <appSettings>
- <add key="ida:AadInstance" value="https://login.microsoftonline.com/{0}/v2.0/.well-known/openid-configuration?p={1}" />
- <add key="ida:Tenant" value="<B2C directory name>.onmicrosoft.com"></add>
- <add key="ida:ClientId" value="<server app id>" />
- <add key="ida:SignUpSignInPolicyId" value="<Sign in policy name>" />
- <!-- The following settings is used for requesting access tokens -->
- <add key="api:ReadScope" value="read" />
- <add key="api:WriteScope" value="write" />
- <add key="ida:Audience" value="<App URI in server app of B2C >" />
- </appSettings>
- Go to solution explorer and add this file under App_Start folder,
- Changes in API controller, Go to cs and add Authorize attribute on class,
- [Authorize]
- [ServiceRequestActionFilter]
- public class ValuesController : ApiController
- {
- It should authorize all the request coming from WPF client.
References
- https://matt.kotsenas.com/posts/https-in-service-fabric-web-api
- https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-cluster-security
- https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-service-manifest-resources
- https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-mutual-certificates-for-clients