<!---
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License. See accompanying LICENSE file.
-->

# YARN Application Security

<!-- MACRO{toc|fromDepth=0|toDepth=1} -->

Anyone writing a YARN application needs to understand the process, in order
to write short-lived applications or long-lived services. They also need to
start testing on secure clusters during early development stages, in order
to write code that actually works.

## How YARN Security works

YARN Resource Managers (RMs) and Node Managers (NMs) co-operate to execute
the user's application with the identity and hence access rights of that user.

The (active) Resource Manager:

1. Finds space in a cluster to deploy the core of the application,
the Application Master (AM).

1. Requests that the NM on that node allocate a container and start the AM in it.

1. Communicates with the AM, so that the AM can request new containers and
manipulate/release current ones, and to provide notifications about allocated
and running containers.

The Node Managers:

1. *Localize* resources: Download from HDFS or other filesystem into a local directory. This
is done using the delegation tokens attached to the container launch context. (For non-HDFS
resources, using other credentials such as object store login details in cluster configuration
files)

1. Start the application as the user.

1. Monitor the application and report failure to the RM.

To execute code in the cluster, a YARN application must:

1. Have a client-side application which sets up the `ApplicationSubmissionContext`
detailing what is to be launched. This includes:

    * A list of files in the cluster's filesystem to be "localized".
    * The environment variables to set in the container.
    * The commands to execute in the container to start the application.
    * Any security credentials needed by YARN to launch the application.
    * Any security credentials needed by the application to interact
    with any Hadoop cluster services and applications.

1. Have an Application Master which, when launched, registers with
the YARN RM and listens for events. Any AM which wishes to execute work in
other containers must request them off the RM, and, when allocated, create
a `ContainerLaunchContext` containing the command to execute, the
environment to execute the command, binaries to localize and all relevant
security credentials.

1. Even with the NM handling the localization process, the AM must itself
be able to retrieve the security credentials supplied at launch time so
that it itself may work with HDFS and any other services, and to pass some or
all of these credentials down to the launched containers.

### Acquiring and Adding tokens to a YARN Application

The delegation tokens which a YARN application needs must be acquired
from a program executing as an authenticated user. For a YARN application,
this means the user launching the application. It is the client-side part
of the YARN application which must do this:

1. Log in via `UserGroupInformation`.
1. Identify all tokens which must be acquired.
1. Request these tokens from the specific Hadoop services.
1. Marshall all tokens into a byte buffer.
1. Add them to the `ContainerLaunchContext` within the `ApplicationSubmissionContext`.

Which tokens are required? Normally, at least a token to access HDFS.

An application must request a delegation token from every filesystem with
which it intends to interact —including the cluster's main FS.
`FileSystem.addDelegationTokens(renewer, credentials)` can be used to collect these;
it is a no-op on those filesystems which do not issue tokens (including
non-kerberized HDFS clusters).

Applications talking to other services, such as Apache HBase and Apache Hive,
must request tokens from these services, using the libraries of these
services to acquire delegation tokens. All tokens can be added to the same
set of credentials, then saved to a byte buffer for submission.

The Application Timeline Server also needs a delegation token. This is handled
automatically on AM launch.

### Extracting tokens within the AM

When the Application Master is launched and any of the UGI/Hadoop operations
which trigger a user login invoked, the UGI class will automatically load in all tokens
saved in the file named by the environment variable `HADOOP_TOKEN_FILE_LOCATION`.

This happens on an insecure cluster along with a secure one, and on a secure
cluster even if a keytab is used by the application. Why? Because the
AM/RM token needed to authenticate the application with the YARN RM is always
supplied this way.

This means you have a relative similar workflow across secure and insecure clusters.

1. Suring AM startup, log in to Kerberos.
A call to `UserGroupInformation.isSecurityEnabled()` will trigger this operation.

1. Enumerate the current user's credentials, through a call of
`UserGroupInformation.getCurrentUser().getCredentials()`.

