New to Kestra?
Use blueprints to kickstart your first workflows.
Find anonymous MySQL accounts, empty passwords, remote root and global admin privileges, alert Slack, drop, lock or revoke and re-audit.
MySQL servers that have been running for a few years have accounts nobody remembers:
''@'localhost' left from an old install;root@'%' added to debug from a laptop;ALL PRIVILEGES ON *.* WITH GRANT OPTION, which can read every file the server can and create new admins.This blueprint reads mysql.user every day, flags those accounts and alerts Slack. It can generate and run the fixes one by one, then audit again to verify. The account the flow itself connects with is reported, but never changed, so a fix cannot lock the flow out halfway through.
This blueprint was created by zkasuran.
| Finding | Severity | Rule | Fix |
|---|---|---|---|
ANONYMOUS_ACCOUNT |
High | Account with an empty user name | DROP USER |
EMPTY_PASSWORD |
High | Unlocked account with no password | ALTER USER ... ACCOUNT LOCK |
REMOTE_ROOT |
High | root allowed from a host other than localhost |
ALTER USER ... ACCOUNT LOCK |
GLOBAL_ADMIN_PRIVILEGES |
High | Global SUPER, FILE, SHUTDOWN, CREATE USER or GRANT OPTION outside privileged_accounts |
REVOKE IF EXISTS of exactly those privileges, the others are kept |
WILDCARD_HOST |
Medium | Non-root account allowed from % |
Report only |
Account names are quoted with QUOTE() inside MySQL. A finding on CURRENT_USER() is marked as the connected account and skipped.
audit (io.kestra.plugin.jdbc.mysql.Query, read-only) runs one query on mysql.user that returns every finding with its fix statement. MySQL's own system accounts (mysql.sys, mysql.session, mysql.infoschema) are excluded.summary logs every finding.route (io.kestra.plugin.core.flow.If):alert posts to Slack.remediate_gate runs only when auto_remediate is true and dry_run is false.remediate (io.kestra.plugin.core.flow.Loop) runs each fix in its own Query, so one failure does not stop the others.reaudit runs the same audit again, and verify_result fails if a fixed finding is still there.errors alerts Slack when the audit cannot run.The query is easy to run once. Kestra runs it every day on every server, sends findings to Slack, gates the fixes behind auto_remediate and dry_run, isolates each statement, verifies the result, and keeps an execution history as evidence for audits.
On Kestra 2.0.5 OSS against MySQL 8.4, with a local HTTP sink standing in for Slack. The flow connected as root@'%'. Seeded accounts:
''@'localhost', and reporting@'%' with an empty password;legacy_etl@'%' with ALL ON *.* WITH GRANT OPTION, and dba_tom@'%' with SUPER, FILE;app@'10.0.%', and a backup@localhost with RELOAD, PROCESS.| Run | Inputs | Result |
|---|---|---|
| 1 | defaults | 8 findings. root@% reported as the connected account. app and backup not flagged. Alert sent |
| 2 | auto_remediate: true, dry_run: true |
Same report. The anonymous account is still in mysql.user |
| 3 | auto_remediate: true, dry_run: false |
4 statements run and verified: anonymous dropped, reporting locked, admin privileges revoked from dba_tom and legacy_etl. Then in MySQL: reporting gets Account is locked, legacy_etl still reads but SHUTDOWN is denied. root@% untouched |
| 4 | same again | Only root@% and the wildcard hosts left, nothing run |
| 5 | unreachable server | errors alert sent |
REVOKE IF EXISTS), reachable from the Kestra worker.SELECT on mysql.user. For remediation, CREATE USER and the privileges to revoke, with GRANT OPTION.docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=pw mysql:8.4, then create a few accounts.MYSQL_USERNAME / MYSQL_PASSWORD: credentials used by every query.SLACK_WEBHOOK_URL: used by alert and the errors block.SECRET_.| Input | Default | Purpose |
|---|---|---|
jdbc_url |
jdbc:mysql://localhost:3306/mysql |
Server |
privileged_accounts |
[root@localhost] |
Accounts allowed admin privileges |
auto_remediate |
false |
Allow fixes |
dry_run |
true |
Must be false for a statement to run |
root. Add MYSQL_USERNAME, MYSQL_PASSWORD and SLACK_WEBHOOK_URL as secrets.jdbc_url and privileged_accounts.auto_remediate: true and dry_run: false.daily trigger, which always runs in safe mode.SYSTEM_VARIABLES_ADMIN, BINLOG_ADMIN...) from mysql.global_grants to the admin rule.password_last_changed).Pause before locking accounts on production.