11月 242021
 

前の記事で紹介したパラレルESI(pesi)など、様々なVMODがあるのですが、残念なことにパッケージで提供されているVMODはほとんどありません。

自分がメンテしているカスタムでカウンタを作るもの(xcounter)とかは、勉強がてらGithub actionsでdebを作るようにしてみたのですが、なかなかめんどくさいのが実情でこれは提供したがらないよなぁと思ってました。

なんなら公式のvarnish-modulesも公式ではパッケージ提供されていません。

では、VMODを使いたい場合はどうしてたのかというと、自分でパッケージを作るか.so配布するかとか、利用者・作者共ににも辛い状態だったわけです。

誰かが作るんじゃないかなと思ってたんですが、だれも作らなかったのと急に思い立ったのでえいやでdebとかrpmが作れるツールを作りました。

https://github.com/xcir/vmod-packager

使いかたは簡単で、docker, curl, jqが使える環境で

xcir@build01:~/git/vmod-packager$ cd src
xcir@build01:~/git/vmod-packager/src$ git clone https://github.com/varnish/varnish-modules.git
xcir@build01:~/git/vmod-packager/src$ cd ..
xcir@build01:~/git/vmod-packager$ ./vmod-packager.sh -e 0.19 varnish-modules
...
##################################################
        docker image: vmod-packager/focal:7.0.0-1
                Dist: focal
     Varnish Version: 7.0.0
         Varnish VRT: 140
           VMOD name: varnish-modules
        VMOD Version: 140.0.19
              Status: SUCCESS

xcir@build01:~/git/vmod-packager$ ls pkgs/debs/varnish-modules/
varnish-modules_140.0.19~focal-1_amd64.build      varnish-modules_140.0.19~focal-1_amd64.changes  varnish-modules-dbgsym_140.0.19~focal-1_amd64.ddeb
varnish-modules_140.0.19~focal-1_amd64.buildinfo  varnish-modules_140.0.19~focal-1_amd64.deb

こんな感じでパッケージを作れます。

もちろんrpmも

xcir@build01:~/git/vmod-packager$ ./vmod-packager.sh -d centos8 -e 0.19 varnish-modules
...
##################################################
        docker image: vmod-packager/centos8:7.0.0-1
                Dist: centos8
     Varnish Version: 7.0.0
         Varnish VRT: 140
           VMOD name: varnish-modules
        VMOD Version: 140.0.19
              Status: SUCCESS

xcir@build01:~/git/vmod-packager$ ls pkgs/rpms/varnish-modules/
varnish-modules-140.0.19-1.el8.src.rpm  varnish-modules-140.0.19-1.el8.x86_64.rpm

作れます。

もちろん、このように簡単にビルドできるのは

  • ./autogen.sh(もしくは./bootstrap) + ./configure + makeで作れるもの
  • 依存パッケージがないもの

上記を満たす必要がありますが、追加パッケージが必要だったり、ちょっと特殊なビルドが必要なものでも
カスタムスクリプトでパッケージにすることが可能です。

実際に先の記事で紹介したlibvdp-pesiをパッケージにしてみましょう。

xcir@build01:~/git/vmod-packager$ cd src/
xcir@build01:~/git/vmod-packager/src$ git clone https://gitlab.com/uplex/varnish/libvdp-pesi.git

xcir@build01:~/git/vmod-packager/src$ cd libvdp-pesi/
xcir@build01:~/git/vmod-packager/src/libvdp-pesi$ git checkout -b 7.0 remotes/origin/7.0
xcir@build01:~/git/vmod-packager/src$ cat libvdp-pesi_init.sh 
#!/bin/sh

cp -rp ${VMP_ROOT_DIR}/src/m4 ${VMP_WORK_DIR}/src/m4

xcir@build01:~/git/vmod-packager/src$ cat libvdp-pesi_config.sh 
#!/bin/sh

./autogen.sh
./configure VARNISHSRC=/tmp/varnish/src

xcir@build01:~/git/vmod-packager$ ./vmod-packager.sh -f  src/libvdp-pesi
...

##################################################
        docker image: vmod-packager/focal:7.0.0-1
                Dist: focal
     Varnish Version: 7.0.0
         Varnish VRT: 140
           VMOD name: libvdp-pesi
        VMOD Version: 140.0.1
   Enable fixed mode
              Status: SUCCESS

