Advanced COM+ Security

On top of incredibly rich, user-friendly administrative support for all your security needs, COM+ provides low-level, advanced programmatic security capabilities. These features cater to complex security needs. However, I have found that there is almost always a good design solution that lets me use COM+ configurable settings without having to resort to advanced, programmatic, low-level security manipulation. In fact, you can probably lead a productive and fulfilling development career using COM+ without using low-level security manipulation at all. If what you have read so far fulfills your requirements, feel free to skip this section and move to the conclusion of this chapter and its account of the ever-present pitfalls of COM+ security. If not, continue reading.

Server-Side Impersonation

Setting the allowed impersonation level is a client-side configuration, in which the client declares the level of trust it has toward the server. Configuring the impersonation level is not an advanced security measure; it is a necessary precaution because you cannot know what the server is up to and whether it intends to impersonate the client. However, server-side impersonation is advanced security.

You should be aware that server-side impersonation is not an extensible or scalable design approach. Each COM+ application should be configured with enough credentials (that is, a security identity) to perform its work, and should not rely on the client’s credentials by impersonating it. Impersonation couples the server application to the client’s identity and prevents the application from evolving independently. In almost all cases when impersonation is a critical part of the application design, the design is not scalable. Consider, for example, an application in which the database performs its own authentication and authorization of end users to secure access to data in the database. Middle-tier objects have to impersonate the caller to access the database, resulting in a programming model that is tightly coupled to the identity of the callers (bank tellers can only access the accounts they are responsible for). Adding new users is not trivial, and therefore does not scale. A better design decision would be to have the database authenticate just the COM+ applications accessing it and trust the applications to authenticate and authorize the clients securely.

Allocating database connections per user is another example of when using impersonation is not scalable. The middle-tier objects have to impersonate the user to get a connection. Consequently, the connections cannot be shared (no connection pooling) and the total number of users the system can handle is drastically reduced.

One more impersonation liability is performance—impersonating the client can be much slower than making the call directly under the application identity. If the client does not have enough credentials to access a resource, the call fails downstream, when the impersonating object tries to access the resource, instead of upstream, when the client first accesses the object. Impersonation may also involve intensive under-the-hood traffic and validations.

If you decide to use impersonation, do so judiciously, and only for the purpose of obtaining the client’s identity to verify access to a sensitive resource the server application has independent access to. Once the server has verified that the client has enough credentials, the server object should revert to its own identity and access the resource.

The call context object supports another interface called IServerSecurity . The server can access IServerSecurity by calling CoGetCallContext( ) . Remember that the pointer to IServerSecurity will only be valid for the duration of the current call and cannot be cached.

To impersonate the calling client, the server should call IServerSecurity::ImpersonateClient( ) . To revert back to its original identity, the server should call IServerSecurity::RevertToSelf( ) .

Example 7-2 shows a server object impersonating a client to verify that the client has access rights to create a file. If it does, the server reverts to its original identity and creates the file under its own identity.

Example 7-2. The server impersonating the client to verify file creation access rights

