Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7e452e0
add logic for per user authentication
christos-diamantis Aug 3, 2025
379b71a
extend the models with new fields
christos-diamantis Aug 3, 2025
2cbc926
extend the zabbixSettings with new fields
christos-diamantis Aug 3, 2025
89f53b4
add integration tests for newly created feature
christos-diamantis Aug 3, 2025
d960e0d
helper functions for getting a user and generating a token for the user
christos-diamantis Aug 3, 2025
134f5da
add new fields on datasource configuration
christos-diamantis Aug 3, 2025
22356ff
complete the devenv and integration tests
christos-diamantis Aug 3, 2025
20b2ab4
add new major feature
christos-diamantis Aug 3, 2025
1b00278
fix an error
christos-diamantis Aug 3, 2025
e01eb53
add the correct types on the ds definition
christos-diamantis Aug 3, 2025
617331c
update changeset
christos-diamantis Aug 3, 2025
828b931
added the ability to exclude specific users from the per-user auth
christos-diamantis Aug 4, 2025
50d18ec
fix integration tests
christos-diamantis Aug 4, 2025
1041d0f
add new method for request with params being an array instead of object
christos-diamantis Aug 4, 2025
10249d3
golang best practices: error should not be capitalized
christos-diamantis Aug 4, 2025
a09a63e
change the awaiting error string based on change
christos-diamantis Aug 4, 2025
f5dda5d
new file with helper funtions to cache the token
christos-diamantis Oct 29, 2025
fa972b9
authentication flow to apply per user auth
christos-diamantis Oct 29, 2025
989edcb
per user auth offloaded
christos-diamantis Oct 29, 2025
70a03bb
apply per user auth to ZabbixAPIHandler and DBConnectionPostProcessin…
christos-diamantis Oct 29, 2025
a904e74
when per user auth enabled, default the field to username
christos-diamantis Oct 29, 2025
a4439eb
corrections on token generation
christos-diamantis Oct 29, 2025
c45de7c
change the error to reflect the new backend method
christos-diamantis Oct 30, 2025
24476d6
Merge branch 'main' into add-authentication-passthrough
christos-diamantis Oct 30, 2025
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
5 changes: 5 additions & 0 deletions .changeset/curvy-lizards-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'grafana-zabbix': major
---

Add the ability to have per-user authentication
1 change: 1 addition & 0 deletions .github/workflows/compatibility-60.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
ZABBIX_URL: 'https://localhost/api_jsonrpc.php'
ZABBIX_USER: 'Admin'
ZABBIX_PASSWORD: 'zabbix'
ZABBIX_TARGET_USER: 'grafana_test'
run: go test -v ./pkg/zabbixapi/...

- name: Cleanup
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/compatibility-70.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
ZABBIX_URL: 'https://localhost/api_jsonrpc.php'
ZABBIX_USER: 'Admin'
ZABBIX_PASSWORD: 'zabbix'
ZABBIX_TARGET_USER: 'grafana_test'
run: go test -v ./pkg/zabbixapi/...

- name: Cleanup
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/compatibility-72.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
ZABBIX_URL: 'http://localhost:8188/api_jsonrpc.php'
ZABBIX_USER: 'Admin'
ZABBIX_PASSWORD: 'zabbix'
ZABBIX_TARGET_USER: 'grafana_test'
run: go test -v ./pkg/zabbixapi/...

- name: Cleanup
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/compatibility-74.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
ZABBIX_URL: 'http://localhost:8188/api_jsonrpc.php'
ZABBIX_USER: 'Admin'
ZABBIX_PASSWORD: 'zabbix'
ZABBIX_TARGET_USER: 'grafana_test'
run: go test -v ./pkg/zabbixapi/...

