forgerock https://www.vincentliefooghe.net/ fr Problème de connexion ldapsearch sur un AD LDS https://www.vincentliefooghe.net/content/probl%C3%A8me-connexion-ldapsearch-sur-un-ad-lds <span property="dc:title" class="field field--name-title field--type-string field--label-hidden">Problème de connexion ldapsearch sur un AD LDS</span> <div property="content:encoded" class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>J'ai été confronté à un problème curieux d'accès à un serveur AD LDS à partir d'un container de type Forgerock DS. </p> <p>Côté réseau, tout était OK, les ports ouverts d'un côté comme de l'autre.</p> <p>Par contre une commande <em>ldapsearch</em> tombait en erreur :</p> <pre> /opt/opendj/bin/ldapsearch --hostname 10.1.2.3 --port 389 -D "CN=admin,CN=Admin-Users,dc=example,dc=fr" -w admin2003 \ -b "CN=Applications,DC=example,DC=fr" "(objectclass=exApplications)" Unable to connect to the server: 91 (Connect Error) Additional Information: Remote close</pre><p>Pas très parlant.</p> <p>Côté AD LDS, en modifiant le niveau de logs, on a :</p> <pre> Internal event: The LDAP server returned an error. Additional Data Error value: 00000057: LdapErr: DSID-0C0C0095, comment: Error decoding ldap message, data 0, v4563</pre><p>Peu d'éléments complémentaires. Sachant qu'en plus, en local sur la machine AD LDS, la même connexion se fait sans problème.</p> <p>En investiguant un peu plus, je tente une connexion en mode "startSSL", pour voir si j'ai le même comportement :</p> <pre> /opt/opendj/bin/ldapsearch --useStartTls --hostname 10.1.2.3</pre><p>Et là, le message d'erreur côté machine cliente est différent et me met sur la piste :</p> <pre> An error occurred while parsing the command-line arguments: You may not provide both the --useStartTls and the --useSsl arguments See "ldapsearch --help" to get more usage help</pre><p>Comme je n'ai pas précisé les 2 options (useStartTls et useSsl) dans ma ligne de commande, l'option useSsl doit venir d'un paramétrage par défaut.</p> <p>Et en effet, par défaut les commandes ldap utilisent un fichier de propriétés (<em>/opt/opendj/config/tools.properties</em>) qui définit un certain nom de paramètres par défaut.<br /> On y trouvait notamment :</p> <pre> useSsl=true trustAll=true</pre><p>Du coup lorsque je tentais une connexion "en clair" sur le port 389, AD LDS recevait une requête sur un port non chiffré, mais avec une instruction qui disait que la connexion devait être chiffrée... d'où les messages d'erreur.</p> <p>En utilisant l'option "--noPropertiesFile" dans le ldapsearch, on peut du coup accéder en LDAP sur le port 389, et récupérer les données.</p> <p>L'autre option (plus sécurisée), serait d'utiliser du LDAPS entre les deux serveurs.</p> </div> <span rel="sioc:has_creator" class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/vincentl" typeof="schema:Person" property="schema:name" datatype="">vincentl</span></span> <span property="dc:date dc:created" content="2022-05-13T12:05:59+00:00" datatype="xsd:dateTime" class="field field--name-created field--type-created field--label-hidden">ven 13/05/2022 - 14:05</span> <div class="field field--name-field-categorie field--type-entity-reference field--label-above"> <div class="field__label">Catégorie</div> <div class="field__item"><a href="/cat%C3%A9gorie/iam" hreflang="fr">IAM</a></div> </div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tag</div> <div class="field__items"> <div class="field__item"><a href="/tags/ldap" hreflang="fr">ldap</a></div> <div class="field__item"><a href="/tags/forgerock" hreflang="fr">forgerock</a></div> </div> </div> <section class="field field--name-comment-node-blog field--type-comment field--label-hidden comment-wrapper"> <h2 class="title comment-form__title">Ajouter un commentaire</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=216&amp;2=comment_node_blog&amp;3=comment_node_blog" token="93czUIzLBmw8jkXBBdRQU08vkcT1Wk3MoNH84UAYHfA"></drupal-render-placeholder> </section> Fri, 13 May 2022 12:05:59 +0000 vincentl 216 at https://www.vincentliefooghe.net Pass-Through Authentication et migration des mots de passe https://www.vincentliefooghe.net/content/pass-through-authentication-et-migration-des-mots-passe <span class="field field--name-title field--type-string field--label-hidden">Pass-Through Authentication et migration des mots de passe</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><h2>Contexte</h2> <p>Dans un projet de migration d'un annuaire AD LDS vers un Forgerock DS, le principal problème est de pouvoir récupérer les mots de passe.</p> <p>En effet, AD LDS (et AD DS) ne permettent pas d'exporter le hash du mot de passe en LDIF, et il n'est pas possible non plus <em>simplement</em> de les récupérer, même en passant par des outils tels que <em>creddump</em> , <em>ntdsextract</em> ou autre.</p> <p>Du coup, l'idée peut être intéressante de paramétrer l'annuaire Forgerock DS en mode <em>pass through authentication</em>, d'activer le cache, et ensuite d'utiliser le mot de passe local, une fois la première authentification correcte réalisée sur l'AD LDS (ou n'importe quel annuaire).</p> <h2>Pré-requis : import du certificat</h2> <p>Dans un premier temps, il faut importer le certificat de l'annuaire d'authentification. On peut récupérer le certificat avec une commande <em>openssl</em> :</p> <pre> <code>openssl s_client -connect authserver:2636 </code></pre><p>Une fois le certificat sauvegardé, on va l'intégrer avec <em>keytool</em> :</p> <pre> <code>keytool -importcert -alias authserver -keystore /opt/opendj/config/keystore -file ~/authserver.pem </code></pre><h2>Création d'un "fournisseur de confiance"</h2> <p>Via la commande <em>dsconfig</em>, on va déclarer le serveur d'authentification distant comme "<em>fournisseur de confiance</em>"</p> <pre> <code>dsconfig \ create-trust-manager-provider \ --hostname localhost \ --port 4444 \ --bindDN uid=admin \ --bindPassword password \ --provider-name AuthServerPKCS12 \ --type file-based \ --set enabled:true \ --set trust-store-type:PKCS12 \ --set trust-store-file:config/keystore \ --set trust-store-pin:"&amp;{file:config/keystore.pin}" \ --usePkcs12TrustStore /opt/opendj/config/keystore \ --trustStorePassword:file /opt/opendj/config/keystore.pin \ --no-prompt </code></pre><h2>Paramétrage du mode PTA</h2> <p>Il faut ensuite créer une politique d'authentification</p> <pre> <code>dsconfig \ create-password-policy \ --hostname localhost \ --port 4444 \ --bindDN uid=admin \ --bindPassword password \ --policy-name "AuthServer_PTA_Policy" \ --type ldap-pass-through \ --set primary-remote-ldap-server:authserver:636 \ --set mapped-attribute:uid \ --set mapped-search-base-dn:"cn=Users,dc=example,dc=com" \ --set mapped-search-bind-dn:"uid=ad-bind-account,cn=admins,dc=example,dc=com" \ --set mapped-search-bind-password:ZmQ5OWUw-ADpassword \ --set trust-manager-provider:AuthServerPKCS12 \ --set mapping-policy:mapped-search \ --set use-password-caching:true \ --set cached-password-storage-scheme:PBKDF2-HMAC-SHA256 \ --set cache-password-ttl:24h \ --set use-ssl:true \ --usePkcs12TrustStore /opt/opendj/config/keystore \ --trustStorePassword:file /opt/opendj/config/keystore.pin \ --no-prompt </code></pre><p>On précise notamment :</p> <ul><li>le <em>hostname</em> et le port du serveur d'authentification (dans notre exemple, <strong>authserver</strong>, qui écoute sur le port <strong>636</strong>)</li> <li>la base de recherche pour les utilisateurs</li> <li>l'attribut commun entre le compte local et le compte sur le serveur d'authentification, dans l'exemple on utilise <strong>uid</strong> (il faut donc que l'attribut soit bien unique et identique sur les 2 serveurs)</li> <li>le DN du compte sur le serveur distant</li> <li>le mot de passe du compte distant</li> <li>Le trust manager (déclaré ci-avant)</li> </ul><p>En optionnel, on active le "<em>password caching</em>", qui permet, lors d'une première authentification réalisée avec succès, de cacher le mot de passe en local pour éviter les multiples accès sur le serveur d'authentification distant. Pour ceci, il faut également définir le schéma de stockage du mot de passe caché.</p> <p>On positionne la valeur du cache (TTL) à 24 heures.</p> <h2>Assignation de la password policy à un utilisateur</h2> <p>Pour que la politique de type "<em>pass through</em>" soit appliquée à l'utilisateur, il faut l'assigner de manière explicite, par exemple via un fichier ldif :</p> <pre> <code>dn: cn=user1,ou=Externes,ou=People,dc=example,dc=com changetype: modify add: ds-pwp-password-policy-dn ds-pwp-password-policy-dn: cn=AuthServer_PTA_Policy,cn=Password Policies,cn=config </code></pre><p>Si l'utilisateur tente ensuite de se connecter au serveur DS, on peut voir les logs sur le serveur d'authentification (ici il s'agit des logs d'une autre instance Forgerock DS, utilisée pour les tests):</p> <pre> <code>{"eventName":"DJ-LDAP","client":{"ip":"127.0.0.1","port":60984},"server":{"ip":"127.0.0.1","port":2636},"request":{"protocol":"LDAPS","operation":"SEARCH","connId":8,"msgId":9,"dn":"cn=Users,dc=example,dc=com","scope":"sub","filter":"(uid=1189670333702hawl)","attrs":["1.1"]},"transactionId":"0a31b37e-b7f3-4854-9154-5ebad695ff24-488","response":{"status":"SUCCESSFUL","statusCode":"0","elapsedTime":1,"elapsedTimeUnits":"MILLISECONDS","nentries":1},"userId":"uid=ad-bind-account,ou=admins,dc=example,dc=com","timestamp":"2022-04-04T14:15:36.803Z","_id":"0a31b37e-b7f3-4854-9154-5ebad695ff24-490"} {"eventName":"DJ-LDAP","client":{"ip":"127.0.0.1","port":60986},"server":{"ip":"127.0.0.1","port":2636},"request":{"protocol":"LDAPS","operation":"BIND","connId":9,"msgId":8,"version":"3","dn":"cn=user1,cn=Users,dc=example,dc=com","authType":"SIMPLE"},"transactionId":"0a31b37e-b7f3-4854-9154-5ebad695ff24-491","response":{"status":"SUCCESSFUL","statusCode":"0","elapsedTime":1,"elapsedTimeUnits":"MILLISECONDS","additionalItems":{"ssf":"256"}},"userId":"cn=user1,cn=Users,dc=example,dc=com","timestamp":"2022-04-04T14:15:36.805Z","_id":"0a31b37e-b7f3-4854-9154-5ebad695ff24-493"} </code></pre><p>Si on a activé le cache, on peut constater qu'après une première authentification correcte, l'annuaire distant n'est plus sollicité lors des authentifications suivantes, sauf si le mot de passe ne correspond plus à la valeur en cache.</p> <p>Au niveau local, le mot de passe est stocké dans un attribut opérationnel : <em>ds-pta-cached-password</em>. On y trouve également la date de mise en cache du mot de passe.</p> <p>Par exemple :</p> <pre> <code>dn: cn=user1,ou=Externes,ou=People,dc=example,dc=com ds-pta-cached-password: {SSHA512}CpdBFPCD2LrQpDOFK+hjOifS+hwRz79yWYjMFS543iyAmHZIliBgHnoKjY+HhERC61eQMqNmkgEvrlXYtkmhyK9HK+0gqQQBraFUUd2TViM= ds-pta-cached-password-time: 20220404141914Z ds-pwp-password-policy-dn: cn=AuthServer_PTA_Policy,cn=Password Policies,cn=config </code></pre><h2>Migration progressive des mots de passe</h2> <p>En utilisant le mécanisme de cache et l'authentification en mode <em>pass through</em>, on pourra donc envisager de migrer progressivement les mots de passe de la manière suivante :</p> <ul><li>pour tous les utilisateurs qui doivent être migrés, assigner la politique de mot de passe de type "PTA Policy"</li> <li>pour les autres, on peut se reposer sur les politiques de mots de passe par défaut, ou celles définies dans les données (de type <em>subentry</em>)</li> </ul><p>Il faudra ensuite avoir un traitement complémentaire, qui sera lancé à intervalle régulier, et avec une fréquence supérieure à la durée de cache, qui va réaliser le traitement suivant :</p> <ul><li>rechercher les comptes qui ont l'attribut <em>ds_pta-cached-password</em> positionné, avec la politique de mot de passe "PTA"</li> <li>Pour ces comptes :</li> <li>mettre à jour l'attribut <em>userPassword</em> avec la valeur de l'attribut de cache</li> <li>supprimer l'assignation de la politique de mot de passe, pour récupérer celle définie par défaut.</li> </ul><p>Afin d'améliorer les performances, on pourra créer un index sur l'attribut :</p> <pre> <code>create-backend-index --backend-name amIdentityStore --index-name ds-pta-cached-password --type generic --set index-type:presence </code></pre><p>Ceci peut donc permettre d'utiliser les mots de passe actuels, tout en déconnectant au fil de l'eau le lien avec l'ancien annuaire.</p> <p>Au bout d'un certain temps, on pourra considérer que les comptes qui ont encore l'ancienne politique de mots de passe (ou qui n'ont pas encore d'attribut <em>userPassword</em>) ne sont pas utilisés, et on pourra faire un peu de "ménage", ou faire une campagne de renouvellement de mots de passe.</p> <p> </p> <p> </p> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/vincentl" typeof="schema:Person" property="schema:name" datatype="">vincentl</span></span> <span class="field field--name-created field--type-created field--label-hidden">lun 04/04/2022 - 17:09</span> <div class="field field--name-field-categorie field--type-entity-reference field--label-above"> <div class="field__label">Catégorie</div> <div class="field__item"><a href="/cat%C3%A9gorie/iam" hreflang="fr">IAM</a></div> </div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tag</div> <div class="field__items"> <div class="field__item"><a href="/tags/forgerock" hreflang="fr">forgerock</a></div> <div class="field__item"><a href="/tags/ds" hreflang="fr">ds</a></div> <div class="field__item"><a href="/tags/opendj" hreflang="fr">opendj</a></div> </div> </div> <section class="field field--name-comment-node-book field--type-comment field--label-hidden comment-wrapper"> <h2 class="title comment-form__title">Ajouter un commentaire</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=215&amp;2=comment_node_book&amp;3=comment_node_book" token="5eq-zcWEYJm_Yk06U0kNvELXXhjQF7kEMPtT6Lez1yY"></drupal-render-placeholder> </section> Mon, 04 Apr 2022 15:09:40 +0000 vincentl 215 at https://www.vincentliefooghe.net Utilisation d'attributs Json dans Forgerock DS https://www.vincentliefooghe.net/content/utilisation-dattributs-json-dans-forgerock-ds <span class="field field--name-title field--type-string field--label-hidden">Utilisation d&#039;attributs Json dans Forgerock DS</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><h2>Problématique</h2> <p>Il arrive des cas où l'utilisation des attributs "simples", qu'ils soient mono ou multi-valués, n'est pas satisfaisante par rapport aux besoins des utilisateurs.</p> <p>Prenons le cas suivant : on veut gérer des délégations pour X types de profils et chacun avec des rôles spécifiques.</p> <p>Soit :</p> <ul><li>délégation pour une société S1, avec les rôles RS1, RS2, RS3</li> <li>délégation pour une société S2, avec les rôles RS1, RS3</li> <li>délégation pour un utilisateur U1, avec les rôles RU1, RU2</li> </ul><p>Si on utilise un attribut de type chaîne de caractères, on peut s'en sortir avec des concaténations et des caractères de séparation. Par exemple :</p> <pre> <code>type|identifiant du type|role1-role2-role3 </code></pre><p>Dans ce cas on doit déjà gérer 2 types de séparateurs :le "|" pour les différents éléments, et le "-" pour les rôles.</p> <p>Dans notre exemple, ça donnerait :</p> <ul><li>societe|S1|RS1-RS2-RS3</li> <li>societe|S2|RS1-RS3</li> <li>utilisateur|U1|RU1-RU2</li> </ul><p>Par contre pour les recherches, ça devient plus compliqué, sauf à pré-calculer un filtre, en jouant avec les séparateurs.</p> <h2>Attribut Json</h2> <p>Depuis la version 5.5 (au moins), Forgerock permet d'utiliser une syntaxe Json dans les attributs, ce qui permet de gérer des données structurées à l'intérieur d'un attribut.</p> <h3>Déclaration dans le schéma</h3> <p>L'ajout d'un attribut de type JSON se fait de manière <em>traditionnelle</em>, en utilisant une syntaxe spécifique (<em>1.3.6.1.4.1.36733.2.1.3.1</em>).</p> <p>On ajoute ensuite les attributs dans le schéma :</p> <pre> <code>attributeTypes: ( 1.3.6.1.4.1.18472.2.101 NAME 'delegations' SYNTAX 1.3.6.1.4.1.36733.2.1.3.1 EQUALITY caseIgnoreJsonQueryMatch X-ORIGIN 'Mes tests json' ) attributeTypes: ( 1.3.6.1.4.1.18472.2.102 NAME 'jsonId' SYNTAX 1.3.6.1.4.1.36733.2.1.3.1 EQUALITY caseIgnoreJsonIdMatch SINGLE-VALUE X-ORIGIN 'Mes tests json' ) </code></pre><p>On doit également préciser le type de règle d'égalité qui sera utilisée. Il en existe 4 pré-définies :</p> <ul><li>caseIgnoreJsonQueryMatch : 2 objets sont identiques si tous leurs champs sont les mêmes, à la casse près</li> <li>caseExactJsonQueryMatch : 2 objets sont identiques si tous leurs champs sont les mêmes, avec la même casse de caractères</li> <li>caseIgnoreJsonIdMatch : 2 objets sont identiques si leur champ "_id" est identique, quelle que soit la casse de caractères</li> <li>caseExactJsonIdMatch : 2 objets sont identiques si leur champ "_id" est identique, avec la même casse de caractères.</li> </ul><p>Quelques exemples pour illustrer les cas. En supposant que j'ai 2 entrées dans l'annuaire LDAP, avec :</p> <ol><li>{"_id":"json","type":"user","value":"user123"}</li> <li>{"_id":"Json","type":"User","value":"USER123"}</li> <li>{"_id":"json","type":"user","value":"user345"}</li> <li>{"_id":"Json","type":"User","value":"USER345"}</li> </ol><p>Selon les cas :</p> <ul><li>avec la règle <strong>caseIgnoreJsonQueryMatch</strong>, les objets 1 et 2, et les objets 3 et 4 sont identiques</li> <li>avec la règle <strong>caseExactJsonQueryMatch</strong>, il n'y a pas de correspondance</li> <li>avec la règle <strong>caseIgnoreJsonIdMatch</strong>, les 4 objets sont identiques (ils ont le même _id)</li> <li>avec la règle <strong>caseExactJsonIdMatch</strong>, les objets 1 et 3 sont considérés identiques, ainsi que 2 et 4</li> </ul><h3>Indexation</h3> <p>Lorsqu'on indexe un attribut JSON, le comportement par défaut de Forgerock DS est de maintenir des clés pour chaque champ JSON. De ce fait, si l'attribut JSON contient de nombreux champs différents (et qui ne font pas l'objet de recherches) on peut choisir de n'indexer que ces champs. Dans ce cas, il faudra définir soi-même une règle (de type <em>json-equality-matching-rule</em>).</p> <p>Si on utilise une règle de comparaison basée sur l'ID uniquement, c'est ce champ qui sera indexé, et pas les autres.</p> <h3>Recherche</h3> <p>La syntaxe du filtre de recherche est la même que celle utilisée pour les API REST Forgerock (communément appelées CREST = Common REST).</p> <p>Quelques exemples de filtres, sur la base de nos attributs <em>delegations</em> et <em>jsonId</em> déclarés précédemment :</p> <pre> <code>delegations: {"type":"societe","attributions":[{"id":"societe1","roles":["role-soc1","role-soc2","role-soc3"]},{"id":"societe2","roles":["role-soc4"]}]} delegations: {"type":"utilisateur","attributions":[{"id":"user1","roles":["role-usr1","role-usr2"]},{"id":"user2","roles":["role-usr4"]}]} delegations: {"type":"org","attributions":[{"id":"org1","roles":["role-org1","role-org2","role-org4"]}] } jsonId: {"_id":"json","type":"user","value":"user123"} jsonId: {"_id":"Json","type":"User","value":"USER123"} jsonId: {"_id":"json","type":"user","value":"user345"} jsonId: {"_id":"Json","type":"User","value":"USER345"} </code></pre><ul><li>(delegations=*) : va renvoyer toutes les entrées qui ont un attribut <em>delegations</em></li> <li>(jsonid=_id eq 'json') va renvoyer les entrées qui ont la valeur 'json' dans le champ "_id" de l'attribut</li> <li>(delegations=type eq 'utilisateur' or type eq 'societe') : va renvoyer les objets ayant l'une ou l'autre valeur dans le champ type</li> </ul><p><strong>Limitations sur les filtres de recherche</strong></p> <p>Il n'est pas possible de faire une recherche dans un tableau qui contient plusieurs objets (attributions dans notre exemple).</p> <pre> <code>(delegations=id eq "user2") </code></pre><p>ne ramène rien (on n'y accède pas directement, il faudrait passer par attributions-&gt;id).</p> <p>Par contre, si on a une structure dont l'un des attributs est un tableau, qui ne contient que des valeurs, cela fonctionne.</p> <p>Si on a des attributs comme ceci :</p> <pre> <code>delegations: {"type":"utilisateur","id":"user1","roles":["role-usr1","role-usr2"]} delegations: {"type":"utilisateur","id":"user2","roles":["role-usr4","role-usr2"]} delegations: {"type":"societe","id":"societe1","roles":["role-soc1","role-soc2","role-soc3"]} delegations: {"type":"societe","id":"societe2","roles":["role-soc4"]} delegations: {"type":"org","id":"org2","roles":["role-org1","role-org2","role-org4"]} </code></pre><p>On peut bien faire des recherches de type :</p> <pre> <code>ldapsearch -b ou=people,dc=example,dc=com '(delegations=id eq "user2")' </code></pre><p>Si on veut éviter des collisions entre les différents types, on peut combiner plusieurs recherches. Si elles portent sur plusieurs éléments de l'attribut <em>json</em>, il faut utiliser le filtre adéquat. Par exemple :</p> <pre> <code>(delegations=id eq "2" and type eq "societe") </code></pre><p>La recherche dans un tableau fonctionne également. On peut ainsi rechercher tout les objets LDAP qui ont un role "role-usr4" :</p> <pre> <code>(delegations=roles eq "role-usr4") </code></pre><p>Idem pour les objets avec un type "societe" et un rôle "role-soc1" :</p> <pre> <code>(delegations=type eq "societe" and roles eq "role-soc1") </code></pre></div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/vincentl" typeof="schema:Person" property="schema:name" datatype="">vincentl</span></span> <span class="field field--name-created field--type-created field--label-hidden">ven 04/03/2022 - 14:55</span> <div class="field field--name-field-categorie field--type-entity-reference field--label-above"> <div class="field__label">Catégorie</div> <div class="field__item"><a href="/cat%C3%A9gorie/iam" hreflang="fr">IAM</a></div> </div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tag</div> <div class="field__items"> <div class="field__item"><a href="/tags/forgerock" hreflang="fr">forgerock</a></div> <div class="field__item"><a href="/tags/opendj" hreflang="fr">opendj</a></div> <div class="field__item"><a href="/tags/ds" hreflang="fr">ds</a></div> <div class="field__item"><a href="/tags/json" hreflang="fr">json</a></div> </div> </div> <section class="field field--name-comment-node-book field--type-comment field--label-hidden comment-wrapper"> <h2 class="title comment-form__title">Ajouter un commentaire</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=214&amp;2=comment_node_book&amp;3=comment_node_book" token="sYSuQMghlx0xCAUDqXvngNEvgVyPnyCe-RJ6u6n_6dw"></drupal-render-placeholder> </section> Fri, 04 Mar 2022 13:55:34 +0000 vincentl 214 at https://www.vincentliefooghe.net OpenIDM - Sécuriser un Connecteur Database Table https://www.vincentliefooghe.net/content/openidm-s%C3%A9curiser-un-connecteur-database-table <span class="field field--name-title field--type-string field--label-hidden">OpenIDM - Sécuriser un Connecteur Database Table</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Dans un projet client qui utilise IDM 7 (après une montée de version OpenIDM 3.1 vers IDM 7 qui s'est plutôt bien passée), nous avons mis en place la gestion du cycle de vie et notamment la suppression automatique de comptes 30 jours après la fin de contrat, comme ce que l'on voit souvent.</p> <p>Il a été décidé d'archiver quelques données des utilisateurs, histoire d'avoir un peu de traces.</p> <p>Pour ce faire nous avons mis en place un connecteur Database Table, qui pointe sur une table d'une base PostgreSQL. La base utilisée est la même que celle qui héberge le <em>repository</em> IDM.</p> <p>Tout fonctionnait correctement, jusqu'au moment où une insertion dans la table est tombée en erreur.</p> <p>Le message d'erreur :</p> <pre> [122] janv. 07, 2021 9:06:00.069 AM org.identityconnectors.framework.impl.api.local.operations.CreateImpl create GRAVE: buildSelectBasedAttributeInfo in SQL: 'SELECT * FROM openidm.archive_managed_user WHERE objectid IS NULL' org.postgresql.util.PSQLException: FATAL: terminating connection due to administrator command at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2532) at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2267) [122] janv. 07, 2021 9:06:00.071 AM org.apache.tomcat.jdbc.pool.PooledConnection clearWarnings AVERTISSEMENT: Unable to clear Warnings, connection will be closed. org.postgresql.util.PSQLException: This connection has been closed. at org.postgresql.jdbc.PgConnection.checkClosed(PgConnection.java:865) at org.postgresql.jdbc.PgConnection.clearWarnings(PgConnection.java:759) at org.apache.tomcat.jdbc.pool.PooledConnection.clearWarnings(PooledConnection.java:837) at org.apache.tomcat.jdbc.pool.ConnectionPool.returnConnection(ConnectionPool.java:977) [122] janv. 07, 2021 9:06:00.073 AM org.forgerock.openidm.quartz.SchedulerServiceJob execute AVERTISSEMENT: Scheduled service "scheduler-service-group.delete" invocation reported failure: org.forgerock.json.resource.ForbiddenException: {code=500, reason=Internal Server Error, message=Operation CREATE failed with ConnectorException on system object} org.forgerock.openidm.quartz.ExecutionException: org.forgerock.json.resource.ForbiddenException: {code=500, reason=Internal Server Error, message=Operation CREATE failed with ConnectorException on system object} at org.forgerock.openidm.script.impl.ScriptRegistryService.execute(ScriptRegistryService.java:772) at org.forgerock.openidm.quartz.SchedulerServiceJob.execute(SchedulerServiceJob.java:123)</pre><p> </p> <h2>Analyse de la cause</h2> <p>En regardant dans les logs nous nous sommes aperçus que la base était indisponible périodiquement, pour une sauvegarde <em>offline</em>.</p> <p>Autant le "<em>moteur</em>" IDM était capable de reprendre son activité une fois la sauvegarde terminée et la base redémarrée, autant le connecteur n'arrivait pas à relancer une connexion.</p> <h2>Résolution</h2> <p>Après différentes recherches sur le Web et l'ouverture d'un ticket "How do I ? " chez Forgerock, j'ai pu modifier le fichier de configuration <em>provisioner.openicf-archiveDB.json</em> pour y modifier les paramètres suivants :</p> <ul><li>"validationInterval" : "30000"  (permet de limiter la fréquence de validation)</li> <li>"validationQueryTimeout" : "30" (time-out pour les requêtes de validation)</li> <li>"validationQuery" : "SELECT 1 FROM archive_managed_user" (requête SQL utilisée pour la validation)</li> <li>"testOnConnect" : true (permet de valider avant la connection)</li> <li>"testOnBorrow" : true (permet de valider avant d'utiliser une connection dans le <em>pool</em>)</li> </ul><p>Ce sont surtout les paramètres <strong>validationQuery</strong> et <strong>testOnBorrow</strong> qui permettent de gérer les cas de déconnexion.</p> <p>Une fois la nouvelle configuration mise en place, le connecteur devient <em>fault-tolerant</em> et peut reprendre son activité même après une perte de connexion à la base de données.</p> <p> </p> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/vincentl" typeof="schema:Person" property="schema:name" datatype="">vincentl</span></span> <span class="field field--name-created field--type-created field--label-hidden">jeu 07/01/2021 - 09:54</span> <div class="field field--name-field-categorie field--type-entity-reference field--label-above"> <div class="field__label">Catégorie</div> <div class="field__item"><a href="/cat%C3%A9gorie/iam" hreflang="fr">IAM</a></div> </div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tag</div> <div class="field__items"> <div class="field__item"><a href="/tags/forgerock" hreflang="fr">forgerock</a></div> <div class="field__item"><a href="/taxonomy/term/3" hreflang="fr">openidm</a></div> <div class="field__item"><a href="/tags/connecteur" hreflang="fr">connecteur</a></div> </div> </div> <section class="field field--name-comment-node-book field--type-comment field--label-hidden comment-wrapper"> <h2 class="title comment-form__title">Ajouter un commentaire</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=209&amp;2=comment_node_book&amp;3=comment_node_book" token="7WaxU_wvuZ5r5sBmYS8TtoL5QqM9MAXmAWnuTkMTqFc"></drupal-render-placeholder> </section> Thu, 07 Jan 2021 08:54:49 +0000 vincentl 209 at https://www.vincentliefooghe.net https://www.vincentliefooghe.net/content/openidm-s%C3%A9curiser-un-connecteur-database-table#comments Forgerock OpenDJ / DS - Supprimer les contrôles de syntaxe https://www.vincentliefooghe.net/content/forgerock-opendj-ds-supprimer-les-contr%C3%B4les-syntaxe <span class="field field--name-title field--type-string field--label-hidden">Forgerock OpenDJ / DS - Supprimer les contrôles de syntaxe</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Par défaut les nouveaux annuaires LDAP issus des sources OpenDS (tels que OpenDJ / DS chez Forgerock, mais aussi Ping Directory) renforcent les contrôles sur la syntaxe des attributs, le schéma, etc.</p> <p>Ceci empêche par exemple d'avoir un objet avec de multiples classes structurelles non hiérarchiques (par exemple <em>inetorgperson</em> et <em>country</em>).</p> <p>Par contre lorsqu'on importe des données d'un annuaire un peu moins strict (type Sun / Oracle DSEE), on se heurte souvent à des erreurs.</p> <p>La solution la plus propre est de modifier le fichier LDIF pour remettre au carré les données :</p> <ul><li>supprimer / modifier les attributs non conformes</li> <li>modifier le schéma pour éviter les héritages multiples</li> </ul><p>Mais ceci n'est pas toujours faisable.</p> <p>On peut alors relâcher les contrôles effectuer, en utilisant l'utilitaire <em>dsconfig </em>(testé depuis la version OpenDJ 2.6 jusque DS 7) :</p> <pre> #!/bin/bash PORT=5444 HOST=djlyreco BINDPASS="SuperPa$$w0rd" cd /path/to//opendj # Do not check schema bin/dsconfig set-global-configuration-prop --hostname $HOST --port ${PORT} --bindDN "cn=Directory Manager" \ --bindPassword ${BINDPASS} --set check-schema:false --trustAll --no-prompt # Allows multiple structural object classes (warning only) bin/dsconfig set-global-configuration-prop --hostname $HOST --port ${PORT} --bindDN "cn=Directory Manager" \ --bindPassword ${BINDPASS} --set single-structural-objectclass-behavior:warn --trustAll --no-prompt # Allows invalid attribute syntax bin/dsconfig set-global-configuration-prop --hostname $HOST --port ${PORT} \ --bindDN "cn=Directory Manager" --bindPassword ${BINDPASS} --set invalid-attribute-syntax-behavior:warn --trustAll --no-prompt # Allow pre-encoded passwords : pour l'import de compte avec des mots de passe existant bin/dsconfig -p ${PORT} -h $HOST -D "cn=Directory Manager" -w ${BINDPASS} \ set-password-policy-prop --policy-name "Default Password Policy" --set allow-pre-encoded-passwords:true -X -n</pre><p> </p> <p>La dernière ligne permet également d'importer des mots de passe pré-encodés. C'est utile en cas de migration de données entre annuaires, pour garder les mêmes mots de passe.</p> <p> </p> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">Vincent</span></span> <span class="field field--name-created field--type-created field--label-hidden">mer 09/12/2020 - 11:30</span> <div class="field field--name-field-categorie field--type-entity-reference field--label-above"> <div class="field__label">Catégorie</div> <div class="field__item"><a href="/cat%C3%A9gorie/iam" hreflang="fr">IAM</a></div> </div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tag</div> <div class="field__items"> <div class="field__item"><a href="/tags/ldap" hreflang="fr">ldap</a></div> <div class="field__item"><a href="/tags/opendj" hreflang="fr">opendj</a></div> <div class="field__item"><a href="/tags/forgerock" hreflang="fr">forgerock</a></div> </div> </div> <section class="field field--name-comment-node-book field--type-comment field--label-hidden comment-wrapper"> <h2 class="title comment-form__title">Ajouter un commentaire</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=208&amp;2=comment_node_book&amp;3=comment_node_book" token="FCUJHl0s_eMjKa8Cw3DvkSQUZ310pub5HrSmdbeosj4"></drupal-render-placeholder> </section> Wed, 09 Dec 2020 10:30:28 +0000 Vincent 208 at https://www.vincentliefooghe.net https://www.vincentliefooghe.net/content/forgerock-opendj-ds-supprimer-les-contr%C3%B4les-syntaxe#comments Upgrade Forgerock OpenDJ / DS https://www.vincentliefooghe.net/content/upgrade-forgerock-opendj-ds <span class="field field--name-title field--type-string field--label-hidden">Upgrade Forgerock OpenDJ / DS</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Deux scénarios de migration / upgrade seront étudiés :</p> <ul><li>upgrade "en place" d'un annuaire en version inférieure</li> <li>ajout dans la topologie de réplication d'un annuaire en version supérieure.</li> </ul><p>Dans cet exemple, on va procéder à une migration de la version OpenDJ 3.5.1 à la version DS 6.5.</p> <h2>Environnement</h2> <p>L'environnement de test est le suivant :</p> <p>3 serveurs : <em>dj1.id-num.com</em>, <em>dj2.id-num.com</em>, <em>dj3.id-num.com</em>.</p> <p>Dans un premier temps, seuls les serveurs dj1 et dj2 sont installés, en mode multi-maîtres :</p> <ul><li>O.S. CentOS 7</li> <li>Java jre 1.8</li> <li>OpenDJ 3.5.1, avec 2 annuaires répliqués</li> <li>Schéma de données <em>custom</em> avec attributs et classes d'objets spécifiques</li> <li>Annuaire installé dans le répertoire <code>/opt/opendj</code></li> <li>Index créé sur le serveur dj2, sur l'attribut <em>company</em></li> </ul><h3>Script d'installation</h3> <p>L'installation sur les 2 serveurs a été faite avec le script suivant :</p> <pre><code>#!/bin/sh # # Installation Forgerock OpenDJ 3.5.1 # #--------------------------------------- LDAPPORT=1389 PASS=ldapMaster REPLADM=replicationAdmin REPLPASS=replicationPassword ADMINPORT=4445 HOST1=dj1.id-num.com HOST2=dj2.id-num.com HOST3=dj3.id-num.com JAVAHOME=/opt/jre1.8.0_144 BASEDN="o=example" BASEDIR=/opt/opendj BINDIR=${BASESDIR}/bin export JAVA_HOME=${JAVAHOME} export OPENDJ_JAVA_HOME=${JAVAHOME} ${BASEDIR}/setup \ --cli \ --baseDN ${BASEDN} \ --addBaseEntry \ --ldapPort ${LDAPPORT} \ --adminConnectorPort ${ADMINPORT} \ --rootUserDN cn=Directory\ Manager \ --rootUserPassword ${PASS} \ --acceptLicense \ --no-prompt \ --noPropertiesFile </code></pre><h3>Script de réplication</h3> <p>La réplication est mise en place entre les serveurs <strong>dj1</strong> et <strong>dj2</strong>, via la commande :</p> <pre><code>${BINDIR}/dsreplication enable \ --host1 ${HOST1} --port1 ${ADMINPORT} \ --bindDN1 "cn=Directory Manager" \ --bindPassword1 ${PASS} \ --replicationPort1 8989 \ --host2 ${HOST2} --port2 ${ADMINPORT} \ --bindDN2 "cn=Directory Manager" \ --bindPassword2 ${PASS} \ --replicationPort2 8989 \ --adminUID ${REPLADM} \ --adminPassword ${REPLPASS} \ --baseDN ${BASEDN} \ --trustAll \ --no-prompt </code></pre><p>On peut ensuite lancer l'init, pour tous les serveurs de la topologie :</p> <pre><code>${BINDIR}/dsreplication \ initialize-all \ --adminUID ${REPLADM} \ --adminPassword ${REPLPASS} \ --baseDN ${BASEDN} \ --hostname $(hostname) \ --port ${ADMINPORT} \ --trustAll \ --no-prompt </code></pre><p>On vérifie, avec l'option <em>status</em> :</p> <pre><code>${BINDIR}/dsreplication status \ --adminUID ${REPLADM} \ --adminPassword ${REPLPASS} \ --port ${ADMINPORT} \ --trustAll \ --no-prompt Suffix DN : Server : Entries : Replication enabled : DS ID : RS ID : RS Port (1) : M.C. (2) : A.O.M.C. (3) : Security (4) ---------------------:---------------------:---------:---------------------:-------:-------:-------------:----------:--------------:------------- cn=Directory Manager : dj2.id-num.com:4445 : : : : : : : : o=example : dj1.id-num.com:4445 : 45 : true : 14774 : 27576 : 8989 : 0 : : false o=example : dj2.id-num.com:4445 : : true : 29513 : 481 : 8989 : 0 : : false </code></pre><h3>Schéma spécifique</h3> <p>Le schéma est modifié avec l'ajout de 2 attributs et une classe d'objet :</p> <pre><code>dn: cn=schema changetype: modify add: attributeTypes attributeTypes: ( partneruid-oid NAME 'partneruid' SUP name X-ORIGIN 'user Defined' ) attributeTypes: ( company-oid NAME 'company' SUP name X-ORIGIN 'user Defined' ) - add: objectclasses objectClasses: ( partnerperson-oid NAME 'partnerPerson' SUP inetorgperson MUST ( company ) MAY ( partneruid) ) </code></pre><h3>Version OpenDJ / DS</h3> <p>On peut vérifier la version actuelle avec la commande :</p> <pre><code>/opt/opendj/bin/dsconfig --version 3.5.1 (revision 23b322a7502f029b6d3725212c162de36f038122) </code></pre><h2>Upgrade "en place"</h2> <p>Il faut tout d'abord vérifier la trajectoire de migration supportée.</p> <p>Pour <em>Directory Services 6.5</em>, sorti fin 2018, la migration est supportée depuis la version OpenDJ 2.6 et supérieure.</p> <p>La version de Java est également à vérifier : Java 8 ou 11 pour DS 6.5. Nous utilisons une version 8 ce qui convient donc.</p> <p>Forgerock recommande de faire une sauvegarde <strong>physique</strong> du serveur à <em>upgrader</em>, en arrêtant l'annuaire et en faisant un <em>backup</em> de toute l'arborescence :</p> <pre><code>/opt/opendj/bin/stop-ds tar zcf opendj-3.5.tgz opendj </code></pre><p><strong>Note</strong> : l'upgrade en place nécessite d'arrêter l'instance d'annuaire qui va être migrée.</p> <h3>Procédure de migration</h3> <p>La procédure est simple ; elle consiste essentiellement à :</p> <ul><li>extraire l'archive de la nouvelle version au dessus de l'ancienne</li> <li>lancer la commande <strong>upgrade</strong></li> <li>vérifier et éventuellement adapter le paramétrage.</li> </ul><p>En détail :</p> <pre><code>cd /opt unzip /path/to/Software/Forgerock/DS-6.6.0.zip Archive: DS-6.6.0.zip creating: opendj/legal-notices/third-party-licenses/ creating: opendj/template/extlib/ creating: opendj/template/setup-profiles/ .../... creating: opendj/template/setup-profiles/IDM/repo/6.5/ creating: opendj/template/setup-profiles/IDM/repo/6.5/schema/ replace opendj/snmp/mib/rfc2605.txt? [y]es, [n]o, [A]ll, [N]one, [r]ename: y </code></pre><p>Répondre "A" pour écraser / remplacer les anciennes versions de fichiers.</p> <p>Lancer ensuite l'upgrade :</p> <pre><code>/opt/opendj/upgrade &gt;&gt;&gt;&gt; OpenDJ Upgrade Utility * OpenDJ configuration will be upgraded from version 3.5.1.23b322a7502f029b6d3725212c162de36f038122 to 6.5.0.e2a029a050cc6908fd144a6ca9c687e01ad56ea3 * OpenDJ data will be upgraded from version 6.0.0 to 6.5.0.e2a029a050cc6908fd144a6ca9c687e01ad56ea3 * See '/opt/opendj/logs/upgrade.log' for a detailed log of this operation &gt;&gt;&gt;&gt; Preparing to upgrade OpenDJ 5.5.0 changed the indexing algorithm for JSON equality matching rules. All JSON based attribute indexes must be rebuilt which may take a long time. Do you want to rebuild the indexes automatically at the end of the upgrade? (yes/no) [no]: yes OpenDJ 6.5.0 changed the indexing algorithm for replication metadata. Its index must be rebuilt which may take a long time; without a working index every server start will take longer than normal. Do you want to rebuild the index automatically at the end of the upgrade? (yes/no) [no]: yes The upgrade is ready to proceed. Do you wish to continue? (yes/no) [yes]: &gt;&gt;&gt;&gt; Performing upgrade Adding configuration for schema providers........................... 100% Adding configuration entry 'dn: cn=Core Schema,cn=Schema Providers,cn=config'................................................ 100% Removing top configuration entry for matching rules................. 100% Removing configuration for syntaxes................................. 100% .../... Replacing schema file '02-config.ldif'.............................. 100% Archiving concatenated schema....................................... 100% Migrating replication changelog files to 6.5.0 format............... 100% &gt;&gt;&gt;&gt; OpenDJ configuration was successfully upgraded from version 3.5.1.23b322a7502f029b6d3725212c162de36f038122 to 6.5.0.e2a029a050cc6908fd144a6ca9c687e01ad56ea3 &gt;&gt;&gt;&gt; OpenDJ data was successfully upgraded from version 6.0.0 to 6.5.0.e2a029a050cc6908fd144a6ca9c687e01ad56ea3 &gt;&gt;&gt;&gt; Performing post upgrade tasks Rebuilding index(es) '[.caseIgnoreJsonQueryMatch, .caseExactJsonQueryMatch, ds-sync-hist.changeSequenceNumberOrderingMatch]' for base dn(s) '[o=example]'....................................................... 100% &gt;&gt;&gt;&gt; Post upgrade tasks complete </code></pre><p>Les logs de la migration se trouvent dans <code>$DSHOME/logs/upgrade.log</code>.</p> <p>On peut ensuite démarrer l'instance, via la commande <code>bin/start-ds</code></p> <p>L'annuaire est bien passé en version supérieure :</p> <pre><code>/opt/opendj/bin/dsconfig --version 6.5.0 (revision e2a029a050cc6908fd144a6ca9c687e01ad56ea3) </code></pre><h3>Post Upgrade</h3> <p>Si on part d'une version inférieure à la version 5.5, Forgerock rcommande de donner des privilèges spécifiques au compte de réplication. Les privilèges à ajouter sont les suivants :</p> <ul><li>bypass-lockdown</li> <li>monitor-read</li> <li>server-lockdown</li> </ul><p>Ceci peut être fait en LDIF. Dans notre cas, le compte utilisé est <em>replicationAdmin</em> :</p> <pre><code>dn: cn=replicationAdmin,cn=Administrators,cn=admin data changetype: modify add: ds-privilege-name ds-privilege-name: bypass-lockdown ds-privilege-name: monitor-read ds-privilege-name: server-lockdown </code></pre><p>La modification n'est à faire qu'une seule fois, les données étant répliquées.</p> <p>L'upgrade peut ensuite être fait pour tous les serveurs de la topologie.</p> <h2>Ajout d'un nouveau serveur dans la topologie</h2> <p>Dans cette option, on va ajouter un nouveau serveur dans la topologie de réplication. Ceci peut donc être fait sans interruption de service.</p> <h3>Installation de DS 6.5</h3> <p>L'installation ds DS 6.5 se fait quasiment de la même manière que OpenDJ 3.5. Les seules différences : pas d'option <em>--cli</em> et l'option <em>--hostname</em> qui est obligatoire.</p> <pre><code>LDAPPORT=1389 PASS=ldapMaster REPLADM=replicationAdmin REPLPASS=replicationPassword ADMINPORT=4445 HOST1=dj1.id-num.com HOST2=dj2.id-num.com HOST3=dj3.id-num.com JAVAHOME=/opt/jre1.8.0_144 BASEDN="o=example" BASEDIR=/opt/opendj BINDIR=${BASESDIR}/bin export JAVA_HOME=${JAVAHOME} export OPENDJ_JAVA_HOME=${JAVAHOME} /opt/opendj/setup \ --hostname ${HOST3} \ --baseDN ${BASEDN} \ --addBaseEntry \ --ldapPort ${LDAPPORT} \ --adminConnectorPort ${ADMINPORT} \ --rootUserDN cn=Directory\ Manager \ --rootUserPassword ${PASS} \ --acceptLicense </code></pre><p>A ce stade, on a donc un annuaire en mode "standby". Nous pouvons l'inclure dans le domaine de réplication.</p> <h3>Ajout dans la réplication</h3> <p>Forgerock recommande d'utiliser la commande <em>dsreplication</em> du nouveau serveur pour l'ajouter dans une topologie de réplication existante.</p> <p>La commande pour configurer une nouvelle réplication utilise maintenant l'option <em>configure</em> plutôt que <em>enable</em> :</p> <pre><code>${BINDIR}/dsreplication configure \ --host1 ${HOST1} --port1 4445 \ --bindDN1 "cn=Directory Manager" \ --bindPassword1 ${PASS} \ --replicationPort1 8989 \ --host2 ${HOST3} --port2 4445 \ --bindDN2 "cn=Directory Manager" \ --bindPassword2 ${PASS} \ --replicationPort2 8989 \ --adminUID ${REPLADM} \ --adminPassword ${REPLPASS} \ --baseDN ${BASEDN} \ --trustAll \ --no-prompt </code></pre><h3>Initialisation de la réplication</h3> <p>On peut maintenant lancer l'initialisation de la réplication sur un seul serveur. Avec l'option <em>initialize</em> plutôt que <em>initialize-all</em> il faut préciser les serveurs sources et destinations :</p> <pre><code>${BINDIR}/dsreplication initialize \ --adminUID ${REPLADM} \ --adminPassword ${REPLPASS} \ --baseDN ${BASEDN} \ --hostSource ${HOST1} \ --portSource ${ADMINPORT} \ --hostDestination ${HOST2} \ --portDestination ${ADMINPORT} \ --trustAll \ --no-prompt </code></pre><p>On peut ensuite vérifier :</p> <pre><code>Suffix DN : Server : Entries : Replication enabled : DS ID : RS ID : RS Port (1) : Delay (ms) : Security (2) ----------:---------------------:---------:---------------------:-------:-------:-------------:------------:------------- o=example : dj1.id-num.com:4445 : 2025 : true : 14774 : 27576 : 8989 : N/A : false o=example : dj2.id-num.com:4445 : 2025 : true : 29513 : 481 : 8989 : 0 : false o=example : dj3.id-num.com:4445 : 2025 : true : 706 : 30128 : 8989 : 0 : false </code></pre><p>Une fois que l'on a ajouté le nouveau serveur, il faut éventuellement paramétrer les éléments propres à l'instance (par exemple les index) qui sont positionnés via l'utilitaire <em>dsconfig</em>.</p> <h3>Adaptations et différences</h3> <p>On peut trouver quelques différences entre OpenDJ 3.5 et DS 6.5. A première vue, on peut citer :</p> <ul><li>les options des commandes <em>dsconfig</em> ou <em>dsreplication</em></li> <li>les fichiers de définition du schéma qui ont migré de <code>DJ_HOME/config/schema</code> à <code>DS_HOME/db/schema</code></li> </ul><h2>Synthèse</h2> <p>Nous avons vu que deux possibilités sont offertes pour faire un <em>upgrade</em>, de manière assez simple.</p> <p>L'upgrade "en place" est intéressant pour récupérer toute la configuration spécifique de l'instance (paramètres positionnés par l'utilitaire <strong>dsconfig</strong> et qui ne sont pas répliqués). Dans ce cas, on garde le même serveur, et donc la même version d'O.S., de java, et d'adresse IP.</p> <p>Cependant, ceci nécessite d'arrêter l'une des instances de la topologie de réplication.</p> <p>L'ajout d'une nouvelle instance peut se justifier si on veut changer de version d'O.S. et/ou limiter au maximum les interruptions de service. Cela permet aussi d'adapter les scripts aux nouvelles versions d'outils (<strong>même si l'environnement de Test est fait pour cela</strong>). Par contre, ceci implique de reprendre le paramétrage de la configuration.</p> <table><tbody><tr><th>Méthode</th> <th>Avantages</th> <th>Inconvénients</th> </tr><tr><th>Upgrade en place</th> <td>Pas de changement d'adresse IP<br />Récupération de la configuration</td> <td>Arrêt de l'instance</td> </tr><tr><th>Ajout d'une nouvelle instance</th> <td>Possibilité de monter de version d'O.S.<br />Pas d'arrêt de serveur<br />Possibilité de tester les outils</td> <td>Nouveau serveur à provisionner<br />Nouvelle adresse IP à gérer<br />Pas de récupération du paramétrage spécifique</td> </tr></tbody></table><p> </p> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/vincentl" typeof="schema:Person" property="schema:name" datatype="">vincentl</span></span> <span class="field field--name-created field--type-created field--label-hidden">ven 14/12/2018 - 22:06</span> <div class="field field--name-field-categorie field--type-entity-reference field--label-above"> <div class="field__label">Catégorie</div> <div class="field__item"><a href="/cat%C3%A9gorie/iam" hreflang="fr">IAM</a></div> </div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tag</div> <div class="field__items"> <div class="field__item"><a href="/tags/forgerock" hreflang="fr">forgerock</a></div> <div class="field__item"><a href="/tags/opendj" hreflang="fr">opendj</a></div> </div> </div> <section class="field field--name-comment-node-book field--type-comment field--label-hidden comment-wrapper"> </section> Fri, 14 Dec 2018 21:06:26 +0000 vincentl 197 at https://www.vincentliefooghe.net https://www.vincentliefooghe.net/content/upgrade-forgerock-opendj-ds#comments Migration Sun DSEE vers un autre annuaire LDAP https://www.vincentliefooghe.net/content/migration-sun-dsee-vers-un-autre-annuaire-ldap <span property="dc:title" class="field field--name-title field--type-string field--label-hidden">Migration Sun DSEE vers un autre annuaire LDAP</span> <div property="content:encoded" class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Suite au rachat (déjà ancien) de Sun par Oracle, l'annuaire LDAP Sun DSEE, devenu Oracle DSEE depuis, arrive en fin de support. Ce produit a eu ses beaux jours en entreprise, à l'époque c'était probablement l'un des meilleurs, capable de gérer des centaines de milliers d'entrées, avec une architecture assez simple.</p> <p>Depuis, pas mal de concurrents sont arrivés, et les entreprises se lancent vers des migrations d'annuaires LDAP Sun / Oracle DSEE vers d'autres solutions plus récentes, soit sur un modèle Open Source (OpenLDAP généralement) ou propriétaires. La migration d'annuaire LDAP peut se faire assez rapidement.</p> <p>Les points d'attention sont les éléments non couverts par le standard LDAP, à savoir :</p> <ul><li>Formalisme du schéma (attributs et classes d'objets)</li> <li>Réplication</li> <li>ACI</li> <li>Privilèges et droits des comptes</li> <li>Politiques de mots de passe</li> </ul><p>Les données elles-mêmes peuvent être <em>relativement</em> facilement, avec un export et import LDIF. Selon la méthode utilisée (<em>ldapsearch</em> ou fonction <em>export</em> de l'annuaire), on peut ou non récupérer des attributs opérationnels.</p> <h2>Schéma de données</h2> <p>Sun DSEE permet de gérer des OID alphanumériques, par exemple :</p> <pre>attributeTypes: (sitename-oid NAME 'sitename' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'user defined' ) </pre><p>Si on veut être conforme aux RFC - par exemple dans le cas de OpenLDAP - il faut utiliser des <em>OID</em> numériques. La bonne pratique demande aussi d'utiliser sa propre classe d'OID avec une demande auprès de l'IANA. Dans ce cas, l'OID serait, par exemple :</p> <pre>attributeTypes: (1.3.6.1.4.1.31488.2.3.51 NAME 'sitename' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'user defined' ) </pre><p>A part cela, la syntaxe elle-même est différente entre Sun DSEE et OpenLDAP. Les objets du schéma OpenLDAD sont préfixés par <em>olc</em></p> <h3>Exemple Sun DSEE</h3> <p>Les éléments spécifique du schéma (attributs et classes d'objets) sont définis dans le fichier <code>[DIRECTORY_HOME]/config/schema/99-user.ldif</code>, au format suivant :</p> <pre>dn: cn=schema objectClass: top objectClass: ldapSubentry objectClass: subschema cn: schema attributeTypes: (sitename-oid NAME 'sitename' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'user defined' ) objectClasses: (1.3.6.1.4.1.31488.2.4.18 NAME 'ocSite' SUP top STRUCTURAL MUST (cn $ description) MAY ( sitename $ c $ l $ telephonenumber ) X-ORIGIN 'user defined' ) </pre><p>Dans le cas d'une migration vers Forgerock, Ping Directory (et généralement tous les annuaires issus des sources de <em>OpenDS</em>), on peut réutiliser la même définition pour les attributs et classes d'objets.</p> <h3>Exemple avec OpenLDAP</h3> <p>OpenLDAP utilise une syntaxe un peu différente, en dehors des OID numériques. On doit notamment préciser le DN complet pour l'objet du schéma. Par exemple :</p> <pre>dn: cn=myschema,cn=schema,cn=config objectClass: olcSchemaConfig cn: myschema olcattributeTypes: ( 1.3.6.1.4.1.31488.2.3.110 NAME 'sitename' DESC 'Site name' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE) olcobjectClasses: ( 1.3.6.1.4.1.31488.2.4.18 NAME 'ocSite' SUP top STRUCTURAL MUST (cn $ description) MAY ( sitename $ c $ l $ telephonenumber ) ) </pre><h3>Différences sur des classes d'objet issues du standard</h3> <p>Suite à une migration à partir de Sun DSEE, je me suis aperçu que Ping Directory &amp; Forgerock définissent également des types différents pour certaines classes d'objet, notamment celles liées aux objets Posix :</p> <pre>objectClasses: ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' SUP top STRUCTURAL DESC 'Abstraction of an account with POSIX attributes' MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( authPassword $ userPassword $ loginShell $ gecos $ description ) X-ORIGIN 'draft-howard-rfc2307bis' ) objectClasses: ( 1.3.6.1.1.1.2.2 NAME 'posixGroup' SUP top STRUCTURAL DESC 'Abstraction of a group of accounts' MUST gidNumber MAY ( cn $ authPassword $ userPassword $ memberUid $ description ) X-ORIGIN 'draft-howard-rfc2307bis' ) </pre><p>Si dans les données, on utilise ce type d'objets, il peut y avoir des modifications à apporter, sachant qu'il est recommandé d'activer les contrôles de cohérence / validité du schéma lors des imports initiaux, pour avoir une meilleur qualité de données.</p> </div> <span rel="sioc:has_creator" class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/vincentl" typeof="schema:Person" property="schema:name" datatype="">vincentl</span></span> <span property="dc:date dc:created" content="2018-07-29T09:47:52+00:00" datatype="xsd:dateTime" class="field field--name-created field--type-created field--label-hidden">dim 29/07/2018 - 11:47</span> <div class="field field--name-field-categorie field--type-entity-reference field--label-above"> <div class="field__label">Catégorie</div> <div class="field__item"><a href="/cat%C3%A9gorie/iam" hreflang="fr">IAM</a></div> </div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tag</div> <div class="field__items"> <div class="field__item"><a href="/tags/ldap" hreflang="fr">ldap</a></div> <div class="field__item"><a href="/tags/openldap" hreflang="fr">openldap</a></div> <div class="field__item"><a href="/tags/sun-dsee" hreflang="fr">Sun DSEE</a></div> <div class="field__item"><a href="/tags/forgerock" hreflang="fr">forgerock</a></div> <div class="field__item"><a href="/tags/ping-directory" hreflang="fr">ping directory</a></div> </div> </div> <section class="field field--name-comment-node-blog field--type-comment field--label-hidden comment-wrapper"> </section> Sun, 29 Jul 2018 09:47:52 +0000 vincentl 193 at https://www.vincentliefooghe.net https://www.vincentliefooghe.net/content/migration-sun-dsee-vers-un-autre-annuaire-ldap#comments Création de comptes Administrateurs dans OpenDJ / Ping Directory https://www.vincentliefooghe.net/content/cr%C3%A9ation-comptes-administrateurs-dans-opendj-ping-directory <span property="dc:title" class="field field--name-title field--type-string field--label-hidden">Création de comptes Administrateurs dans OpenDJ / Ping Directory</span> <div property="content:encoded" class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Lorsqu'on installe un annuaire LDAP, on crée un compte (souvent "<em>cn=Directory Manager</em>" par défaut) qui a tous les privilèges, qui n'est pas soumis aux ACI, bref, un superAdmin avec tous les privilèges.</p> <p>Il vaut donc mieux éviter d'utiliser ce compte pour des opérations. On peut toujours utiliser des comptes "utilisateurs", mais dans ce cas, le DN du compte change selon le baseDN.</p> <p>Si on veut industrialiser le monitoring de l'annuaire, par exemple, il est intéressant d'avoir toujours le même compte, quel que soit l'instance et donc le baseDN.</p> <p>C'est ici qu'interviennent les Root DNs, qui sont stockés dans la configuration (le <em>cn=config</em>).</p> <ul><li>Avantage : le DN sera le même sur tous les annuaires</li> <li>Inconvénient : le cn=config n'étant pas répliqué, il faut répéter la création sur chaque instance</li> </ul><p>OpenDJ et Ping Directory sont des annuaires "cousins", bâtis sur les sources de OpenDS après le rachat de Sun Microsystems par Oracle. Les deux produits ont divergé au fil du temps, mais les principes restent similaires. Nous allons voir les commandes à lancer sur les 2 produits.</p> <h2>OpenDJ</h2> <p>Avec OpenDJ, il faut ajouter une entrée dans le cn=config, via un fichier LDIF et un ldapmodify :</p> <pre>cat /tmp/monitor.ldif dn: cn=LDAP Monitoring,cn=Root DNs,cn=config changetype: add objectClass: ds-cfg-root-dn-user objectClass: top objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person sn: Monitoring cn: LDAP Monitoring givenName: LDAP ds-privilege-name: config-read ds-privilege-name: metrics-read ds-privilege-name: unindexed-search ds-cfg-alternate-bind-dn: cn=ldapMonitoring userPassword: Ld@pM0n|T0R1n6 </pre><p>On intègre ensuite les données :</p> <pre>/opt/opendj/bin/ldapmodify -D "cn=Directory Manager" -w secret -f /tmp/monitor.ldif</pre><p>A ce niveau on peut se connecter avec cn=ldapMonitoring, sans devoir passer le DN complet.</p> <p>On peut par exemple récupérer les informations du <em>cn=monitor</em> :</p> <pre>ldapsearch -x -h localhost -p 1389 -D cn=ldapMonitoring -w Ld@pM0n|T0R1n6 -b cn=monitor (objectclass=ds-monitor-entry)</pre><h2>Ping Directory</h2> <p>Ping Directory a nettement enrichi l'utilitaire <em>dsconfig</em>, en ajoutant des dizaines d'option par rapport à ses prédécesseurs.</p> <p>La création d'un compte de type Root DN passe maintenant par l'utilitaire dsconfig, avec l'option create-root-dn-user.</p> <pre>/opt/ping/bin/dsconfig create-root-dn-user --bindDN "cn=Directory Manager" --bindPassword secret --user-name ldapMonitoring \ --set alternate-bind-dn:cn=ldapMonitoring --set first-name:LDAP --set inherit-default-root-privileges:false --set last-name:Monitoring \ --set 'password:Ld@pM0n|T0R1n6' --set privilege:config-read --set privilege:metrics-read --set privilege:unindexed-search --no-prompt</pre><p>Par rapport à OpenDJ, sur lequel tous les utilisateurs authentifiés peuvent accéder aux données de cn=monitor, Ping Directory ne donne pas d'accès par défaut. Il faut donc ajouter une global ACI sur cette entrée :</p> <pre>/opt/ping/bin/dsconfig set-access-control-handler-prop --bindPassword secret \ --add global-aci:'(target="ldap:///cn=monitor")(targetattr="*")(version 3.0; acl "Allow access to the monitor tree by ldapMonitoring"; allow(all) userdn="ldap:///cn=ldapMonitoring,cn=Root DNS,cn=config";)' --no-prompt</pre><p> </p> </div> <span rel="sioc:has_creator" class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/vincentl" typeof="schema:Person" property="schema:name" datatype="">vincentl</span></span> <span property="dc:date dc:created" content="2018-04-04T08:08:30+00:00" datatype="xsd:dateTime" class="field field--name-created field--type-created field--label-hidden">mer 04/04/2018 - 10:08</span> <div class="field field--name-field-categorie field--type-entity-reference field--label-above"> <div class="field__label">Catégorie</div> <div class="field__item"><a href="/cat%C3%A9gorie/iam" hreflang="fr">IAM</a></div> </div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tag</div> <div class="field__items"> <div class="field__item"><a href="/tags/opendj" hreflang="fr">opendj</a></div> <div class="field__item"><a href="/tags/forgerock" hreflang="fr">forgerock</a></div> <div class="field__item"><a href="/tags/ping-directory" hreflang="fr">ping directory</a></div> <div class="field__item"><a href="/tags/unboundid" hreflang="fr">unboundid</a></div> </div> </div> <section class="field field--name-comment-node-blog field--type-comment field--label-hidden comment-wrapper"> </section> Wed, 04 Apr 2018 08:08:30 +0000 vincentl 191 at https://www.vincentliefooghe.net https://www.vincentliefooghe.net/content/cr%C3%A9ation-comptes-administrateurs-dans-opendj-ping-directory#comments OpenDJ : mise en place du suivi des connexions https://www.vincentliefooghe.net/content/opendj-mise-place-du-suivi-des-connexions <span class="field field--name-title field--type-string field--label-hidden">OpenDJ : mise en place du suivi des connexions</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Par défaut, il n'est pas possible de déterminer la date de dernière connexion réussie sur un annuaire LDAP (last login time). Seules les erreurs de connexion peuvent être détectées dans les politiques de mots de passe.</p> <p>Parfois cependant, il est intéressant de connaître la date de dernière connexion, afin de pouvoir supprimer - ou bloquer - des comptes inutilisés depuis plusieurs mois.</p> <p>Certains annuaire permettent de modifier les politiques de mots de passe pour tracer les connexions réussies, mais ceci a un impact sur les authentifications. En effet, à chaque authentification réussie, l'annuaire doit écrire la date de connexion sur l'objet utilisateur. On passe donc d'une suite d'opérations <em>search and bind</em> à une suite <em>search and bind and mod</em>.</p> <p>Quel est donc l'impact de ce suivi sur les performances d'un annuaire ? C'est l'objet de ce test, réalisé sur un annuaire LDAP Forgerock Opendj (DS 5.5).</p> <h2>Configuration utilisée</h2> <p>La configuration utilisée est la suivante :</p> <ul><li>2 vCPU Intel(R) Xeon(R) CPU E5-2698 v3 @ 2.30GHz</li> <li>16 Go de RAM</li> </ul><p>Le jeu de test est constitué d'un fichier LDIF de 481 Mo, comprenant 220 000 entrées utilisateur.</p> <p>Parmi ces 220 000 entrées, 50 000 sont situées dans une OU (<em>organizational unit</em>) spécifique : ou=People,ou=FR,o=example.</p> <h2>Politique de mots de passe par défaut</h2> <p>La politique de mot de passe par défaut est utilisée, avec le chargement des entrées.</p> <pre><code>Property : Value(s) ------------------------------------------:------------------------------------ account-status-notification-handler : - allow-expired-password-changes : false allow-multiple-password-values : false allow-pre-encoded-passwords : false allow-user-password-changes : true default-password-storage-scheme : Salted SHA-512 deprecated-password-storage-scheme : - expire-passwords-without-warning : false force-change-on-add : false force-change-on-reset : false grace-login-count : 0 idle-lockout-interval : 0 s java-class : org.opends.server.core.PasswordPolicyFactory last-login-time-attribute : - last-login-time-format : - lockout-duration : 0 s lockout-failure-count : 0 lockout-failure-expiration-interval : 0 s max-password-age : 25 w 5 d max-password-reset-age : 0 s min-password-age : 0 s password-attribute : userPassword password-change-requires-current-password : false password-expiration-warning-interval : 5 d password-generator : Random Password Generator password-history-count : 5 password-history-duration : 0 s password-validator : Length-Based Password Validator previous-last-login-time-format : - require-change-by-time : - require-secure-authentication : false require-secure-password-changes : false skip-validation-for-administrators : false state-update-failure-policy : reactive </code></pre><h2>Outils de benchmark</h2> <p>Les tests sont lancés avec les utilitaires <em>authrate</em> et <em>searchrate</em>, qui sont intégrés aux binaires de l'annuaire.</p> <p>Pour la recherche :</p> <p><style type="text/css"> <!--/*--><![CDATA[/* ><!--*/ <!--/*--><![CDATA[/* ><!--*/ p, li { white-space: pre-wrap; } /*--><!]]]]><![CDATA[>*/ /*--><!]]>*/ </style></p> <pre style="margin: 0px; text-indent: 0px;">/opt/opendj/bin/searchrate -h 10.150.29.161 -p 1389 -D 'uid=user.1,ou=people,ou=fr,o=example' -w password -F -c 10 -b o=example -m 300000 -g 'rand(100,49000)' '(uid=user.%d)'</pre><p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> </p> <p>Pour les authentifications, en mode search + bind :</p> <pre><code>/opt/opendj/bin/authrate -h 10.150.29.161 -p 1389 -D '%2$s' -w password -f -c 10 -b ou=people,ou=fr,o=example -s sub -m 300000 -g 'rand(100,49000)' '(uid=user.%d)' </code></pre><h2>Procédure de tests</h2> <p>La procédure de tests est la suivante :</p> <ul><li>démarrage de l'annuaire</li> <li>lancement d'un premier test de type <em>searchrate</em>, qui permet de cacher les entrées en RAM</li> <li>lancement d'un test d'authentification avec <em>authrate</em> en mode search + bind</li> </ul><h2>Mesures sans suivi de la date de dernière connexion</h2> <p>Avec le paramétrage par défaut, on obtient un débit moyen de 4283 authentifications par seconde, avec un temps de réponse moyen de 2.131 millisecondes.</p> <pre><code>------------------------------------------------------------------------------ Throughput Response Time (ops/second) (milliseconds) recent average recent average 99.9% 99.99% 99.999% err/sec bind time % ------------------------------------------------------------------------------ 3901.6 3907.1 2.573 2.573 25.304 162.729 167.620 0.0 44.4 4186.4 4047.8 2.372 2.469 26.546 166.949 195.819 0.0 46.6 4540.4 4212.9 2.183 2.365 23.388 166.223 195.819 0.0 44.2 4624.0 4315.7 2.147 2.307 22.435 162.729 195.819 0.0 42.6 4609.7 4374.6 2.156 2.275 22.083 160.540 172.100 0.0 43.0 4663.1 4422.7 2.130 2.250 21.634 158.399 172.100 0.0 42.5 4765.9 4471.7 2.088 2.225 21.431 154.617 172.100 0.0 42.8 4764.9 4508.4 2.089 2.207 20.982 141.395 172.100 0.0 42.0 4864.1 4547.9 2.050 2.188 21.024 39.778 169.393 0.0 42.2 4811.6 4574.2 2.077 2.177 20.934 34.832 169.393 0.0 42.0 4777.0 4592.6 2.091 2.169 20.525 32.932 169.393 0.0 41.8 4825.5 4611.9 2.072 2.160 20.090 32.724 169.393 0.0 41.9 4101.4 4572.6 1.898 2.142 19.807 32.213 169.393 0.0 41.7 530.8 4283.0 0.869 2.131 19.703 32.118 167.620 0.0 41.0 </code></pre><p>La CPU est utilisée à 90 % environ sur le serveur cible (qui dispose de 2 vCPU).</p> <h2>Mise en place du suivi du <em>last login time</em></h2> <h3>Création d'un attribut spécifique</h3> <p>La mise en place du suivi demande tout d'abord de créer un attribut qui sera utilisé pour stocker la date de dernière connexion. La documentation donne justement un exemple de politique de mots de passe qui met en oeuvre le suivi.</p> <pre><code>dn: cn=schema changetype: modify add: attributeTypes attributeTypes: ( lastLoginTime-oid NAME 'lastLoginTime' DESC 'Last time the user logged in' EQUALITY generalizedTimeMatch ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'OpenDJ example documentation' ) </code></pre><p>On ajoute l'attribut au schéma via la commande <em>ldapmodify</em> :</p> <pre><code>ldapmodify \ --hostname localhost \ --port 1389 \ --bindDN "cn=Directory Manager" \ --bindPassword password \ lastLoginTime.ldif </code></pre><h3>Création d'une politique de mots de passe spécifique</h3> <p>Si on veut cibler certains comptes utilisateurs, on peut créer une politique de mots de passe spécifiquement pour cet usage :</p> <pre><code>/opt/opendj/bin/dsconfig \ create-password-policy \ --hostname localhost --port 4444 \ --bindDN "cn=Directory Manager" \ --bindPassword password \ --policy-name "Track Last Login Time" \ --type password-policy \ --set default-password-storage-scheme:"Salted SHA-512" \ --set password-attribute:userPassword \ --set last-login-time-attribute:lastLoginTime \ --set last-login-time-format:"yyyyMMddHH'Z'" \ --trustAll \ --no-prompt </code></pre><h3>Modification de la politique de mots de passe</h3> <p>On peut créer une nouvelle politique de mots de passe, ou modifier la politique existante. Dans ce cas, la commande à utiliser est la suivante :</p> <pre><code>/opt/opendj/bin/dsconfig set-password-policy-prop \ --policy-name Default\ Password\ Policy \ --set last-login-time-attribute:lastLoginTime \ --set last-login-time-format:"yyyyMMddHH'Z'" \ --hostname localhost \ --port 4444 \ --bindDn cn=Directory\ Manager \ --bindPassword password \ --trustAll \ --no-prompt </code></pre><h2>Test sur une entrée</h2> <p>Si on fait une requête avec un utilisateur en se connectant, on peut ensuite récupérer la date de dernière connexion sur son entrée, à condition de disposer des droits adéquats.</p> <p>Par exemple :</p> <pre><code>ldapsearch -x -h localhost -p 1389 -D 'cn=Directory Manager' -w password -b o=example '(uid=user.2)' lastLoginTime dn: uid=user.2,ou=People,ou=FR,o=example lastLoginTime: 2017121909Z </code></pre><p>On constate que la date est bien au format déclaré, c'est à dire comprenant l'année, le mois, le jour et l'heure. En effet, il n'est pas nécessaire de suivre les connexions à la minute près.</p> <h2>Mesures AVEC suivi de la date de dernière connexion</h2> <p>Avec le nouveau paramétrage, incluant la mise à jour de la dernière date de connexion, on obtient un débit maximum de 2860 authentifications par seconde en moyenne, avec un temps de réponse moyen de 3.382 millisecondes.</p> <p>La charge CPU sur le serveur d'annuaire oscille entre 50 et 95%, avec des I/O wait.</p> <p>On a bien un impact assez fort, avec notamment des phases pendant laquelle l'annuaire est en I/O wait, révélateur des mises à jour effectuées sur les entrées.</p> <pre><code>------------------------------------------------------------------------------ Throughput Response Time (ops/second) (milliseconds) recent average recent average 99.9% 99.99% 99.999% err/sec bind time % ------------------------------------------------------------------------------ 1570.3 1573.1 6.421 6.421 152.833 177.026 177.026 0.0 63.6 2308.4 1942.2 4.325 5.170 53.701 176.974 177.026 0.0 57.0 2479.4 2122.0 4.023 4.722 49.889 165.267 177.026 0.0 53.3 1025.2 1847.4 3.712 4.581 48.670 165.267 177.026 0.0 51.6 0.0 1477.0 - 4.581 48.670 165.267 177.026 0.0 NaN 1013.4 1399.8 25.871 7.149 50.589 11195.648 11204.023 0.0 93.5 2996.8 1627.5 3.332 6.147 46.142 11193.992 11204.023 0.0 49.8 3301.1 1836.1 3.028 5.447 44.262 11187.622 11204.023 0.0 48.4 3329.9 2001.8 2.737 4.948 41.735 11165.580 11204.023 0.0 47.0 0.0 1800.9 - 4.948 41.735 11165.580 11204.023 0.0 NaN 92.4 1645.4 226.166 6.078 43.042 11156.315 11204.023 0.0 99.2 4024.9 1843.1 2.485 5.426 39.488 10339.090 11203.705 0.0 45.4 4193.7 2023.6 2.382 4.942 38.478 10328.566 11203.705 0.0 44.3 4487.2 2199.3 2.227 4.547 36.814 10316.059 11203.705 0.0 43.1 4496.0 2352.3 2.222 4.251 35.540 10313.618 11203.705 0.0 43.3 4530.5 2488.3 2.207 4.018 34.530 10213.330 11203.705 0.0 43.0 4535.5 2608.8 2.204 3.833 33.649 168.792 11202.112 0.0 43.3 4479.8 2712.7 2.230 3.686 33.112 163.941 11202.112 0.0 42.7 4722.0 2818.5 2.116 3.547 32.285 160.038 11202.112 0.0 42.6 4710.9 2913.1 2.108 3.431 31.666 79.285 11202.112 0.0 42.5 1816.5 2860.5 1.834 3.382 31.418 75.221 11195.866 0.0 42.9 </code></pre><p><em>Note</em> : ceci est cependant à modérer, puisque notre première campagne de tir va déclencher une écriture pour chaque utilisateur qui s'authentifie. Si on relance un deuxième tir dans l'heure (qui est l'unité minimale de suivi de la date de connexion), on retrouve des résultats quasiment identiques au premier cas : un débit de 4291 authentifications par seconde, et un temps de réponse de 2.195 ms. Si on regarde la charge sur le serveur d'annuaire, on ne constate plus d'écritures sur disque.</p> <pre><code>------------------------------------------------------------------------------ Throughput Response Time (ops/second) (milliseconds) recent average recent average 99.9% 99.99% 99.999% err/sec bind time % ------------------------------------------------------------------------------ 3988.3 3994.8 2.514 2.514 23.066 163.522 167.640 0.0 42.6 .../... 4628.0 4525.7 2.159 2.206 18.978 28.433 158.819 0.0 42.9 4327.2 4510.4 2.219 2.207 19.051 28.221 158.819 0.0 43.0 1450.9 4291.0 1.721 2.195 18.968 28.071 158.406 0.0 42.1 </code></pre><h2>Conclusion</h2> <p>Si l'impact du suivi de la date de dernière connexion n'est pas anodin, surtout dans le cas d'un test, en situation réelle, l'impact peut être relativement négligeable, notamment si les authentifications se font au fil de l'eau.</p> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/vincentl" typeof="schema:Person" property="schema:name" datatype="">vincentl</span></span> <span class="field field--name-created field--type-created field--label-hidden">mar 19/12/2017 - 10:42</span> <div class="field field--name-field-categorie field--type-entity-reference field--label-above"> <div class="field__label">Catégorie</div> <div class="field__item"><a href="/cat%C3%A9gorie/iam" hreflang="fr">IAM</a></div> </div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tag</div> <div class="field__items"> <div class="field__item"><a href="/tags/forgerock" hreflang="fr">forgerock</a></div> <div class="field__item"><a href="/tags/opendj" hreflang="fr">opendj</a></div> <div class="field__item"><a href="/tags/ldap" hreflang="fr">ldap</a></div> <div class="field__item"><a href="/tags/performances" hreflang="fr">performances</a></div> </div> </div> <section class="field field--name-comment-node-book field--type-comment field--label-hidden comment-wrapper"> </section> Tue, 19 Dec 2017 09:42:42 +0000 vincentl 187 at https://www.vincentliefooghe.net https://www.vincentliefooghe.net/content/opendj-mise-place-du-suivi-des-connexions#comments OpenIDM : purge des tables audit https://www.vincentliefooghe.net/content/openidm-purge-des-tables-audit <span class="field field--name-title field--type-string field--label-hidden">OpenIDM : purge des tables audit </span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><h2>Contexte</h2> <p>Dans un environnement client OpenIDM 3.1 en production depuis plusieurs mois, l'accès à l'écran d'administration des mappings met de plus en plus longtemps à s'afficher.</p> <h3>English Summary</h3> <p>It may help : if the access time to mapping administration form in OpenIDM becomes very slow, then check the numbers of lines in <em>auditrecon</em> table, and delete old records.</p> <h2>Analyse du problème</h2> <p>En regardant les échanges avec le serveur (via la console du navigateur), on voit que l'URL "endpoint/mappingDetails" est celle qui prend le plus de temps.<br />Ce endpoint appelle un script Javascript, <code>./bin/defaults/script/ui/mappingDetails.js</code>, qui lance une requête SQL permettant de récupérer la date de dernière réconciliation, et ce pour chaque <em>mapping</em> :</p> <pre> lastRecon = openidm.query("audit/recon", { "_queryId": "audit-last-recon-for-mapping", "mapping": m.name, "formatted": false }); </pre><p>En regardant dans le fichier <code>conf/repo.jdbc.json</code>, on peut récupérer le détail de la requête :</p> <pre>audit-last-recon-for-mapping" : "SELECT * FROM ${_dbSchema}.auditrecon WHERE entryType = 'start' AND mapping = ${mapping} and reconaction &lt;&gt; 'reconById' ORDER BY activitydate DESC LIMIT 1" </pre><p>C'est cette requête qui pose problème, notamment car la table <em>auditrecon</em> n'a jamais été purgée.</p> <p>Si on regarde le nombre de lignes par type de <em>mapping</em>, on peut avoir une idée de la volumétrie :</p> <pre>select mapping, count(1) from auditrecon group by mapping ; +-------------------------------------+----------+ | mapping | count(1) | +-------------------------------------+----------+ | AdOrganizationalUnit_Officelocation | 3956 | | ldapFunctionnal_Orga | 482874 | +-------------------------------------+----------+ 2 rows in set (3.36 sec) </pre><p>On peut alors tester la requête lancée par le script <code>mappingDetails.js</code> :</p> <pre>SELECT * FROM auditrecon WHERE entryType = 'start' AND mapping = 'ldapFunctionnal_Orga' and reconaction &lt;&gt; 'reconById' ORDER BY activitydate DESC LIMIT 1 ; </pre><p>Le nombre élevé s'explique car il s'agit d'une réconciliation lancée à intervalle régulier (toutes les 15 minutes), pour récupérer des objets de type 'organisation' : départements, fonctions, sections.<br />De ce fait, on arrive rapidement à un nombre élevé de lignes, sachant que chaque réconciliation traite près de 500 lignes, soit 4*24*500 = 48 000 lignes par jour !</p> <p>On peut tenter d'en savoir un peu plus sur la volumétrie. Pour avoir la taille des tables :</p> <pre>SELECT table_name, table_rows as 'Rows #', data_length /1024 as 'Data size KB', index_length/1024 as 'Index size KB' , round( (data_length + index_length) / 1024 / 1024,2 ) as 'Taille MB' FROM information_schema.tables WHERE table_schema = 'openidm' AND table_name like 'audit%'; +---------------+--------+--------------+---------------+-----------+ | table_name | Rows # | Data size KB | Index size KB | Taille MB | +---------------+--------+--------------+---------------+-----------+ | auditaccess | 6153 | 2080.0000 | 2144.0000 | 4.13 | | auditactivity | 843 | 22544.0000 | 1056.0000 | 23.05 | | auditrecon | 449959 | 541952.0000 | 592448.0000 | 1107.81 | | auditsync | 797 | 7696.0000 | 0.0000 | 7.52 | +---------------+--------+--------------+---------------+-----------+ 4 rows in set (0.15 sec) </pre><p><strong>Note</strong> : s'agissant de tables utilisant le moteur InnoDB, le nombre de lignes donnés par cette requête peut varier. Il vaut mieux utiliser un SELECT COUNT pour avoir le nombre réel de lignes. La volumétrie en KB, par contre, semble stable.</p> <h2>Epuration des tables d'audit</h2> <p>Il existe une tâche programmée qui permet de lancer un nettoyage régulier des tables d'audit. Sur une installation OpenIDM 3.1, le fichier de définition se trouve dans <code>samples/schedules/schedule-autoPurgeAuditRecon.json</code> :</p> <pre>{ "enabled" : false, "type" : "cron", "schedule" : "0 0 */12 * * ?", "persisted" : true, "misfirePolicy" : "doNothing", "invokeService" : "script", "invokeContext" : { "script" : { "type" : "text/javascript", "file" : "audit/autoPurgeAuditRecon.js", "input" : { "mappings" : [ "%" ], "purgeType" : "purgeByNumOfReconsToKeep", "numOfRecons" : 1, "intervalUnit" : "minutes", "intervalValue" : 1 } } } } </pre><p>Dans la configuration par défaut, on limite à un nombre fini de réconciliations. Si on veut garder un nombre de jours limité, il faut modifier la définition pour utiliser le type de purge <em>purgeByExpired</em> :</p> <pre>{ "enabled" : true, "type" : "cron", "schedule" : "0 0 22 * * ?", "persisted" : true, "misfirePolicy" : "doNothing", "invokeService" : "script", "invokeContext" : { "script" : { "type" : "text/javascript", "file" : "audit/autoPurgeAuditRecon.js", "input" : { "mappings" : [ "%" ], "purgeType" : "purgeByExpired", "numOfRecons" : 1, "intervalUnit" : "days", "intervalValue" : 30 } } } } </pre><p>Le souci étant dans ce cas que, puisqu'on n'a jamais lancé de purge, la requête tombe en time-out...</p> <p>Dans ce cas, On peut aussi utiliser une méthode plus radicale, consistant à supprimer les lignes dans la table, de la manière suivante :</p> <pre>DELETE FROM auditaccess WHERE activitydate &lt; 'YYYY-MM-DD' ; </pre><p>On peut générer les instructions avec un script shell, qui va calculer la date (ce qui est plus rapide que d'utiliser les fonctions MySQL) :</p> <pre>#!/bin/bash # ----------------------------- # Purge OpenIDM audit database # ----------------------------- # # Please stop openidm first, and there remove felix-cache content before restarting #----------------------------------------------------------------------------------- # Set data retention period here LongTimeAgo=$(date -d 'now -6 months' +'%Y-%m-%d') echo "DELETE FROM auditaccess WHERE activitydate &lt; '$LongTimeAgo' ;" &gt; /tmp/purge.sql echo "DELETE FROM auditactivity WHERE activitydate &lt; '$LongTimeAgo' ;" &gt;&gt; /tmp/purge.sql echo "DELETE FROM auditrecon WHERE activitydate &lt; '$LongTimeAgo' ;" &gt;&gt; /tmp/purge.sql echo "DELETE FROM auditsync WHERE activitydate &lt; '$LongTimeAgo' ;" &gt;&gt; /tmp/purge.sql # Ou utiliser la commande mysql en ligne echo "SELECT table_name, data_length /1024 AS 'Data size KB', index_length/1024 AS 'Index size KB' , round( (data_length + index_length) / 1024 / 1024,2 ) AS 'Taille MB' FROM information_schema.tables WHERE table_schema = 'openidm' and table_name like 'audit%'; " &gt;&gt; /tmp/purge.sql echo "exit" &gt;&gt; /tmp/purge.sql SQLUSER=openidm SQLPWD=openidm SQLDB=openidm DBHOST=db.mydomain.com DBPORT=3306 mysql -h ${DBHOST} -P ${DBPORT} -u ${SQLUSER} -p${SQLPWD} ${SQLDB} &lt; /tmp/purge.sql # # --- End of script # </pre><p>Il ne reste plus qu'à lancer le script shell, de manière réguliere (une fois par semaine par exemple), pour limiter le nombre de lignes dans la table, et retrouver un temps de réponse correct lorsqu'on passe par l'écran de gestion des <em>mappings</em></p> <h2>En conclusion</h2> <p>Si l'accès à la page <em>Mappings</em> de la console d'administration OpenIDM est de plus en plus long, vérifier le nombre de lignes dans la table <em>auditrecon</em>, et lancer régulièrement une purge de cette table, soit via le <em>schedule-autoPurgeAuditRecon</em>, soit directement en SQL.</p> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/vincentl" typeof="schema:Person" property="schema:name" datatype="">vincentl</span></span> <span class="field field--name-created field--type-created field--label-hidden">mer 14/12/2016 - 09:31</span> <div class="field field--name-field-categorie field--type-entity-reference field--label-above"> <div class="field__label">Catégorie</div> <div class="field__item"><a href="/cat%C3%A9gorie/iam" hreflang="fr">IAM</a></div> </div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tag</div> <div class="field__items"> <div class="field__item"><a href="/taxonomy/term/3" hreflang="fr">openidm</a></div> <div class="field__item"><a href="/tags/forgerock" hreflang="fr">forgerock</a></div> <div class="field__item"><a href="/tags/audit" hreflang="fr">audit</a></div> <div class="field__item"><a href="/tags/performances" hreflang="fr">performances</a></div> </div> </div> <section class="field field--name-comment-node-book field--type-comment field--label-hidden comment-wrapper"> </section> Wed, 14 Dec 2016 08:31:54 +0000 vincentl 171 at https://www.vincentliefooghe.net