Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions container/features/src/main/resources/features.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,12 @@
<bundle>mvn:org.opennms.features.api-layer/org.opennms.features.api-layer.core/${project.version}</bundle>
<bundle>mvn:org.opennms.features/ui-extension/${project.version}</bundle>
</feature>
<feature name="opennms-api-tokens" version="${project.version}" description="OpenNMS :: Features :: API Tokens">
<!-- api and impl are pulled in via lib directory -->
<!--<bundle>mvn:org.opennms.features/org.opennms.features.api-tokens.api/${project.version}</bundle>-->
<!--<bundle>mvn:org.opennms.features/org.opennms.features.api-tokens.impl/${project.version}</bundle>-->
<bundle>mvn:org.opennms.features/org.opennms.features.api-tokens.shell/${project.version}</bundle>
</feature>
<feature name="opennms-enlinkd-shell" version="${project.version}" description="OpenNMS :: Features :: Enlinkd :: Shell">
<feature>opennms-dao</feature>
<feature>opennms-core-daemon</feature>
Expand Down
7 changes: 7 additions & 0 deletions container/karaf/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@

<!-- OPENNMS: Add http, http-whiteboard to the default list of features -->
<feature>opennms-http-whiteboard</feature>
<feature>opennms-api-tokens</feature>
</bootFeatures>
<installedBundles>
<!-- override for security (see etc/overrides.properties) -->
Expand Down Expand Up @@ -285,6 +286,12 @@
<type>pom</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.opennms.features</groupId>
<artifactId>org.opennms.features.api-tokens.shell</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,7 @@ org.osgi.framework.system.packages.extra=org.apache.karaf.branding,\
org.opennms.core.mate.api;version=${opennms.osgi.version},\
org.opennms.core.utils.url;version=${opennms.osgi.version},\
org.opennms.features.activemq.broker.api;version=${opennms.osgi.version},\
org.opennms.features.apitokens;version=${opennms.osgi.version},\
org.opennms.features.deviceconfig.persistence.api;version=${opennms.osgi.version},\
org.opennms.features.usageanalytics.api;version=${opennms.osgi.version},\
org.opennms.features.dhcpd;version=${opennms.osgi.version},\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ featuresBoot = ( \
opennms-config-management, \
opennms-scv-rest, \
scv-shell, \
opennms-api-tokens, \
opennms-karaf-health
# Ensure that the 'opennms-karaf-health' feature is installed *last*

Expand Down
38 changes: 38 additions & 0 deletions core/schema/src/main/liquibase/35.0.0/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,42 @@
constraintName="uk_eventconf_sources_name"/>
</changeSet>

<changeSet author="cnewkirk" id="35.0.0-api-tokens-table">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="api_tokens"/>
</not>
</preConditions>

<createTable tableName="api_tokens">
<column name="id" type="integer">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="token_hash" type="varchar(64)">
<constraints nullable="false" unique="true"/>
</column>
<column name="username" type="varchar(256)">
<constraints nullable="false"/>
</column>
<column name="description" type="varchar(256)"/>
<column name="created_at" type="timestamp with time zone">
<constraints nullable="false"/>
</column>
<column name="expires_at" type="timestamp with time zone">
<constraints nullable="false"/>
</column>
<column name="last_used_at" type="timestamp with time zone"/>
</createTable>

<createSequence sequenceName="api_tokens_id_seq" startValue="1" incrementBy="1"/>

<createIndex tableName="api_tokens" indexName="idx_api_tokens_token_hash">
<column name="token_hash"/>
</createIndex>

<createIndex tableName="api_tokens" indexName="idx_api_tokens_username">
<column name="username"/>
</createIndex>
</changeSet>

</databaseChangeLog>
1 change: 1 addition & 0 deletions docs/modules/development/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* xref:rest/rest-api.adoc[]
** xref:rest/osgi.adoc[]
** xref:rest/implemented.adoc[Interfaces]
*** xref:rest/api-tokens.adoc[]
*** xref:rest/acknowledgements.adoc[]
*** xref:rest/alarm_statistics.adoc[]
*** xref:rest/alarms.adoc[]
Expand Down
121 changes: 121 additions & 0 deletions docs/modules/development/pages/rest/api-tokens.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@

= API Tokens

API tokens provide long-lived bearer token authentication for programmatic REST API access.
Tokens are generated once, displayed once at creation, and stored as SHA-256 hashes.
Token authentication inherits the user's full role set, matching existing basic auth behavior.

== Authentication

Include the token in the `Authorization` header:

[source,bash]
----
curl -H "Authorization: Bearer onms_<token>" http://localhost:8980/opennms/api/v2/apiTokens
----

== GETs (reading data)

[caption=]
.API Tokens GET functions
[cols="1,3"]
|===
| Resource | Description

| /api/v2/apiTokens
| List the authenticated user's tokens.
Returns id, description, createdAt, expiresAt, and lastUsedAt for each token.
The token hash is never included in responses.

| /api/v2/apiTokens?username=\{user}
| List another user's tokens (requires ROLE_ADMIN).
|===

== POSTs (creating data)

[caption=]
.API Tokens POST functions
[cols="1,3"]
|===
| Resource | Description

| /api/v2/apiTokens
| Create a token for the authenticated user.
Accepts JSON body with optional `description` (string, max 256 chars) and `expiresInDays` (integer).
Returns 201 with the plaintext token, which is shown only once.

| /api/v2/apiTokens?username=\{user}
| Create a token for another user (requires ROLE_ADMIN).
The admin must transmit the plaintext token to the target user out-of-band.
|===

.Example create request
[source,json]
----
{
"description": "grafana integration",
"expiresInDays": 90
}
----

.Example create response
[source,json]
----
{
"id": 1,
"token": "onms_a1b2c3d4...",
"description": "grafana integration",
"createdAt": "2026-03-22T10:00:00.000Z",
"expiresAt": "2026-06-20T10:00:00.000Z"
}
----

== DELETEs (removing data)

[caption=]
.API Tokens DELETE functions
[cols="1,3"]
|===
| Resource | Description

| /api/v2/apiTokens/\{id}
| Revoke a specific token.
Users can revoke their own tokens; admins can revoke any token.
Returns 204 on success, 404 if not found or not authorized.

| /api/v2/apiTokens?username=\{user}
| Revoke all tokens for a user.
Users can revoke their own; admins can revoke any user's tokens (requires ROLE_ADMIN for other users).
Returns 204 on success.
|===

== Karaf Shell Commands

[cols="1,3"]
|===
| Command | Description

| `opennms:api-token-generate [-d description] [-e days] <username>`
| Generate a new token. Prints the plaintext token once.

| `opennms:api-token-list <username>`
| List all tokens for a user.

| `opennms:api-token-revoke <token-id>`
| Revoke a specific token by ID.

| `opennms:api-token-revoke-all <username>`
| Revoke all tokens for a user.
|===

NOTE: In the Karaf shell, options (`-d`, `-e`) must come before the positional argument.

== Configuration

See xref:reference:configuration/system-properties.adoc[System Properties] for configurable limits:

* `org.opennms.api.tokens.max-expiry-days` (default: 365)
* `org.opennms.api.tokens.default-expiry-days` (default: 365)
* `org.opennms.api.tokens.max-tokens-per-user` (default: 50)

These can be set in `$\{OPENNMS_HOME}/etc/opennms.properties.d/api-tokens.properties`.
8 changes: 8 additions & 0 deletions docs/modules/development/pages/rest/rest-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ For instance, `http://localhost:8980/opennms/rest/alarms/` will give you the cur
Use HTTP basic authentication to provide a valid username and password.
By default, you will not receive a challenge, so you must configure your REST client library to send basic authentication proactively.

Alternatively, you can use xref:rest/api-tokens.adoc[API tokens] for programmatic access.
API tokens are long-lived bearer tokens that avoid exposing your password:

[source,bash]
----
curl -H "Authorization: Bearer onms_<token>" http://localhost:8980/opennms/api/v2/info
----

== Data format

Jersey enables {page-component-title} to create REST calls using either XML or JSON.
Expand Down
12 changes: 12 additions & 0 deletions docs/modules/reference/pages/configuration/system-properties.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,18 @@ Many of the properties here are documented with much greater detail in their rel
| false
| Enable or disable exporting performance data to an external system over a TCP port

| `org.opennms.api.tokens.default-expiry-days`
| 365
| Default expiry period in days for newly created API tokens when no expiry is specified

| `org.opennms.api.tokens.max-expiry-days`
| 365
| Maximum allowed expiry period in days for API tokens. Set to `0` to disable token creation entirely.

| `org.opennms.api.tokens.max-tokens-per-user`
| 50
| Maximum number of active API tokens each user is allowed to have

| `org.opennms.security.disableLoginSuccessEvent`
| false
| Enable or disable sending successful login events on a successful login to the webui
Expand Down
51 changes: 51 additions & 0 deletions features/api-tokens/api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0"?>
<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/maven-v4_0_0.xsd">
<parent>
<groupId>org.opennms.features</groupId>
<artifactId>org.opennms.features.api-tokens</artifactId>
<version>35.0.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>org.opennms.features.api-tokens.api</artifactId>
<packaging>bundle</packaging>
<name>OpenNMS :: Features :: API Tokens :: API</name>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-RequiredExecutionEnvironment>JavaSE-17</Bundle-RequiredExecutionEnvironment>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Bundle-Version>${project.version}</Bundle-Version>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.opennms</groupId>
<artifactId>opennms-dao-api</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.opennms.dependencies</groupId>
<artifactId>jaxb-dependencies</artifactId>
<type>pom</type>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Loading
Loading