- name: Cleanup
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Transform and shape your data with [metric processing functions](https://grafana.com/docs/plugins/alexanderzobnin-zabbix-app/latest/reference/functions/) (Avg, Median, Min, Max, Multiply, Summarize, Time shift, Alias)
- Find problems faster with [Alerting](https://grafana.com/docs/plugins/alexanderzobnin-zabbix-app/latest/reference/alerting/) feature
- Mix metrics from multiple data sources in the same dashboard or even graph
- Per user authentication using Bearer tokens, so that Grafana respects the RBAC on Zabbix
- Discover and share [dashboards](https://grafana.com/dashboards) in the official library

See all features overview and dashboards examples at Grafana-Zabbix [Live demo](http://play.grafana-zabbix.org) site.
Expand Down
2 changes: 2 additions & 0 deletions devenv/zabbix60/bootstrap/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ ENV ZBX_API_USER="Admin"
ENV ZBX_API_PASSWORD="zabbix"
ENV ZBX_CONFIG="zbx_export_hosts.xml"
ENV ZBX_BOOTSTRAP_SCRIPT="bootstrap_config.py"
ENV ZBX_TEST_USER="grafana_test"
ENV ZBX_TEST_PASS="grafana_test_pass"

RUN pip install pyzabbix

Expand Down
26 changes: 25 additions & 1 deletion devenv/zabbix60/bootstrap/bootstrap_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
zabbix_url = os.environ['ZBX_API_URL']
zabbix_user = os.environ['ZBX_API_USER']
zabbix_password = os.environ['ZBX_API_PASSWORD']
print(zabbix_url, zabbix_user, zabbix_password)
zabbix_test_user = os.environ['ZBX_TEST_USER']
zabbix_test_pass = os.environ['ZBX_TEST_PASS']
print(zabbix_url, zabbix_user, zabbix_password, zabbix_test_user, zabbix_test_pass)

zapi = ZabbixAPI(zabbix_url, timeout=5)

Expand Down Expand Up @@ -76,5 +78,27 @@
except ZabbixAPIException as e:
print e

print("Creating a user for testing per-user auth")
groups = zapi.usergoup.get(output="extend", filter={"name": "Zabbix administrators"})
if not groups:
groups = zapi.usergroup.get(output="extend")
groupid = groups[0]['usrgrpid']

user_create_params = {
"alias": zabbix_test_user,
"passwd": zabbix_test_pass,
"name": "Grafana",
"surname": "Test",
"type": 1, # Zabbix user type
"user_groups": [{"usrgrpid": groupid}],
}

try:
user = zapi.user.create(**user_create_params)
print("User created successfully: %s" % user['userids'][0])
except Exception as e:
print("Failed to create user: %s" % e)


for h in zapi.host.get(output="extend"):
print(h['name'])
2 changes: 2 additions & 0 deletions devenv/zabbix60/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ services:
ZBX_API_URL: http://zabbix-web:8080
ZBX_API_USER: Admin
ZBX_API_PASSWORD: zabbix
ZBX_TEST_USER: grafana_test
ZBX_TEST_PASS: grafana_test_pass
depends_on:
- database
- zabbix-server
Expand Down
2 changes: 2 additions & 0 deletions devenv/zabbix70/bootstrap/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ ENV ZBX_API_USER="Admin"
ENV ZBX_API_PASSWORD="zabbix"
ENV ZBX_CONFIG="zbx_export_hosts.json"
ENV ZBX_BOOTSTRAP_SCRIPT="bootstrap_config.py"
ENV ZBX_TEST_USER="grafana_test"
ENV ZBX_TEST_PASS="grafana_test_pass"

RUN pip install zabbix_utils

Expand Down
25 changes: 24 additions & 1 deletion devenv/zabbix70/bootstrap/bootstrap_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
zabbix_url = os.environ['ZBX_API_URL']
zabbix_user = os.environ['ZBX_API_USER']
zabbix_password = os.environ['ZBX_API_PASSWORD']
print(zabbix_url, zabbix_user, zabbix_password)
zabbix_test_user = os.environ['ZBX_TEST_USER']
zabbix_test_pass = os.environ['ZBX_TEST_PASS']
print(zabbix_url, zabbix_user, zabbix_password, zabbix_test_user, zabbix_test_pass)

zapi = ZabbixAPI(zabbix_url)

Expand Down Expand Up @@ -71,5 +73,26 @@
except Exception as e:
print(e)

print("Creating a user for testing per-user auth")
groups = zapi.usergoup.get(output="extend", filter={"name": "Zabbix administrators"})
if not groups:
groups = zapi.usergroup.get(output="extend")
groupid = groups[0]['usrgrpid']

user_create_params = {
"alias": zabbix_test_user,
"passwd": zabbix_test_pass,
"name": "Grafana",
"surname": "Test",
"type": 1, # Zabbix user type
"user_groups": [{"usrgrpid": groupid}],
}

try:
user = zapi.user.create(**user_create_params)
print("User created successfully: %s" % user['userids'][0])
except Exception as e:
print("Failed to create user: %s" % e)

for h in zapi.host.get(output="extend"):
print(h['name'])
2 changes: 2 additions & 0 deletions devenv/zabbix70/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ services:
ZBX_API_URL: http://zabbix-web:8080
ZBX_API_USER: Admin
ZBX_API_PASSWORD: zabbix
ZBX_TEST_USER: grafana_test
ZBX_TEST_PASS: grafana_test_pass
depends_on:
- database
- zabbix-server
Expand Down
2 changes: 2 additions & 0 deletions devenv/zabbix72/bootstrap/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ ENV ZBX_API_USER="Admin"
ENV ZBX_API_PASSWORD="zabbix"
ENV ZBX_CONFIG="zbx_export_hosts.json"
ENV ZBX_BOOTSTRAP_SCRIPT="bootstrap_config.py"
ENV ZBX_TEST_USER="grafana_test"
ENV ZBX_TEST_PASS="grafana_test_pass"

RUN pip install zabbix_utils

Expand Down
25 changes: 24 additions & 1 deletion devenv/zabbix72/bootstrap/bootstrap_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
zabbix_url = os.environ['ZBX_API_URL']
zabbix_user = os.environ['ZBX_API_USER']
zabbix_password = os.environ['ZBX_API_PASSWORD']
print(zabbix_url, zabbix_user, zabbix_password)
zabbix_test_user = os.environ['ZBX_TEST_USER']
zabbix_test_pass = os.environ['ZBX_TEST_PASS']
print(zabbix_url, zabbix_user, zabbix_password, zabbix_test_user, zabbix_test_pass)

zapi = ZabbixAPI(zabbix_url)

Expand Down Expand Up @@ -71,5 +73,26 @@
except Exception as e:
print(e)

print("Creating a user for testing per-user auth")
groups = zapi.usergoup.get(output="extend", filter={"name": "Zabbix administrators"})
if not groups:
groups = zapi.usergroup.get(output="extend")
groupid = groups[0]['usrgrpid']

user_create_params = {
"alias": zabbix_test_user,
"passwd": zabbix_test_pass,
"name": "Grafana",
"surname": "Test",
"type": 1, # Zabbix user type
"user_groups": [{"usrgrpid": groupid}],
}

try:
user = zapi.user.create(**user_create_params)
print("User created successfully: %s" % user['userids'][0])
except Exception as e:
print("Failed to create user: %s" % e)

for h in zapi.host.get(output="extend"):
print(h['name'])
2 changes: 2 additions & 0 deletions devenv/zabbix72/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ services:
ZBX_API_URL: http://zabbix-web:8080
ZBX_API_USER: Admin
ZBX_API_PASSWORD: zabbix
ZBX_TEST_USER: grafana_test
ZBX_TEST_PASS: grafana_test_pass
depends_on:
- database
- zabbix-server
Expand Down
2 changes: 2 additions & 0 deletions devenv/zabbix74/bootstrap/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ ENV ZBX_API_USER="Admin"
ENV ZBX_API_PASSWORD="zabbix"
ENV ZBX_CONFIG="zbx_export_hosts.json"
ENV ZBX_BOOTSTRAP_SCRIPT="bootstrap_config.py"
ENV ZBX_TEST_USER="grafana_test"
ENV ZBX_TEST_PASS="grafana_test_pass"

RUN pip install zabbix_utils

Expand Down
25 changes: 24 additions & 1 deletion devenv/zabbix74/bootstrap/bootstrap_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
zabbix_url = os.environ['ZBX_API_URL']
zabbix_user = os.environ['ZBX_API_USER']
zabbix_password = os.environ['ZBX_API_PASSWORD']
print(zabbix_url, zabbix_user, zabbix_password)
zabbix_test_user = os.environ['ZBX_TEST_USER']
zabbix_test_pass = os.environ['ZBX_TEST_PASS']
print(zabbix_url, zabbix_user, zabbix_password, zabbix_test_user, zabbix_test_pass)

zapi = ZabbixAPI(zabbix_url)

Expand Down Expand Up @@ -71,5 +73,26 @@
except Exception as e:
print(e)

print("Creating a user for testing per-user auth")
groups = zapi.usergoup.get(output="extend", filter={"name": "Zabbix administrators"})
if not groups:
groups = zapi.usergroup.get(output="extend")
groupid = groups[0]['usrgrpid']

user_create_params = {
"alias": zabbix_test_user,
"passwd": zabbix_test_pass,
"name": "Grafana",
"surname": "Test",
"type": 1, # Zabbix user type
"user_groups": [{"usrgrpid": groupid}],
}

try:
user = zapi.user.create(**user_create_params)
print("User created successfully: %s" % user['userids'][0])
except Exception as e:
print("Failed to create user: %s" % e)

for h in zapi.host.get(output="extend"):
print(h['name'])
2 changes: 2 additions & 0 deletions devenv/zabbix74/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ services:
ZBX_API_URL: http://zabbix-web:8080
ZBX_API_USER: Admin
ZBX_API_PASSWORD: zabbix
ZBX_TEST_USER: grafana_test
ZBX_TEST_PASS: grafana_test_pass
depends_on:
- database
- zabbix-server
Expand Down
57 changes: 57 additions & 0 deletions pkg/cache/token_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cache

import (
"sync"
"time"
)

type TokenInfo struct {
Token string
ExpiresAt time.Time
UserID string
Username string
}

type TokenCache struct {
tokens sync.Map // key: "datasourceUID:identity:userID"
}

func NewTokenCache() *TokenCache {
return &TokenCache{}
}

func (tc *TokenCache) Get(datasourceUID, identity, userID string) (*TokenInfo, bool) {
key := datasourceUID + ":" + identity + ":" + userID
if val, ok := tc.tokens.Load(key); ok {
tokenInfo := val.(*TokenInfo)
if time.Now().Before(tokenInfo.ExpiresAt) {
return tokenInfo, true
}
tc.tokens.Delete(key)
}
return nil, false
}

func (tc *TokenCache) Set(datasourceUID, identity, userID, token string, ttl time.Duration) {
key := datasourceUID + ":" + identity + ":" + userID
tokenInfo := &TokenInfo{
Token: token,
ExpiresAt: time.Now().Add(ttl),
UserID: userID,
Username: identity,
}
tc.tokens.Store(key, tokenInfo)
}

func (tc *TokenCache) CleanupExpired() int {
count := 0
tc.tokens.Range(func(key, value interface{}) bool {
tokenInfo := value.(*TokenInfo)
if time.Now().After(tokenInfo.ExpiresAt) {
tc.tokens.Delete(key)
count++
}
return true
})
return count
}
Loading