Automated Setup of Sonatype Nexus Repository Manager

May 17, 2019

In this article, we give a small introduction to a particular component of software development. We are going to introduce the Sonatype Nexus Repository Manager OSS, in the following Nexus, and its purpose in the software development/continuous integration process.

Providing such a crucial component, we must ensure that the setup is reproducible in terms of quality and time. I will provide a small example of the capabilities of Nexus for automatic setup and provisioning.

There are other products like Nexus on the market which have similar features. Other products and capabilities are not our concern in this article, but we are happy to help you if you would like to know more or need help with Nexus.

Stats for Nerds

Find below some meta-data during the work and writing process.

Leeroy Jenkins is a character name for a player character created by Ben Schulz in Blizzard Entertainment's MMORPG World of Warcraft. The character became popular due to his role in a video which became an Internet meme. Knowledge of the video has spread beyond the game's community and into other online and mainstream media. The video is even used as part of military education programs to parody the real-life military concept that "no plan survives first contact."

wikipedia.org

The Purpose of Nexus

Nexus is a repository manager. For the non-developers, it sounds fancy. Let's try to shed some light to it. I will reduce the information to a high-level overview.

The Java software development process produces artifacts. Artifacts are java archives (jar, war, ear) that have different purposes. They contain bundled code. Automated build systems like Apache Maven or Gradle, ensuring dependency management, building and packaging of those artifacts. These artifacts can either be deployed in runtime environments or be reused in other software components.

To do so, we need a place to store them and make them accessible to others. That is the purpose of Nexus.

Nexus allows you to proxy, collect, and manage your dependencies so that you are not continually juggling a collection of java archives. It makes it easy to distribute your software. Internally, you configure your build to publish artifacts to Nexus, and they then become available to other developers.

You can think of a repository like you would think of a library. It is a server that stores and retrieves files, which we refer to as artifacts. When you write a piece of software, you might depend on internal and external libraries. Nexus is the repository manager for your development.

Why Automation?

Doing manual configuration and setups are, in consequence, error-prone. If you are doing that more than once, it becomes cumbersome and repetitive. There is a more straightforward solution to make that setup more reproducible.

Nexus provides a Provisioning REST API which allows to upload scripts to Nexus and execute those scripts. This feature allows us to do such kind of setup in a scripted manner.

I am going to configure Nexus with a script. The benefit of a script is, we can redo the configuration from a fresh start. Another benefit is the easy integration with Ansible, the automation solution from RedHat.

I use example scripts from the Sonatype Nexus Community as a baseline. You can find all source files in this repository.

Security Use-Case

We limit our setup example to security. We could do a lot more, like adding npm and docker repositories, but it goes beyond the scope of the present article. We apply several security actions for Nexus.

In our security case, we create

Furthermore, we disable anonymous access to the repository that holds our precious software components.

Nexus and Docker

The simplest way to do experiments related to the Nexus Provisioning API is to use a Docker setup. You can get a default installation and repeat it.

We start Nexus as a Docker container. Docker is a container technology that we are not going to explain.

Sonatype offers an OpenShift compatible docker image. See also the Docker configuration.

To start in the foreground:

docker run --rm -p 8081:8081 --name nexus sonatype/nexus3

You should see some log output like this:

2019-05-17 12:34:32,242+0000 INFO  [jetty-main-1] *SYSTEM org.sonatype.nexus.bootstrap.osgi.BootstrapListener - Initializing
2019-05-17 12:34:32,244+0000 INFO  [jetty-main-1] *SYSTEM org.sonatype.nexus.bootstrap.osgi.BootstrapListener - Loading OSS Edition
2019-05-17 12:34:32,245+0000 INFO  [jetty-main-1] *SYSTEM org.sonatype.nexus.bootstrap.osgi.BootstrapListener - Installing: nexus-oss-edition/3.16.1.02
2019-05-17 12:34:33,801+0000 INFO  [jetty-main-1] *SYSTEM org.sonatype.nexus.bootstrap.osgi.BootstrapListener - Installed: nexus-oss-edition/3.16.1.02

At the end you see:

-------------------------------------------------

Started Sonatype Nexus OSS 3.16.1-02

-------------------------------------------------
2019-05-17 12:35:06,950+0000 INFO  [qtp519767623-58] *UNKNOWN org.apache.shiro.session.mgt.AbstractValidatingSessionManager - Enabling session validation scheduler...
2019-05-17 12:35:06,978+0000 INFO  [qtp519767623-58] *UNKNOWN org.sonatype.nexus.internal.security.anonymous.AnonymousManagerImpl - Using default configuration: AnonymousConfiguration{enabled=true, userId='anonymous', realmName='NexusAuthorizingRealm'}

As you can see the access for anonymous is allowed.

Hands-On Provisioning

Nexus scripts are in the Groovy language. Apache Groovy is a powerful, optionally typed and dynamic language.

Our example covers three files:

The provision.sh script is the starting point. It contains the default password for the default admin user.

The script calls addUpdateScript.groovy. security.groovy is our main concern or content added to Nexus.

