mirror of https://github.com/grpc/grpc-java.git
api: Add ServerCredentials
This commit is contained in:
parent
76ad953c36
commit
60319dad2d
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a list of {@link ServerCredentials}, where any one may be used. The credentials are in
|
||||||
|
* preference order.
|
||||||
|
*/
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7621")
|
||||||
|
public final class ChoiceServerCredentials extends ServerCredentials {
|
||||||
|
/**
|
||||||
|
* Constructs with the provided {@code creds} as options, with preferred credentials first.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if no creds are provided
|
||||||
|
*/
|
||||||
|
public static ServerCredentials create(ServerCredentials... creds) {
|
||||||
|
if (creds.length == 0) {
|
||||||
|
throw new IllegalArgumentException("At least one credential is required");
|
||||||
|
}
|
||||||
|
return new ChoiceServerCredentials(creds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<ServerCredentials> creds;
|
||||||
|
|
||||||
|
private ChoiceServerCredentials(ServerCredentials... creds) {
|
||||||
|
for (ServerCredentials cred : creds) {
|
||||||
|
if (cred == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.creds = Collections.unmodifiableList(new ArrayList<>(Arrays.asList(creds)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Non-empty list of credentials, in preference order. */
|
||||||
|
public List<ServerCredentials> getCredentialsList() {
|
||||||
|
return creds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -124,4 +124,15 @@ public final class Grpc {
|
||||||
throw new IllegalArgumentException("Invalid host or port: " + host + " " + port, ex);
|
throw new IllegalArgumentException("Invalid host or port: " + host + " " + port, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static factory for creating a new ServerBuilder.
|
||||||
|
*
|
||||||
|
* @param port the port to listen on
|
||||||
|
* @param creds the server identity
|
||||||
|
*/
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7621")
|
||||||
|
public static ServerBuilder<?> newServerBuilderForPort(int port, ServerCredentials creds) {
|
||||||
|
return ServerRegistry.getDefaultRegistry().newServerBuilderForPort(port, creds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc;
|
||||||
|
|
||||||
|
/** No server identity or encryption is to be used. */
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7621")
|
||||||
|
public final class InsecureServerCredentials extends ServerCredentials {
|
||||||
|
public static ServerCredentials create() {
|
||||||
|
return new InsecureServerCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
private InsecureServerCredentials() {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a security configuration to be used for servers. There is no generic mechanism for
|
||||||
|
* processing arbitrary {@code ServerCredentials}; the consumer of the credential (the server)
|
||||||
|
* must support each implementation explicitly and separately. Consumers are not required to support
|
||||||
|
* all types or even all possible configurations for types that are partially supported, but they
|
||||||
|
* <em>must</em> at least fully support {@link ChoiceServerCredentials}.
|
||||||
|
*
|
||||||
|
* <p>A {@code ServerCredential} provides server identity. They can also influence types of
|
||||||
|
* encryption used and similar security configuration.
|
||||||
|
*
|
||||||
|
* <p>The concrete credential type should not be relevant to most users of the API and may be an
|
||||||
|
* implementation decision. Users should generally use the {@code ServerCredentials} type for
|
||||||
|
* variables instead of the concrete type. Freshly-constructed credentials should be returned as
|
||||||
|
* {@code ServerCredentials} instead of a concrete type to encourage this pattern. Concrete types
|
||||||
|
* would only be used after {@code instanceof} checks (which must consider
|
||||||
|
* {@code ChoiceServerCredentials}!).
|
||||||
|
*/
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7621")
|
||||||
|
public abstract class ServerCredentials {}
|
||||||
|
|
@ -16,9 +16,8 @@
|
||||||
|
|
||||||
package io.grpc;
|
package io.grpc;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
import io.grpc.ManagedChannelProvider.ProviderNotFoundException;
|
import io.grpc.ManagedChannelProvider.ProviderNotFoundException;
|
||||||
import io.grpc.ServiceProviders.PriorityAccessor;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provider of servers for transport agnostic consumption.
|
* Provider of servers for transport agnostic consumption.
|
||||||
|
|
@ -34,28 +33,13 @@ import java.util.Collections;
|
||||||
*/
|
*/
|
||||||
@Internal
|
@Internal
|
||||||
public abstract class ServerProvider {
|
public abstract class ServerProvider {
|
||||||
private static final ServerProvider provider = ServiceProviders.load(
|
|
||||||
ServerProvider.class,
|
|
||||||
Collections.<Class<?>>emptyList(),
|
|
||||||
ServerProvider.class.getClassLoader(),
|
|
||||||
new PriorityAccessor<ServerProvider>() {
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable(ServerProvider provider) {
|
|
||||||
return provider.isAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority(ServerProvider provider) {
|
|
||||||
return provider.priority();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ClassLoader-wide default server.
|
* Returns the ClassLoader-wide default server.
|
||||||
*
|
*
|
||||||
* @throws ProviderNotFoundException if no provider is available
|
* @throws ProviderNotFoundException if no provider is available
|
||||||
*/
|
*/
|
||||||
public static ServerProvider provider() {
|
public static ServerProvider provider() {
|
||||||
|
ServerProvider provider = ServerRegistry.getDefaultRegistry().provider();
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
throw new ProviderNotFoundException("No functional server found. "
|
throw new ProviderNotFoundException("No functional server found. "
|
||||||
+ "Try adding a dependency on the grpc-netty or grpc-netty-shaded artifact");
|
+ "Try adding a dependency on the grpc-netty or grpc-netty-shaded artifact");
|
||||||
|
|
@ -81,4 +65,38 @@ public abstract class ServerProvider {
|
||||||
* Creates a new builder with the given port.
|
* Creates a new builder with the given port.
|
||||||
*/
|
*/
|
||||||
protected abstract ServerBuilder<?> builderForPort(int port);
|
protected abstract ServerBuilder<?> builderForPort(int port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new builder with the given port and credentials. Returns an error-string result if
|
||||||
|
* unable to understand the credentials.
|
||||||
|
*/
|
||||||
|
protected NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) {
|
||||||
|
return NewServerBuilderResult.error("ServerCredentials are unsupported");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class NewServerBuilderResult {
|
||||||
|
private final ServerBuilder<?> serverBuilder;
|
||||||
|
private final String error;
|
||||||
|
|
||||||
|
private NewServerBuilderResult(ServerBuilder<?> serverBuilder, String error) {
|
||||||
|
this.serverBuilder = serverBuilder;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NewServerBuilderResult serverBuilder(ServerBuilder<?> builder) {
|
||||||
|
return new NewServerBuilderResult(Preconditions.checkNotNull(builder), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NewServerBuilderResult error(String error) {
|
||||||
|
return new NewServerBuilderResult(null, Preconditions.checkNotNull(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerBuilder<?> getServerBuilder() {
|
||||||
|
return serverBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry of {@link ServerProvider}s. The {@link #getDefaultRegistry default instance} loads
|
||||||
|
* providers at runtime through the Java service provider mechanism.
|
||||||
|
*/
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7621")
|
||||||
|
@ThreadSafe
|
||||||
|
public final class ServerRegistry {
|
||||||
|
private static final Logger logger = Logger.getLogger(ServerRegistry.class.getName());
|
||||||
|
private static ServerRegistry instance;
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private final LinkedHashSet<ServerProvider> allProviders = new LinkedHashSet<>();
|
||||||
|
/** Immutable, sorted version of {@code allProviders}. Is replaced instead of mutating. */
|
||||||
|
@GuardedBy("this")
|
||||||
|
private List<ServerProvider> effectiveProviders = Collections.emptyList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a provider.
|
||||||
|
*
|
||||||
|
* <p>If the provider's {@link ServerProvider#isAvailable isAvailable()} returns
|
||||||
|
* {@code false}, this method will throw {@link IllegalArgumentException}.
|
||||||
|
*
|
||||||
|
* <p>Providers will be used in priority order. In case of ties, providers are used in
|
||||||
|
* registration order.
|
||||||
|
*/
|
||||||
|
public synchronized void register(ServerProvider provider) {
|
||||||
|
addProvider(provider);
|
||||||
|
refreshProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void addProvider(ServerProvider provider) {
|
||||||
|
Preconditions.checkArgument(provider.isAvailable(), "isAvailable() returned false");
|
||||||
|
allProviders.add(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deregisters a provider. No-op if the provider is not in the registry.
|
||||||
|
*
|
||||||
|
* @param provider the provider that was added to the register via {@link #register}.
|
||||||
|
*/
|
||||||
|
public synchronized void deregister(ServerProvider provider) {
|
||||||
|
allProviders.remove(provider);
|
||||||
|
refreshProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void refreshProviders() {
|
||||||
|
List<ServerProvider> providers = new ArrayList<>(allProviders);
|
||||||
|
// Sort descending based on priority.
|
||||||
|
// sort() must be stable, as we prefer first-registered providers
|
||||||
|
Collections.sort(providers, Collections.reverseOrder(new Comparator<ServerProvider>() {
|
||||||
|
@Override
|
||||||
|
public int compare(ServerProvider o1, ServerProvider o2) {
|
||||||
|
return o1.priority() - o2.priority();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
effectiveProviders = Collections.unmodifiableList(providers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default registry that loads providers via the Java service loader mechanism.
|
||||||
|
*/
|
||||||
|
public static synchronized ServerRegistry getDefaultRegistry() {
|
||||||
|
if (instance == null) {
|
||||||
|
List<ServerProvider> providerList = ServiceProviders.loadAll(
|
||||||
|
ServerProvider.class,
|
||||||
|
Collections.<Class<?>>emptyList(),
|
||||||
|
ServerProvider.class.getClassLoader(),
|
||||||
|
new ServerPriorityAccessor());
|
||||||
|
instance = new ServerRegistry();
|
||||||
|
for (ServerProvider provider : providerList) {
|
||||||
|
logger.fine("Service loader found " + provider);
|
||||||
|
if (provider.isAvailable()) {
|
||||||
|
instance.addProvider(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instance.refreshProviders();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns effective providers, in priority order.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
synchronized List<ServerProvider> providers() {
|
||||||
|
return effectiveProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For emulating ServerProvider.provider()
|
||||||
|
ServerProvider provider() {
|
||||||
|
List<ServerProvider> providers = providers();
|
||||||
|
return providers.isEmpty() ? null : providers.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerBuilder<?> newServerBuilderForPort(int port, ServerCredentials creds) {
|
||||||
|
List<ServerProvider> providers = providers();
|
||||||
|
if (providers.isEmpty()) {
|
||||||
|
throw new ProviderNotFoundException("No functional server found. "
|
||||||
|
+ "Try adding a dependency on the grpc-netty or grpc-netty-shaded artifact");
|
||||||
|
}
|
||||||
|
StringBuilder error = new StringBuilder();
|
||||||
|
for (ServerProvider provider : providers()) {
|
||||||
|
ServerProvider.NewServerBuilderResult result
|
||||||
|
= provider.newServerBuilderForPort(port, creds);
|
||||||
|
if (result.getServerBuilder() != null) {
|
||||||
|
return result.getServerBuilder();
|
||||||
|
}
|
||||||
|
error.append("; ");
|
||||||
|
error.append(provider.getClass().getName());
|
||||||
|
error.append(": ");
|
||||||
|
error.append(result.getError());
|
||||||
|
}
|
||||||
|
throw new ProviderNotFoundException(error.substring(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ServerPriorityAccessor
|
||||||
|
implements ServiceProviders.PriorityAccessor<ServerProvider> {
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable(ServerProvider provider) {
|
||||||
|
return provider.isAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority(ServerProvider provider) {
|
||||||
|
return provider.priority();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Thrown when no suitable {@link ServerProvider} objects can be found. */
|
||||||
|
public static final class ProviderNotFoundException extends RuntimeException {
|
||||||
|
private static final long serialVersionUID = 1;
|
||||||
|
|
||||||
|
public ProviderNotFoundException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc;
|
||||||
|
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TLS credentials, providing server identity and encryption. Consumers of this credential must
|
||||||
|
* verify they understand the configuration via the {@link #incomprehensible incomprehensible()}
|
||||||
|
* method. Unless overridden by a {@code Feature}, server identity is provided via {@link
|
||||||
|
* #getCertificateChain}, {@link #getPrivateKey}, and {@link #getPrivateKeyPassword}.
|
||||||
|
*/
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7621")
|
||||||
|
public final class TlsServerCredentials extends ServerCredentials {
|
||||||
|
/**
|
||||||
|
* Creates an instance using provided certificate chain and private key. Generally they should be
|
||||||
|
* PEM-encoded and the key is an unencrypted PKCS#8 key (file headers have "BEGIN CERTIFICATE" and
|
||||||
|
* "BEGIN PRIVATE KEY").
|
||||||
|
*/
|
||||||
|
public static ServerCredentials create(File certChain, File privateKey) throws IOException {
|
||||||
|
return newBuilder().keyManager(certChain, privateKey).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance using provided certificate chain and private key. Generally they should be
|
||||||
|
* PEM-encoded and the key is an unencrypted PKCS#8 key (file headers have "BEGIN CERTIFICATE" and
|
||||||
|
* "BEGIN PRIVATE KEY").
|
||||||
|
*
|
||||||
|
* <p>The streams will not be automatically closed.
|
||||||
|
*/
|
||||||
|
public static ServerCredentials create(
|
||||||
|
InputStream certChain, InputStream privateKey) throws IOException {
|
||||||
|
return newBuilder().keyManager(certChain, privateKey).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean fakeFeature;
|
||||||
|
private final byte[] certificateChain;
|
||||||
|
private final byte[] privateKey;
|
||||||
|
private final String privateKeyPassword;
|
||||||
|
|
||||||
|
TlsServerCredentials(Builder builder) {
|
||||||
|
fakeFeature = builder.fakeFeature;
|
||||||
|
certificateChain = builder.certificateChain;
|
||||||
|
privateKey = builder.privateKey;
|
||||||
|
privateKeyPassword = builder.privateKeyPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The certificate chain, as a new byte array. Generally should be PEM-encoded.
|
||||||
|
*/
|
||||||
|
public byte[] getCertificateChain() {
|
||||||
|
return Arrays.copyOf(certificateChain, certificateChain.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The private key, as a new byte array. Generally should be in PKCS#8 format. If encrypted,
|
||||||
|
* {@link #getPrivateKeyPassword} is the decryption key. If unencrypted, the password will be
|
||||||
|
* {@code null}.
|
||||||
|
*/
|
||||||
|
public byte[] getPrivateKey() {
|
||||||
|
return Arrays.copyOf(privateKey, privateKey.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the password to decrypt the private key, or {@code null} if unencrypted. */
|
||||||
|
public String getPrivateKeyPassword() {
|
||||||
|
return privateKeyPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an empty set if this credential can be adequately understood via
|
||||||
|
* the features listed, otherwise returns a hint of features that are lacking
|
||||||
|
* to understand the configuration to be used for manual debugging.
|
||||||
|
*
|
||||||
|
* <p>An "understood" feature does not imply the caller is able to fully
|
||||||
|
* handle the feature. It simply means the caller understands the feature
|
||||||
|
* enough to use the appropriate APIs to read the configuration. The caller
|
||||||
|
* may support just a subset of a feature, in which case the caller would
|
||||||
|
* need to look at the configuration to determine if only the supported
|
||||||
|
* subset is used.
|
||||||
|
*
|
||||||
|
* <p>This method may not be as simple as a set difference. There may be
|
||||||
|
* multiple features that can independently satisfy a piece of configuration.
|
||||||
|
* If the configuration is incomprehensible, all such features would be
|
||||||
|
* returned, even though only one may be necessary.
|
||||||
|
*
|
||||||
|
* <p>An empty set does not imply that the credentials are fully understood.
|
||||||
|
* There may be optional configuration that can be ignored if not understood.
|
||||||
|
*
|
||||||
|
* <p>Since {@code Feature} is an {@code enum}, {@code understoodFeatures}
|
||||||
|
* should generally be an {@link java.util.EnumSet}. {@code
|
||||||
|
* understoodFeatures} will not be modified.
|
||||||
|
*
|
||||||
|
* @param understoodFeatures the features understood by the caller
|
||||||
|
* @return empty set if the caller can adequately understand the configuration
|
||||||
|
*/
|
||||||
|
public Set<Feature> incomprehensible(Set<Feature> understoodFeatures) {
|
||||||
|
Set<Feature> incomprehensible = EnumSet.noneOf(Feature.class);
|
||||||
|
if (fakeFeature) {
|
||||||
|
requiredFeature(understoodFeatures, incomprehensible, Feature.FAKE);
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableSet(incomprehensible);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void requiredFeature(
|
||||||
|
Set<Feature> understoodFeatures, Set<Feature> incomprehensible, Feature feature) {
|
||||||
|
if (!understoodFeatures.contains(feature)) {
|
||||||
|
incomprehensible.add(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Features to understand TLS configuration. Additional enum values may be added in the future.
|
||||||
|
*/
|
||||||
|
public enum Feature {
|
||||||
|
/**
|
||||||
|
* A feature that no consumer should understand. It should be used for unit testing to confirm
|
||||||
|
* a call to {@link #incomprehensible incomprehensible()} is implemented properly.
|
||||||
|
*/
|
||||||
|
FAKE,
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a builder for changing default configuration. */
|
||||||
|
public static Builder newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builder for {@link TlsServerCredentials}. */
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7621")
|
||||||
|
public static final class Builder {
|
||||||
|
private boolean fakeFeature;
|
||||||
|
private byte[] certificateChain;
|
||||||
|
private byte[] privateKey;
|
||||||
|
private String privateKeyPassword;
|
||||||
|
|
||||||
|
private Builder() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires {@link Feature#FAKE} to be understood. For use in testing consumers of this
|
||||||
|
* credential.
|
||||||
|
*/
|
||||||
|
public Builder requireFakeFeature() {
|
||||||
|
fakeFeature = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance using provided certificate chain and private key. Generally they should
|
||||||
|
* be PEM-encoded and the key is an unencrypted PKCS#8 key (file headers have "BEGIN
|
||||||
|
* CERTIFICATE" and "BEGIN PRIVATE KEY").
|
||||||
|
*/
|
||||||
|
public Builder keyManager(File certChain, File privateKey) throws IOException {
|
||||||
|
return keyManager(certChain, privateKey, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance using provided certificate chain and possibly-encrypted private key.
|
||||||
|
* Generally they should be PEM-encoded and the key is a PKCS#8 key. If the private key is
|
||||||
|
* unencrypted, then password must be {@code null}.
|
||||||
|
*/
|
||||||
|
public Builder keyManager(File certChain, File privateKey, String privateKeyPassword)
|
||||||
|
throws IOException {
|
||||||
|
InputStream certChainIs = new FileInputStream(certChain);
|
||||||
|
try {
|
||||||
|
InputStream privateKeyIs = new FileInputStream(privateKey);
|
||||||
|
try {
|
||||||
|
return keyManager(certChainIs, privateKeyIs, privateKeyPassword);
|
||||||
|
} finally {
|
||||||
|
privateKeyIs.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
certChainIs.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance using provided certificate chain and private key. Generally they should
|
||||||
|
* be PEM-encoded and the key is an unencrypted PKCS#8 key (file headers have "BEGIN
|
||||||
|
* CERTIFICATE" and "BEGIN PRIVATE KEY").
|
||||||
|
*/
|
||||||
|
public Builder keyManager(InputStream certChain, InputStream privateKey) throws IOException {
|
||||||
|
return keyManager(certChain, privateKey, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance using provided certificate chain and possibly-encrypted private key.
|
||||||
|
* Generally they should be PEM-encoded and the key is a PKCS#8 key. If the private key is
|
||||||
|
* unencrypted, then password must be {@code null}.
|
||||||
|
*/
|
||||||
|
public Builder keyManager(
|
||||||
|
InputStream certChain, InputStream privateKey, String privateKeyPassword)
|
||||||
|
throws IOException {
|
||||||
|
byte[] certChainBytes = ByteStreams.toByteArray(certChain);
|
||||||
|
byte[] privateKeyBytes = ByteStreams.toByteArray(privateKey);
|
||||||
|
this.certificateChain = certChainBytes;
|
||||||
|
this.privateKey = privateKeyBytes;
|
||||||
|
this.privateKeyPassword = privateKeyPassword;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Construct the credentials. */
|
||||||
|
public ServerCredentials build() {
|
||||||
|
if (certificateChain == null) {
|
||||||
|
throw new IllegalStateException("A key manager is required");
|
||||||
|
}
|
||||||
|
return new TlsServerCredentials(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ public class ManagedChannelRegistryTest {
|
||||||
private ChannelCredentials creds = new ChannelCredentials() {};
|
private ChannelCredentials creds = new ChannelCredentials() {};
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void register_unavilableProviderThrows() {
|
public void register_unavailableProviderThrows() {
|
||||||
ManagedChannelRegistry reg = new ManagedChannelRegistry();
|
ManagedChannelRegistry reg = new ManagedChannelRegistry();
|
||||||
try {
|
try {
|
||||||
reg.register(new BaseProvider(false, 5));
|
reg.register(new BaseProvider(false, 5));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
/** Unit tests for {@link ServerRegistry}. */
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class ServerRegistryTest {
|
||||||
|
private int port = 123;
|
||||||
|
private ServerCredentials creds = new ServerCredentials() {};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void register_unavailableProviderThrows() {
|
||||||
|
ServerRegistry reg = new ServerRegistry();
|
||||||
|
try {
|
||||||
|
reg.register(new BaseProvider(false, 5));
|
||||||
|
fail("Should throw");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
assertThat(e).hasMessageThat().contains("isAvailable() returned false");
|
||||||
|
}
|
||||||
|
assertThat(reg.providers()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deregister() {
|
||||||
|
ServerRegistry reg = new ServerRegistry();
|
||||||
|
ServerProvider p1 = new BaseProvider(true, 5);
|
||||||
|
ServerProvider p2 = new BaseProvider(true, 5);
|
||||||
|
ServerProvider p3 = new BaseProvider(true, 5);
|
||||||
|
reg.register(p1);
|
||||||
|
reg.register(p2);
|
||||||
|
reg.register(p3);
|
||||||
|
assertThat(reg.providers()).containsExactly(p1, p2, p3).inOrder();
|
||||||
|
reg.deregister(p2);
|
||||||
|
assertThat(reg.providers()).containsExactly(p1, p3).inOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void provider_sorted() {
|
||||||
|
ServerRegistry reg = new ServerRegistry();
|
||||||
|
ServerProvider p1 = new BaseProvider(true, 5);
|
||||||
|
ServerProvider p2 = new BaseProvider(true, 3);
|
||||||
|
ServerProvider p3 = new BaseProvider(true, 8);
|
||||||
|
ServerProvider p4 = new BaseProvider(true, 3);
|
||||||
|
ServerProvider p5 = new BaseProvider(true, 8);
|
||||||
|
reg.register(p1);
|
||||||
|
reg.register(p2);
|
||||||
|
reg.register(p3);
|
||||||
|
reg.register(p4);
|
||||||
|
reg.register(p5);
|
||||||
|
assertThat(reg.providers()).containsExactly(p3, p5, p1, p2, p4).inOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getProvider_noProvider() {
|
||||||
|
assertThat(new ServerRegistry().provider()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newServerBuilderForPort_providerReturnsError() {
|
||||||
|
final String errorString = "brisking";
|
||||||
|
class ErrorProvider extends BaseProvider {
|
||||||
|
ErrorProvider() {
|
||||||
|
super(true, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NewServerBuilderResult newServerBuilderForPort(
|
||||||
|
int passedPort, ServerCredentials passedCreds) {
|
||||||
|
assertThat(passedPort).isEqualTo(port);
|
||||||
|
assertThat(passedCreds).isSameInstanceAs(creds);
|
||||||
|
return NewServerBuilderResult.error(errorString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerRegistry registry = new ServerRegistry();
|
||||||
|
registry.register(new ErrorProvider());
|
||||||
|
try {
|
||||||
|
registry.newServerBuilderForPort(port, creds);
|
||||||
|
fail("expected exception");
|
||||||
|
} catch (ServerRegistry.ProviderNotFoundException ex) {
|
||||||
|
assertThat(ex).hasMessageThat().contains(errorString);
|
||||||
|
assertThat(ex).hasMessageThat().contains(ErrorProvider.class.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newServerBuilderForPort_providerReturnsNonNull() {
|
||||||
|
ServerRegistry registry = new ServerRegistry();
|
||||||
|
registry.register(new BaseProvider(true, 5) {
|
||||||
|
@Override
|
||||||
|
public NewServerBuilderResult newServerBuilderForPort(
|
||||||
|
int passedPort, ServerCredentials passedCreds) {
|
||||||
|
return NewServerBuilderResult.error("dodging");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
class MockServerBuilder extends ForwardingServerBuilder<MockServerBuilder> {
|
||||||
|
@Override public ServerBuilder<?> delegate() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ServerBuilder<?> mcb = new MockServerBuilder();
|
||||||
|
registry.register(new BaseProvider(true, 4) {
|
||||||
|
@Override
|
||||||
|
public NewServerBuilderResult newServerBuilderForPort(
|
||||||
|
int passedPort, ServerCredentials passedCreds) {
|
||||||
|
return NewServerBuilderResult.serverBuilder(mcb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
registry.register(new BaseProvider(true, 3) {
|
||||||
|
@Override
|
||||||
|
public NewServerBuilderResult newServerBuilderForPort(
|
||||||
|
int passedPort, ServerCredentials passedCreds) {
|
||||||
|
fail("Should not be called");
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThat(registry.newServerBuilderForPort(port, creds)).isSameInstanceAs(mcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newServerBuilderForPort_noProvider() {
|
||||||
|
ServerRegistry registry = new ServerRegistry();
|
||||||
|
try {
|
||||||
|
registry.newServerBuilderForPort(port, creds);
|
||||||
|
fail("expected exception");
|
||||||
|
} catch (ServerRegistry.ProviderNotFoundException ex) {
|
||||||
|
assertThat(ex).hasMessageThat().contains("No functional server found");
|
||||||
|
assertThat(ex).hasMessageThat().contains("grpc-netty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BaseProvider extends ServerProvider {
|
||||||
|
private final boolean isAvailable;
|
||||||
|
private final int priority;
|
||||||
|
|
||||||
|
public BaseProvider(boolean isAvailable, int priority) {
|
||||||
|
this.isAvailable = isAvailable;
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isAvailable() {
|
||||||
|
return isAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int priority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ServerBuilder<?> builderForPort(int port) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue