diff --git a/README.md b/README.md index d87b445..33389f2 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,9 @@ The following additional parameters can be set in set in `elasticsearch.yml`: |*_openshift.acl.expire_in_millis_*| The delay in milliseconds before generated ACLs are removed from| |*_openshift.config.project_index_prefix_*| The string value that project/namespace indices use as their prefix (default: ``) for example, with the common data model, if the namespace is `test`, the index name will be `project.test.$uuid.YYYY.MM.DD`. In this case, use `"project"` as the prefix - do not include the trailing `.`.| |*_openshift.kibana.index.mode_*| The setting that determines the kibana index is used by users. Valid values are one of the following: | +|*openshift.master.url*| If Openshift users must be authenticated to a remote cluster, this parameter must contain its URL (default: `https://kubernetes.default.svc`).| +|*openshift.trust.certificates*| Trust remote Openshift certificate (default: `true`)| +|*openshift.ca.path*| Absolute file path to the certificate that has to be used to authenticate remote Openshift cluster (default: `empty`)| *Note*: The `io.fabric8.elasticsearch.kibana.mapping.*` properties are required and must be defined for the plugin to function. A sample file may be found in the `samples` folder. diff --git a/src/main/java/io/fabric8/elasticsearch/plugin/ConfigurationSettings.java b/src/main/java/io/fabric8/elasticsearch/plugin/ConfigurationSettings.java index a2b2f6c..d9de244 100644 --- a/src/main/java/io/fabric8/elasticsearch/plugin/ConfigurationSettings.java +++ b/src/main/java/io/fabric8/elasticsearch/plugin/ConfigurationSettings.java @@ -89,6 +89,10 @@ public interface ConfigurationSettings extends KibanaIndexMode{ static final String OPENSHIFT_ACL_ROLE_STRATEGY = "openshift.acl.role_strategy"; static final String DEFAULT_ACL_ROLE_STRATEGY = "user"; + static final String OPENSHIFT_MASTER = "openshift.master.url"; + static final String OPENSHIFT_CA_PATH = "openshift.ca.path"; + static final String OPENSHIFT_TRUST_CERT = "openshift.trust.certificates"; + /** * List of index patterns to create for operations users */ diff --git a/src/main/java/io/fabric8/elasticsearch/plugin/OpenShiftElasticSearchPlugin.java b/src/main/java/io/fabric8/elasticsearch/plugin/OpenShiftElasticSearchPlugin.java index cebcf3e..ed9987b 100644 --- a/src/main/java/io/fabric8/elasticsearch/plugin/OpenShiftElasticSearchPlugin.java +++ b/src/main/java/io/fabric8/elasticsearch/plugin/OpenShiftElasticSearchPlugin.java @@ -87,7 +87,7 @@ public Collection createComponents(Client client, ClusterService cluster final PluginSettings pluginSettings = new PluginSettings(settings); final IndexMappingLoader indexMappingLoader = new IndexMappingLoader(settings); final PluginClient pluginClient = new PluginClient(client, threadPool.getThreadContext()); - final OpenshiftAPIService apiService = new OpenshiftAPIService(); + final OpenshiftAPIService apiService = new OpenshiftAPIService(pluginSettings); final RequestUtils requestUtils = new RequestUtils(pluginSettings, apiService); final OpenshiftRequestContextFactory contextFactory = new OpenshiftRequestContextFactory(settings, requestUtils, apiService); @@ -212,6 +212,9 @@ public List> getSettings() { settings.add(Setting.boolSetting("openshift.operations.allow_cluster_reader", false, Property.NodeScope)); settings.add(Setting.simpleString("openshift.kibana.index.mode", Property.NodeScope)); settings.add(Setting.simpleString(OPENSHIFT_ACL_ROLE_STRATEGY, Property.NodeScope)); + settings.add(Setting.simpleString(OPENSHIFT_CA_PATH, Property.NodeScope)); + settings.add(Setting.simpleString(OPENSHIFT_MASTER, Property.NodeScope)); + settings.add(Setting.simpleString(OPENSHIFT_TRUST_CERT, Property.NodeScope)); return settings; } diff --git a/src/main/java/io/fabric8/elasticsearch/plugin/OpenshiftAPIService.java b/src/main/java/io/fabric8/elasticsearch/plugin/OpenshiftAPIService.java index d84b224..2c19a31 100644 --- a/src/main/java/io/fabric8/elasticsearch/plugin/OpenshiftAPIService.java +++ b/src/main/java/io/fabric8/elasticsearch/plugin/OpenshiftAPIService.java @@ -51,18 +51,20 @@ public class OpenshiftAPIService { private static final String APPLICATION_JSON = "application/json"; private static final Logger LOGGER = Loggers.getLogger(OpenshiftAPIService.class); private final OpenShiftClientFactory factory; - - public OpenshiftAPIService() { - this(new OpenShiftClientFactory(){}); + private final PluginSettings settings; + + public OpenshiftAPIService(PluginSettings settings) { + this(new OpenShiftClientFactory(){}, settings); } - - public OpenshiftAPIService(OpenShiftClientFactory factory) { + + public OpenshiftAPIService(OpenShiftClientFactory factory, PluginSettings settings) { this.factory = factory; + this.settings = settings; } public String userName(final String token) { Response response = null; - try (DefaultOpenShiftClient client = factory.buildClient(token)) { + try (DefaultOpenShiftClient client = factory.buildClient(settings, token)) { Request okRequest = new Request.Builder() .url(client.getMasterUrl() + "apis/user.openshift.io/v1/users/~") .header(ACCEPT, APPLICATION_JSON) @@ -83,7 +85,7 @@ public String userName(final String token) { } public Set projectNames(final String token){ - try (DefaultOpenShiftClient client = factory.buildClient(token)) { + try (DefaultOpenShiftClient client = factory.buildClient(settings, token)) { Request request = new Request.Builder() .url(client.getMasterUrl() + "apis/project.openshift.io/v1/projects") .header(ACCEPT, APPLICATION_JSON) @@ -124,7 +126,7 @@ public Set projectNames(final String token){ */ public boolean localSubjectAccessReview(final String token, final String project, final String verb, final String resource, final String resourceAPIGroup, final String [] scopes) { - try (DefaultOpenShiftClient client = factory.buildClient(token)) { + try (DefaultOpenShiftClient client = factory.buildClient(settings, token)) { XContentBuilder payload = XContentFactory.jsonBuilder() .startObject() .field("kind","SubjectAccessReview") @@ -189,8 +191,20 @@ private void log(Response response, String body) { } interface OpenShiftClientFactory { - default DefaultOpenShiftClient buildClient(final String token) { + default DefaultOpenShiftClient buildClient(final PluginSettings settings, final String token) { Config config = new ConfigBuilder().withOauthToken(token).build(); + + if (settings.getMasterUrl() != null) { + config.setMasterUrl(settings.getMasterUrl()); + } + if (settings.isTrustCerts() != null) { + config.setTrustCerts(settings.isTrustCerts()); + } + if (settings.getOpenshiftCaPath() != null) { + config.setCaCertFile(settings.getOpenshiftCaPath()); + } + LOGGER.debug("Target cluster is {}, trust cert is {}, ca path is {}", + config.getMasterUrl(), config.isTrustCerts(), config.getCaCertFile()); return new DefaultOpenShiftClient(config); } diff --git a/src/main/java/io/fabric8/elasticsearch/plugin/PluginSettings.java b/src/main/java/io/fabric8/elasticsearch/plugin/PluginSettings.java index f438323..b7cf44a 100644 --- a/src/main/java/io/fabric8/elasticsearch/plugin/PluginSettings.java +++ b/src/main/java/io/fabric8/elasticsearch/plugin/PluginSettings.java @@ -42,6 +42,9 @@ public class PluginSettings implements ConfigurationSettings { private final Set opsIndexPatterns; private final long expireInMillis; private final Settings settings; + private final String masterUrl; + private final Boolean isTrustCerts; + private final String openshiftCaPath; public PluginSettings(final Settings settings) { this.settings = settings; @@ -64,6 +67,15 @@ public PluginSettings(final Settings settings) { this.opsIndexPatterns = new HashSet(Arrays.asList(settings.getAsArray(OPENSHIFT_KIBANA_OPS_INDEX_PATTERNS, DEFAULT_KIBANA_OPS_INDEX_PATTERNS))); this.expireInMillis = settings.getAsLong(OPENSHIFT_ACL_EXPIRE_IN_MILLIS, new Long(1000 * 60)); + this.masterUrl = settings.get(OPENSHIFT_MASTER); + this.openshiftCaPath = settings.get(OPENSHIFT_CA_PATH); + // Do not overwrite default K8S behavior + if (settings.get(OPENSHIFT_TRUST_CERT) != null) { + this.isTrustCerts = settings.getAsBoolean(OPENSHIFT_TRUST_CERT, true); + } else { + this.isTrustCerts = null; + } + LOGGER.info("Using kibanaIndexMode: '{}'", this.kibanaIndexMode); LOGGER.debug("searchGuardIndex: {}", this.searchGuardIndex); LOGGER.debug("roleStrategy: {}", this.roleStrategy); @@ -113,4 +125,16 @@ public void setKibanaIndexMode(String kibanaIndexMode) { public Set getKibanaOpsIndexPatterns() { return opsIndexPatterns; } + + public String getMasterUrl() { + return masterUrl; + } + + public String getOpenshiftCaPath() { + return openshiftCaPath; + } + + public Boolean isTrustCerts() { + return isTrustCerts; + } } diff --git a/src/main/java/io/fabric8/elasticsearch/util/RequestUtils.java b/src/main/java/io/fabric8/elasticsearch/util/RequestUtils.java index 05b16d3..8babe8a 100644 --- a/src/main/java/io/fabric8/elasticsearch/util/RequestUtils.java +++ b/src/main/java/io/fabric8/elasticsearch/util/RequestUtils.java @@ -107,7 +107,7 @@ public String getBearerToken(RestRequest request) { } return StringUtils.defaultIfEmpty(token, ""); } - + public boolean isClientCertAuth(final ThreadContext threadContext) { return threadContext != null && StringUtils.isNotEmpty(threadContext.getTransient("_sg_ssl_transport_principal")); } diff --git a/src/test/java/io/fabric8/elasticsearch/plugin/OpenshiftAPIServiceTest.java b/src/test/java/io/fabric8/elasticsearch/plugin/OpenshiftAPIServiceTest.java index 9c5904e..6e5a70d 100644 --- a/src/test/java/io/fabric8/elasticsearch/plugin/OpenshiftAPIServiceTest.java +++ b/src/test/java/io/fabric8/elasticsearch/plugin/OpenshiftAPIServiceTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.mock; @@ -29,6 +30,7 @@ import java.util.Set; import org.apache.commons.lang.ArrayUtils; +import org.elasticsearch.common.settings.Settings; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -55,7 +57,8 @@ public class OpenshiftAPIServiceTest { @Rule public OpenShiftServer apiServer = new OpenShiftServer(); - private OpenshiftAPIService service = new OpenshiftAPIService(); + private final PluginSettings pluginSettings = new PluginSettings(Settings.EMPTY); + private OpenshiftAPIService service = new OpenshiftAPIService(pluginSettings); @Before public void setup() { @@ -105,7 +108,7 @@ public void testLocalSubjectAccessReviewWhenNotNonResourceURL() throws IOExcepti DefaultOpenShiftClient client = mock(DefaultOpenShiftClient.class); OpenShiftClientFactory factory = mock(OpenShiftClientFactory.class); Call call = mock(Call.class); - when(factory.buildClient(anyString())).thenReturn(client); + when(factory.buildClient(eq(pluginSettings), anyString())).thenReturn(client); when(client.getHttpClient()).thenReturn(okClient); when(client.getMasterUrl()).thenReturn(new URL("https://localhost:8443/")); @@ -121,7 +124,7 @@ public void testLocalSubjectAccessReviewWhenNotNonResourceURL() throws IOExcepti when(okClient.newCall(any(Request.class))).thenAnswer(answer); when(call.execute()).thenReturn(response); - service = new OpenshiftAPIService(factory ); + service = new OpenshiftAPIService(factory, pluginSettings); assertTrue(service.localSubjectAccessReview("sometoken", "openshift-logging", "get", "pod/metrics", null, ArrayUtils.EMPTY_STRING_ARRAY)); Buffer buffer = new Buffer(); @@ -139,7 +142,7 @@ public void testLocalSubjectAccessReviewForNonResourceURL() throws IOException{ DefaultOpenShiftClient client = mock(DefaultOpenShiftClient.class); OpenShiftClientFactory factory = mock(OpenShiftClientFactory.class); Call call = mock(Call.class); - when(factory.buildClient(anyString())).thenReturn(client); + when(factory.buildClient(eq(pluginSettings), anyString())).thenReturn(client); when(client.getHttpClient()).thenReturn(okClient); when(client.getMasterUrl()).thenReturn(new URL("https://localhost:8443/")); @@ -155,7 +158,7 @@ public void testLocalSubjectAccessReviewForNonResourceURL() throws IOException{ when(okClient.newCall(any(Request.class))).thenAnswer(answer); when(call.execute()).thenReturn(response); - service = new OpenshiftAPIService(factory ); + service = new OpenshiftAPIService(factory, pluginSettings); assertTrue(service.localSubjectAccessReview("sometoken", "openshift-logging", "get", "/metrics", null, ArrayUtils.EMPTY_STRING_ARRAY)); Buffer buffer = new Buffer(); diff --git a/src/test/java/io/fabric8/elasticsearch/plugin/OpenshiftClientFactoryTest.java b/src/test/java/io/fabric8/elasticsearch/plugin/OpenshiftClientFactoryTest.java new file mode 100644 index 0000000..4a1f692 --- /dev/null +++ b/src/test/java/io/fabric8/elasticsearch/plugin/OpenshiftClientFactoryTest.java @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * 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.fabric8.elasticsearch.plugin; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import com.google.common.io.Files; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.openshift.client.NamespacedOpenShiftClient; + +public class OpenshiftClientFactoryTest { + + private final String cert = + "-----BEGIN CERTIFICATE-----\n" + + "MIIC0DCCAbigAwIBAgIBATANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDEw5sb2dn\n" + + "aW5nLXNpZ25lcjAeFw0xNzEwMTQxMTMwMDFaFw0yNzEwMTIxMTMwMDJaMBkxFzAV\n" + + "BgNVBAMTDmxvZ2dpbmctc2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" + + "CgKCAQEAwjq3zygMEywx0PD/qGO2IwZxN18DwJbWB71JH+ldbLQHMJ3fvIy4wpJV\n" + + "FlJDPAejQ6hFnsoArVZInxcIcVfTiLgX15CXfCcrWUXXxfY2WWc6qDbQKje/+VZX\n" + + "/nr8c5DvbiDQxTDjXNO7WDGxqCaJIKg72VIqE4ac4AYNEwHeW3rd5cLEh/wfAu3n\n" + + "/iGFQ0v75ZG8ef2QQE364/d5GHMrXcWXUrXxuqRO/wdEjuXkP3SY/8sUZHCdugt8\n" + + "QygSXLp5mHaMc+Ie70/gl7u8wAxJGOvkjYVEgZPUTbemjEYhr9QwMuPvXxalWNc8\n" + + "kWIsOXnnyKG+RWDo7FE7kZtXpYfBcwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAqQw\n" + + "DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAf+1IaNvSYQ1BNQVa\n" + + "hODEru6x+Ytg5HUyykT4tmxvvlqLS03ez37zKi2tDQBI/Sl4l46mu9H7GS98viO7\n" + + "Cj9Vn7km70GH6vDvCjY3iMKYK+rXzp1D2az0wmdmYymfrP8WC4X0q+KMZKPSVb9g\n" + + "9/0kAKPtH7YRzTiaSMlWhxNFQxM+zrHvw/Vp16PXZwq+FCbtv6zemQKo4JBHN2LM\n" + + "dIfgLqEMBkpvo1TeD3HOB4LyJJ6nnG4bUWsOnYYSZw1L70rHX9Vu5xq7xap2eL9g\n" + + "Uk4XsS8F+8hOE3zaHbqKbxRSqxnNqBI+UM+nQc1i3Qh2CXy8jgdVTjxWstDN/IHN\n" + + "Y6RrKw== \n" + + "-----END CERTIFICATE-----"; + + // We need a temporary folder because K8S plugin check if CA file exists in constructor + @Rule + public TemporaryFolder certFolder = new TemporaryFolder(); + + @Test + public void testWhenRemoteCAIsNotNull() throws Exception { + + File tempCaFile = certFolder.newFile("ca.crt"); + Files.write(cert.getBytes(), tempCaFile); + + final Config config = (new ConfigBuilder()).build(); + final PluginSettings pluginSettings = mock(PluginSettings.class); + when(pluginSettings.getOpenshiftCaPath()).thenReturn(tempCaFile.getAbsolutePath()); + when(pluginSettings.getMasterUrl()).thenReturn("https://foo.bar"); + when(pluginSettings.isTrustCerts()).thenReturn(false); + + OpenshiftAPIService.OpenShiftClientFactory factory = new OpenshiftAPIService.OpenShiftClientFactory(){}; + + final NamespacedOpenShiftClient openShiftClient = factory.buildClient(pluginSettings, "foo"); + final Config k8sConfig = openShiftClient.getConfiguration(); + + assertEquals("Exp. the CA file path to be loaded by K8S plugin", tempCaFile.getAbsolutePath(), k8sConfig.getCaCertFile()); + assertEquals("Exp. the master url to be correctly set in the K8S plugin", "https://foo.bar/", k8sConfig.getMasterUrl()); + assertFalse("Exp. the trust cert boolean to be correctly set in the K8S plugin", k8sConfig.isTrustCerts()); + } + +} diff --git a/src/test/java/io/fabric8/elasticsearch/plugin/PluginSettingsTest.java b/src/test/java/io/fabric8/elasticsearch/plugin/PluginSettingsTest.java index 07a332c..9a89cbf 100644 --- a/src/test/java/io/fabric8/elasticsearch/plugin/PluginSettingsTest.java +++ b/src/test/java/io/fabric8/elasticsearch/plugin/PluginSettingsTest.java @@ -17,6 +17,9 @@ package io.fabric8.elasticsearch.plugin; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import org.elasticsearch.common.settings.Settings; import org.junit.Test; @@ -37,4 +40,25 @@ public void testKibanaIndexModeDefault() { assertEquals("Exp. the plugin default to make kibana index mode unique", "unique", plugin.getKibanaIndexMode()); } + @Test + public void testRemoteOpenshiftWithDefaultConfiguration() { + PluginSettings plugin = new PluginSettings(Settings.builder().build()); + assertNull("Exp. remote Openshift URL is null by default to not override default K8S plugin behaviour", plugin.getMasterUrl()); + assertNull("Exp. Openshift certificate authority is null by default to not override default K8S plugin behaviour", plugin.getOpenshiftCaPath()); + assertNull("Exp. default trust cert is null to not override default K8S plugin behaviour", plugin.isTrustCerts()); + } + + @Test + public void testRemoteOpenshift() { + final String expectedRemoteOpenshiftUrl = "https://foo.bar:8443"; + final String expectedOpenshiftCaPath = "/etc/elasticsearch/secret/openshift-ca"; + final String source = "openshift.master.url: " + expectedRemoteOpenshiftUrl + "\n" + + "openshift.ca.path: " + expectedOpenshiftCaPath + "\n" + + "openshift.trust.certificates: false"; + PluginSettings plugin = new PluginSettings(Settings.builder().loadFromSource(source).build()); + assertEquals("Exp. the correct remote Openshift URL", expectedRemoteOpenshiftUrl, plugin.getMasterUrl()); + assertEquals("Exp. the correct Openshift certificate authority", expectedOpenshiftCaPath, plugin.getOpenshiftCaPath()); + assertFalse("Exp. the correct non default trust cert value from configuration", plugin.isTrustCerts()); + } + }