Share on:

How to Use Apache Cassandra as User DataStore in OpenAM

Original article: https://github.com/OpenIdentityPlatform/OpenAM/wiki/How-to-Use-Apache-Cassandra-as-User-DataStore-in-OpenAM

Table of Contents

Introduction

One of the performance bottlenecks of authentication is User DataStore. This becomes noticeable when the total number of user accounts exceeds 4 000 000.

To maintain acceptable perfomance, the most obvious solution is to scale User DataStore horisontally or vertically. But with increasing amount of data, DataStore scaling does not affect performance.

With authentication system increasing load on the authentication system, the most obvious solution is to scale horizontally or vertically the number of user repository nodes.

Another alternative solution is to use storage specifically designed for working with large amounts of data. On of such storages is Apache Cassandra. It has proved itself as high-availability and high-performance datastore.

Prepare Apache Cassandra to use as User DataStore

At first you need to create a role in Apache Cassandra, which will have access to OpenAM user accounts. For example:

CREATE ROLE openam WITH LOGIN = true AND PASSWORD = 'openam';

Next you need to create the corresponding Keyspaces and tables, for each OpenAM realm. For example, for realm b2c/users script will be like this.

CREATE KEYSPACE b2c_users WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}   AND durable_writes = true;

CREATE TABLE b2c_users.rowindexdata (
    id int,
    value text,
    key text,
    time timestamp,
    PRIMARY KEY (id, value, key)
) WITH CLUSTERING ORDER BY (value ASC, key ASC)
    AND bloom_filter_fp_chance = 0.01
    AND comment = ''
    AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
    AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
    AND dclocal_read_repair_chance = 0.1
    AND default_time_to_live = 0
    AND gc_grace_seconds = 864000
    AND max_index_interval = 2048
    AND memtable_flush_period_in_ms = 0
    AND min_index_interval = 128
    AND read_repair_chance = 0.0
    AND speculative_retry = '99.0PERCENTILE';

CREATE TABLE b2c_users.realm (
    uid text PRIMARY KEY,
    cospriority set<text>,
    "iplanet-am-session-add-session-listener-on-all-sessions" set<text>,
    "iplanet-am-session-destroy-sessions" set<text>,
    "iplanet-am-session-get-valid-sessions" set<text>,
    "iplanet-am-session-max-caching-time" set<text>,
    "iplanet-am-session-max-idle-time" set<text>,
    "iplanet-am-session-max-session-time" set<text>,
    "iplanet-am-session-quota-limit" set<text>,
    "iplanet-am-session-service-status" set<text>,
    "iplanet-am-user-account-life" set<text>,
    "iplanet-am-user-admin-start-dn" set<text>,
    "iplanet-am-user-alias-list" set<text>,
    "iplanet-am-user-auth-config" set<text>,
    "iplanet-am-user-auth-modules" set<text>,
    "iplanet-am-user-failure-url" set<text>,
    "iplanet-am-user-federation-info" set<text>,
    "iplanet-am-user-federation-info-key" set<text>,
    "iplanet-am-user-login-status" set<text>,
    "iplanet-am-user-password-reset-force-reset" set<text>,
    "iplanet-am-user-password-reset-options" set<text>,
    "iplanet-am-user-password-reset-question-answer" set<text>,
    "iplanet-am-user-success-url" set<text>,
    "objectClass" set<text>,
    "serviceName" set<text>,
    "sunIdentityServerDiscoEntries" set<text>
) WITH bloom_filter_fp_chance = 0.01
    AND comment = ''
    AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
    AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
    AND dclocal_read_repair_chance = 0.1
    AND default_time_to_live = 0
    AND gc_grace_seconds = 864000
    AND max_index_interval = 2048
    AND memtable_flush_period_in_ms = 0
    AND min_index_interval = 128
    AND read_repair_chance = 0.0
    AND speculative_retry = '99.0PERCENTILE';
CREATE INDEX servicename ON b2c_users.realm ("serviceName");
CREATE INDEX objectclass2 ON b2c_users.realm ("objectClass");

