Skip to content

Commit e7c8d6b

Browse files
monshriTeryl Taylorcrivetimihai
authored
feat: add OPA plugin for policy enforcement (IBM#853)
* Initial OPA plugin template Signed-off-by: Shriti Priya <[email protected]> * Adding opa server installation, tool invoke with policy evaluations Signed-off-by: Shriti Priya <[email protected]> * Sample policy holders for pre/post tool, resource and prompt invocations, url changes and opa version (arm architecture 1.7.0) Signed-off-by: Shriti Priya <[email protected]> * feat: add shared context capabilities and fixed error issues. Signed-off-by: Teryl Taylor <[email protected]> * fix: plugin cleanup to support multiple external plugins. Signed-off-by: Teryl Taylor <[email protected]> * fix(lint): fixed linting issues Signed-off-by: Teryl Taylor <[email protected]> * feat(error): update error handling with enforce_ignore_error Signed-off-by: Teryl Taylor <[email protected]> * Additiona of context-tool-policy mapping using applied_to Signed-off-by: Shriti Priya <[email protected]> * Changes in plugin config schema Signed-off-by: Shriti Priya <[email protected]> * Schema update models.py Signed-off-by: Shriti Priya <[email protected]> * updated schema Signed-off-by: Shriti Priya <[email protected]> * Adding endpoint to policy Signed-off-by: Shriti Priya <[email protected]> * documentation for OPA Plugin Signed-off-by: Shriti Priya <[email protected]> * documentation update Signed-off-by: Shriti Priya <[email protected]> * documentation update Signed-off-by: Shriti Priya <[email protected]> * documentation update Signed-off-by: Shriti Priya <[email protected]> * documentation update Signed-off-by: Shriti Priya <[email protected]> * fix: flake8 and doctest Signed-off-by: Shriti Priya <[email protected]> * fix: solving doctest errors Signed-off-by: Shriti Priya <[email protected]> * fix:doctest Signed-off-by: Shriti Priya <[email protected]> * Adding tool_name variable change Signed-off-by: Shriti Priya <[email protected]> * test cases for opapluginfilter Signed-off-by: Shriti Priya <[email protected]> * Update manifest.in with exclude Signed-off-by: Shriti Priya <[email protected]> * updated prehook Signed-off-by: Shriti Priya <[email protected]> * updating documentation Signed-off-by: Shriti Priya <[email protected]> * rebase Signed-off-by: Mihai Criveti <[email protected]> --------- Signed-off-by: Shriti Priya <[email protected]> Signed-off-by: Teryl Taylor <[email protected]> Signed-off-by: Mihai Criveti <[email protected]> Co-authored-by: Teryl Taylor <[email protected]> Co-authored-by: Mihai Criveti <[email protected]>
1 parent 5098594 commit e7c8d6b

File tree

27 files changed

+1997
-4
lines changed

27 files changed

+1997
-4
lines changed

MANIFEST.in

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,13 @@ exclude llms-full.txt
9696
prune deployment
9797
prune mcp-servers
9898
prune agent_runtimes
99+
100+
# Exclude opa
101+
exclude plugins/external/opa/.dockerignore
102+
exclude plugins/external/opa/.env.template
103+
exclude plugins/external/opa/.ruff.toml
104+
exclude plugins/external/opa/Containerfile
105+
exclude plugins/external/opa/MANIFEST.in
106+
exclude plugins/external/opa/opaserver/rego/example.rego
107+
exclude plugins/external/opa/pyproject.toml
108+
exclude plugins/external/opa/run-server.sh

mcpgateway/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3151,6 +3151,8 @@ async def handle_rpc(request: Request, db: Session = Depends(get_db), user=Depen
31513151
result = await tool_service.invoke_tool(db=db, name=method, arguments=params, request_headers=headers)
31523152
if hasattr(result, "model_dump"):
31533153
result = result.model_dump(by_alias=True, exclude_none=True)
3154+
except PluginViolationError:
3155+
return JSONResponse(status_code=403, content={"detail": "policy_deny"})
31543156
except (ValueError, Exception):
31553157
# If not a tool, try forwarding to gateway
31563158
try:

mcpgateway/plugins/framework/models.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,29 @@ class PluginMode(str, Enum):
8484
DISABLED = "disabled"
8585

8686

87-
class ToolTemplate(BaseModel):
87+
class BaseTemplate(BaseModel):
88+
"""Base Template.The ToolTemplate, PromptTemplate and ResourceTemplate could be extended using this
89+
90+
Attributes:
91+
context (Optional[list[str]]): specifies the keys of context to be extracted. The context could be global (shared between the plugins) or
92+
local (shared within the plugin). Example: global.key1.
93+
extensions (Optional[dict[str, Any]]): add custom keys for your specific plugin. Example - 'policy'
94+
key for opa plugin.
95+
96+
Examples:
97+
>>> base = BaseTemplate(context=["global.key1.key2", "local.key1.key2"])
98+
>>> base.context
99+
['global.key1.key2', 'local.key1.key2']
100+
>>> base = BaseTemplate(context=["global.key1.key2"], extensions={"policy" : "sample policy"})
101+
>>> base.extensions
102+
{'policy': 'sample policy'}
103+
"""
104+
105+
context: Optional[list[str]] = None
106+
extensions: Optional[dict[str, Any]] = None
107+
108+
109+
class ToolTemplate(BaseTemplate):
88110
"""Tool Template.
89111
90112
Attributes:
@@ -110,7 +132,7 @@ class ToolTemplate(BaseModel):
110132
result: bool = False
111133

112134

113-
class PromptTemplate(BaseModel):
135+
class PromptTemplate(BaseTemplate):
114136
"""Prompt Template.
115137
116138
Attributes:
@@ -134,7 +156,7 @@ class PromptTemplate(BaseModel):
134156
result: bool = False
135157

136158

137-
class ResourceTemplate(BaseModel):
159+
class ResourceTemplate(BaseTemplate):
138160
"""Resource Template.
139161
140162
Attributes:
@@ -215,6 +237,8 @@ class AppliedTo(BaseModel):
215237
tools (Optional[list[ToolTemplate]]): tools and fields to be applied.
216238
prompts (Optional[list[PromptTemplate]]): prompts and fields to be applied.
217239
resources (Optional[list[ResourceTemplate]]): resources and fields to be applied.
240+
global_context (Optional[list[str]]): keys in the context to be applied on globally
241+
local_context(Optional[list[str]]): keys in the context to be applied on locally
218242
"""
219243

220244
tools: Optional[list[ToolTemplate]] = None
@@ -308,7 +332,7 @@ class PluginConfig(BaseModel):
308332
mode: PluginMode = PluginMode.ENFORCE
309333
priority: Optional[int] = None # Lower = higher priority
310334
conditions: Optional[list[PluginCondition]] = None # When to apply
311-
applied_to: Optional[list[AppliedTo]] = None # Fields to apply to.
335+
applied_to: Optional[AppliedTo] = None # Fields to apply to.
312336
config: Optional[dict[str, Any]] = None
313337
mcp: Optional[MCPConfig] = None
314338

plugins/config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ plugins:
110110
- pattern: "secret\\s*[:=]\\s*\\S+"
111111
replacement: "secret: [REDACTED]"
112112

113+
- name: "OPAPluginFilter"
114+
kind: "external"
115+
priority: 10 # adjust the priority
116+
mcp:
117+
proto: STREAMABLEHTTP
118+
url: http://127.0.0.1:8000/mcp
119+
113120
# Plugin directories to scan
114121
plugin_dirs:
115122
- "plugins/native" # Built-in plugins

plugins/external/config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ plugins:
77
proto: STREAMABLEHTTP
88
url: http://127.0.0.1:3000/mcp
99

10+
- name: "OPAPluginFilter"
11+
kind: "external"
12+
priority: 10 # adjust the priority
13+
mcp:
14+
proto: STREAMABLEHTTP
15+
url: http://127.0.0.1:8000/mcp
16+
1017
# Plugin directories to scan
1118
plugin_dirs:
1219
- "plugins/native" # Built-in plugins

0 commit comments

Comments
 (0)