Security
Authentication
Currently the only supported authentication mechanism is Kerberos, which is disabled by default. For Kerberos to work a Kerberos KDC is needed, which the user needs to provide. The secret-operator documentation states which kind of Kerberos servers are supported and how they can be configured.
Kerberos is supported starting from HDFS version 3.3.x |
1. Prepare Kerberos server
To configure HDFS to use Kerberos you first need to collect information about your Kerberos server, e.g. hostname and port. Additionally you need a service-user, which the secret-operator uses to create principals for the HDFS services.
2. Create Kerberos SecretClass
Afterwards you need to enter all the needed information into a SecretClass, as described in secret-operator documentation.
The following guide assumes you have named your SecretClass kerberos-hdfs
.
3. Configure HDFS to use SecretClass
The last step is to configure your HdfsCluster to use the newly created SecretClass.
spec:
clusterConfig:
authentication:
tlsSecretClass: tls # Optional, defaults to "tls"
kerberos:
secretClass: kerberos-hdfs # Put your SecretClass name in here
The kerberos.secretClass
is used to give HDFS the possibility to request keytabs from the secret-operator.
The tlsSecretClass
is needed to request TLS certificates, used e.g. for the Web UIs.
4. Verify that Kerberos authentication is required
Use stackablectl stacklet list
to get the endpoints where the HDFS namenodes are reachable.
Open the link (note that the namenode is now using https).
You should see a Web UI similar to the following:
The important part is
Security is on.
You can also shell into the namenode and try to access the file system:
kubectl exec -it hdfs-namenode-default-0 -c namenode — bash -c 'kdestroy && bin/hdfs dfs -ls /'
You should get the error message org.apache.hadoop.security.AccessControlException: Client cannot authenticate via:[TOKEN, KERBEROS]
.
5. Access HDFS
In case you want to access your HDFS it is recommended to start up a client Pod that connects to HDFS, rather than shelling into the namenode. We have an integration test for this exact purpose, where you can see how to connect and get a valid keytab.
Authorization
For authorization we developed hdfs-utils, which contains an OPA authorizer and group mapper. This matches our general OPA authorization mechanisms.
It is recommended to enable Kerberos when doing Authorization, as otherwise you don’t have any security measures at all. There still might be cases where you want authorization on top of a cluster without authentication, as you don’t want to accidentally drop files and therefore use different users for different use-cases. |
In order to use the authorizer you need a ConfigMap containing a rego rule and reference that in your HDFS cluster.
In addition to this you need a OpaCluster that serves the rego rules - this guide assumes it’s called opa
.
---
apiVersion: v1
kind: ConfigMap
metadata:
name: hdfs-regorules
labels:
opa.stackable.tech/bundle: "true"
data:
hdfs.rego: |
package hdfs
import rego.v1
default allow = true
This rego rule is intended for demonstration purposes and allows every operation. For a production setup you will probably need to have something much more granular. We provide a more representative rego rule in our integration tests and in the aforementioned hdfs-utils repository. Details can be found below in the fine-granular rego rules section. Reference the rego rule as follows in your HdfsCluster:
spec:
clusterConfig:
authorization:
opa:
configMapName: opa
package: hdfs
How it works
Take all your knowledge about HDFS authorization and throw it in the bin. The approach we are taking for our authorization paradigm departs significantly from traditional Hadoop patterns and POSIX-style permissions. |
In short, the current rego rules ignore the file ownership, permissions, ACLs and all other attributes files can have. All of this is state in HDFS and clashes with the infrastructure-as-code approach (IaC).
Instead, HDFS will send a request detailing who (e.g. alice/test-hdfs-permissions.default.svc.cluster.local@CLUSTER.LOCAL
) is trying to do what (e.g. open
, create
, delete
or append
) on what file (e.g. /foo/bar
).
OPA then makes a decision if this action is allowed or not.
Instead of chown
-ing a directory to a different user to assign write permissions, you should go to your IaC Git repository and add a rego rule entry specifying that the user is allowed to read and write to that directory.
Group memberships
We encountered several challenges while implementing the group mapper, the most serious of which being that the GroupMappingServiceProvider
interface only passes the shortUsername
when asking for group memberships.
This does not allow us to differentiate between e.g. hbase/hbase-prod.prod-namespace.svc.cluster.local@CLUSTER.LOCAL
and hbase/hbase-dev.dev-namespace.svc.cluster.local@CLUSTER.LOCAL
, as the GroupMapper will get asked for hbase
group memberships in both cases.
Users could work around this to assign unique shortNames by using hadoop.security.auth_to_local
.
This is however a potentially complex and error-prone process.
We also tried mapping the principals from Kerberos 1:1 to the HDFS shortUserName
, so the GroupMapper has access to the full userName
.
However, this did not work, as HDFS only allows "simple usernames", which are not allowed to contain a /
or @
.
Because of these issues we do not use a custom GroupMapper and only rely on the authorizer, which in turn receives a complete UserGroupInformation
object, including the shortUserName
and the precious full userName
.
This has the downside that the group memberships used in OPA for authorization are not known to HDFS.
The implication is thus that you cannot add users to the superuser
group, which is needed for certain administrative actions in HDFS.
We have decided that this is an acceptable approach as normal operations will not be affected.
In case you really need users to be part of the superusers
group, you can use a configOverride on hadoop.user.group.static.mapping.overrides
for that.
Fine-granular rego rules
The hdfs-utils repository contains a more production-ready rego-rule here. With a few minor differences (e.g. Pod names) it is the same rego rule that is used in this integration test.
Access is granted by looking at three bits of information that must be supplied for every rego-rule callout:
-
the identity of the user
-
the resource requested by the user
-
the operation which the user wants to perform on the resource
Each operation has an implicit action-level attribute e.g. create
requires at least read-write permissions.
This action attribute is then checked against the permissions assigned to the user by an ACL and the operation is permitted if this check is fulfilled.
The basic structure of this rego rule is shown below (you can refer to the full here).
package hdfs
import rego.v1
# Turn off access by default.
default allow := false
default matches_identity(identity) := false
# Check access in order of increasing specificity (i.e. identity first).
# Deny access as "early" as possible.
allow if {
some acl in acls
matches_identity(acl.identity)
matches_resource(input.path, acl.resource)
action_sufficient_for_operation(acl.action, input.operationName)
}
# Identity checks based on e.g.
# - explicit matches on the (long) userName or shortUsername
# - regex matches
# - the group membership (simple- or regex-matches on long-or short-username)
matches_identity(identity) if {
...
}
# Resource checks on e.g.
# - explicit file- or directory-mentions
# - inclusion of the file in recursively applied access rights
matches_resource(file, resource) if {
...
}
# Check the operation and its implicit action against an ACL
action_sufficient_for_operation(action, operation) if {
action_hierarchy[action][_] == action_for_operation[operation]
}
action_hierarchy := {
"full": ["full", "rw", "ro"],
"rw": ["rw", "ro"],
"ro": ["ro"],
}
# This should contain a list of all HDFS actions relevant to the application
action_for_operation := {
"abandonBlock": "rw",
...
}
acls := [
{
"identity": "group:admins",
"action": "full",
"resource": "hdfs:dir:/",
},
...
]
The full file in the hdfs-utils repository contains extra documentary information such as a listing of HDFS actions that would not typically be subject to an ACL. In hdfs-utils there is also a test file to verify the rules, where different asserts are applied to the rules. Take the test case below as an example:
test_admin_access_to_developers if {
allow with input as {
"callerUgi": {
"shortUserName": "admin",
"userName": "admin/test-hdfs-permissions.default.svc.cluster.local@CLUSTER.LOCAL",
},
"path": "/developers/file",
"operationName": "create",
}
}
This test passes through the following steps:
1. Does the user or group exist in the ACL?
Yes, a match is found on userName via the corresponding group (admins
, yielded by the mapping groups_for_user
).
2. Does this user/group have permission to fulfill the specified operation on the given path?
Yes, as this ACL item
{
"identity": "group:admins",
"action": "full",
"resource": "hdfs:dir:/",
},
matches the resource on
# Resource mentions a folder higher up the tree, which will will grant access recursively
matches_resource(file, resource) if {
startswith(resource, "hdfs:dir:/")
# directories need to have a trailing slash
endswith(resource, "/")
startswith(file, trim_prefix(resource, "hdfs:dir:"))
}
and the action permission required for the operation create
(rw
) is a subset of the ACL grant (full
).
The various checks for matches_identity and matches_resource are generic, given that the internal list of HDFS actions is comprehensive and the input structure is an internal implementation. This means that only the ACL needs to be adapted to specific customer needs.
|
Wire encryption
In case Kerberos is enabled, Privacy
mode is used for best security.
Wire encryption without Kerberos as well as other wire encryption modes are not supported.