|
|
|
Security considerations
Project Zero implements system-level authentication and authorization. Applications can take advantage of Project Zero security by defining security rules that determine which resources are protected, by what means they are protected, and what users and groups are allowed to access those resources.
The following sections of this article provide information about authentication, authorization and requiring SSL:
Types of authentication
Project Zero provides implementations of the following types of authentication:
- Basic authentication
- Basic authentication is defined in RFC 2617.
- Form-based authentication
- The full-page form login that leverages external redirect.
- Single sign-on authentication
- The URL based login that leverages HTTP request parameters.
- OpenID authentication
- OpenID consumer based authentication for third party authentication with an OpenID provider.
- Programmatic login authentication
- Provides an API-based authentication model.
Security rules
Project Zero security leverages the user service that defines the users and groups referenced in the security rules. Security rules define protected resources. Resources that are not covered by any security rule are not protected.
If multiple rules match a given resource, only the first rule defined determines how that resource is protected.
Each rule contains the following information:
- conditions
- Any Zero condition expression. Commonly based on URI patterns (e.g.
(/request/path =~ /customers(/.*)?) ) and methods (e.g. (/request/method == [PUT|POST])).
- authType
- The type of authentication: Basic, Form or SSO.
- users
- An optional list of users, separated by commas, that are allowed to access these resources.
- groups
- An optional list of groups, separated by commas, that are allowed to access these resources.
- roles
- An optional list of roles, separated by commas, that are allowed to access these resources.
Advanced
The following example shows basic authentication configuration:
@include "security/rule.config"{
"conditions": "(/request/path =~ /customers(/.*)?) && (/request/method ==[PUT|POST]) ",
"authType" : "Basic",
"users" : ["myUser1"],
"groups" : ["CUSTOMERS"]
}
This rule protects all resources that match the URI pattern /customers(/.*)? and are accessed with the HTTP method of PUT or POST with basic authentication. In this example, users must be either the user named myUser1 or be a member of the CUSTOMERS group.
You can configure an optional realm to be used by basic authentication as shown in the following example:
/config/security/realm="Secure Area"
If no realm is set, the default realm is Secure Area.
Best practices for configuring security rules
The recommended URI pattern is /<prefix>(/.*)?. For example, a pattern of /foo(/.*)? would match requests for /foo, /foo/ and /foo/bar/index.html. Using this pattern that is based on prefixes, prevents clients from circumventing security rules by adding pathInfo to the end of a request. Also, because of the default file serving behavior when a directory is accessed, if you want the default files protected, you need to be sure to protect the directory. For example, if you don't want /foo/index.html served, you should be sure to protect /foo/. This is accomplished using the prefix-based rules.
Authorizing URIs
For certain types of applications, you can group resources according to who owns them or global to any authenticated users. Securing such applications can be hard because you might not know user names and groups in advance and, even if you did, they might be too numerous to track manually. Project Zero enables authorizing URIs with user names or group names or all authenticated users.
Authorizing URIs for all authenticated users
You can create authorization rules to allow any authenticated user to access a protected resource. To enable this function, Zero provides a Group named 'ALL_AUTHENTICATED_USERS'. For example, the following rule will permit any authenticated user to access the resource that starts with /formauth/customers.
@include "security/rule.config"{
"conditions": "/request/path =~ /formauth/customers(/.*)?",
"authType" : "Form",
"groups" : ["ALL_AUTHENTICATED_USERS"]
}
Authorizing URIs with user names
You can authorize URIs with user names. As an example, a file sharing service might give every user a URI like /resources/shares/{user}, where anything that comes after {user} is part of the user's file share. Securing such a service so that only the user can modify his shared resources can be handled in Project Zero by creating a relationship between the URI pattern and the list of authorized users. Project Zero has a special configuration variable, remoteUser, that can be added to URI patterns to indicate that only the authenticated user whose name matches the URI token can perform certain operations. For example, we can limit write operations on our file sharing service as shown in the following example:
@include "security/rule.config"{
"conditions": "(/request/path =~ /resources/shares/{remoteUser}) && (/request/method == [PUT|POST|DELETE])",
"authType" : "Basic",
"condition" : "urimatches"
}
With this rule in place, PUT, POST, and DELETE operations are only allowed if the value of {remoteUser} is equal to the name of the person who is making the request. If the resource in question is /resources/shares/joe/file1.txt, then only code acting on behalf of user joe can perform PUT operations on it.
Rules that use the {remoteUser} variable can allow access to other users as well. For example, if we want our site administrator to be able to delete any shared file, we can update the rule as shown in the following example:
@include "security/rule.config"{
"conditions": "(/request/path == /resources/shares/{remoteUser}) && (/request/method == [PUT|POST])",
"authType" : "Basic",
}
@include "security/rule.config"{
"conditions": "(/request/path == /resources/shares/{remoteUser} ) && (/request/method == DELETE)",
"authType" : "Basic",
"users": ["admin"]
}
Authorizing URIs with group names
Just as you can incorporate user names into your URIs to ease the configuration of user-owned resources, you can also include a group name for group-owned resources. All of the semantics of the Authorizing URIs with user names section can be applied to groups. To do this, replace {remoteUser} with {remoteGroup}, and your authorization rule must contain a groups property that contains the list of authorized groups, as shown in the following example:
@include "security/rule.config"{
"conditions": "/request/path == /resources/calendars/{remoteGroup}",
"authType" : "Basic",
"groups": ["newyork-team", "raleigh-team", "chicago-team"]
}
Retrieving user information
After Project Zero has verified the credentials, they are available in the request context as /request/subject Map exposed using the remote_user and groups keys as shown in the following examples in java and grooovy:
// java snippet
String remoteUser = GlobalContext.zget(Request.Subject.remoteUser);
java.util.List groupNames = GlobalContext.zget(Request.Subject.groups);
// groovy snippet
RemoteUser: <%=request.subject.remoteUser[] %>
Groups: <%=request.subject.groups[] %>
This information is only available when accessing protected resources. If you want to be able to use this information when accessing non-protected resources you can store it in the user's session.
Requiring SSL
To require HTTPS access to a resource, create a requireSSL=true rule. For example, the following rule protects the resource with basic authentication while also requiring the connection to be SSL:
@include "security/rule.config"{
"conditions": "/request/path =~ /employees(/.*)?",
"authType" : "Basic",
"requireSSL" : true
}
The SSL requirement can be combined with requirements for authentication, authorization or both by adding the requireSSL=true line to any stanza. It can also be used where there are no other security requirements. For example, the same resource in the previous example can be designated as requiring SSL but not be protected as shown in the following example:
@include "security/requireSSL.config"{
"conditions" : "/request/path =~/employees(/.*)?",
"requireSSL": true
}
If a resource that requires SSL access is accessed without it, a 403 (Access Forbidden) status code is returned to the client.
Caching of secured resources
In an effort to limit the caching of secured resources on the client, Zero security sets the Cache-Control header for all secured resources and their associated login forms (OpenID, Form-based Login, Single Sign-On) that are provided as part of the Zero security model.
The default Cache-Control header value is "must-revalidate","max-age=0","private". To override this default setting, the following zero configuration is provided with the example demonstrating the default Cache-Control value.
# max-age/must-revalidate can be dropped if there are only 1.1
# caches as it would improve cacheability
/config/security/cacheControl = ["must-revalidate","max-age=0","private"]
Note Custom security handlers that override the secure event or provide a custom security implementation for obtaining a user's credentials will need to handle the Cache-Control header themselves. To add this Cache-Control header programmatically, the following API is provided.
zero.core.security.TokenService.setCacheControlHeader();
Using roles
Advanced Topic Use of roles is an advanced topic and should not be required for most applications.
By including role based authorization, Project Zero provides a method for developers to specify which roles can access specific resources. For example, you could provide access only to users where the users role consists of users from the groups developers, sysadmins, and testers.
To enable role based authentication requires the specification of roles to users or groups or both. For example, for development purposes, you could include the following lines in the application configuration:
# developmentRoles.config
/config/security/roles/ADMIN/users=["testUser"]
When in production, you might want to enable the productionRoles.config as shown in the following example:
# productionRoles.config
/config/security/roles/ADMIN/groups=["ROOT"]
The following example shows how to leverage roles for authorization:
@include "developmentRoles.config"
#Authorization rules
@include "security/rule.config"{
"conditions": "/request/path =~formauth/adminRoleOnly(/.*)?",
"authType" : "Form",
"roles" : ["ADMIN"]
}
After a user has successfully authenticated for the REST resource /formauth/adminRoleOnly(/.*)?, the global context is updated to reflect not only the remote user (remoteUser) and groups for the current subject but also the set of roles of which this user is a member. The following examples in java and groovy filters users based on whether the user is part of a role:
// java snippet
boolean isUserInAdminRole =
GlobalContext.zget(Request.Subject.roles).contains("ADMIN");
// groovy snippet
boolean isUserInAdminRole = request.subject.roles[].contains("ADMIN");
|