Jul 032014
 

It feels wrong for a client or server to use the “owner” shared secret credentials in an Azure Service Bus connection string. It’s pure evil with 100s or 1000s of Azure Service Bus queue and topic clients sending messages. So how about I supplement the documentation and show how to easily change from using <sharedSecret> to <sharedAccessSignature>?

Step 1: Create some SAS policies

Log into the Azure management portal, click on your Service Bus queue (or topic), and then click Configure. Add one or more policies, choose their respective permissions, and click Save. After saving, the policy name and keys appear under the “shared access key generator” section below. Copy the primary key and move on to step 2.

image

Step 2: Modify your config file

If you’re like me, you like to keep your WCF hosting code free of configuration. If hosted in a console app, the following code is all I use to start a service.

ServiceHost testHost = new ServiceHost(typeof(TestManager));
testHost.Open();

When adding an additional endpoint for NetMessagingBinding, it’s really simple to just add a new endpoint and behavior configuration. The documentation in place today always shows <sharedSecret> being used. This is not a real-world scenario since every client and service should have their own credentials.

To use your new shared access keys, change this:

    <behaviors>
      <endpointBehaviors>
        <behavior name="ServiceBusTokenProvider">
          <transportClientEndpointBehavior>
            <tokenProvider>
              <sharedSecret issuerName="owner" issuerSecret="blAhblAh+Blah/blaH+BLAhblAhBLaHblAHBlahblaH=" />
            </tokenProvider>
          </transportClientEndpointBehavior>
        </behavior>
      </endpointBehaviors>
    </behaviors>

to something like this, using your newly-generated keys:

    <behaviors>
      <endpointBehaviors>
        <behavior name="SASPolicyTokenProvider">
          <transportClientEndpointBehavior>
            <tokenProvider>
              <sharedAccessSignature keyName="ingest-manager" key="SASkey+sAskEY/SASKeysaSKey+SaskeYsaSKEy/SaS=" />
            </tokenProvider>
          </transportClientEndpointBehavior>
        </behavior>
      </endpointBehaviors>
    </behaviors>

And that’s it.

Feb 062014
 

If you’ve been operating an application as an Azure Cloud Service for a year or two, then you are probably due to renew or upgrade your SSL certificate. People move on, and contractor rates go up. You may not be the person that installed the original SSL cert or may not have documentation on how to install a new cert. The process is simple and takes only an hour once you’ve acquired your new certificate.

  1. Downloaded the new certificate from your certificate provider
    Each provider is different in how they will deliver the certificates. GoDaddy will have you select your server type (e.g. IIS6, IIS7, Tomcat, Apache) before downloading the certificate. When you requested the certificate from your provider, you had to use one of these servers to generate the CSR (certificate signing request). You will be receiving a CRT (web server certificate) from your provider. It’s important to choose the right server type so the CRT can be imported. If you’re deploying to Azure, then you’ll probably choose IIS7 like I did. Download the cert files (or zip file) and save it somewhere safe from prying eyes.

    NOTE: You will likely also receive some intermediate certificates. These have much longer lifespans than a 1-2 year SSL certificate. You’ll follow your provider’s instructions to install these later, if necessary.

  2. Complete the certificate request on IIS
    If you received intermediate certificates from your provider, now is the time to do so. This will ensure that you have a full certification path. Follow your provider’s instructions for this. These intermediate certificates have lifespans of up to 10-20 years, so if the thumbprint is the same no action will be necessary. You can check this by double-clicking the certificate and checking the thumbprint on the details tab. Compare that value to what was previously uploaded to Azure under Cloud Services – <Your Cloud Service> – Certificates tab. Any previously uploaded intermediate certificates will appear here as well as your existing SSL certificate.

    In IIS7 on your server, VM, developer workstation, click on Server Certificates. In the Actions pane on the right, click Complete Certificate Request. Browse to find the name of the CRT file you downloaded in the previous step. Type a friendly name like “*.my-domain.com” and click OK. If the import is successful, you’ll see your certificate appear in the list on the Server Certificates screen in IIS.
    image

  3. Export the certificate as a PFX file
    Open MMC and add the snap-in to work with the Local Machine certificate stores appearing as Certificates (Local Computer). Find your certificate in the Personal \ Certificates store and look for the friendly name you entered in the previous step. Right-click on the certificate, choose All Tasks and then Export to open the Certificate Export Wizard. Follow the wizard, choosing Yes, export the private key and Include all certificates in the certification path if possible options. Type a good password and choose a file path to export the PFX file. For future-you’s sake, name the file with a .pfx extension. When the wizard completes, your PFX file will be ready for use.

    Before you leave the certificate manager (MMC), double-click the certificate to open it and copy the thumbprint from the Details tab. You’ll need the thumbprint in later steps.

  4. Upload the PFX and intermediate certificates to Azure
    Now that you have both certificates, navigate to the uploaded to the Azure Management Portal, and click on Cloud Services. Find the desired cloud service in the list, and click on it to select it. Click on the Certificates tab, and then click the Upload button in the bottom menu. Browse to find your PFX file, and type the password. Click the OK button.
    image
  5. Change the service configuration to use the new thumbprint
    Open your application’s solution, and open the ServiceConfiguration.Cloud.cscfg file in the Azure hosting project. Find the existing SSL certificate under <Certificates>. Paste in your new thumbprint, making to sure it’s all uppercase with no spaces. If your thumbprintAlgorithm has changed, change that value in the config file as well.
  6. Deploy your app to Staging
    Now that your certificate is on Azure and your application has been updated, it’s time to deploy to staging, Once your deployed is complete and your staging environment status returns to “Running”, try out the Staging environment using the HTTPS version of the Site URL seen in the Azure Management Portal. Using Chrome, find the certificate information by clicking on the lock symbol in the address bar and then click the Connection tab and then Certificate Information.
    image
    It’s expected that it is complaining at this point because we aren’t using the intended domain name, staging uses <random>.cloudapp.net. Check that the end date, thumbprint, name, and other properties are what you expect to see.
  7. Swap VIPs
    Once you’re satisfied that the Staging environment is a good build and that the certificate is correctly assigned, swap the staging and production environments. When completed (< 30 seconds), check out your application using the HTTPS endpoint and domain name. You should see the lock in the address bar, and make sure to check the properties (e.g. expiration date) again.

