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.
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.
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; }
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