Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- [Resource Sharing] Make migrate api require default access level to be supplied and updates documentations + tests ([#5717](https://github.com/opensearch-project/security/pull/5717))
- [Resource Sharing] Removes share and revoke java APIs ([#5718](https://github.com/opensearch-project/security/pull/5718))
- Fix build failure in SecurityFilterTests ([#5736](https://github.com/opensearch-project/security/pull/5736))
- [Resource Sharing]Refactor ResourceProvider to an interface and other ResourceSharing refactors ([#5755](https://github.com/opensearch-project/security/pull/5755))
- Replace AccessController and remove restriction on word Extension ([#5750](https://github.com/opensearch-project/security/pull/5750))
- Add security provider earlier in bootstrap process ([#5749](https://github.com/opensearch-project/security/pull/5749))
- [GRPC] Fix compilation errors from core protobuf version bump to 0.23.0 ([#5763](https://github.com/opensearch-project/security/pull/5763))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ public void testTypesApi_mustListSampleResourceAsAType() {
Map<String, Object> firstType = (Map<String, Object>) types.get(0);
assertThat(firstType.get("type"), equalTo("sample-resource"));
assertThat(
(List<String>) firstType.get("action_groups"),
(List<String>) firstType.get("access_levels"),
containsInAnyOrder("sample_read_only", "sample_read_write", "sample_full_access")
);
Map<String, Object> secondType = (Map<String, Object>) types.get(1);
assertThat(secondType.get("type"), equalTo("sample-resource-group"));
assertThat(
(List<String>) secondType.get("action_groups"),
(List<String>) secondType.get("access_levels"),
containsInAnyOrder("sample_group_read_only", "sample_group_read_write", "sample_group_full_access")
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@ public class SampleResourceExtension implements ResourceSharingExtension {

@Override
public Set<ResourceProvider> getResourceProviders() {
return Set.of(new ResourceProvider(RESOURCE_TYPE, RESOURCE_INDEX_NAME));
return Set.of(new ResourceProvider() {
@Override
public String resourceType() {
return RESOURCE_TYPE;
}

@Override
public String resourceIndexName() {
return RESOURCE_INDEX_NAME;
}
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@ public class SampleResourceGroupExtension implements ResourceSharingExtension {

@Override
public Set<ResourceProvider> getResourceProviders() {
return Set.of(new ResourceProvider(RESOURCE_GROUP_TYPE, RESOURCE_INDEX_NAME));
return Set.of(new ResourceProvider() {
@Override
public String resourceType() {
return RESOURCE_GROUP_TYPE;
}

@Override
public String resourceIndexName() {
return RESOURCE_INDEX_NAME;
}
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
*
* @opensearch.experimental
*/
public record ResourceProvider(String resourceType, String resourceIndexName) {
public interface ResourceProvider {

String resourceType();

String resourceIndexName();

}
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ public OpenSearchSecurityPlugin(final Settings settings, final Path configPath)
resourceSharingEnabledSetting = new ResourceSharingFeatureFlagSetting(settings, resourcePluginInfo); // not filtered
resourceSharingProtectedResourceTypesSetting = new ResourceSharingProtectedResourcesSetting(settings, resourcePluginInfo); // not
// filtered
resourcePluginInfo.setResourceSharingProtectedTypesSetting(resourceSharingProtectedResourceTypesSetting);
resourcePluginInfo.setProtectedTypesSetting(resourceSharingProtectedResourceTypesSetting);

if (disabled) {
this.sslCertReloadEnabled = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import org.opensearch.security.auth.UserSubjectImpl;
import org.opensearch.security.configuration.AdminDNs;
import org.opensearch.security.privileges.PrivilegesEvaluator;
import org.opensearch.security.resources.sharing.Recipient;
import org.opensearch.security.resources.sharing.ResourceSharing;
import org.opensearch.security.resources.sharing.ShareWith;
import org.opensearch.security.securityconf.FlattenedActionGroups;
Expand Down Expand Up @@ -170,27 +169,22 @@ public void hasPermission(
return;
}

resourceSharingIndexHandler.fetchSharingInfo(resourceIndex, resourceId, ActionListener.wrap(document -> {
// Document may be null when cluster has enabled resource-sharing protection for that index, but have not migrated any records.
resourceSharingIndexHandler.fetchSharingInfo(resourceIndex, resourceId, ActionListener.wrap(sharingInfo -> {
// sharingInfo may be null when cluster has enabled resource-sharing protection for that index, but have not migrated any
// records.
// This also means that for non-existing documents, the evaluator will return 403 instead
if (document == null) {
if (sharingInfo == null) {
LOGGER.warn("No sharing info found for '{}'. Action {} is not allowed.", resourceId, action);
listener.onResponse(false);
return;
}

userRoles.add("*");
userBackendRoles.add("*");

if (document.isCreatedBy(user.getName())) {
if (sharingInfo.isCreatedBy(user.getName())) {
listener.onResponse(true);
return;
}

Set<String> accessLevels = new HashSet<>();
accessLevels.addAll(document.fetchAccessLevels(Recipient.USERS, Set.of(user.getName(), "*")));
accessLevels.addAll(document.fetchAccessLevels(Recipient.ROLES, userRoles));
accessLevels.addAll(document.fetchAccessLevels(Recipient.BACKEND_ROLES, userBackendRoles));
Set<String> accessLevels = sharingInfo.getAccessLevelsForUser(user);

if (accessLevels.isEmpty()) {
listener.onResponse(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.opensearch.security.securityconf.FlattenedActionGroups;
import org.opensearch.security.securityconf.impl.CType;
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;
import org.opensearch.security.securityconf.impl.v7.ActionGroupsV7;
Expand Down Expand Up @@ -88,11 +87,8 @@ public static void loadActionGroupsConfig(ResourcePluginInfo resourcePluginInfo)
|| e.getValue().getAllowed_actions().isEmpty()
);

FlattenedActionGroups flattened = new FlattenedActionGroups(cfg);

// Publish to ResourcePluginInfo → used by UI and authZ
resourcePluginInfo.registerActionGroupNames(resType, cfg.getCEntries().keySet());
resourcePluginInfo.registerFlattened(resType, flattened);
resourcePluginInfo.registerAccessLevels(resType, cfg);

log.info("Registered {} action-groups for {}", cfg.getCEntries().size(), resType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,12 @@ public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult re
resourceIndex
);
}, e -> { log.debug(e.getMessage()); });
ResourceSharing.Builder builder = ResourceSharing.builder()
.resourceId(resourceId)
.createdBy(new CreatedBy(user.getName(), user.getRequestedTenant()));
ResourceSharing sharingInfo = builder.build();
// User.getRequestedTenant() is null if multi-tenancy is disabled
this.resourceSharingIndexHandler.indexResourceSharing(
resourceId,
resourceIndex,
new CreatedBy(user.getName(), user.getRequestedTenant()),
null,
listener
);
this.resourceSharingIndexHandler.indexResourceSharing(resourceIndex, sharingInfo, listener);

} catch (IOException e) {
log.debug("Failed to create a resource sharing entry for resource: {}", resourceId, e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
package org.opensearch.security.resources;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
Expand All @@ -27,6 +26,8 @@
import org.opensearch.core.xcontent.ToXContentObject;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.security.securityconf.FlattenedActionGroups;
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;
import org.opensearch.security.securityconf.impl.v7.ActionGroupsV7;
import org.opensearch.security.setting.OpensearchDynamicSetting;
import org.opensearch.security.spi.resources.ResourceSharingExtension;
import org.opensearch.security.spi.resources.client.ResourceSharingClient;
Expand All @@ -41,15 +42,15 @@ public class ResourcePluginInfo {

private ResourceSharingClient resourceAccessControlClient;

private OpensearchDynamicSetting<List<String>> resourceSharingProtectedTypesSetting;
private OpensearchDynamicSetting<List<String>> protectedTypesSetting;

private final Set<ResourceSharingExtension> resourceSharingExtensions = new HashSet<>();

// type <-> index
private final Map<String, String> typeToIndex = new HashMap<>();

// UI: action-group *names* per type
private final Map<String, LinkedHashSet<String>> typeToGroupNames = new HashMap<>();
// UI: access-level *names* per type
private final Map<String, LinkedHashSet<String>> typeToAccessLevels = new HashMap<>();

// AuthZ: resolved (flattened) groups per type
private final Map<String, FlattenedActionGroups> typeToFlattened = new HashMap<>();
Expand All @@ -59,8 +60,8 @@ public class ResourcePluginInfo {
private Set<String> currentProtectedTypes = Collections.emptySet(); // snapshot of last set
private Set<String> cachedProtectedTypeIndices = Collections.emptySet(); // precomputed indices

public void setResourceSharingProtectedTypesSetting(OpensearchDynamicSetting<List<String>> resourceSharingProtectedTypesSetting) {
this.resourceSharingProtectedTypesSetting = resourceSharingProtectedTypesSetting;
public void setProtectedTypesSetting(OpensearchDynamicSetting<List<String>> protectedTypesSetting) {
this.protectedTypesSetting = protectedTypesSetting;
}

public void setResourceSharingExtensions(Set<ResourceSharingExtension> extensions) {
Expand Down Expand Up @@ -144,34 +145,26 @@ public ResourceSharingClient getResourceAccessControlClient() {
return resourceAccessControlClient;
}

/** Register/merge action-group names for a given resource type. */
public record ResourceDashboardInfo(String resourceType, Set<String> actionGroups // names only (for UI)
/** Register/merge access-levels names for a given resource type. */
public record ResourceDashboardInfo(String resourceType, Set<String> accessLevels // names only (for UI)
) implements ToXContentObject {
@Override
public XContentBuilder toXContent(XContentBuilder b, Params p) throws IOException {
b.startObject();
b.field("type", resourceType);
b.field("action_groups", actionGroups == null ? Collections.emptyList() : actionGroups);
b.field("access_levels", accessLevels == null ? Collections.emptyList() : accessLevels);
return b.endObject();
}
}

public void registerActionGroupNames(String resourceType, Collection<String> names) {
if (resourceType == null || names == null) return;
lock.writeLock().lock();
try {
typeToGroupNames.computeIfAbsent(resourceType, k -> new LinkedHashSet<>())
.addAll(names.stream().filter(Objects::nonNull).map(String::trim).filter(s -> !s.isEmpty()).toList());
} finally {
lock.writeLock().unlock();
}
}

public void registerFlattened(String resourceType, FlattenedActionGroups flattened) {
if (resourceType == null || flattened == null) return;
public void registerAccessLevels(String resourceType, SecurityDynamicConfiguration<ActionGroupsV7> accessLevels) {
if (resourceType == null || accessLevels == null) return;
lock.writeLock().lock();
try {
FlattenedActionGroups flattened = new FlattenedActionGroups(accessLevels);
typeToFlattened.put(resourceType, flattened);
typeToAccessLevels.computeIfAbsent(resourceType, k -> new LinkedHashSet<>())
.addAll(accessLevels.getCEntries().keySet().stream().toList());
} finally {
lock.writeLock().unlock();
}
Expand Down Expand Up @@ -214,7 +207,10 @@ public Set<ResourceDashboardInfo> getResourceTypes() {
return typeToIndex.keySet()
.stream()
.map(
s -> new ResourceDashboardInfo(s, Collections.unmodifiableSet(typeToGroupNames.getOrDefault(s, new LinkedHashSet<>())))
s -> new ResourceDashboardInfo(
s,
Collections.unmodifiableSet(typeToAccessLevels.getOrDefault(s, new LinkedHashSet<>()))
)
)
.collect(Collectors.toCollection(LinkedHashSet::new));
} finally {
Expand All @@ -232,7 +228,7 @@ public Set<String> getResourceIndices() {
}

public Set<String> getResourceIndicesForProtectedTypes() {
List<String> resourceTypes = this.resourceSharingProtectedTypesSetting.getDynamicSettingValue();
List<String> resourceTypes = this.protectedTypesSetting.getDynamicSettingValue();
if (resourceTypes == null || resourceTypes.isEmpty()) {
return Collections.emptySet();
}
Expand Down
Loading
Loading