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

Proof of concept for extension reserved indices #93

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2837200
Outline changes
stephen-crawford Jul 12, 2023
570c04b
Basic outline
stephen-crawford Jul 12, 2023
abd83c4
Basic token passing
stephen-crawford Jul 13, 2023
0b4d85d
Merge branch 'main' into tokenPassingForExtensions
stephen-crawford Jul 13, 2023
afc6b34
Minor changes
stephen-crawford Jul 13, 2023
ee602cf
Swap issue token type
stephen-crawford Jul 13, 2023
095932d
Update changelog
stephen-crawford Jul 13, 2023
676d14f
SPotless
stephen-crawford Jul 13, 2023
b224770
Merge branch 'main' into tokenPassingForExtensions
stephen-crawford Jul 13, 2023
3db805c
Add test
stephen-crawford Jul 13, 2023
4d6e2fd
Swap out authentication
stephen-crawford Jul 13, 2023
7b1757e
Merge branch 'main' into tokenPassingForExtensions
stephen-crawford Jul 13, 2023
b15df51
Swap to string, obj map
stephen-crawford Jul 13, 2023
d937ca9
Update with Craig's feedback
stephen-crawford Jul 14, 2023
af9d2d7
Add javadoc
stephen-crawford Jul 14, 2023
f6209d4
add annotation
stephen-crawford Jul 14, 2023
acd3330
Fix missing path import
stephen-crawford Jul 14, 2023
ed2d09c
Fix test
stephen-crawford Jul 14, 2023
4fdfaf7
boost coverage
stephen-crawford Jul 14, 2023
0e7f23e
Proof of concept for extension reserved indices
cwperks Jul 14, 2023
adc09c4
Merge branch 'main' into extension-service-account-tokens
cwperks Jul 17, 2023
1136cb3
Merge branch 'main' into extension-service-account-tokens
cwperks Jul 18, 2023
0b80916
Merge remote-tracking branch 'stephen/tokenPassingForExtensions' into…
cwperks Jul 18, 2023
8d1bcb0
unflatten map
cwperks Jul 20, 2023
601a3d6
Merge branch 'main' into extension-service-account-tokens
cwperks Jul 21, 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))
- Pass OnBehalfOfToken in RestSendToExtensionAction ([#8679](https://github.com/opensearch-project/OpenSearch/pull/8679))

### Dependencies
- Bump `log4j-core` from 2.18.0 to 2.19.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@

package org.opensearch.identity.shiro;

import org.opensearch.identity.Subject;
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.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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.opensearch.OpenSearchSecurityException;
import org.opensearch.common.Randomness;
import org.opensearch.identity.IdentityService;
import org.opensearch.identity.Subject;
import org.opensearch.identity.noop.NoopSubject;
import org.opensearch.identity.tokens.AuthToken;
import org.opensearch.identity.tokens.BasicAuthToken;
import org.opensearch.identity.tokens.TokenManager;
import org.passay.CharacterRule;
import org.passay.EnglishCharacterData;
import org.passay.PasswordGenerator;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.opensearch.identity.noop.NoopTokenManager.NOOP_AUTH_TOKEN;

/**
* Extracts Shiro's {@link AuthenticationToken} from different types of auth headers
Expand All @@ -51,15 +55,16 @@ public Optional<AuthenticationToken> translateAuthToken(org.opensearch.identity.
final BasicAuthToken basicAuthToken = (BasicAuthToken) authenticationToken;
return Optional.of(new UsernamePasswordToken(basicAuthToken.getUser(), basicAuthToken.getPassword()));
}

return Optional.empty();
}

@Override
public AuthToken issueToken(String audience) {
public AuthToken issueOnBehalfOfToken(Map<String, Object> claims) {

String password = generatePassword();
final byte[] rawEncoded = Base64.getEncoder().encode((audience + ":" + password).getBytes(UTF_8));
final byte[] rawEncoded = Base64.getEncoder().encode((claims.get("aud") + ":" + password).getBytes(UTF_8)); // Make a new
// ShiroSubject w/
// audience as name
final String usernamePassword = new String(rawEncoded, UTF_8);
final String header = "Basic " + usernamePassword;
BasicAuthToken token = new BasicAuthToken(header);
Expand All @@ -68,6 +73,16 @@ public AuthToken issueToken(String audience) {
return token;
}

@Override
public AuthToken issueServiceAccountToken(String extensionUniqueId) throws OpenSearchSecurityException {
return NOOP_AUTH_TOKEN;
}

@Override
public Subject authenticateToken(AuthToken authToken) {
return new NoopSubject();
}

public boolean validateToken(AuthToken token) {
if (token instanceof BasicAuthToken) {
final BasicAuthToken basicAuthToken = (BasicAuthToken) token;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

package org.opensearch.identity.shiro;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
Expand All @@ -16,6 +18,7 @@
import org.opensearch.identity.tokens.AuthToken;
import org.opensearch.identity.tokens.BasicAuthToken;
import org.opensearch.identity.tokens.BearerAuthToken;
import org.opensearch.identity.tokens.StandardTokenClaims;
import org.opensearch.test.OpenSearchTestCase;
import org.passay.CharacterCharacteristicsRule;
import org.passay.CharacterRule;
Expand All @@ -31,12 +34,10 @@
public class AuthTokenHandlerTests extends OpenSearchTestCase {

private ShiroTokenManager shiroAuthTokenHandler;
private NoopTokenManager noopTokenManager;

@Before
public void testSetup() {
shiroAuthTokenHandler = new ShiroTokenManager();
noopTokenManager = new NoopTokenManager();
}

public void testShouldExtractBasicAuthTokenSuccessfully() {
Expand Down Expand Up @@ -144,4 +145,30 @@ public void testGeneratedPasswordContents() {
validator.validate(data);
}

public void testIssueOnBehalfOfTokenFromClaims() {
Map<String, Object> claims = new HashMap<>();
claims.put("aud", "test");
BasicAuthToken authToken = (BasicAuthToken) shiroAuthTokenHandler.issueOnBehalfOfToken(claims);
assertTrue(authToken instanceof BasicAuthToken);
UsernamePasswordToken translatedToken = (UsernamePasswordToken) shiroAuthTokenHandler.translateAuthToken(authToken).get();
assertEquals(authToken.getPassword(), new String(translatedToken.getPassword()));
assertTrue(shiroAuthTokenHandler.getShiroTokenPasswordMap().containsKey(authToken));
assertEquals(shiroAuthTokenHandler.getShiroTokenPasswordMap().get(authToken), new String(translatedToken.getPassword()));
}

public void testTokenNoopIssuance() {
NoopTokenManager tokenManager = new NoopTokenManager();
AuthToken token = tokenManager.issueOnBehalfOfToken(Map.of("test", "test"));
assertTrue(token instanceof AuthToken);
}

public void testStandardTokenClaims() {
assertEquals(StandardTokenClaims.AUDIENCE.getName(), "aud");
assertEquals(StandardTokenClaims.ISSUED_AT.getName(), "iat");
assertEquals(StandardTokenClaims.ISSUER.getName(), "iss");
assertEquals(StandardTokenClaims.EXPIRATION_TIME.getName(), "exp");
assertEquals(StandardTokenClaims.JWT_ID.getName(), "jti");
assertEquals(StandardTokenClaims.NOT_BEFORE.getName(), "nbf");
assertEquals(StandardTokenClaims.SUBJECT.getName(), "sub");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,20 @@ public String get(String setting, String defaultValue) {
return retVal == null ? defaultValue : retVal;
}

/**
* Returns a setting value based on the setting key.
*/
public Settings getNestedSettings(String key) {
return (Settings) settings.get(key);
}

/**
* Returns a setting value based on the setting key.
*/
public List<Settings> getNestedListOfSettings(String key) {
return (List<Settings>) settings.get(key);
}

/**
* Returns the setting value (as float) associated with the setting key. If it does not exists,
* returns the default value provided.
Expand Down Expand Up @@ -663,6 +677,7 @@ private static void fromXContent(XContentParser parser, StringBuilder keyBuilder
fromXContent(parser, keyBuilder, builder, allowNullValues);
} else if (parser.currentToken() == XContentParser.Token.START_ARRAY) {
List<String> list = new ArrayList<>();
List<Object> listOfObjects = new ArrayList<>();
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
if (parser.currentToken() == XContentParser.Token.VALUE_STRING) {
list.add(parser.text());
Expand All @@ -671,12 +686,19 @@ private static void fromXContent(XContentParser parser, StringBuilder keyBuilder
} else if (parser.currentToken() == XContentParser.Token.VALUE_BOOLEAN) {
list.add(String.valueOf(parser.text()));
} else {
throw new IllegalStateException("only value lists are allowed in serialized settings");
listOfObjects.add(fromXContent(parser, true, false));
// throw new IllegalStateException("only value lists are allowed in serialized settings");
}
}
String key = keyBuilder.toString();
validateValue(key, list, parser, allowNullValues);
builder.putList(key, list);
if (!listOfObjects.isEmpty()) {
builder.putListOfObjects(key, listOfObjects);
}
if (!list.isEmpty() && !listOfObjects.isEmpty()) {
throw new IllegalStateException("list cannot contain both values and objects");
}
} else if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
String key = keyBuilder.toString();
validateValue(key, null, parser, allowNullValues);
Expand Down Expand Up @@ -782,6 +804,20 @@ public String get(String key) {
return Settings.toString(map.get(key));
}

/**
* Returns a setting value based on the setting key.
*/
public Settings getNestedSettings(String key) {
return (Settings) map.get(key);
}

/**
* Returns a setting value based on the setting key.
*/
public List<Settings> getNestedListOfSettings(String key) {
return (List<Settings>) map.get(key);
}

/** Return the current secure settings, or {@code null} if none have been set. */
public SecureSettings getSecureSettings() {
return secureSettings.get();
Expand Down Expand Up @@ -1019,6 +1055,19 @@ public Builder putList(String setting, List<String> values) {
return this;
}

/**
* Sets the setting with the provided setting key and a list of values.
*
* @param setting The setting key
* @param values The values
* @return The builder
*/
public Builder putListOfObjects(String setting, List<Object> values) {
remove(setting);
map.put(setting, new ArrayList<>(values));
return this;
}

/**
* Sets all the provided settings including secure settings
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.discovery;

import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.common.io.stream.StreamOutput;
import org.opensearch.transport.TransportRequest;

import java.io.IOException;
import java.util.Objects;

/**
* InitializeExtensionRequest to initialize plugin
*
* @opensearch.internal
*/
public class InitializeExtensionSecurityRequest extends TransportRequest {

private final String serviceAccountToken;

public InitializeExtensionSecurityRequest(String serviceAccountToken) {
this.serviceAccountToken = serviceAccountToken;
}

public InitializeExtensionSecurityRequest(StreamInput in) throws IOException {
super(in);
serviceAccountToken = in.readString();
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(serviceAccountToken);
}

public String getServiceAccountToken() {
return serviceAccountToken;
}

@Override
public String toString() {
return "InitializeExtensionsRequest{" + "serviceAccountToken= " + serviceAccountToken + "}";
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InitializeExtensionSecurityRequest that = (InitializeExtensionSecurityRequest) o;
return Objects.equals(serviceAccountToken, that.serviceAccountToken);
}

@Override
public int hashCode() {
return Objects.hash(serviceAccountToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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.
*/

/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.discovery;

import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.common.io.stream.StreamOutput;
import org.opensearch.transport.TransportResponse;

import java.io.IOException;
import java.util.Objects;

/**
* PluginResponse to intialize plugin
*
* @opensearch.internal
*/
public class InitializeExtensionSecurityResponse extends TransportResponse {
private String name;

public InitializeExtensionSecurityResponse(String name) {
this.name = name;
}

public InitializeExtensionSecurityResponse(StreamInput in) throws IOException {
name = in.readString();
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(name);
}

/**
* @return the node that is currently leading, according to the responding node.
*/

public String getName() {
return this.name;
}

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

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

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