Disable Anonymous Access

We prevent unknown users our access to Nexus.

security.setAnonymousAccess(false)
log.info('Anonymous access disabled')

Create new Admin User

We create an additional administrator user. Just in case the admin user is compromised, so we name the user jc ;-).

def adminRole = ['nx-admin']
def adminUser = security.addUser('juan.carlos', 'Juan', 'Carlos', 'jc@mimacom.com', true, 'admin456', adminRole)
log.info('User jc created')

Create Developer User

John Legend is our developer that have permission to use our Nexus.

def devPrivileges = ['nx-healthcheck-read', 'nx-healthcheck-summary-read']
def anoRole = ['nx-anonymous']

security.addRole('developer', 'Developer', 'User with privileges to allow read access to repo content and healtcheck', devPrivileges, anoRole)
log.info('Role developer created')

// use the new role to create a user 
def devRoles = ['developer']
def johnLegend = security.addUser('john.legend', 'John', 'Legend', 'john.legend@mimacom.com', true, 'changMe456', devRoles)
log.info('User john.legend created')

Create Deployer User

Leeroy Jenkins is the user for Jenkins CI. Jenkins is a continuous integration (CI) server that builds the artifacts and uploads them to Nexus.

def depPrivileges = ['nx-repository-view-*-*-add', 'nx-repository-view-*-*-edit']
def roles = ['developer']

security.addRole('deployer', 'Deployer', 'User with privileges to allow deployment all repositories', depPrivileges, roles)
log.info('Role deployer created')
def depRoles = ['deployer']
def lJenkins = security.addUser('jenkins', 'Leeroy', 'Jenkins', 'leeroy.jenkins@mimacom.com', true, 'changMe789', depRoles)
log.info('User jenkins created')

Change the Default Password

The default password for user admin in Nexus is admin123. The first security measure is to change the default password. If the default password remains unchanged and the associated access persists, the attack surface is conceivably large. Needless to say that you shouldn't use the new example passwords, e.g. admin456.

def user = security.securitySystem.getUser('admin')
user.setEmailAddress('vinh-vinh@mimacom.com')
security.securitySystem.updateUser(user)
security.securitySystem.changePassword('admin','admin456')
log.info('default password for admin changed')

Run Provisioning

After you start the Docker container, make provision.sh executable and run it.

# chmod +x provision.sh
# sh provision.sh

The resulting output:

Script named 'security' will be created
Stored scripts are now: [security]

Published security.groovy as security

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8081 (#0)
* Server auth using Basic with user 'admin'
> POST /service/rest/v1/script/security/run HTTP/1.1
> Host: localhost:8081
> Authorization: Basic YWRtaW46YWRtaW4xMjM=
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type: text/plain
>
< HTTP/1.1 200 OK
< Date: Fri, 17 May 2019 11:49:17 GMT
< Server: Nexus/3.16.1-02 (OSS)
< X-Content-Type-Options: nosniff
< Content-Type: application/json
< Content-Length: 1199
<
{
  "name" : "security",
  "result" : "[{\"userId\":\"juan.carlos\",\"status\":\"active\",\"roles\":[{\"roleId\":\"nx-admin\",\"source\":\"default\"}],\"firstName\":\"Juan\",\"version\":null,\"lastName\":\"Carlos\",\"emailAddress\":\"jc@mimacom.com\",\"readOnly\":false,\"source\":\"default\",\"name\":\"Juan Carlos\"},{\"userId\":\"john.legend\",\"status\":\"active\",\"roles\":[{\"roleId\":\"developer\",\"source\":\"default\"}],\"firstName\":\"John\",\"version\":null,\"lastName\":\"Legend\",\"emailAddress\":\"john.legend@mimacom.com\",\"readOnly\":false,\"source\":\"default\",\"name\":\"John Legend\"},{\"userId\":\"jenkins\",\"status\":\"active\",\"roles\":[{\"roleId\":\"deployer\",\"source\":\"default\"}],\"firstName\":\"Leeroy\",\"version\":null,\"lastName\":\"Jenkins\",\"emailAddress\":\"leeroy.jenkins@mimacom.com\",\"readOnly\":false,\"source\":\"default\",\"name\":\"Leeroy Jenkins\"},{\"userId\":\"admin\",\"status\":\"active\",\"roles\":[{\"roleId\":\"nx-admin\",\"source\":\"default\"}],\"firstName\":\"Administrator\",\"version\":\"1\",\"lastName\":\"User\",\"emailAddress\":\"vinh-vinh@mimacom.com\",\"readOnly\":false,\"source\":\"default\",\"name\":\"Administrator User\"}]"
* Connection #0 to host localhost left intact
}
Successfully executed security script

Provisioning Scripts Completed

Summary

What we have learned:

You can integrate the successful configuration of Nexus with Ansible into your staging and production environments. For instance, if you deploy Nexus in OpenShift invoke the configuration after the pod is started.

About the author: Vinh Nguyên

Loves to code, hike and mostly drink black coffee. Favors Apache Kafka, Elasticsearch, Java Development and 80's music.

Comments
Join us