That’s it. Party on!

Dec 042010
 

An ACE (Application Control Engine) Appliance is commonly referred to as simply being a load balancer. An ACE appliance provides many more features that are commonly employed. One of the most desirable features is SSL off-loading. When properly configured for offloading, the ACE will serve as a load balancer, and also take on the responsibility of processing all SSL requests made to a web server farm. In this configuration web farm server’s IIS does not need to worry about SSL at all. SSL traffic ends at the ACE, and only unencrypted HTTP traffic exists between the ACE and IIS. This is desirable because it provides a hardware solution to SSL processing and alleviates some load and configuration on the web servers and IIS.

While these ACE features are wonderful, it creates a little headache for WCF servers hosted on IIS behind the ACE. The problem shows itself as one of the following error messages as seen on the client end:

System.ArgumentException: The provided URI scheme ‘http’ is invalid; expected ‘https’. Parameter name: via

System.ServiceModel.ServiceActivationException: The requested service, ‘https://sub.mydomain.com/MyService.svc’ could not be activated. See the server’s diagnostic trace logs for more information.

System.ServiceModel.ProtocolException: The content type text/html; charset=utf-8 of the response message does not match the content type of the binding (application/soap+msbin1). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly.

System.Net.WebException: The remote server returned an error: (500) Internal Server Error.

The clients have to call the service with a URL similar to https://sub.mydomain.com/MyService.svc. The ACE handles the SSL, converts the request to an unencrypted HTTP request and passes it onto the web server. When the request reaches IIS and the WCF dispatcher, a service cannot be found that matches the HTTPS To address in the header because only HTTP service endpoints are being exposed. To make it work, you need to trick WCF into thinking its HTTP services can support transport security without actual transport or message protection. You also have to configure the service to listen on one address (HTTP) and respond to requests from another (HTTPS).

Enter the professionals. Michele Leroux Bustamante, of IDesign, wrote a few important articles in DevProConnections in late 2008 (links at the bottom of this post) that discuss this in detail and provide a solution. I have modified her solution slightly to provide a slightly easier configuration experience.

AssertEncryptionHttpTransportElement.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.ServiceModel.Configuration;
using System.ServiceModel.Channels;

namespace WcfExtensions
{
    public class AssertEncryptionHttpTransportElement : HttpTransportElement
    {
        private bool m_RequireClientCertificate = false;
        ConfigurationPropertyCollection m_PropCol = null;

        public override Type BindingElementType
        {
            get
            {
                return typeof(AssertEncryptionHttpTransportBindingElement);
            }
        }

        protected override TransportBindingElement CreateDefaultBindingElement()
        {
            AssertEncryptionHttpTransportBindingElement retval =
                new AssertEncryptionHttpTransportBindingElement(m_RequireClientCertificate);
            return retval;
        }

        protected override ConfigurationPropertyCollection Properties
        {
            get
            {
                if (m_PropCol == null)
                {
                    m_PropCol = base.Properties;
                    m_PropCol.Add(
			new ConfigurationProperty("requireClientCertificate",
				typeof(bool), false, null, null, ConfigurationPropertyOptions.None));
                }
                return m_PropCol;
            }
        }

        protected override BindingElement CreateBindingElement()
        {
            return new AssertEncryptionHttpTransportBindingElement(this.RequireClientCertificate);
        }

