Using Nexus OSS repository in an isolated environment

Dan Erez
Cloud Computing
November 30, 2021

Using Nexus OSS repository in an isolated environment

No internet access, no libraries?

In our workplace, like in many other big firms, government offices, and banks, the internal network is isolated from the outside world. Nothing goes out and nothing goes in. But, how can you conveniently work with open source libraries if you cannot use maven or npm (and other repositories) to fetch needed libraries?


Nexus (https://www.sonatype.com/product-nexus-repository), and other similar products can be installed in pairs, to bridge between networks. But AFAIK that topology can only work in synchronous mode and you cannot add your own asynchronous checks like using sandboxes or anti virus checks - and that can be a problem. Plus, we wanted to use only the free, open source version of nexus (OSS), as we try to rely on open source as much as possible.

So we came up with a solution for that, drawn here:

No alt text provided for this image



Basically, we act as a proxy to the internal Nexus, receiving its requests (initiated by developers issuing ‘mvn install’ or ‘npm install’ commands). Next, we pass the requests to the external network via IBM’s Data Power (which verifies the request, in case someone tries to exploit the end point - similar products exist as well). Data Power passes the request to an external proxy, with internet access. This external proxy downloads the required library, along with all its dependencies, and passes it on to the organizational file checking system (sandboxes, antivirus, etc.). If the file is approved it is copied into the internal network (via a vault or a similar mechanism). There, the internal proxy comes into live again, picks up those libraries and uploads them into Nexus. Then the developer can retry to fetch the libraries and...  voilà! The libraries are there and he can use them in his code.

Inner Proxy

The inner proxy is a Java Spring Boot application. It has two parts:

  • The first part is a REST controller, exposing a GET url that accepts requests from Nexus. Using a servlet filter these requests are caught and sent to the DataPower (Listing 1).
  • The second part watches a designated folder, to which the approved libraries are copied (wrapped in a zip file). It extracts the libraries from the zip and uploads them into Nexus (Listing 2).

Outer Proxy

The outer proxy is also a Java Spring Boot application. It accepts GET requests for the needed libraries. Then it downloads the libraries and their dependencies from maven or npm, and places them in a designated folder. The organizational file checking system will take the files from there ,check them, and if they are approved they will be moved into the inner network wrapped as a zip file (Listing 3).

That’s it! You are welcome to use a similar solution in your environment and start enjoying the wealth of open source libraries out there…

Listing 1 - Intercepting the requests

public class NexusInterceptingFilter extends DispatcherServlet {

private static final long serialVersionUID = 1L;

private static final String DP_URL_KEY = "dp_url";

private static final String SEPARATOR = "/";

protected static Logger logger = LoggerFactory.getLogger(NexusInterceptingFilter.class);


@Override

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

if (!(request instanceof ContentCachingRequestWrapper)) {

request = new ContentCachingRequestWrapper(request);

}

if (!(response instanceof ContentCachingResponseWrapper)) {

response = new ContentCachingResponseWrapper(response);

}

HandlerExecutionChain handler = getHandler(request);

try {

super.doDispatch(request, response);

} finally {

handleRequests(request, response, handler);

updateResponse(response);

}

}


private void handleRequests(HttpServletRequest requestToCache, HttpServletResponse responseToCache,

HandlerExecutionChain handler) {

logger.info(">>>>> Fetching: "+ requestToCache.getRequestURI());

// This is for java only

if (requestToCache.getRequestURI().endsWith(".pom")) {

// make the request for the lib ( etc. abbot/abbot/0.13.0/abbot-0.13.0.pom )

RestTemplate rest = new RestTemplate();

Environment env = (Environment)ApplicationContextProvider.getBeanByClass(Environment.class);

String dpUrl = env.getProperty(DP_URL_KEY);

String theUrl = dpUrl+"?libName="+getLibRequest(requestToCache.getRequestURI());

logger.info("The request: "+theUrl);

rest.getForObject(theUrl, Void.class);

}

}


private String getLibRequest(String requestURI) {

if (requestURI.endsWith(".pom")) {

String[] parts = requestURI.split(SEPARATOR);

String version = parts[parts.length - 2];

String artifactId = parts[parts.length - 3];

StringBuilder grp = new StringBuilder();

for(int i=2;i<parts.length - 3;i++) {

grp.append(parts[i]).append(".");

}

return grp.toString().substring(0,grp.length()-1)+SEPARATOR+

artifactId+SEPARATOR+version;

}

return null;

}


private void updateResponse(HttpServletResponse response) throws IOException {

ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response,

ContentCachingResponseWrapper.class);

responseWrapper.copyBodyToResponse();

}


}

Listing 2 - Upload to Nexus

@Component

public class ScheduledLibrariesReader {

private static final String MANIFEST = "manifest.txt";

@Value("${vaultFolder}")

private String vaultFolder;

@Value("${tempFolde}")

private String tempFolder;

@Value("${nexus_url}")

private String nexusUploadUrl;


protected static Logger logger = LoggerFactory.getLogger(ScheduledLibrariesReader.class);

@Scheduled(fixedRate = 60000)

public void readFiles() throws Exception {

// Read all files (= zip for java)

try (Stream<Path> paths = Files.walk(Paths.get(vaultFolder))) {

paths.filter(Files::isRegularFile).forEach(f -> {

try {

// open the zip to a temp folder we create

String theFolder = parseAndRename(f);

// upload all files from this dir, according to the manifest

upload(theFolder);

// remove zip and files

removeAll(theFolder, f);

} catch (Exception e) {

throw new RuntimeException(e);

}

});

}

}


private String parseAndRename(Path zipFile) throws Exception {

// output dir is under the temp folder in a new folder, based on the zip name

String dirName = tempFolder + "/"

+ zipFile.getFileName().toString().substring(0, zipFile.getFileName().toString().lastIndexOf("."));

File destDir = new File(dirName);

if (!destDir.exists()) {

destDir.mkdir();

}

byte[] buffer = new byte[1024];

ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile.toFile()));

