Writing a custom plugin¶
Ranger's stack model lets you protect a new application without changing Ranger itself. You describe the application's resources and permissions in a JSON service definition, register it with Ranger Admin, and embed the Ranger plugin runtime in the application to authorize requests. Ranger Admin then offers the usual UI, REST API, auditing, tags, roles and security zones for the new service type.
This page walks through the four pieces, using the sampleapp example in ranger-examples as the running
example.
flowchart LR
def[Service definition JSON] -->|register via REST| admin[Ranger Admin]
admin -->|policies| plugin[RangerBasePlugin in your app]
app[Your application hook] --> plugin
admin -.->|Test Connection, lookup| svc[RangerBaseService impl on Admin classpath]
1. Define the service type¶
A service definition declares:
- resources — the hierarchy of things you protect (
database›table›column, or a singlepath), with matcher options (wildCard,ignoreCase),recursiveSupported,excludesSupported,lookupSupported,isValidLeafand optionalaccessTypeRestrictions; - accessTypes — permissions such as
read,write,submit-app, optionally withimpliedGrantsand acategory(READ,UPDATE,CREATE,DELETE,MANAGE); - configs — what Ranger Admin needs to connect to the application (URL, user, password, ...);
- optionally policyConditions, contextEnrichers, dataMaskDef, rowFilterDef, enums
and options (
enableDenyAndExceptionsInPolicies,enableTagBasedPolicies, ...).
The YARN definition is a compact real-world example:
{
"name": "yarn",
"implClass": "org.apache.ranger.services.yarn.RangerServiceYarn",
"label": "YARN",
"description": "YARN",
"resources": [
{
"itemId": 1, "name": "queue", "type": "string", "level": 10,
"mandatory": true, "lookupSupported": true, "recursiveSupported": true,
"matcher": "org.apache.ranger.plugin.resourcematcher.RangerPathResourceMatcher",
"matcherOptions": { "wildCard": true, "ignoreCase": false, "pathSeparatorChar": "." },
"label": "Queue", "description": "Queue"
}
],
"accessTypes": [
{ "itemId": 1, "name": "submit-app", "label": "submit-app", "category": "UPDATE" },
{ "itemId": 2, "name": "admin-queue", "label": "admin-queue", "category": "MANAGE", "impliedGrants": ["submit-app"] }
],
"configs": [
{ "itemId": 1, "name": "username", "type": "string", "mandatory": true, "label": "Username" },
{ "itemId": 2, "name": "password", "type": "password", "mandatory": true, "label": "Password" },
{ "itemId": 3, "name": "yarn.url", "type": "string", "mandatory": true, "label": "YARN REST URL" }
]
}
Start from the definition closest to your model in
agents-common/src/main/resources/service-defs,
give every element a unique itemId, and pick a name that is a valid identifier — it becomes the property
prefix ranger.plugin.<name>.* and the audit service type. The full model is described in
Plugin architecture.
2. Register the definition with Ranger Admin¶
Ranger Admin exposes the service-definition API under /service (the public v2 API in
PublicAPIsv2 delegates to ServiceREST, which also serves the internal path; the public v2 API is the stable one):
# Public v2 API (recommended)
curl -u admin:password -X POST -H "Content-Type: application/json" \
-d @ranger-servicedef-myapp.json \
http://ranger-admin:6080/service/public/v2/api/servicedef/
# Internal path used in older documentation
curl -u admin:password -X POST -H "Content-Type: application/json" \
-d @ranger-servicedef-myapp.json \
http://ranger-admin:6080/service/plugins/definitions
Useful related endpoints: GET /service/public/v2/api/servicedef/name/{name},
PUT /service/public/v2/api/servicedef/name/{name} to update, and
DELETE /service/public/v2/api/servicedef/name/{name}. Creating a definition requires the Admin role.
See REST API.
Once registered, Service Manager shows the new type; create a service instance (for example myapp_dev)
and write policies as for any other plugin.
Tip
The definition of sampleapp in the example is deliberately omitted: the sample reuses an HDFS
service (path resource with read, write, execute) so that it runs against a stock Ranger Admin.
For your own application, register a definition whose resource names match what your authorizer sets in
step 3.
3. Embed the plugin in your application¶
The runtime is org.apache.ranger.plugin.service.RangerBasePlugin from ranger-plugins-common. The pattern
used by every plugin in the repository, and by ranger-examples/plugin-sampleapp/RangerAuthorizer.java, is:
public class RangerAuthorizer implements IAuthorizer {
private static volatile RangerBasePlugin plugin;
public void init() {
if (plugin == null) {
synchronized (RangerAuthorizer.class) {
if (plugin == null) {
plugin = new RangerBasePlugin("sampleapp", "sampleapp"); // serviceType, appId
plugin.setResultProcessor(new RangerDefaultAuditHandler(plugin.getConfig()));
plugin.init(); // loads policies, starts the refresher thread
}
}
}
}
public boolean authorize(String fileName, String accessType, String user, Set<String> userGroups) {
RangerAccessResourceImpl resource = new RangerAccessResourceImpl();
resource.setValue("path", fileName); // must be a resource name from the servicedef
RangerAccessRequest request = new RangerAccessRequestImpl(resource, accessType, user, userGroups, null);
RangerAccessResult result = plugin.isAccessAllowed(request);
return result != null && result.getIsAllowed();
}
}
Key points:
- Create one
RangerBasePlugin(serviceType, appId)per process and keep it for the lifetime of the application.init()readsranger-<serviceType>-security.xml,ranger-<serviceType>-audit.xmlandranger-<serviceType>-policymgr-ssl.xmlfrom the classpath (plus the TLS file named byranger.plugin.<serviceType>.policy.rest.ssl.config.file), loads the cached policies if present, and starts thePolicyRefresherthat polls Ranger Admin everyranger.plugin.<serviceType>.policy.pollIntervalMs. - Set an audit handler with
setResultProcessor(new RangerDefaultAuditHandler(...)), or pass a handler toisAccessAllowed(request, handler)when you need to batch or post-process audit events. - Build a
RangerAccessRequestImplwith the resource values, access type, user, groups and (optionally) client IP, action name, access time and request data;isAccessAllowed()returns aRangerAccessResultwithgetIsAllowed(),getIsAccessDetermined()andgetPolicyId(). - For masking and filtering call
evalDataMaskPolicies()/evalRowFilterPolicies()and apply the returned mask type or filter expression yourself (see the nested structure plugin for a complete example). getResourceACLs(request)returns the effective permissions for a resource, useful for UIs that show what a user may do.plugin.cleanup()stops the refresher on shutdown.
Configuration files for the example are in ranger-examples/plugin-sampleapp/conf.
ranger-sampleapp-security.xml¶
RangerBasePlugin reads this file from the classpath; replace sampleapp with your service type. It names
the Ranger Admin to contact and the service whose policies are enforced — these two properties are mandatory.
The values for the URL, service name, TLS file and cache directory are those of the example; every other
property is understood by every plugin and is shown with its default.
<configuration>
<!-- Connection to Ranger Admin -->
<property>
<name>ranger.plugin.sampleapp.policy.rest.url</name>
<value>http://localhost:6080</value>
<description>MANDATORY: URL of Ranger Admin. Separate several URLs with commas for Ranger Admin
high availability.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.service.name</name>
<value>cl1_hadoop</value>
<description>MANDATORY: Name of the Ranger service whose policies are enforced.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.policy.source.impl</name>
<value>org.apache.ranger.admin.client.RangerAdminRESTClient</value>
<description>Class that retrieves policies. The default downloads them from Ranger Admin over
REST.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.policy.rest.ssl.config.file</name>
<value>ranger-policymgr-ssl.xml</value>
<description>Path of the TLS client configuration file (ranger-policymgr-ssl.xml). Needed only
when Ranger Admin uses HTTPS. Default: not set.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.policy.rest.client.connection.timeoutMs</name>
<value>120000</value>
<description>Connect timeout for calls to Ranger Admin. Unit: milliseconds.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.policy.rest.client.read.timeoutMs</name>
<value>30000</value>
<description>Read timeout for calls to Ranger Admin. Unit: milliseconds.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.policy.rest.client.max.retry.attempts</name>
<value>3</value>
<description>Number of retries for a failed call to Ranger Admin.</description>
</property>
<!-- Policy refresh and cache -->
<property>
<name>ranger.plugin.sampleapp.policy.pollIntervalMs</name>
<value>30000</value>
<description>Interval between policy refreshes. Unit: milliseconds.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.policy.cache.dir</name>
<value>/tmp</value>
<description>Directory for the on-disk policy cache. It must be writable by the process that
hosts the plugin. Default: not set.</description>
</property>
<!-- Authorization behavior -->
<property>
<name>ranger.plugin.sampleapp.service.admins</name>
<value></value>
<description>Comma-separated list of users treated as administrators of the service by the
policy engine. Default: not set.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.is.fallback.supported</name>
<value>false</value>
<description>Tells the authorizer that it may fall back to the application's native
authorization when no Ranger policy decides the access.</description>
</property>
<!-- Users, groups and roles -->
<property>
<name>ranger.plugin.sampleapp.use.rangerGroups</name>
<value>false</value>
<description>Add the groups Ranger knows for the user (from UserSync) to the groups supplied
with the request.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.use.only.rangerGroups</name>
<value>false</value>
<description>Ignore the groups supplied with the request and use only the groups Ranger knows
for the user.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.super.users</name>
<value></value>
<description>Comma-separated list of users that are authorized for every access without a
policy. Default: not set.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.super.groups</name>
<value></value>
<description>Comma-separated list of groups whose members are authorized for every access
without a policy. Default: not set.</description>
</property>
<property>
<name>ranger.plugin.sampleapp.audit.exclude.users</name>
<value></value>
<description>Comma-separated list of users whose accesses are not audited.
ranger.plugin.sampleapp.audit.exclude.groups and ranger.plugin.sampleapp.audit.exclude.roles
work the same way for groups and roles. Default: not set.</description>
</property>
</configuration>
ranger-sampleapp-audit.xml¶
In the example this file writes audits through log4j (xasecure.audit.destination.log4j=true,
xasecure.audit.destination.log4j.logger=ranger_audit_logger); switch on xasecure.audit.destination.solr,
xasecure.audit.destination.elasticsearch, xasecure.audit.destination.hdfs or
xasecure.audit.destination.auditserver for production. The properties are the same for every plugin; see
the annotated file on any plugin page, for example Hive, and the
Audit framework.
Running the sample¶
cd ranger-examples
mvn clean package assembly:assembly
mkdir /tmp/sampleapp && cd /tmp/sampleapp
tar xvfz .../target/ranger-examples-<version>-sampleapp.tar.gz
tar xvfz .../target/ranger-examples-<version>-sampleapp-plugin.tar.gz
# edit conf/ranger-sampleapp-security.xml, conf/ranger-sampleapp-audit.xml
./run-sampleapp.sh # default authorizer: allows everything
./run-sampleapp.sh ranger-authz # Ranger authorizer
command> read /tmp/somefile user1 group1 group2
run-sampleapp.sh ranger-authz adds lib/ranger-sampleapp-plugin-impl/*.jar to the classpath and sets
-Dsampleapp.authorizer=org.apache.ranger.examples.sampleapp.RangerAuthorizer; SampleApp instantiates
that class by name, which is the usual way an application makes its authorizer pluggable.
4. Support Test Connection and autocomplete (optional)¶
To let Ranger Admin validate the service configuration and autocomplete resource names, extend
org.apache.ranger.plugin.service.RangerBaseService:
public class RangerServiceMyApp extends RangerBaseService {
@Override
public Map<String, Object> validateConfig() throws Exception {
// connect using getConfigs(); throw on failure
}
@Override
public List<String> lookupResource(ResourceLookupContext context) throws Exception {
// context.getResourceName(), context.getUserInput(), context.getResources()
}
}
RangerBaseService also gives you getDefaultRangerPolicies(), which you can override to add extra
policy items to the default all policies created with each new service (the Trino service adds select
for the lookup user this way).
Set the class in the definition's implClass, and put the jar and its dependencies in Ranger Admin's
classpath under ews/webapp/WEB-INF/classes/ranger-plugins/<serviceType>/ (that is how the built-in
ranger-plugins/kudu, ranger-plugins/nifi, ... directories are populated by the admin assembly).
Restart Ranger Admin.
5. Package and isolate¶
- Plain library — add
ranger-plugins-common,ranger-audit-core,ranger-plugins-credand the audit destination modules you use (ranger-audit-dest-solr,ranger-audit-dest-auditserver, ... fromagents-audit) to the application classpath. This is whatplugin-sampleappand the nested structure plugin do. - Shim + classloader — if the host application's dependencies clash with Ranger's, split the plugin into
a shim that the host loads and an implementation directory loaded through
ranger-plugin-classloader(RangerPluginClassLoader). The Presto plugin (ranger-presto-plugin-shimwithlib/ranger-presto-plugin-impl) and the Hive/HBase/HDFS shims are examples. - Configuration — document for your users what must be set in the application's own configuration to
activate the authorizer, and where the
ranger-<serviceType>-*.xmlfiles must be placed, the way the pages in this section do (see Trino for the shape).
Custom policy conditions and context enrichers can be referenced from the definition the same way built-in ones are.
Alternatives to RangerBasePlugin¶
- New Java integrations can use the authorization API (
RangerEmbeddedAuthorizer) instead ofRangerBasePlugin; the Apache Polaris authorizer is built this way. - If you cannot run Java in the application, call the Ranger PDP over REST with the same API. The service definition and policies are the same in every case.