xcir@build01:~/git/vmod-packager$ dpkg --info pkgs/debs/libvdp-pesi/libvdp-pesi_140.0.1~focal-1_amd64.deb 
 new Debian package, version 2.0.
 size 56760 bytes: control archive=816 bytes.
     309 bytes,    11 lines      control              
     711 bytes,    10 lines      md5sums              
 Package: libvdp-pesi
 Version: 140.0.1~focal-1
 Architecture: amd64
 Maintainer: libvdp-pesi <example@localhost>
 Installed-Size: 141
 Depends: libc6 (>= 2.14), varnish (= 7.0.0)
 Replaces: libvdp-pesi (<< 140)
 Section: web
 Priority: optional
 Homepage: https://github.com/xcir/
 Description: packed by vmod-packager

ビルド時に事前準備が必要な場合は[VMODの名前]_init.shにその内容を書きます。
今回はm4をコピーしていますが、ここでビルドに必要なパッケージをインストールすることも可能です。

この例では使っていませんが、dependsに追加が必要な場合は[VMODの名前]_env.shという別のファイルに入れる形になります。

また、libvdp-pesiはconfigureでvarnishのソースパスを指定する必要があるので [VMODの名前]_config.shで記述できるようにしています。

ちなみに-fオプションというのを使っていますが、これはlibvdp-pesiがVarnishのバージョンが厳密に一致する必要があるためでバージョンを固定するオプションになります。

こんな感じで割と複雑な手順が必要なVMODでも簡単にパッケージ化が可能です。

とはいえ、細かい指定はまぁいろいろできるんですが、自分は今のところsrc以下に使ってるvmodをcloneした後に

for i in `find src/  -mindepth 1 -maxdepth 1 -type d` ; do echo $i;cd $i ;git pull; cd ..;cd .. ; done

./vmod-packager.sh -v 7.0.1 -t -e `date +%Y%m%d` `find src/  -mindepth 1 -maxdepth 1 -type d`

find pkgs/debs/ -type f -name *.deb|grep `date +%Y%m%d`| xargs -i cp -p {} [コピー先]

みたいな感じで、更新した後に一気にバージョンを日付でビルドしてあとはよしなにするといった感じです。

何はともあれ、使いかたはREADMEを見てほしいです。
今のところよく使われてそうなものはビルドを試しており、大体いけるんじゃないかなと考えています。
が、もしビルド出来ないVMODや気になる点があったら指摘してもらえると嬉しかったりします。

なお、このツールでパッケージにしたものは外部に配布することを想定していません。

これはcopyrightとかdescriptionとかまでカスタマイズできるするのが面倒だったというのがあるので、そのうちなんとかするかもしれないです。
が、現時点では自身の環境にインストールするパッケージを作るのに使うぐらいが良いでしょう。


9月 062012
 

Amazon S3 REST-API is necessary to generate signature.
vmod-awsrest generate to Authorization and Date header for Amazon S3.

How to use

VCL


import awsrest;

backend default {
  .host = "s3.amazonaws.com";
  .port = "80";
}

sub vcl_recv{
  awsrest.s3_generic(
  "accessKey",            //AWSAccessKeyId
  "secretKey",            //SecretAccessKeyID
  req.request,            //HTTP-Verb
  req.http.content-md5,   //Content-MD5
  req.http.content-type,  //Content-Type
  "",                     //canonicalizedAmzHeaders
  req.url,                //canonicalizedResource
  now                     //Date
  );
}

Output


15 TxHeader     b Date: Tue, 03 Jul 2012 16:21:47 +0000
15 TxHeader     b Authorization: AWS accessKey:XUfSbQDuOWL24PTR1qavWSr6vjM=

This module set to req.http.Authorization and req.http.Date, bereq is not use.
I recommend call in the vcl_recv.
And, be careful to default settings.
If req.http.Authorization contains, it is not caching. (default setting)

download here.
libvmod-awsrest


Reference site
PHP で Amazon S3 の REST API を使用 #1
Authenticating REST Requests

日本語はこっち
VarnishでAmazon S3の認証ヘッダを作るVMODを作ってみた


8月 042012
 

