Search code examples
c#wcfwcf-securitynlog

How can I enable Security in LogReceiverService (NLog)


I have to make a centralized log repository and I decided to mount a WCF service implementing NLog's LogReceiverService (through wsHttpBinding). I followed this topic where I found a working example (there is a working code at bitbucket).

Ok, now the problem: I would like to add some security to this WCF Service, expose it through HTTPS and maybe add an Authentication Token. I have programmed this kind of authentication earlier, so I do know how to do it, it's just I don't know how can I program that within NLog. Should I modify the Class where NLog makes the call to the WCF Method? I just can't picture how to do it. Any ideas about how to achieve this functionality is really appreciated.


Solution

  • Finally I was able to do this.

    Let me tell you I was able to configure the desired behavior :)

    First we configure the server as follows:

    The configuration of System.ServiceModel for the web.config of the WCFService is:

      <system.serviceModel>
        <services>
          <service name="Your.Namespace.Path.To.Your.Service" behaviorConfiguration="SecureBehavior">
            <endpoint binding="wsHttpBinding" bindingConfiguration="SecureBinding" contract="NLog.LogReceiverService.ILogReceiverServer"/>
            <endpoint binding="mexHttpBinding" contract="IMetadataExchange" address="mex"/>
            <host>
              <baseAddresses>
                <add baseAddress="https://your_secure_domain.com/servicePath"/>
              </baseAddresses>
            </host>
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior name="SecureBehavior">
              <serviceDebug includeExceptionDetailInFaults="true"/>
              <serviceMetadata httpsGetEnabled="true"/>
              <serviceCredentials>
                <!--You must set your certificate configuration to make this example work-->
                <serviceCertificate findValue="0726d1969a5c8564e0636f9eec83f92e" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySerialNumber"/>
                <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="AssamblyOf.YourCustom.UsernameValidator.UsernameValidator, AssamblyOf.YourCustom.UsernameValidator"/>
              </serviceCredentials>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <bindings>
          <wsHttpBinding>
            <binding name="SecureBinding" closeTimeout="00:00:20" openTimeout="00:00:20" receiveTimeout="00:00:20" sendTimeout="00:00:20">
              <security mode="TransportWithMessageCredential">
                <message clientCredentialType="UserName"/>
                <transport clientCredentialType="None"/>
              </security>
            </binding>
          </wsHttpBinding>
        </bindings>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
      </system.serviceModel>
    

    The CustomUserNameValidator

    public class UsernameValidator : UserNamePasswordValidator
    {
        private const string UserName = "your_username_here";
        private const string Password = "your_password_here";
    
        public override void Validate(string userName, string password)
        {
            // validate arguments
            if (string.IsNullOrEmpty(userName))
                throw new ArgumentNullException("userName");
            if (string.IsNullOrEmpty(password))
                throw new ArgumentNullException("password");
    
            //
            // Nombre de usuario y contraseñas hardcodeados por seguridad
            //
            if (!userName.Equals(UserName) || !password.Equals(Password))
                throw new SecurityTokenException("Nombre de usuario o contraseña no válidos para consumir este servicio");
        }
    }
    

    then we go to the Client configuration

    First, create a inherited class from LogReceiverWebServiceTarget and I override the method CreateWcfLogReceiverClient, then in that method add the credentials.

    // we assume that this class is created in NLog.CustomExtendedService namespace
    
    [Target("LogReceiverSecureService")]
    public class LogReceiverSecureService : NLog.Targets.LogReceiverWebServiceTarget
    {
        /// <summary>
        /// Gets or sets the UserName of the service when it's authentication is set to UserName
        /// </summary>
        /// <value>The name of the endpoint configuration.</value>
        public string ServiceUsername { get; set; } 
    
        /// <summary>
        /// Gets or sets de Password of the service when it's authentication is set to UserName
        /// </summary>
        public string ServicePassword { get; set; }
    
        /// <summary>
        /// Creates a new instance of WcfLogReceiverClient.
        /// 
        /// We make override over this method to allow the authentication
        /// </summary>
        /// <returns></returns>
        protected override NLog.LogReceiverService.WcfLogReceiverClient CreateWcfLogReceiverClient()
        {
            var client = base.CreateWcfLogReceiverClient();
            if (client.ClientCredentials != null)
            {
                //
                // You could use the config file configuration (this example) or you could hard-code it (if you do not want to expose the credentials)
                //
                client.ClientCredentials.UserName.UserName = this.ServiceUsername;
                client.ClientCredentials.UserName.Password = this.ServicePassword;
            }
            return client;
        }
    }
    

    Then we set up the application's config file

    <system.serviceModel>
        <bindings>
          <wsHttpBinding>
            <binding name="WSHttpBinding_ILogReceiverServer">
              <security mode="TransportWithMessageCredential">
                <message clientCredentialType="UserName" />
                <transport clientCredentialType="None" />
              </security>
            </binding>
          </wsHttpBinding>
        </bindings>
    
        <client>
          <endpoint address="https://your_secure_domain.com/servicePath/Logger.svc" binding="wsHttpBinding"
            bindingConfiguration="WSHttpBinding_ILogReceiverServer" contract="NLog.LogReceiverService.ILogReceiverClient"
            name="WSHttpBinding_ILogReceiverServer" />
        </client>
    
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
      </system.serviceModel>
    

    Finally we configure the NLog.config

      <extensions>
        <add assembly="NLog.CustomExtendedService"  /> <!--Assuming the custom Target was added to this assambly -->
      </extensions>
    
      <targets>
        <target xsi:type="LogReceiverSecureService"
            name="RemoteWcfLogger"
            endpointConfigurationName="WSHttpBinding_ILogReceiverServer"
            endpointAddress="https://your_secure_domain.com/servicePath/Logger.svc"
            ServiceUsername="your_username_here"
            ServicePassword="your_password_here"
            useBinaryEncoding="True"
            clientId="YourApplicationNameOrId"
            includeEventProperties="True">
        </target>
      </targets>
    

    I posted an entire answer at the googlegroup of NLog, so enjoy it https://groups.google.com/d/msg/nlog-users/Xryu61TaZKM/Utbvrr5mwA0J