1. Filter out the AMRM token, resulting in a new set of credentials. In an
insecure cluster, the list of credentials will now be empty; in a secure cluster
they will contain

1. Set the credentials of all containers to be launched to this (possibly empty)
list of credentials.

1. If the filtered list of tokens to renew, is non-empty start up a thread
to renew them.

### Token Renewal

Tokens *expire*: they have a limited lifespan. An application wishing to
use a token past this expiry date must *renew* the token before the token
expires.

Hadoop automatically sets up a delegation token renewal thread when needed,
the `DelegationTokenRenewer`.

It is the responsibility of the application to renew all tokens other
than the AMRM and timeline tokens.

Here are the different strategies

1. Don't. Rely on the lifespan of the application being so short that token
renewal is not needed. For applications whose life can always be measured
in minutes or tens of minutes, this is a viable strategy.

1. Start a background thread/Executor to renew the tokens at a regular interval.
This what most YARN applications do.

## Other Aspects of YARN Security


### AM/RM Token Refresh

The AM/RM token is renewed automatically; the AM pushes out a new token
to the AM within an `allocate` message. Consult the `AMRMClientImpl` class
to see the process. *Your AM code does not need to worry about this process*

### Token Renewal on AM Restart

Even if an application is renewing tokens regularly, if an AM fails and is
restarted, it gets restarted from that original
`ApplicationSubmissionContext`. The tokens there may have expired, so localization
may fail, even before the issue of credentials to talk to other services.

How is this problem addressed? The YARN Resource Manager gets a new token
for the node managers, if needed.

More precisely

1. The token passed by the RM to the NM for localization is refreshed/updated as needed.
1. Tokens in the app launch context for use by the application are *not* refreshed.
That is, if it has an out of date HDFS token —that token is not renewed. This
also holds for tokens for for Hive, HBase, etc.
1. Therefore, to survive AM restart after token expiry, your AM has to get the
NMs to localize the keytab or make no HDFS accesses until (somehow) a new token has been passed to them from a client.

This is primarily an issue for long-lived services (see below).

### Unmanaged Application Masters

Unmanaged application masters are not launched in a container set up by
the RM and NM, so cannot automatically pick up an AM/RM token at launch time.
The `YarnClient.getAMRMToken()` API permits an Unmanaged AM to request an AM/RM
token. Consult `UnmanagedAMLauncher` for the specifics.

### Identity on an insecure cluster: `HADOOP_USER_NAME`

In an insecure cluster, the application will run as the identity of
the account of the node manager, typically something such as `yarn`
or `mapred`. By default, the application will access HDFS
as that user, with a different home directory, and with
a different user identified in audit logs and on file system owner attributes.

This can be avoided by having the client identify the identify of the
HDFS/Hadoop user under which the application is expected to run. *This
does not affect the OS-level user or the application's access rights
to the local machine*.

When Kerberos is disabled, the identity of a user is picked up
by Hadoop first from the environment variable `HADOOP_USER_NAME`,
then from the OS-level username (e.g. the system property `user.name`).

YARN applications should propagate the user name of the user launching
an application by setting this environment variable.

```java
Map<String, String> env = new HashMap<>();
String userName = UserGroupInformation.getCurrentUser().getUserName();
env.put(UserGroupInformation.HADOOP_USER_NAME, userName);
containerLaunchContext.setEnvironment(env);
```

Note that this environment variable is picked up in all applications
which talk to HDFS via the hadoop libraries. That is, if set, it
is the identity picked up by HBase and other applications executed
within the environment of a YARN container within which this environment
variable is set.

### Oozie integration and `HADOOP_TOKEN_FILE_LOCATION`

Apache Oozie can launch an application in a secure cluster either by acquiring
all relevant credentials, saving them to a file in the local filesystem,
then setting the path to this file in the environment variable
`HADOOP_TOKEN_FILE_LOCATION`. This is of course the same environment variable
passed down by YARN in launched containers, as is similar content: a byte
array with credentials.

Here, however, the environment variable is set in the environment
executing the YARN client. This client must use the token information saved
in the named file *instead of acquiring any tokens of its own*.

Loading in the token file is automatic: UGI does it during user login.

The client is then responsible for passing the same credentials into the
AM launch context. This can be done simply by passing down the current
credentials.

```java
credentials = new Credentials(
    UserGroupInformation.getCurrentUser().getCredentials());
```

### Timeline Server integration

The [Application Timeline Server](TimelineServer.html) can be deployed as a secure service
—in which case the application will need the relevant token to authenticate with
it. This process is handled automatically in `YarnClientImpl` if ATS is
enabled in a secure cluster. Similarly, the AM-side `TimelineClient` YARN service
class manages token renewal automatically via the ATS's SPNEGO-authenticated REST API.

If you need to prepare a set of delegation tokens for a YARN application launch
via Oozie, this can be done via the timeline client API.

```java
try(TimelineClient timelineClient = TimelineClient.createTimelineClient()) {
  timelineClient.init(conf);
  timelineClient.start();
  Token<TimelineDelegationTokenIdentifier> token =
      timelineClient.getDelegationToken(rmprincipal));
  credentials.addToken(token.getService(), token);
}
```

### Cancelling Tokens

Applications *may* wish to cancel tokens they hold when terminating their AM.
This ensures that the tokens are no-longer valid.

This is not mandatory, and as a clean shutdown of a YARN application cannot
be guaranteed, it is not possible to guarantee that the tokens will always
be during application termination. However, it does reduce the window of
vulnerability to stolen tokens.

## Securing Long-lived YARN Services

There is a time limit on all token renewals, after which tokens won't renew,
causing the application to stop working. This is somewhere between seventy-two
hours and seven days.

Any YARN service intended to run for an extended period of time *must* have
a strategy for renewing credentials.

Here are the strategies:

### Pre-installed Keytabs for AM and containers

A keytab is provided for the application's use on every node.

This is done by:

1. Installing it in every cluster node's local filesystem.
1. Providing the path to this in a configuration option.
1. The application loading the credentials via
  `UserGroupInformation.loginUserFromKeytab()`.

The keytab must be in a secure directory path, where
only the service (and other trusted accounts) can read it. Distribution
becomes a responsibility of the cluster operations team.

This is effectively how all static Hadoop applications get their security credentials.

### Keytabs for AM and containers distributed via YARN


1. A keytab is uploaded to HDFS.

1. When launching the AM, the keytab is listed as a resource to localize to
the AM's container.

1. The Application Master is configured with the relative path to the keytab,
and logs in with `UserGroupInformation.loginUserFromKeytab()`.

1. When the AM launches the container, it lists the HDFS path to the keytab
as a resource to localize.

1. It adds the HDFS delegation token to the container launch context, so
that the keytab and other application files can be localized.

1. Launched containers must themselves log in via
  `UserGroupInformation.loginUserFromKeytab()`. UGI handles the login, and
  schedules a background thread to relogin the user periodically.

1. Token creation is handled automatically in the Hadoop IPC and REST APIs,
the containers stay logged in via kerberos for their entire duration.

This avoids the administration task of installing keytabs for specific services
across the entire cluster.

It does require the client to have access to the keytab
and, as it is uploaded to the distributed filesystem, must be secured through
the appropriate path permissions/ACLs.

As all containers have access to the keytab, all code executing in the containers
has to be trusted. Malicious code (or code escaping some form of sandbox)
could read the keytab, and hence have access to the cluster until the keys
expire or are revoked.

This is the strategy implemented by Apache Slider (incubating).

### AM keytab distributed via YARN; AM regenerates delegation tokens for containers.

1. A keytab is uploaded to HDFS by the client.

1. When launching the AM, the keytab is listed as a resource to localize to
the AM's container.

1. The Application Master is configured with the relative path to the keytab,
and logs in with `UserGroupInformation.loginUserFromKeytab()`. The UGI
codepath will still automatically load the file references by
`$HADOOP_TOKEN_FILE_LOCATION`, which is how the AMRM token is picked up.

