Skip to content

Commit

Permalink
Add a new gPlazma account phase plugin
Browse files Browse the repository at this point in the history
gPlazma2 was still missing a simple way to ban users. This patch adds a
new plugin that allows blacklisting users based on <principal
type>:<value>
pairs given in a plain text file.

Example file:

---

alias name=org.dcache.auth.LoginNamePrincipal
alias dn=org.globus.gsi.jaas.GlobusPrincipal

ban dn:/C=XY/O=org/OU=Some Where/CN=Some One
ban javax.security.auth.kerberos.KerberosPrincipal:SOMEONE@SOMEWHERE.ORG
ban name:someone
ban dn:/C=XY/O=org/OU=Some Where/CN=Some One Else
ban javax.security.auth.kerberos.KerberosPrincipal:SOMEONEELSE@SOMEWHERE.ORG
---

Acked-by: Paul
Acked-by: Gerd
Target: master
Request: 2.6
Require-book: yes
Require-notes: yes
Patch: http://rb.dcache.org/r/5777/
  • Loading branch information
Karsten Schwank committed Jul 31, 2013
1 parent 3fb26cb commit eb7c315
Show file tree
Hide file tree
Showing 10 changed files with 430 additions and 25 deletions.
56 changes: 36 additions & 20 deletions modules/common/src/main/java/org/dcache/auth/Subjects.java
Expand Up @@ -5,6 +5,8 @@
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -124,7 +126,7 @@ public static long[] getUids(Subject subject)
* Returns the principal of the given type of the subject. Returns
* null if there is no such principal.
*
* @throw IllegalArguemntException is subject has more than one such principal
* @throws IllegalArgumentException is subject has more than one such principal
*/
private static <T> T getUniquePrincipal(Subject subject, Class<T> type)
throws IllegalArgumentException
Expand Down Expand Up @@ -216,7 +218,7 @@ public static long getPrimaryGid(Subject subject)
* Returns the origin of a subject. Returns null if subject has no
* origin.
*
* @param IllegalArgumentException if there is more than one origin
* @throws IllegalArgumentException if there is more than one origin
*/
public static Origin getOrigin(Subject subject)
throws IllegalArgumentException
Expand All @@ -227,7 +229,7 @@ public static Origin getOrigin(Subject subject)
/**
* Returns the DN of a subject. Returns null if subject has no DN.
*
* @param IllegalArgumentException if there is more than one origin
* @throws IllegalArgumentException if there is more than one origin
*/
public static String getDn(Subject subject)
throws IllegalArgumentException
Expand Down Expand Up @@ -279,7 +281,7 @@ public static Collection<String> getFqans(Subject subject)
* Returns the the user name of a subject. If UserNamePrincipal is
* not defined then null is returned.
*
* @throw IllegalArgumentException if subject has more than one
* @throws IllegalArgumentException if subject has more than one
* user name
*/
public static String getUserName(Subject subject)
Expand All @@ -293,7 +295,7 @@ public static String getUserName(Subject subject)
* Returns the the login name of a subject. If LoginNamePrincipal
* is not defined then null is returned.
*
* @throw IllegalArgumentException if subject has more than one
* @throws IllegalArgumentException if subject has more than one
* login name
*/
public static String getLoginName(Subject subject)
Expand Down Expand Up @@ -446,21 +448,35 @@ public static Set<Principal> principalsFromArgs(List<String> args)
Principal principal;

switch (type) {
case "dn":
principal = new GlobusPrincipal(value);
break;
case "kerberos":
principal = new KerberosPrincipal(value);
break;
case "fqan":
principal = new FQANPrincipal(value, isPrimaryFqan);
isPrimaryFqan = false;
break;
case "name":
principal = new LoginNamePrincipal(value);
break;
default:
throw new IllegalArgumentException("unknown type: " + type);
case "dn":
principal = new GlobusPrincipal(value);
break;
case "kerberos":
principal = new KerberosPrincipal(value);
break;
case "fqan":
principal = new FQANPrincipal(value, isPrimaryFqan);
isPrimaryFqan = false;
break;
case "name":
principal = new LoginNamePrincipal(value);
break;
default:
try {
Class principalClass = Class.forName(type);
Constructor principalConstructor = principalClass.getConstructor(String.class);
principal = (Principal)principalConstructor.newInstance(value);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("No matching constructor found: "+type+"(String)");
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("No matching class found: "+type);
} catch (InvocationTargetException e) {
throw new IllegalArgumentException("Invocation failed: "+e.toString());
} catch (InstantiationException e) {
throw new IllegalArgumentException("Instantiation failed: "+e.toString());
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Access Exception: "+e.toString());
}
}

principals.add(principal);
Expand Down
61 changes: 61 additions & 0 deletions modules/gplazma2-banfile/pom.xml
@@ -0,0 +1,61 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.dcache</groupId>
<artifactId>dcache-parent</artifactId>
<version>2.7.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>gplazma2-banfile</artifactId>
<packaging>jar</packaging>

