惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
博客园 - 三生石上(FineUI控件)
S
Securelist
U
Unit 42
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Simon Willison's Weblog
Simon Willison's Weblog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
B
Blog
T
Tenable Blog
The Hacker News
The Hacker News
The Register - Security
The Register - Security
IT之家
IT之家
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
P
Privacy & Cybersecurity Law Blog
博客园_首页
T
Tailwind CSS Blog
人人都是产品经理
人人都是产品经理
C
Cybersecurity and Infrastructure Security Agency CISA
Know Your Adversary
Know Your Adversary
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
T
Tor Project blog
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
A
Arctic Wolf
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
V2EX
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
V
Visual Studio Blog
月光博客
月光博客
爱范儿
爱范儿
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
G
GRAHAM CLULEY
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Building a Morpheus Plugin A Practical Walkthrough
Emre Baykal · 2026-04-30 · via DEV Community

Morpheus is a hybrid cloud management platform with a plugin SDK that lets you extend almost everything cloud integrations, custom reports, IPAM and DNS providers, backup integrations, UI tabs, global UI hooks. The SDK is documented in pieces, but the day-to-day workflow of going from "I have an idea" to "shadow JAR is running on my appliance" isn't laid out anywhere. This post walks through that workflow.

I'll use a plugin we recently built as the running example: a small enforcement utility that forces every Morpheus user to enable 2FA. If MFA isn't your problem, the example is incidental. What matters is the path.

What you can extend

The SDK exposes more than fifty provider types. A provider is your hook into Morpheus; Morpheus calls back into your code at well defined moments, and you pick the type that matches what you want to do.

A non exhaustive sample, mostly to give a feel for the catalog:

  • CloudProvider, ProvisionProvider — add a new cloud integration
  • BackupProvider — register a backup integration
  • ReportProvider — add a custom report type
  • AppTabProvider, ServerTabProvider, ClusterTabProvider, InstanceTabProvider, NetworkTabProvider — add a tab to a detail page
  • GlobalUIComponentProvider — inject HTML into every authenticated page render
  • TaskProvider — register a task type for the automation engine
  • CypherProvider — extend the secure-vault subsystem
  • GuidanceRecommendationProvider, AnalyticsProvider — feed dashboard widgets
  • GenericProvider — generic integration for things that don't fit elsewhere

The full list lives at morpheus-plugin-core/morpheus-plugin-api/src/main/java/com/morpheusdata/core/providers/. Picking the right one is the most important early decision; everything else flows from it.

Set up your reference material first

Before writing any code, clone these three repositories locally:

git clone --depth 1 --branch v1.2.x \
    https://github.com/HewlettPackard/morpheus-plugin-core.git
git clone --depth 1 \
    https://github.com/HewlettPackard/morpheus-docs.git
git clone --depth 1 \
    https://github.com/HewlettPackard/morpheus-openapi.git

Enter fullscreen mode Exit fullscreen mode

morpheus-plugin-core is the source of the com.morpheusdata:morpheus-plugin-api library you'll compile against. Provider interfaces, abstract classes, model classes everything is here. When you want to know what a method returns or which fields a model exposes, grep this.

morpheus-docs is the source of the official Morpheus product documentation, currently published as HPE Morpheus VM Essentials on the HPE support portal. When you need to understand a feature from the user's perspective what does Morpheus call this concept, how does the UI organize it this is the reference.

morpheus-openapi is the OpenAPI 3.1 spec for Morpheus's REST API: 600+ endpoint files, 650+ schemas. If your plugin needs to call Morpheus over HTTP, or you need the canonical shape of a data model, this is faster than the live docs site.

Grep is fast; docs search is slow. A local clone pays off within the first hour.

A few web resources are worth bookmarking alongside the local clones:

Use them when the local clones don't have what you need; the local clones answer most questions faster.

Pick the right provider type

The methodology is straightforward:

  1. Write down what you want Morpheus to do.
  2. Translate that into a question: when does Morpheus need to call my code?
  3. Find the provider whose method signatures answer that question.

For the MFA enforcer, the chain went:

"Force users without 2FA to enable it before they can use Morpheus."

"We need to be invoked on every page render, with the current user, so we can decide whether to render a blocking overlay."

Match: GlobalUIComponentProvider, which defines Boolean show(User, Account) and HTMLResponse renderTemplate(User, Account). Morpheus invokes both on every authenticated page render.

If we'd wanted a custom report, we'd have picked ReportProvider. If we'd wanted a tab on the instance detail page, InstanceTabProvider. The question-to-provider mapping becomes mechanical once you know the catalog.

One thing worth knowing up front: not every hook you might want exists. There is no LoginProvider and no AuthenticationProvider — you can't intercept the login flow itself. For the MFA enforcer that meant settling for GlobalUIComponentProvider, which runs after login. The SDK won't always give you exactly what you want; pick the closest match and design around its constraints.

Build environment

The plugin API targets Java 8, but Gradle 7.x needs JDK 11 to build. The shadow plugin used by older Morpheus samples (com.bmuschko.gradle-plugin-shadow 2.0.3) is Gradle 6.x only — use the maintained com.github.johnrengelman.shadow 7.1.2 instead.

On Linux:

curl -fsSL "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"

sdk install java 11.0.22-tem
sdk install gradle 7.6.4

cd my-plugin
gradle wrapper --gradle-version 7.6.4 --distribution-type bin
./gradlew shadowJar

Enter fullscreen mode Exit fullscreen mode

A minimal build.gradle:

plugins {
    id 'java'
    id 'groovy'
    id 'com.github.johnrengelman.shadow' version '7.1.2'
}

group   = 'com.morpheusdata'
version = '1.0.0'

sourceCompatibility = '11'
targetCompatibility = '11'

repositories { mavenCentral() }

dependencies {
    compileOnly 'com.morpheusdata:morpheus-plugin-api:0.15.4'
    compileOnly 'org.codehaus.groovy:groovy-all:3.0.11'
    compileOnly 'io.reactivex.rxjava3:rxjava:3.1.5'
    compileOnly 'org.slf4j:slf4j-api:1.7.26'
}

shadowJar {
    manifest {
        attributes(
            'Plugin-Class'  : 'com.example.myplugin.MyPlugin',
            'Plugin-Version': archiveVersion.get()
        )
    }
}

Enter fullscreen mode Exit fullscreen mode

compileOnly is not optional. Morpheus's runtime already provides these libraries; bundling them into your shadow JAR causes classpath conflicts that surface as NoSuchMethodError at load time.

Bumping the version field on every revision is worth doing — the version becomes part of the JAR filename, so you can confirm which build is loaded by glancing at the plugin list in the Morpheus UI.

Anatomy of a plugin

Three pieces.

The Plugin class is your entry point, registered in the JAR manifest as Plugin-Class. It registers your providers in initialize():

class MfaEnforcerPlugin extends Plugin {
    @Override String getCode() { 'morpheus-mfa-enforcer-plugin' }

    @Override void initialize() {
        setName("MFA Enforcer")
        def provider = new MfaEnforcerProvider(this, morpheus)
        pluginProviders.put(provider.code, provider)
    }
}

Enter fullscreen mode Exit fullscreen mode

The Provider class is where the work lives. Always extend the matching Abstract*Provider instead of implementing the interface directly. The abstract classes set up the renderer with the correct classpath prefix, register handlebars helpers (asset, nonce, i18n), and wire up things you don't want to wire up yourself.

@Slf4j
class MfaEnforcerProvider extends AbstractGlobalUIComponentProvider {
    Plugin plugin
    MorpheusContext morpheusContext

    MfaEnforcerProvider(Plugin plugin, MorpheusContext ctx) {
        this.plugin = plugin
        this.morpheusContext = ctx
    }

    @Override Boolean show(User user, Account account) {
        if (user == null || account == null) return false
        if (isMasterTenantAdmin(user, account)) return false
        return !isUsing2FA(user)
    }

    @Override HTMLResponse renderTemplate(User user, Account account) {
        def model = new ViewModel<>()
        model.object = [userSettingsUrl: '/user-settings']
        return getRenderer().renderTemplate("hbs/mfaEnforcer", model)
    }

    @Override MorpheusContext getMorpheus() { morpheusContext }
    @Override Plugin getPlugin() { plugin }
}

Enter fullscreen mode Exit fullscreen mode

The getMorpheus() and getPlugin() overrides are required by PluginProvider and need to be explicit because Groovy's auto-generated getters use the field name (getMorpheusContext, not getMorpheus).

Resources live under src/main/resources/. Handlebars templates go in renderer/hbs/. The renderer's classpath prefix is renderer, so getRenderer().renderTemplate("hbs/mfaEnforcer", model) resolves to src/main/resources/renderer/hbs/mfaEnforcer.hbs.

For inline <style> and <script> tags, use the {{nonce}} helper:

<div id="mfa-shield" role="dialog" aria-modal="true">
    <style nonce="{{nonce}}">
        #mfa-shield {
            position: fixed; inset: 0; z-index: 2147483647;
            background: rgba(15, 23, 42, 0.75);
            display: flex; align-items: center; justify-content: center;
        }
        /* ... */
    </style>
    <div class="card">
        <h1>Two-Factor Authentication Required</h1>
        <a class="cta" href="{{userSettingsUrl}}" target="_top">
            Go to User Settings → Enable 2FA
        </a>
    </div>
