Usage
Client application code that is to use Kerberos for authentication must to authenticate against the KDC and acquire a service ticket for the Neo4j service (in our example: neo4j/neo4j.windomain.local@WINDOMAIN.LOCAL
).
The service ticket must use the Kerberos v5 (1.2.840.113554.1.2.2)
mechanism either directly or wrapped in SPNEGO (1.3.6.1.5.5.2)
.
The service ticket should be provided to the Neo4j driver in an auth token with the following properties:
-
Principal: empty
-
Credentials: the Base64-encoded service ticket
-
Realm:
add-on-Neo4j-Kerberos
Please note that the Kerberos Add-on does not currently work with the Neo4j Browser. It only works with applications using the Neo4j Drivers. |
Example code
This is an example implementation using the 5.18.0 Java driver.
public void connect() throws Exception
{
AuthTokenManager tokenManager = new AuthTokenManager()
{
@Override
public CompletionStage<AuthToken> getToken()
{
return CompletableFuture.supplyAsync( () -> {
try
{
byte[] serviceTicket = get( serviceDomainName );
String encodedServiceTicket = Base64.getEncoder().encodeToString( serviceTicket );
return AuthTokens.kerberos( encodedServiceTicket );
} catch ( Exception e ) {
e.printStackTrace();
return null;
}
});
}
@Override
public boolean handleSecurityException( AuthToken authToken, SecurityException exception )
{
return false;
}
};
try ( Driver driver = GraphDatabase.driver( "bolt://" + serviceDomainName, tokenManager ) )
{
// do interesting things
}
}
public byte[] get( String serviceDomainName ) throws LoginException, GSSException
{
Map<String,String> options = Collections.singletonMap( "useTicketCache", "true" );
Krb5Configuration loginContextConfiguration = new Krb5Configuration( options );
LoginContext loginContext = new LoginContext(
"KerberosClient",
null, // this is the subject
null, // no need for this
loginContextConfiguration
);
loginContext.login();
return getServiceTicket( loginContext.getSubject(), "neo4j@" + serviceDomainName );
}
public static final Oid SPNEGO_OID = getOid( "1.3.6.1.5.5.2" );
public byte[] getServiceTicket( Subject subject, String servicePrincipalName ) throws GSSException
{
GSSManager manager = GSSManager.getInstance();
GSSName serverName = manager.createName( servicePrincipalName, GSSName.NT_HOSTBASED_SERVICE );
final GSSContext context = manager.createContext(
serverName, SPNEGO_OID, null, GSSContext.DEFAULT_LIFETIME );
// The GSS context initiation has to be performed as a privileged action.
return Subject.doAs( subject, new PrivilegedAction<byte[]>()
{
public byte[] run()
{
try
{
// This is a one pass context initialisation.
context.requestMutualAuth( false );
context.requestCredDeleg( false );
return context.initSecContext( new byte[0], 0, 0 );
}
catch ( GSSException e )
{
e.printStackTrace();
return null;
}
}
} );
}
private class Krb5Configuration extends Configuration
{
private final AppConfigurationEntry[] configList;
public Krb5Configuration( Map<String,String> options )
{
this.configList = new AppConfigurationEntry[1];
configList[0] =
new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options
);
}
@Override
public AppConfigurationEntry[] getAppConfigurationEntry( String name )
{
return configList;
}
}
This is an example implementation using C# with the 1.3 .NET driver.
var token = AuthTokens.kerberos(getTicket("neo4j"));
using (var driver = GraphDatabase.Driver("bolt://neo4j.windomain.local:7687", token))
{
try
{
using (var session = driver.Session())
{
var result = session.Run("MATCH () RETURN count(*) AS count");
foreach (var record in result)
{
Console.WriteLine($"Nodecount: {record["count"].As<string>()}");
}
}
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
private static String getTicket(string serviceName)
{
AppDomain.CurrentDomain.SetPrincipalPolicy(System.Security.Principal.PrincipalPolicy.WindowsPrincipal);
var domain = Domain.GetCurrentDomain().ToString();
using (var domainContext = new PrincipalContext(ContextType.Domain, domain))
{
string spn = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, serviceName).UserPrincipalName;
Console.WriteLine("Service Principale name: " + spn);
KerberosSecurityTokenProvider tokenProvider = new KerberosSecurityTokenProvider(spn);
KerberosRequestorSecurityToken securityToken = tokenProvider.GetToken(TimeSpan.FromMinutes(1)) as KerberosRequestorSecurityToken;
var token = securityToken.GetRequest();
String ticket = Convert.ToBase64String(token);
return ticket;
}
}