Highlevel setup

For the basic service we want to have a rather simple attribute model to be exposed by the VOpaas AA. The AA of the basis service should only rely on providing attributes that exist in well known schema. If a VO needs custom attributes, they should be using the advanced services.

COmanage standardizes on eduPerson Schema, but also allows arbitrary attributes to be configured to express roles and rights within the VO. In addition Comanage allows groups and group memberships to be defined.

For scalability we want Comanage to be decoupled from the AAs, so we can scale the AA separately (see technical design). We will use a MySQL database in master-slave mode to achieve this. The master database will be connected to CoManage, whereas the slaves (1-n) will be on the VMs that provide the AA capabilities. To avoid possible issue with the MySQL data model of the Comanage application, we will decouple the data model from CoManage from the data model we will use for the AAs. In addition this allows us to tune and tweak these databases specifically for their tasks (read/write vs write once, read many). To transfer the data from the CoManage database to the AA database we will use a standard feature of Comanage called the provisioning plugins. A provisioning plugin may be defined for a VO to deliver in real time changes to the data that is being managed via CoManage into a so called "provisioning target". We will define the same plugin for all VOs, which will provision data to the AA database which lives on the Master MySQL database, which will act as our provisioning target. The mysql master-slave mechanism will then peculate the changes towards the slave nodes.

Mapping attributes between CoManage and AA db

Mapping attributes from the Comanage schema to the AA schema will be done by the CoManage SQL provisioner plugin. This plugin will contain the business logic to perform the right mappings.

Given that Comanage already supports eduPERSON schema, it seems logical to use a one on one mapping of these attributes in the AA database schema. Only consideration to take into account is that several attributes may actually be multi valued, also we will not support all attributes in eduPerson, as some are rarely used, and others come with data protection constraints.

For the custom attributes that may be defined in Comanage to define roles and rights, as well as for groups a different approach is needed:

  • roles and rights will be mapped all  attributes into an eduPersonEntitlement attribute, for which the values will be scoped to the VO and will follow a described semantic setup.
  • all groups will be expressed as IsMemeberOf attribute values, again scoped to the VO

This behaviour is very similar to the current LDAP provisioner in CoManage.

Supported attributes

The following attributes are supported:

friendly nameOID
eduPersonAffiliation1.3.6.1.4.1.5923.1.1.1.1
eduPersonEntitlement1.3.6.1.4.1.5923.1.1.1.7
eduPersonPrincipalName1.3.6.1.4.1.5923.1.1.1.6
eduPersonScopedAffiliation1.3.6.1.4.1.5923.1.1.1.9
eduPersonUniqueId1.3.6.1.4.1.5923.1.1.1.13
eduPersonOrcid1.3.6.1.4.1.5923.1.1.1.16
cn2.5.4.3
description2.5.4.13
displayName2.16.840.1.113730.3.1.241
facsimileTelephoneNumber2.5.4.23
givenName2.5.4.42
l2.5.4.7
labeledURI1.3.6.1.4.1.250.1.57
mail0.9.2342.19200300.100.1.3
mobile0.9.2342.19200300.100.1.41
o2.5.4.10
ou2.5.4.11
postalAddress2.5.4.16
postalCode2.5.4.17
sn2.5.4.4
st2.5.4.8
street2.5.4.9
telephoneNumber2.5.4.20
title2.5.4.12
uid0.9.2342.19200300.100.1.1
isMemberOf1.3.6.1.4.1.5923.1.5.1.1

MySQL AA data model

Because we cannot beforehand know how many attribute values we need to store for a given user (multiple emails, group memberships, entitlements) we will store the AA data in a flat table that mimics a key value store.

Database CO_AA

Table attribute holds a lookup table of the attributes we support.