急に、LDAPにアクセスしてみたくなったので、勉強がてら作ってみました。

よく使われそうな、シンプルなLDAPの認証はもちろん
特定のグループにだけ許可のようなことも可能です。
VCLはロジックがかけるので、親和性が高いんじゃないかなと考えています。

シンプルなLDAPを使ったBASIC認証


import ldap;

sub vcl_error {
  if (obj.status == 401) {
    set obj.http.WWW-Authenticate = {"Basic realm="Authorization Required""};
    synthetic {"Error 401 Unauthorized"};
    return(deliver);
  }
}

sub vcl_recv{

if(req.url ~ "^/member/"){
        if(!(req.http.Authorization &;amp;&;amp; ldap.simple_auth(
          true, //V3プロトコルで接続するか
          "cn=Manager,dc=ldap,dc=example,dc=com", //バインドアカウント(User)
          "password", //バインドアカウント(Pass)
          "ldap://192.168.1.1/ou=people,dc=ldap,dc=example,dc=com?uid?sub?(objectClass=*)", //LDAP接続先
          ldap.get_basicuser(), //認証したいアカウント(User)
          ldap.get_basicpass()  //認証したいアカウント(Pass)
        ))){
                error 401;
        }
}

グループとユーザで制限してみる


import ldap;

sub vcl_deliver{
  //LDAPを閉じる
  ldap.close();
}

sub vcl_error{
  if (obj.status == 401) {
    set obj.http.WWW-Authenticate = {"Basic realm="Authorization Required""};
    synthetic {"Error 401 Unauthorized"};
    return(deliver);
  }
}

sub vcl_recv{

  if(req.url ~ "^/member/"){
        //LDAPに接続
        if(!(req.http.Authorization &;amp;&;amp; ldap.open(
          true, //V3プロトコルで接続するか
          "cn=Manager,dc=ldap,dc=example,dc=com", //バインドアカウント(User)
          "password", //バインドアカウント(Pass)
          "ldap://192.168.1.1/ou=people,dc=ldap,dc=example,dc=com?uid?sub?(objectClass=*)", //LDAP接続先
          ldap.get_basicuser(), //認証したいアカウント(User)
          ldap.get_basicpass()  //認証したいアカウント(Pass)
        ))){
                error 401;
        }
        //グループの照合
        if(!ldap.compare("cn=test,ou=people,dc=ldap,dc=example,dc=com","memberUid")){ldap.close();error 401;}
        //ユーザの照合
        if(!ldap.require_user("uid=hogehoge,ou=people,dc=ldap,dc=example,dc=com")){ldap.close();error 401;}
        //パスワードの照合
        if(!ldap.bind()){ldap.close();error 401;}
        ldap.close();
  }
}

ちなみにget_basicuserとget_basicpassを使うと
BASIC認証を行った時に送られてくるAuthorizationヘッダからユーザIDとパスを取得する事ができます。

なおシンプルな認証で使うsimple_authは内部ではopenとbindとcloseを呼び出しています。

ダウンロードはこちら(vmod-ldap)


7月 242012
 

先日、社内で勉強会をした時に発表した資料(ちょっと改変)です。
最初はVMODを作るときに知っておくと楽じゃないかなぁ的なポイントを解説しようと思ったんですが
そもそもVMODのワンポイントなんか社内的に誰得極まりないので
公式でリスト化されてるVMODを紹介してみました。
サービス側がこれ使ってみたいってのがあればいいなぁと考えています。

結構口頭で説明してるとこがあるので、そこは申し訳ないです。
いろいろ他の人で面白い発表も聞けたのでまた第二回とかやりたいですね


7月 112012
 

hiro_yさんからこんな質問を受けたので


AWSの勉強がてら作ってみました。

S3のREST-APIのAuthorizationヘッダは、日付やリソースの場所などを改行で結合して
HMAC-SHA1でハッシュ化して、BASE64エンコードする必要があります。
HMAC-SHA1については、Varnish公式が公開しているvmod-digestを使うことでできるのですが
出力をBASE64にすることができないので、コードを拝借して今回のVMODを作ってみました。
ちなみに改行を扱うことについても、インラインCかVMODを使う必要があります。

使い方


import awsrest;

backend default {
  .host = "s3.amazonaws.com";
  .port = "80";
}

sub vcl_recv{
  awsrest.s3_generic(
  "accessKey",            //AWSAccessKeyId
  "secretKey",            //SecretAccessKeyID
  req.request,            //HTTP-Verb
  req.http.content-md5,   //Content-MD5
  req.http.content-type,  //Content-Type
  "",                     //canonicalizedAmzHeaders
  req.url,                //canonicalizedResource
  now                     //Date
  );
}

上記のように呼び出すとreq.http.Authorizationとreq.http.Dateに生成したヘッダがセットされます。


15 TxHeader     b Date: Tue, 03 Jul 2012 16:21:47 +0000
15 TxHeader     b Authorization: AWS accessKey:XUfSbQDuOWL24PTR1qavWSr6vjM=

セットされるのがbereqではないのでvcl_recvで呼び出すことを推奨します。

またAuthorizationを生成するのでもしキャッシュを行いたい場合は適切にVCLを記述してください。
デフォルトのVCLではreq.http.Authorizationを含む場合はpassするためです。

僕の方でも簡単な動作チェックを行なっていますが、もし変な動きをしたら教えて下さい。

downloadはこちら(libvmod-awsrest)


参考サイト
PHP で Amazon S3 の REST API を使用 #1
Authenticating REST Requests


6月 132012
 

Difficult to access the POST/GET/Cookie value in Varnish.
I want more easily to access it.
Therefore, I tried to make a VMOD to parse.

Feature

  • Support POST/GET request and Cookie header.
  • Support application/x-www-form-urlencoded and multipart/form-data Content-type in POST request.
  • Can be getting a list of key.
  • Support Varnish 3.0.1, 3.0.2 , 3.0.3-rc1

How to use

For example, how to set the response header from the POST key of hoge.
It’s a simple.


import parsereq;

vcl_recv{
  //please write "parsereq.init();" to 1st line in vcl_recv.
  parsereq.init();
}
vcl_deliver{
  set resp.http.hoge = parsereq.post_header("hoge");
}

Future plans

  • urlencode/decode
  • change the value and set to bereq
  • refactoring

I hope that this code is of help to you.

download here.
vmod-parsereq


6月 102012
 

この前のエントリでPOSTを扱うVMODを作ってみたというのを上げたのですが
あの後、使ってくれた人とかからインタフェース変えるとイイヨーイイヨーと言われたので
req.http.*に格納するのではなくvmod_curlのように良い感じに使いやすくしてみました。

それだけじゃつまらないということで、おまけでGETとCOOKIEに対応してみました。

今回の特徴は以下です。
・application/x-www-form-urlencodedとmultipart/form-data両方に対応
・GET・POST・COOKIEに対応
・格納されているキーの一覧を取得することができます。
 インラインCと併用することですべてのGET・POST・COOKIEのキーに対して特定の値
 (攻撃っぽいコードなど)が含まれているかなどのチェックが容易です

使い方はこんな感じです。
たとえばPOSTでhogeというキーを取得してレスポンスする場合はこんな感じです。


import parsereq;

vcl_recv{
  parsereq.init();//必ずvcl_recvの先頭で宣言する
}
vcl_deliver{
  set resp.http.hoge = parsereq.post_header("hoge");
}

前に比べてわかりやすくなったんではないかと思います。
また今回は値はurlencodeされていません。生の値です。
そのため改行を含む可能性がある場合はresp.http.*などに含めると
意図しない結果が起きるので注意が必要です。
ココらへんはencode/decodeメソッドを用意して解決する予定ですので
少し待ってもらえるとありがたいです。

関数はこんな感じです。

VOID init()
 初期化を行う。
 必ずvcl_recvの先頭で呼び出す必要があります。

INT errcode()
 初期化の結果を受け取る


(成功) 2   content-typeが無いかサポート外
(成功) 1   パースに成功
(失敗) -1  sess_workspaceの残りサイズがなくなってメモリ確保に失敗
(失敗) -2  content-lengthが無い
(失敗) -3  読み取れるサイズがcontent-length以下

STRING post_header(STRING)
STRING get_header(STRING)
STRING cookie_header(STRING)

 指定したキーの値を取得する

STRING post_body()
STRING get_body()
STRING cookie_body()

 POST,GET,COOKIEの生データを取得

STRING post_read_keylist()
STRING get_read_keylist()
STRING cookie_read_keylist()

 呼び出すごとに格納されているキーを取得する


//req
/?name1=a&name2=b

//vcl
vcl_deliver{
  set resp.http.n1 = parsereq.get_read_keylist();
  set resp.http.n2 = parsereq.get_read_keylist();
  //nothing
  set resp.http.n3 = parsereq.get_read_keylist();
}

//return
n1: name2
n2: name1

VOID post_seek_reset()
VOID get_seek_reset()
VOID cookie_seek_reset()

 前述のXXX_read_keylistで、何処まで取得したかをリセットする。
 リセットしてXXX_read_keylistを呼び出すとまた最初から読み出す。

今後リリースされる3.0.3についても
rc1において正常動作を確認していますので多分動くと思います。
基本的に3.0.1以降であれば動きます。

今後の予定は
・エンコード・デコードロジック追加
・値の変更した後にリビルドしてそれを使う
・コードが若干カオスってるので綺麗にする
・etc…
です

downloadはこちら


6月 042012
 

Varnishは基本的にPOSTデータを解釈してなにかしらの処理をすることができません。
そこまで不便はないとは思うのですが、たとえば特定のキーワードがPOSTに入ってたら
Varnishではじきたいといったことは普通は出来ません。(インジェクションっぽいクエリとか)
そのためVarnishでPOSTを手軽に扱うためのVMODを作ってみました。

以下のような特徴・機能があります。
 ・application/x-www-form-urlencodedとmultipart/form-data両方に対応
 ・パースした値をreq.http.*に格納する
 ・指定されたヘッダにパースしていない生データを格納可能
 ・multipartのデータはurlencodeして格納
 ・multipartの生データは(扱いづらいので)application/x-www-form-urlencodedと同じ形式に直して格納
 ・multipart時のファイルについても格納

こんな感じで利用します。


//VCL
if(parsepost.parse("x-raw",true,"p_",true,true) == 1){
  std.log("raw: " + req.http.x-raw);
  std.log("submitter: " + req.http.p_submitter);
  std.log("submitter2: " + req.http.p_submitter2);
}

//response
12 VCL_Log      c raw: submitter=abcdef&submitter2=b
12 VCL_Log      c submitter: abcdef
12 VCL_Log      c submitter2: b

また特性上セッションワークスペースとスタックを多く使います。
そのためエラーが出る場合は
Varnishのsess_workspaceの値の引き上げとulimit -sの値の引き上げを行なってください。
またhttp_req_sizeを大きめにしておくと結果としてメモリの節約になります

あと生データは大きくなりがちなので使い終わったらunsetをおすすめします
そうしないとバックエンドにフェッチする際のリクエストに含まれてヘッダ大きすぎエラー(413 Request Entity Too Large/400 Bad Request)が出る可能性もあります。

ダウンロードはこちらから


5月 262012
 

VMOD processing want to before/after VCL function processing.
But, I do not want to write extra line.
How to do it?


Can be realized by the replace the pointer to sp->vcl->XXXX_func (VCL_XXXX)


Can be added to the processing by replacing pointer



static vcl_func_f         *vmod_redirect_Hook_vcl_error = NULL;
static pthread_mutex_t    vmod_redirect_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned           hook_done = 0;

static int vmod_Hook_vcl_error(struct sess *sp){

    ....    //write the processing

    //call original vcl_error
    return(vmod_redirect_Hook_vcl_error(sp));

}

int
vmod_location(struct sess *sp, int status, const char*p,...)
{
        //vcl reload detection.
        if(hook_done == 1
           && sp->vcl->error_func != vmod_redirect_Hook_vcl_error ) hook_done = 0;

        if(hook_done == 0){
            //lock to prevent another thread write
            AZ(pthread_mutex_lock(&vmod_redirect_mutex));

            if(hook_done == 0)
            {
                //store the original vcl_error pointer
                vmod_redirect_Hook_vcl_error = sp->vcl->error_func;

                //hook
                sp->vcl->error_func = vmod_Hook_vcl_error;

                hook_done = 1;
            }
            //unlock
            AZ(pthread_mutex_unlock(&vmod_redirect_mutex));
        }

    ....

    return (status);
}

(via:vmod_redirect)

Hook to vcl_error at vmod processing in first time.
because there is no way to write-access the pointer at vmod_Init.


also, has locked using the mutex, because to prevent the loop by thread concurrent access.

I hope that this code is of help to you.

ATTENTION
don’t use varnishadm’s command “vcl.use” and “vcl.discard” . because to the segfault or call to other vcl function.


modify(2012-08-01)
when you vcl reloaded, hook method be off.


5月 252012
 

VMODを作る上で何かしらの後処理や前処理を、ユーザの処理前にやりたいケースが存在します。
たとえば僕が以前作ったvmod_redirectの場合です。

Varnishのリダイレクトはかなりめんどくさくて以下のように記述する必要があります。


sub vcl_recv {
  if (req.http.user-agent ~ "iP(hone|od)") {
    error 750 "Moved Temporarily";
  }
}

sub vcl_error {
  if (obj.status == 750) {
    set obj.http.Location = "http://www.example.com/iphoneversion/";
    set obj.status = 302;
    return(deliver);
  }
}

このように一回エラー関数でvcl_errorに飛ばして判断する必要があります。
しかしvmod_redirectではvcl_errorに記述することなくredirectを実現しています。


import redirect;
sub vcl_recv {
   if (req.http.user-agent ~ "iP(hone|od)") {
      error(redirect.location(302,"http://www.example.com/iphoneversion/") , "Moved Temporarily");
   }
}

これはvcl_errorの関数ポインタをフックすることにより
オリジナルのvcl_errorが呼び出される前に独自の関数を呼び出すためです。

ではフックするためのポインタは何処に定義されているでしょうか?
vmodで使うsturct sess構造体(sp)に含まれています。


vcl_errorの場合
 sp->vcl->error_func

vcl_deliverの場合
 sp->vcl->deliver_func
...

ここのポインタを書き換えることでVarnishが呼び出すアクションを変更することが可能です。

そのことでオリジナルのアクションを呼び出す前後で任意の処理を行うことができ非常に便利です。

実際に書き換える場合はこのようなコードで行います


static vcl_func_f         *vmod_redirect_Hook_vcl_error = NULL;
static pthread_mutex_t    vmod_redirect_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned           hook_done = 0;

static int vmod_Hook_vcl_error(struct sess *sp){

    ....//任意の処理を書く

    //オリジナルのvcl_errorを呼び出してリターン
    return(vmod_redirect_Hook_vcl_error(sp));

}


int 
vmod_location(struct sess *sp, int status, const char*p,...)
{
        //VCLのreloadでフックが外れた場合の検知
        if(hook_done == 1
           && sp->vcl->error_func != vmod_redirect_Hook_vcl_error ) hook_done = 0;

        if(hook_done == 0){
            //別のスレッドが同時に書き込みをしないようにロックする
            AZ(pthread_mutex_lock(&vmod_redirect_mutex));
            if(hook_done == 0)
            {
                //退避先にオリジナルのポインタを保存
                vmod_redirect_Hook_vcl_error = sp->vcl->error_func;
                //フックする
                sp->vcl->error_func = vmod_Hook_vcl_error;
                
                hook_done = 1;
            }
            //ロック開放
            AZ(pthread_mutex_unlock(&vmod_redirect_mutex));
        }
    
    ....

    return (status);
}


ポイントとして初回のvmod関数呼び出し時にフックします。
これはvmod_Initではフックをするために必要な構造体へのアクセス方法が無いためです。
またmutexを利用してロックしていますが、これは複数のスレッドが同時にフックを行おうとしてフック関数でループするのを防ぐためです


ロックを行わないと、上図の(6)でオリジナルのポインタとおもって、フック後のポインタを取得してしまいます。
二回0チェックしているのは、ロックをかける間に他のスレッドが書き込んでいないことを確認するためです。

このようにフックを使うことでよりVMODの表現が増えるんじゃないかなと思います。
参考になれば幸いです

注意
varnishadmのvcl.useとvcl.discardを使わないでください。
vcl.useの場合は直前のVCLのメソッドが呼ばれ、vcl.discardはセグフォを起こす危険性があります

—-
2012-08-02追記
VCLのリロードを行った時にフックが外れるので検知用コードを追加(hook_done)