opengrok 权限管控

en:https://github.com/oracle/opengrok/wiki/Authorization#setting-up-tomcat

 使用OpenGrok授权

本指导提供关于opengrok授权的授权框架的信息。你可以和project groupings结合使用该模块, project groupings允许你创建各种项目结构。

兼容性

如果插件目录不存在或者目录下没有任何插件,这个框架允许每一个请求。

配置

你可以配置:

  1. 插件目录

    这个配置已经从web.xml移动到OpenGrok通用xml配置文件中。提供新插件目录的方式是创建一个新的xml配置文件,“read-only configuration” 文件,该文件将与合并索引器生成的配置合并。

    在这个文件中,提供一个新的名为pluginDirectory的变量,带有一个字符串参数,参数值指向插件所在的绝对路径。

    默认情况下,这个目录指向 OPENGROK_DATA_ROOT/pluginsOPENGROK_DATA_ROOT 是在主配置文件configuration.xml设置的dataRoot 目录。如果插件不可见/目录不存在,那么框架会允许每一个请求。

    Opengrok授权在该目录中使用插件类,它将这些类作为插件加载到内存中,然后对每个请求执行授权。

  2. 监听服务

    类似监控服务在配置中都有一个专用的配置选项,你可以按照上面的描述进行设置。配置项的名称是authorizationWatchdogEnabled, 值为boolean类型。

    该服务监听插件目录的修改(包含递归子目录),如果发生了如下修改 (modifing, deleting),它会重新加载插件。内存中所有的的旧插件都会被清除,并且只有插件目录的插件会被加载。这个是实时的。

    注意:修改插件目录下的任何文件都会被作为一个事件广播出来。这就意味着,在插件目录大批量修改文件,会导致多次重加载发生。

    默认情况下,该值为flase

  3. 授权堆栈

    在下面的高级配置中讨论。

  4. 提供一个插件

    你可以将编译后的插件类(.class或.jar)放到插件目录来提供你自己的插件。

    注意:当使用.jar时,有很重要的经验,将在 故障诊断与排除 一节中被提到。

插件

例子

在git库的根目录的plugins文件夹中包含了一个 TruePlugin.java 的例子。

接口实现

每个插件必须实现 IAuthorizationPlugin 接口,它包含了四个方法:

  1. void load ( void );

    每当插件被框架加载到内存时,这个方法就会被调用

    • 监听服务启用,并且有变更(强制重加载)
    • 部署web应用
    • 配置变更(变更插件目录.....)
  2. void unload ( void );

    每当框架销毁插件时,该方法被调用。

    • 监听服务启用,并且有变更(强制重加载)(重载的顺序是先卸载旧的实例,然后加载新的)
    • web应用取消部署
  3. boolean isAllowed(HttpServletRequest request, Project project);

    该project是否应该允许当前请求,这个方法给一个决策。

  4. boolean isAllowed(HttpServletRequest request, Group group);

    类似的,对该group给出决策。

所有这些方法在代码中也都有描述。

当为了用户授权所做的底层读取操作的成本很高时,都希望能为这些决策实现某种缓存。

HttpServletRequest 对象是当前请求,包含了所有特性:session, attributes, getUser()getPrincipal()

会话

如果你决定你的插件使用某种会话,当授权框架重加载插件时,你可能希望也会重载这个会话。框架会记录一个重加载的编号,这个对你都是可见的,通过 RuntimeEnvironment.getInstance().getPluginVersion() 可以获取到。你可以比对这个编号,如果与你的版本(假设在你的会话中记录了)不同,那重启你的会话。

限制条件

自定义类加载器限制插件只能加载org.opensolaris.opengrok包里的类:

private final static String[] classWhitelist = new String[]{
    "org.opensolaris.opengrok.configuration.Group",
    "org.opensolaris.opengrok.configuration.Project",
    "org.opensolaris.opengrok.configuration.RuntimeEnvironment",
    "org.opensolaris.opengrok.authorization.IAuthorizationPlugin",
    "org.opensolaris.opengrok.util.*",
    "org.opensolaris.opengrok.logger.*",
};

