Blob Blame History Raw
diff --git a/src/dome/DomeMysql_authn.cpp b/src/dome/DomeMysql_authn.cpp
index e9f33dd9..ec0a71e3 100644
--- a/src/dome/DomeMysql_authn.cpp
+++ b/src/dome/DomeMysql_authn.cpp
@@ -464,7 +464,7 @@ int DomeMySql::getGroups(DomeStatus &st)
   try {
     Statement stmt(*conn_, DomeMySql::cnsdb,
                    "SELECT gid, groupname, banned, xattr\
-                   FROM Cns_groupinfo"
+                   FROM Cns_groupinfo ORDER BY rowid ASC"
     );
     stmt.execute();
 
diff --git a/src/dome/DomeStatus.cpp b/src/dome/DomeStatus.cpp
index cd174900..474c31e6 100644
--- a/src/dome/DomeStatus.cpp
+++ b/src/dome/DomeStatus.cpp
@@ -65,6 +65,7 @@ bool DomeFsInfo::canPullFile(DomeStatus &st) {
 
 DomeStatus::DomeStatus() {
   davixPool = NULL;
+  iamnormalize = false;
   lastreloadusersgroups = lastfscheck = lastreload = 0;
 
 
@@ -109,6 +110,11 @@ int DomeStatus::Init() {
   // Ugly hack to make this information available to excessively isolated classes
   CFG->SetString("glb.restclient.present-as", (char *)myhostname.c_str());
   
+  iamnormalize = CFG->GetBool("head.iam-normalize", false);
+  if (iamnormalize) {
+    Log(Logger::Lvl0, domelogmask, domelogname, "Normalize group names for IAM and VOMS compatibility");
+  }
+
   // Set root user and group
   rootUserInfo.userid = 0;
   rootUserInfo.username = "root";
@@ -1329,6 +1335,10 @@ int DomeStatus::getGroup(std::string groupname, DomeGroupInfo &gi) {
     return 1;
   }
 
+  if (iamnormalize) {
+    groupname = normalizeGroup(groupname);
+  }
+
   // lock status
   boost::unique_lock<boost::recursive_mutex> l(*this);
 
@@ -1342,6 +1352,30 @@ int DomeStatus::getGroup(std::string groupname, DomeGroupInfo &gi) {
   return 1;
 }
 
+/// Normalize group name for IAM and VOMS compatibility
+std::string DomeStatus::normalizeGroup(const std::string &groupname) {
+  std::string ret(groupname);
+
+  // remove "Role=" substring
+  size_t role_start = groupname.find("/Role=");
+  if (role_start != std::string::npos) {
+    ret = groupname.substr(0, role_start+1) + groupname.substr(role_start+6);
+  }
+
+  // remove whole "Capability=*" substring
+  size_t cap_start = ret.find("/Capability=");
+  if (cap_start != std::string::npos) {
+    size_t cap_end = ret.find("/", cap_start+12);
+    if (cap_end != std::string::npos) {
+      return ret.substr(0, cap_start) + ret.substr(cap_end);
+    } else {
+      return ret.substr(0, cap_start);
+    }
+  }
+
+  return ret;
+}
+
 /// Inserts/overwrites an user
 int DomeStatus::insertUser(DomeUserInfo &ui) {
   // lock status
@@ -1354,11 +1388,31 @@ int DomeStatus::insertUser(DomeUserInfo &ui) {
 }
 /// Inserts/overwrites a group
 int DomeStatus::insertGroup(DomeGroupInfo &gi) {
+  std::string groupname(gi.groupname);
+  bool normalized = false;
+  if (iamnormalize) {
+    groupname = normalizeGroup(gi.groupname);
+    normalized = groupname != gi.groupname;
+  }
+
   // lock status
   boost::unique_lock<boost::recursive_mutex> l(*this);
 
-  groupsbygid[gi.groupid] = gi;
-  groupsbyname[gi.groupname] = gi;
+  if (!normalized) {
+    groupsbygid[gi.groupid] = gi;
+    groupsbyname[gi.groupname] = gi;
+  } else {
+    // for normalized groupname that already exists use first
+    // inserted group data for all additional duplicate groups
+    if (groupsbyname.find(groupname) != groupsbyname.end()) {
+      Log(Logger::Lvl3, domelogmask, domelogname, "duplicate normalized group: '" <<
+        gi.groupname << "' normalized: '" << groupname << "' gid: " << gi.groupid);
+      groupsbygid[gi.groupid] = groupsbyname[groupname];
+    } else {
+      groupsbygid[gi.groupid] = gi;
+      groupsbyname[groupname] = gi;
+    }
+  }
 
   return 0;
 }
diff --git a/src/dome/DomeStatus.h b/src/dome/DomeStatus.h
index 13497183..7af7affe 100644
--- a/src/dome/DomeStatus.h
+++ b/src/dome/DomeStatus.h
@@ -346,6 +346,8 @@ public:
   int getGroup(int gid, DomeGroupInfo &gi);
   /// Gets group info from name. Returns 0 on failure
   int getGroup(std::string groupname, DomeGroupInfo &gi);
+  /// Normalize group name for IAM and VOMS compatibility
+  std::string normalizeGroup(const std::string &groupname);
 
   /// A quick rendition of the grid mapfile, translating from user DN to VOMS group
   /// The implementation of getIdMap may use this
@@ -456,6 +458,8 @@ public:
   /// neededops can be "r" "w"
   dmlite::DmStatus oidc_auth(const std::string &lfnorpath, DomeReq &dreq, std::string neededops);
 private:
+  bool iamnormalize;
+
   DomeUserInfo rootUserInfo;
   DomeGroupInfo rootGroupInfo;
 
diff --git a/doc/dome/dome.tex b/doc/dome/dome.tex
index 5fbdc06c..a23665c3 100644
--- a/doc/dome/dome.tex
+++ b/doc/dome/dome.tex
@@ -2270,7 +2270,7 @@ Absolute path to an executable that can produce stat information for a logical f
 will be invoked passing the LFN as the only parameter.\\
 The stat information has to be produced as a text line in the standard output, prefixed by the string \lstinline">>>>>" \\
 Example:\\
-\lstinline"glb.filepuller.stathook: /usr/bin/externalstat.py"\\
+\lstinline"head.filepuller.stathook: /usr/bin/externalstat.py"\\
 Example output:\\
 \lstinline">>>>> Size:898945"
 
@@ -2355,9 +2355,10 @@ Default: 7200
 
 
 
+\subsubsection{head.iam-normalize}
+Get compatible group names between legacy VOMS and new IAM. IAM dropped Role and Capability from group names and by enabling this configuration option it is possible to use X.509 proxies from old legacy VOMS and new IAM at the same time. All existing ACLs will work fine, because internally Role= substring is stripped and whole Capability=* removed from group name. Database groupinfo entries are not modified and normalized names are stored only in memory and used for group lookups. Normalization can lead to duplicate names (e.g. /dteam/Role=test and /dteam/test) and in such situation it is used first normalized(groupname) to gid mapping from database (DB groupinfo is processed in rowid ascending order).
 
-
-
+Default: false
 
 
 
diff --git a/etc/domehead.conf.example b/etc/domehead.conf.example
index 4ae60c2f..d3771630 100644
--- a/etc/domehead.conf.example
+++ b/etc/domehead.conf.example
@@ -27,6 +27,9 @@ head.db.poolsz: 128
 head.db.cnsdbname: cns_db
 head.db.dpmdbname: dpm_db
 
+# VOMS & IAM
+#head.iam-normalize: false
+
 # OIDC
 #head.oidc.allowissuer[]: "/dpm" "https://wlcg.cloud.cnaf.infn.it/" dteam
 #head.oidc.allowissuer[]: "/dpm" "https://wlcg.cloud.cnaf.infn.it/" wlcg
diff --git a/src/puppet/dmlite/manifests/dome/config.pp b/src/puppet/dmlite/manifests/dome/config.pp
index f26a459b..394ddb4b 100644
--- a/src/puppet/dmlite/manifests/dome/config.pp
+++ b/src/puppet/dmlite/manifests/dome/config.pp
@@ -31,6 +31,7 @@ class dmlite::dome::config (
   $headnode_domeurl = $dmlite::dome::params::headnode_domeurl,
   $proxy_timeout = $dmlite::dome::params::proxy_timeout,
   $restclient_cli_xrdhttpkey = $dmlite::dome::params::restclient_cli_xrdhttpkey,
+  $iam_normalize = $dmlite::dome::params::iam_normalize,
 
   $enable_ns_oidc            = $dmlite::dome::params::enable_ns_oidc,
   $ns_oidc_clientid          = $dmlite::dome::params::ns_oidc_clientid,
diff --git a/src/puppet/dmlite/manifests/dome/params.pp b/src/puppet/dmlite/manifests/dome/params.pp
index b7b7d2b0..85c128d2 100644
--- a/src/puppet/dmlite/manifests/dome/params.pp
+++ b/src/puppet/dmlite/manifests/dome/params.pp
@@ -32,6 +32,7 @@ class dmlite::dome::params (
   $headnode_domeurl = hiera('dmlite::dome::params::headnode_domeurl',undef)
   $proxy_timeout = hiera('dmlite::dome::params::proxy_timeout',600)
   $restclient_cli_xrdhttpkey = hiera('dmlite::dome::params::restclient_cli_xrdhttpkey',undef)
+  $iam_normalize = hiera('dmlite::dome::params::iam_normalize', undef)
 
   $enable_ns_oidc = hiera('dmlite::dav::params::enable_ns_oidc', false)
   $ns_oidc_metadataurl = hiera('dmlite::dav::params::ns_oidc_metadataurl', 'https://wlcg.cloud.cnaf.infn.it/.well-known/openid-configuration')
diff --git a/src/puppet/dmlite/manifests/head.pp b/src/puppet/dmlite/manifests/head.pp
index 16d678ea..95bbbd52 100644
--- a/src/puppet/dmlite/manifests/head.pp
+++ b/src/puppet/dmlite/manifests/head.pp
@@ -25,6 +25,7 @@ class dmlite::head (
   Boolean $enable_disknode = false,
   Boolean $enable_domeadapter = false,
   String $host_dn = '',
+  Optional[Boolean] $iam_normalize = undef,
   Optional[String] $oidc_clientid = undef,
   Array[String] $oidc_allowissuer = [],
   Array[String] $oidc_allowaudience = [],
@@ -214,6 +215,7 @@ class dmlite::head (
       dpmdb_name                => $dpm_db,
       headnode_domeurl          => "http://${dpmhost}:1094/domehead",
       restclient_cli_xrdhttpkey => $token_password,
+      iam_normalize             => $iam_normalize,
       enable_ns_oidc            => $oidc_clientid != undef,
       ns_oidc_clientid          => $oidc_clientid,
       ns_oidc_allowissuer       => $oidc_allowissuer,
diff --git a/src/puppet/dmlite/templates/dome/domehead.conf.erb b/src/puppet/dmlite/templates/dome/domehead.conf.erb
index 0f4ec667..331b9f67 100644
--- a/src/puppet/dmlite/templates/dome/domehead.conf.erb
+++ b/src/puppet/dmlite/templates/dome/domehead.conf.erb
@@ -29,6 +29,9 @@ head.db.port: <%= @db_port %>
 head.db.poolsz: <%= @db_pool_size %>
 head.db.cnsdbname: <%= @cnsdb_name %>
 head.db.dpmdbname: <%= @dpmdb_name %>
+<% if @iam_normalize -%>
+head.iam-normalize: true
+<% end -%>
 <% if @enable_ns_oidc -%>
 head.oidc.allowaudience[]: <%= @ns_oidc_clientid %>
 <% @ns_oidc_allowaudience.each do |audience| -%>
diff --git a/src/puppet/dpm/manifests/head_disknode.pp b/src/puppet/dpm/manifests/head_disknode.pp
index 79c799ff..47b82d66 100644
--- a/src/puppet/dpm/manifests/head_disknode.pp
+++ b/src/puppet/dpm/manifests/head_disknode.pp
@@ -43,6 +43,8 @@ class dpm::head_disknode (
   Boolean $xrootd_use_voms = $dpm::params::xrootd_use_voms,
   Optional[String] $http_macaroon_secret = $dpm::params::http_macaroon_secret,
 
+  Optional[Boolean] $iam_normalize = $dpm::params::iam_normalize,
+
   Optional[String] $oidc_clientid = $dpm::params::oidc_clientid,
   Optional[String] $oidc_clientsecret = $dpm::params::oidc_clientsecret,
   Optional[String] $oidc_passphrase = $dpm::params::oidc_passphrase,
@@ -243,6 +245,7 @@ class dpm::head_disknode (
     enable_domeadapter => $configure_domeadapter,
     enable_disknode    => true,
     host_dn            => $host_dn,
+    iam_normalize      => $iam_normalize,
     oidc_clientid      => $oidc_clientid,
     oidc_allowissuer   => $oidc_allowissuer,
     oidc_allowaudience => $oidc_allowaudience,
diff --git a/src/puppet/dpm/manifests/headnode.pp b/src/puppet/dpm/manifests/headnode.pp
index 93aba0e5..15abd9e9 100644
--- a/src/puppet/dpm/manifests/headnode.pp
+++ b/src/puppet/dpm/manifests/headnode.pp
@@ -50,6 +50,8 @@ class dpm::headnode (
   Boolean $xrootd_use_voms = $dpm::params::xrootd_use_voms,
   Optional[String] $http_macaroon_secret = $dpm::params::http_macaroon_secret,
 
+  Optional[Boolean] $iam_normalize = $dpm::params::iam_normalize,
+
   Optional[String] $oidc_clientid = $dpm::params::oidc_clientid,
   Optional[String] $oidc_clientsecret = $dpm::params::oidc_clientsecret,
   Optional[String] $oidc_passphrase = $dpm::params::oidc_passphrase,
@@ -263,6 +265,7 @@ class dpm::headnode (
     enable_dome        => $configure_dome,
     enable_domeadapter => $configure_domeadapter,
     host_dn            => $host_dn,
+    iam_normalize      => $iam_normalize,
     oidc_clientid      => $oidc_clientid,
     oidc_allowissuer   => $oidc_allowissuer,
     oidc_allowaudience => $oidc_allowaudience,
diff --git a/src/puppet/dpm/manifests/params.pp b/src/puppet/dpm/manifests/params.pp
index a7861a22..f574d20c 100644
--- a/src/puppet/dpm/manifests/params.pp
+++ b/src/puppet/dpm/manifests/params.pp
@@ -61,6 +61,8 @@ class dpm::params {
   $xrootd_use_voms = hiera('dpm::params::xrootd_use_voms',true)
   $http_macaroon_secret = hiera('dpm::params::http_macaroon_secret',undef)
 
+  $iam_normalize = hiera('dpm::params::iam_normalize',undef)
+
   $oidc_clientid      = hiera('dpm::params::oidc_clientid',undef)
   $oidc_clientsecret  = hiera('dpm::params::oidc_clientsecret',undef)
   $oidc_passphrase    = hiera('dpm::params::oidc_passphrase',undef)