1. When the AM launches a container, it acquires all the delegation tokens
needed by that container, and adds them to the container's container launch context.

1. Launched containers must load the delegation tokens from `$HADOOP_TOKEN_FILE_LOCATION`,
and use them (including renewals) until they can no longer be renewed.

1. The AM must implement an IPC interface which permits containers to request
a new set of delegation tokens; this interface must itself use authentication
and ideally wire encryption.

1. Before a delegation token is due to expire, the processes running in the containers
must request new tokens from the Application Master over the IPC channel.

1. When the containers need the new tokens, the AM, logged in with a keytab,
 asks the various cluster services for new tokens.

(Note there is an alternative direction for refresh operations: from AM
 to the containers, again over whatever IPC channel is implemented between
 AM and containers). The rest of the algorithm: AM regenerated tokens passed
 to containers over IPC.

This is the strategy used by Apache Spark 1.5+, with a netty-based protocol
between containers and the AM for token updates.

Because only the AM has direct access to the keytab, it is less exposed.
Code running in the containers only has access to the delegation tokens.

However, those containers will have access to HDFS from the tokens
passed in at container launch, so will have access to the copy of the keytab
used for launching the AM. While the AM could delete that keytab on launch,
doing so would stop YARN being able to successfully relaunch the AM after any
failure.

### Client-side Token Push

This strategy may be the sole one acceptable to a strict operations team: a client process
running on an account holding a Kerberos TGT negotiates with all needed cluster services
for new delegation tokens, tokens which are then pushed out to the Application Master via
some RPC interface.

