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

Issue and ferry Service Account Tokens and On-Behalf-Of Tokens to an Extension #94

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 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
53e8f9b
Merge branch 'main' into extension-obo
cwperks Aug 18, 2023
a0e4013
Merge branch 'main' into extension-obo
cwperks Aug 25, 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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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;
Expand All @@ -35,6 +36,7 @@
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 Down Expand Up @@ -75,6 +77,11 @@ public AuthToken issueOnBehalfOfToken(Subject subject, OnBehalfOfClaims claims)
return token;
}

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

@Override
public Subject authenticateToken(AuthToken authToken) {
return new NoopSubject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,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 @@ -664,6 +678,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 @@ -672,12 +687,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 @@ -783,6 +805,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 @@ -1020,6 +1056,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.core.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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import org.opensearch.core.transport.TransportResponse;
import org.opensearch.discovery.InitializeExtensionRequest;
import org.opensearch.discovery.InitializeExtensionResponse;
import org.opensearch.discovery.InitializeExtensionSecurityRequest;
import org.opensearch.discovery.InitializeExtensionSecurityResponse;
import org.opensearch.env.EnvironmentSettingsResponse;
import org.opensearch.extensions.ExtensionsSettings.Extension;
import org.opensearch.extensions.action.ExtensionActionRequest;
Expand All @@ -41,6 +43,7 @@
import org.opensearch.extensions.settings.CustomSettingsRequestHandler;
import org.opensearch.extensions.settings.RegisterCustomSettingsRequest;
import org.opensearch.identity.IdentityService;
import org.opensearch.identity.tokens.AuthToken;
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.transport.ConnectTransportException;
import org.opensearch.transport.TransportException;
Expand All @@ -55,6 +58,9 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

/**
Expand All @@ -70,6 +76,7 @@ public class ExtensionsManager {
public static final String REQUEST_EXTENSION_ADD_SETTINGS_UPDATE_CONSUMER = "internal:discovery/addsettingsupdateconsumer";
public static final String REQUEST_EXTENSION_UPDATE_SETTINGS = "internal:discovery/updatesettings";
public static final String REQUEST_EXTENSION_DEPENDENCY_INFORMATION = "internal:discovery/dependencyinformation";
public static final String REQUEST_EXTENSION_REGISTER_SECURITY_SETTINGS = "internal:discovery/registersecuritysettings";
public static final String REQUEST_EXTENSION_REGISTER_CUSTOM_SETTINGS = "internal:discovery/registercustomsettings";
public static final String REQUEST_EXTENSION_REGISTER_REST_ACTIONS = "internal:discovery/registerrestactions";
public static final String REQUEST_EXTENSION_REGISTER_TRANSPORT_ACTIONS = "internal:discovery/registertransportactions";
Expand Down Expand Up @@ -97,6 +104,8 @@ public static enum OpenSearchRequestType {
private CustomSettingsRequestHandler customSettingsRequestHandler;
private TransportService transportService;
private ClusterService clusterService;

private IdentityService identityService;
private final Set<Setting<?>> additionalSettings;
private Settings environmentSettings;
private AddSettingsUpdateConsumerRequestHandler addSettingsUpdateConsumerRequestHandler;
Expand Down Expand Up @@ -403,10 +412,62 @@ protected void doRun() throws Exception {
new InitializeExtensionRequest(transportService.getLocalNode(), extension),
initializeExtensionResponseHandler
);
initializeExtensionSecurity(extension);
}
});
}

private void initializeExtensionSecurity(DiscoveryExtensionNode extension) {
final CompletableFuture<InitializeExtensionSecurityResponse> inProgressFuture = new CompletableFuture<>();
final TransportResponseHandler<InitializeExtensionSecurityResponse> initializeExtensionSecurityResponseHandler =
new TransportResponseHandler<InitializeExtensionSecurityResponse>() {

@Override
public InitializeExtensionSecurityResponse read(StreamInput in) throws IOException {
return new InitializeExtensionSecurityResponse(in);
}

@Override
public void handleResponse(InitializeExtensionSecurityResponse response) {
System.out.println("Registered security settings for " + response.getName());
inProgressFuture.complete(response);
}

@Override
public void handleException(TransportException exp) {
logger.error(new ParameterizedMessage("Extension initialization failed"), exp);
inProgressFuture.completeExceptionally(exp);
}

@Override
public String executor() {
return ThreadPool.Names.GENERIC;
}
};
try {
logger.info("Sending extension request type: " + REQUEST_EXTENSION_REGISTER_SECURITY_SETTINGS);
AuthToken serviceAccountToken = identityService.getTokenManager().issueServiceAccountToken(extension.getId());
transportService.sendRequest(
extension,
REQUEST_EXTENSION_REGISTER_SECURITY_SETTINGS,
new InitializeExtensionSecurityRequest(serviceAccountToken.asAuthHeaderValue()),
initializeExtensionSecurityResponseHandler
);

inProgressFuture.orTimeout(EXTENSION_REQUEST_WAIT_TIMEOUT, TimeUnit.SECONDS).join();
} catch (CompletionException | ConnectTransportException e) {
if (e.getCause() instanceof TimeoutException || e instanceof ConnectTransportException) {
logger.info("No response from extension to request.", e);
} else if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else if (e.getCause() instanceof Error) {
throw (Error) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}

/**
* Handles an {@link ExtensionRequest}.
*
Expand Down Expand Up @@ -491,6 +552,10 @@ void setClusterService(ClusterService clusterService) {
this.clusterService = clusterService;
}

public void setIdentityService(IdentityService identityService) {
this.identityService = identityService;
}

CustomSettingsRequestHandler getCustomSettingsRequestHandler() {
return customSettingsRequestHandler;
}
Expand Down
Loading