CREATE TABLE b2c_users.user (
    uid text PRIMARY KEY,
    ad set<text>,
    "adminRole" set<text>,
    "assignedDashboard" set<text>,
    "authorityRevocationList" set<text>,
    balance set<text>,
    birthday set<text>,
    bonus set<text>,
    businesscategory set<text>,
    "caCertificate" set<text>,
    cn set<text>,
    "comstar-b2c-login" set<text>,
    "comstar-b2c-password" set<text>,
    customer set<text>,
    destinationindicator set<text>,
    "devicePrintProfiles" set<text>,
    displayname set<text>,
    "distinguishedName" set<text>,
    dn set<text>,
    "employeeNumber" set<text>,
    employeetype set<text>,
    envelope set<text>,
    generateid set<text>,
    "givenName" set<text>,
    h2o set<text>,
    imsi set<text>,
    "inetUserHttpURL" set<text>,
    "inetUserStatus" set<text>,
    "iplanet-am-auth-config" set<text>,
    "iplanet-am-session-add-session-listener-on-all-sessions" set<text>,
    "iplanet-am-session-destroy-sessions" set<text>,
    "iplanet-am-session-get-valid-sessions" set<text>,
    "iplanet-am-session-max-caching-time" set<text>,
    "iplanet-am-session-max-idle-time" set<text>,
    "iplanet-am-session-max-session-time" set<text>,
    "iplanet-am-session-quota-limit" set<text>,
    "iplanet-am-session-service-status" set<text>,
    "iplanet-am-user-account-life" set<text>,
    "iplanet-am-user-admin-start-dn" set<text>,
    "iplanet-am-user-alias-list" set<text>,
    "iplanet-am-user-auth-config" set<text>,
    "iplanet-am-user-auth-modules" set<text>,
    "iplanet-am-user-failure-url" set<text>,
    "iplanet-am-user-federation-info" set<text>,
    "iplanet-am-user-federation-info-key" set<text>,
    "iplanet-am-user-login-status" set<text>,
    "iplanet-am-user-password-reset-force-reset" set<text>,
    "iplanet-am-user-password-reset-options" set<text>,
    "iplanet-am-user-password-reset-question-answer" set<text>,
    "iplanet-am-user-success-url" set<text>,
    mail set<text>,
    manager set<text>,
    "memberOf" set<text>,
    "modifyTimestamp" set<text>,
    o set<text>,
    "objectClass" set<text>,
    orderid set<text>,
    ou set<text>,
    personalaccountnumber set<text>,
    po set<text>,
    "postalAddress" set<text>,
    "preferredLocale" set<text>,
    preferredlanguage set<text>,
    preferredtimezone set<text>,
    service set<text>,
    services set<text>,
    sn set<text>,
    "sun-fm-saml2-nameid-info" set<text>,
    "sun-fm-saml2-nameid-infokey" set<text>,
    "sunAMAuthInvalidAttemptsData" set<text>,
    "sunIdentityMSISDNNumber" set<text>,
    "sunIdentityServerDiscoEntries" set<text>,
    "sunIdentityServerPPAddressCard" set<text>,
    "sunIdentityServerPPCommonNameAltCN" set<text>,
    "sunIdentityServerPPCommonNameCN" set<text>,
    "sunIdentityServerPPCommonNameFN" set<text>,
    "sunIdentityServerPPCommonNameMN" set<text>,
    "sunIdentityServerPPCommonNamePT" set<text>,
    "sunIdentityServerPPCommonNameSN" set<text>,
    "sunIdentityServerPPDemographicsAge" set<text>,
    "sunIdentityServerPPDemographicsBirthDay" set<text>,
    "sunIdentityServerPPDemographicsDisplayLanguage" set<text>,
    "sunIdentityServerPPDemographicsLanguage" set<text>,
    "sunIdentityServerPPDemographicsTimeZone" set<text>,
    "sunIdentityServerPPEmergencyContact" set<text>,
    "sunIdentityServerPPEmploymentIdentityAltO" set<text>,
    "sunIdentityServerPPEmploymentIdentityJobTitle" set<text>,
    "sunIdentityServerPPEmploymentIdentityOrg" set<text>,
    "sunIdentityServerPPEncryPTKey" set<text>,
    "sunIdentityServerPPFacadeGreetSound" set<text>,
    "sunIdentityServerPPFacadeMugShot" set<text>,
    "sunIdentityServerPPFacadeNamePronounced" set<text>,
    "sunIdentityServerPPFacadeWebSite" set<text>,
    "sunIdentityServerPPFacadegreetmesound" set<text>,
    "sunIdentityServerPPInformalName" set<text>,
    "sunIdentityServerPPLegalIdentityAltIdType" set<text>,
    "sunIdentityServerPPLegalIdentityAltIdValue" set<text>,
    "sunIdentityServerPPLegalIdentityDOB" set<text>,
    "sunIdentityServerPPLegalIdentityGender" set<text>,
    "sunIdentityServerPPLegalIdentityLegalName" set<text>,
    "sunIdentityServerPPLegalIdentityMaritalStatus" set<text>,
    "sunIdentityServerPPLegalIdentityVATIdType" set<text>,
    "sunIdentityServerPPLegalIdentityVATIdValue" set<text>,
    "sunIdentityServerPPMsgContact" set<text>,
    "sunIdentityServerPPSignKey" set<text>,
    tariff set<text>,
    tariffid set<text>,
    tdid set<text>,
    tdn set<text>,
    "telephoneNumber" set<text>,
    terminal set<text>,
    "time-balance-actual" set<text>,
    "time-change-password" set<text>,
    "userCertificate" set<text>,
    "userPassword" set<text>,
    "confirmUseOAuth" set<text>,
    "oauth-token-access" set<text>,
    "oauth-token-refresh" set<text>,
    "session-success" set<text>,
    "session-failed" set<text>
) WITH bloom_filter_fp_chance = 0.01
     AND comment = ''
    AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
    AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
    AND dclocal_read_repair_chance = 0.1
    AND default_time_to_live = 5184000
    AND gc_grace_seconds = 864000
    AND max_index_interval = 2048
    AND memtable_flush_period_in_ms = 0
    AND min_index_interval = 128
    AND read_repair_chance = 0.0
    AND speculative_retry = '99.0PERCENTILE';