并且禁止这些包被继承。

private final static String[] packageBlacklist = new String[]{
     "java",
     "javax",
     "org.w3c",
     "org.xml",
     "org.omg",
     "sun"
};

并且JVM也会禁止你去继承一些不应被继承的包。

设置

插件类必须编译成 .class 文件(也可以打包成 .jar 文件)。框架同时支持 .class 和 .jar 文件。 为了编译,你必须在classpath中提供 opengrok.jar,以及 servlet api (HttpServletRequest) 。

例如 (对于包含在仓库中的 TruePlugin ):$ javac -classpath dist/opengrok.jar -d . plugins/TruePlugin.java

然后,你可以将编译后的 .class 文件放到插件目录中,并部署web应用。如果插件是包的一部分,你必须复制编译器生成的相对源目录的整个文件夹。

如果发生错误,你应该从log中查找信息。

运行

框架由三部分组成

  1. 插件框架

    它是插件的容器

    • 执行授权决策
    • 缓存授权结果,以便每个请求的授权,特定的插件的针对Project/Group的isAllowed方法只会被调用一次。
  2. ProjectHelper

    过滤(授权)后的projects/groups/repositories 的界面呈现,应该只用于在任何地方显示过滤后的信息。

    • 提供获取过滤后的 projects/groups/repositories 的方法
    • 为每个请求缓存过滤结果,这样就可以避免不必要的框架调用
  3. AuthorizationFilter

    标准的javax.servlet.Filter 用于所有urls,判定当前请求是否有权限访问该url。

    • 限制用户访问被禁止的xref/history/download 等。返回403 HTTP错误。
    • 只有当url包含关于项目的信息时才会发生反应(所以它可以决定每个项目)

每一个403错误都会被记录。但是关于用户的信息是丢失的,因为决策是由插件做的,过滤器不知道如何解析请求来从中获取用户信息。

高级配置

新1.0版本包含了一个如何在更多细节上配置插件调用的新特性。

一堆插件

这些插件构成了一个列表的线性结构(称为堆栈)。这些插件的isAllowed方法的调用顺序是由它们在堆栈中的位置决定的。另外,你可以为每一个插件配置不同的标志,以便在执行鉴权时受到重视。

有三种标志:

  1. REQUIRED 这种插件的失败会最终导致框架的授权失败,但是要等剩下的插件都执行完毕之后。

  2. REQUISITE 类似 required,不过,一旦这种插件返回授权失败,控件会直接返回到应用程序。与第一个required 或 requisite插件相关联的返回值置为失败。

  3. SUFFICIENT 如果这种插件成功鉴权,并且之前没有失败的REQUIRED插件,授权框架立刻会给应用层返回成功信息,而不再调用堆栈中的其他插件。sufficient插件的失败会被忽略,然后继续处理插件列表,最终结果不受影响。

这些是受到了 PAM Authorization framework 的启发,这些定义也都是直接取自PAM配置的手册页。不过OpenGrok并没有实现所有的PAM标志。

向后兼容

你可以使用授权框架而不必提供这些高级配置,是因为所有已经加载的没有这个高级配置的插件,都被追加了REQUIRED到列表中。可是,由于类发现的特性,意味着这些插件被调用是非常随机的。

示例

这是一个 "read-only configuration"的例子。它定义了三个插件,每一个都有不同的规则,以此来从不同的方式影响堆栈的处理进程。作为授权规则,用类名来指定目标插件,把其中一种标志作为授权角色。这个堆栈稍后对每个请求从上到下进行处理(因为它是一个列表),对带有标志的特定插件的决策进行评估。

 1 <void property="pluginStack">
 2   <void method="add">
 3     <object class="org.opensolaris.opengrok.authorization.AuthorizationPlugin">
 4       <void property="flag">
 5         <string>REQUISITE</string>
 6       </void>
 7       <void property="name">
 8         <string>some.my.package.RequisitePlugin</string>
 9       </void>
