I am Dimitri and I am a user and contributor of Verdaccio.
Today, I will explain how I migrated my private dockerized Verdaccio registry from v3.x
to v4.x
.
I will also configure verdaccio-ldap
to authenticate my users against LDAP.
First of all, I wante to congratulate everyone who tested, contributed to Verdaccio v4 .
V4 include bunch of improvment, optimization, starting with the Web UI made completely redesigned with ReactJS and MaterialUI.
Not only that security has been improved with the introduction of the optional JWT
, but v4
also bring a new feature to unpublish
packages.
Let's upgrade it!
Prerequisite
- Read verdaccio documentation.
- Read verdaccio-ldap documentation.
- A backup of your v3
storage
directory (just in case). - A running LDAP database (such as OpenLDAP).
- Docker installed.
Goal
- Update Verdaccio from
v3.x
tov4.x.
- Configure LDAP.
- Configure JWT. (Read more)
Dockerfile
This is my tree structure:
├── conf
│ └── config.yaml
└── Dockerfile
First thing I had to do was to update my Dockerfile
, this is what I have done:
FROM verdaccio/verdaccio:4.3
USER root
RUN npm i && npm i verdaccio-ldap
COPY conf /verdaccio/conf
RUN chown -R $VERDACCIO_USER_UID /verdaccio
USER verdaccio
v3.x
is now using by defaultverdaccio
user for security reason. This is why need to switch toroot
user to usenpm
.- We install
verdaccio-ldap
but you can install any plugin. (Only if you don't want theverdaccio-htaccess
builtin solution to be your user database) - Later, you MUST solve the
storage
directory permissions and ownership.
Configuration
This is my config.yaml
:
storage: /verdaccio/storage
max_body_size: 100mb
web:
enable: true
title: My private NPM registry
gravatar: true
sort_packages: asc
security:
legacy: false
api:
jwt:
sign:
expiresIn: 30d
notBefore: 0
web:
sign:
expiresIn: 7d
notBefore: 1
auth:
ldap:
type: ldap
client_options:
url: 'ldap://ldap.verdaccio.private.rocks'
# Only required if you need auth to bind
adminDn: 'cn=readonly,dc=verdaccio.private,dc=rocks'
adminPassword: '********'
# Search base for users
searchBase: 'dc=verdaccio.private,dc=rocks'
searchFilter: '(&(uid={{username}})(memberOf=cn=npm_users,ou=npm,ou=groups,ou=developers,dc=verdaccio.private,dc=rocks))'
# # If you are using groups, this is also needed
groupDnProperty: 'cn'
groupSearchBase: 'ou=npm,ou=groups,ou=developers,dc=verdaccio.private,dc=rocks'
# If you have memberOf support on your ldap
searchAttributes: ['*', 'memberOf']
# Else, if you don't (use one or the other):
# groupSearchFilter: '(memberUid={{dn}})'
#
# Optional, default false.
# If true, then up to 100 credentials at a time will be cached for 5 minutes.
cache: false
# Optional
reconnect: true
# a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
'@scope-*/*':
# scoped packages
access: npm_access
publish: npm_publisher
unpublish: npm_publisher
'@scope/*':
# scoped packages
access: npm_access
publish: npm_publisher
unpublish: npm_publisher
'@*/*':
# scoped packages
access: $all
publish: $authenticated
proxy: npmjs
'**':
# allow all users (including non-authenticated users) to read and
# publish all packages
#
# you can specify usernames/groupnames (depending on your auth plugin)
# and three keywords: "$all", "$anonymous", "$authenticated"
access: $all
# allow all known users to publish packages
# (anyone can register by default, remember?)
publish: $authenticated
# if package is not available locally, proxy requests to 'npmjs' registry
proxy: npmjs
# log settings
logs:
- { type: stdout, format: pretty, level: trace }
# - {type: file, path: verdaccio.log, level: info}
listen:
- 0.0.0.0:4873
Available options are explained in details in Configuration File documentation.
I will describe the most important here.
LDAP
We use verdacio-ldap
plugin to authenticate with LDAP.
searchFilter
I use the memberOf
overlay, and this LDAP query will allow to connect only users present in a defined LDAP group.
If you are not using the memberOf
overlay, you can allow all users to login as follow:
searchFilter: '(&(uid={{username}}))'
groupSearchBase
I use an organization unit to store all my group for verdaccio-ldap security.
groupSearchBase: 'ou=npm,ou=groups,ou=developers,dc=verdaccio.private,dc=rocks'
Security
packages
You SHOULD use scope for all your privates packages, in this scenario, we use LDAP groups for access
, publish
and unpublish
.
Note that we do not use proxy: npmjs
because they only exist on our private registry.
I recommend you to create scope for all of your private packages, and reserve the group on npmjs registry so no one will be able to publish publicly in it in the futur.
'@scope-*/*':
access: npm_access
publish: npm_publisher
unpublish: npm_publisher
'@scope/*':
# scoped packages
access: npm_access
publish: npm_publisher
unpublish: npm_publisher
They are some public package on npmjs registry which are scoped, this will proxy all the request to npmjs registry.
I recommend not to change this, otherwise you might get issue to download them.
'@*/*':
# scoped packages
access: $all
publish: $authenticated
proxy: npmjs
For all other packages, to prevent anyone to use our registry, we just allow $authenticated
to publish.
We also use proxy: npmjs
so we also serve all the public package on npmjs registry.
We allow $all
to download from our registry, because it is public, but if you want to preserve your bandwidth or just forbid unknown user to authenticate, just use $authenticated
as well.
'**':
access: $all
publish: $authenticated
proxy: npmjs
security
You should (and I recommend it) use JWT
security, otherwise your LDAP server will received an authentication request for each request.
If you don't mind, you can keep legacy: true
.
If you do use the JWT authentication, then all your users will have to re-authenticate with npm adduser
.
security:
legacy: false
api:
jwt:
sign:
expiresIn: 30d
notBefore: 0
web:
sign:
expiresIn: 7d
notBefore: 0
expiresIn
: You will have to reauthenticate after30 days
, and7 days
on the web UI.notBefore
: Just set it to0
, it is the time to wait before the JWT starts it's validity.
Build the image
Use docker build
to build the new image.
-t
will give the nameverdaccio-3-ldap
to the new image.
means that the Dockerfile is in the current working directory.
$ docker build -t verdaccio-3-ldap .
Sending build context to Docker daemon 14.34kB
Step 1/7 : FROM verdaccio/verdaccio:4.3
4.3: Pulling from verdaccio/verdaccio
e7c96db7181b: Already exists
50958466d97a: Already exists
56174ae7ed1d: Already exists
284842a36c0d: Already exists
38829697cf41: Pull complete
67d4be407dc1: Pull complete
75921a7a709e: Pull complete
27621c093247: Pull complete
b5dd63eea3d5: Pull complete
3d5fd2ab9d4d: Pull complete
Digest: sha256:2a79d82601596f1889f2fe99d397c8900bf473c6682624cc0c37288896617e99
Status: Downloaded newer image for verdaccio/verdaccio:4.3
---> 03eefd251eef
# etc...
Step 7/7 : USER verdaccio
---> Running in 2426b01499b8
Removing intermediate container 2426b01499b8
---> 5e36f29f5374
Successfully built 5e36f29f5374
Successfully tagged verdaccio-3-ldap:latest
Your image is ready, you can push it to your private docker registry, or on Docker Hub if you can host private images.
Do not publish it publicly unless you remove all your LDAP credentials in the configuration.
To do so, remove the config.yaml
within the Dockerfile
:
FROM verdaccio/verdaccio:4.3
USER root
RUN npm i && npm i verdaccio-ldap
- COPY conf /verdaccio/conf
RUN chown -R $VERDACCIO_USER_UID /verdaccio
USER verdaccio
And mount the configuration on startup with a volume:
docker run -v $(pwd)/config.yaml:/verdaccio/conf/config.yaml verdaccio-3-ldap
Run the service
You will have to mount the storage
volume when using Docker
, to do that, just use -v
with docker run
command:
docker run -v /srv/verdaccio/storage:/verdaccio/storage verdaccio-3-ldap
Remember, you have made a backup of your storage directory, now let's fix permissions
and ownership
to finish verdaccio migration.
Because within the docker container, the user is verdaccio
, you can't run chown
and chmod
commands. Just do it directly from your host as root
:
cd /srv/verdaccio/ # the location depend of your installation
chmod -R 777 storage
VERDACCIO_USER_UID=10001 # unless you have changed it
chown -R $VERDACCIO_USER_UID storage
Test the service
First, fill appropriate LDAP group for all your LDAP users that should have access to the private npm registry.
We call
$IP
the IP address of the server. If you serve it overhttps
behind a reverse proxy or directly, then fix all the following command to use the right protocol.
This is all the test I have done while configurating verdaccio, before going to production:
plugin
[x]
it should work withverdaccio-htaccess
whenverdaccio-ldap
is not installed.[x]
it should work withverdaccio-htaccess
whenauth.ldap
is disabled andverdaccio-ldap
is installed.[x]
it should work with oneverdaccio-ldap
.[x]
it should work withverdaccio-htaccess
and fallback toverdaccio-ldap
through web.[x]
it should work withverdaccio-htaccess
and fallback toverdaccio-ldap
through npm. Either useverdaccio-htaccess
orverdaccio-ldap
, it is useless to use both, even if the web work with the two, thenpm --add-user
command will fail.
npm
[x]
npm --adduser
should work with different users.[x]
npm --adduser
should fail with wrong user/password.[x]
it should auth with JWT and theverdaccio-ldap
plugin.[x]
it should auth with legacy and theverdaccio-ldap
plugin.[x]
npm i
in CI that download from the registry should spam the LDAP with authentication requests with legaxy.[x]
npm i
in CI that download from the registry should not spam the LDAP with authentication requests with JWT.
Web
The new design with material-UI is super nice btw.
[x]
it should authenticate with different users.[x]
it should fail to authenticate with different users and wrong password.[x]
it should show packages to users withaccess
permissions.[x]
it should hide packages to users withoutaccess
permissions.
Packages permissions
[x]
access
should work with a user with perms.[x]
access
should fail with a user without perms..[x]
access
should work with a user in ldap group with perms.[x]
access
should fail with a user not in ldap group with perms.[x]
publish
should work with a user with perms.[x]
publish
should work with a user in ldap group with perms.[x]
publish
should fail with a user not in ldap group without perms.[x]
unpublish
should work with a user with perms.[x]
unpublish
should fail with a user without perms.[x]
unpublish
should work with a ldap group with perms.[x]
unpublish
should fail with a user not in ldap group with perms.
Web
- Test the web interface, generally
http://$IP:4873
if you are not using a reverse proxy.
After login, you won't be able to see private scopped package if you don't have the access
group.
npm
Because we use the JWT, you must re-authenticate, this is how we do:
npm adduser --registry http://$IP --always-auth
If you want to use it just for a specific scope:
npm set @scope:registry http://$IP
If you want to use it as your default proxy for npm:
npm set registry http://$IP
Conclusion and thanks
Docker, LDAP are a great way to authenticate users from your organization. In this article, you have learned how to setup verdaccion with LDAP and Docker.
I have to thank the teams and community behind verdaccio projects, specially Juan Picado, Daniel Refde and Sergio Hg for their help on the GitHub issues and the discord chat.
Also, but not less important, I want to thank all the people that makes Verdaccio possible, contributing, donating, documenting, and more.
I hope it is well explained and you people of verdaccio are able to reproduce a configuration that fit with your LDAP.
To me it took a while to figure out the different errors I had and the most annoying things was those manual step to fix the permissions and access.
If you have any question, please check at the FAQ below, or feel free to reply to this blog post.
If you 😍 Verdaccio as I do, helps them to grow by donating to the project via OpenCollective.
Thanks for reading and long life to Verdaccio !
FAQ
- Can we use two authentication plugin together such as
verdaccio-htaccess
?
No you can't, but pull request are welcome.
- Does my registry users need to re-authenticate?
If you use JWT
for authentication, which I recommend, they will all have to re-authenticate.
- I have
404
or401
errors with good credentials.
This is due to wrong permissions or ownership in storage
directory, dont forget to chmod -R 777 /verdaccio/storage
and chown -R $VERDACCIO_USER_UID /verdaccio
.
- When should I use
--always-auth
when running--add-user
?
Since npm 7.10, the config option always-auth
has no effect. You can safely remove it from your configuration.