http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/DataTransferResource.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/DataTransferResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/DataTransferResource.java index 886f24a..7e0aff9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/DataTransferResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/DataTransferResource.java @@ -16,12 +16,47 @@ */ package org.apache.nifi.web.api; +import static org.apache.commons.lang3.StringUtils.isEmpty; +import static org.apache.nifi.remote.protocol.HandshakeProperty.BATCH_COUNT; +import static org.apache.nifi.remote.protocol.HandshakeProperty.BATCH_DURATION; +import static org.apache.nifi.remote.protocol.HandshakeProperty.BATCH_SIZE; +import static org.apache.nifi.remote.protocol.HandshakeProperty.REQUEST_EXPIRATION_MILLIS; +import static org.apache.nifi.remote.protocol.http.HttpHeaders.HANDSHAKE_PROPERTY_BATCH_COUNT; +import static org.apache.nifi.remote.protocol.http.HttpHeaders.HANDSHAKE_PROPERTY_BATCH_DURATION; +import static org.apache.nifi.remote.protocol.http.HttpHeaders.HANDSHAKE_PROPERTY_BATCH_SIZE; +import static org.apache.nifi.remote.protocol.http.HttpHeaders.HANDSHAKE_PROPERTY_REQUEST_EXPIRATION; +import static org.apache.nifi.remote.protocol.http.HttpHeaders.HANDSHAKE_PROPERTY_USE_COMPRESSION; + import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; import com.wordnik.swagger.annotations.ApiResponse; import com.wordnik.swagger.annotations.ApiResponses; import com.wordnik.swagger.annotations.Authorization; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; +import javax.ws.rs.core.UriInfo; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authorization.AccessDeniedException; import org.apache.nifi.authorization.AuthorizationRequest; @@ -53,47 +88,11 @@ import org.apache.nifi.remote.protocol.ResponseCode; import org.apache.nifi.remote.protocol.http.HttpFlowFileServerProtocol; import org.apache.nifi.remote.protocol.http.StandardHttpFlowFileServerProtocol; import org.apache.nifi.stream.io.ByteArrayOutputStream; +import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.api.entity.TransactionResultEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.StreamingOutput; -import javax.ws.rs.core.UriInfo; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; - -import static org.apache.commons.lang3.StringUtils.isEmpty; -import static org.apache.nifi.remote.protocol.HandshakeProperty.BATCH_COUNT; -import static org.apache.nifi.remote.protocol.HandshakeProperty.BATCH_DURATION; -import static org.apache.nifi.remote.protocol.HandshakeProperty.BATCH_SIZE; -import static org.apache.nifi.remote.protocol.HandshakeProperty.REQUEST_EXPIRATION_MILLIS; -import static org.apache.nifi.remote.protocol.http.HttpHeaders.HANDSHAKE_PROPERTY_BATCH_COUNT; -import static org.apache.nifi.remote.protocol.http.HttpHeaders.HANDSHAKE_PROPERTY_BATCH_DURATION; -import static org.apache.nifi.remote.protocol.http.HttpHeaders.HANDSHAKE_PROPERTY_BATCH_SIZE; -import static org.apache.nifi.remote.protocol.http.HttpHeaders.HANDSHAKE_PROPERTY_REQUEST_EXPIRATION; -import static org.apache.nifi.remote.protocol.http.HttpHeaders.HANDSHAKE_PROPERTY_USE_COMPRESSION; -import org.apache.nifi.util.NiFiProperties; - /** * RESTful endpoint for managing a SiteToSite connection. */ @@ -109,6 +108,7 @@ public class DataTransferResource extends ApplicationResource { public static final String CHECK_SUM = "checksum"; public static final String RESPONSE_CODE = "responseCode"; + private static final String PORT_TYPE_INPUT = "input-ports"; private static final String PORT_TYPE_OUTPUT = "output-ports"; @@ -116,8 +116,10 @@ public class DataTransferResource extends ApplicationResource { private final ResponseCreator responseCreator = new ResponseCreator(); private final VersionNegotiator transportProtocolVersionNegotiator = new TransportProtocolVersionNegotiator(1); private final HttpRemoteSiteListener transactionManager; + private final NiFiProperties nifiProperties; - public DataTransferResource(final NiFiProperties nifiProperties) { + public DataTransferResource(final NiFiProperties nifiProperties){ + this.nifiProperties = nifiProperties; transactionManager = HttpRemoteSiteListener.getInstance(nifiProperties); } @@ -165,17 +167,18 @@ public class DataTransferResource extends ApplicationResource { value = "Create a transaction to the specified output port or input port", response = TransactionResultEntity.class, authorizations = { - @Authorization(value = "Write - /data-transfer/{component-type}/{uuid}", type = "") + @Authorization(value = "Write - /data-transfer/{component-type}/{uuid}", type = "") } ) @ApiResponses( value = { - @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), - @ApiResponse(code = 401, message = "Client could not be authenticated."), - @ApiResponse(code = 403, message = "Client is not authorized to make this request."), - @ApiResponse(code = 404, message = "The specified resource could not be found."), - @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), - @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"),} + @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(code = 401, message = "Client could not be authenticated."), + @ApiResponse(code = 403, message = "Client is not authorized to make this request."), + @ApiResponse(code = 404, message = "The specified resource could not be found."), + @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), + @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"), + } ) public Response createPortTransaction( @ApiParam( @@ -190,6 +193,7 @@ public class DataTransferResource extends ApplicationResource { @Context UriInfo uriInfo, InputStream inputStream) { + if (!PORT_TYPE_INPUT.equals(portType) && !PORT_TYPE_OUTPUT.equals(portType)) { return responseCreator.wrongPortTypeResponse(portType, portId); } @@ -237,17 +241,18 @@ public class DataTransferResource extends ApplicationResource { value = "Transfer flow files to the input port", response = String.class, authorizations = { - @Authorization(value = "Write - /data-transfer/input-ports/{uuid}", type = "") + @Authorization(value = "Write - /data-transfer/input-ports/{uuid}", type = "") } ) @ApiResponses( value = { - @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), - @ApiResponse(code = 401, message = "Client could not be authenticated."), - @ApiResponse(code = 403, message = "Client is not authorized to make this request."), - @ApiResponse(code = 404, message = "The specified resource could not be found."), - @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), - @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"),} + @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(code = 401, message = "Client could not be authenticated."), + @ApiResponse(code = 403, message = "Client is not authorized to make this request."), + @ApiResponse(code = 404, message = "The specified resource could not be found."), + @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), + @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"), + } ) public Response receiveFlowFiles( @ApiParam( @@ -300,26 +305,26 @@ public class DataTransferResource extends ApplicationResource { } private HttpFlowFileServerProtocol initiateServerProtocol(final HttpServletRequest req, final Peer peer, - final Integer transportProtocolVersion) throws IOException { + final Integer transportProtocolVersion) throws IOException { // Switch transaction protocol version based on transport protocol version. TransportProtocolVersionNegotiator negotiatedTransportProtocolVersion = new TransportProtocolVersionNegotiator(transportProtocolVersion); VersionNegotiator versionNegotiator = new StandardVersionNegotiator(negotiatedTransportProtocolVersion.getTransactionProtocolVersion()); final String dataTransferUrl = req.getRequestURL().toString(); - ((HttpCommunicationsSession) peer.getCommunicationsSession()).setDataTransferUrl(dataTransferUrl); + ((HttpCommunicationsSession)peer.getCommunicationsSession()).setDataTransferUrl(dataTransferUrl); HttpFlowFileServerProtocol serverProtocol = getHttpFlowFileServerProtocol(versionNegotiator); - HttpRemoteSiteListener.getInstance(getProperties()).setupServerProtocol(serverProtocol); + HttpRemoteSiteListener.getInstance(nifiProperties).setupServerProtocol(serverProtocol); serverProtocol.handshake(peer); return serverProtocol; } HttpFlowFileServerProtocol getHttpFlowFileServerProtocol(final VersionNegotiator versionNegotiator) { - return new StandardHttpFlowFileServerProtocol(versionNegotiator, getProperties()); + return new StandardHttpFlowFileServerProtocol(versionNegotiator, nifiProperties); } private Peer constructPeer(final HttpServletRequest req, final InputStream inputStream, - final OutputStream outputStream, final String portId, final String transactionId) { + final OutputStream outputStream, final String portId, final String transactionId) { final String clientHostName = req.getRemoteHost(); final int clientPort = req.getRemotePort(); @@ -377,17 +382,18 @@ public class DataTransferResource extends ApplicationResource { value = "Commit or cancel the specified transaction", response = TransactionResultEntity.class, authorizations = { - @Authorization(value = "Write - /data-transfer/output-ports/{uuid}", type = "") + @Authorization(value = "Write - /data-transfer/output-ports/{uuid}", type = "") } ) @ApiResponses( value = { - @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), - @ApiResponse(code = 401, message = "Client could not be authenticated."), - @ApiResponse(code = 403, message = "Client is not authorized to make this request."), - @ApiResponse(code = 404, message = "The specified resource could not be found."), - @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), - @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"),} + @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(code = 401, message = "Client could not be authenticated."), + @ApiResponse(code = 403, message = "Client is not authorized to make this request."), + @ApiResponse(code = 404, message = "The specified resource could not be found."), + @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), + @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"), + } ) public Response commitOutputPortTransaction( @ApiParam( @@ -474,6 +480,7 @@ public class DataTransferResource extends ApplicationResource { return clusterContext(noCache(setCommonHeaders(Response.ok(entity), transportProtocolVersion, transactionManager))).build(); } + @DELETE @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_JSON) @@ -482,17 +489,18 @@ public class DataTransferResource extends ApplicationResource { value = "Commit or cancel the specified transaction", response = TransactionResultEntity.class, authorizations = { - @Authorization(value = "Write - /data-transfer/input-ports/{uuid}", type = "") + @Authorization(value = "Write - /data-transfer/input-ports/{uuid}", type = "") } ) @ApiResponses( value = { - @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), - @ApiResponse(code = 401, message = "Client could not be authenticated."), - @ApiResponse(code = 403, message = "Client is not authorized to make this request."), - @ApiResponse(code = 404, message = "The specified resource could not be found."), - @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), - @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"),} + @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(code = 401, message = "Client could not be authenticated."), + @ApiResponse(code = 403, message = "Client is not authorized to make this request."), + @ApiResponse(code = 404, message = "The specified resource could not be found."), + @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), + @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"), + } ) public Response commitInputPortTransaction( @ApiParam( @@ -590,6 +598,7 @@ public class DataTransferResource extends ApplicationResource { return Response.ok(entity).build(); } + @GET @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_OCTET_STREAM) @@ -598,18 +607,19 @@ public class DataTransferResource extends ApplicationResource { value = "Transfer flow files from the output port", response = StreamingOutput.class, authorizations = { - @Authorization(value = "Write - /data-transfer/output-ports/{uuid}", type = "") + @Authorization(value = "Write - /data-transfer/output-ports/{uuid}", type = "") } ) @ApiResponses( value = { - @ApiResponse(code = 200, message = "There is no flow file to return."), - @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), - @ApiResponse(code = 401, message = "Client could not be authenticated."), - @ApiResponse(code = 403, message = "Client is not authorized to make this request."), - @ApiResponse(code = 404, message = "The specified resource could not be found."), - @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), - @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"),} + @ApiResponse(code = 200, message = "There is no flow file to return."), + @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(code = 401, message = "Client could not be authenticated."), + @ApiResponse(code = 403, message = "Client is not authorized to make this request."), + @ApiResponse(code = 404, message = "The specified resource could not be found."), + @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), + @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"), + } ) public Response transferFlowFiles( @ApiParam( @@ -681,16 +691,16 @@ public class DataTransferResource extends ApplicationResource { value = "Extend transaction TTL", response = TransactionResultEntity.class, authorizations = { - @Authorization(value = "Write - /data-transfer/input-ports/{uuid}", type = "") + @Authorization(value = "Write - /data-transfer/input-ports/{uuid}", type = "") } ) @ApiResponses( value = { - @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), - @ApiResponse(code = 401, message = "Client could not be authenticated."), - @ApiResponse(code = 403, message = "Client is not authorized to make this request."), - @ApiResponse(code = 404, message = "The specified resource could not be found."), - @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.") + @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(code = 401, message = "Client could not be authenticated."), + @ApiResponse(code = 403, message = "Client is not authorized to make this request."), + @ApiResponse(code = 404, message = "The specified resource could not be found."), + @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.") } ) public Response extendInputPortTransactionTTL( @@ -716,17 +726,18 @@ public class DataTransferResource extends ApplicationResource { value = "Extend transaction TTL", response = TransactionResultEntity.class, authorizations = { - @Authorization(value = "Write - /data-transfer/output-ports/{uuid}", type = "") + @Authorization(value = "Write - /data-transfer/output-ports/{uuid}", type = "") } ) @ApiResponses( value = { - @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), - @ApiResponse(code = 401, message = "Client could not be authenticated."), - @ApiResponse(code = 403, message = "Client is not authorized to make this request."), - @ApiResponse(code = 404, message = "The specified resource could not be found."), - @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), - @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"),} + @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(code = 401, message = "Client could not be authenticated."), + @ApiResponse(code = 403, message = "Client is not authorized to make this request."), + @ApiResponse(code = 404, message = "The specified resource could not be found."), + @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful."), + @ApiResponse(code = 503, message = "NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"), + } ) public Response extendOutputPortTransactionTTL( @PathParam("portId") String portId, @@ -789,7 +800,6 @@ public class DataTransferResource extends ApplicationResource { } private class ValidateRequestResult { - private Integer transportProtocolVersion; private Response errResponse; } @@ -820,7 +830,9 @@ public class DataTransferResource extends ApplicationResource { return result; } + // setters + public void setAuthorizer(Authorizer authorizer) { this.authorizer = authorizer; }
http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java index 036ac2a..88bdeb6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java @@ -16,6 +16,10 @@ */ package org.apache.nifi.web.api; + +import java.net.InetAddress; +import java.net.UnknownHostException; + import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiResponse; @@ -56,8 +60,6 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -171,6 +173,7 @@ public class SiteToSiteResource extends ApplicationResource { @Path("/peers") @Consumes(MediaType.WILDCARD) @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + // TODO: @PreAuthorize("hasRole('ROLE_NIFI')") @ApiOperation( value = "Returns the available Peers and its status of this NiFi", response = PeersEntity.class, http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml index ed74922..9a27b13 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml @@ -40,6 +40,7 @@ <module>nifi-resources</module> <module>nifi-documentation</module> <module>nifi-authorizer</module> + <module>nifi-properties-loader</module> </modules> <dependencies> <dependency> http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-nar-bundles/nifi-framework-bundle/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/pom.xml index 14593de..d62ef60 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/pom.xml @@ -75,6 +75,11 @@ </dependency> <dependency> <groupId>org.apache.nifi</groupId> + <artifactId>nifi-properties-loader</artifactId> + <version>1.0.0-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> <artifactId>nifi-framework-authorization</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java index 4190ebb..942fea4 100644 --- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java +++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java @@ -44,7 +44,6 @@ public class TestVolatileProvenanceRepository { @Test public void testAddAndGet() throws IOException, InterruptedException { - repo = new VolatileProvenanceRepository(NiFiProperties.createBasicNiFiProperties(null, null)); final Map<String, String> attributes = new HashMap<>(); http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-toolkit/nifi-toolkit-assembly/NOTICE ---------------------------------------------------------------------- diff --git a/nifi-toolkit/nifi-toolkit-assembly/NOTICE b/nifi-toolkit/nifi-toolkit-assembly/NOTICE index 653ba83..5ce896b 100644 --- a/nifi-toolkit/nifi-toolkit-assembly/NOTICE +++ b/nifi-toolkit/nifi-toolkit-assembly/NOTICE @@ -90,5 +90,15 @@ The following binary components are provided under the Apache Software License v (ASLv2) Jetty The following NOTICE information applies: - Jetty Web Container - Copyright 1995-2015 Mort Bay Consulting Pty Ltd. + Jetty Web Container + Copyright 1995-2015 Mort Bay Consulting Pty Ltd. + + (ASLv2) Groovy (org.codehaus.groovy:groovy-all:jar:2.4.5 - http://www.groovy-lang.org) + The following NOTICE information applies: + Groovy Language + Copyright 2003-2015 The respective authors and developers + Developers and Contributors are listed in the project POM file + and Gradle build + + This product includes software developed by + The Groovy community (http://groovy.codehaus.org/). \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-toolkit/nifi-toolkit-assembly/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-toolkit/nifi-toolkit-assembly/pom.xml b/nifi-toolkit/nifi-toolkit-assembly/pom.xml index 4439d5b..cd2ece2 100644 --- a/nifi-toolkit/nifi-toolkit-assembly/pom.xml +++ b/nifi-toolkit/nifi-toolkit-assembly/pom.xml @@ -18,7 +18,7 @@ language governing permissions and limitations under the License. --> </parent> <artifactId>nifi-toolkit-assembly</artifactId> <packaging>pom</packaging> - <description>This is the assembly Apache NiFi Toolkit</description> + <description>This is the assembly for the Apache NiFi Toolkit</description> <build> <plugins> <plugin> @@ -65,6 +65,10 @@ language governing permissions and limitations under the License. --> <artifactId>nifi-toolkit-tls</artifactId> </dependency> <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-toolkit-encrypt-config</artifactId> + </dependency> + <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <scope>compile</scope> http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-toolkit/nifi-toolkit-assembly/src/main/assembly/dependencies.xml ---------------------------------------------------------------------- diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/assembly/dependencies.xml b/nifi-toolkit/nifi-toolkit-assembly/src/main/assembly/dependencies.xml index 626287a..1ade182 100644 --- a/nifi-toolkit/nifi-toolkit-assembly/src/main/assembly/dependencies.xml +++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/assembly/dependencies.xml @@ -45,6 +45,15 @@ <outputDirectory>conf/</outputDirectory> <fileMode>0600</fileMode> </fileSet> + <!--<fileSet>--> + <!--<directory>${project.build.directory}/nifi-resources/src/main/resources/conf</directory>--> + <!--<outputDirectory>conf/</outputDirectory>--> + <!--<fileMode>0600</fileMode>--> + <!--<includes>--> + <!--<include>nifi.properties</include>--> + <!--<include>bootstrap.conf</include>--> + <!--</includes>--> + <!--</fileSet>--> <fileSet> <directory>${project.basedir}/src/main/resources/classpath</directory> <outputDirectory>classpath/</outputDirectory> http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.bat ---------------------------------------------------------------------- diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.bat b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.bat new file mode 100644 index 0000000..de3fbf7 --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.bat @@ -0,0 +1,40 @@ +@echo off +rem +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. +rem + +rem Use JAVA_HOME if it's set; otherwise, just use java + +if "%JAVA_HOME%" == "" goto noJavaHome +if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome +set JAVA_EXE=%JAVA_HOME%\bin\java.exe +goto startConfig + +:noJavaHome +echo The JAVA_HOME environment variable is not defined correctly. +echo Instead the PATH will be used to find the java executable. +echo. +set JAVA_EXE=java +goto startConfig + +:startConfig +set LIB_DIR=%~dp0..\classpath;%~dp0..\lib + +SET JAVA_PARAMS=-cp %LIB_DIR%\* -Xms12m -Xmx24m %JAVA_ARGS% org.apache.nifi.util.config.ConfigEncryptionTool + +cmd.exe /C "%JAVA_EXE%" %JAVA_PARAMS% %* + +popd http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.sh ---------------------------------------------------------------------- diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.sh b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.sh new file mode 100644 index 0000000..b6b5f45 --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.sh @@ -0,0 +1,120 @@ +#!/bin/sh +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF 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. +# +# + +# Script structure inspired from Apache Karaf and other Apache projects with similar startup approaches + +SCRIPT_DIR=$(dirname "$0") +SCRIPT_NAME=$(basename "$0") +NIFI_TOOLKIT_HOME=$(cd "${SCRIPT_DIR}" && cd .. && pwd) +PROGNAME=$(basename "$0") + + +warn() { + echo "${PROGNAME}: $*" +} + +die() { + warn "$*" + exit 1 +} + +detectOS() { + # OS specific support (must be 'true' or 'false'). + cygwin=false; + aix=false; + os400=false; + darwin=false; + case "$(uname)" in + CYGWIN*) + cygwin=true + ;; + AIX*) + aix=true + ;; + OS400*) + os400=true + ;; + Darwin) + darwin=true + ;; + esac + # For AIX, set an environment variable + if ${aix}; then + export LDR_CNTRL=MAXDATA=0xB0000000@DSA + echo ${LDR_CNTRL} + fi +} + +locateJava() { + # Setup the Java Virtual Machine + if $cygwin ; then + [ -n "${JAVA}" ] && JAVA=$(cygpath --unix "${JAVA}") + [ -n "${JAVA_HOME}" ] && JAVA_HOME=$(cygpath --unix "${JAVA_HOME}") + fi + + if [ "x${JAVA}" = "x" ] && [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi + if [ "x${JAVA}" = "x" ]; then + if [ "x${JAVA_HOME}" != "x" ]; then + if [ ! -d "${JAVA_HOME}" ]; then + die "JAVA_HOME is not valid: ${JAVA_HOME}" + fi + JAVA="${JAVA_HOME}/bin/java" + else + warn "JAVA_HOME not set; results may vary" + JAVA=$(type java) + JAVA=$(expr "${JAVA}" : '.* \(/.*\)$') + if [ "x${JAVA}" = "x" ]; then + die "java command not found" + fi + fi + fi +} + +init() { + # Determine if there is special OS handling we must perform + detectOS + + # Locate the Java VM to execute + locateJava +} + +run() { + LIBS="${NIFI_TOOLKIT_HOME}/lib/*" + + sudo_cmd_prefix="" + if $cygwin; then + NIFI_TOOLKIT_HOME=$(cygpath --path --windows "${NIFI_TOOLKIT_HOME}") + CLASSPATH="$NIFI_TOOLKIT_HOME/classpath";$(cygpath --path --windows "${LIBS}") + else + CLASSPATH="$NIFI_TOOLKIT_HOME/classpath:${LIBS}" + fi + + export JAVA_HOME="$JAVA_HOME" + export NIFI_TOOLKIT_HOME="$NIFI_TOOLKIT_HOME" + + umask 0077 + "${JAVA}" -cp "${CLASSPATH}" -Xms128m -Xmx256m org.apache.nifi.properties.ConfigEncryptionTool "$@" + return $? +} + + +init +run "$@" \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-toolkit/nifi-toolkit-encrypt-config/LICENSE ---------------------------------------------------------------------- diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/LICENSE b/nifi-toolkit/nifi-toolkit-encrypt-config/LICENSE new file mode 100644 index 0000000..e1b4124 --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-encrypt-config/LICENSE @@ -0,0 +1,225 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +This product bundles source from 'AbstractingTheJavaConsole'. The source is available under an MIT LICENSE. + + Copyright (C) 2010 McDowell + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml new file mode 100644 index 0000000..c146e9c --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-toolkit</artifactId> + <version>1.0.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>nifi-toolkit-encrypt-config</artifactId> + <description>Tool to encrypt sensitive configuration values</description> + <dependencies> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-properties</artifactId> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-properties-loader</artifactId> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-toolkit-tls</artifactId> + <exclusions> + <exclusion> + <groupId>commons-logging</groupId> + <artifactId>commons-logging</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>commons-cli</groupId> + <artifactId>commons-cli</artifactId> + </dependency> + <!--<dependency>--> + <!--<groupId>org.codehaus.groovy</groupId>--> + <!--<artifactId>groovy-all</artifactId>--> + <!--<scope>compile</scope>--> + <!--</dependency>--> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.github.stefanbirkner</groupId> + <artifactId>system-rules</artifactId> + <version>1.16.0</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>compile</goal> + <goal>testCompile</goal> + </goals> + <configuration> + <compilerId>groovy-eclipse-compiler</compilerId> + </configuration> + </execution> + </executions> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + <dependencies> + <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy-eclipse-compiler</artifactId> + <version>2.9.2-01</version> + </dependency> + <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy-eclipse-batch</artifactId> + <version>2.4.3-01</version> + </dependency> + </dependencies> + </plugin> + <!--<plugin>--> + <!--<groupId>org.codehaus.groovy</groupId>--> + <!--<artifactId>groovy-eclipse-compiler</artifactId>--> + <!--<version>2.9.2-01</version>--> + <!--<extensions>true</extensions>--> + <!--</plugin>--> + <!--<plugin>--> + <!--<groupId>org.apache.maven.plugins</groupId>--> + <!--<artifactId>maven-shade-plugin</artifactId>--> + <!--<version>2.1</version>--> + <!--<executions>--> + <!--<execution>--> + <!--<phase>package</phase>--> + <!--<goals>--> + <!--<goal>shade</goal>--> + <!--</goals>--> + <!--</execution>--> + <!--</executions>--> + <!--<configuration>--> + <!--<transformers>--> + <!--<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">--> + <!--<mainClass>ConfigEncryptionTool</mainClass>--> + <!--</transformer>--> + <!--</transformers>--> + <!--</configuration>--> + <!--</plugin>--> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <version>1.5</version> + <executions> + <execution> + <id>add-source</id> + <phase>generate-sources</phase> + <goals> + <goal>add-source</goal> + </goals> + <configuration> + <sources> + <source>src/main/groovy</source> + </sources> + </configuration> + </execution> + <execution> + <id>add-test-source</id> + <phase>generate-test-sources</phase> + <goals> + <goal>add-test-source</goal> + </goals> + <configuration> + <sources> + <source>src/test/groovy</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi/blob/c638191a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy ---------------------------------------------------------------------- diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy new file mode 100644 index 0000000..8a275be --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy @@ -0,0 +1,578 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.nifi.properties + +import groovy.io.GroovyPrintWriter +import org.apache.commons.cli.CommandLine +import org.apache.commons.cli.CommandLineParser +import org.apache.commons.cli.DefaultParser +import org.apache.commons.cli.HelpFormatter +import org.apache.commons.cli.Options +import org.apache.commons.cli.ParseException +import org.apache.commons.codec.binary.Hex +import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException +import org.apache.nifi.toolkit.tls.commandLine.ExitCode +import org.apache.nifi.util.NiFiProperties +import org.apache.nifi.util.console.TextDevice +import org.apache.nifi.util.console.TextDevices +import org.bouncycastle.crypto.generators.SCrypt +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import javax.crypto.Cipher +import java.nio.charset.StandardCharsets +import java.security.KeyException +import java.security.Security + +class ConfigEncryptionTool { + private static final Logger logger = LoggerFactory.getLogger(ConfigEncryptionTool.class) + + public String bootstrapConfPath + public String niFiPropertiesPath + public String outputNiFiPropertiesPath + public String loginIdentityProvidersPath + + private String keyHex + private String password + private NiFiProperties niFiProperties + + private boolean usingPassword = true + private boolean isVerbose = false + + private static final String HELP_ARG = "help" + private static final String VERBOSE_ARG = "verbose" + private static final String BOOTSTRAP_CONF_ARG = "bootstrapConf" + private static final String NIFI_PROPERTIES_ARG = "niFiProperties" + private static final String OUTPUT_NIFI_PROPERTIES_ARG = "outputNiFiProperties" + private static final String KEY_ARG = "key" + private static final String PASSWORD_ARG = "password" + private static final String USE_KEY_ARG = "useRawKey" + + private static final int MIN_PASSWORD_LENGTH = 12 + + // Strong parameters as of 12 Aug 2016 + private static final int SCRYPT_N = 2**16 + private static final int SCRYPT_R = 8 + private static final int SCRYPT_P = 1 + + private static + final String BOOTSTRAP_KEY_COMMENT = "# Master key in hexadecimal format for encrypted sensitive configuration values" + private static final String BOOTSTRAP_KEY_PREFIX = "nifi.bootstrap.sensitive.key=" + private static final String JAVA_HOME = "JAVA_HOME" + private static final String NIFI_TOOLKIT_HOME = "NIFI_TOOLKIT_HOME" + private static final String SEP = System.lineSeparator() + + private static final String FOOTER = buildFooter() + + private static + final String DEFAULT_DESCRIPTION = "This tool reads from a nifi.properties file with plain sensitive configuration values, prompts the user for a master key, and encrypts each value. It will replace the plain value with the protected value in the same file (or write to a new nifi.properties file if specified)." + + private static String buildHeader(String description = DEFAULT_DESCRIPTION) { + "${SEP}${description}${SEP * 2}" + } + + private static String buildFooter() { + "${SEP}Java home: ${System.getenv(JAVA_HOME)}${SEP}NiFi Toolkit home: ${System.getenv(NIFI_TOOLKIT_HOME)}" + } + + private final Options options; + private final String header; + + + public ConfigEncryptionTool() { + this(DEFAULT_DESCRIPTION) + } + + public ConfigEncryptionTool(String description) { + this.header = buildHeader(description) + this.options = new Options() + options.addOption("h", HELP_ARG, false, "Prints this usage message") + options.addOption("v", VERBOSE_ARG, false, "Sets verbose mode (default false)") + options.addOption("n", NIFI_PROPERTIES_ARG, true, "The nifi.properties file containing unprotected config values (will be overwritten)") + options.addOption("b", BOOTSTRAP_CONF_ARG, true, "The bootstrap.conf file to persist master key") + options.addOption("o", OUTPUT_NIFI_PROPERTIES_ARG, true, "The destination nifi.properties file containing protected config values (will not modify input nifi.properties)") + options.addOption("k", KEY_ARG, true, "The raw hexadecimal key to use to encrypt the sensitive properties") + options.addOption("p", PASSWORD_ARG, true, "The password from which to derive the key to use to encrypt the sensitive properties") + options.addOption("r", USE_KEY_ARG, false, "If provided, the secure console will prompt for the raw key value in hexadecimal form") + } + + /** + * Prints the usage message and available arguments for this tool (along with a specific error message if provided). + * + * @param errorMessage the optional error message + */ + public void printUsage(String errorMessage) { + if (errorMessage) { + System.out.println(errorMessage) + System.out.println() + } + HelpFormatter helpFormatter = new HelpFormatter() + helpFormatter.setWidth(160) + helpFormatter.printHelp(ConfigEncryptionTool.class.getCanonicalName(), header, options, FOOTER, true) + } + + protected void printUsageAndThrow(String errorMessage, ExitCode exitCode) throws CommandLineParseException { + printUsage(errorMessage); + throw new CommandLineParseException(errorMessage, exitCode); + } + + protected CommandLine parse(String[] args) throws CommandLineParseException { + CommandLineParser parser = new DefaultParser() + CommandLine commandLine + try { + commandLine = parser.parse(options, args) + if (commandLine.hasOption(HELP_ARG)) { + printUsageAndThrow(null, ExitCode.HELP) + } + + isVerbose = commandLine.hasOption(VERBOSE_ARG) + + bootstrapConfPath = commandLine.getOptionValue(BOOTSTRAP_CONF_ARG, determineDefaultBootstrapConfPath()) + niFiPropertiesPath = commandLine.getOptionValue(NIFI_PROPERTIES_ARG, determineDefaultNiFiPropertiesPath()) + outputNiFiPropertiesPath = commandLine.getOptionValue(OUTPUT_NIFI_PROPERTIES_ARG, niFiPropertiesPath) + + if (niFiPropertiesPath == outputNiFiPropertiesPath) { + // TODO: Add confirmation pause and provide -y flag to offer no-interaction mode? + logger.warn("The source nifi.properties and destination nifi.properties are identical [${outputNiFiPropertiesPath}] so the original will be overwritten") + } + + if (commandLine.hasOption(PASSWORD_ARG)) { + usingPassword = true + if (commandLine.hasOption(KEY_ARG)) { + printUsageAndThrow("Only one of ${PASSWORD_ARG} and ${KEY_ARG} can be used", ExitCode.INVALID_ARGS) + } else { + password = commandLine.getOptionValue(PASSWORD_ARG) + } + } else { + keyHex = commandLine.getOptionValue(KEY_ARG) + usingPassword = !keyHex + } + + if (commandLine.hasOption(USE_KEY_ARG)) { + if (keyHex || password) { + logger.warn("If the key or password is provided in the arguments, '-r'/'--${USE_KEY_ARG}' is ignored") + } else { + usingPassword = false + } + } + } catch (ParseException e) { + if (isVerbose) { + logger.error("Encountered an error", e) + } + printUsageAndThrow("Error parsing command line. (" + e.getMessage() + ")", ExitCode.ERROR_PARSING_COMMAND_LINE) + } + return commandLine + } + + private String getKey(TextDevice device = TextDevices.defaultTextDevice()) { + if (usingPassword) { + if (!password) { + password = readPasswordFromConsole(device) + } + keyHex = deriveKeyFromPassword(password) + password = null + usingPassword = false + + return keyHex + } else { + if (!keyHex) { + keyHex = readKeyFromConsole(device) + } + + return keyHex + } + } + + private static String readKeyFromConsole(TextDevice textDevice) { + textDevice.printf("Enter the master key in hexadecimal format (spaces acceptable): ") + new String(textDevice.readPassword()) + } + + private static String readPasswordFromConsole(TextDevice textDevice) { + textDevice.printf("Enter the password: ") + new String(textDevice.readPassword()) + } + + /** + * Returns the key in uppercase hexadecimal format with delimiters (spaces, '-', etc.) removed. All non-hex chars are removed. If the result is not a valid length (32, 48, 64 chars depending on the JCE), an exception is thrown. + * + * @param rawKey the unprocessed key input + * @return the formatted hex string in uppercase + * @throws KeyException if the key is not a valid length after parsing + */ + private static String parseKey(String rawKey) throws KeyException { + String hexKey = rawKey.replaceAll("[^0-9a-fA-F]", "") + def validKeyLengths = getValidKeyLengths() + if (!validKeyLengths.contains(hexKey.size() * 4)) { + throw new KeyException("The key (${hexKey.size()} hex chars) must be of length ${validKeyLengths} bits (${validKeyLengths.collect { it / 4 }} hex characters)") + } + hexKey.toUpperCase() + } + + /** + * Returns the list of acceptable key lengths in bits based on the current JCE policies. + * + * @return 128 , [192, 256] + */ + public static List<Integer> getValidKeyLengths() { + Cipher.getMaxAllowedKeyLength("AES") > 128 ? [128, 192, 256] : [128] + } + + /** + * Loads the {@link NiFiProperties} instance from the provided file path (restoring the original value of the System property {@code nifi.properties.file.path} after loading this instance). + * + * @return the NiFiProperties instance + * @throw IOException if the nifi.properties file cannot be read + */ + private NiFiProperties loadNiFiProperties() throws IOException { + File niFiPropertiesFile + if (niFiPropertiesPath && (niFiPropertiesFile = new File(niFiPropertiesPath)).exists()) { + String oldNiFiPropertiesPath = System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH) + logger.debug("Saving existing NiFiProperties file path ${oldNiFiPropertiesPath}") + + System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, niFiPropertiesFile.absolutePath) + logger.debug("Temporarily set NiFiProperties file path to ${niFiPropertiesFile.absolutePath}") + + NiFiProperties properties + try { + properties = NiFiPropertiesLoader.withKey(keyHex).load(niFiPropertiesFile) + logger.info("Loaded NiFiProperties instance with ${properties.size()} properties") + return properties + } catch (RuntimeException e) { + if (isVerbose) { + logger.error("Encountered an error", e) + } + throw new IOException("Cannot load NiFiProperties from [${niFiPropertiesPath}]", e) + } finally { + // Can't set a system property to null + if (oldNiFiPropertiesPath) { + System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, oldNiFiPropertiesPath) + } else { + System.clearProperty(NiFiProperties.PROPERTIES_FILE_PATH) + } + logger.debug("Restored system variable ${NiFiProperties.PROPERTIES_FILE_PATH} to ${oldNiFiPropertiesPath}") + } + } else { + printUsageAndThrow("Cannot load NiFiProperties from [${niFiPropertiesPath}]", ExitCode.ERROR_READING_NIFI_PROPERTIES) + } + } + + /** + * Accepts a {@link NiFiProperties} instance, iterates over all non-empty sensitive properties which are not already marked as protected, encrypts them using the master key, and updates the property with the protected value. Additionally, adds a new sibling property {@code x.y.z.protected=aes/gcm/{128,256}} for each indicating the encryption scheme used. + * + * @param plainProperties the NiFiProperties instance containing the raw values + * @return the NiFiProperties containing protected values + */ + private NiFiProperties encryptSensitiveProperties(NiFiProperties plainProperties) { + if (!plainProperties) { + throw new IllegalArgumentException("Cannot encrypt empty NiFiProperties") + } + + ProtectedNiFiProperties protectedWrapper = new ProtectedNiFiProperties(plainProperties) + + List<String> sensitivePropertyKeys = protectedWrapper.getSensitivePropertyKeys() + if (sensitivePropertyKeys.isEmpty()) { + logger.info("No sensitive properties to encrypt") + return plainProperties + } + + // Holder for encrypted properties and protection schemes + Properties encryptedProperties = new Properties() + + AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(keyHex) + protectedWrapper.addSensitivePropertyProvider(spp) + + List<String> keysToSkip = [] + + // Iterate over each -- encrypt and add .protected if populated + sensitivePropertyKeys.each { String key -> + if (!plainProperties.getProperty(key)) { + logger.debug("Skipping encryption of ${key} because it is empty") + } else { + String protectedValue = spp.protect(plainProperties.getProperty(key)) + + // Add the encrypted value + encryptedProperties.setProperty(key, protectedValue) + logger.info("Protected ${key} with ${spp.getIdentifierKey()} -> \t${protectedValue}") + + // Add the protection key ("x.y.z.protected" -> "aes/gcm/{128,256}") + String protectionKey = protectedWrapper.getProtectionKey(key) + encryptedProperties.setProperty(protectionKey, spp.getIdentifierKey()) + logger.info("Updated protection key ${protectionKey}") + + keysToSkip << key << protectionKey + } + } + + // Combine the original raw NiFiProperties and the newly-encrypted properties + // Memory-wasteful but NiFiProperties are immutable -- no setter available (unless we monkey-patch...) + Set<String> nonSensitiveKeys = plainProperties.getPropertyKeys() - keysToSkip + nonSensitiveKeys.each { String key -> + encryptedProperties.setProperty(key, plainProperties.getProperty(key)) + } + NiFiProperties mergedProperties = new StandardNiFiProperties(encryptedProperties) + logger.info("Final result: ${mergedProperties.size()} keys including ${ProtectedNiFiProperties.countProtectedProperties(mergedProperties)} protected keys") + + mergedProperties + } + + /** + * Reads the existing {@code bootstrap.conf} file, updates it to contain the master key, and persists it back to the same location. + * + * @throw IOException if there is a problem reading or writing the bootstrap.conf file + */ + private void writeKeyToBootstrapConf() throws IOException { + File bootstrapConfFile + if (bootstrapConfPath && (bootstrapConfFile = new File(bootstrapConfPath)).exists() && bootstrapConfFile.canRead() && bootstrapConfFile.canWrite()) { + try { + List<String> lines = bootstrapConfFile.readLines() + + updateBootstrapContentsWithKey(lines) + + // Write the updated values back to the file + bootstrapConfFile.text = lines.join("\n") + } catch (IOException e) { + def msg = "Encountered an exception updating the bootstrap.conf file with the master key" + logger.error(msg, e) + throw e + } + } else { + throw new IOException("The bootstrap.conf file at ${bootstrapConfPath} must exist and be readable and writable by the user running this tool") + } + } + + /** + * Accepts the lines of the {@code bootstrap.conf} file as a {@code List <String>} and updates or adds the key property (and associated comment). + * + * @param lines the lines of the bootstrap file + * @return the updated lines + */ + private List<String> updateBootstrapContentsWithKey(List<String> lines) { + String keyLine = "${BOOTSTRAP_KEY_PREFIX}${keyHex}" + // Try to locate the key property line + int keyLineIndex = lines.findIndexOf { it.startsWith(BOOTSTRAP_KEY_PREFIX) } + + // If it was found, update inline + if (keyLineIndex != -1) { + logger.debug("The key property was detected in bootstrap.conf") + lines[keyLineIndex] = keyLine + logger.debug("The bootstrap key value was updated") + + // Ensure the comment explaining the property immediately precedes it (check for edge case where key is first line) + int keyCommentLineIndex = keyLineIndex > 0 ? keyLineIndex - 1 : 0 + if (lines[keyCommentLineIndex] != BOOTSTRAP_KEY_COMMENT) { + lines.add(keyCommentLineIndex, BOOTSTRAP_KEY_COMMENT) + logger.debug("A comment explaining the bootstrap key property was added") + } + } else { + // If it wasn't present originally, add the comment and key property + lines.addAll(["\n", BOOTSTRAP_KEY_COMMENT, keyLine]) + logger.debug("The key property was not detected in bootstrap.conf so it was added along with a comment explaining it") + } + + lines + } + + /** + * Writes the contents of the {@link NiFiProperties} instance with encrypted values to the output {@code nifi.properties} file. + * + * @throw IOException if there is a problem reading or writing the nifi.properties file + */ + private void writeNiFiProperties() throws IOException { + if (!outputNiFiPropertiesPath) { + throw new IllegalArgumentException("Cannot write encrypted properties to empty nifi.properties path") + } + + File outputNiFiPropertiesFile = new File(outputNiFiPropertiesPath) + + if (isSafeToWrite(outputNiFiPropertiesFile)) { + try { + List<String> linesToPersist + File niFiPropertiesFile = new File(niFiPropertiesPath) + if (niFiPropertiesFile.exists() && niFiPropertiesFile.canRead()) { + // Instead of just writing the NiFiProperties instance to a properties file, this method attempts to maintain the structure of the original file and preserves comments + linesToPersist = serializeNiFiPropertiesAndPreserveFormat(niFiProperties, niFiPropertiesFile) + } else { + linesToPersist = serializeNiFiProperties(niFiProperties) + } + + // Write the updated values back to the file + outputNiFiPropertiesFile.text = linesToPersist.join("\n") + } catch (IOException e) { + def msg = "Encountered an exception updating the nifi.properties file with the encrypted values" + logger.error(msg, e) + throw e + } + } else { + throw new IOException("The nifi.properties file at ${outputNiFiPropertiesPath} must be writable by the user running this tool") + } + } + + private + static List<String> serializeNiFiPropertiesAndPreserveFormat(NiFiProperties niFiProperties, File originalPropertiesFile) { + List<String> lines = originalPropertiesFile.readLines() + + ProtectedNiFiProperties protectedNiFiProperties = new ProtectedNiFiProperties(niFiProperties) + // Only need to replace the keys that have been protected + Map<String, String> protectedKeys = protectedNiFiProperties.getProtectedPropertyKeys() + + protectedKeys.each { String key, String protectionScheme -> + int l = lines.findIndexOf { it.startsWith(key) } + if (l != -1) { + lines[l] = "${key}=${protectedNiFiProperties.getProperty(key)}" + } + // Get the index of the following line (or cap at max) + int p = l + 1 > lines.size() ? lines.size() : l + 1 + String protectionLine = "${protectedNiFiProperties.getProtectionKey(key)}=${protectionScheme}" + if (p < lines.size() && lines.get(p).startsWith("${protectedNiFiProperties.getProtectionKey(key)}=")) { + lines.set(p, protectionLine) + } else { + lines.add(p, protectionLine) + } + } + + lines + } + + private static List<String> serializeNiFiProperties(NiFiProperties nifiProperties) { + OutputStream out = new ByteArrayOutputStream() + Writer writer = new GroovyPrintWriter(out) + + // Again, waste of memory, but respecting the interface + Properties properties = new Properties() + nifiProperties.getPropertyKeys().each { String key -> + properties.setProperty(key, nifiProperties.getProperty(key)) + } + + properties.store(writer, null) + writer.flush() + out.toString().split("\n") + } + + /** + * Helper method which returns true if it is "safe" to write to the provided file. + * + * Conditions: + * file does not exist and the parent directory is writable + * -OR- + * file exists and is writable + * + * @param fileToWrite the proposed file to be written to + * @return true if the caller can "safely" write to this file location + */ + private static boolean isSafeToWrite(File fileToWrite) { + fileToWrite && ((!fileToWrite.exists() && fileToWrite.absoluteFile.parentFile.canWrite()) || (fileToWrite.exists() && fileToWrite.canWrite())) + } + + private static String determineDefaultBootstrapConfPath() { + String niFiToolkitPath = System.getenv(NIFI_TOOLKIT_HOME) ?: "" + "${niFiToolkitPath ? niFiToolkitPath + "/" : ""}conf/bootstrap.conf" + } + + private static String determineDefaultNiFiPropertiesPath() { + String niFiToolkitPath = System.getenv(NIFI_TOOLKIT_HOME) ?: "" + "${niFiToolkitPath ? niFiToolkitPath + "/" : ""}conf/nifi.properties" + } + + private static String deriveKeyFromPassword(String password) { + password = password?.trim() + if (!password || password.length() < MIN_PASSWORD_LENGTH) { + throw new KeyException("Cannot derive key from empty/short password -- password must be at least ${MIN_PASSWORD_LENGTH} characters") + } + + // Generate a 128 bit salt + byte[] salt = generateScryptSalt() + int keyLengthInBytes = getValidKeyLengths().max() / 8 + byte[] derivedKeyBytes = SCrypt.generate(password.getBytes(StandardCharsets.UTF_8), salt, SCRYPT_N, SCRYPT_R, SCRYPT_P, keyLengthInBytes) + Hex.encodeHexString(derivedKeyBytes).toUpperCase() + } + + private static byte[] generateScryptSalt() { +// byte[] salt = new byte[16] +// new SecureRandom().nextBytes(salt) +// salt + /* It is not ideal to use a static salt, but the KDF operation must be deterministic + for a given password, and storing and retrieving the salt in bootstrap.conf causes + compatibility concerns + */ + "NIFI_SCRYPT_SALT".getBytes(StandardCharsets.UTF_8) + } + + /** + * Runs main tool logic (parsing arguments, reading files, protecting properties, and writing key and properties out to destination files). + * + * @param args the command-line arguments + */ + public static void main(String[] args) { + Security.addProvider(new BouncyCastleProvider()) + + ConfigEncryptionTool tool = new ConfigEncryptionTool() + + try { + try { + tool.parse(args) + + tool.keyHex = tool.getKey() + + if (!tool.keyHex) { + tool.printUsageAndThrow("Hex key must be provided", ExitCode.INVALID_ARGS) + } + + try { + // Validate the length and format + tool.keyHex = parseKey(tool.keyHex) + } catch (KeyException e) { + if (tool.isVerbose) { + logger.error("Encountered an error", e) + } + tool.printUsageAndThrow(e.getMessage(), ExitCode.INVALID_ARGS) + } + + tool.niFiProperties = tool.loadNiFiProperties() + tool.niFiProperties = tool.encryptSensitiveProperties(tool.niFiProperties) + } catch (CommandLineParseException e) { + if (e.exitCode == ExitCode.HELP) { + System.exit(ExitCode.HELP.ordinal()) + } + throw e + } catch (Exception e) { + if (tool.isVerbose) { + logger.error("Encountered an error", e) + } + tool.printUsageAndThrow(e.message, ExitCode.ERROR_PARSING_COMMAND_LINE) + } + + try { + // Do this as part of a transaction? + synchronized (this) { + tool.writeKeyToBootstrapConf() + tool.writeNiFiProperties() + } + } catch (Exception e) { + if (tool.isVerbose) { + logger.error("Encountered an error", e) + } + tool.printUsageAndThrow("Encountered an error writing the master key to the bootstrap.conf file and the encrypted properties to nifi.properties", ExitCode.ERROR_GENERATING_CONFIG) + } + } catch (CommandLineParseException e) { + System.exit(e.exitCode.ordinal()) + } + + System.exit(ExitCode.SUCCESS.ordinal()) + } +}
