Tutorial 2: Hooking up Gitea with Meerkat DSA
In this tutorial, we will configure a Gitea server to use Meerkat DSA as a source of authentication. After this tutorial, you will be able to add users to a Meerkat DSA instance and have them gain access to a Gitea server automatically. These users will be members of a fictitious organization named Foobar, Inc.
Gitea was chosen for this tutorial because it is one of the easiest applications to start up with almost no configuration. In fact, merely installing it via the Helm chart with no modified settings is sufficient for this tutorial.
This tutorial will assume that you have a Meerkat DSA instance already started, but there is no data other than the Root DSE (which is created automatically if it is not found).
This tutorial will also assume that you have installed and configured the X.500 command-line interface to connect anonymously to your DSA.
How It Works
Gitea, as well as many other programs, support LDAP as a source of user information. Rather than storing user names and passwords in a program's database, a lot of applications can wire up to an LDAP server to fetch information about users, groups, roles, permissions, etc. Unfortunately, DAP is not widely supported, but we are going to change that; for now, you have to use LDAP. Still, for the sake of this tutorial, we can use DAP commands to set up Meerkat DSA, then we will configure Gitea to access our Meerkat DSA instance via LDAP to authenticate users.
Creating the Organization's DSE
Create an organization first-level DSE using the following command:
x500 dap add org 'o=Foobar' --organizationName='Foobar'
We are also going to set the password for the organization, because we will consider this DSE to be the administrative user for the organization. It will have supreme permissions to read and write everything in the organization.
We could have done this
in the previous command using the --userPassword
option, but this is less
secure, because this command might end up in your shell's history, and the
password can be transmitted hashed if we use the apw
command instead, which
means that we can set the password without the DSA ever even knowing what it is.
To do so, run this command:
x500 dap apw 'o=Foobar'
Note that, because we defined our first password in this DSA, no users will be able to add top-level DSEs except this first one.
There is no defined, standard way to restrict who can add first-level DSEs, so Meerkat DSA's solution is to grant the first user with a password the ability to add first-level DSEs. Prior to this first password's existence, any user can add new first-level DSEs. For this reason, it is important to create a user with a password as soon as you have your first-level DSEs created. For clarification, this special privilege is only for adding first-level DSEs; outside of adding these entries, normal access controls apply.
Configure Access Control
First we need to create an access control subentry. Its subtree specification will cover the whole administrative area, which makes our command a lot simpler.
x500 dap add subentry 'o=Foobar,cn=Access Control' --commonName='Access Control'
Now, we want this subentry to become an access control subentry, so run this command:
x500 dap mod become acsub 'o=Foobar,cn=Access Control'
Unfortunately, our next command is not simple at all. We have to define Access Control Information (ACI) items, which define our access control rules. The first one we should add establishes that the organization user (the administrator) can do anything within the organzation.
x500 dap mod add aci 'o=Foobar,cn=Access Control' prescriptive 'Organization administrator' 250 simple \
--userName='o=Foobar' \
--entry \
--allUserAttributeTypesAndValues \
--allOperationalAttributeTypesAndValues \
--grantAdd \
--grantDiscloseOnError \
--grantRead \
--grantRemove \
--grantBrowse \
--grantExport \
--grantImport \
--grantModify \
--grantRename \
--grantReturnDN \
--grantCompare \
--grantFilterMatch \
--grantInvoke
The above command creates a prescriptive ACI item with the tag
"Organization administrator" and a precedence of 250 (pretty high, ensuring that
the administrator will always have administrative access) and simple
authentication level. The userName
parameter specifically names o=Foobar
as
the subject of this ACI item. The entry
and allUserAttributeTypesAndValues
options name entries themselves and all of their attribute types and values as
the objects of this ACI item. Finally, we use every single --grant*
option to
permit the subject every permissions to the objects.
In X.500 directories, subentries are controlled separately by subentry ACI items, which are stored in their respective administrative points. We need to create a separate ACI item for this so we can read and write to our subentry!
x500 dap mod add aci 'o=Foobar' subentry 'Organization administrator' 250 simple \
--userName='o=Foobar' \
--entry \
--allUserAttributeTypesAndValues \
--allOperationalAttributeTypesAndValues \
--grantAdd \
--grantDiscloseOnError \
--grantRead \
--grantRemove \
--grantBrowse \
--grantExport \
--grantImport \
--grantModify \
--grantRename \
--grantReturnDN \
--grantCompare \
--grantFilterMatch \
--grantInvoke
Note that the above command is almost the exact same as the one that came
before it. We just changed prescriptive
to subentry
and we changed the
target object to o=Foobar
. The idea is still the same: we are granting
o=Foobar
all permission to do everything to all subentries within this
access control administrative area.
For now, we only need these two ACI items. In a real directory, you will probably want to define many more before you "flip the switch" to turn on access controls. We are going to turn on access controls now, using the following commands:
x500 dap mod become admpoint 'o=Foobar' -z -a -u
The -z
option will make this administrative point an autonomous administrative
point. (It should already be autonomous, though. The X.500 specifications
require that all first-level DSEs are autonomous administrative points, so
Meerkat DSA automatically adds the autonomous administrative role to first-level
DSEs created without an administrativeRole
attribute.) The -a
option will
make this an access control administrative point for an Access Control Specific
Area (ACSA). The -u
option will make this a subschema administrative area,
which will be useful shortly.
Finally, there is one more step to turn on access controls: add an access control scheme attribute to the administrative point by using this command:
x500 dap mod add acs 'o=Foobar' 2.5.28.1
This adds an access control scheme of 2.5.28.1
to this administrative point,
which enables the Basic Access Control detailed in ITU Recommendation X.501.
Because we enabled access controls, and we only defined ACI items for the
o=Foobar
user, no other users will be able to do anything in this Access
Control Administrative Area (ACSA) unless the administrator, o=Foobar
adds
more ACI items to allow it.
Because you enabled access controls, you will need to change your X.500
configuration file to authenticate as o=Foobar
with a password instead of
anonymously. You will not be able to do anything otherwise.
Creating Schema
Just because you are logged in as a user whose access controls permit you to do
anything does not really mean that you can do anything. You are still
governed by schema rules, and since you did not define any DIT structure rules,
you cannot add any entries beneath o=Foobar
, nor assign any auxiliary object
classes to any entries, nor use contexts, etc. Fortunately, o=Foobar
has the
permissions needed to change this.
We are going to create a new subentry for the subschema.
Technically, there is no reason you could not use the access control subentry for this, but we named this subentry "Access Control," so it would be a misleading name if we also put our subschema in there. Also, it is a requirement of the X.500 specifications that subschema administrative areas apply to the whole area (e.g. the subtree specification has to have 0 minimum, no maximum, no chops, no refinements, and an empty base), which means that, if we changed or added a subtree to our access control subentry, it would make our subschema non-compliant. (Technically, Meerkat DSA just ignores the subtree specifications for subschema subentries, so this shouldn't be a problem for Meerkat DSA, but you don't want to be non-compliant, do you?)
Run the following command:
x500 dap add subentry 'o=Foobar,cn=Subschema' --commonName='Subschema'
Now, we want to make this a subschema subentry, so run this command:
x500 dap mod become subschema 'o=Foobar,cn=Subschema'
Now, we can get to business. You could define your own name forms, but we are
just going to use the orgNameForm
that comes installed in Meerkat DSA by
default, so we just need to define a DIT structure rule to use it. Run this
command to define a DIT structure rule that applies to the administrative point:
x500 dap mod add sr 'o=Foobar,cn=Subschema' 1 2.5.15.3
The command above defines a DIT structure rule with ID 1 that applies to an
administrative point with the name form identified by the object identifier
2.5.15.3
. This object identifier is for orgNameForm
. Since this name form
conforms to what we named our administrative point, and since this DIT structure
rule names no superior structure rules, this structure rule applies to the
administrative point o=Foobar
, which will set the governing structure rule
for o=Foobar
. Now that o=Foobar
has a governing structure rule, you can
place other entries beneath it once you define other structure rules that permit
them.
Run this command to permit the creation of an organizationalPerson
with the
name form orgPersonNameForm
beneath o=Foobar
:
x500 dap mod add sr 'o=Foobar,cn=Subschema' 2 2.5.15.6 -s 1
The above command will create a structure rule with an ID of 2. The object
identifier 2.5.15.6
is for orgPersonNameForm
. The -s 1
says that this
structure rule may permit entries under another entry whose governing structure
rule is 1 (which is our administrative point, o=Foobar
, in this case).
That should be sufficient for our simple use case, but in a real DIT, you would probably want to define many more structure rules, as well as content rules, context use rules, matching rule uses, etc.
Creating Users
Now, we need to have a heart-to-heart: the schema specified in the X.500 specifications is not perfect. Notably, the schema for an organizational person does not permit an email address, which is required by Gitea. Also, none of these attributes are really suitable for storing a username. This means that we are going to have to abuse these attributes' meanings and store email addresses and usernames where they do not belong. Again, this is just a tutorial, and you should define proper schema that does use the correct attribute types.
In the very near future, Meerkat DSA will introduce a LOT more pre-installed
schema, including the inetOrgPerson
object class widely uses in LDAP servers.
This object class would be a great choice for this use case, if you can wait.
For our purposes, we are going to abuse the commonName
attribute to store the
user's username and the title
attribute to store their email address.
Now we can create this troublesome organizational user by running this command:
x500 dap add op 'o=Foobar,cn=cnorris' \
--commonName='cnorris' \
--surname='Norris' \
--title='cnorris@gmail.com'
If that command succeeded, we can now set the password for this user:
x500 dap apw 'o=Foobar,cn=cnorris'
And that's it for the Meerkat DSA setup. Now we can configure Gitea to search
for users under o=Foobar
!
Gitea Configuration
If you installed Gitea via the
Helm chart, there should already be an
admin user, whose credentials can be found in the values.yaml
file in the
linked repository (unless you overwrote them when you ran helm
). Log in as
this user and click on the profile picture in the top right. Select
"Site Administration" from the dropdown. Open the "Authentication Sources" tab.
Click on "Add Authentication Source."
In the Authentication Type dropdown, select "LDAP (via BindDN)." Give it a name,
and specify the hostname and port of your Meerkat DSA instance. Your Bind DN
should be o=Foobar
. Your password should be the password you set for
o=Foobar
. User search base should be o=Foobar
.
Your search filter should be (&(objectClass=organizationalPerson)(cn=%s))
.
The %s
gets replaced with the username, so if you type in cnorris
on the
login page, Gitea will search for an entry with object class
organizationalPerson
and with a common name of cnorris
.
For the other settings:
- Username Attribute:
cn
- First Name Attribute:
cn
- Surname Attribute:
sn
- Email Attribute:
title
Finally, at the bottom, check "Fetch Attributes in Bind DN Context."
I don't fully understand what "Fetch Attributes in Bind DN Context" does (the documentation on Gitea's LDAP integration is a little sparse), but this is what I think it does:
When you use "LDAP (via BindDN)" authentication, the username and password
supplied at the configuration page are used to perform a search against the LDAP
server (in this case, Meerkat DSA) for the existence of a user according to a
search filter, and if that user appears in the results, Gitea permits that login
as that user, but Gitea still has to query that user's attributes to get their
name and email address. By default, Gitea will query the user's entry with the
credentials given at the login page, but since we did not define any access
controls that allow o=Foobar,cn=cnorris
to query his own entry, Gitea
unexpectedly fails to read the attributes it needs. Specifically, it receives a
noSuchObject
error, because, technically, o=Foobar,cn=cnorris
is not even
allowed to know about its own existence! However, when we check "Fetch
Attributes in Bind DN Context," I believe it will use the Bind DN credentials to
query the object's attributes, and o=Foobar
is permitted to read this entry.
And that's it! Save the authentication source and log in using the username
cnorris
and the password you created in Meerkat DSA for this user. Congrats on
making it this far!