This does require the client process to be re-executed on a regular basis; a cron or Oozie job
can do this. The AM will need to implement an IPC API over which renewed
tokens can be provided. (Note that as Oozie can collect the tokens itself,
all the updater application needs to do whenever executed is set up an IPC
connection with the AM and pass up the current user's credentials).

## Securing YARN Application Web UIs and REST APIs

YARN provides a straightforward way of giving every YARN application SPNEGO authenticated
web pages: it implements SPNEGO authentication in the Resource Manager Proxy.

YARN web UI are expected to load the AM proxy filter when setting up its web UI; this filter
will redirect all HTTP Requests coming from any host other than the RM Proxy hosts to an
RM proxy, to which the client app/browser must re-issue the request. The client will authenticate
against the principal of the RM Proxy (usually `yarn`), and, once authenticated, have its
request forwared.

As a result, all client interactions are SPNEGO-authenticated, without the YARN application
itself needing any kerberos principal for the clients to authenticate against.

Known weaknesses in this approach are:

1. As calls coming from the proxy hosts are not redirected, any application running
on those hosts has unrestricted access to the YARN applications. This is why in a secure cluster
the proxy hosts *must* run on cluster nodes which do not run end user code (i.e. not run YARN
NodeManagers and hence schedule YARN containers, nor support logins by end users).

1. The HTTP requests between proxy and YARN RM Server are not currently encrypted.
That is: HTTPS is not supported.

## Securing YARN Application REST APIs

YARN REST APIs running on the same port as the registered web UI of a YARN application are
automatically authenticated via SPNEGO authentication in the RM proxy.

Any REST endpoint (and equally, any web UI) brought up on a different port does not
support SPNEGO authentication unless implemented in the YARN application itself.

## Checklist for YARN Applications

Here is the checklist of core actions which a YARN application must do
to successfully launch in a YARN cluster.

### Client

`[ ]` Client checks for security being enabled via `UserGroupInformation.isSecurityEnabled()`

In a secure cluster:

`[ ]` If `HADOOP_TOKEN_FILE_LOCATION` is unset, client acquires delegation tokens
 for the local filesystems, with the RM principal set as the renewer.

`[ ]` If `HADOOP_TOKEN_FILE_LOCATION` is unset, client acquires delegation tokens
for all other services to be used in the YARN application.

`[ ]` If `HADOOP_TOKEN_FILE_LOCATION` is set, client uses the current user's credentials
as the source of all tokens to be added to the container launch context.

`[ ]` Client sets all tokens on AM `ContainerLaunchContext.setTokens()`.

`[ ]` Recommended: if it is set in the client's environment,
client sets the environment variable `HADOOP_JAAS_DEBUG=true`
in the Container launch context of the AM.

In an insecure cluster:

`[ ]` Propagate local username to YARN AM, hence HDFS identity via the
`HADOOP_USER_NAME` environment variable.

### App Master

`[ ]` In a secure cluster, AM retrieves security tokens from `HADOOP_TOKEN_FILE_LOCATION`
environment variable (automatically done by UGI).

`[ ]` A copy the token set is filtered to remove the AM/RM token and any timeline
token.

`[ ]` A thread or executor is started to renew threads on a regular basis.

`[ ]` Recommended: AM cancels tokens when application completes.

### Container Launch by AM

`[ ]` Tokens to be passed to containers are passed via
`ContainerLaunchContext.setTokens()`.

`[ ]` In an insecure cluster, propagate the `HADOOP_USER_NAME` environment variable.

`[ ]` Recommended: AM sets the environment variable `HADOOP_JAAS_DEBUG=true`
in the Container launch context if it is set in the AM's environment.

### Launched Containers

`[ ]` Call `UserGroupInformation.isSecurityEnabled()` to trigger security setup.

`[ ]` A thread or executor is started to renew threads on a regular basis.

### YARN service

`[ ]` Application developers have chosen and implemented a token renewal strategy:
shared keytab, AM keytab or client-side token refresh.

`[ ]` In a secure cluster, the keytab is either already in HDFS (and checked for),
or it is in the local FS of the client, in which case it must be uploaded and added to
the list of resources to localize.

`[ ]` If stored in HDFS, keytab permissions should be checked. If the keytab
is readable by principals other than the current user, warn,
and consider actually failing the launch (similar to the normal `ssh` application.)

`[ ]` Client acquires HDFS delegation token and and attaches to the AM Container
Launch Context,

`[ ]` AM logs in as principal in keytab via `loginUserFromKeytab()`.

`[ ]` (AM extracts AM/RM token from the `HADOOP_TOKEN_FILE_LOCATION` environment
variable).

`[ ]` For launched containers, either the keytab is propagated, or
the AM acquires/attaches all required delegation tokens to the Container Launch
context alongside the HDFS delegation token needed by the NMs.

## Testing YARN applications in a secure cluster.

It is straightforward to be confident that a YARN application works in secure
cluster. The process to do so is: test on a secure cluster.

Even a single VM-cluster can be set up with security enabled. If doing so,
we recommend turning security up to its strictest, with SPNEGO-authenticated
Web UIs (and hence RM Proxy), as well as IPC wire encryption. Setting the
kerberos token expiry to under an hour will find kerberos expiry problems
early —so is also recommended.

`[ ]` Application launched in secure cluster.

`[ ]` Launched application runs as user submitting job (tip: log `user.name`
system property in AM).

`[ ]` Web browser interaction verified in secure cluster.

`[ ]` REST client interation (GET operations) tested.

`[ ]` Application continues to run after Kerberos Token expiry.

`[ ]` Application does not launch if user lacks Kerberos credentials.

`[ ]` If the application supports the timeline server, verify that it publishes
events in a secure cluster.

`[ ]` If the application integrates with other applications, such as HBase or Hive,
verify that the interaction works in a secure cluster.

`[ ]` If the application communicates with remote HDFS clusters, verify
that it can do so in a secure cluster (i.e. that the client extracted any
delegation tokens for this at launch time)

## Important

*If you don't test your YARN application in a secure Hadoop cluster,
it won't work.*

And without those tests: *your users will be the ones to find out
that your application doesn't work in a secure cluster.*

Bear that in mind when considering how much development effort to put into
Kerberos support.