CREATE TABLE `attribute` (
  `a_id` int(11) NOT NULL,
  `a_name` varchar(256) DEFAULT NULL,
  `a_oid` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`a_id`)
)
INSERT INTO `a_type` VALUES (1,'eduPersonAffiliation','urn:oid:1.3.6.1.4.1.5923.1.1.1.1'),(2,'eduPersonEntitlement','urn:oid:1.3.6.1.4.1.5923.1.1.1.7'),(3,'eduPersonPrincipalName','urn:oid:1.3.6.1.4.1.5923.1.1.1.6'),(4,'eduPersonScopedAffiliation','urn:oid:1.3.6.1.4.1.5923.1.1.1.9'),(5,'eduPersonUniqueId','urn:oid:1.3.6.1.4.1.5923.1.1.1.13'),(6,'eduPersonOrcid','urn:oid:1.3.6.1.4.1.5923.1.1.1.16'),(7,'cn','urn:oid:2.5.4.3'),(8,'description','urn:oid:2.5.4.13'),(9,'displayName','urn:oid:2.16.840.1.113730.3.1.241'),(10,'facsimileTelephoneNumber','urn:oid:2.5.4.23'),(11,'givenName','urn:oid:2.5.4.42'),(12,'l','urn:oid:2.5.4.7'),(13,'labeledURI','urn:oid:1.3.6.1.4.1.250.1.57'),(14,'mail','urn:oid:0.9.2342.19200300.100.1.3'),(15,'mobile','urn:oid:0.9.2342.19200300.100.1.41'),(16,'o','urn:oid:2.5.4.10'),(17,'ou','urn:oid:2.5.4.11'),(18,'postalAddress','urn:oid:2.5.4.16'),(19,'postalCode','urn:oid:2.5.4.17'),(20,'sn','urn:oid:2.5.4.4'),(21,'st','urn:oid:2.5.4.8'),(22,'street','urn:oid:2.5.4.9'),(23,'telephoneNumber','urn:oid:2.5.4.20'),(24,'title','urn:oid:2.5.4.12'),(25,'uid','urn:oid:0.9.2342.19200300.100.1.1'),(26,'isMemberOf','urn:oid:1.3.6.1.4.1.5923.1.5.1.1');

 

Table user is a lookup table for the identifier of the users we have data for

CREATE TABLE `user` (
  `u_id` int(11) NOT NULL,
  `u_identifier` varchar(256) NOT NULL,
  PRIMARY KEY (`uid`),
  KEY `u_identifier` (`u_identifier`)
)

 

The table vo is a lookup table that holds the collaborative organisations that we have data for

CREATE TABLE `vo` (
  `vo_id` int(11) NOT NULL,
  `vo_identifier` varchar(256) NOT NULL,
  PRIMARY KEY (`vid`)
)

 

The attr provides a key - value pair based table of the attributes we have per user and per VO

CREATE TABLE `attr` (
  `attr_values_id` int(11) NOT NULL AUTO_INCREMENT,
  `vo_id` int(11) NOT NULL,
  `u_id` int(11) NOT NULL,
  `a_id` int(11) NOT NULL,
  `a_val` varchar(2048) DEFAULT NULL,
  PRIMARY KEY (`attr_values_id`),
  KEY `v_id` (`v_id`,`u_id`)
)

For the AA we need a per user, per VO representation of the attributes. To this end we create a view user_attributes that will be queried by the aa.

