Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin service accounts #8526

Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5782c10
PluginServiceAccounts
stephen-crawford Jul 5, 2023
4eb4a22
Update changelog
stephen-crawford Jul 5, 2023
3e3f44e
Use pluginInfo
stephen-crawford Jul 5, 2023
949cc6e
Structure changes
stephen-crawford Jul 6, 2023
968d2e1
Add test outline
stephen-crawford Jul 6, 2023
f0c8e93
Basic Service Account Tests
stephen-crawford Jul 7, 2023
a35fce9
Merge branch 'opensearch-project:main' into pluginServiceAccount
stephen-crawford Jul 7, 2023
c93370c
Update changelog
stephen-crawford Jul 7, 2023
a958e44
Use noop to pass tests
stephen-crawford Jul 7, 2023
fc35c8c
Add noop for tests
stephen-crawford Jul 7, 2023
f4a8b35
optimize imports
stephen-crawford Jul 7, 2023
eacf3bb
Merge branch 'main' into pluginServiceAccounts
stephen-crawford Jul 7, 2023
08e0cb9
Refactor identity elements and swap to classes from interfaces to all…
stephen-crawford Jul 10, 2023
305518d
Merge branch 'main' into pluginServiceAccounts
stephen-crawford Jul 11, 2023
1d76147
start outlining runas
stephen-crawford Jul 11, 2023
6be0f4f
Add runAs
stephen-crawford Jul 12, 2023
4937eb1
Swap var
stephen-crawford Jul 12, 2023
68ed66d
Merge branch 'main' into pluginServiceAccounts
stephen-crawford Jul 24, 2023
25c713f
Merge branch 'opensearch-project:main' into pluginServiceAccounts
stephen-crawford Jul 28, 2023
9297405
Merge branch 'opensearch-project:main' into pluginServiceAccounts
stephen-crawford Jul 31, 2023
b5b4868
Fix imports
stephen-crawford Jul 31, 2023
56787c8
Fix imports
stephen-crawford Jul 31, 2023
cc18cfe
connect both extensions and plugins; tests failing because client pas…
stephen-crawford Jul 31, 2023
140c039
Merge branch 'main' into pluginServiceAccounts
stephen-crawford Aug 8, 2023
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Add getter for path field in NestedQueryBuilder ([#4636](https://github.com/opensearch-project/OpenSearch/pull/4636))
- Allow mmap to use new JDK-19 preview APIs in Apache Lucene 9.4+ ([#5151](https://github.com/opensearch-project/OpenSearch/pull/5151))
- Add events correlation engine plugin ([#6854](https://github.com/opensearch-project/OpenSearch/issues/6854))
- Add support for ignoring missing Javadoc on generated code using annotation ([#7604](https://github.com/opensearch-project/OpenSearch/pull/7604))
- Add partial results support for concurrent segment search ([#8306](https://github.com/opensearch-project/OpenSearch/pull/8306))
- - Introduce Service Accounts for Plugins ([#8526](https://github.com/opensearch-project/OpenSearch/pull/8526))

### Dependencies
- Bump `log4j-core` from 2.18.0 to 2.19.0
Expand Down
4 changes: 3 additions & 1 deletion plugins/identity-shiro/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ dependencies {
implementation 'org.passay:passay:1.6.3'

implementation "org.bouncycastle:bcprov-jdk15to18:${versions.bouncycastle}"
implementation project(path: ':server')

testImplementation project(path: ':modules:transport-netty4') // for http
testImplementation project(path: ':modules:transport-netty4') // for http
testImplementation project(path: ':plugins:transport-nio') // for http
testImplementation "org.mockito:mockito-core:${versions.mockito}"
testImplementation project(path: ':client:rest-high-level')
testImplementation 'junit:junit:4.13.2'
testImplementation project(path: ':server')
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@

package org.opensearch.identity.shiro;

import org.opensearch.identity.Subject;
import java.security.Principal;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.opensearch.cluster.ApplicationManager;
import org.opensearch.common.settings.Settings;
import org.opensearch.identity.Subject;
import org.opensearch.identity.tokens.TokenManager;
import org.opensearch.plugins.IdentityPlugin;
import org.opensearch.common.settings.Settings;
import org.opensearch.plugins.Plugin;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;

/**
* Identity implementation with Shiro
Expand All @@ -28,16 +30,18 @@ public final class ShiroIdentityPlugin extends Plugin implements IdentityPlugin

private final Settings settings;
private final ShiroTokenManager authTokenHandler;
private final ApplicationManager applicationManager;
private Principal identityContext = null;

/**
* Create a new instance of the Shiro Identity Plugin
*
* @param settings settings being used in the configuration
*/
public ShiroIdentityPlugin(final Settings settings) {
public ShiroIdentityPlugin(final Settings settings, ApplicationManager applicationManager) {
this.settings = settings;
authTokenHandler = new ShiroTokenManager();

this.applicationManager = applicationManager;
SecurityManager securityManager = new ShiroSecurityManager();
SecurityUtils.setSecurityManager(securityManager);
}
Expand All @@ -61,4 +65,23 @@ public Subject getSubject() {
public TokenManager getTokenManager() {
return this.authTokenHandler;
}

@Override
public ApplicationManager getApplicationManager() {
return this.applicationManager;
}

/**
* Uses the provided principal as a reference for permission resolution and audit logging
*
* @param principal The principal to be used
*/
@Override
public void setIdentityContext(Principal principal) {
identityContext = principal;
}

public Principal getIdentityContext() {
return this.identityContext;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.identity.shiro;

import java.security.Principal;
import java.util.List;
import java.util.Objects;
import org.opensearch.Application;
import org.opensearch.identity.ServiceAccount;

/**
* This class defines a ServiceAccount in the context of the Shiro Identity Plugin.
*
* Here we can see the ShiroServiceAccount implement the ServiceAccount and be used to track permissions assigned to applications.
*/
public class ShiroServiceAccount extends ServiceAccount {

private final Application application;
private final Principal name;
private List<String> permissions = List.of(); // Fine-grained access controls not yet configured

/**
* Creates a principal for an application identity
* @param application The application to be associated with the service account
*/
public ShiroServiceAccount(final Application application) {
super(application);

this.application = application;
name = application.getPrincipal();
}

/**
* This method will not usually exist but is required until fine-grained access controls are implemented
*
* @param permissions The permissions to assign the service account
*/
public void setPermissions(List<String> permissions) {
this.permissions = List.copyOf(permissions);
}

@Override
public String getName() {
return name.getName();
}

@Override
public boolean equals(final Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
final ServiceAccount that = (ServiceAccount) obj;
return Objects.equals(name, that.getName());
}

@Override
public int hashCode() {
return Objects.hash(name);
}

@Override
public String toString() {
return "ServiceAccount(" + "name=" + name + ")";
}

@Override
public List<String> getPermissions() {
return List.copyOf(permissions);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.identity.shiro;

import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.Application;
import org.opensearch.identity.IdentityService;
import org.opensearch.identity.ServiceAccount;
import org.opensearch.identity.ServiceAccountManager;

/**
* Oversees the assignment of ServiceAccounts when using the ShiroIdentityPlugin
*
* @opensearch.experimental
*/
class ShiroServiceAccountManager extends ServiceAccountManager {

private static final Logger log = LogManager.getLogger(IdentityService.class);

private static Map<Application, ServiceAccount> applicationServiceAccountMap = new HashMap<>();

public ShiroServiceAccountManager() {

}

@Override
public ServiceAccount getServiceAccount(Application app) {
if (applicationServiceAccountMap.get(app) == null) {
applicationServiceAccountMap.put(app, new ShiroServiceAccount(app));
}
return applicationServiceAccountMap.get(app);
}

public Map<Application, ServiceAccount> getApplicationServiceAccountMap() {
return applicationServiceAccountMap;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,44 @@

package org.opensearch.identity.shiro;

import java.io.IOException;
import java.util.List;
import org.opensearch.OpenSearchException;
import org.opensearch.cluster.ApplicationManager;
import org.opensearch.common.settings.Settings;
import org.opensearch.extensions.NoopExtensionsManager;
import org.opensearch.identity.IdentityService;
import org.opensearch.identity.noop.NoopServiceAccountManager;
import org.opensearch.plugins.IdentityPlugin;
import org.opensearch.test.OpenSearchTestCase;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThrows;

public class ShiroIdentityPluginTests extends OpenSearchTestCase {

public void testSingleIdentityPluginSucceeds() {
IdentityPlugin identityPlugin1 = new ShiroIdentityPlugin(Settings.EMPTY);
public void testSingleIdentityPluginSucceeds() throws IOException {
IdentityPlugin identityPlugin1 = new ShiroIdentityPlugin(
Settings.EMPTY,
new ApplicationManager(new NoopExtensionsManager(), null, new NoopServiceAccountManager())
);
List<IdentityPlugin> pluginList1 = List.of(identityPlugin1);
IdentityService identityService1 = new IdentityService(Settings.EMPTY, pluginList1);
assertThat(identityService1.getTokenManager(), is(instanceOf(ShiroTokenManager.class)));
}

public void testMultipleIdentityPluginsFail() {
IdentityPlugin identityPlugin1 = new ShiroIdentityPlugin(Settings.EMPTY);
IdentityPlugin identityPlugin2 = new ShiroIdentityPlugin(Settings.EMPTY);
IdentityPlugin identityPlugin3 = new ShiroIdentityPlugin(Settings.EMPTY);
public void testMultipleIdentityPluginsFail() throws IOException {
IdentityPlugin identityPlugin1 = new ShiroIdentityPlugin(
Settings.EMPTY,
new ApplicationManager(new NoopExtensionsManager(), null, new NoopServiceAccountManager())
);
IdentityPlugin identityPlugin2 = new ShiroIdentityPlugin(
Settings.EMPTY,
new ApplicationManager(new NoopExtensionsManager(), null, new NoopServiceAccountManager())
);
IdentityPlugin identityPlugin3 = new ShiroIdentityPlugin(
Settings.EMPTY,
new ApplicationManager(new NoopExtensionsManager(), null, new NoopServiceAccountManager())
);
List<IdentityPlugin> pluginList = List.of(identityPlugin1, identityPlugin2, identityPlugin3);
Exception ex = assertThrows(OpenSearchException.class, () -> new IdentityService(Settings.EMPTY, pluginList));
assert (ex.getMessage().contains("Multiple identity plugins are not supported,"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.identity.shiro;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.opensearch.cluster.ApplicationManager;
import org.opensearch.common.settings.Settings;
import org.opensearch.env.Environment;
import org.opensearch.env.TestEnvironment;
import org.opensearch.extensions.ExtensionsManager;
import org.opensearch.identity.IdentityService;
import org.opensearch.identity.ServiceAccount;
import org.opensearch.index.IndexModule;
import org.opensearch.plugins.IdentityPlugin;
import org.opensearch.plugins.Plugin;
import org.opensearch.plugins.PluginsService;
import org.opensearch.test.OpenSearchTestCase;

public class ShiroServiceAccountTests extends OpenSearchTestCase {

private ShiroServiceAccountManager shiroServiceAccountManager;
private ApplicationManager applicationManager;
private PluginsService pluginsService;
private IdentityService identityService;
private IdentityPlugin identityPlugin;
private AdditionalSettingsPlugin1 additionalSettingsPlugin1;
private AdditionalSettingsPlugin2 additionalSettingsPlugin2;

Settings settings = Settings.builder()
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir())
.put("my.setting", "test")
.put(IndexModule.INDEX_STORE_TYPE_SETTING.getKey(), IndexModule.Type.NIOFS.getSettingsKey())
.build();

@SuppressWarnings("unchecked")
static PluginsService newPluginsService(
Settings settings,
ApplicationManager applicationManager,
Class<? extends Plugin>... classpathPlugins
) {
return new PluginsService(
settings,
null,
null,
TestEnvironment.newEnvironment(settings).pluginsDir(),
Arrays.asList(classpathPlugins)
);
}

public static class AdditionalSettingsPlugin1 extends Plugin {
@Override
public Settings additionalSettings() {
return Settings.builder()
.put("foo.bar", "1")
.put(IndexModule.INDEX_STORE_TYPE_SETTING.getKey(), IndexModule.Type.MMAPFS.getSettingsKey())
.build();
}
}

public static class AdditionalSettingsPlugin2 extends Plugin {
@Override
public Settings additionalSettings() {
return Settings.builder().put("test.this", "2").build();
}
}

@Before
public void setup() throws IOException {
shiroServiceAccountManager = new ShiroServiceAccountManager();
applicationManager = new ApplicationManager(new ExtensionsManager(Set.of()), pluginsService, shiroServiceAccountManager);
identityPlugin = new ShiroIdentityPlugin(Settings.EMPTY, applicationManager);
List<IdentityPlugin> pluginList = List.of(identityPlugin);
identityService = new IdentityService(Settings.EMPTY, pluginList);
additionalSettingsPlugin1 = new AdditionalSettingsPlugin1();
additionalSettingsPlugin2 = new AdditionalSettingsPlugin2();
}

@SuppressWarnings("unchecked")
public void testRegisterSinglePlugin() {
pluginsService = newPluginsService(settings, applicationManager, additionalSettingsPlugin1.getClass());
ServiceAccount serviceAccount = shiroServiceAccountManager.getServiceAccount(pluginsService.plugins.get(0).v1());
assertEquals(pluginsService.plugins.get(0).v1().getPrincipal().getName(), serviceAccount.getName());
assertEquals(shiroServiceAccountManager.getServiceAccount(pluginsService.plugins.get(0).v1()), serviceAccount);
}

@SuppressWarnings("unchecked")
public void testRegisterMultiplePlugins() {
pluginsService = newPluginsService(
settings,
applicationManager,
additionalSettingsPlugin1.getClass(),
additionalSettingsPlugin2.getClass()
);
ServiceAccount serviceAccount1 = shiroServiceAccountManager.getServiceAccount(pluginsService.plugins.get(0).v1());
ServiceAccount serviceAccount2 = shiroServiceAccountManager.getServiceAccount(pluginsService.plugins.get(1).v1());
assertEquals(pluginsService.plugins.get(0).v1().getPrincipal().getName(), serviceAccount1.getName());
assertEquals(shiroServiceAccountManager.getServiceAccount(pluginsService.plugins.get(0).v1()), serviceAccount1);
assertEquals(pluginsService.plugins.get(1).v1().getPrincipal().getName(), serviceAccount2.getName());
assertEquals(shiroServiceAccountManager.getServiceAccount(pluginsService.plugins.get(1).v1()), serviceAccount2);
}

@SuppressWarnings("unchecked")
public void testRegisterMultiplePluginsWithSameName() {
pluginsService = newPluginsService(
settings,
applicationManager,
additionalSettingsPlugin1.getClass(),
additionalSettingsPlugin1.getClass()
);
ServiceAccount serviceAccount1a = shiroServiceAccountManager.getServiceAccount(pluginsService.plugins.get(0).v1());
ServiceAccount serviceAccount1b = shiroServiceAccountManager.getServiceAccount(pluginsService.plugins.get(1).v1());
assertEquals(pluginsService.plugins.get(0).v1().getPrincipal().getName(), serviceAccount1a.getName());
assertEquals(shiroServiceAccountManager.getServiceAccount(pluginsService.plugins.get(0).v1()), serviceAccount1a);
assertEquals(pluginsService.plugins.get(1).v1().getPrincipal().getName(), serviceAccount1b.getName());
assertEquals(shiroServiceAccountManager.getServiceAccount(pluginsService.plugins.get(1).v1()), serviceAccount1b);
assertEquals(serviceAccount1a, serviceAccount1b); // Plugins are identified by their names so same name means same service account
}
}
Loading