10     </object>
11   </void> 
12   <void method="add">
13     <object class="org.opensolaris.opengrok.authorization.AuthorizationPlugin">
14       <void property="flag">
15         <string>SUFFICIENT</string>
16       </void>
17       <void property="name">
18         <string>Plugin</string>
19       </void>
20     </object>
21   </void>
22   <void method="add">
23     <object class="org.opensolaris.opengrok.authorization.AuthorizationPlugin">
24       <void property="flag">
25         <string>REQUIRED</string>
26       </void>
27       <void property="name">
28         <string>ExamplePlugin</string>
29       </void>
30     </object>
31   </void>   
32 </void>

这个配置的一个典型应用可能是:

RequisitePlugin

  1. 在这个requisite plugin 中确定用户标识,并将其保存在请求中。
  2. 当这个动作成功后返回true或false。

Plugin

  1. 使用在requisite plugin 中保存在请求中的确定了的用户标识。
  2. 执行一项授权检查,如果成功,该检查可以导致立刻从堆栈中返回。
  3. 如果检查是否定值,堆栈会继续执行第三个插件。

ExamplePlugin

  1. 使用在requisite plugin 中保存在请求中的确定了的用户标识。
  2. 过滤掉之前的插件执行时没有被启用的部分。

HTTP基础教程

这是一个使用http基础授权的例子。

这个例子有几个前提条件:

  1. 你有一个干净的正在工作中的OpenWork实例(无项目,无群组)
  2. 你会使用 Groups 子命令。(当你在tools下的克隆github仓时会用到)
  3. 你有权限修改Tomcat文件
  4. 你已经设置过插件目录或你知道它在哪 (见上面)

设置Tomcat

Tomcat支持HTTP,并且我们可以配置一个简单的示例,包含用户和角色。在本向导中,将使用我们自己定义的用户和角色来介绍如何设置Tomcat。

Tomcat users

我们需要修改一个Tomcat文件来提供该系统中的用户和角色信息。这个文件放置在 $CATALINA_BASE/conf/tomcat-users.xml. 为了这个示例,添加这些行到该文件的 <tomcat-users>节点中。

<role rolename="users"/>
<role rolename="admins"/>
<role rolename="plugins"/>
<role rolename="ghost"/>

<user username="007" password="123456" roles="users"/>
<user username="008" password="123456" roles="plugins"/>
<user username="009" password="123456" roles="users,admins"/>
<user username="00A" password="123456" roles="admins"/>
<user username="00B" password="123456" roles="admins,plugins"/>
<user username="00F" password="123456" roles="ghost"/>

通过这几行,我们告诉Tomcat设置4个角色和6个关联他们角色的用户。你可以根据你的需要修改用户名和密码,但是我们将在这个示例中用到这些。

Application deployment descriptor

现在,我们需要告诉应用程序,它应该使用HTTP Basic认证来保护它的资源。我们可以通过修改web.xml来做到,这个文件通常被放置在你应用程序的WEB-INF文件夹中。为了让它工作,下面这些行是必须的。