        [ConfigurationProperty("requireClientCertificate", DefaultValue = false)]
        public bool RequireClientCertificate
        {
            get
            {
                bool retval = (bool)base["requireClientCertificate"];
                return retval;
            }
        }
    }
}

AssertEncryptionHttpTransportBindingElement.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Xml;
using System.Net.Security;

namespace WcfExtensions
{
    public class AssertEncryptionHttpTransportBindingElement : HttpTransportBindingElement, ITransportTokenAssertionProvider, IPolicyExportExtension
    {
        private bool m_RequireClientCertificate = false;

        public AssertEncryptionHttpTransportBindingElement() : base() { }

        public AssertEncryptionHttpTransportBindingElement(bool requireClientCertificate)
            : base()
        {
            m_RequireClientCertificate = requireClientCertificate;
        }

        public override BindingElement Clone()
        {
            AssertEncryptionHttpTransportBindingElement retval = new AssertEncryptionHttpTransportBindingElement(m_RequireClientCertificate);
            return retval;
        }

        public override T GetProperty<T>(BindingContext context)
        {
            if (typeof(T) == typeof(ISecurityCapabilities))
            {
                AssertEncryptionSecurityCapabilities retval = new AssertEncryptionSecurityCapabilities();
                return (T)(object)retval;
            }
            else
                return base.GetProperty<T>(context);
        }

        #region ITransportTokenAssertionProvider Members

        public XmlElement GetTransportTokenAssertion()
        {
            string secpolNS0 = "http://schemas.xmlsoap.org/ws/2005/07/securitypolicy";
            //string secpolNS = "http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702";
            XmlDocument xmldoc = new XmlDocument();
            XmlElement TransTokAssert = xmldoc.CreateElement("sp", "HttpsToken", secpolNS0);
            TransTokAssert.SetAttribute("RequireClientCertificate", m_RequireClientCertificate.ToString().ToLower());

            return TransTokAssert;
        }

        #endregion

        public bool RequireClientCertificate
        {
            get
            {
                return this.m_RequireClientCertificate;
            }
            set
            {
                this.m_RequireClientCertificate = value;
            }
        }

        #region IPolicyExportExtension Members

        void IPolicyExportExtension.ExportPolicy(MetadataExporter exporter, PolicyConversionContext context)
        {
            HttpsTransportBindingElement httpsTBE = new HttpsTransportBindingElement();
            httpsTBE.RequireClientCertificate = m_RequireClientCertificate;
            ((IPolicyExportExtension)httpsTBE).ExportPolicy(exporter, context);
        }

        #endregion
    }
}

AssertEncryptionSecurityCapabilities.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using System.Net.Security;

namespace WcfExtensions
{
    internal class AssertEncryptionSecurityCapabilities : ISecurityCapabilities
    {
        #region ISecurityCapabilities Members

        public ProtectionLevel SupportedRequestProtectionLevel
        {
            get { return ProtectionLevel.EncryptAndSign; }
        }

        public ProtectionLevel SupportedResponseProtectionLevel
        {
            get { return ProtectionLevel.EncryptAndSign; }
        }

        public bool SupportsClientAuthentication
        {
            get { return false; }
        }

        public bool SupportsClientWindowsIdentity
        {
            get { return false; }
        }

        public bool SupportsServerAuthentication
        {
            get { return true; }
        }

        #endregion
    }
}

Hosting application configuration file:

<system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

    <extensions>
      <bindingElementExtensions>
        <add name="assertEncryptionHttpTransportElement"
                type="WcfExtensions.AssertEncryptionHttpTransportElement, WcfExtensions" />
      </bindingElementExtensions>
    </extensions>

    <bindings>
      <customBinding>
        <binding name="MyService.customBinding0">
            <binaryMessageEncoding/>

            <!-- Replaces httpsTransport -->
            <assertEncryptionHttpTransportElement/>
        </binding>
      </customBinding>
    </bindings>

    <services>
      <service name="MyServices.MyService">
        <endpoint address="https://sub.mydomain.com/MyService.svc"
                  listenUri="http://myserver.mydomain.local/MyService.svc"
                  binding="customBinding"
                  bindingConfiguration="MyService.customBinding0"
                  contract="MyServices.IMyService" />
      </service>
    </services>
</system.serviceModel>

 

The server name in the “listenUri” must appear exactly as it does on the “You have created a service” page. This is typically the internal name of the server. The “address” must appear as the clients call the service (at the load balancer). The result of this configuration change is WCF responding to an unencrypted request sent to an HTTPS address while listening on an HTTP address. You can read about the internals of this solution in more detail in Michele’s articles referenced below.

You can download the project that compiles to give you the WcfExtensions DLL referenced above.

Resources:

 Posted by at 8:15 pm