Commit 93d4dd9c authored by Geoff Simmons's avatar Geoff Simmons

add introductory and interface documentation

parent 92acaacd
Pipeline #112 skipped
......@@ -26,12 +26,221 @@ import hoailona [from "path"] ;
::
new OBJECT = ...
new OBJECT = hoailona.policy(ENUM type, DURATION ttl
[, STRING description] [, BLOB secret]
[, INT start_offset])
new OBJECT = hoailona.hosts()
<obj>.add(STRING host, STRING policy [, STRING path]
[, STRING description])
INT <obj>.policy(STRING host, STRING path)
STRING <obj>.token([STRING acl] [, DURATION ttl] [, STRING data])
BLOB <obj>.secret()
STRING <obj>.explain()
STRING hoailona.version()
DESCRIPTION
===========
This Varnish Module (VMOD) ...
This Varnish Module (VMOD) supports use of the SecureHD Policy service
provided by Akamai Media Services. Applications of the VMOD include:
* Defining policies for access to media content:
* Policy type TOKEN: token authorization required, with a TTL
(time-to-live) limiting the duration of authorized access, and
possibly with a shared secret used for keyed-hash message
authentication codes (HMACs) that are required for authorization
* Policy type OPEN: access permitted without authorization
* Policy type DENY: access denied
* Assigning policies to hosts, either globally for a host, or for
sets of paths defined for the host
* Determining which policy holds for a given host and path
* Generating authorization tokens
This manual presupposes familiarity with the Akamai SecureHD
service. For more information, see the documentation provided by
Akamai (XXX: links).
The VMOD does not provide cryptographic code to generate HMACs, but it
does provide the means to associate shared secrets with a policy,
which can be used together with a VMOD that does compute HMACs (such
as the ``blobdigest`` VMOD, see `SEE ALSO`_).
The name of the VMOD is inspired by the Hawaiian word *hō`ailona*, for
"sign" or "symbol" (pronounced "ho-eye-lona"), which we believe to be
a suitable translation for "token". We welcome feedback from speakers
of Hawaiian about the choice of the name.
Defining policies
-----------------
Policies are defined by means of ``policy`` objects that are constructed
in ``vcl_init``. A policy is defined by its type (TOKEN, OPEN or DENY),
a TTL, and possibly a shared secret used for authorization. For example::
import hoailona;
import blobcode;
sub vcl_init {
# Define a policy for token authorization lasting one hour,
# and associate it with a shared secret.
new token_policy
= hoailona.policy(TOKEN, 1h, blobcode.decode(encoded="secret"));
# Define a policy for open access (authorization not required)
new open_policy = hoailona.policy(OPEN, 1h);
# Define an "access denied" policy
new deny_policy = hoailona.policy(DENY, 1h);
}
Policy objects have no methods; they become useful when they are
assigned to hosts and paths, as shown in the following.
Assigning policies to hosts and paths
-------------------------------------
Most of the work of the VMOD is done through the ``hosts`` object,
which is used in ``vcl_init`` to assign policies to hostnames, either
globally for a host, or for sets of paths on a host. Patterns for
paths are defined with the same syntax used by Akamai's SecureHD
Policy Editor::
sub vcl_init {
# After policies have been defined as shown above ...
new config = hoialona.hosts();
# Assign the token_policy globally to host example.com
config.add("example.com", "token_policy");
# Assign the open_policy to the path /foo/bar on host example.org
config.add("example.org", "open_policy", "/foo/bar");
# Assign the deny_policy to any path beginning with /baz/quux
# on subdomains of example.org
config.add("*.example.org", "deny_policy", "/baz/quux/...");
}
Policies are assigned by using strings that must exactly match the
object names (the symbols in the VCL source) for policy objects that
were previously defined in ``vcl_init``. Details about permissible
host names and the pattern syntax used for paths are given below.
Determining the policy for a host and path
------------------------------------------
After policies and their assignments to hosts and paths have been
configured in ``vcl_init``, the policy that holds for a given host and
path can be determined from the ``policy`` method of the ``hosts``
object::
sub vcl_recv {
# The policy method returns 0 for policy type DENY
if (config.policy(req.http.Host, req.url) == 0) {
# Handle "access denied" by returning 403 Forbidden
return(synth(403));
}
# .policy() returns 1 for policy type OPEN
if (config.policy(req.http.Host, req.url) == 1) {
return(pass);
}
# .policy() returns 2 for policy type TOKEN
if (config.policy(req.http.Host, req.url) == 2) {
# Handle token authorization ...
# [...]
}
}
Generating authorization tokens
-------------------------------
When the policy type TOKEN has been determined for a host and path
(return value 2 from the ``.policy()`` method), the ``.token()``
method can be used to generate the non-cryptographic portion of
the authorization token, and ``.secret()`` can be used to retrieve
the shared secret associated with the policy, to generate the
HMAC for the token::
import blobdigest;
import blobcode;
sub vcl_recv {
# .policy() returns 2 for policy type TOKEN
if (config.policy(req.http.Host, req.url) == 2) {
# Handle token authorization:
# Assign the non-cryptographic part of the token to a temp
# header
set req.http.Tmp-Token = config.token();
# Use VMOD blobdigest to generate the HMAC, and VMOD blobcode
# to encode the result in lower case hex.
# The shared secret serves as the HMAC key, and the token just
# assigned to the temp header is the message to be hashed.
set req.http.Tmp-HMAC
= blobcode.encode(HEXLC,
blobdigest.hmacf(SHA256, config.secret(),
blobcode.decode(IDENTITY,
req.http.Tmp-Token)));
# These two temp headers can now be combined to form the full
# token string required for authorization at the Akamai
# server, such as:
#
# "hdnea=" + req.http.Tmp-Token + "~hmac=" + req.http.Tmp-HMAC
}
}
At minimum, the string returned by ``.token()`` contains the
parameters ``st`` and ``exp``, whose values are the start and end
times (epoch times) for the duration of the authorization. By default,
the authorization begins "now" and lasts for the duration of the TTL
defined in the policy constructor, but these can be overriden by
optional parameters. Other optional parameters can provide values for
additional token parameters such as ``acl`` and ``data``, as described
below.
The ``.secret()`` method returns the BLOB that was provided in the
constructor of the policy object, for the policy that was determined
for the given host and path. Together with VMODs for cryptography,
this can be used to generate the HMAC for authorization. The HMAC and
the string returned from ``.token()`` can then be combined to form the
URL query string or Cookie as required according to Akamai's APIs
(for example, by generating a redirect response from VCL).
Invocations of the ``.token()`` and ``.secret()`` methods have task
scope, meaning that they refer back to the most recent invocation of
``.policy()`` in the same client or backend transaction. For example,
when ``.policy()`` is called in any of the ``vcl_backend_*``
subroutines, subsequent calls to ``.token()`` and ``.secret()`` in the
same backend transaction are based on the policy that was determined
by that call.
Diagnosis and logging
---------------------
To understand the policy that was determined for a host and path by
the ``.policy()`` method, the ``.explain()`` method can be used to
generate a string that contains:
* The name of the policy object that was determined
* The hostname that matched
* If applicable, the path pattern that matched
The ``.explain()`` method also has task scope, meaning that it refers
to the most recent invocation of ``.policy()`` in the same client or
backend transaction. The string that it returns can then, for example,
be entered into the Varnish log, or assigned to a debugging header.
XXX: ``.explain()`` is not implemented yet.
CONTENTS
========
......@@ -49,6 +258,56 @@ policy
new OBJ = policy(PRIV_TASK, ENUM {OPEN,DENY,TOKEN} type, DURATION ttl, STRING description=0, BLOB secret=0, INT start_offset=0)
Create a policy. The ``type`` enum classifies the policy as ``OPEN``,
``DENY`` or ``TOKEN``, and ``ttl`` determines the length of time for
which token authorization is valid by default. Unless the TTL is
overriden, strings generated by the ``hosts.token()`` method contain
parameters (epoch times) that define the duration of the authorization
to correspond with ``ttl``. The ``type`` and ``ttl`` parameters are
required.
The optional ``secret`` parameter may contain a shared secret for
authorization, which serves as the key for an HMAC. The data type for
``secret`` is BLOB, which cannot be expressed in native VCL, but can
be generated by a VMOD (such as VMOD ``blobcode``). By default, no
shared secret is stored for the policy.
The optional ``description`` parameter may contain any string; if
present, this string is used in the output of the ``hosts.explain()``
method to describe the policy chosed by ``hosts.policy()``. By default,
no description is stored for a policy.
The optional ``start_offset`` parameter can be used to alter the
"start" time (parameter ``st``) in tokens that are generated based
on this policy. This can be useful, for example, to address issues
of time synchronization between the Akamai server and the host on
which Varnish is running.
By default, ``start_offset`` is 0; in this case ``st`` is unmodified
and is set to the epoch time for "now". When ``start_offset`` is set
to -10, for example, then ``st`` is set to 10 seconds before "now"
(and hence authorization may be less likely to fail due to
unsynchronized clocks).
Examples::
# Open policy, no authorization required
new open = hoailona.policy(OPEN, 1h);
# Token authorization required, where authorization lasts 2 hours,
# using the given shared secret, and setting the start offset to
# 10 seconds before "now".
# (Note that in current versions of VCL, the negative integer for
# start_offset must be written as 0-10, because negative literals
# are not parsed correctly.)
import blobcode;
new token = hoailona.policy(type=TOKEN, ttl=2h, start_offset=0-10,
secret=blobcode.decode(HEX,
"717569636B2062726F776E20666F7879"));
# A policy for "access denied"
new forbid = hoailona.policy(DENY, 1h, description="access denied");
.. _obj_hosts:
hosts
......@@ -58,6 +317,11 @@ hosts
new OBJ = hosts()
Create a ``hosts`` object, which provides a store for a configuration
that associates with policies with hostnames, and optionally with
path patterns. The constructor has no parameters; the object only
becomes useful by calling the ``.add()`` method.
.. _func_hosts.add:
hosts.add
......@@ -67,7 +331,129 @@ hosts.add
VOID hosts.add(PRIV_TASK, STRING host, STRING policy, STRING path=0, STRING description=0)
# $Method adds are done
Associate ``policy`` with the ``host``, optionally restricted to the
path pattern described by ``path``. The ``host`` and ``policy``
parameters are required, and must be non-empty.
The ``.add()`` method MUST be called in ``vcl_init`` only. If it is
called in any other subroutine, then an error message is emitted to
the Varnish log (using the ``VCL_Error`` tag), and the method call is
ignored.
The value of ``host`` MUST be a valid host name, optionally beginning
with an asterisk (``*``):
* A host name may only contain alphanumeric characters, ``-`` or
``.``, and optionally the leading ``*``.
* It may not begin with ``.`` or ``-``.
* ``*`` may only appear as the first character.
If ``host`` begins with ``*``, then host names given in the
``.policy()`` method will match any non-empty string at the beginning,
if followed by the same suffix. Thus for example ``*.example.com`` can
be used to specify any subdomain of ``example.com``.
Host names are case-insensitive; that is, ``.policy()`` will match
a host name successfully regardless of case.
The string in ``policy`` MUST be identical to the object name (VCL
symbol) for a ``policy`` object previously defined in ``vcl_init``.
If no such policy object exists, then the VCL load will fail with an
error message.
If no ``path`` parameter is provided, then ``policy`` is assigned
globally to the host; that is, the ``.policy()`` method will determine
that the given policy holds for the host regardless of the path. In
that case, subsequent invocations of ``.add()`` MAY NOT assign any
other policy to the same host name, either globally or restricted to
any path pattern. If the same value of ``host`` appears again in any
other call to ``.add()``, then the VCL load will fail with an error
message.
If a ``path`` parameter is provided, then the policy holds for
``host`` for patterns that match ``path``. The VMOD uses the same
pattern language for path patterns that is used by the Akamai SecureHD
Policy Editor:
* An asterisk matches a single path component, i.e. any non-empty
string of characters that are not ``/``.
* Thus ``/foo/*/bar`` matches ``/foo/baz/bar`` but not
``/foo/baz/quux/bar`` or ``/foo//bar``.
* When three dots (``...``) follow or precede a slash, they match
a non-empty sequence of path components of any length.
* Thus ``/foo/.../bar`` matches both ``/foo/baz/bar`` and
``/foo/baz/quux/bar``, but not ``/foo//bar``.
* ``/foo/bar/...`` matches any path prefixed by ``/foo/bar/``,
but not ``/foo/bar/`` (with no suffix) or ``/foo/bar``.
* ``.../foo/bar`` matches any path ending in ``/foo/bar``,
but not ``/foo/bar`` with no prefix.
* Any other dot in the pattern matches a literal dot in a path.
* Any other characters in the pattern match exactly with a path. Thus
if none of ``*`` or ``...`` appear in ``path``, then ``path``
specifies an exact string match.
The VMOD also enforces the same syntactic restrictions on path
patterns as Akamai:
* Valid characters are alphanumerics, space, or any of these
characters: ``_-~.%:/[]@!$&()*+,;=``
* ``...`` may only appear before or after ``/``.
* Two or more consecutive asterisks are not allowed.
If ``path`` violates any of these restrictions, then the VCL load will
fail with an error message.
If a policy is assigned to a ``host`` and a ``path`` pattern, then
subsequent invocations of ``.add()`` may assign policies to the same
host and different patterns. A later invocation of ``.add()`` MAY NOT
assign a policy to the same host globally, or to the same host and the
same pattern. In either of these cases, the VCL load will fail with an
error message.
The optional ``description`` parameter may be any string, and if
present it is used in the output of ``.explain()``. By default, no
description is set.
Examples::
sub vcl_init {
new p1 = hoailona.policy(OPEN, 1h);
new p2 = hoailona.policy(TOKEN, 1h);
new p3 = hoailona.policy(TOKEN, 2h);
new p4 = hoailona.policy(TOKEN, 3h);
new deny = hoailona.policy(DENY, 1h);
new h = hoailona.hosts();
# Assign a policy globally to example.com
h.add("example.com", "p1");
# Assign a policy to a fixed path on subdomains of example.com
h.add("*.example.com", "p2", "/foo/bar");
# Assign a policy to any path beginning with /baz/quux/
# on example.org
h.add("example.org", "p3", "/baz/quux/...");
# Assign a policy to any path with three components, where
# the first component is /foo/ and the last is /bar, on example.org
h.add("example.org", "p4", "/foo/*/bar");
# Deny access to any path on evil.org, with a description to be used
# by h.explain()
h.add("evil.org", "deny", description="no access to evil.org");
}
.. _func_hosts.policy:
......@@ -78,6 +464,57 @@ hosts.policy
INT hosts.policy(PRIV_TASK, STRING host, STRING path)
Determine the policy type that holds for ``host`` and ``path``. The
return values are:
* 0 for ``DENY``
* 1 for ``OPEN``
* 2 for ``TOKEN``
* -1 if no matching policy can be found
* -2 if there was an internal error
The ``host`` and ``path`` parameters are required, and must be
non-empty. This method MAY NOT be called in ``vcl_init``. If it is,
then the VCL load fails.
The method searches for host names added by the ``.add()`` method that
match ``host``, possibly matching the suffix if the host name in
``.add()`` began with an asterisk. If it finds a host for which a
policy was assigned globally (without a path pattern), then it returns
the type for that policy.
If it finds a host for which path patterns were defined, it attempts
to match ``path``, respecting the use of ``*`` or ``...`` in the
patterns. It returns the policy type assigned for a matching pattern.
If more than one path pattern was assigned for the host, then it
attempts to match the "most specific" patterns first. The general idea
is: if, for example, the patterns ``/foo/.../bar`` and
``/foo/.../baz/bar`` were assigned for a matching host, and the
``path`` to be matched is ``/foo/quux/baz/bar``, then
``/foo/.../baz/bar`` will be matched and the policy type assigned for
that pattern will be returned, even though ``/foo/.../bar`` would have
also matched.
Formally, the "more specific" relation is defined as:
* Pattern A is more specific than pattern B if:
* A has more slashes than B
* otherwise (if A and B have the same number of slashes) if
B contains ``...`` and A does not
* else if A has fewer asterisks than B
* else if A is longer than B
* else if A precedes B lexigraphically
Subsequent calls to the ``.token()``, ``.secret()`` or ``.explain()``
methods refer to the most recent invocation of ``.policy()`` in the
same task scope, that is in the same client or backend transaction.
.. _func_hosts.token:
hosts.token
......@@ -87,6 +524,54 @@ hosts.token
STRING hosts.token(PRIV_TASK, STRING acl=0, DURATION ttl=0, STRING data=0)
If the previous invocation of ``.policy()`` determined policy type
``TOKEN`` (return value 2 from ``.policy()``), then return the
non-cryptographic portion of an authorization token; return NULL if no
matching policy could be determined. There are no required parameters.
This method MAY NOT be called in ``vcl_init``; if it is, then the VCL
load fails. If the previous ``.policy()`` call did not determine
policy type TOKEN, or if ``.policy()`` was not called previously in
the current task scope, then an error message is emitted to the
Varnish log with the ``VCL_Error`` tag, and the method returns NULL.
If none of the optional parameters are specified, then the method
returns a string with the parameters ``st`` and ``exp`` for the start
and end times (as epoch times) for the duration of the authorization.
``st`` is derived from "now", and may be modified by a
``start_offset`` defined for the chosen policy, as described above.
``exp`` is set by adding the duration of the TTL for the chosen policy
to ``st``. So at minimum, ``.token()`` may generate a string like::
st=1484251854~exp=1484255454
The optional ``ttl`` parameter overrides the TTL determined from the
policy; if set, then the ``exp`` parameter is computed accordingly.
The optional parameters ``acl`` and ``data`` may be used to set values
for parameters ``acl`` and/or ``data`` in the token string, if these
are required for your SecureHD authorization. By default, neither of
the ``acl`` or ``data`` parameters are included in the token string.
Examples::
sub vcl_recv {
if (config.policy(req.http.Host, req.url) == 2) {
# This generates the simplest token with default values
set req.http.Tmp = config.token();
# Override the TTL determined from the policy
set req.http.Tmp = config.token(ttl=3h);
# Include values for acl and data in the token string
set req.http.Tmp = config.token(acl="/foo", data="user=foo");
# This last example may generate a token string like:
# st=1484251854~exp=1484255454~acl=/foo~data=user=foo
# The contents of the Tmp header may now be used as
# needed for SecureHD authorization.
}
.. _func_hosts.secret:
hosts.secret
......@@ -96,6 +581,43 @@ hosts.secret
BLOB hosts.secret(PRIV_TASK)
Return the shared secret stored for the policy determined by the
previous invocation of ``.policy()``. Returns NULL if no such shared
secret was specified, or if no matching policy could be determined.
This method MAY NOT be called in ``vcl_init``; if it is, then the VCL
load fails. If ``.policy()`` was not called previously in the current
task scope, then an error message is emitted to the Varnish log with
the ``VCL_Error`` tag, and the method returns NULL.
Examples::
import blobdigest;
import blobcode;
sub vcl_recv {
if (config.policy(req.http.Host, req.url) == 2) {
# Generates the non-crypto part of the token
set req.http.Tmp = config.token();
# Use VMOD blobdigest to generate the HMAC, where
# the shared secret serves as the HMAC key.
set req.http.Tmp-HMAC
= blobcode.encode(HEXLC,
blobdigest.hmacf(SHA256, config.secret(),
blobcode.decode(IDENTITY,
req.http.Tmp-Token)));
# Concatenate elements of the authorization token
set req.http.Token = "hdnea=" + req.http.Tmp + "~hmac="
+ req.http.Tmp-HMAC;
# The contents of the Tmp header may now be used as
# a query string or cookie contents, as required for
# authorization at the Akamai server (for example by
# constructing a redirect response in VCL).
}
.. _func_hosts.explain:
hosts.explain
......@@ -105,6 +627,8 @@ hosts.explain
STRING hosts.explain(PRIV_TASK)
**XXX NOT IMPLEMENTED YET**
.. _func_version:
version
......
......@@ -648,7 +648,7 @@ vmod_hosts_token(VRT_CTX, struct vmod_hoailona_hosts *hosts,
if (ttl > 0)
t = ttl;
st = (int) (ctx->now + policy->start_offset);
exp = (int) (ctx->now + t);
exp = (int) (ctx->now + t); // XXX should it be st + t ?
if (acl != NULL) {
if (data != NULL)
token = WS_Printf(ctx->ws,
......
......@@ -9,31 +9,555 @@ $Module hoailona 3 Akamai SecureHD Token Authorization VMOD
::
new OBJECT = ...
new OBJECT = hoailona.policy(ENUM type, DURATION ttl
[, STRING description] [, BLOB secret]
[, INT start_offset])
new OBJECT = hoailona.hosts()
<obj>.add(STRING host, STRING policy [, STRING path]
[, STRING description])
INT <obj>.policy(STRING host, STRING path)
STRING <obj>.token([STRING acl] [, DURATION ttl] [, STRING data])
BLOB <obj>.secret()
STRING <obj>.explain()
STRING hoailona.version()
DESCRIPTION
===========
This Varnish Module (VMOD) ...
This Varnish Module (VMOD) supports use of the SecureHD Policy service
provided by Akamai Media Services. Applications of the VMOD include:
* Defining policies for access to media content:
* Policy type TOKEN: token authorization required, with a TTL
(time-to-live) limiting the duration of authorized access, and
possibly with a shared secret used for keyed-hash message
authentication codes (HMACs) that are required for authorization
* Policy type OPEN: access permitted without authorization
* Policy type DENY: access denied
* Assigning policies to hosts, either globally for a host, or for
sets of paths defined for the host
* Determining which policy holds for a given host and path
* Generating authorization tokens
This manual presupposes familiarity with the Akamai SecureHD
service. For more information, see the documentation provided by
Akamai (XXX: links).
The VMOD does not provide cryptographic code to generate HMACs, but it
does provide the means to associate shared secrets with a policy,
which can be used together with a VMOD that does compute HMACs (such
as the ``blobdigest`` VMOD, see `SEE ALSO`_).
The name of the VMOD is inspired by the Hawaiian word *hō`ailona*, for
"sign" or "symbol" (pronounced "ho-eye-lona"), which we believe to be
a suitable translation for "token". We welcome feedback from speakers
of Hawaiian about the choice of the name.
Defining policies
-----------------
Policies are defined by means of ``policy`` objects that are constructed
in ``vcl_init``. A policy is defined by its type (TOKEN, OPEN or DENY),
a TTL, and possibly a shared secret used for authorization. For example::
import hoailona;
import blobcode;
sub vcl_init {
# Define a policy for token authorization lasting one hour,
# and associate it with a shared secret.
new token_policy
= hoailona.policy(TOKEN, 1h, blobcode.decode(encoded="secret"));
# Define a policy for open access (authorization not required)
new open_policy = hoailona.policy(OPEN, 1h);
# Define an "access denied" policy
new deny_policy = hoailona.policy(DENY, 1h);
}
Policy objects have no methods; they become useful when they are
assigned to hosts and paths, as shown in the following.
Assigning policies to hosts and paths
-------------------------------------
Most of the work of the VMOD is done through the ``hosts`` object,
which is used in ``vcl_init`` to assign policies to hostnames, either
globally for a host, or for sets of paths on a host. Patterns for
paths are defined with the same syntax used by Akamai's SecureHD
Policy Editor::
sub vcl_init {
# After policies have been defined as shown above ...
new config = hoialona.hosts();
# Assign the token_policy globally to host example.com
config.add("example.com", "token_policy");
# Assign the open_policy to the path /foo/bar on host example.org
config.add("example.org", "open_policy", "/foo/bar");
# Assign the deny_policy to any path beginning with /baz/quux
# on subdomains of example.org
config.add("*.example.org", "deny_policy", "/baz/quux/...");
}
Policies are assigned by using strings that must exactly match the
object names (the symbols in the VCL source) for policy objects that
were previously defined in ``vcl_init``. Details about permissible
host names and the pattern syntax used for paths are given below.
Determining the policy for a host and path
------------------------------------------
After policies and their assignments to hosts and paths have been
configured in ``vcl_init``, the policy that holds for a given host and
path can be determined from the ``policy`` method of the ``hosts``
object::
sub vcl_recv {
# The policy method returns 0 for policy type DENY
if (config.policy(req.http.Host, req.url) == 0) {
# Handle "access denied" by returning 403 Forbidden
return(synth(403));
}
# .policy() returns 1 for policy type OPEN
if (config.policy(req.http.Host, req.url) == 1) {
return(pass);
}
# .policy() returns 2 for policy type TOKEN
if (config.policy(req.http.Host, req.url) == 2) {
# Handle token authorization ...
# [...]
}
}
Generating authorization tokens
-------------------------------
When the policy type TOKEN has been determined for a host and path
(return value 2 from the ``.policy()`` method), the ``.token()``
method can be used to generate the non-cryptographic portion of
the authorization token, and ``.secret()`` can be used to retrieve
the shared secret associated with the policy, to generate the
HMAC for the token::
import blobdigest;
import blobcode;
sub vcl_recv {
# .policy() returns 2 for policy type TOKEN
if (config.policy(req.http.Host, req.url) == 2) {
# Handle token authorization:
# Assign the non-cryptographic part of the token to a temp
# header
set req.http.Tmp-Token = config.token();
# Use VMOD blobdigest to generate the HMAC, and VMOD blobcode
# to encode the result in lower case hex.
# The shared secret serves as the HMAC key, and the token just
# assigned to the temp header is the message to be hashed.
set req.http.Tmp-HMAC
= blobcode.encode(HEXLC,
blobdigest.hmacf(SHA256, config.secret(),
blobcode.decode(IDENTITY,
req.http.Tmp-Token)));
# These two temp headers can now be combined to form the full
# token string required for authorization at the Akamai
# server, such as:
#
# "hdnea=" + req.http.Tmp-Token + "~hmac=" + req.http.Tmp-HMAC
}
}
At minimum, the string returned by ``.token()`` contains the
parameters ``st`` and ``exp``, whose values are the start and end
times (epoch times) for the duration of the authorization. By default,
the authorization begins "now" and lasts for the duration of the TTL
defined in the policy constructor, but these can be overriden by
optional parameters. Other optional parameters can provide values for
additional token parameters such as ``acl`` and ``data``, as described
below.
The ``.secret()`` method returns the BLOB that was provided in the
constructor of the policy object, for the policy that was determined
for the given host and path. Together with VMODs for cryptography,
this can be used to generate the HMAC for authorization. The HMAC and
the string returned from ``.token()`` can then be combined to form the
URL query string or Cookie as required according to Akamai's APIs
(for example, by generating a redirect response from VCL).
Invocations of the ``.token()`` and ``.secret()`` methods have task
scope, meaning that they refer back to the most recent invocation of
``.policy()`` in the same client or backend transaction. For example,
when ``.policy()`` is called in any of the ``vcl_backend_*``
subroutines, subsequent calls to ``.token()`` and ``.secret()`` in the
same backend transaction are based on the policy that was determined
by that call.
Diagnosis and logging
---------------------
To understand the policy that was determined for a host and path by
the ``.policy()`` method, the ``.explain()`` method can be used to
generate a string that contains:
* The name of the policy object that was determined
* The hostname that matched
* If applicable, the path pattern that matched
The ``.explain()`` method also has task scope, meaning that it refers
to the most recent invocation of ``.policy()`` in the same client or
backend transaction. The string that it returns can then, for example,
be entered into the Varnish log, or assigned to a debugging header.
XXX: ``.explain()`` is not implemented yet.
$Object policy(PRIV_TASK, ENUM {OPEN, DENY, TOKEN} type, DURATION ttl,
STRING description=0, BLOB secret=0, INT start_offset=0)
Create a policy. The ``type`` enum classifies the policy as ``OPEN``,
``DENY`` or ``TOKEN``, and ``ttl`` determines the length of time for
which token authorization is valid by default. Unless the TTL is
overriden, strings generated by the ``hosts.token()`` method contain
parameters (epoch times) that define the duration of the authorization
to correspond with ``ttl``. The ``type`` and ``ttl`` parameters are
required.
The optional ``secret`` parameter may contain a shared secret for
authorization, which serves as the key for an HMAC. The data type for
``secret`` is BLOB, which cannot be expressed in native VCL, but can
be generated by a VMOD (such as VMOD ``blobcode``). By default, no
shared secret is stored for the policy.
The optional ``description`` parameter may contain any string; if
present, this string is used in the output of the ``hosts.explain()``
method to describe the policy chosed by ``hosts.policy()``. By default,
no description is stored for a policy.
The optional ``start_offset`` parameter can be used to alter the
"start" time (parameter ``st``) in tokens that are generated based
on this policy. This can be useful, for example, to address issues
of time synchronization between the Akamai server and the host on
which Varnish is running.
By default, ``start_offset`` is 0; in this case ``st`` is unmodified
and is set to the epoch time for "now". When ``start_offset`` is set
to -10, for example, then ``st`` is set to 10 seconds before "now"
(and hence authorization may be less likely to fail due to
unsynchronized clocks).
Examples::
# Open policy, no authorization required
new open = hoailona.policy(OPEN, 1h);
# Token authorization required, where authorization lasts 2 hours,
# using the given shared secret, and setting the start offset to
# 10 seconds before "now".
# (Note that in current versions of VCL, the negative integer for
# start_offset must be written as 0-10, because negative literals
# are not parsed correctly.)
import blobcode;
new token = hoailona.policy(type=TOKEN, ttl=2h, start_offset=0-10,
secret=blobcode.decode(HEX,
"717569636B2062726F776E20666F7879"));
# A policy for "access denied"
new forbid = hoailona.policy(DENY, 1h, description="access denied");
$Object hosts()
Create a ``hosts`` object, which provides a store for a configuration
that associates with policies with hostnames, and optionally with
path patterns. The constructor has no parameters; the object only
becomes useful by calling the ``.add()`` method.
$Method VOID .add(PRIV_TASK, STRING host, STRING policy, STRING path=0,
STRING description=0)
# $Method adds are done
Associate ``policy`` with the ``host``, optionally restricted to the
path pattern described by ``path``. The ``host`` and ``policy``
parameters are required, and must be non-empty.
The ``.add()`` method MUST be called in ``vcl_init`` only. If it is
called in any other subroutine, then an error message is emitted to
the Varnish log (using the ``VCL_Error`` tag), and the method call is
ignored.
The value of ``host`` MUST be a valid host name, optionally beginning
with an asterisk (``*``):
* A host name may only contain alphanumeric characters, ``-`` or
``.``, and optionally the leading ``*``.
* It may not begin with ``.`` or ``-``.
* ``*`` may only appear as the first character.
If ``host`` begins with ``*``, then host names given in the
``.policy()`` method will match any non-empty string at the beginning,
if followed by the same suffix. Thus for example ``*.example.com`` can
be used to specify any subdomain of ``example.com``.
Host names are case-insensitive; that is, ``.policy()`` will match
a host name successfully regardless of case.
The string in ``policy`` MUST be identical to the object name (VCL
symbol) for a ``policy`` object previously defined in ``vcl_init``.
If no such policy object exists, then the VCL load will fail with an
error message.
If no ``path`` parameter is provided, then ``policy`` is assigned
globally to the host; that is, the ``.policy()`` method will determine
that the given policy holds for the host regardless of the path. In
that case, subsequent invocations of ``.add()`` MAY NOT assign any
other policy to the same host name, either globally or restricted to
any path pattern. If the same value of ``host`` appears again in any
other call to ``.add()``, then the VCL load will fail with an error
message.
If a ``path`` parameter is provided, then the policy holds for
``host`` for patterns that match ``path``. The VMOD uses the same
pattern language for path patterns that is used by the Akamai SecureHD
Policy Editor:
* An asterisk matches a single path component, i.e. any non-empty
string of characters that are not ``/``.
* Thus ``/foo/*/bar`` matches ``/foo/baz/bar`` but not
``/foo/baz/quux/bar`` or ``/foo//bar``.
* When three dots (``...``) follow or precede a slash, they match
a non-empty sequence of path components of any length.
* Thus ``/foo/.../bar`` matches both ``/foo/baz/bar`` and
``/foo/baz/quux/bar``, but not ``/foo//bar``.
* ``/foo/bar/...`` matches any path prefixed by ``/foo/bar/``,
but not ``/foo/bar/`` (with no suffix) or ``/foo/bar``.
* ``.../foo/bar`` matches any path ending in ``/foo/bar``,
but not ``/foo/bar`` with no prefix.
* Any other dot in the pattern matches a literal dot in a path.
* Any other characters in the pattern match exactly with a path. Thus
if none of ``*`` or ``...`` appear in ``path``, then ``path``
specifies an exact string match.
The VMOD also enforces the same syntactic restrictions on path
patterns as Akamai:
* Valid characters are alphanumerics, space, or any of these
characters: ``_-~.%:/[]@!$&()*+,;=``
* ``...`` may only appear before or after ``/``.
* Two or more consecutive asterisks are not allowed.
If ``path`` violates any of these restrictions, then the VCL load will
fail with an error message.
If a policy is assigned to a ``host`` and a ``path`` pattern, then
subsequent invocations of ``.add()`` may assign policies to the same
host and different patterns. A later invocation of ``.add()`` MAY NOT
assign a policy to the same host globally, or to the same host and the
same pattern. In either of these cases, the VCL load will fail with an
error message.
The optional ``description`` parameter may be any string, and if
present it is used in the output of ``.explain()``. By default, no
description is set.
Examples::
sub vcl_init {
new p1 = hoailona.policy(OPEN, 1h);
new p2 = hoailona.policy(TOKEN, 1h);
new p3 = hoailona.policy(TOKEN, 2h);
new p4 = hoailona.policy(TOKEN, 3h);
new deny = hoailona.policy(DENY, 1h);
new h = hoailona.hosts();
# Assign a policy globally to example.com
h.add("example.com", "p1");
# Assign a policy to a fixed path on subdomains of example.com
h.add("*.example.com", "p2", "/foo/bar");
# Assign a policy to any path beginning with /baz/quux/
# on example.org
h.add("example.org", "p3", "/baz/quux/...");
# Assign a policy to any path with three components, where
# the first component is /foo/ and the last is /bar, on example.org
h.add("example.org", "p4", "/foo/*/bar");
# Deny access to any path on evil.org, with a description to be used
# by h.explain()
h.add("evil.org", "deny", description="no access to evil.org");
}
$Method INT .policy(PRIV_TASK, STRING host, STRING path)
Determine the policy type that holds for ``host`` and ``path``. The
return values are:
* 0 for ``DENY``
* 1 for ``OPEN``
* 2 for ``TOKEN``
* -1 if no matching policy can be found
* -2 if there was an internal error
The ``host`` and ``path`` parameters are required, and must be
non-empty. This method MAY NOT be called in ``vcl_init``. If it is,
then the VCL load fails.
The method searches for host names added by the ``.add()`` method that
match ``host``, possibly matching the suffix if the host name in
``.add()`` began with an asterisk. If it finds a host for which a
policy was assigned globally (without a path pattern), then it returns
the type for that policy.
If it finds a host for which path patterns were defined, it attempts
to match ``path``, respecting the use of ``*`` or ``...`` in the
patterns. It returns the policy type assigned for a matching pattern.
If more than one path pattern was assigned for the host, then it
attempts to match the "most specific" patterns first. The general idea
is: if, for example, the patterns ``/foo/.../bar`` and
``/foo/.../baz/bar`` were assigned for a matching host, and the
``path`` to be matched is ``/foo/quux/baz/bar``, then
``/foo/.../baz/bar`` will be matched and the policy type assigned for
that pattern will be returned, even though ``/foo/.../bar`` would have
also matched.
Formally, the "more specific" relation is defined as:
* Pattern A is more specific than pattern B if:
* A has more slashes than B
* otherwise (if A and B have the same number of slashes) if
B contains ``...`` and A does not
* else if A has fewer asterisks than B
* else if A is longer than B
* else if A precedes B lexigraphically
Subsequent calls to the ``.token()``, ``.secret()`` or ``.explain()``
methods refer to the most recent invocation of ``.policy()`` in the
same task scope, that is in the same client or backend transaction.
$Method STRING .token(PRIV_TASK, STRING acl=0, DURATION ttl=0, STRING data=0)
If the previous invocation of ``.policy()`` determined policy type
``TOKEN`` (return value 2 from ``.policy()``), then return the
non-cryptographic portion of an authorization token; return NULL if no
matching policy could be determined. There are no required parameters.
This method MAY NOT be called in ``vcl_init``; if it is, then the VCL
load fails. If the previous ``.policy()`` call did not determine
policy type TOKEN, or if ``.policy()`` was not called previously in
the current task scope, then an error message is emitted to the
Varnish log with the ``VCL_Error`` tag, and the method returns NULL.
If none of the optional parameters are specified, then the method
returns a string with the parameters ``st`` and ``exp`` for the start
and end times (as epoch times) for the duration of the authorization.
``st`` is derived from "now", and may be modified by a
``start_offset`` defined for the chosen policy, as described above.
``exp`` is set by adding the duration of the TTL for the chosen policy
to ``st``. So at minimum, ``.token()`` may generate a string like::
st=1484251854~exp=1484255454
The optional ``ttl`` parameter overrides the TTL determined from the
policy; if set, then the ``exp`` parameter is computed accordingly.
The optional parameters ``acl`` and ``data`` may be used to set values
for parameters ``acl`` and/or ``data`` in the token string, if these
are required for your SecureHD authorization. By default, neither of
the ``acl`` or ``data`` parameters are included in the token string.
Examples::
sub vcl_recv {
if (config.policy(req.http.Host, req.url) == 2) {
# This generates the simplest token with default values
set req.http.Tmp = config.token();
# Override the TTL determined from the policy
set req.http.Tmp = config.token(ttl=3h);
# Include values for acl and data in the token string
set req.http.Tmp = config.token(acl="/foo", data="user=foo");
# This last example may generate a token string like:
# st=1484251854~exp=1484255454~acl=/foo~data=user=foo
# The contents of the Tmp header may now be used as
# needed for SecureHD authorization.
}
$Method BLOB .secret(PRIV_TASK)
Return the shared secret stored for the policy determined by the
previous invocation of ``.policy()``. Returns NULL if no such shared
secret was specified, or if no matching policy could be determined.
This method MAY NOT be called in ``vcl_init``; if it is, then the VCL
load fails. If ``.policy()`` was not called previously in the current
task scope, then an error message is emitted to the Varnish log with
the ``VCL_Error`` tag, and the method returns NULL.
Examples::
import blobdigest;
import blobcode;
sub vcl_recv {
if (config.policy(req.http.Host, req.url) == 2) {
# Generates the non-crypto part of the token
set req.http.Tmp = config.token();
# Use VMOD blobdigest to generate the HMAC, where
# the shared secret serves as the HMAC key.
set req.http.Tmp-HMAC
= blobcode.encode(HEXLC,
blobdigest.hmacf(SHA256, config.secret(),
blobcode.decode(IDENTITY,
req.http.Tmp-Token)));
# Concatenate elements of the authorization token
set req.http.Token = "hdnea=" + req.http.Tmp + "~hmac="
+ req.http.Tmp-HMAC;
# The contents of the Tmp header may now be used as
# a query string or cookie contents, as required for
# authorization at the Akamai server (for example by
# constructing a redirect response in VCL).
}
$Method STRING .explain(PRIV_TASK)
**XXX NOT IMPLEMENTED YET**
$Function STRING version()
Returns the version string for this VMOD.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment