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())
+    }
+}

Reply via email to