Date: Tue, 19 Mar 2024 08:14:50 +0000 (UTC) Message-ID: <1915367369.55989.1710836090401@cwiki-he-fi.apache.org> Subject: Exported From Confluence MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_Part_55988_1706933001.1710836090400" ------=_Part_55988_1706933001.1710836090400 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Content-Location: file:///C:/exported.html
Geode can be configured to authenticate peer system members, clients, an= d remote gateways. Geode also provides for authorization of cache operation= s on a server from clients. This allows to block unauthenticated access to = a Geode Distributed System, or block cache operations as per user defined p= olicies. When the peer system members are configured for authentication, th= en the Geode system is required to use the locator service for discovery.= p>
The Geode authentication and authorization sub-system is implemented as = a framework allowing applications to plug-in external providers such as LDA= P, Kerberos, etc. We find that most enterprise customers typically have a c= ommon infrastructure such as single sign-on or centralized authentication s= ystems they need to integrate with. Geode provides sample implementations b= ased on LDAP and PKCS along with source code that users can adapt to suit t= heir requirements or roll their own.
It is also possible to use SSL (Secure Sockets Layer) for "on-the-wire" = confidentiality in Geode.
Note that authentication and entitlement management is provided for each= VM that joins a Geode Distributed System, or for client/gateway processes.= Each member node can only represent a single 'principal' or 'user'. Authen= tication at a connection level (support for multiple users in a single proc= ess) will be added in a future release.
We first walk through the authentication interfaces provided as part of = security framework with simple implementations. Then configuring Geode to u= se those implementations is described. The sample implementations provided = with Geode are described next. In the latter part, more advanced implementa= tions like integration with a Kerberos realm, object-level authorization ar= e provided.
The first requirement is obtaining credentials for a client/peer which i= s catered to by the AuthInitialize interface. This is required for a client= or peer or gateway that needs to connect to a Geode Distributed System whi= ch has security enabled (more on enabling security in section "Configuratio= n of authentication callbacks for peers").
public interf= ace AuthInitialize extends CacheCallback { public void init(LogWriter systemLogger, LogWriter securityLogger) throws AuthenticationFailedException; public Properties getCredentials(Properties securityProps, DistributedMember server, boolean isPeer) throws AuthenticationFailedException; }
The first method "init" in the interface is to initialize the callback w= ith the given LogWriters. Usually a callback will use the securityLogger fo= r logging purpose which is a special logger for security that will mark log= lines with a "security-" prefix and the logs can also be configured to go = to a separate log file. A sample implementation is below:
public class = SampleAuthInit implements AuthInitialize { private LogWriter logger; public void init(LogWriter systemLogger, LogWriter securityLogger) throws AuthenticationFailedException { this.logger =3D securityLogger; }
The work of obtaining credentials for a VM is done by the "getCredential= s" method.
The securityProps argument contains the set of Geode = properties containing the prefix "security-". The server argument is for th= e host, port and other membership information of remote server (for the cas= e of clients or gateways) or locator/peer (for the case of peer members) wh= ich will authenticate this member. The isPeer argument i= s passed as true when the remote member is a peer or locator, while it is f= alse when the remote member is a server. This is useful if a member is both= a peer in a Distributed System and client/gateway for another Distributed = System and the member needs to use different credentials for the two.
A simple user/password implementation may expect "security-username" pro= perty to be set for the name of user, and "security-password" property to b= e set for the password (latter would normally be set programmatically) so a= ll that is needed to be done is pass back the two properties as below:
public Proper= ties getCredentials(Properties securityProps, DistributedMember server, boo= lean isPeer) throws AuthenticationFailedException { Properties credentials =3D new Properties(); String userName =3D securityProps.getProperty("security-username"); credentials.setProperty("security-username", userName); String passwd =3D securityProps.getProperty("security-password"); credentials.setProperty("security-password", passwd); logger.info("SampleAuthInit: successfully obtained credentials for user " = + userName); return credentials;}
Adding a few checks in the above code, our sample implementation looks l= ike below:
package com.g= emstone.samples; import java.util.Properties; import com.gemstone.gemfire.LogWriter; import com.gemstone.gemfire.distributed.DistributedMember; import com.gemstone.gemfire.security.AuthInitialize; import com.gemstone.gemfire.security.AuthenticationFailedException; public class SampleAuthInit implements AuthInitialize { private LogWriter logger; public static final String USER_NAME =3D "security-username"; public static final String PASSWORD =3D "security-password"; public static AuthInitialize create() { return new SampleAuthInit(); } public void init(LogWriter systemLogger, LogWriter securityLogger) throws AuthenticationFailedException { this.logger =3D securityLogger; } public Properties getCredentials(Properties securityProps, DistributedMember server, boolean isPeer) throws AuthenticationFailedException { Properties credentials =3D new Properties(); String userName =3D securityProps.getProperty(USER_NAME); if (userName =3D=3D null) { throw new AuthenticationFailedException( "SampleAuthInit: user name property [" + USER_NAME + "] not set."= ); } credentials.setProperty(USER_NAME, userName); String passwd =3D securityProps.getProperty(PASSWORD); if (passwd =3D=3D null) { throw new AuthenticationFailedException( "SampleAuthInit: password property [" + PASSWORD + "] not set."); } credentials.setProperty(PASSWORD, passwd); logger.info("SampleAuthInit: successfully obtained credentials for user= " + userName); return credentials; } public void close() { } }
The "close" method in the above implementation comes from the C= acheCallbackinterface that AuthInitialize extends. = The static "create" method is used to create an instance of the interface w= hich is used for registration of the callback.
Next is the Authenticator interface that is required = to be implemented on a server/peer/locator that will authenticate a new cli= ent or peer member. This callback is provided the credentials of the joinin= g member as a set of properties as obtained fromAuthInitialize#getCrede= ntials on the member.
public interf= ace Authenticator extends CacheCallback { public void init(Properties securityProps, LogWriter systemLogger, LogWriter securityLogger) throws AuthenticationFailedException; public Principal authenticate(Properties props, DistributedMember member) throws AuthenticationFailedException; }
This has a couple of methods (apart from the "close" method inherited fr= omCacheCallback interface). The first one "init" is used to p= erform any initialization of the callback and provided LogWriters useful fo= r logging. The securityProps argument provides all the G= eode properties of this member that start with the prefix "security-". For = our sample implementation we will again use the securityLogger provided.
public = class SampleAuthenticator implements Authenticator { private LogWriter logger; public void init(Properties securityProps, LogWriter systemLogger, LogWriter securityLogger) throws AuthenticationFailedException { this.logger =3D securityLogger; }
The "authenticate" method is the guts of callback that authenticates the= client or peer member. It is provided the credentials of the joining membe= r as a set of Properties in theprops argum= ent. This is the same set of properties that have been returned by theA= uthInitialize#getCredentials on the member. Lastly the m= ember argument provides the membership information of the joining= client or peer member.
Continuing with the simple username/password based authentication exampl= e, our sample authenticator uses a JAAS username/password implementation to= verify the credentials of the user. Firstly we need a CallbackHan= dler for JAAS that will just provide the username/password provid= ed in the properties to the JAAS LoginModule. This is handled= by the SampleCallbackHandler class below.
package= com.gemstone.samples; import java.io.IOException; import java.security.Principal; import java.util.Properties; import java.util.Set; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import com.gemstone.gemfire.LogWriter; import com.gemstone.gemfire.distributed.DistributedMember; import com.gemstone.gemfire.security.AuthenticationFailedException; import com.gemstone.gemfire.security.Authenticator; public class SampleAuthenticator implements Authenticator { private LogWriter logger; private String jaasEntry; private LoginContext currentContext; public static final String JAAS_ENTRY =3D "security-jaas-entry"; public static Authenticator create() { return new SampleAuthenticator(); } public void init(Properties securityProps, LogWriter systemLogger, LogWriter securityLogger) throws AuthenticationFailedException { this.logger =3D securityLogger; this.jaasEntry =3D securityProps.getProperty(JAAS_ENTRY); } public Principal authenticate(Properties props, DistributedMember member) throws AuthenticationFailedException { SampleCallbackHandler callbackHandler =3D new SampleCallbackHandler(pro= ps); try { this.currentContext =3D new LoginContext(this.jaasEntry, callbackHand= ler); } catch (LoginException ex) { throw new AuthenticationFailedException("SampleAuthenticator: failed = " + "in creation of LoginContext for JAAS entry: " + this.jaasEntry, ex); } try { this.currentContext.login(); } catch (LoginException ex) { throw new AuthenticationFailedException("SampleAuthenticator: " + "authentication failed for JAAS entry: " + this.jaasEntry, ex); } Set<Principal> principals =3D this.currentContext.getSubject() .getPrincipals(); // assume only one Principal if (principals =3D=3D null || principals.size() !=3D 1) { throw new AuthenticationFailedException("SampleAuthenticator: expecte= d " + "one Principal but got: " + principals); } logger.info("SampleAuthenticator: successfully authenticated member: " + callbackHandler.userName); return principals.iterator().next(); } public void close() { } class SampleCallbackHandler implements CallbackHandler { private final String userName; private final String password; public SampleCallbackHandler(Properties props) { this.userName =3D props.getProperty(SampleAuthInit.USER_NAME); this.password =3D props.getProperty(SampleAuthInit.PASSWORD); } public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { if (callback instanceof NameCallback) { ((NameCallback)callback).setName(this.userName); } else if (callback instanceof PasswordCallback) { ((PasswordCallback)callback).setPassword(this.password.toCharArra= y()); } } } } }
The static "create" method is used to create an instance of the interfac= e which is used for registration of the callback. Next, the configuration o= f the above two callbacks is discussed.
As mentioned before, a Geode Distributed System must use locators for di= scovery for peer security i.e. multicast discovery is incompatible with pee= r security settings discussed below.
A peer attempting to join a secure distributed system presents its crede= ntials to one of the authenticated locators. The first locator to join the = Distributed System is assumed to be authenticated and subsequent locators a= uthenticate against the first one. The list of locators is obtained from th= e "locators" Geode property. The credentials obtained from the Aut= hIntialize#getCredentials method is sent to one of the locators f= or authentication. The security-peer-auth-init property = should be set to the name of a zero argument static method that returns an&= nbsp;AuthInitialize object on the members while the = security-peer-authenticator property should be set to the name of= zero argument static method that returns an Authenticator object on the me= mbers and locators. Note that since the members also authenticate the VIEW = messages sent out, so all members also need to be configured with the Authe= nticator in addition to the locators.
The settings required for the above example implementations are:
security-peer-auth-init =E2=80=93 com.gemstone.samples.Sam= pleAuthInit.create
security-username =E2=80=93 a valid user name
security-password =E2=80=93 password for the above user
security-peer-authenticator =E2=80=93 com.gemstone.samples= .SampleAuthenticator.create
_security-jaas-entry _=E2=80=93 entry name in JAAS configuration file to= use for authentication
The JAAS configuration file should be provided using the normal "java.se= curity.auth.login.config" System property. These need to be set on all the = peer members and locators of the Distributed System. Sample code to do this= programmatically is below:
Propertie= s props =3D new Properties(); props.setProperty("security-peer-auth-init", "com.gemstone.samples.SampleAuthInit.create"); props.setProperty(SampleAuthInit.USER_NAME, "user1"); props.setProperty(SampleAuthInit.PASSWORD, "xxx"); props.setProperty("security-peer-authenticator", "com.gemstone.samples.SampleAuthenticator.create"); props.setProperty("security-jaas-entry", "Sample"); DistributedSystem sys =3D DistributedSystem.connect(props);
If authentication for a peer member or locator fails, then the Distribut= edSystem.connect() method throws an AuthenticationFailedException<= /em>. If the locators/peers have the security-peer-authenticator= em> property set but the members do not have the security-pee= r-auth-initproperty set, then an AuthenticationRequiredExcept= ion is thrown. All security exceptions have GemFireSecur= ityException as the base class so user code can choose to catch t= he base class exception where required.
A client is authenticated for each handshake it initiates with a Geode c=
ache server i.e. for each TCP connection from client to server. The client =
passes its credentials during the handshake and the server uses them to aut=
henticate the client. The client must trust all the cache server host:bind-address[port] pairs in its endpoints list, since th=
e client could connect to any server in the list and pass along its credent=
ials. The credentials obtained from the AuthInitialize#getCredenti=
als method is sent to the servers for authentication. The
The settings required for the above example implementations are:
For clients:
security-client-auth-init =E2=80=93 com.gemstone.samples.S= ampleAuthInit.create
security-username =E2=80=93 a valid user name
security-password =E2=80=93 password for the above user
For servers:
security-client-authenticator =E2=80=93 com.gemstone.sampl= es.SampleAuthenticator.create
security-jaas-entry =E2=80=93 entry name in JAAS configura= tion file to use for authentication
As before the JAAS configuration file should be provided using the norma= l "java.security.auth.login.config" System property. These need to be set o= n all the peer members and locators of the Distributed System. Sample code = to do this programmatically is below:
For clients:
Propertie= s props =3D new Properties(); props.setProperty("security-client-auth-init", "com.gemstone.samples.SampleAuthInit.create"); props.setProperty(SampleAuthInit.USER_NAME, "user1"); props.setProperty(SampleAuthInit.PASSWORD, "xxx"); DistributedSystem sys =3D DistributedSystem.connect(props);
For servers:
Propert= ies props =3D new Properties(); props.setProperty("security-client-authenticator", "com.gemstone.samples.SampleAuthenticator.create"); props.setProperty("security-jaas-entry", "Sample"); DistributedSystem sys =3D DistributedSystem.connect(props);
Unlike for peers, the authentication of clients is performed for each cl= ient-server connection that are created dynamically during Region operation= s. If authentication for a client fails, then the Region API method that re= quires to go to the server throws anAuthenticationFailedException.= If the servers have the security-client-authenticatorpropert= y set but the clients do not have the security-client-auth-init property set, then anAuthenticationRequiredException = is thrown by the Region API methods.
Authorization for cache operations is currently provided for clients tha= t should first authenticate to the server as above. Once a client has authe= nticated to a server as above, the Principal _object returned by _= Authenticator#authenticate method is associated to the client. Th= is is then passed on to the authorization callback on the server, if any.= p>
There are two places where authorization of cache operations can be perf= ormed: one in the pre-operation phase before an operation is performed, and= second in the post-operation phase after the operation is complete on the = server and before sending result back to the client (for get/query kind of = operations that return a result). In addition, notifications sent to client= s by servers are also authorized in the post-operation phase.
public interf= ace AccessControl extends CacheCallback { public void init(Principal principal, DistributedMember remoteMember, Cache cache) throws NotAuthorizedException; public boolean authorizeOperation(String regionName, OperationContext cont= ext); }
The "init" method is invoked to initialize the callback for a client. Th= e Principal object obtained for the authenticated client= (result of Authenticator#authenticate method) is passed= as the first argument to the method. Membership information for the client= is provided in the remoteMember argument, while the Geode Cache is passed = as the third argument. The "authorizeOperation" method is invoked for each = client cache operation on the authenticated connection. It is provided the = region name of the operation and anOperationContext object th= at encapsulates information of the current cache operation.
Continuing with our username/password example, we assume that the JAAS a= uthentication module in above sample implementations returns a Pri= ncipal (MyPrincipalclass) that provides an "isReader" me= thod that will return true if the member should be given read-only permissi= ons, while other members have all permissions. The code for pre-authorizati= on callback is below:
package com.g= emstone.samples; import java.security.Principal; import com.gemstone.gemfire.cache.Cache; import com.gemstone.gemfire.cache.operations.OperationContext; import com.gemstone.gemfire.cache.operations.OperationContext.OperationCode= ; import com.gemstone.gemfire.distributed.DistributedMember; import com.gemstone.gemfire.security.AccessControl; import com.gemstone.gemfire.security.NotAuthorizedException; public class SampleAccessControl implements AccessControl { private boolean isReader; public static AccessControl create() { return new SampleAccessControl(); } public void init(Principal principal, DistributedMember remoteMember, Cache cache) throws NotAuthorizedException { if (principal instanceof MyPrincipal) { this.isReader =3D ((MyPrincipal)principal).isReader(); } else { this.isReader =3D false; } } public boolean authorizeOperation(String regionName, OperationContext cont= ext) { if (this.isReader) { OperationCode opCode =3D context.getOperationCode(); // these cache operations do not modify data return (opCode.isGet() || opCode.isQuery() || opCode.isContainsKey() || opCode.isKeySet() || opCode.isRegisterInterest() || opCode.isUnregisterInterest() || opCode.isExecuteCQ() || opCode.isCloseCQ() || opCode.isStopCQ()); } else { return true; } } public void close() { } }
The "init" method caches the "isReader" flag for the client since the&nb= sp;isReader method may be potentially expensive. The "authori= zeOperation" method then allows read-only operations when the "isReader" fl= ag is true.
The setting required on the servers for the above example implementation= is:
security-client-access-control =E2=80=93 com.gemstone.samp= les.SampleAccessControl.create
If the authorization for a cache operation is denied by the server (i.e.= authorizeOperationmethod on server returns false), then the = client receives a NotAuthorizedException for the operati= on.