Skip to content

Commit 994e115

Browse files
stephen-crawfordowaiskazi19peternied
authored
Provide service accounts tokens to extensions (#9618)
Provide service accounts tokens to extensions This change adds a new transport action which passes the extension a string representation of its service account auth token. This token is created by the TokenManager interface implementation. The token is expected to be an encoded basic auth credential string which can be used by the extension to interact with its own system index. Signed-off-by: Stephen Crawford <[email protected]> Signed-off-by: Stephen Crawford <[email protected]> Signed-off-by: Peter Nied <[email protected]> Co-authored-by: Owais Kazi <[email protected]> Co-authored-by: Peter Nied <[email protected]>
1 parent cbff21d commit 994e115

File tree

15 files changed

+119
-34
lines changed

15 files changed

+119
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1212
- Introduce new dynamic cluster setting to control slice computation for concurrent segment search ([#9107](https://github.com/opensearch-project/OpenSearch/pull/9107))
1313
- Implement on behalf of token passing for extensions ([#8679](https://github.com/opensearch-project/OpenSearch/pull/8679))
1414
- Implement Visitor Design pattern in QueryBuilder to enable the capability to traverse through the complex QueryBuilder tree. ([#10110](https://github.com/opensearch-project/OpenSearch/pull/10110))
15+
- Provide service accounts tokens to extensions ([#9618](https://github.com/opensearch-project/OpenSearch/pull/9618))
1516

1617
### Dependencies
1718
- Bump `log4j-core` from 2.18.0 to 2.19.0

plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ public Optional<AuthenticationToken> translateAuthToken(org.opensearch.identity.
6464
public AuthToken issueOnBehalfOfToken(Subject subject, OnBehalfOfClaims claims) {
6565

6666
String password = generatePassword();
67-
final byte[] rawEncoded = Base64.getEncoder().encode((claims.getAudience() + ":" + password).getBytes(UTF_8)); // Make a new
68-
// ShiroSubject w/
69-
// audience as name
67+
// Make a new ShiroSubject audience as name
68+
final byte[] rawEncoded = Base64.getUrlEncoder().encode((claims.getAudience() + ":" + password).getBytes(UTF_8));
69+
7070
final String usernamePassword = new String(rawEncoded, UTF_8);
7171
final String header = "Basic " + usernamePassword;
7272
BasicAuthToken token = new BasicAuthToken(header);
@@ -75,6 +75,19 @@ public AuthToken issueOnBehalfOfToken(Subject subject, OnBehalfOfClaims claims)
7575
return token;
7676
}
7777

78+
@Override
79+
public AuthToken issueServiceAccountToken(String audience) {
80+
81+
String password = generatePassword();
82+
final byte[] rawEncoded = Base64.getUrlEncoder().withoutPadding().encode((audience + ":" + password).getBytes(UTF_8)); // Make a new
83+
final String usernamePassword = new String(rawEncoded, UTF_8);
84+
final String header = "Basic " + usernamePassword;
85+
86+
BasicAuthToken token = new BasicAuthToken(header);
87+
shiroTokenPasswordMap.put(token, password);
88+
return token;
89+
}
90+
7891
@Override
7992
public Subject authenticateToken(AuthToken authToken) {
8093
return new NoopSubject();

plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,18 @@ public void testTokenNoopIssuance() {
167167
Subject subject = new NoopSubject();
168168
AuthToken token = tokenManager.issueOnBehalfOfToken(subject, claims);
169169
assertTrue(token instanceof AuthToken);
170+
AuthToken serviceAccountToken = tokenManager.issueServiceAccountToken("test");
171+
assertTrue(serviceAccountToken instanceof AuthToken);
172+
assertEquals(serviceAccountToken.asAuthHeaderValue(), "noopToken");
170173
}
171174

175+
public void testShouldSucceedIssueServiceAccountToken() {
176+
String audience = "testExtensionName";
177+
BasicAuthToken authToken = (BasicAuthToken) shiroAuthTokenHandler.issueServiceAccountToken(audience);
178+
assertTrue(authToken instanceof BasicAuthToken);
179+
UsernamePasswordToken translatedToken = (UsernamePasswordToken) shiroAuthTokenHandler.translateAuthToken(authToken).get();
180+
assertEquals(authToken.getPassword(), new String(translatedToken.getPassword()));
181+
assertTrue(shiroAuthTokenHandler.getShiroTokenPasswordMap().containsKey(authToken));
182+
assertEquals(shiroAuthTokenHandler.getShiroTokenPasswordMap().get(authToken), new String(translatedToken.getPassword()));
183+
}
172184
}

server/src/main/java/org/opensearch/discovery/InitializeExtensionRequest.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,27 @@
2525
public class InitializeExtensionRequest extends TransportRequest {
2626
private final DiscoveryNode sourceNode;
2727
private final DiscoveryExtensionNode extension;
28+
private final String serviceAccountHeader;
2829

29-
public InitializeExtensionRequest(DiscoveryNode sourceNode, DiscoveryExtensionNode extension) {
30+
public InitializeExtensionRequest(DiscoveryNode sourceNode, DiscoveryExtensionNode extension, String serviceAccountHeader) {
3031
this.sourceNode = sourceNode;
3132
this.extension = extension;
33+
this.serviceAccountHeader = serviceAccountHeader;
3234
}
3335

3436
public InitializeExtensionRequest(StreamInput in) throws IOException {
3537
super(in);
3638
sourceNode = new DiscoveryNode(in);
3739
extension = new DiscoveryExtensionNode(in);
40+
serviceAccountHeader = in.readString();
3841
}
3942

4043
@Override
4144
public void writeTo(StreamOutput out) throws IOException {
4245
super.writeTo(out);
4346
sourceNode.writeTo(out);
4447
extension.writeTo(out);
48+
out.writeString(serviceAccountHeader);
4549
}
4650

4751
public DiscoveryNode getSourceNode() {
@@ -52,6 +56,10 @@ public DiscoveryExtensionNode getExtension() {
5256
return extension;
5357
}
5458

59+
public String getServiceAccountHeader() {
60+
return serviceAccountHeader;
61+
}
62+
5563
@Override
5664
public String toString() {
5765
return "InitializeExtensionsRequest{" + "sourceNode=" + sourceNode + ", extension=" + extension + '}';
@@ -62,7 +70,9 @@ public boolean equals(Object o) {
6270
if (this == o) return true;
6371
if (o == null || getClass() != o.getClass()) return false;
6472
InitializeExtensionRequest that = (InitializeExtensionRequest) o;
65-
return Objects.equals(sourceNode, that.sourceNode) && Objects.equals(extension, that.extension);
73+
return Objects.equals(sourceNode, that.sourceNode)
74+
&& Objects.equals(extension, that.extension)
75+
&& Objects.equals(serviceAccountHeader, that.getServiceAccountHeader());
6676
}
6777

6878
@Override

server/src/main/java/org/opensearch/extensions/ExtensionsManager.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.opensearch.extensions.settings.CustomSettingsRequestHandler;
4242
import org.opensearch.extensions.settings.RegisterCustomSettingsRequest;
4343
import org.opensearch.identity.IdentityService;
44+
import org.opensearch.identity.tokens.AuthToken;
4445
import org.opensearch.threadpool.ThreadPool;
4546
import org.opensearch.transport.ConnectTransportException;
4647
import org.opensearch.transport.TransportException;
@@ -101,14 +102,15 @@ public static enum OpenSearchRequestType {
101102
private Settings environmentSettings;
102103
private AddSettingsUpdateConsumerRequestHandler addSettingsUpdateConsumerRequestHandler;
103104
private NodeClient client;
105+
private IdentityService identityService;
104106

105107
/**
106108
* Instantiate a new ExtensionsManager object to handle requests and responses from extensions. This is called during Node bootstrap.
107109
*
108110
* @param additionalSettings Additional settings to read in from extension initialization request
109111
* @throws IOException If the extensions discovery file is not properly retrieved.
110112
*/
111-
public ExtensionsManager(Set<Setting<?>> additionalSettings) throws IOException {
113+
public ExtensionsManager(Set<Setting<?>> additionalSettings, IdentityService identityService) throws IOException {
112114
logger.info("ExtensionsManager initialized");
113115
this.initializedExtensions = new HashMap<String, DiscoveryExtensionNode>();
114116
this.extensionIdMap = new HashMap<String, DiscoveryExtensionNode>();
@@ -123,6 +125,7 @@ public ExtensionsManager(Set<Setting<?>> additionalSettings) throws IOException
123125
}
124126
this.client = null;
125127
this.extensionTransportActionsHandler = null;
128+
this.identityService = identityService;
126129
}
127130

128131
/**
@@ -400,7 +403,7 @@ protected void doRun() throws Exception {
400403
transportService.sendRequest(
401404
extension,
402405
REQUEST_EXTENSION_ACTION_NAME,
403-
new InitializeExtensionRequest(transportService.getLocalNode(), extension),
406+
new InitializeExtensionRequest(transportService.getLocalNode(), extension, issueServiceAccount(extension)),
404407
initializeExtensionResponseHandler
405408
);
406409
}
@@ -443,6 +446,15 @@ TransportResponse handleExtensionRequest(ExtensionRequest extensionRequest) thro
443446
}
444447
}
445448

449+
/**
450+
* A helper method called during initialization that issues a service accounts to extensions
451+
* @param extension The extension to be issued a service account
452+
*/
453+
private String issueServiceAccount(DiscoveryExtensionNode extension) {
454+
AuthToken serviceAccountToken = identityService.getTokenManager().issueServiceAccountToken(extension.getId());
455+
return serviceAccountToken.asAuthHeaderValue();
456+
}
457+
446458
static String getRequestExtensionActionName() {
447459
return REQUEST_EXTENSION_ACTION_NAME;
448460
}

server/src/main/java/org/opensearch/extensions/NoopExtensionsManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.opensearch.transport.TransportService;
2121

2222
import java.io.IOException;
23+
import java.util.List;
2324
import java.util.Optional;
2425
import java.util.Set;
2526

@@ -31,7 +32,7 @@
3132
public class NoopExtensionsManager extends ExtensionsManager {
3233

3334
public NoopExtensionsManager() throws IOException {
34-
super(Set.of());
35+
super(Set.of(), new IdentityService(Settings.EMPTY, List.of()));
3536
}
3637

3738
@Override

server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,20 @@ public String asAuthHeaderValue() {
3737
};
3838
}
3939

40+
/**
41+
* Issue a new Noop Token
42+
* @return a new Noop Token
43+
*/
44+
@Override
45+
public AuthToken issueServiceAccountToken(final String audience) {
46+
return new AuthToken() {
47+
@Override
48+
public String asAuthHeaderValue() {
49+
return "noopToken";
50+
}
51+
};
52+
}
53+
4054
@Override
4155
public Subject authenticateToken(AuthToken authToken) {
4256
return null;

server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public final class BasicAuthToken implements AuthToken {
2323

2424
public BasicAuthToken(final String headerValue) {
2525
final String base64Encoded = headerValue.substring(TOKEN_IDENTIFIER.length()).trim();
26-
final byte[] rawDecoded = Base64.getDecoder().decode(base64Encoded);
26+
final byte[] rawDecoded = Base64.getUrlDecoder().decode(base64Encoded);
2727
final String usernamepassword = new String(rawDecoded, StandardCharsets.UTF_8);
2828

2929
final String[] tokenParts = usernamepassword.split(":", 2);

server/src/main/java/org/opensearch/identity/tokens/TokenManager.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ public interface TokenManager {
2323
*/
2424
public AuthToken issueOnBehalfOfToken(final Subject subject, final OnBehalfOfClaims claims);
2525

26+
/**
27+
* Create a new service account token
28+
*
29+
* @param audience: A string representing the unique id of the extension for which a service account token should be generated
30+
* @return a new auth token
31+
*/
32+
public AuthToken issueServiceAccountToken(final String audience);
33+
2634
/**
2735
* Authenticates a provided authToken
2836
* @param authToken: The authToken to authenticate

server/src/main/java/org/opensearch/node/Node.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ protected Node(
495495
for (ExtensionAwarePlugin extAwarePlugin : extensionAwarePlugins) {
496496
additionalSettings.addAll(extAwarePlugin.getExtensionSettings());
497497
}
498-
this.extensionsManager = new ExtensionsManager(additionalSettings);
498+
this.extensionsManager = new ExtensionsManager(additionalSettings, identityService);
499499
} else {
500500
this.extensionsManager = new NoopExtensionsManager();
501501
}

server/src/test/java/org/opensearch/action/ActionModuleTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public void testSetupRestHandlerContainsKnownBuiltin() throws IOException {
143143
usageService,
144144
null,
145145
new IdentityService(Settings.EMPTY, new ArrayList<>()),
146-
new ExtensionsManager(Set.of())
146+
new ExtensionsManager(Set.of(), new IdentityService(Settings.EMPTY, List.of()))
147147
);
148148
actionModule.initRestHandlers(null);
149149
// At this point the easiest way to confirm that a handler is loaded is to try to register another one on top of it and to fail

server/src/test/java/org/opensearch/discovery/InitializeExtensionRequestTests.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class InitializeExtensionRequestTests extends OpenSearchTestCase {
2727
public void testInitializeExtensionRequest() throws Exception {
2828
String expectedUniqueId = "test uniqueid";
2929
Version expectedVersion = Version.fromString("2.0.0");
30+
String expectedServiceAccountHeader = "test";
3031
ExtensionDependency expectedDependency = new ExtensionDependency(expectedUniqueId, expectedVersion);
3132
DiscoveryExtensionNode expectedExtensionNode = new DiscoveryExtensionNode(
3233
"firstExtension",
@@ -46,9 +47,14 @@ public void testInitializeExtensionRequest() throws Exception {
4647
Version.CURRENT
4748
);
4849

49-
InitializeExtensionRequest initializeExtensionRequest = new InitializeExtensionRequest(expectedSourceNode, expectedExtensionNode);
50+
InitializeExtensionRequest initializeExtensionRequest = new InitializeExtensionRequest(
51+
expectedSourceNode,
52+
expectedExtensionNode,
53+
expectedServiceAccountHeader
54+
);
5055
assertEquals(expectedExtensionNode, initializeExtensionRequest.getExtension());
5156
assertEquals(expectedSourceNode, initializeExtensionRequest.getSourceNode());
57+
assertEquals(expectedServiceAccountHeader, initializeExtensionRequest.getServiceAccountHeader());
5258

5359
try (BytesStreamOutput out = new BytesStreamOutput()) {
5460
initializeExtensionRequest.writeTo(out);
@@ -58,6 +64,7 @@ public void testInitializeExtensionRequest() throws Exception {
5864

5965
assertEquals(expectedExtensionNode, initializeExtensionRequest.getExtension());
6066
assertEquals(expectedSourceNode, initializeExtensionRequest.getSourceNode());
67+
assertEquals(expectedServiceAccountHeader, initializeExtensionRequest.getServiceAccountHeader());
6168
}
6269
}
6370
}

0 commit comments

Comments
 (0)