<name>gPlazma 2 principal ban file plugin</name>

<dependencies>
<dependency>
<groupId>org.dcache</groupId>
<artifactId>gplazma2</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,6 @@
<plugins>
<plugin>
<name>banfile</name>
<class>org.dcache.gplazma.plugins.BanFilePlugin</class>
</plugin>
</plugins>
@@ -0,0 +1,104 @@
package org.dcache.gplazma.plugins

import scala.collection.JavaConversions._

import java.util
import java.util.Properties
import java.security.Principal

import org.dcache.auth.Subjects
import org.dcache.gplazma.AuthenticationException
import scala.io.Source

object BanFilePlugin {
val BAN_FILE = "gplazma.banfile.path"
}

class BanFilePlugin(properties : Properties) extends GPlazmaAccountPlugin with FileCache[Set[Principal]] {

/**
* Get the filename of the ban file from the properties.
*/
val banFile = {
if (properties == null) {
throw new IllegalArgumentException("properties is null")
}
val filename = properties getProperty BanFilePlugin.BAN_FILE
if (filename == null) {
throw new IllegalArgumentException(BanFilePlugin.BAN_FILE + " not set")
}

filename
}

private[plugins] def fromSource : Source = try {
Source fromFile banFile
} catch {
case e:Exception => throw new IllegalStateException("cannot read file " + banFile +": "+e.getMessage, e)
}

/**
* Create a list of principals from the source file.
* principalsFromSource filters out empty lines and comments, i.e., lines starting with #
* It expects the file to be of the format:
* alias <alias>=<full qualified classname>
* ban <full qualified classname or alias>:<principal string>
* e.g.,
* alias username=org.dcache.auth.LoginNamePrincipal
* ban username:Someuser
* or
* ban org.dcache.auth.LoginNamePrincipal:Someuser
*
* @return a set of banned principals
*/
private def principalsFromFile(filename : String) = {

def filteredLines(lines : List[String], filtered : List[String], aliases : Map[String, String]) : List[String] = {
lines match {
case Nil => filtered
case line :: rest if line startsWith "#" => filteredLines(rest, filtered, aliases)
case line :: rest if line.trim == "" => filteredLines(rest, filtered, aliases)
case line :: rest if line startsWith "alias" => {
"""^alias\s+([^:]+)=(.*)$""".r("alias", "class") findFirstMatchIn line match {
case None => throw new IllegalArgumentException("Bad alias line format: '"+line+"', expected 'alias <alias>=<class>'")
case Some(m) => filteredLines(rest, filtered, aliases + (m.group("alias").trim -> m.group("class").trim))
}
}
case line :: rest if line startsWith "ban" => {
"""^ban\s+([^:]+):(.*)$""".r("class", "params") findFirstMatchIn line match {
case None => throw new IllegalArgumentException("Bad ban line format: '"+line+"', expected 'ban <classOrAlias>:<value>'")
case Some(m) => filteredLines(rest, {
aliases.get(m.group("class").trim) match {
case None => m.group("class").trim
case Some(a) => a
}
}+":"+m.group("params") :: filtered, aliases)
}
}
case line :: _ => throw new IllegalArgumentException("Line has bad format: '"+line+"', expected '[alias|ban] <key>:<value>'")
}
}

Subjects.principalsFromArgs(filteredLines(fromSource.getLines().toList, Nil, Map())).toSet
}

/**
* Get banned principals from file
* @return a set of banned principals
*/
private def bannedPrincipals = getOrFetch(banFile)(principalsFromFile)

/**
* Check if any of the principals in authorizedPrincipals is blacklisted in the
* file specified by the dCache property gplazma.banfile.uri.
*
* @param authorizedPrincipals principals associated with a user
* @throws AuthenticationException indicating a banned user
*/
def account(authorizedPrincipals: util.Set[Principal]) {
if ((authorizedPrincipals intersect bannedPrincipals).nonEmpty) {
throw new AuthenticationException("user banned")
}
}
}

@@ -0,0 +1,20 @@
package org.dcache.gplazma.plugins

import scala.collection.mutable
import java.io.File

trait FileCache[T] {

private var cache = new mutable.HashMap[String, (Long, T)]()

def getOrFetch(filename : String)(fetch : (String) => T) : T = {
val file = new File(filename)
cache.get(filename) match {
case None => cache += (filename -> (file.lastModified, fetch(filename)))
case Some((lastFetch, _)) if file.lastModified > lastFetch => cache(filename) = (file.lastModified, fetch(filename))
case _ => // entry exists and is up to date
}
cache(filename)._2
}

}
1 change: 1 addition & 0 deletions modules/gplazma2-banfile/src/test/resources/ban.conf
@@ -0,0 +1 @@
# this is just an empty file for testing plugin initialisation

0 comments on commit eb7c315

Please sign in to comment.