In terms of policy, I think that it's safe enough to say "if you're using
the command-line client, we'll run and enforce hooks". If you let people
use the scheduler API directly, all bets are off - and that's fine. The
hooks, as specified, are specifically hooks for command-line operations.

   -Mark


On Thu, Apr 3, 2014 at 6:19 PM, Kevin Sweeney <kevi...@apache.org> wrote:

> On Thu, Apr 3, 2014 at 2:24 PM, Mark Chu-Carroll <mchucarr...@apache.org
> >wrote:
>
> > A while ago, I sent around a proposal for how to do hooks in the v2
> command
> > line for Aurora. Since then, I've been talking to a variety of people
> about
> > what they'd like to be able to do with hooks, and I've revised the
> > proposal. Please respond with any comments, criticisms, praise, or
> > brickbats.
> >
> >     -Mark
> >
> > # Command Hooks for the Aurora Client
> >
> > ## Introduction/Motivation
> >
> > We've got hooks in the client that surround API calls. These are
> > pretty awkward, because they don't correlate with user actions. For
> > example, suppose we wanted a policy that said users weren't allowed to
> > kill all instances of a production job at once.
> >
> > Right now, all that we could hook would be the "killJob" api call. But
> > kill (at least in newer versions of the client) normally runs in
> > batches. If a user called killall, what we would see on the API level
> > is a series of "killJob" calls, each of which specified a batch of
> > instances. We woudn't be able to distinguish between really killing
> > all instances of a job (which is forbidden under this policy), and
> > carefully killing in batches (which is permitted.) In each case, the
> > hook would just see a series of API calls, and couldn't find out what
> > the actual command being executed was!
> >
> > For most policy enforcement, what we really want to be able to do is
> > look at and vet the commands that a user is performing, not the API
> > calls that the client uses to implement those commands.
> >
> > So I propose that we add a new kind of hooks, which surround noun/verb
> > commands. A hook will register itself to handle a collection of (noun,
> > verb) pairs. Whenever any of those noun/verb commands are invoked, the
> > hooks methods will be called around the execution of the verb. A
> > pre-hook will have the ability to reject a command, preventing the
> > verb from being executed.
> >
> > ## Registering Hooks
> >
> > These hooks will be registered three ways:
> > * System hooks file. There will be an global configuration file, much
> like
> > the
> >   current `clusters.json`, which can define hooks.
> > * Project hooks file. If a file named `AuroraHooks` is in the project
> > directory
> >   where an aurora command is being executed, that file will be read,
> >   and its hooks will be registered.
> > * Configuration plugins. A configuration plugin can register hooks using
> an
> > API.
> >   Hooks registered this way are, effectively, hardwired into the client
> > executable.
> >
> > The order of execution of hooks is unspecified: they may be called in
> > any order. There is no way to guarantee that one hook will execute
> > before some other hook.
> >
> >
> > ### Global Hooks
> >
> > Commands registered by the python call are called _global_ hooks,
> > because they will run for all configurations, whether or not they
> > specify any hooks in the configuration file.
> >
> > In the implementation, hooks are registered in the module
> > `apache.aurora.client.cli.hooks`, using the class `HookRegistry`.  A
> > global hook can be registered by calling `HookRegistry.registerHook`
> > in a configuration plugin.
> >
> > ### Hook Files
> >
> > A hook file is a file containing Python source code. It will be
> > dynamically loaded by the Aurora command line executable. After
> > loading, the client will check the module for a global variable named
> > "hooks", which contains a list of hook objects, which will be added to
> > the hook registry.
> >
> > The global hooks file will, by default, be located in
> > `/etc/aurora/hooks`. A project hooks file will be named `AuroraHooks`,
> > and will be located in either the directory where the command is being
> > executed, or one of its parent directories, up to the nearest git
> > repository base.
> >
> > ### The API
> >
> >     class Hook(object)
> >       @property
> >       def id(self):
> >         """Returns an identifier for the hook."
> >
> >       def get_nouns(self):
> >         """Return the nouns that have verbs that should invoke this
> > hook."""
> >
> >       def get_verbs(self, noun):
> >         """Return the verbs for a particular noun that should invoke his
> > hook."""
> >
> >       @abstractmethod
> >       def pre_command(self, noun, verb, context, commandline):
> >         """Execute a hook before invoking a verb.
> >         * noun: the noun being invoked.
> >         * verb: the verb being invoked.
> >         * context: the context object that will be used to invoke the
> verb.
> >           The options object will be initialized before calling the hook
> >         * commandline: the original argv collection used to invoke the
> > client.
> >         Returns: True if the command should be allowed to proceed; False
> if
> > the command
> >         should be rejected.
> >         """
> >
> >       def post_command(self, noun, verb, context, commandline, result):
> >         """Execute a hook after invoking a verb.
> >         * noun: the noun being invoked.
> >         * verb: the verb being invoked.
> >         * context: the context object that will be used to invoke the
> verb.
> >           The options object will be initialized before calling the hook
> >         * commandline: the original argv collection used to invoke the
> > client.
> >         * result: the result code returned by the verb.
> >         Returns: nothing
> >         """
> >
> >     class HookRegistry(object):
> >       @classmethod
> >       def register_hook(self, hook):
> >         pass
> >
> > ## Skipping Hooks
> >
> > In a perfect world, hooks would represent a global property or policy
> > that should always be enforced. Unfortunately, we don't live in a
> > perfect world, which means that sometimes, every rule needs to get
> > broken.
> >
> > For example, an organization could decide that every configuration
> > must be checked in to source control before it could be
> > deployed. That's an entirely reasonable policy. It would be easy to
> > implement it using a hook. But what if there's a problem, and the
> > source repos is down?
> >
> > The easiest solution is just to allow a user to add a `--skip-hooks`
> > flag to the command-line. But doing that means that an organization
> > can't actually use hooks to enforce policy, because users can skip
> > them whenever they want.
> >
> > Instead, we'd like to have a system where it's possible to create
> > hooks to enforce policy, and then include a way of building policy
> > about when hooks can be skipped.
> >
> > I'm using sudo as a rough model for this. Many organizations need to
> > give people the ability to run privileged commands, but they still
> > want to have some control. Sudo allows them to specify who is allowed
> > to run a privileged command; where they're allowed to run it; and what
> > command(s) they're allowed to run.  All of that is specified in a
> > special system file located in `/etc/sudoers` on a typical unix
> > machine.
>
>
> The command-line client is not actually capable of enforcing these rules.
> We need a middleman somewhere that's holding the keys needed to talk to the
> scheduler API if we want this to actually be enforceable, otherwise the
> user can just talk to the scheduler API directly. If it's actually a
> requirement that these rules be *enforceable* I don't see a way around
> that, otherwise they're just advisory.
>
> >
> > ### Specifying when hooks can be skipped
> >
> > The sudoers file has a terrible syntax, so I'm not going to try to
> > adopt it; instead, I'm going to stick with the Pystachio-based
> > configuration syntax that we use in Aurora. A rule that permits a
> > group of users to skip hooks is defined using a Pystachio struct:
> >
> >     class HookRule(Struct):
> >       roles = List(String)
> >       commands = Map(String, List(String))
> >       arg_patterns = List(String)
> >   hooks = List(String)
> >
> > * `roles` is a list of role names, or regular expressions that range over
> > role
> >   names. This rule gives permission to those users to skip hooks.
> > * `commands` is a map from nouns to lists of verbs. If a command `aurora
> n
> > v`
> >   is being executed, this rule allows the hooks to be skipped if
> >   `v` is in `commands[n]`. If this is empty, then all commands can be
> > skipped.
> > * `arg_patterns` is a list of regular expressions ranging over
> parameters.
> >   If any of the parameters of the command match the parameters in this
> > list,
> >   the hook can be skipped.
> > * `hooks` is a list of hook identifiers which can be skipped by a user
> >   that satisfies this rule.
> >
> > The hooks file defines a global variable `hook_rules`, which is a list of
> > `HookRule` objects. If any of the hook rules matches, then the command
> > can be run with hooks skipped.
> >
> > For example, the following is a hook rules file which allows:
> > * The admin (role admin) to skip any hook.
> > * Any user to skip hooks for test jobs.
> > * A specific group of users to skip hooks for jobs in cluster `east`
> > * Another group of users to skip hooks for `job kill` in cluster `west`.
> >
> >     allow_admin = HookRule(roles=['admin'])
> >     allow_test = HookRule(roles=['.*'],  arg_patterns=['.*/.*/test/.*'])
> >     allow_east_users = HookRule(roles=['john', 'mary', 'mike', 'sue'],
> >         arg_patterns=['east/.*/.*./*'])
> >     allow_west_kills = HookRule(roles=['anne', 'bill', 'chris'],
> >       commands = { 'job': ['kill']}, arg_patterns = ['west/.*/.*./*'])
> >
> >     hook_rules = [allow_admin, allow_test, allow_east_users,
> > allow_west_kills]
> >
> > ## Skipping Hooks
> >
> > To skip a hook, a user uses a command-line option, `--skip-hooks`. The
> > option can either
> > specify specific hooks to skip, or "all":
> >
> > * `aurora --skip-hooks=all job create east/bozo/devel/myjob` will create
> a
> > job
> >   without running any hooks.
> > * `aurora --skip-hooks=test,iq create east/bozo/devel/myjob` will create
> a
> > job,
> >   and will skip only the hooks named "test" and "iq".
> >
> >
> > ## Changes
> >
> > Major changes between this and the last version of this proposal.
> > * Command hooks can't be declared in a configuration file. There's a
> simple
> >   reason why: hooks run before a command's implementation is invoked.
> >   Config files are read during the commands invocation if necessary. If
> the
> >   hook is declared in the config file, by the time you know that it
> should
> >   have been run, it's too late. So I've removed config-hooks from the
> >   proposal. (API hooks defined by configs still work.)
> > * Skipping hooks. We expect aurora to be used inside of large
> >   organizations. One of the primary use-cases of hooks is to create
> >   enforcable policy that are specific to an organization. If hooks
> >   can just be skipped because a user wants to skip them, it means that
> >   the policy can't be enforced, which defeats the purpose of having them.
> >   So in this update, I propose a mechanism, loosely based on a
> `sudo`-like
> >   mechanism for defining when hooks can be skipped.
> >
>

Reply via email to