CREATE INDEX telephonenumber ON b2c_users.user ("telephoneNumber");
CREATE INDEX service2terminal ON b2c_users.user (terminal);
CREATE INDEX cn ON b2c_users.user (cn);
CREATE INDEX iplanet_am_user_federation_info_key ON b2c_users.user ("iplanet-am-user-federation-info-key");
CREATE INDEX objectclass ON b2c_users.user ("objectClass");
CREATE INDEX envelope ON b2c_users.user (envelope);
CREATE INDEX generateid ON b2c_users.user (generateid);
CREATE INDEX comstar_b2c_login ON b2c_users.user ("comstar-b2c-login");
CREATE INDEX iplanet_am_user_alias_list ON b2c_users.user ("iplanet-am-user-alias-list");
CREATE INDEX sunidentitymsisdnnumber ON b2c_users.user ("sunIdentityMSISDNNumber");
CREATE INDEX sun_fm_saml2_nameid_infokey ON b2c_users.user ("sun-fm-saml2-nameid-infokey");
CREATE INDEX memberof ON b2c_users.user ("memberOf");
CREATE INDEX givenName ON b2c_users.user ("givenName");
CREATE INDEX personalaccountnumber ON b2c_users.user (personalaccountnumber);
CREATE INDEX manager ON b2c_users.user ("manager");

CREATE TABLE b2c_users.rowindexschema (
    id int PRIMARY KEY,
    name text
) WITH bloom_filter_fp_chance = 0.01
    AND comment = ''
    AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
    AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
    AND dclocal_read_repair_chance = 0.1
    AND default_time_to_live = 0
    AND gc_grace_seconds = 864000
    AND max_index_interval = 2048
    AND memtable_flush_period_in_ms = 0
    AND min_index_interval = 128
    AND read_repair_chance = 0.0
    AND speculative_retry = '99.0PERCENTILE';

GRANT SELECT ON b2c_users.realm TO openam;
GRANT MODIFY ON b2c_users.realm TO openam;

GRANT SELECT ON b2c_users.user TO openam;
GRANT MODIFY ON b2c_users.user TO openam;

This script creates keyspace b2c_users, tables and grants openam role accees to this tables.

To make sure, that Apache Cassandra openam role have access to this tables run following command in cqlsh console:

cassandra@cqlsh> LIST ALL;

 role   | username | resource                | permission
--------+----------+-------------------------+------------
 openam |   openam | <table b2c_users.realm> |     SELECT
 openam |   openam | <table b2c_users.realm> |     MODIFY
 openam |   openam |  <table b2c_users.user> |     SELECT
 openam |   openam |  <table b2c_users.user> |     MODIFY

OpenAM Setup

Open OpenAM administration console, goto Realms select target realm, then goto DataStores and create new DataStore.

OpenAM Create Cassandra DataStore

Then setup following settins:

Server settings

Setting Value
Servers Cassandra node names, for example cassandra-1
User name Cassandra role, that have access to cassandra tables, for example openam
Password Cassandra role password, for example openam
Password (confirm) openam
Keyspace Cassandra keyspace for this realm b2c_users

Plugin configuration

Setting Value
Database Repository Plugin Class Name org.openidentityplatform.openam.cassandra.Repo
Tables group=group
user=user
realm=realm
Operations realm=read,create,edit,delete,service
group=read,create,edit,delete
user=read,create,edit,delete,service

User Configuration

Setting Value
TTL realm:attr1=86400
user:attr2=86400
group:attr3=86400
Attribute Name of User Status inetuserstatus
User Status Active Value Active

Group Configuration

|Setting|Value| |——-|—————————————————| |Attribute Name for Group Membership|memberOf|

Then click Save button

You can check datastore. Goto Subjects -> Users and create new user account. For example:

OpenAM Create Cassandra User Account

Click Save and make sure, this record exists in Cassandra:

cassandra@cqlsh:b2c_users> SELECT uid, cn, sn FROM b2c_users.user ;

 uid  | cn           | sn
------+--------------+---------
 john | {'John Doe'} | {'Doe'}

(1 rows)