Candlepin supports four modes of authentication. Two of the modes require that a request provide explicit authentication. The other two defer authentication to external systems. They are presented below in the order in which they are checked. By default, all authentication modes are enabled. If one mode authenticates a user or a consumer successfully, then no other types are checked.
Once authenticated a Principal is created with a collection of Roles which are discussed below.
OAuth is used to provided a secure connection with an external system which does the authentication. You can learn more about configuring OAuth here
Trusted auth behaves much like OAuth, but with no additional headers used to
protect against replay attacks. It is the most simple form of integration, but
requires firewalls and other OS level hardening to protect the engine against
attacks. With this form of authentication, the engine looks for either a
cp-consumer
header or a cp-user
header. If either is present, then the
engine will treat them as authenticated. This authentication mode can be
enabled with:
candlepin.auth.trusted.enabled = true
If desired the cp-lookup-permissions
header can be set to “true”, which will
trigger a call back to the configured user service asking for the users actual
permissions, which will then be enforced in Candlepin.
HTTP Basic Auth is used to pass user credentials. This authentication mode can be enabled with:
candlepin.auth.basic.enabled = true
Identify certificates, which are created by Candlepin, can be used to authenticate consumers. This authentication mode can be enabled with:
candlepin.auth.ssl.enabled = true
and in Tomcat’s server.xml
:
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="want" sslProtocol="TLS"
keystoreFile="conf/keystore"
truststoreFile="conf/keystore"
keystorePass="password"
truststorePass="password" />
keystore
is a Java keystore created by keytool
; it should contain the CA
certificate used to sign client certificates so the server can verify them.
Principals are created by whichever authentication mechanism is used and carry a list of permissions.
The two main types of principals in use today:
Objects carried by principals which grant access to domain model objects of a particular type. These sometimes match based on an access level. There are several types of permissions, created as needed to solve specific situations we encounter. Some permission types in use today:
Permissions also have the ability to inject filters into some database queries allowing certain records to be created. This is primarily useful for “my system” administrators who need to be able to list the consumers in an org, but may only be able to see those that match their username.
Roles are a database construct allowing us to store information about what permissions the role grants and which users they are associated with. Because most deployments of Candlepin do now actually use our user service, roles are largely out of the picture.
Roles are defined by a name, a set of permission blueprints the role grants, and a set of users who have this role. Permission blueprints are just enough information for the engine to know which concrete Permission class to create and what parameters to pass it.
Note that a principal does not carry roles, only permissions. In the case of a UserPrincipal, the permissions carried would be the entire set of all permissions from all their roles. In this regard Roles are somewhat just an implementation detail of the currently configured UserServiceAdapter.
Roles can be created and managed by super admins over the REST API. (See /roles resource) This assumes that the UserServiceAdapter supports them, otherwise an exception will be thrown indicating the operation is not supported.
Currently Candlepin supports the following roles:
Whenever a request comes in, the authentication code kicks in resulting in a configured Principal class. Subsequently, an implementation of the AbstractAuthorizationFilter is triggered to examine the method being invoked and determine if the Principal should be able to call that method.
There are three implementations of the AbstractAuthorizationFilter. During servlet initialization, the AuthorizationFeature is invoked for each resource method and it determines which filter is appropriate for that method. That determination is only done at initialization time and does not impose additional overhead during a request.
If the method has an @SecurityHole
annotation, the
SecurityHoleAuthorizationFilter is used. If the method has any parameters with
an @Verify
annotation, the VerifyAuthorizationFilter is used. Otherwise, the
SuperAdminAuthorizationFilter is used.
WARNING: The AuthorizationFeature class implements the JAX RS 2.0 DynamicFeature
interface. Do not use the @Provider
annotation on filters meant to be applied
to methods using a DynamicFeature
implementation as Resteasy will get confused
about which resource methods to actually apply the filter to.
The SecurityHoleAuthorizationFilter is a no-op filter and all methods assigned to this filter will always authorize successfully.
The SuperAdminAuthorizationFilter is a very strict filter that only authorizes Principals who have the superadmin role.
The VerifyAuthorizationFilter provides object level granularity for
authorization. Any object with an @Verify
annotation will be examined to make
sure that the requesting Principal has access to that object with the required
access level.
An example:
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Path("{consumer_uuid}")
@Transactional
public void updateConsumer(
@PathParam("consumer_uuid") @Verify(Consumer.class) String uuid,
Consumer consumer, @Context Principal principal) {
In the example above the @Verify annotation tells the VerifyAuthorizationFilter we need to lookup a consumer with the given UUID, and ask the current principal if we have access to it.
Objects annotated with @Verify
can specify a required access level, but if
none is specified, the VerifyAuthorizationFilter uses a default access level
based on the HTTP verb of the request.
The default access level requirements are
The Verify annotation can also carry a sub-resource, which allows for situations where for example, a consumer needs to be able to list an org’s pools (GET /owners/{key}/pools}, but we do not want to grant it read-only access to the entire org. The sub-resource is passed through to the Permissions when asking if the principal has access, and each permission can be as specific or general as it likes with respect to this information.