package com.thinkbiganalytics.nifi.v2.core.metadata; /*- * #%L * thinkbig-nifi-core-service * %% * Copyright (C) 2017 ThinkBig Analytics * %% * 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. * #L% */ import com.thinkbiganalytics.metadata.rest.client.MetadataClient; import com.thinkbiganalytics.nifi.core.api.metadata.KyloNiFiFlowProvider; import com.thinkbiganalytics.nifi.core.api.metadata.MetadataProvider; import com.thinkbiganalytics.nifi.core.api.metadata.MetadataProviderService; import com.thinkbiganalytics.nifi.core.api.metadata.MetadataRecorder; import org.apache.commons.lang3.StringUtils; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.ssl.SSLContextBuilder; import org.apache.nifi.annotation.lifecycle.OnEnabled; import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.components.PropertyValue; import org.apache.nifi.controller.AbstractControllerService; import org.apache.nifi.controller.ConfigurationContext; import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.reporting.InitializationException; import org.apache.nifi.ssl.SSLContextService; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.net.ssl.SSLContext; /** * */ public class MetadataProviderSelectorService extends AbstractControllerService implements MetadataProviderService { public static final PropertyDescriptor CLIENT_URL = new PropertyDescriptor.Builder() .name("rest-client-url") .displayName("REST Client URL") .description("The base URL to the metadata server when the REST API client implementation is chosen.") .defaultValue("http://localhost:8400/proxy/v1/metadata") .addValidator(StandardValidators.URL_VALIDATOR) .required(false) .build(); public static final PropertyDescriptor CLIENT_USERNAME = new PropertyDescriptor.Builder() .name("client-username") .displayName("REST Client User Name") .description("Optional user name if the client requires a credential") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .defaultValue("dladmin") .required(false) .build(); public static final PropertyDescriptor CLIENT_PASSWORD = new PropertyDescriptor.Builder() .name("client-password") .displayName("REST Client Password") .description("Optional password if the client requires a credential") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .defaultValue("") .sensitive(true) .required(false) .build(); public static final PropertyDescriptor SSL_CONTEXT_SERVICE = new PropertyDescriptor.Builder() .name("SSL Context Service") .description("The Controller Service to obtain the SSL Context") .required(false) .identifiesControllerService(SSLContextService.class) .build(); private static final AllowableValue[] ALLOWABLE_IMPLEMENATIONS = { new AllowableValue("LOCAL", "Local, In-memory storage", "An implemenation that stores metadata locally in memory (for development-only)"), new AllowableValue("REMOTE", "REST API", "An implementation that accesses metadata via the metadata service REST API") }; public static final PropertyDescriptor IMPLEMENTATION = new PropertyDescriptor.Builder() .name("Implementation") .description("Specifies which implementation of the metadata providers should be used") .allowableValues(ALLOWABLE_IMPLEMENATIONS) .defaultValue("REMOTE") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .required(true) .build(); private static final List<PropertyDescriptor> properties; static { final List<PropertyDescriptor> props = new ArrayList<>(); props.add(IMPLEMENTATION); props.add(CLIENT_URL); props.add(CLIENT_USERNAME); props.add(CLIENT_PASSWORD); props.add(SSL_CONTEXT_SERVICE); properties = Collections.unmodifiableList(props); } private volatile MetadataProvider provider; private volatile MetadataRecorder recorder; private volatile KyloProvenanceClientProvider kyloProvenanceClientProvider; /** * The Service holding the SSL Context information */ private SSLContextService sslContextService; @Override protected List<PropertyDescriptor> getSupportedPropertyDescriptors() { return properties; } @OnEnabled public void onConfigured(final ConfigurationContext context) throws InitializationException { PropertyValue impl = context.getProperty(IMPLEMENTATION); if (impl.getValue().equalsIgnoreCase("REMOTE")) { URI uri = URI.create(context.getProperty(CLIENT_URL).getValue()); String user = context.getProperty(CLIENT_USERNAME).getValue(); String password = context.getProperty(CLIENT_PASSWORD).getValue(); MetadataClient client; SSLContext sslContext = null; if (context.getProperty(SSL_CONTEXT_SERVICE) != null && context.getProperty(SSL_CONTEXT_SERVICE).isSet()) { this.sslContextService = context.getProperty(SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class); sslContext = this.sslContextService.createSSLContext(SSLContextService.ClientAuth.REQUIRED); } if (StringUtils.isEmpty(user)) { client = new MetadataClient(uri, sslContext); } else { client = new MetadataClient(uri, user, password, sslContext); } this.provider = new MetadataClientProvider(client); this.recorder = new MetadataClientRecorder(client); this.kyloProvenanceClientProvider = new KyloProvenanceClientProvider(client); } else { throw new UnsupportedOperationException("Provider implementations not currently supported: " + impl.getValue()); } } @Override public MetadataProvider getProvider() { return this.provider; } @Override public MetadataRecorder getRecorder() { return recorder; } public KyloNiFiFlowProvider getKyloNiFiFlowProvider() { return this.kyloProvenanceClientProvider; } /** * Taken from NiFi GetHttp Processor */ private SSLContext createSSLContext(final SSLContextService service) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException { final SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); if (StringUtils.isNotBlank(service.getTrustStoreFile())) { final KeyStore truststore = KeyStore.getInstance(service.getTrustStoreType()); try (final InputStream in = new FileInputStream(new File(service.getTrustStoreFile()))) { truststore.load(in, service.getTrustStorePassword().toCharArray()); } sslContextBuilder.loadTrustMaterial(truststore, new TrustSelfSignedStrategy()); } if (StringUtils.isNotBlank(service.getKeyStoreFile())) { final KeyStore keystore = KeyStore.getInstance(service.getKeyStoreType()); try (final InputStream in = new FileInputStream(new File(service.getKeyStoreFile()))) { keystore.load(in, service.getKeyStorePassword().toCharArray()); } sslContextBuilder.loadKeyMaterial(keystore, service.getKeyStorePassword().toCharArray()); } sslContextBuilder.useProtocol(service.getSslAlgorithm()); return sslContextBuilder.build(); } }