Skip to content

Commit

Permalink
Issue 46: Pravega CLI to work in environments with security enabled
Browse files Browse the repository at this point in the history
- Making changes to the CLI to allow for functioning in TLS settings.
- Changing the config and code to make TLS support togglable.

Signed-off-by: anirudhkovuru <[email protected]>
  • Loading branch information
Ravi Sharda authored Sep 21, 2020
2 parents 09188a4 + 232118e commit 000e440
Show file tree
Hide file tree
Showing 21 changed files with 772 additions and 20 deletions.
19 changes: 13 additions & 6 deletions pravega-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,20 @@ You will se an output related to the default configuration parameters available
(you may want to change this file according to your setting):
```
Pravega CLI.
Initial configuration:
pravegaservice.zkURL=localhost:2181
bookkeeper.bkLedgerPath=/pravega/pravega-cluster/bookkeeper/ledgers
pravegaservice.containerCount=4
cli.controllerRestUri=http://localhost:9091
pravegaservice.clusterName=pravega-cluster
cli.tlsEnabled=false
cli.controllerRestUri=localhost:9091
cli.authEnabled=false
bookkeeper.bkLedgerPath=/pravega/pravega/bookkeeper/ledgers
cli.security.tls.trustStore.location=./conf/client.truststore.jks
pravegaservice.zkURL=localhost:4000
pravegaservice.clusterName=pravega
cli.metadataBackend=segmentstore
cli.controllerGrpcUri=localhost:9090
cli.userName=admin
cli.password=1111_aaaa
pravegaservice.containerCount=4
```
From that point onwards, you can check the available commands typing `help`:
```
Expand Down
2 changes: 2 additions & 0 deletions pravega-cli/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ dependencies {
compile group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: javaxwsrsApiVersion
compile group: 'com.googlecode.json-simple', name: 'json-simple', version: jsonSimpleVersion
compile group: 'com.google.code.gson', name: 'gson', version: gsonVersion
testCompile group: 'io.pravega', name: 'pravega-standalone', version: pravegaVersion
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.apache.bookkeeper', name: 'bookkeeper-common', version: bookKeeperVersion, classifier: 'tests'
testCompile group: 'org.apache.bookkeeper', name: 'bookkeeper-server', version: bookKeeperVersion, classifier: 'tests'
Expand Down Expand Up @@ -103,6 +104,7 @@ distributions {
into('conf') {
from(project.file('src/main/resources/logback.xml'))
from(project.file('src/main/resources/config.properties'))
from(project.file('src/main/resources/client.truststore.jks'))
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions pravega-cli/gradle/rat.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ rat {
'**/*.lock',
'**/*.json',
'**/config/*jks*',
'**/resources/*jks*',
'**/resources/*.crt',
'**/resources/*.key',
'CODE_OF_CONDUCT.md'
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,28 @@
import io.pravega.controller.server.rest.generated.api.JacksonJsonProvider;
import io.pravega.tools.pravegacli.commands.Command;
import io.pravega.tools.pravegacli.commands.CommandArgs;

import javax.net.ssl.HostnameVerifier;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;

import io.pravega.tools.pravegacli.commands.utils.ControllerHostnameVerifier;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import static javax.ws.rs.core.Response.Status.OK;
import static javax.ws.rs.core.Response.Status.UNAUTHORIZED;

Expand Down Expand Up @@ -49,7 +61,35 @@ protected ControllerCommand.Context createContext() {
ClientConfig clientConfig = new ClientConfig();
clientConfig.register(JacksonJsonProvider.class);
clientConfig.property("sun.net.http.allowRestrictedHeaders", "true");
Client client = ClientBuilder.newClient(clientConfig);

Client client;

// If tls parameters are configured, set them in client
if (getCLIControllerConfig().isTlsEnabled()) {
KeyStore ks = null;
try {
ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(new File(getCLIControllerConfig().getTruststore())), null);
} catch (KeyStoreException e) {
output("The keystore file is invalid, the keystore type is not supported: " + e.toString());
} catch (IOException e) {
output("The keystore file is invalid, check if the file exists: " + e.toString());
} catch (NoSuchAlgorithmException e) {
output("The keystore file is invalid, the keystore file might be in the wrong format: " + e.toString());
} catch (CertificateException e) {
output("The keystore file is invalid, check if the certificates are valid: " + e.toString());
}

HostnameVerifier controllerHostnameVerifier = new ControllerHostnameVerifier();
client = ClientBuilder.newBuilder()
.withConfig(clientConfig)
.trustStore(ks)
.hostnameVerifier(controllerHostnameVerifier)
.build();
} else {
client = ClientBuilder.newClient(clientConfig);
}

// If authorization parameters are configured, set them in the client.
if (getCLIControllerConfig().isAuthEnabled()) {
HttpAuthenticationFeature auth = HttpAuthenticationFeature.basic(getCLIControllerConfig().getUserName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
package io.pravega.tools.pravegacli.commands.controller;

import com.google.common.annotations.VisibleForTesting;
import io.pravega.client.ClientConfig;
import io.pravega.client.connection.impl.ConnectionPool;
import io.pravega.client.connection.impl.ConnectionPoolImpl;
Expand All @@ -29,9 +30,7 @@
import io.pravega.tools.pravegacli.commands.CommandArgs;
import io.pravega.tools.pravegacli.commands.utils.CLIControllerConfig;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;

Expand Down Expand Up @@ -71,7 +70,12 @@ public void execute() {
store = StreamStoreFactory.createZKStore(zkClient, executor);
} else {
segmentHelper = instantiateSegmentHelper(zkClient);
GrpcAuthHelper authHelper = GrpcAuthHelper.getDisabledAuthHelper();
GrpcAuthHelper authHelper;
if (getCLIControllerConfig().isAuthEnabled()) {
authHelper = new GrpcAuthHelper(true, "secret", 300);
} else {
authHelper = GrpcAuthHelper.getDisabledAuthHelper();
}
store = StreamStoreFactory.createPravegaTablesStore(segmentHelper, authHelper, zkClient, executor);
}

Expand Down Expand Up @@ -112,6 +116,7 @@ public void execute() {
}
} catch (Exception e) {
System.err.println("Exception accessing the metadata store: " + e.getMessage());
e.printStackTrace();
}
}

Expand All @@ -121,7 +126,8 @@ public static CommandDescriptor descriptor() {
new ArgDescriptor("stream-name", "Name of the Stream to describe."));
}

private SegmentHelper instantiateSegmentHelper(CuratorFramework zkClient) {
@VisibleForTesting
protected SegmentHelper instantiateSegmentHelper(CuratorFramework zkClient) {
HostMonitorConfig hostMonitorConfig = HostMonitorConfigImpl.builder()
.hostMonitorEnabled(true)
.hostMonitorMinRebalanceInterval(Config.CLUSTER_MIN_REBALANCE_INTERVAL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
import io.pravega.common.util.TypedProperties;
import lombok.Getter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
* Configuration for CLI client, specially related to the Controller service in Pravega.
*/
Expand All @@ -24,13 +29,19 @@ public enum MetadataBackends {
SEGMENTSTORE, ZOOKEEPER
}

private static final Property<String> CONTROLLER_REST_URI = Property.named("controllerRestUri", "http://localhost:9091");
private static final Property<String> CONTROLLER_GRPC_URI = Property.named("controllerGrpcUri", "tcp://localhost:9090");
private static final Property<String> CONTROLLER_REST_URI = Property.named("controllerRestUri", "localhost:9091");
private static final Property<String> CONTROLLER_GRPC_URI = Property.named("controllerGrpcUri", "localhost:9090");

private static final Property<String> SEGMENT_STORE_URI = Property.named("segmentStore.uri", "127.0.1.1:6000");

private static final Property<Boolean> AUTH_ENABLED = Property.named("authEnabled", false);
private static final Property<String> CONTROLLER_USER_NAME = Property.named("userName", "");
private static final Property<String> CONTROLLER_PASSWORD = Property.named("password", "");
private static final Property<String> METADATA_BACKEND = Property.named("metadataBackend", MetadataBackends.SEGMENTSTORE.name());

private static final Property<Boolean> TLS_ENABLED = Property.named("tlsEnabled", false);
private static final Property<String> TRUSTSTORE_JKS = Property.named("security.tls.trustStore.location", "");

private static final String COMPONENT_CODE = "cli";

/**
Expand All @@ -45,12 +56,24 @@ public enum MetadataBackends {
@Getter
private final String controllerGrpcURI;

/**
* The Segment Store URI.
*/
@Getter
private final List<String> segmentStoreURI;

/**
* Defines whether or not to use authentication in Controller requests.
*/
@Getter
private final boolean authEnabled;

/**
* Defines whether or not to use tls in Controller requests.
*/
@Getter
private final boolean tlsEnabled;

/**
* User name if authentication is configured in the Controller.
*/
Expand All @@ -69,13 +92,22 @@ public enum MetadataBackends {
@Getter
private final String metadataBackend;

/**
* Truststore if tls is configured in the Controller.
*/
@Getter
private final String truststore;

private CLIControllerConfig(TypedProperties properties) throws ConfigurationException {
this.controllerRestURI = properties.get(CONTROLLER_REST_URI);
this.controllerGrpcURI = properties.get(CONTROLLER_GRPC_URI);
this.tlsEnabled = properties.getBoolean(TLS_ENABLED);
this.controllerRestURI = (this.isTlsEnabled() ? "https://" : "http://") + properties.get(CONTROLLER_REST_URI);
this.controllerGrpcURI = (this.isTlsEnabled() ? "tls://" : "tcp://") + properties.get(CONTROLLER_GRPC_URI);
this.authEnabled = properties.getBoolean(AUTH_ENABLED);
this.userName = properties.get(CONTROLLER_USER_NAME);
this.password = properties.get(CONTROLLER_PASSWORD);
this.metadataBackend = properties.get(METADATA_BACKEND);
this.truststore = properties.get(TRUSTSTORE_JKS);
this.segmentStoreURI = Arrays.asList(properties.get(SEGMENT_STORE_URI).split(","));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2017 Dell Inc., or its subsidiaries. All Rights Reserved.
*
* 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
*/
package io.pravega.tools.pravegacli.commands.utils;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

public class ControllerHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String s, SSLSession sslSession) {
return s.equalsIgnoreCase(sslSession.getPeerHost());
}
}
Binary file added pravega-cli/src/main/resources/client.truststore.jks
Binary file not shown.
13 changes: 10 additions & 3 deletions pravega-cli/src/main/resources/config.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,27 @@

# Config for CLI credentials (at the moment, Controller auth is only supported).
cli.authEnabled=false
cli.tlsEnabled=false
cli.userName=admin
cli.password=1111_aaaa

# Config for CLI security
cli.security.tls.trustStore.location=./conf/client.truststore.jks

# Config for CLI Controller REST/GRPC endpoints.
cli.controllerRestUri=http://localhost:10080
cli.controllerGrpcUri=tcp://localhost:9090
cli.controllerRestUri=localhost:9091
cli.controllerGrpcUri=localhost:9090
cli.metadataBackend=segmentstore

# Config for CLI SegmentStore endpoints
cli.segmentStore.uri = 127.0.1.1:6000

# General configuration for the Pravega cluster at hand.
pravegaservice.clusterName=pravega
pravegaservice.containerCount=4

# Config for Zookeeper service.
pravegaservice.zkURL=localhost:2181
pravegaservice.zkURL=localhost:4000

# Config for Bookkeeper commands.
bookkeeper.bkLedgerPath=/pravega/pravega/bookkeeper/ledgers
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (c) Dell Inc., or its subsidiaries. All Rights Reserved.
*
* 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
*/
package io.pravega.tools.pravegacli;

import io.pravega.client.ClientConfig;
import io.pravega.client.stream.impl.DefaultCredentials;
import io.pravega.test.common.SecurityConfigDefaults;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.net.URI;

public class AuthEnabledControllerDescribeStreamCommandTest extends ControllerDescribeStreamCommandTest{
@Before
public void setUp() throws Exception {
this.AUTH_ENABLED = true;
super.setUp();
}

@Override
ClientConfig prepareValidClientConfig() {
return ClientConfig.builder()
.controllerURI(URI.create(this.localPravega.getInProcPravegaCluster().getControllerURI()))

// Auth-related
.credentials(new DefaultCredentials(SecurityConfigDefaults.AUTH_ADMIN_PASSWORD,
SecurityConfigDefaults.AUTH_ADMIN_USERNAME))
.build();
}

@Test
@Override
public void testDescribeStreamCommand() throws Exception {
super.testDescribeStreamCommand();
}

@After
@Override
public void tearDown() throws Exception {
super.tearDown();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,15 @@ public static void setup() throws Exception {
SETUP_UTILS.startAllServices();
STATE.set(new AdminCommandState());
Properties pravegaProperties = new Properties();
pravegaProperties.setProperty("cli.controllerRestUri", SETUP_UTILS.getControllerRestUri().toString());

// The uri returned by SETUP_UTILS is in the form http://localhost:9091 (protocol + domain + port)
// but for the CLI we need to set the REST uri as localhost:9091 (domain + port). Because the protocol
// is decided based on whether security is enabled or not.
pravegaProperties.setProperty("cli.controllerRestUri", SETUP_UTILS.getControllerRestUri().toString().substring(7));
pravegaProperties.setProperty("pravegaservice.zkURL", "localhost:2181");
pravegaProperties.setProperty("pravegaservice.containerCount", "4");
pravegaProperties.setProperty("cli.authEnabled", "false");
pravegaProperties.setProperty("cli.tlsEnabled", "false");
STATE.get().getConfigBuilder().include(pravegaProperties);
}

Expand Down Expand Up @@ -73,4 +79,10 @@ public void testDescribeScopeCommand() throws Exception {
String commandResult = TestUtils.executeCommand("controller describe-scope _system", STATE.get());
Assert.assertTrue(commandResult.contains("_system"));
}

@Test
public void testDescribeReaderGroupCommand() throws Exception {
String commandResult = TestUtils.executeCommand("controller describe-readergroup _system scaleGroup", STATE.get());
Assert.assertTrue(commandResult.contains("scaleGroup"));
}
}
Loading

0 comments on commit 000e440

Please sign in to comment.