23 Replies Latest reply on Sep 8, 2016 8:02 AM by Dries.Verhees

    IIS connecting to AF - Windows authentication

    Dries.Verhees

      Hi,

       

      I have an IIS application which connects to AF.

      During the first call, the following code is executed:

      PISystems piSystems = new PISystems();
      PISystem piSystem = piSystems["AFSERVERNAME"];
      piSystem.Connect();
      AFDatabases afDatabases = piSystem.Databases;
      AFDatabase afDatabase = afDatabases["AFDATABASENAME"]; // Result stored in a static variable, therefore shared between requests/users
      

      This code is executed only once, the afDatabase object is stored in a static variable. All consecutive calls are using the static variable.

      Is this a safe approach? Especially when multiple users are connecting to IIS. IIS is configured with pass-through authentication, as shown below.

      iis_auth.png

      Note that different users will have access to different AF elements, or some users may not have access to the AF database.

      I'm checking the connection using piSystem.ConnectionInfo.IsConnected for each incoming request.

      Is this a safe approach? Or should I recall Connect() for each incoming request?

        • Re: IIS connecting to AF - Windows authentication
          Eugene Lee

          Hi Dries,

           

          If you are using AF 2.7 onwards, this is fine. Remember to use a high privilege user to maintain the AF hierarchy.

           

          You can then do client side authorization using methods such as

          AFSecurity.CheckSecurity Method (PISystem, WindowsIdentity, IList(AFSecurityRightsToken))

          to determine if the user has access rights.

           

          Therefore, what you are doing is recommended over giving each user their own copy of AF since it is memory intense.

          1 of 1 people found this helpful
          • Re: IIS connecting to AF - Windows authentication
            Dries.Verhees

            Hi Eugene,

             

            Thanks for the answer. That is clear to me.

            Unfortunately, I'm currently getting the following exceptions when I try to retrieve interpolated PI data, using an AF attribute.

             

            System.InvalidOperationException: Attempting to connect to PI Data Archive 'VM-BE-0005' as 'IIS APPPOOL\\xxxxxx' instead of original user 'Domain\\User1'.

            at OSIsoft.AF.PI.PIServer.CheckConnectingAsSameUser() 

            at OSIsoft.AF.PI.PIServer.AutoConnect(Boolean allowDirectConnect, Boolean force)

            at OSIsoft.AF.PI.PIPoint.AutoConnect()

            at OSIsoft.AF.PI.PIPoint.InterpolatedValuesAtTimes(IList`1 times, String filterExpression, Boolean includeFilteredValues)

            at OSIsoft.AF.PI.PIPoint.InterpolatedValuesByCount(AFTimeRange timeRange, Int32 numberOfValues, String filterExpression, Boolean includeFilteredValues) 

            at OSIsoft.AF.Asset.DataReference.PIPointDR.InterpolatedValuesByCount(AFTimeRange timeRange, Int32 numberOfValues, String filterExpression, Boolean includeFilteredValues, AFAttributeList inputAttributes, AFValues[] inputValues)

            at OSIsoft.AF.Asset.DataReference.PIPointDR.<>c__DisplayClass1f.<ListGetInterpolatedValuesByCount>b__1b(Object taskInfo)

             

            I'm only getting this error during my first call. All consecutive calls seem to work fine. This is of course related to the PI connection, which I'm not touching from my application. I'm only working with AF. (no straight PI or PIPoint calls)

            Below, you'll find the versions that we are using.

            piaf_version.png

            Since the data reference is served from the server, could it be related with the AF server version? Unfortunately, updating our AF for testing is not feasible.

              • Re: IIS connecting to AF - Windows authentication
                David Hearn

                You are seeing this error because you are attempting to use an SDK object that was acquired by another user to connect to the server.

                 

                The recommended approach if you want to connect to AF using a single account and perform your own security checks is to impersonate the account that you want to use before making calls to AF. This provides the best performance and memory usage, but requires much more work on your part. The simplest approach is to make sure you get a new PISystems object on every public method call and navigate to the object that you want to use so that the AF SDK does the proper caching and security checks for each user.

                 

                You would either need to call the code in your original question on every entry point so that the AF SDK performs the proper security check and caching for each user or you need to impersonate a 'super' user that has permission to read all elements before making SDK calls and then perform your own security checks as Eugene suggested.

                 

                There was a lab presentation at TechCon 2015 titled "Working with the PI AF SDK" that has a section on "Using PI AF SDK in a Web Service" that explains some of the approaches to using the AF SDK in a web service correctly.

                1 of 1 people found this helpful
                  • Re: IIS connecting to AF - Windows authentication
                    Dries.Verhees

                    Hi David,

                    Thanks for the answer.

                    I do not want to make use of a single account for connecting to AF (not allowed by IT rules), so the impersonation is not an option.

                    If I understand you correctly, I have to instantiate a new PISystems object for every call. That was actually my original idea, but Eugene seems to indicate that it is not required.

                    For my understanding, you suggest the recall the code, that I mentioned in my first message, on each public call and do not make sure of any static variables? Is that correct?

                    In that case, how would it work with the static methods like AFElement.FindElementsByPath, etc?

                     

                    I will definitely check the TechCon presentation. Do you have a link available?

                    • Re: IIS connecting to AF - Windows authentication
                      Dries.Verhees

                      Hi David,

                       

                      I have rewritten my code and I have no static variables anymore. The PISystems() object is instantiated during each call.

                      Unfortunately, I'm still getting the same error message. Note that the second calls always went through.

                      Why did it try to connect with the IIS Application Pool account? Is there something that I could test further? Or do I need to call a method to re-establish the PI connection with the correct account?

                       

                      Thanks,

                      Dries

                        • Re: IIS connecting to AF - Windows authentication
                          David Hearn

                          If the error message is the same as before, it is indicating that the current user on the thread for the first call is the IIS Application Pool account and it is not impersonating a client user. You could try checking who the current user is before each call with 'System.Security.Prinicpal.WindowsIdentity.GetCurrent().Name'.

                            • Re: IIS connecting to AF - Windows authentication
                              Dries.Verhees

                              Hi David,

                               

                              Thanks for the prompt reply. For debugging purpose, I have logged 'WindowsIdentity.GetCurrent().Name' before every AF call (=new PISystems()). It never logs the IIS Application pool account, so which means that all calls are impersonated by DOMAIN\User1. Is there other initialization that I could have missed?

                                • Re: IIS connecting to AF - Windows authentication
                                  Charles Henze

                                  Hi Dries.

                                  Does the app pool user in the error message match that shown in the IIS Manager when you enabled impersonation? (https://technet.microsoft.com/en-us/library/cc730708(v=ws.10).aspx  ) Does the type of impersonation make a difference?

                                   

                                  Are you calling in the current IIS thread or using the thread pool or task factory?

                                   

                                  Using tracing may provide insight into other PISystems collections unexpectedly created or used.  Tracing may be enabled for AF by using the command line AFGetTrace tool found in %PIHOME64%\AF (usually \program files\PIPC\AF).  It defaults to console output, but you can add the /log option to generate a file.

                                    • Re: IIS connecting to AF - Windows authentication
                                      dmoler

                                      What is likely happening is that the managed impersonation is not set up correctly.  Impersonation can be set on an unmanaged thread and that will work fine for synchronous calls.  However when crossing asynchronous boundaries, the .NET runtime needs the proper SecurityContext installed on the thread for it to automatically carry the impersonation across the asynchronous point.  In AFSDK, asynchronous boundaries are crossed when making *Async data calls and when making paged calls that go against a PI Server that does not support them natively (it looks like that is the situation here).

                                       

                                      How are you performing your impersonation?  If you are using something like NamedPipeServerStream.RunAsClient you can get the impersonated identity from the unmanaged thread using: WindowsIdentity.GetCurrent(ifImpersonating: true).  Then call Impersonate() on the result - this will install it in the current thread's SecurityContext.  The MSDN document on SecurityContext has a helpful discussion of how it is flowed.

                                        • Re: IIS connecting to AF - Windows authentication
                                          Dries.Verhees

                                          Hi Charles, David,

                                           

                                          I'm using "Declaratively Impersonation", which is mentioned here: WCF Security – Impersonation – Security Tools (configured as Required)

                                          The ASP.NET impersonation is disabled.

                                           

                                          I was using different threads, but for testing purpose, I'm calling everything from the current IIS thread.

                                          Below, you'll find some snippets of the code which is executed on request:

                                          Select the PI system and AF database

                                           

                                          PISystems piSystems = new PISystems();
                                          PISystem piSystem = piSystems[serverName] ?? piSystems.Add(serverName);piSystem.Connect();
                                          AFDatabase afDatabase = afDatabases[databaseName] ?? afDatabases.Add(dataBaseName);

                                           

                                          Navigate to attribute

                                          Starting from afDatabase, and then take .Elements[elementName] and finally .Attributes[attributeName]

                                          Retrieving data
                                          Create an attribute list with all the required attributes

                                          PIPagingConfiguration pagingConfig = new PIPagingConfiguration(PIPageType.TagCount, 25);

                                          IEnumerable<AFValues> afValues = afAttributeList.Data.InterpolatedValues(timeRange, stepSize, null, true, pagingConfig);

                                           

                                          This is all executed on the IIS thread, using the impersonation as described above. Does it make sense?

                                          In the mean time, I will try to do some additional tests or logging.

                                            • Re: IIS connecting to AF - Windows authentication
                                              dmoler

                                              Hi Dries,

                                               

                                              It's not easy to tell if the SecurityContext is set up correctly or not.  Try checking this while impersonated:

                                               

                                                      var securityContext = System.Security.SecurityContext.Capture();

                                                      var identity = securityContext.GetType().GetProperty("WindowsIdentity", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(securityContext) as System.Security.Principal.WindowsIdentity;

                                               

                                              If the identity is null, then the WindowsIdentity is not installed in the SecurityContext and you need to call Impersonate() on the current WindowsIdentity as outlined here (you'll want to make sure to dispose of it when you are done).

                                               

                                              If the identity is not null, then the SecurityContext is set up correctly and we will need to troubleshoot further.

                                                • Re: IIS connecting to AF - Windows authentication
                                                  Dries.Verhees

                                                  Hi David,

                                                   

                                                  I have executed your test, but the variable identity is always null, also when the call was actually successful.

                                                  As a test, I have executed the following code:

                                                   

                                                  var securityContext = System.Security.SecurityContext.Capture();

                                                   

                                                  var securityIdentity = securityContext?.GetType().GetProperty("WindowsIdentity", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(securityContext);

                                                   

                                                  var identity = securityIdentity as System.Security.Principal.WindowsIdentity;

                                                   

                                                  WindowsIdentity callerWindowsIdentity = ServiceSecurityContext.Current.WindowsIdentity;

                                                   

                                                  using (callerWindowsIdentity.Impersonate())

                                                  {

                                                  var securityContext2 = System.Security.SecurityContext.Capture();

                                                   

                                                  var securityIdentity2 = securityContext?.GetType().GetProperty("WindowsIdentity", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(securityContext);

                                                   

                                                  var identity2 = securityIdentity as System.Security.Principal.WindowsIdentity;

                                                  }

                                                   

                                                  Both securityIdentity and securityIdentity2 are always null. The callerWindowsIdentity is always correct.

                                                   

                                                  I still found it very strange why it is working fine for the snapshot, but it is failing for the bulk interpolated archive. I would expect that the same connection is used.

                                                    • Re: IIS connecting to AF - Windows authentication
                                                      dmoler

                                                      Hi Dries,

                                                       

                                                      You have a small error - inside the Impersonate() using block, you are calling GetValue on securityContext, not securityContext2.  It should be:

                                                      using (callerWindowsIdentity.Impersonate())

                                                      {

                                                      var securityContext2 = System.Security.SecurityContext.Capture();

                                                       

                                                      var securityIdentity2 = securityContext2?.GetType().GetProperty("WindowsIdentity", BindingFlags.NonPublic |BindingFlags.Instance).GetValue(securityContext2);

                                                       

                                                      var identity2 = securityIdentity2 as System.Security.Principal.WindowsIdentity;

                                                      }

                                                       

                                                      With these fixes, I suspect identity2 will be non-null.

                                                       

                                                      In general, the default impersonation approach will work - it will only fail across asynchronous points.  When you make single InterpolatedValues calls these are all executed synchronously on the calling thread so they will work fine.  The paging calls (against older PI Servers) and async calls can exhibit this problem if the SecurityContext is not set up properly for .NET.

                                                        • Re: IIS connecting to AF - Windows authentication
                                                          Dries.Verhees

                                                          Hi David,

                                                           

                                                          Thanks for the update.

                                                          I have corrected the small error, but it does not make any difference; as shown below.

                                                          securityContext.png

                                                          Note that the callerWindowsIdentity is correct, it embeds my domain account.

                                                           

                                                          I understand the difference between the async (bulk) calls and the single calls, but I'm not sure how to fix it on my side. The identity seems to be correct when I call the AF SDK methods. (Snapshot call always seems to work)

                                                            • Re: IIS connecting to AF - Windows authentication
                                                              dmoler

                                                              Hi Dries,

                                                               

                                                              The paging calls will make async calls under the hood if the PI Server does not natively support the paging calls (3.4.390 and later for InterpolatedValues).  If there is not a WindowsIdentity associated with the SecurityContext, you will get this security exception.  So the behavior here is not surprising given that the WindowsIdentity is not associated with the SecurityContext.

                                                                • Re: IIS connecting to AF - Windows authentication
                                                                  Dries.Verhees

                                                                  Hi David,

                                                                   

                                                                  It is clear to me how the InterpolatedValues works, but I'm not able to get the required security context. The service security context is set correctly.

                                                                  serviceSecurityContext.png

                                                                  securityContext.png

                                                                  I'm getting the same security context and service security context inside the impersonation block.

                                                                   

                                                                  Marcos, I have run the code again and I was getting the following exception:

                                                                   

                                                                  System.InvalidOperationException: Attempting to connect to PI Data Archive 'VM-PI-SERVER' as 'IIS APPPOOL\\xxxxxxxxx' instead of original user 'xxxxxx\\Dries Verhees'.
                                                                  at OSIsoft.AF.PI.PIServer.CheckConnectingAsSameUser()
                                                                  at OSIsoft.AF.PI.PIServer.AutoConnect(Boolean allowDirectConnect, Boolean force)
                                                                  at ...

                                                                  Unfortunately, I don't see any corresponding entry in the message log on the PI server. Maybe it does not even try to establish the connection? It seems to fail on the CheckConnectingAsSameUser method.

                                                                   

                                                          • Re: IIS connecting to AF - Windows authentication
                                                            Dries.Verhees

                                                            Hi David,

                                                             

                                                             

                                                            I have found something interesting, while performing a couple of tests.

                                                            As indicated before, it is failing for the following code (only first call fails).

                                                            PIPagingConfiguration pagingConfig = new PIPagingConfiguration(PIPageType.TagCount, 25);

                                                            IEnumerable<AFValues> afValues = afAttributeList.Data.InterpolatedValues(timeRange, stepSize, null, true, pagingConfig);

                                                            As a test case, I have replaced the last line with the following

                                                            var afValuesList = new List<AFValues>();

                                                            foreach (var afAtrribute in afAttributeList)

                                                            {

                                                                 var values = afAtrribute.Data.InterpolatedValues(timeRange, stepSize, null, null, true)

                                                             

                                                                 afValuesList.Add(values);

                                                            }

                                                            IEnumerable<AFValues> afValues = afValuesList;

                                                            so far, it looks like this is always working, also during the first call.