Automated Setup of Sonatype Nexus Repository Manager
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.
- Time spent: 4 hours 23 minutes
- Most played song: Dire Straits - Sultan of Swings
- Word Count: 1379 words
- Groovy Debugging: 7 minutes 32 seconds
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
- a new admin user named
jc
(Juan Carlos) - the user
john.legend
(John Legend) with developer role - the user
jenkins
(Leeroy Jenkins) with deployer role - modify the default password for the default admin to prevent misuse
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:
- provision.sh
- addUpdateScript.groovy
- security.groovy
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:
- The new provisioning possibility of Nexus enables an automatic setup.
- Setup scripts are written in the Groovy language.
- You can test these scripts against a local Nexus Docker container.
- We run an example script of security changes.
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.