</div>

Enter fullscreen mode Exit fullscreen mode

Without the nonce, Morpheus's Content-Security-Policy header blocks the styles and the script silently. There's no console error, no log entry, no visible failure mode — the page just renders without your component. Easy hours to lose.

The example, end-to-end

A quick note on why this plugin exists, for the curious. Morpheus supports two-factor authentication out of the box, but only as an opt-in toggle on each user's profile there is no appliance wide setting to require it. The product documentation says so explicitly: "There is not currently a way for an administrator to enforce the use of two-factor authentication appliance wide." As an MSP we couldn't rely on every sub-tenant user enabling 2FA on their own, so we wrote a small plugin to close the gap.

MFA enforcer's full source is at emrbaykal/morpheus-plugin-test/morpheus-mfa-enforcer. It's roughly 200 lines split across four files:

morpheus-mfa-enforcer/
├── build.gradle                                          # project config
└── src/main/
    ├── groovy/com/morpheusdata/mfaenforcer/
    │   ├── MfaEnforcerPlugin.groovy                      # entry point, registers provider
    │   └── MfaEnforcerProvider.groovy                    # show() + renderTemplate() + state lookup
    └── resources/renderer/hbs/
        └── mfaEnforcer.hbs                               # overlay HTML + CSS + small JS

Enter fullscreen mode Exit fullscreen mode

The decision logic in show() reads the user's 2FA flag from the Morpheus database through MorpheusReportService.getReadOnlyDatabaseConnection(). The result is cached in a static ConcurrentHashMap with a short TTL — show() is hot, called on every authenticated page render, and uncached SELECTs would generate noticeable load and contention against Morpheus's own writes.

The visible result, in five steps:

A user logs in:

Login screen

The dashboard loads, but the overlay covers it immediately:

Dashboard with MFA overlay

The CTA hard-navigates to /user-settings, where the standard Morpheus 2FA enable flow runs (password confirmation, QR code, authenticator app):

Enable 2FA — confirm password

After enabling, the user-settings page reflects the new state:

User settings showing Disable 2FA / Get 2FA Code

On the next login, Morpheus prompts for the 2FA code from the authenticator:

2FA code prompt at login

And the dashboard loads cleanly the overlay is gone, because show() now reads is_using2fa = true:

Clean dashboard, no overlay

Build, package, deploy

./gradlew shadowJar
# build/libs/morpheus-mfa-enforcer-1.0.10-all.jar

Enter fullscreen mode Exit fullscreen mode

Then in the Morpheus UI: Administration → Integrations → Plugins → Choose File. Upload the shadow JAR. The plugin loads on every appliance node, no daemon restart required.

The plugin list in that section shows the version from the JAR filename. Combined with version-bump-on-every-revision, that's all the deployment tracking you need for a small plugin.

A few practical notes

A handful of things that aren't obvious from reading the SDK:

Always extend the matching Abstract*Provider. The abstract classes do non-trivial work — registering handlebars helpers, configuring the renderer's classpath prefix, wiring up locale support. Implementing the bare interface is reinventing this work, and the omissions tend to manifest as silent failures (template not found, CSS blocked by CSP) that are time-consuming to diagnose.

Be careful with hot-path DB access. Any provider invoked per page render needs caching. Without it you add latency to every authenticated request and, in the worst case, contend with Morpheus's own writes via InnoDB share locks. A small ConcurrentHashMap with a short TTL plus an explicit sql.commit() after each SELECT keeps the plugin a good citizen.

/api/* endpoints require an OAuth2 bearer token. Browser session cookies aren't accepted. If your plugin tries to fetch them from inline JavaScript in a template, you'll get 401. For state lookups, do them server-side via the database or via plugin-side service calls.

When something doesn't work, check the Morpheus daemon log first. Browser console second. Plugin failures often produce no console output at all — the symptoms are silent CSP blocks, silent SPA click intercepts, silent fail-open paths. The daemon log is more honest.

Closing

The plugin SDK is more capable than the documentation makes obvious. Once you have the three reference repositories cloned, the build environment set up, and the right Abstract*Provider class identified, the actual work moves quickly.

The same pattern applies to password complexity rules, IP-based access restrictions, role-aware session timeouts, and other guardrails the platform doesn't ship with: pick the provider that matches your hook, extend the abstract class, ship the shadow JAR.

If you've built something interesting with the SDK, drop a link in the comments.