STDMETHODIMP CMyServer::CreateFile(BSTR bstrFileName)
{
   HRESULT hres = S_OK;

   IServerSecurity* pServerSecurity = NULL;
   hres = ::CoGetCallContext(IID_IServerSecurity,(void**)&pServerSecurity);
   ASSERT(pServerSecurity);

   hres = pServerSecurity->ImpersonateClient(  );
   HANDLE  hFile = ::CreateFile(_bstr_t(bstrFileName),STANDARD_RIGHTS_ALL,0,NULL,
                                CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

   //Do the cleanup first, before the error handling 
   ::CloseHandle(hFile);//Does not change the value of hFile
   hres = pServerSecurity->RevertToSelf(  );
   pServerSecurity->Release(  );

   if(hFile == INVALID_HANDLE_VALUE)//failure due to lack of access rights 
                                    //as well as anything else that can go wrong
   {
      return E_FAIL;
   }
   //The client has the right access rights to this file, now create it again 
   //under the server's own identity

   //m_hFile is a member of this object
   m_hFile = ::CreateFile(_bstr_t(bstrFileName),STANDARD_RIGHTS_ALL,0,NULL,
                          CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
 
   if(m_hFile == INVALID_HANDLE_VALUE)//Something went wrong
   {
      return E_FAIL;
   }
   return hres;
}

COM+ provides two helper functions to automate coding sequences like the one in Example 7-2. CoImpersonateClient( ) creates the server security object, impersonates the client, and releases the server security object. CoRevertToSelf( ) similarly creates the server security object, reverts to the server’s original identity, and releases the server security object. Example 7-3 shows the same sequence as in Example 7-2, using the helper functions.

Tip

Even though the code in Example 7-3 is more concise and readable than Example 7-2, you should be aware of a slight performance penalty that using the impersonation helper functions introduces. In Example 7-2, the server security object is only created and released once, while it is done twice in Example 7-3. Nevertheless, I recommend using the helper functions because that penalty is truly miniscule and readable code is always essential.

Example 7-3. Verifying file creation access rights with CoImpersonateClient( ) and CoRevertToSelf( )

STDMETHODIMP CMyObj::CreateFile(BSTR bstrFileName)
{
   HRESULT hres = S_OK;

   hres = ::CoImpersonateClient(  );

   HANDLE  hFile = ::CreateFile(_bstr_t(bstrFileName),STANDARD_RIGHTS_ALL,0,NULL,
                                CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

   ::CloseHandle(hFile);//Does not change the value of hFile
   hres = ::CoRevertToSelf(  );

   if(hFile == INVALID_HANDLE_VALUE)//failure due to lack of access rights as well
                                    //as anything else that can go wrong
   {
      return E_FAIL;
   }
   //The client has the right access rights to this file, now create it again 
   //under server own identity

   //m_hFile is a member of this object
   m_hFile = ::CreateFile(_bstr_t(bstrFileName),STANDARD_RIGHTS_ALL,0,NULL,
                          CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
 
   if(m_hFile == INVALID_HANDLE_VALUE)//Something went wrong
   {
      return E_FAIL;
   }

   return hres;

}

Programmatic Client-Side Security

Every client-side proxy supports an interface called IClientSecurity , which lets the client set the security attributes on the communication channel between it and the object behind that proxy. COM+ calls the set of security attributes (such as authentication and impersonation levels) a security blanket. Using the IClientSecurity method SetBlanket( ) , the client can set a new authentication level, a new impersonation level, or both. It can also set other security attributes. However, the proxy may be shared among a few clients, and not all of them may be interested in a new security blanket. COM+ allows a client to clone a personal copy of the proxy for its own use, using another method of IClientSecurity called CopyProxy( ) that gives the client a private new proxy. You can set a security blanket without cloning your own proxy, but it is recommended that you clone it.

Setting a security blanket may be useful in a few situations:

  • When the global application security level is not granular enough. For example, some methods may require additional authentication. In the bank example, the client may want to set an explicit high authentication level for the TransferMoney( ) method but use whatever security level the application uses for GetBalance( ).

  • When a library application is at the mercy of the hosting process. If the library application is a client of other objects, though, it can set its own authentication and impersonation levels using IClientSecurity.

COM+ provides helper functions to automate coping a proxy (the CoCopyProxy( ) function) and setting a security blanket (the CoSetProxyBlanket( ) function). Example 7-4 shows a client of the bank application, copies the proxy, and sets explicit impersonation and authentication levels for the TransferMoney( ) method, using the helper functions.

Example 7-4. Setting explicit authentication and impersonation levels for the TransferMoney( ) method

HRESULT hres = S_OK;
//pAccountsManager is initialized somewhere else
IAccountsManager* pPrivateAccountsManager = NULL;//This client private copy
   
//copying the proxy to get a private copy, so not to interfere with other clients
hres = ::CoCopyProxy(pAccountsManager,(IUnknown**)&pPrivateAccountsManager);

//Setting explicit authentication and impersonation levels
hres = ::CoSetProxyBlanket(pPrivateAccountsManager,//The private proxy
                           RPC_C_AUTHN_DEFAULT,//The system default authentication 
                           RPC_C_AUTHZ_DEFAULT ,//Default authorization
                           NULL,
                           //Use authentication level "Packet Integrity"
                           RPC_C_AUTHN_LEVEL_PKT_INTEGRITY,
                           //Impersonation level is "Identify"
                           RPC_C_IMP_LEVEL_IDENTIFY,
                           NULL,//Use process identity
                           EOAC_DEFAULT);//Default capabilities
hres = pPrivateAccountsManager->TransferMoney(1000,1234,5678);   
pPrivateAccountsManager->Release(  );//Release private copy

I would advise that you always prefer automatic, administrative, declarative security rather than doing security within components, whether it is on the server or the client side. Following this simple rule will make it easier to:

  • Write and maintain components

  • Design consistent security across an entire application

  • Modify an application’s security policy without recoding it

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset