Initial public release

parent f7253ecd
......@@ -30,7 +30,7 @@ stamp-h1
# vmodtool
vcc_*_if.[ch]
vmod_*.rst
vmod_acltools.rst
# man
......
INSTALLATION
============
Building from source
~~~~~~~~~~~~~~~~~~~~
The VMOD is built on a system where an instance of Varnish is
installed, and the auto-tools will attempt to locate the Varnish
instance, and then pull in libraries and other support files from
there.
Quick start
-----------
This sequence should be enough in typical setups:
1. ``./bootstrap`` (for git-installation)
3. ``make``
4. ``make check`` (regression tests)
5. ``make install`` (may require root: sudo make install)
Alternative configs
-------------------
If you have installed Varnish to a non-standard directory, call
``autogen.sh`` and ``configure`` with ``PKG_CONFIG_PATH`` pointing to
the appropriate path. For example, when varnishd configure was called
with ``--prefix=$PREFIX``, use::
PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig
ACLOCAL_PATH=${PREFIX}/share/aclocal
export PKG_CONFIG_PATH ACLOCAL_PATH
=================================================
Access Control List (ACL) Tools for Varnish-Cache
=================================================
.. role:: ref(emphasis)
This project provides additional tools for Access Control Lists (ACLs)
in Varnish-Cache.
PROJECT RESOURCES
=================
* The primary repository is at
https://code.uplex.de/uplex-varnish/libvmod-acltools
This server does not accept user registrations, so please use ...
* the mirror at https://gitlab.com/uplex/varnish/libvmod-acltools for issues,
merge requests and all other interactions.
INTRODUCTION
============
For now, this project adds a single missing function to Varnish-Cache:
To create dynamic Access Control Lists with a single entry from
strings in VCL.
Example::
import acltools;
sub vcl_recv {
if (client.ip ~ acltools.dyn_single(req.http.acl)) {
return (synth(200, "matched"));
} else {
return (synth(403, "denied"));
}
}
In this example, an ACL gets created from the `acl` request header
header and is then used for a match of `client.ip`.
The acl entry is expected in the format::
<ip>[/<mask>]
.. _vmod_acltools.man.rst: src/vmod_acltools.man.rst
See the :ref:`vmod_acltools(3)` man page for more details. If you are
reading this file on line, it should also be available as
`vmod_acltools.man.rst`_.
INSTALLATION
============
.. _INSTALL.rst: https://code.uplex.de/uplex-varnish/libvmod-acltools/blob/master/INSTALL.rst
See `INSTALL.rst`_.
SUPPORT
=======
.. _gitlab.com issues: https://gitlab.com/uplex/varnish/libvmod-acltools/-/issues
To report bugs, use `gitlab.com issues`_.
For enquiries about professional service and support, please contact
info@uplex.de\ .
CONTRIBUTING
============
.. _merge requests on gitlab.com: https://gitlab.com/uplex/varnish/libvmod-acltools/-/merge_requests
To contribute to the project, please use `merge requests on gitlab.com`_.
To support the project's development and maintenance, there are
several options:
.. _paypal: https://www.paypal.com/donate/?hosted_button_id=BTA6YE2H5VSXA
.. _github sponsor: https://github.com/sponsors/nigoroll
* Donate money through `paypal`_. If you wish to receive a commercial
invoice, please add your details (address, email, any requirements
on the invoice text) to the message sent with your donation.
* Become a `github sponsor`_.
* Contact info@uplex.de to receive a commercial invoice for SWIFT payment.
AC_PREREQ([2.68])
AC_INIT([libvmod-acltools], [0.1])
AC_INIT([libvmod-acltools], [0.2])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_HEADERS([config.h])
......@@ -13,7 +13,7 @@ AC_ARG_WITH([rst2man],
[RST2MAN="$withval"],
[AC_CHECK_PROGS(RST2MAN, [rst2man rst2man.py], [])])
VARNISH_PREREQ([6.0.0])
VARNISH_PREREQ([7.2.0])
VARNISH_VMODS([acltools])
AM_PROG_AR
......
#include "config.h"
#include <limits.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <cache/cache.h>
#include <vsa.h>
#include <vtcp.h>
#include "vcc_acltools_if.h"
VCL_STRING
vmod_hello(VRT_CTX)
/* XXX vcc_interface.h */
typedef int acl_match_f(VRT_CTX, const VCL_IP);
struct vrt_acl {
unsigned magic;
#define VRT_ACL_MAGIC 0x78329d96
acl_match_f *match;
const char *name;
};
/* XXX vss.h */
const struct suckaddr *VSS_ResolveFirst(void *dst,
const char *addr, const char *port,
int family, int socktype, int flags);
int acl_fail_f(VRT_CTX, const VCL_IP ip)
{
(void) ctx;
(void) ip;
return (0);
}
struct vrt_acl acl_fail = {
.magic = VRT_ACL_MAGIC,
.match = acl_fail_f,
.name = "acltools.fail"
};
struct acl_dyn_single {
unsigned magic;
#define ACL_DYN_SINGLE_MAGIC 0xb30f5ac9
unsigned mask;
struct vrt_acl acl;
char vsa[];
};
int dyn_single_match_f(VRT_CTX, const VCL_IP ip)
{
struct vmod_priv *priv_task;
const struct acl_dyn_single *ads;
unsigned const char *aip, *aacl;
unsigned b, m;
int fip, facl;
priv_task = VRT_priv_task_get(ctx, dyn_single_match_f);
if (priv_task == NULL) {
VRT_fail(ctx, "No priv_task for dynamic ACL match");
return (0);
}
CAST_OBJ_NOTNULL(ads, priv_task->priv, ACL_DYN_SINGLE_MAGIC);
fip = VSA_GetPtr(ip, &aip);
facl = VSA_GetPtr((struct suckaddr *)ads->vsa, &aacl);
if (fip != facl) {
VSLb(ctx->vsl, SLT_Error,
"acltools.dyn_single: address family mismatch: "
"ip: %s, acl: %s",
fip == PF_INET ? "IPv4" : "IPv6",
facl == PF_INET ? "IPv4" : "IPv6");
return (0);
}
b = ads->mask / 8;
m = ads->mask % 8;
if (memcmp(aip, aacl, b))
goto nomatch;
if (m == 0)
goto match;
m = 0xff00 >> m;
m &= 0xff;
if ((aip[b] & m) == (aacl[b] & m))
goto match;
nomatch:
VSLbs(ctx->vsl, SLT_VCL_acl, TOSTRANDS(2,
"NO_MATCH",
ads->acl.name));
return (0);
match:
VSLbs(ctx->vsl, SLT_VCL_acl, TOSTRANDS(2,
"MATCH",
ads->acl.name));
return (1);
}
#define skipws(s) while( \
*(s) == ' ' || \
*(s) == '\t'|| \
*(s) == '\r'|| \
*(s) == '\n') \
(s)++
VCL_ACL
vmod_dyn_single(VRT_CTX, struct VARGS(dyn_single)*args)
{
struct acl_dyn_single *ads;
const struct suckaddr *vsa;
char *p, *pp, ipmask[VTCP_ADDRBUFSIZE + 4];
const char *a;
unsigned const char *addr;
struct vmod_priv *priv_task;
unsigned long mask = ULONG_MAX;
unsigned maxmask;
uintptr_t sn;
int quot = 0;
int fam;
VCL_ACL fb;
if (args->valid_fallback && args->fallback != NULL &&
args->fallback->magic == VRT_ACL_MAGIC)
fb = args->fallback;
else
fb = &acl_fail;
if (args->ipmask == NULL) {
VSLb(ctx->vsl, SLT_Error, "acltools.dyn_single: empty ipmask");
return (fb);
}
a = args->ipmask;
skipws(a);
if (a[0] == '\0') {
VSLb(ctx->vsl, SLT_Error, "acltools.dyn_single: empty ipmask");
return (fb);
}
skipws(a);
if (*a == '"') {
a++;
quot = 1;
}
if (strlen(a) > sizeof ipmask - 1) {
VSLb(ctx->vsl, SLT_Error,
"acltools.dyn_single: too long: %s",
args->ipmask);
return (fb);
}
(void) strcpy(ipmask, a);
a = NULL;
p = ipmask;
if (quot) {
p = strchr(p, '"');
if (p == NULL) {
VSLb(ctx->vsl, SLT_Error,
"acltools.dyn_single: unmatched quote for %s",
args->ipmask);
return (fb);
}
*p = '\0';
p++;
skipws(p);
}
p = strchr(p, '/');
if (p != NULL) {
*p = '\0';
p++; // ok to point to \0 now
skipws(p);
pp = NULL;
mask = strtoul(p, &pp, 0);
if (pp != NULL) {
skipws(pp);
if (*pp != '\0') {
VSLb(ctx->vsl, SLT_Error,
"acltools.dyn_single: "
"garbage after mask in %s",
args->ipmask);
return (fb);
}
}
}
sn = WS_Snapshot(ctx->ws);
ads = WS_Alloc(ctx->ws, sizeof *ads + vsa_suckaddr_len);
if (ads == NULL) {
VRT_fail(ctx, "acltools.dyn_single: insufficient workspace");
return (fb);
}
INIT_OBJ(ads, ACL_DYN_SINGLE_MAGIC);
vsa = VSS_ResolveFirst(
ads->vsa, ipmask, "0",
AF_UNSPEC, SOCK_STREAM,
args->resolve ? 0 : AI_NUMERICHOST|AI_NUMERICSERV);
if (vsa == NULL) {
VSLb(ctx->vsl, SLT_Error,
"acltools.dyn_single: conversion failed for %s",
ipmask);
WS_Reset(ctx->ws, sn);
return (fb);
}
assert((void *)vsa == (void *)ads->vsa);
fam = VSA_GetPtr(vsa, &addr);
switch (fam) {
case PF_INET:
maxmask = 32;
break;
case PF_INET6:
maxmask = 128;
break;
default:
WRONG("address family");
}
if (mask == ULONG_MAX)
mask = maxmask;
else if (mask > maxmask) {
VSLb(ctx->vsl, SLT_Error,
"acltools.dyn_single: mask %lu too long for %s",
mask, args->ipmask);
WS_Reset(ctx->ws, sn);
return (fb);
}
ads->mask = mask;
ads->acl.magic = VRT_ACL_MAGIC;
ads->acl.match = dyn_single_match_f;
ads->acl.name = args->ipmask;
priv_task = VRT_priv_task(ctx, dyn_single_match_f);
if (priv_task == NULL) {
VRT_fail(ctx, "acltools.dyn_single: no task available");
return (fb);
}
priv_task->priv = ads;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
return ("vmod-acltools");
return (&ads->acl);
}
$Module acltools 3 "Varnish acltools Module"
$Module acltools 3 "Varnish Access Control List (ACL) Module"
DESCRIPTION
===========
This VCC file was generated by VCDK, it is used to for both the VMOD
interface and its manual using reStructuredText.
This vmod contains additional functions for Access Control Lists
(ACLs).
XXX: document vmod-acltools
$Function ACL dyn_single(STRING ipmask, [ACL fallback],
BOOL resolve = 0)
Returns a dynamic ACL **for immediate use** (see **NOTE** below) with
a single entry constructed from the *ipmask* string, which must be of
format::
<ip>[/<mask>]
or, for compatibility with built-in static ACLs::
"<ip>"[/<mask>]
.. _`std.ip()`: https://varnish-cache.org/docs/trunk/reference/vmod_std.html#std-ip
*<ip>* undergoes the same parsing as the *s* argument of
`std.ip()`_. If *<ip>* is an IPv4 address, *<mask>*, if present, must
be an integer between 0 and 32. If *<ip>* is an IPv6 address,
*<mask>*, if present, must be an integer between 0 and 128. If
*<mask>* is not present, it defaults to the maximum mask (32 or 128,
respectively).
If *<ip>* is not a valid address or *<mask>* is invalid for the
address, the *fallback* argument is returned, if present. If
*fallback* is not given with invalid input, the ACL match will always
fail.
If the *resolve* argument is ``true``, *<ip>* will be resolved as a
DNS name.
Example
::
import acltools;
sub vcl_deliver {
set resp.http.Hello = acltools.hello();
sub vcl_recv {
if (client.ip ~ acltools.dyn_single(req.http.acl) {
return (synth(200, "matched"));
} else {
return (synth(403, "denied"));
}
}
XXX: define vmod-acltools interface
$Function STRING hello()
Description
Hello world for vmod-acltools
**NOTE** Due to current limitations in the Varnish-Cache APIs, there
only ever exists one ACL created by this function, and any match is
always on the ACL last created. In particular, ACLs created by this
function can not be saved in variables for later use, they always need
to be matched immediately.
SEE ALSO
========
......
varnishtest "test vmod-acltools"
server s1 {
rxreq
txresp
varnish v1 -proto PROXY -vcl {
import acltools;
backend none none;
acl fallback {
"1.2.3.4"/32;
"102:304:506::d0e:f10"/128;
}
sub vcl_recv {
if (req.url == "/") {
if (client.ip ~ acltools.dyn_single(req.http.acl)) {
return (synth(200, "matched"));
} else {
return (synth(403, "denied"));
}
} else if (req.url == "/fallback") {
if (client.ip ~ acltools.dyn_single(req.http.acl,
fallback=fallback)) {
return (synth(200, "matched"));
} else {
return (synth(403, "denied"));
}
}
return (synth(404));
}
} -start
logexpect l1 -v v1 -g session -m -q {Proxy ~ "5.6.7.8"} {
expect * * Error "acltools.dyn_single: empty ipmask"
expect * * Error "acltools.dyn_single: empty ipmask"
expect * * Error "acltools.dyn_single: too long: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef1234"
expect * * Error {acltools.dyn_single: unmatched quote for "1.2.3.4/32}
expect * * Error {acltools.dyn_single: garbage after mask in 1.2.3.4/32"}
expect * * Error "acltools.dyn_single: conversion failed for 1.2.3.x"
expect * * Error "acltools.dyn_single: mask 33 too long for 1.2.3.4/33"
expect * * Error "acltools.dyn_single: address family mismatch: ip: IPv4, acl: IPv6"
} -start
client c1 -proxy1 "1.2.3.4:1111 5.6.7.8:5678" {
txreq
rxresp
expect resp.status == 403
txreq -hdr "acl: 1.2.3.0/24"
rxresp
expect resp.status == 200
txreq -hdr "acl: 1.2.3.0/25"
rxresp
expect resp.status == 200
txreq -hdr "acl: 1.2.3.4/30"
rxresp
expect resp.status == 200
txreq -hdr "acl: 1.2.3.6/31"
rxresp
expect resp.status == 403
txreq -hdr "acl: 1.2.3.4/32"
rxresp
expect resp.status == 200
txreq -hdr {acl: "1.2.3.4"/32}
rxresp
expect resp.status == 200
txreq -hdr "acl: 1.2.3.4 "
rxresp
expect resp.status == 200
## SLT_error cases
# empty
txreq -hdr "acl: "
rxresp
expect resp.status == 403
# too long
txreq -hdr "acl: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef1234"
rxresp
expect resp.status == 403
# unmatched quote
txreq -hdr {acl: "1.2.3.4/32}
rxresp
expect resp.status == 403
# garbage
txreq -hdr {acl: 1.2.3.4/32"}
rxresp
expect resp.status == 403
# failed
txreq -hdr {acl: 1.2.3.x/32}
rxresp
expect resp.status == 403
# mask too long
txreq -hdr {acl: 1.2.3.4/33}
rxresp
expect resp.status == 403
# family mismatch
txreq -hdr {acl: [::1]/128}
rxresp
expect resp.status == 403
} -start
logexpect l2 -v v1 -g session -m -q {Proxy ~ "6.7.8.9"} {
expect * * Error "acltools.dyn_single: empty ipmask"
expect * * Error "acltools.dyn_single: empty ipmask"
expect * * Error "acltools.dyn_single: too long: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef1234"
expect * * Error {acltools.dyn_single: unmatched quote for "1.2.3.4/32}
expect * * Error {acltools.dyn_single: garbage after mask in 1.2.3.4/32"}
expect * * Error "acltools.dyn_single: conversion failed for 1.2.3.x"
expect * * Error "acltools.dyn_single: mask 33 too long for 1.2.3.4/33"
} -start
varnish v1 -vcl+backend {
import acltools;
# for fallback, the error cases are 200
client c2 -proxy1 "1.2.3.4:1111 6.7.8.9:5678" {
# no acl
txreq -url "/fallback"
rxresp
expect resp.status == 200
txreq -url "/fallback" -hdr "acl: 1.2.3.0/24"
rxresp
expect resp.status == 200
txreq -url "/fallback" -hdr "acl: 1.2.3.0/25"
rxresp
expect resp.status == 200
txreq -url "/fallback" -hdr "acl: 1.2.3.4/30"
rxresp
expect resp.status == 200
txreq -url "/fallback" -hdr "acl: 1.2.3.6/31"
rxresp
expect resp.status == 403
txreq -url "/fallback" -hdr "acl: 1.2.3.4/32"
rxresp
expect resp.status == 200
txreq -url "/fallback" -hdr {acl: "1.2.3.4"/32}
rxresp
expect resp.status == 200
sub vcl_deliver {
set resp.http.Hello = acltools.hello();
}
txreq -url "/fallback" -hdr "acl: 1.2.3.4 "
rxresp
expect resp.status == 200
## SLT_error cases
# empty
txreq -url "/fallback" -hdr "acl: "
rxresp
expect resp.status == 200
# too long
txreq -url "/fallback" -hdr "acl: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef1234"
rxresp
expect resp.status == 200
# unmatched quote
txreq -url "/fallback" -hdr {acl: "1.2.3.4/32}
rxresp
expect resp.status == 200
# garbage
txreq -url "/fallback" -hdr {acl: 1.2.3.4/32"}
rxresp
expect resp.status == 200
# failed
txreq -url "/fallback" -hdr {acl: 1.2.3.x/32}
rxresp
expect resp.status == 200
# mask too long
txreq -url "/fallback" -hdr {acl: 1.2.3.4/33}
rxresp
expect resp.status == 200
} -start
client c1 {
client c3 -proxy2 "[102:304:506::d0e:f10]:2314 [8182:8384:8586::8d8e:8f80]:2828" {
txreq
rxresp
expect resp.status == 403
txreq -hdr "acl: 102:304:506::d0e:f/112"
rxresp
expect resp.status == 200
txreq -hdr "acl: 102:304:506::d0e:f/113"
rxresp
expect resp.status == 200
txreq -hdr "acl: 102:304:506::d0e:f10/126"
rxresp
expect resp.status == 200
txreq -hdr "acl: 102:304:506::d0e:f16/127"
rxresp
expect resp.status == 403
txreq -hdr "acl: 102:304:506::d0e:f10/128"
rxresp
expect resp.status == 200
txreq -hdr {acl: "102:304:506::d0e:f10"/128}
rxresp
expect resp.status == 200
txreq -hdr "acl: 102:304:506::d0e:f10 "
rxresp
expect resp.status == 200
expect resp.http.Hello == "vmod-acltools"
} -run
## SLT_error cases
# empty
txreq -hdr "acl: "
rxresp
expect resp.status == 403
# too long
txreq -hdr "acl: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef1234"
rxresp
expect resp.status == 403
# unmatched quote
txreq -hdr {acl: "102:304:506::d0e:4/32}
rxresp
expect resp.status == 403
# garbage
txreq -hdr {acl: 102:304:506::d0e:4/32"}
rxresp
expect resp.status == 403
# failed
txreq -hdr {acl: 102:304:506::d0e:x/32}
rxresp
expect resp.status == 403
# mask too long
txreq -hdr {acl: 102:304:506::d0e:4/129}
rxresp
expect resp.status == 403
# family mismatch
txreq -hdr {acl: 127.0.0.1/32}
rxresp
expect resp.status == 403
} -start
client c1 -wait
client c2 -wait
client c3 -wait
logexpect l1 -wait
logexpect l2 -wait
\ No newline at end of file
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