mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-06 20:03:05 +08:00
🐛 fix(jvm): 收紧 JMX domain allowlist 校验
在 helper runtime 中对直接 ObjectName、资源浏览、变更预览和监控路径统一执行 domain allowlist,阻断默认域别名和空白后缀绕过。
This commit is contained in:
@@ -287,6 +287,10 @@ func TestJMXProviderRealJMXRoundTrip(t *testing.T) {
|
||||
}
|
||||
|
||||
provider := NewJMXProvider()
|
||||
monitoringProvider, ok := provider.(MonitoringCapableProvider)
|
||||
if !ok {
|
||||
t.Fatal("expected JMX provider to implement monitoring snapshots")
|
||||
}
|
||||
fixture := startJMXFixture(t)
|
||||
readOnly := false
|
||||
cfg := connection.ConnectionConfig{
|
||||
@@ -335,14 +339,310 @@ func TestJMXProviderRealJMXRoundTrip(t *testing.T) {
|
||||
}
|
||||
mbean := mbeans[0]
|
||||
|
||||
blockedDomainPath := buildJMXResourcePath(jmxResourceTarget{
|
||||
Kind: jmxResourceKindDomain,
|
||||
Domain: "java.lang",
|
||||
})
|
||||
_, err = provider.ListResources(context.Background(), cfg, blockedDomainPath)
|
||||
if err == nil {
|
||||
t.Fatal("expected list on blocked domain to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "java.lang") {
|
||||
t.Fatalf("expected blocked domain list context, got %v", err)
|
||||
}
|
||||
|
||||
_, err = provider.GetValue(context.Background(), cfg, blockedDomainPath)
|
||||
if err == nil {
|
||||
t.Fatal("expected get on blocked domain to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "java.lang") {
|
||||
t.Fatalf("expected blocked domain get context, got %v", err)
|
||||
}
|
||||
|
||||
blockedMBeanPath := buildJMXResourcePath(jmxResourceTarget{
|
||||
Kind: jmxResourceKindMBean,
|
||||
ObjectName: "java.lang:type=Memory",
|
||||
})
|
||||
_, err = provider.ListResources(context.Background(), cfg, blockedMBeanPath)
|
||||
if err == nil {
|
||||
t.Fatal("expected list on blocked domain mbean to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "java.lang") {
|
||||
t.Fatalf("expected blocked domain mbean list context, got %v", err)
|
||||
}
|
||||
|
||||
_, err = provider.GetValue(context.Background(), cfg, blockedMBeanPath)
|
||||
if err == nil {
|
||||
t.Fatal("expected direct mbean get on blocked domain to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "java.lang") {
|
||||
t.Fatalf("expected blocked domain mbean get context, got %v", err)
|
||||
}
|
||||
|
||||
blockedAttributePath := buildJMXResourcePath(jmxResourceTarget{
|
||||
Kind: jmxResourceKindAttribute,
|
||||
ObjectName: "java.lang:type=Memory",
|
||||
Attribute: "HeapMemoryUsage",
|
||||
})
|
||||
_, err = provider.GetValue(context.Background(), cfg, blockedAttributePath)
|
||||
if err == nil {
|
||||
t.Fatal("expected direct attribute get on blocked domain to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "java.lang") {
|
||||
t.Fatalf("expected blocked domain attribute get context, got %v", err)
|
||||
}
|
||||
|
||||
blockedOperationPath := buildJMXResourcePath(jmxResourceTarget{
|
||||
Kind: jmxResourceKindOperation,
|
||||
ObjectName: "java.lang:type=Memory",
|
||||
Operation: "gc",
|
||||
})
|
||||
_, err = provider.GetValue(context.Background(), cfg, blockedOperationPath)
|
||||
if err == nil {
|
||||
t.Fatal("expected direct operation get on blocked domain to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "java.lang") {
|
||||
t.Fatalf("expected blocked domain operation get context, got %v", err)
|
||||
}
|
||||
|
||||
_, err = provider.PreviewChange(context.Background(), cfg, ChangeRequest{
|
||||
ProviderMode: ModeJMX,
|
||||
ResourceID: blockedOperationPath,
|
||||
Action: "invoke",
|
||||
Reason: "尝试跨域操作预览",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected preview on blocked domain operation to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "java.lang") {
|
||||
t.Fatalf("expected blocked domain operation preview context, got %v", err)
|
||||
}
|
||||
|
||||
_, err = provider.ApplyChange(context.Background(), cfg, ChangeRequest{
|
||||
ProviderMode: ModeJMX,
|
||||
ResourceID: blockedOperationPath,
|
||||
Action: "invoke",
|
||||
Reason: "尝试跨域操作调用",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected apply on blocked domain operation to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "java.lang") {
|
||||
t.Fatalf("expected blocked domain operation apply context, got %v", err)
|
||||
}
|
||||
|
||||
_, err = provider.PreviewChange(context.Background(), cfg, ChangeRequest{
|
||||
ProviderMode: ModeJMX,
|
||||
ResourceID: blockedAttributePath,
|
||||
Action: "update",
|
||||
Reason: "尝试跨域属性预览",
|
||||
Payload: map[string]any{
|
||||
"value": "blocked",
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected preview on blocked domain attribute to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "java.lang") {
|
||||
t.Fatalf("expected blocked domain attribute preview context, got %v", err)
|
||||
}
|
||||
|
||||
_, err = provider.ApplyChange(context.Background(), cfg, ChangeRequest{
|
||||
ProviderMode: ModeJMX,
|
||||
ResourceID: blockedAttributePath,
|
||||
Action: "update",
|
||||
Reason: "尝试跨域属性修改",
|
||||
Payload: map[string]any{
|
||||
"value": "blocked",
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected apply on blocked domain attribute to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "java.lang") {
|
||||
t.Fatalf("expected blocked domain attribute apply context, got %v", err)
|
||||
}
|
||||
|
||||
defaultDomainMBeanPath := buildJMXResourcePath(jmxResourceTarget{
|
||||
Kind: jmxResourceKindMBean,
|
||||
ObjectName: ":type=CacheSettings,name=DefaultDomainCache",
|
||||
})
|
||||
_, err = provider.ListResources(context.Background(), cfg, defaultDomainMBeanPath)
|
||||
if err == nil {
|
||||
t.Fatal("expected list on default domain alias mbean to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain") {
|
||||
t.Fatalf("expected default domain alias mbean list context, got %v", err)
|
||||
}
|
||||
|
||||
defaultDomainAttributePath := buildJMXResourcePath(jmxResourceTarget{
|
||||
Kind: jmxResourceKindAttribute,
|
||||
ObjectName: ":type=CacheSettings,name=DefaultDomainCache",
|
||||
Attribute: "Mode",
|
||||
})
|
||||
_, err = provider.GetValue(context.Background(), cfg, defaultDomainAttributePath)
|
||||
if err == nil {
|
||||
t.Fatal("expected get on default domain alias attribute to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain") {
|
||||
t.Fatalf("expected default domain alias attribute get context, got %v", err)
|
||||
}
|
||||
|
||||
defaultDomainOperationPath := buildJMXResourcePath(jmxResourceTarget{
|
||||
Kind: jmxResourceKindOperation,
|
||||
ObjectName: ":type=CacheSettings,name=DefaultDomainCache",
|
||||
Operation: "resize",
|
||||
Signature: []string{"int", "boolean"},
|
||||
})
|
||||
_, err = provider.PreviewChange(context.Background(), cfg, ChangeRequest{
|
||||
ProviderMode: ModeJMX,
|
||||
ResourceID: defaultDomainOperationPath,
|
||||
Action: "invoke",
|
||||
Reason: "尝试默认域别名操作预览",
|
||||
Payload: map[string]any{
|
||||
"args": []any{3, true},
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected preview on default domain alias operation to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain") {
|
||||
t.Fatalf("expected default domain alias operation preview context, got %v", err)
|
||||
}
|
||||
|
||||
_, err = provider.ApplyChange(context.Background(), cfg, ChangeRequest{
|
||||
ProviderMode: ModeJMX,
|
||||
ResourceID: defaultDomainOperationPath,
|
||||
Action: "invoke",
|
||||
Reason: "尝试默认域别名操作调用",
|
||||
Payload: map[string]any{
|
||||
"args": []any{3, true},
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected apply on default domain alias operation to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain") {
|
||||
t.Fatalf("expected default domain alias operation apply context, got %v", err)
|
||||
}
|
||||
|
||||
whitespaceDomainMBeanPath := buildJMXResourcePath(jmxResourceTarget{
|
||||
Kind: jmxResourceKindMBean,
|
||||
ObjectName: "com.gonavi.fixture :type=CacheSettings,name=WhitespaceDomainCache",
|
||||
})
|
||||
_, err = provider.ListResources(context.Background(), cfg, whitespaceDomainMBeanPath)
|
||||
if err == nil {
|
||||
t.Fatal("expected list on whitespace-suffixed domain mbean to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "com.gonavi.fixture") {
|
||||
t.Fatalf("expected whitespace-suffixed domain mbean list context, got %v", err)
|
||||
}
|
||||
|
||||
whitespaceDomainAttributePath := buildJMXResourcePath(jmxResourceTarget{
|
||||
Kind: jmxResourceKindAttribute,
|
||||
ObjectName: "com.gonavi.fixture :type=CacheSettings,name=WhitespaceDomainCache",
|
||||
Attribute: "Mode",
|
||||
})
|
||||
_, err = provider.GetValue(context.Background(), cfg, whitespaceDomainAttributePath)
|
||||
if err == nil {
|
||||
t.Fatal("expected get on whitespace-suffixed domain attribute to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "com.gonavi.fixture") {
|
||||
t.Fatalf("expected whitespace-suffixed domain attribute get context, got %v", err)
|
||||
}
|
||||
|
||||
whitespaceDomainOperationPath := buildJMXResourcePath(jmxResourceTarget{
|
||||
Kind: jmxResourceKindOperation,
|
||||
ObjectName: "com.gonavi.fixture :type=CacheSettings,name=WhitespaceDomainCache",
|
||||
Operation: "resize",
|
||||
Signature: []string{"int", "boolean"},
|
||||
})
|
||||
_, err = provider.PreviewChange(context.Background(), cfg, ChangeRequest{
|
||||
ProviderMode: ModeJMX,
|
||||
ResourceID: whitespaceDomainOperationPath,
|
||||
Action: "invoke",
|
||||
Reason: "尝试空白后缀域操作预览",
|
||||
Payload: map[string]any{
|
||||
"args": []any{4, true},
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected preview on whitespace-suffixed domain operation to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "com.gonavi.fixture") {
|
||||
t.Fatalf("expected whitespace-suffixed domain operation preview context, got %v", err)
|
||||
}
|
||||
|
||||
_, err = provider.ApplyChange(context.Background(), cfg, ChangeRequest{
|
||||
ProviderMode: ModeJMX,
|
||||
ResourceID: whitespaceDomainOperationPath,
|
||||
Action: "invoke",
|
||||
Reason: "尝试空白后缀域操作调用",
|
||||
Payload: map[string]any{
|
||||
"args": []any{4, true},
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected apply on whitespace-suffixed domain operation to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "com.gonavi.fixture") {
|
||||
t.Fatalf("expected whitespace-suffixed domain operation apply context, got %v", err)
|
||||
}
|
||||
|
||||
_, err = monitoringProvider.GetMonitoringSnapshot(context.Background(), cfg, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected monitor on blocked domain allowlist to fail")
|
||||
}
|
||||
if got := err.Error(); !containsAll(got, "domain", "java.lang") {
|
||||
t.Fatalf("expected blocked domain monitor context, got %v", err)
|
||||
}
|
||||
|
||||
monitoringCfg := cfg
|
||||
monitoringCfg.JVM.JMX.DomainAllowlist = []string{"com.gonavi.fixture", "java.lang"}
|
||||
monitoringSnapshot, err := monitoringProvider.GetMonitoringSnapshot(context.Background(), monitoringCfg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expected monitor with java.lang allowlist to succeed: %v", err)
|
||||
}
|
||||
if monitoringSnapshot.Point.Timestamp <= 0 {
|
||||
t.Fatalf("unexpected monitor snapshot point: %#v", monitoringSnapshot.Point)
|
||||
}
|
||||
|
||||
children, err := provider.ListResources(context.Background(), cfg, mbean.Path)
|
||||
if err != nil {
|
||||
t.Fatalf("ListResources(mbean) returned error: %v", err)
|
||||
}
|
||||
modeAttr := findResourceByName(t, children, "Mode")
|
||||
passwordAttr := findResourceByName(t, children, "Password")
|
||||
apiKeyAttr := findResourceByName(t, children, "ApiKey")
|
||||
lastInvocationAttr := findResourceByName(t, children, "LastInvocation")
|
||||
resizeOp := findResourceByName(t, children, "resize(int,boolean)")
|
||||
|
||||
passwordSnapshot, err := provider.GetValue(context.Background(), cfg, passwordAttr.Path)
|
||||
if err != nil {
|
||||
t.Fatalf("GetValue(password) returned error: %v", err)
|
||||
}
|
||||
if !passwordSnapshot.Sensitive {
|
||||
t.Fatalf("expected password snapshot to be sensitive: %#v", passwordSnapshot)
|
||||
}
|
||||
for _, action := range passwordSnapshot.SupportedActions {
|
||||
if payloadValue, ok := action.PayloadExample["value"]; ok && payloadValue == "secret-token" {
|
||||
t.Fatalf("sensitive payload example leaked raw password: %#v", action.PayloadExample)
|
||||
}
|
||||
}
|
||||
|
||||
apiKeySnapshot, err := provider.GetValue(context.Background(), cfg, apiKeyAttr.Path)
|
||||
if err != nil {
|
||||
t.Fatalf("GetValue(api key) returned error: %v", err)
|
||||
}
|
||||
if !apiKeySnapshot.Sensitive {
|
||||
t.Fatalf("expected api key snapshot to be sensitive: %#v", apiKeySnapshot)
|
||||
}
|
||||
for _, action := range apiKeySnapshot.SupportedActions {
|
||||
if payloadValue, ok := action.PayloadExample["value"]; ok && payloadValue == "api-key-secret" {
|
||||
t.Fatalf("sensitive payload example leaked raw api key: %#v", action.PayloadExample)
|
||||
}
|
||||
}
|
||||
|
||||
modeBefore, err := provider.GetValue(context.Background(), cfg, modeAttr.Path)
|
||||
if err != nil {
|
||||
t.Fatalf("GetValue(mode before) returned error: %v", err)
|
||||
|
||||
Binary file not shown.
@@ -2,6 +2,8 @@ package com.gonavi.fixture;
|
||||
|
||||
public final class CacheSettings implements CacheSettingsMBean {
|
||||
private volatile String mode = "warm";
|
||||
private volatile String password = "secret-token";
|
||||
private volatile String apiKey = "api-key-secret";
|
||||
private final int hitCount = 7;
|
||||
private volatile String lastInvocation = "none";
|
||||
|
||||
@@ -15,6 +17,26 @@ public final class CacheSettings implements CacheSettingsMBean {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHitCount() {
|
||||
return hitCount;
|
||||
|
||||
@@ -4,6 +4,12 @@ public interface CacheSettingsMBean {
|
||||
String getMode();
|
||||
void setMode(String mode);
|
||||
|
||||
String getPassword();
|
||||
void setPassword(String password);
|
||||
|
||||
String getApiKey();
|
||||
void setApiKey(String apiKey);
|
||||
|
||||
int getHitCount();
|
||||
|
||||
String getLastInvocation();
|
||||
|
||||
@@ -15,6 +15,14 @@ public final class JMXTestServer {
|
||||
if (!server.isRegistered(objectName)) {
|
||||
server.registerMBean(new CacheSettings(), objectName);
|
||||
}
|
||||
ObjectName defaultDomainObjectName = new ObjectName(":type=CacheSettings,name=DefaultDomainCache");
|
||||
if (!server.isRegistered(defaultDomainObjectName)) {
|
||||
server.registerMBean(new CacheSettings(), defaultDomainObjectName);
|
||||
}
|
||||
ObjectName whitespaceDomainObjectName = new ObjectName("com.gonavi.fixture :type=CacheSettings,name=WhitespaceDomainCache");
|
||||
if (!server.isRegistered(whitespaceDomainObjectName)) {
|
||||
server.registerMBean(new CacheSettings(), whitespaceDomainObjectName);
|
||||
}
|
||||
|
||||
System.out.println("READY");
|
||||
System.out.flush();
|
||||
|
||||
@@ -52,13 +52,13 @@ final class JmxRuntime {
|
||||
case "list":
|
||||
return listResources(server, connection, target);
|
||||
case "get":
|
||||
return singleton("snapshot", getValue(server, target));
|
||||
return singleton("snapshot", getValue(server, connection, target));
|
||||
case "monitor":
|
||||
return singleton("monitoringSnapshot", getMonitoringSnapshot(server));
|
||||
return singleton("monitoringSnapshot", getMonitoringSnapshot(server, connection));
|
||||
case "preview":
|
||||
return singleton("preview", previewChange(server, target, change));
|
||||
return singleton("preview", previewChange(server, connection, target, change));
|
||||
case "apply":
|
||||
return singleton("applyResult", applyChange(server, target, change));
|
||||
return singleton("applyResult", applyChange(server, connection, target, change));
|
||||
default:
|
||||
throw new IllegalArgumentException("unsupported helper command: " + command);
|
||||
}
|
||||
@@ -100,6 +100,7 @@ final class JmxRuntime {
|
||||
}
|
||||
|
||||
if (target.isDomain()) {
|
||||
requireDomainAllowed(connection, target.domain);
|
||||
Set<ObjectName> names = server.queryNames(new ObjectName(target.domain + ":*"), null);
|
||||
List<ObjectName> sortedNames = new ArrayList<>(names);
|
||||
Collections.sort(sortedNames, Comparator.comparing(ObjectName::getCanonicalName));
|
||||
@@ -123,6 +124,7 @@ final class JmxRuntime {
|
||||
|
||||
if (target.isMBean()) {
|
||||
ObjectName objectName = new ObjectName(target.objectName);
|
||||
requireDomainAllowed(connection, objectName);
|
||||
MBeanInfo info = server.getMBeanInfo(objectName);
|
||||
|
||||
MBeanAttributeInfo[] attributes = info.getAttributes();
|
||||
@@ -170,10 +172,15 @@ final class JmxRuntime {
|
||||
throw new IllegalArgumentException("target kind " + target.kind + " does not support list");
|
||||
}
|
||||
|
||||
private static Map<String, Object> getValue(MBeanServerConnection server, TargetSpec target) throws Exception {
|
||||
private static Map<String, Object> getValue(
|
||||
MBeanServerConnection server,
|
||||
ConnectionSpec connection,
|
||||
TargetSpec target
|
||||
) throws Exception {
|
||||
requireTarget(target);
|
||||
|
||||
if (target.isDomain()) {
|
||||
requireDomainAllowed(connection, target.domain);
|
||||
Set<ObjectName> names = server.queryNames(new ObjectName(target.domain + ":*"), null);
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("domain", target.domain);
|
||||
@@ -182,6 +189,7 @@ final class JmxRuntime {
|
||||
}
|
||||
|
||||
ObjectName objectName = new ObjectName(target.objectName);
|
||||
requireDomainAllowed(connection, objectName);
|
||||
if (target.isMBean()) {
|
||||
MBeanInfo info = server.getMBeanInfo(objectName);
|
||||
List<Map<String, Object>> attributes = new ArrayList<>();
|
||||
@@ -219,7 +227,9 @@ final class JmxRuntime {
|
||||
throw new IllegalArgumentException("unsupported target kind: " + target.kind);
|
||||
}
|
||||
|
||||
private static Map<String, Object> getMonitoringSnapshot(MBeanServerConnection server) throws Exception {
|
||||
private static Map<String, Object> getMonitoringSnapshot(MBeanServerConnection server, ConnectionSpec connection) throws Exception {
|
||||
requireDomainAllowed(connection, "java.lang");
|
||||
|
||||
LinkedHashMap<String, Object> result = new LinkedHashMap<>();
|
||||
LinkedHashMap<String, Object> point = new LinkedHashMap<>();
|
||||
List<String> availableMetrics = new ArrayList<>();
|
||||
@@ -423,6 +433,7 @@ final class JmxRuntime {
|
||||
|
||||
private static Map<String, Object> previewChange(
|
||||
MBeanServerConnection server,
|
||||
ConnectionSpec connection,
|
||||
TargetSpec target,
|
||||
Map<String, Object> change
|
||||
) throws Exception {
|
||||
@@ -431,6 +442,7 @@ final class JmxRuntime {
|
||||
|
||||
if (target.isAttribute()) {
|
||||
ObjectName objectName = new ObjectName(target.objectName);
|
||||
requireDomainAllowed(connection, objectName);
|
||||
MBeanAttributeInfo attributeInfo = requireAttributeInfo(server, objectName, target.attribute);
|
||||
Map<String, Object> before = attributeSnapshot(objectName, attributeInfo, server.getAttribute(objectName, target.attribute));
|
||||
if (!attributeInfo.isWritable()) {
|
||||
@@ -457,6 +469,7 @@ final class JmxRuntime {
|
||||
|
||||
if (target.isOperation()) {
|
||||
ObjectName objectName = new ObjectName(target.objectName);
|
||||
requireDomainAllowed(connection, objectName);
|
||||
MBeanOperationInfo operationInfo = requireOperationInfo(server, objectName, target.operation, target.signature);
|
||||
List<Object> args = argumentList(payload);
|
||||
String[] signature = effectiveSignature(target, payload, operationInfo);
|
||||
@@ -486,6 +499,7 @@ final class JmxRuntime {
|
||||
|
||||
private static Map<String, Object> applyChange(
|
||||
MBeanServerConnection server,
|
||||
ConnectionSpec connection,
|
||||
TargetSpec target,
|
||||
Map<String, Object> change
|
||||
) throws Exception {
|
||||
@@ -494,6 +508,7 @@ final class JmxRuntime {
|
||||
|
||||
if (target.isAttribute()) {
|
||||
ObjectName objectName = new ObjectName(target.objectName);
|
||||
requireDomainAllowed(connection, objectName);
|
||||
MBeanAttributeInfo attributeInfo = requireAttributeInfo(server, objectName, target.attribute);
|
||||
if (!attributeInfo.isWritable()) {
|
||||
throw new IllegalArgumentException("attribute " + target.attribute + " is not writable");
|
||||
@@ -512,6 +527,7 @@ final class JmxRuntime {
|
||||
|
||||
if (target.isOperation()) {
|
||||
ObjectName objectName = new ObjectName(target.objectName);
|
||||
requireDomainAllowed(connection, objectName);
|
||||
MBeanOperationInfo operationInfo = requireOperationInfo(server, objectName, target.operation, target.signature);
|
||||
List<Object> args = argumentList(payload);
|
||||
String[] signature = effectiveSignature(target, payload, operationInfo);
|
||||
@@ -590,17 +606,18 @@ final class JmxRuntime {
|
||||
Object value
|
||||
) {
|
||||
Object jsonValue = toJsonCompatible(value);
|
||||
boolean sensitive = isSensitiveName(attributeInfo.getName());
|
||||
List<Map<String, Object>> supportedActions = attributeInfo.isWritable()
|
||||
? Collections.singletonList(actionDefinition(
|
||||
"set",
|
||||
"设置属性",
|
||||
"更新 JMX 属性 " + attributeInfo.getName(),
|
||||
isSensitiveName(attributeInfo.getName()),
|
||||
sensitive,
|
||||
Collections.singletonList(payloadField("value", attributeInfo.getType(), true, "目标属性值")),
|
||||
metadata("value", jsonValue)
|
||||
sensitive ? Collections.<String, Object>emptyMap() : metadata("value", jsonValue)
|
||||
))
|
||||
: Collections.emptyList();
|
||||
return snapshot("attribute", inferFormat(jsonValue), jsonValue, attributeInfo.getDescription(), isSensitiveName(attributeInfo.getName()), supportedActions, metadata(
|
||||
return snapshot("attribute", inferFormat(jsonValue), jsonValue, attributeInfo.getDescription(), sensitive, supportedActions, metadata(
|
||||
"objectName", objectName.toString(),
|
||||
"attribute", attributeInfo.getName(),
|
||||
"type", attributeInfo.getType(),
|
||||
@@ -898,13 +915,47 @@ final class JmxRuntime {
|
||||
return lowered.contains("password")
|
||||
|| lowered.contains("secret")
|
||||
|| lowered.contains("token")
|
||||
|| lowered.contains("credential");
|
||||
|| lowered.contains("credential")
|
||||
|| lowered.contains("apikey")
|
||||
|| lowered.contains("api_key")
|
||||
|| lowered.contains("accesskey")
|
||||
|| lowered.contains("access_key")
|
||||
|| lowered.contains("privatekey")
|
||||
|| lowered.contains("private_key")
|
||||
|| lowered.contains("secretkey")
|
||||
|| lowered.contains("secret_key")
|
||||
|| lowered.contains("authkey")
|
||||
|| lowered.contains("auth_key");
|
||||
}
|
||||
|
||||
private static String domainOf(ObjectName objectName) {
|
||||
return objectName.getDomain();
|
||||
}
|
||||
|
||||
private static void requireDomainAllowed(ConnectionSpec connection, String domain) {
|
||||
if (connection == null) {
|
||||
return;
|
||||
}
|
||||
String rawDomain = domain == null ? "" : domain;
|
||||
String normalizedDomain = rawDomain.trim();
|
||||
if (normalizedDomain.isEmpty()) {
|
||||
if (connection.hasDomainAllowlist()) {
|
||||
throw new IllegalArgumentException("domain is not allowed: <default>");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!rawDomain.equals(normalizedDomain) || !connection.isDomainAllowed(rawDomain)) {
|
||||
throw new IllegalArgumentException("domain is not allowed: " + normalizedDomain);
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireDomainAllowed(ConnectionSpec connection, ObjectName objectName) {
|
||||
if (objectName == null) {
|
||||
return;
|
||||
}
|
||||
requireDomainAllowed(connection, objectName.getDomain());
|
||||
}
|
||||
|
||||
private static void requireTarget(TargetSpec target) {
|
||||
if (target == null || target.isRoot()) {
|
||||
throw new IllegalArgumentException("change target is required");
|
||||
@@ -1263,6 +1314,10 @@ final class JmxRuntime {
|
||||
return new ConnectionSpec(host, port, username, password, allowlist);
|
||||
}
|
||||
|
||||
private boolean hasDomainAllowlist() {
|
||||
return !domainAllowlist.isEmpty();
|
||||
}
|
||||
|
||||
private boolean isDomainAllowed(String domain) {
|
||||
return domainAllowlist.isEmpty() || domainAllowlist.contains(domain);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user