ZipEntry zipEntry = zis.getNextEntry();

while (zipEntry != null) {

File newFile = new File(destDir, zipEntry);

FileOutputStream fos = new FileOutputStream(newFile);

int len;

while ((len = zis.read(buffer)) > 0) {

fos.write(buffer, 0, len);

}

fos.close();

zipEntry = zis.getNextEntry();

}

zis.closeEntry();

zis.close();

//

return dirName;

}


@SuppressWarnings("unchecked")

private void upload(String theFolder) throws Exception {

List<String> lines = Files.readAllLines(new File(theFolder+"/"+MANIFEST).toPath());

for (String line : lines) {

String[] parts = line.split(":"); // group/artifact/version//filename

ByteArrayOutputStream stdout = new ByteArrayOutputStream();

   ByteArrayOutputStream stderr = new ByteArrayOutputStream();


   Map map = new HashMap();

   map.put("file", new File(theFolder + "/" + parts[3]));

   CommandLine cmdLine = new CommandLine(MAVEN_LOC+"mvn.cmd");

   cmdLine.addArgument("deploy:deploy-file");

   cmdLine.addArgument("-DgroupId=" + parts[0]);

   cmdLine.addArgument("-DartifactId=" + parts[1]);

   cmdLine.addArgument("-Dversion=" + parts[2]);

   cmdLine.addArgument("-DgeneratePom=true");

   cmdLine.addArgument("-Dpackaging=jar");

   cmdLine.addArgument("-DrepositoryId=nexus");

   cmdLine.addArgument("-Durl="+nexusUploadUrl);

   cmdLine.addArgument("-Dfile=${file}");

   cmdLine.setSubstitutionMap(map);

   DefaultExecutor executor = new DefaultExecutor();  

   PumpStreamHandler streamHandler = new PumpStreamHandler(stdout, stderr);

   executor.setStreamHandler(streamHandler);

   DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();

   executor.execute(cmdLine, resultHandler);

   resultHandler.waitFor();

   logger.info("Exit code for uploading "+line+" is "+resultHandler.getExitValue());

   logger.info("Error is:\n"+stderr.toString());

   logger.info("Output is:\n"+stdout.toString());

}

}

Listing 3 - Downloading from Maven

@RestController

public class ProxyController {

private static final String MANIFEST = "manifest.txt";

private static final String SEPARATOR = "\\";

@Value("${vaultFolder:d:/Temp/repo}")

private String vaultFolder;

protected static Logger logger = LoggerFactory.getLogger(ProxyController.class);

@GetMapping("/fetch")

// Expecting groupId/artifactId/version

public void fetchLib(@RequestParam String libName) throws Exception {

// Currently supporting Maven and NPM

if (isMavenLib(libName)) {

List<Path> libs = fetchMavenLibs(libName);

moveToVault(libs);

} else {

// assume npm

fetchJsonLib(libName);

}

}


private void moveToVault(List<Path> libs) throws IOException {

StringBuilder manifest = new StringBuilder();

String zipFile = vaultFolder + "/nexus_" + System.currentTimeMillis() + ".zip";

byte[] buffer = new byte[1024];

ZipOutputStream zos = null;

FileInputStream fis = null;

try {

FileOutputStream fos = new FileOutputStream(zipFile);

zos = new ZipOutputStream(fos);

//

for (Path path : libs) {

manifest.append(getPartsString(path)).append("\n");

// add to zip

fis = new FileInputStream(path.toFile());

// begin writing a new ZIP entry, positions the stream to the start of the entry data

zos.putNextEntry(new ZipEntry(path.getFileName().toString()));

int length;

while ((length = fis.read(buffer)) > 0) {

zos.write(buffer, 0, length);

}

zos.closeEntry();

// close the InputStream

fis.close();

//Files.move(p, new File(vaultFolder + "/" + p.getFileName()).toPath());

}

// add the manifest to the zip

zos.putNextEntry(new ZipEntry(MANIFEST));

zos.write(manifest.toString().getBytes());

zos.closeEntry();

//

} finally {

zos.close();

}


}


private List<Path> fetchMavenLibs(String libName) {

logger.info("Fetching lib " + libName);

String groupId = getGroupId(libName);

String artifactId = getArtifactId(libName);

String version = getVersion(libName);

JkDependencySet deps = JkDependencySet.of().and(groupId + ":" + artifactId + ":" + version)

//   e.g. "com.threerings:tripleplay:1.4")

.withDefaultScopes(COMPILE_AND_RUNTIME);

JkDependencyResolver resolver = JkDependencyResolver.of(JkRepo.ofMavenCentral());

List<Path> libs = resolver.resolve(deps, RUNTIME).getFiles().getEntries();

logger.info("Downloaded lobs: {}", libs);

return libs;

}

}


Dan Erez

Experienced Chief Technology Officer and Software Architect with a demonstrated history of working in the computer software industry. Skilled in micro services, AWS, Java , node.js, Vue.js. Strong engineering professional with a M.Sc. focused in Computer Science from The Open University.

Keep Reading

Newsletter EuropeClouds.com

Thank you! Your submission has been received!

Oops! Something went wrong while submitting the form