<security-constraint>

    <web-resource-collection>
        <url-pattern>/*</url-pattern> <!-- protect the whole application -->
        <http-method>GET</http-method>
        <http-method>POST</http-method>
    </web-resource-collection>
    <auth-constraint>
        <role-name>plugins</role-name> <!-- these are the roles from tomcat-users.xml -->
        <role-name>users</role-name> <!-- these are the roles from tomcat-users.xml -->
        <role-name>admins</role-name> <!-- these are the roles from tomcat-users.xml -->
    </auth-constraint>

    <user-data-constraint>
        <!-- transport-guarantee can be CONFIDENTIAL, INTEGRAL, or NONE -->
        <transport-guarantee>NONE</transport-guarantee>
    </user-data-constraint>
</security-constraint>

<security-role>
    <role-name>plugins</role-name>
    <role-name>users</role-name>
    <role-name>admins</role-name>
</security-role>

<login-config>
    <auth-method>BASIC</auth-method>
</login-config>

监听服务

我们强烈建议你打开监听服务,它适合于开发插件,通过“read-only configuration”设置参数为true.

这会强制应用程序在当插件目录下有任何文件发生修改时,重新加载插件目录下的所有插件。

设置代码仓库

我们需要创建几个测试库来体现授权特性。

$ cd "$OPENGROK_SRC_ROOT" # navigate to the source folder
$ for name in `seq 1 11`; do mkdir "test-project-$name"; cd "test-project-$name"; echo "Give it a try! Hello from $i" > README.md; git init; git config user.email "x"; git config user.name "y"; git add .; git commit -m "init commit"; cd ..; done;

这会在source目录下创建10个名为“test-project-$number”的测试库。

设置分组

为了真正的使用授权特性,建议创建Project群组。在授权插件中,你可以使用整个群组来代替单个项目。

我们将使用一个名为Groups的工具来创建它们。

$ export OPENGROK_READ_XML_CONFIGURATION=/var/opengrok/opt/myconf.xml
$ ./tools/Groups empty > "$OPENGROK_READ_XML_CONFIGURATION"
$ ./tools/Groups add admins  "test-project-1|test-project-2|test-project-3|test-project-4" -u
$ ./tools/Groups add users   "test-project-5|test-project-6|test-project-7|test-project-8" -u
$ ./tools/Groups add plugins "test-project-9|test-project-10" -p users -u

组名与之前在tamcat-users.xml中定义的角色对应。现在最终的群组结构应该看起来像这样:

$ ./tools/Groups list
admins ~ "test-project-1|test-project-2|test-project-3|test-project-4"
users ~ "test-project-5|test-project-6|test-project-7|test-project-8"
plugins ~ "test-project-9|test-project-10"

OPENGROK_READ_XML_CONFIGURATION

这个变量包含了一个指向xml文件路径,该xml文件最终在index/reindex前与主配置文件(通常是 /var/opengrok/configuration.xml)合并。如果你没有使用过这个,你可以用下面语句生成一个新的(示例):

$ export OPENGROK_READ_XML_CONFIGURATION=/var/opengrok/opt/myconf.xml
$ ./tools/Groups add admins "test-project-1|test-project-2|test-project-3|test-project-4" > "$OPENGROK_READ_XML_CONFIGURATION"

这个命令生成了一个“admins”群组的配置项,并将其保存到了 "$OPENGROK_READ_XML_CONFIGURATION" 文件中.

更多关于这个过程的描述,参考 Advanced Configuration

索引

现在像往常一样执行索引。不要忘了使用前一步中使用过的 OPENGROK_READ_XML_CONFIGURATION 变量,因为它会将群组传递到主配置文件中。这是如何在reindex时保存群组的唯一选择 - 使用 OPENGROK_READ_XML_CONFIGURATION 变量!

插件

现在进行主体部分 - 插件本身。

它包含三部分:

  1. 权限策略
  2. 发现群组
  3. 授权检查

权限策略

你可以使用你想使用的无论什么策略,甚至是java或你操作系统中的外置工具。在这个示例中,我们将使用静态映射表,定义哪些用户和群组有权限访问哪些项目。

private static final Map<String, Set<String>> userProjects = new TreeMap<>();
private static final Map<String, Set<String>> userGroups = new TreeMap<>();

static {
    // all have access to "test-project-11" and some to other "test-project-5" or "test-project-8"
    userProjects.put("007", new TreeSet<>(Arrays.asList(new String[]{"test-project-11", "test-project-5"})));
    userProjects.put("008", new TreeSet<>(Arrays.asList(new String[]{"test-project-11", "test-project-8"})));
    userProjects.put("009", new TreeSet<>(Arrays.asList(new String[]{"test-project-11"})));
    userProjects.put("00A", new TreeSet<>(Arrays.asList(new String[]{"test-project-11"})));
    userProjects.put("00B", new TreeSet<>(Arrays.asList(new String[]{"test-project-11"})));
}

static {
    userGroups.put("007", new TreeSet<>(Arrays.asList(new String[]{})));
    userGroups.put("008", new TreeSet<>(Arrays.asList(new String[]{})));
    userGroups.put("009", new TreeSet<>(Arrays.asList(new String[]{})));
    userGroups.put("00A", new TreeSet<>(Arrays.asList(new String[]{})));
    userGroups.put("00B", new TreeSet<>(Arrays.asList(new String[]{})));
}

群组发现

运行在插件上层的插件框架在做决策的时候,无法知道我们的插件想做什么,所以没有途径可以在群组中自动发现groups/subgroups/projects。所有这些都必须在我们的插件中完成,最好是只在特定用户首次加载/使用插件时完成(本示例中不包含)。

非常重要的含义是,允许一个群组并不意味着允许它的子群组或项目。可以考虑将这个视为一个特性,因为它让你允许或不允许特定的群组和项目有更多的自由。

这是最重要的部分 - 如果你为一个用户发现了一个群组,那就把它的所有项目、仓库、子群组和底层对象都添加进去。

if ((g = Group.getByName(group)) != null) {
    // group discovery
    for (Project p : g.getRepositories()) {
        userProjects.get(request.getUserPrincipal().getName()).add(p.getDescription());
    }
    for (Project p : g.getProjects()) {
        userProjects.get(request.getUserPrincipal().getName()).add(p.getDescription());
    }
    for (Group grp : g.getDescendants()) {
        for (Project p : grp.getRepositories()) {
            userProjects.get(request.getUserPrincipal().getName()).add(p.getDescription());
        }
        for (Project p : grp.getProjects()) {
            userProjects.get(request.getUserPrincipal().getName()).add(p.getDescription());
        }
        descendants.add(grp.getName());
    }
    while (g != null) {
        descendants.add(g.getName());
        g = g.getParent();
    }

}

最后,userProjects和userGroups是User-Projects/Groups的一组映射,在这里我们可以通过只遍历映射表和对应的集合,快速的判断一个用户是否有指定实体的权限。

授权检查

最终的授权检查只是一个示例 - 检查一个这个map是否包含某个用户的Project/Group。

// for projects
return userProjects.get(request.getUserPrincipal().getName()).contains(project.getDescription());
// or for groups
return userGroups.get(request.getUserPrincipal().getName()).contains(group.getName());

运行

现在我们可以按照当前wiki页面上边介绍的方式来编译插件,并将编译结果放到插件目录 (默认 $OPENGROK_DATA_ROOT/plugins)。如果你开启了监听服务,那你可以看到结果了,如果没有,你需要重启应用程序。

当你进入到应用程序中,页面会直接触发一个登录表单,在这里你可以输入认证信息(如在 tomcat users 中所写)。根据你进入时的输入,你可以在主页面看到过滤后的结果,并且即使在你搜索任何东西时,你也无法访问其他项目。

你可以退出登录,通过忘记密码会话(ctrl+shift+delete + 忘记当前会话/不同浏览器可能有所不同)来用其他账户登录。

完成的代码

HttpBasicAuthorizationPlugin.java

疑难问题解决

使用IDE

当使用IDE(NetBeans)编译你的插件时,你会面临一个问题,框架因为ClassFormatError而不会加载你的.jar文件导致ClassNotFoundException。

可能的解决方法是:

  1. 禁用 debugging 标志(project/properities/build/compile/uncheck 生成 debugging 信息)
  2. 手动通过javac编译 .java文件,然后手动打包到 .jar中(命令如上)
  3. 手动通过javac编译 .java文件,使用这些目录结构(不打包)

这些应该可以解决这个问题。如果不能,尝试修改代码。

posted @ 2018-04-26 10:52  临江仙·2007  阅读(894)  评论(0编辑  收藏  举报