|
@@ -8,6 +8,7 @@ package org.elasticsearch.xpack.core.security.authz;
|
|
|
|
|
|
import org.elasticsearch.ElasticsearchParseException;
|
|
|
import org.elasticsearch.ElasticsearchSecurityException;
|
|
|
+import org.elasticsearch.TransportVersion;
|
|
|
import org.elasticsearch.common.Strings;
|
|
|
import org.elasticsearch.common.ValidationException;
|
|
|
import org.elasticsearch.common.bytes.BytesArray;
|
|
@@ -55,6 +56,8 @@ import static org.elasticsearch.transport.RemoteClusterPortSettings.TRANSPORT_VE
|
|
|
*/
|
|
|
public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
|
|
|
+ public static final TransportVersion WORKFLOWS_RESTRICTION_VERSION = TransportVersion.V_8_500_005;
|
|
|
+
|
|
|
public static final String ROLE_TYPE = "role";
|
|
|
|
|
|
private final String name;
|
|
@@ -64,6 +67,7 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
private final ApplicationResourcePrivileges[] applicationPrivileges;
|
|
|
private final String[] runAs;
|
|
|
private final RemoteIndicesPrivileges[] remoteIndicesPrivileges;
|
|
|
+ private final Restriction restriction;
|
|
|
private final Map<String, Object> metadata;
|
|
|
private final Map<String, Object> transientMetadata;
|
|
|
|
|
@@ -89,7 +93,7 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
|
|
|
/**
|
|
|
* @deprecated Use {@link #RoleDescriptor(String, String[], IndicesPrivileges[], ApplicationResourcePrivileges[],
|
|
|
- * ConfigurableClusterPrivilege[], String[], Map, Map, RemoteIndicesPrivileges[])}
|
|
|
+ * ConfigurableClusterPrivilege[], String[], Map, Map, RemoteIndicesPrivileges[], Restriction)}
|
|
|
*/
|
|
|
@Deprecated
|
|
|
public RoleDescriptor(
|
|
@@ -104,7 +108,7 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
|
|
|
/**
|
|
|
* @deprecated Use {@link #RoleDescriptor(String, String[], IndicesPrivileges[], ApplicationResourcePrivileges[],
|
|
|
- * ConfigurableClusterPrivilege[], String[], Map, Map, RemoteIndicesPrivileges[])}
|
|
|
+ * ConfigurableClusterPrivilege[], String[], Map, Map, RemoteIndicesPrivileges[], Restriction)}
|
|
|
*/
|
|
|
@Deprecated
|
|
|
public RoleDescriptor(
|
|
@@ -115,7 +119,18 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
@Nullable Map<String, Object> metadata,
|
|
|
@Nullable Map<String, Object> transientMetadata
|
|
|
) {
|
|
|
- this(name, clusterPrivileges, indicesPrivileges, null, null, runAs, metadata, transientMetadata, RemoteIndicesPrivileges.NONE);
|
|
|
+ this(
|
|
|
+ name,
|
|
|
+ clusterPrivileges,
|
|
|
+ indicesPrivileges,
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ runAs,
|
|
|
+ metadata,
|
|
|
+ transientMetadata,
|
|
|
+ RemoteIndicesPrivileges.NONE,
|
|
|
+ Restriction.NONE
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
public RoleDescriptor(
|
|
@@ -137,7 +152,8 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
runAs,
|
|
|
metadata,
|
|
|
transientMetadata,
|
|
|
- RemoteIndicesPrivileges.NONE
|
|
|
+ RemoteIndicesPrivileges.NONE,
|
|
|
+ Restriction.NONE
|
|
|
);
|
|
|
}
|
|
|
|
|
@@ -150,7 +166,8 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
@Nullable String[] runAs,
|
|
|
@Nullable Map<String, Object> metadata,
|
|
|
@Nullable Map<String, Object> transientMetadata,
|
|
|
- @Nullable RemoteIndicesPrivileges[] remoteIndicesPrivileges
|
|
|
+ @Nullable RemoteIndicesPrivileges[] remoteIndicesPrivileges,
|
|
|
+ @Nullable Restriction restriction
|
|
|
) {
|
|
|
this.name = name;
|
|
|
this.clusterPrivileges = clusterPrivileges != null ? clusterPrivileges : Strings.EMPTY_ARRAY;
|
|
@@ -163,6 +180,7 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
? Collections.unmodifiableMap(transientMetadata)
|
|
|
: Collections.singletonMap("enabled", true);
|
|
|
this.remoteIndicesPrivileges = remoteIndicesPrivileges != null ? remoteIndicesPrivileges : RemoteIndicesPrivileges.NONE;
|
|
|
+ this.restriction = restriction != null ? restriction : Restriction.NONE;
|
|
|
}
|
|
|
|
|
|
public RoleDescriptor(StreamInput in) throws IOException {
|
|
@@ -184,6 +202,11 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
} else {
|
|
|
this.remoteIndicesPrivileges = RemoteIndicesPrivileges.NONE;
|
|
|
}
|
|
|
+ if (in.getTransportVersion().onOrAfter(WORKFLOWS_RESTRICTION_VERSION)) {
|
|
|
+ this.restriction = new Restriction(in);
|
|
|
+ } else {
|
|
|
+ this.restriction = Restriction.NONE;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public String getName() {
|
|
@@ -235,13 +258,26 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
|| hasConfigurableClusterPrivileges()
|
|
|
|| hasApplicationPrivileges()
|
|
|
|| hasRunAs()
|
|
|
- || hasRemoteIndicesPrivileges();
|
|
|
+ || hasRemoteIndicesPrivileges()
|
|
|
+ || hasWorkflowsRestriction();
|
|
|
}
|
|
|
|
|
|
public String[] getRunAs() {
|
|
|
return this.runAs;
|
|
|
}
|
|
|
|
|
|
+ public Restriction getRestriction() {
|
|
|
+ return restriction;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean hasRestriction() {
|
|
|
+ return restriction != null && false == restriction.isEmpty();
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean hasWorkflowsRestriction() {
|
|
|
+ return hasRestriction() && restriction.hasWorkflows();
|
|
|
+ }
|
|
|
+
|
|
|
public Map<String, Object> getMetadata() {
|
|
|
return metadata;
|
|
|
}
|
|
@@ -276,7 +312,7 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
for (RemoteIndicesPrivileges group : remoteIndicesPrivileges) {
|
|
|
sb.append(group.toString()).append(",");
|
|
|
}
|
|
|
- sb.append("]");
|
|
|
+ sb.append("], restriction=").append(restriction);
|
|
|
sb.append("]");
|
|
|
return sb.toString();
|
|
|
}
|
|
@@ -295,7 +331,8 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
if (Arrays.equals(applicationPrivileges, that.applicationPrivileges) == false) return false;
|
|
|
if (metadata.equals(that.getMetadata()) == false) return false;
|
|
|
if (Arrays.equals(runAs, that.runAs) == false) return false;
|
|
|
- return Arrays.equals(remoteIndicesPrivileges, that.remoteIndicesPrivileges);
|
|
|
+ if (Arrays.equals(remoteIndicesPrivileges, that.remoteIndicesPrivileges) == false) return false;
|
|
|
+ return restriction.equals(that.restriction);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -308,6 +345,7 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
result = 31 * result + Arrays.hashCode(runAs);
|
|
|
result = 31 * result + metadata.hashCode();
|
|
|
result = 31 * result + Arrays.hashCode(remoteIndicesPrivileges);
|
|
|
+ result = 31 * result + restriction.hashCode();
|
|
|
return result;
|
|
|
}
|
|
|
|
|
@@ -318,7 +356,8 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
&& applicationPrivileges.length == 0
|
|
|
&& runAs.length == 0
|
|
|
&& metadata.size() == 0
|
|
|
- && remoteIndicesPrivileges.length == 0;
|
|
|
+ && remoteIndicesPrivileges.length == 0
|
|
|
+ && restriction.isEmpty();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -358,6 +397,9 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
if (hasRemoteIndicesPrivileges()) {
|
|
|
builder.xContentList(Fields.REMOTE_INDICES.getPreferredName(), remoteIndicesPrivileges);
|
|
|
}
|
|
|
+ if (hasRestriction()) {
|
|
|
+ builder.field(Fields.RESTRICTION.getPreferredName(), restriction);
|
|
|
+ }
|
|
|
return builder.endObject();
|
|
|
}
|
|
|
|
|
@@ -377,6 +419,9 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
if (out.getTransportVersion().onOrAfter(TRANSPORT_VERSION_ADVANCED_REMOTE_CLUSTER_SECURITY_CCS)) {
|
|
|
out.writeArray(remoteIndicesPrivileges);
|
|
|
}
|
|
|
+ if (out.getTransportVersion().onOrAfter(WORKFLOWS_RESTRICTION_VERSION)) {
|
|
|
+ restriction.writeTo(out);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public static RoleDescriptor parse(String name, BytesReference source, boolean allow2xFormat, XContentType xContentType)
|
|
@@ -418,6 +463,7 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
List<ConfigurableClusterPrivilege> configurableClusterPrivileges = Collections.emptyList();
|
|
|
ApplicationResourcePrivileges[] applicationPrivileges = null;
|
|
|
String[] runAsUsers = null;
|
|
|
+ Restriction restriction = null;
|
|
|
Map<String, Object> metadata = null;
|
|
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
|
|
if (token == XContentParser.Token.FIELD_NAME) {
|
|
@@ -457,6 +503,8 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
} else if (untrustedRemoteClusterEnabled
|
|
|
&& Fields.REMOTE_INDICES.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
remoteIndicesPrivileges = parseRemoteIndices(name, parser);
|
|
|
+ } else if (Fields.RESTRICTION.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
+ restriction = Restriction.parse(name, parser);
|
|
|
} else if (Fields.TYPE.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
// don't need it
|
|
|
} else {
|
|
@@ -476,7 +524,8 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
runAsUsers,
|
|
|
metadata,
|
|
|
null,
|
|
|
- remoteIndicesPrivileges
|
|
|
+ remoteIndicesPrivileges,
|
|
|
+ restriction
|
|
|
);
|
|
|
}
|
|
|
|
|
@@ -1513,8 +1562,115 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
}
|
|
|
return applicationPrivileges;
|
|
|
}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static class Restriction implements Writeable, ToXContentObject {
|
|
|
+
|
|
|
+ public static final Restriction NONE = new Restriction((String[]) null);
|
|
|
+
|
|
|
+ private final String[] workflows;
|
|
|
+
|
|
|
+ public Restriction(String[] workflows) {
|
|
|
+ assert workflows == null || workflows.length > 0 : "workflows cannot be an empty array";
|
|
|
+ this.workflows = workflows;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Restriction(StreamInput in) throws IOException {
|
|
|
+ this(in.readOptionalStringArray());
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean hasWorkflows() {
|
|
|
+ return workflows != null && workflows.length > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String[] getWorkflows() {
|
|
|
+ return workflows;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
|
+ builder.startObject();
|
|
|
+ builder.array(Fields.WORKFLOWS.getPreferredName(), workflows);
|
|
|
+ return builder.endObject();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeTo(StreamOutput out) throws IOException {
|
|
|
+ out.writeOptionalStringArray(workflows);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean equals(Object o) {
|
|
|
+ if (this == o) return true;
|
|
|
+ if (o == null || getClass() != o.getClass()) return false;
|
|
|
+ Restriction that = (Restriction) o;
|
|
|
+ return Arrays.equals(workflows, that.workflows);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int hashCode() {
|
|
|
+ return Arrays.hashCode(workflows);
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isEmpty() {
|
|
|
+ return workflows == null || workflows.length == 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() {
|
|
|
+ StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append("[workflows=[")
|
|
|
+ .append(Strings.arrayToCommaDelimitedString(workflows))
|
|
|
+ .append("]]");
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ static Restriction parse(String roleName, XContentParser parser) throws IOException {
|
|
|
+ // advance to the START_OBJECT token if needed
|
|
|
+ XContentParser.Token token = parser.currentToken() == null ? parser.nextToken() : parser.currentToken();
|
|
|
+ if (token != XContentParser.Token.START_OBJECT) {
|
|
|
+ throw new ElasticsearchParseException(
|
|
|
+ "failed to parse restriction for role [{}]. "
|
|
|
+ + "expected field [{}] value to be an object, but found an element of type [{}]",
|
|
|
+ roleName,
|
|
|
+ parser.currentName(),
|
|
|
+ token
|
|
|
+ );
|
|
|
+ }
|
|
|
+ String currentFieldName = null;
|
|
|
+ String[] workflows = null;
|
|
|
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
|
|
+ if (token == XContentParser.Token.FIELD_NAME) {
|
|
|
+ currentFieldName = parser.currentName();
|
|
|
+ } else if (Fields.WORKFLOWS.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
+ workflows = readWorkflowsStringArray(roleName, parser);
|
|
|
+ } else {
|
|
|
+ throw new ElasticsearchParseException(
|
|
|
+ "failed to parse restriction for role [{}]. unexpected field [{}]",
|
|
|
+ roleName,
|
|
|
+ currentFieldName
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (workflows != null && workflows.length <= 0) {
|
|
|
+ throw new ElasticsearchParseException(
|
|
|
+ "failed to parse restriction for role [{}]. [{}] cannot be an empty array",
|
|
|
+ roleName,
|
|
|
+ Fields.WORKFLOWS
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return new Restriction(workflows);
|
|
|
+ }
|
|
|
|
|
|
+ private static String[] readWorkflowsStringArray(String roleName, XContentParser parser) throws IOException {
|
|
|
+ try {
|
|
|
+ return XContentUtils.readStringArray(parser, false);
|
|
|
+ } catch (ElasticsearchParseException e) {
|
|
|
+ // re-wrap in order to add the role name
|
|
|
+ throw new ElasticsearchParseException("failed to parse restriction for role [{}]. {}", e, roleName, e.getMessage());
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
|
|
|
public interface Fields {
|
|
@@ -1539,5 +1695,7 @@ public class RoleDescriptor implements ToXContentObject, Writeable {
|
|
|
ParseField METADATA = new ParseField("metadata");
|
|
|
ParseField TRANSIENT_METADATA = new ParseField("transient_metadata");
|
|
|
ParseField TYPE = new ParseField("type");
|
|
|
+ ParseField RESTRICTION = new ParseField("restriction");
|
|
|
+ ParseField WORKFLOWS = new ParseField("workflows");
|
|
|
}
|
|
|
}
|