CREATE VIEW `user_attributes` AS select `u`.`user_identifier` AS `user_identifier`,group_concat(if((`atype`.`a_oid` = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.1'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:1.3.6.1.4.1.5923.1.1.1.1`,group_concat(if((`atype`.`a_oid` = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.7'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:1.3.6.1.4.1.5923.1.1.1.7`,group_concat(if((`atype`.`a_oid` = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:1.3.6.1.4.1.5923.1.1.1.6`,group_concat(if((`atype`.`a_oid` = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.9'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:1.3.6.1.4.1.5923.1.1.1.9`,group_concat(if((`atype`.`a_oid` = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.13'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:1.3.6.1.4.1.5923.1.1.1.13`,group_concat(if((`atype`.`a_oid` = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.16'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:1.3.6.1.4.1.5923.1.1.1.16`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.3'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.3`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.13'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.13`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.16.840.1.113730.3.1.241'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.16.840.1.113730.3.1.241`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.23'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.23`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.42'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.42`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.7'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.7`,group_concat(if((`atype`.`a_oid` = 'urn:oid:1.3.6.1.4.1.250.1.57'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:1.3.6.1.4.1.250.1.57`,group_concat(if((`atype`.`a_oid` = 'urn:oid:0.9.2342.19200300.100.1.3'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:0.9.2342.19200300.100.1.3`,group_concat(if((`atype`.`a_oid` = 'urn:oid:0.9.2342.19200300.100.1.41'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:0.9.2342.19200300.100.1.41`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.10'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.10`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.11'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.11`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.16'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.16`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.17'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.17`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.4'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.4`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.8'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.8`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.9'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.9`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.20'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.20`,group_concat(if((`atype`.`a_oid` = 'urn:oid:2.5.4.12'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:2.5.4.12`,group_concat(if((`atype`.`a_oid` = 'urn:oid:0.9.2342.19200300.100.1.1'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:0.9.2342.19200300.100.1.1`,group_concat(if((`atype`.`a_oid` = 'urn:oid:1.3.6.1.4.1.5923.1.5.1.1'),`a`.`a_val`,NULL) separator ',') AS `urn:oid:1.3.6.1.4.1.5923.1.5.1.1` from (((`attr` `a` left join `a_type` `atype` on((`a`.`a_id` = `atype`.`a_id`))) left join `user` `u` on((`a`.`u_id` = `u`.`uid`))) left join `vo` `v` on((`a`.`v_id` = `v`.`vid`))) order by `atype`.`a_oid`

Implementation of AA with SimpleSAMLphp (plan)

We can start out with a stock simpleSAMLphp. Currently (20 June, 2016) this is version 1.14.4. : https://simplesamlphp.org/

The "Attribute Authority" module has to be added to this installation: https://github.com/NIIF/simplesamlphp-module-aa

This provides the functionality for handling Attribute Queries.

After that, we need to create a new Authentication Processing Filter by extending SimpleSAML_Auth_ProcessingFilter. The best way to implement this is to create a new module called "comanage" or similar. 

The important part of the code should look like this:

class sspmod_comanage_Auth_Process_Comanage extends SimpleSAML_Auth_ProcessingFilter
{
	public function __construct($config, $reserved) {
		//fetch config
	}

     public function process(&$state) {
        $nameId = $state['Attributes'][(configured nameId)][0];
		
		$spid = $state['Destination']['entityid'];
        // database lookup comes here
        $state['Attributes'] = //set the attributes to return
     }
}

Then ssp has to be configured to use the newly created module in config/config.php

   authproc.aa = array(
       ...
       '60' => array(
            'class' => 'comanage:Comanage',
            'nameId_attribute_name' =>  'subject_nameid', // look at the aa authsource config
            'comanage_db_host' =>          '()',
			'comanage_db_port' =>          '()',
			'comanage_db_dbname' =>          '()',
			'comanage_db_user' =>          '()',
			'comanage_db_password' =>          '()'
       ),


Implementation of AA with SimpleSAMLphp v0.1 (based on plan above)

The database scheme in use is as follows:

Table and view definitions:

CREATE TABLE `attr` (
  `attr_values_id` int(11) NOT NULL,
  `vo_id` int(11) NOT NULL,
  `u_id` int(11) NOT NULL,
  `a_id` int(11) NOT NULL,
  `a_val` varchar(2048) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `attribute_defs` (
  `a_id` int(11) NOT NULL,
  `a_name` varchar(256) DEFAULT NULL,
  `a_oid` varchar(256) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `sp` (
  `sp_id` int(11) NOT NULL,
  `entity_id` varchar(256) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `spvo` (
  `spvo_id` int(11) NOT NULL,
  `sp_id` int(11) NOT NULL,
  `vo_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `user` (
  `u_id` int(11) NOT NULL,
  `u_identifier` varchar(256) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `vo` (
  `vo_id` int(11) NOT NULL,
  `vo_identifier` varchar(256) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

The view that makes it possible to query everything by one SELECT statement

DROP TABLE IF EXISTS `attributeValuesView`;
CREATE ALGORITHM=UNDEFINED DEFINER=`vopaas`@`localhost` SQL SECURITY DEFINER VIEW `attributeValuesView`  AS  select `attribute_defs`.`a_name` AS `attrName`,`attribute_defs`.`a_oid` AS `oid`,`attr`.`a_val` AS `value`,`user`.`u_identifier` AS `userNameId`,`sp`.`entity_id` AS `spEntityId`,`vo`.`vo_identifier` AS `vo` from (((((`attr` join `attribute_defs` on((`attr`.`a_id` = `attribute_defs`.`a_id`))) join `vo` on((`attr`.`vo_id` = `vo`.`vo_id`))) join `user` on((`attr`.`u_id` = `user`.`u_id`))) join `spvo` on((`vo`.`vo_id` = `spvo`.`vo_id`))) join `sp` on((`spvo`.`sp_id` = `sp`.`sp_id`))) ;

Indexes and auto_increment id declarations:

ALTER TABLE `attr`
  ADD PRIMARY KEY (`attr_values_id`),
  ADD KEY `v_id` (`vo_id`,`u_id`);

ALTER TABLE `attribute_defs`
  ADD PRIMARY KEY (`a_id`);

ALTER TABLE `sp`
  ADD PRIMARY KEY (`sp_id`),
  ADD KEY `entity_id` (`entity_id`);

ALTER TABLE `spvo`
  ADD PRIMARY KEY (`spvo_id`);

ALTER TABLE `user`
  ADD PRIMARY KEY (`u_id`),
  ADD KEY `u_identifier` (`u_identifier`);

ALTER TABLE `vo`
  ADD PRIMARY KEY (`vo_id`);

ALTER TABLE `attr`
  MODIFY `attr_values_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;
ALTER TABLE `sp`
  MODIFY `sp_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
ALTER TABLE `spvo`
  MODIFY `spvo_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
ALTER TABLE `user`
  MODIFY `u_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
ALTER TABLE `vo`
  MODIFY `vo_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;

How it is selected:

SELECT oid,value FROM attributeValuesView WHERE userNameId=? AND spEntityId=?

GitLab link to the php code: https://dev.niif.hu/vopaas/vopaas-ssp-module

Ansible script for deployment:

---
# Deploys the vopaas simplesamlphp module that is handling the database connection to vopaas database
# requires a simplesamlphp installation + niif simplesamlphp aa module
- hosts: vopaas-aa

  tasks:
  - name: download vopaas-module files
    unarchive: src=vopaas-ssp-module.tar.gz dest={{ssp_path}}/modules

  - name: enable vopaas-module in configuration
    blockinfile:
      dest: "{{ssp_path}}/config/config.php"
      # this line is chosen because it is before the authproc.idp segment
      insertafter: "shib13.signresponse"
      block: |
            'authproc.aa' => array(
                '60' => array(
                     'class' => 'vopaas:Vopaas',
                     'nameId_attribute_name' =>  'eduPersonPrincipalName',
                     'vopaas_db_host' => '{{vopaas_db_host}}',
                     'vopaas_db_port' => '{{vopaas_db_port}}',
                     'vopaas_db_dbname' => '{{vopaas_db_dbname}}',
                     'vopaas_db_user' => '{{vopaas_db_user}}',
                     'vopaas_db_password' => '{{vopaas_db_password}}'
                ),
                90 => 'core:LanguageAdaptor',
            ),
  • No labels