Skip to content
Snippets Groups Projects
ngx_http_voms_module.cpp 8.92 KiB
Newer Older
Francesco Giacomini's avatar
Francesco Giacomini committed
extern "C" {
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
}
#include <voms/voms_api.h>
#include <cassert>
#include <chrono>
#include <iostream>
#include <memory>
#include <numeric>
#include <string>
#include <boost/algorithm/string/join.hpp>
#include <boost/optional.hpp>

using BioPtr = std::unique_ptr<BIO, decltype(&BIO_free)>;
using X509Ptr = std::unique_ptr<X509, decltype(&X509_free)>;
using VomsAc = voms;
using MaybeVomsAc = boost::optional<VomsAc>;

static ngx_int_t add_variables(ngx_conf_t* cf);

static ngx_http_module_t ctx = {
    add_variables,  // preconfiguration
    NULL,           // postconfiguration
    NULL,           // create main configuration
    NULL,           // init main configuration
    NULL,           // create server configuration
    NULL,           // merge server configuration
    NULL,           // create location configuration
    NULL            // merge location configuration
};

ngx_module_t ngx_http_voms_module = {
    NGX_MODULE_V1,
    &ctx,                  // module context
    NULL,                  // module directives
    NGX_HTTP_MODULE,       // module type
    NULL,                  // init master
    NULL,                  // init module
    NULL,                  // init process
    NULL,                  // init thread
    NULL,                  // exit thread
    NULL,                  // exit process
    NULL,                  // exit master
    NGX_MODULE_V1_PADDING  //
};

static std::unique_ptr<vomsdata> vomsdata_ptr;

Francesco Giacomini's avatar
Francesco Giacomini committed
static ngx_int_t get_voms_fqans(  //
    ngx_http_request_t* r,
    ngx_http_variable_value_t* v,
    uintptr_t data);
static ngx_int_t get_voms_user(  //
    ngx_http_request_t* r,
    ngx_http_variable_value_t* v,
    uintptr_t data);

static ngx_http_variable_t variables[] = {
    {
        ngx_string("voms_fqans"),
        NULL,
        get_voms_fqans,
        0,
        NGX_HTTP_VAR_NOCACHEABLE,
        0  //
    },
    {
        ngx_string("voms_user"),
        NULL,
        get_voms_user,
        0,
        NGX_HTTP_VAR_NOCACHEABLE,
        0  //
    },
    ngx_http_null_variable  //
};

static ngx_int_t add_variables(ngx_conf_t* cf)
{
  for (ngx_http_variable_t* v = variables; v->name.len; ++v) {
    ngx_http_variable_t* var = ngx_http_add_variable(cf, &v->name, v->flags);
    if (var == NULL) {
      return NGX_ERROR;
    }

    var->get_handler = v->get_handler;
    var->data = v->data;
  }

  return NGX_OK;
}

template <class S>
static std::string to_string(S const* s)
{
  return std::string(reinterpret_cast<char const*>(s->data),
                     reinterpret_cast<char const*>(s->data) + s->len);
}

boost::optional<std::string> to_pem(X509& x509)
{
  BioPtr bio{BIO_new(BIO_s_mem()), BIO_free};
  if (PEM_write_bio_X509(bio.get(), &x509) == 0) {
    return boost::none;
  } else {
    char* data = nullptr;
    auto len = BIO_get_mem_data(bio.get(), &data);
    if (len > 0) {
      return std::string(data, data + len);
    } else {
      return boost::none;
    }
  }
}

// return the first AC, if present
static MaybeVomsAc retrieve_voms_ac_from_proxy(ngx_http_request_t* r)
{
  ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "%s", __FUNCTION__);

  if (!r->http_connection->ssl) {
    return boost::none;
  }

  auto client_cert = X509Ptr{
      SSL_get_peer_certificate(r->connection->ssl->connection), X509_free};
  if (!client_cert) {
    ngx_log_error(NGX_LOG_ERR,
                  r->connection->log,
                  0,
                  "SSL_get_peer_certificate() failed");
    return boost::none;
  }

  auto client_chain = SSL_get_peer_cert_chain(r->connection->ssl->connection);
  if (!client_chain) {
    ngx_log_error(
        NGX_LOG_ERR, r->connection->log, 0, "SSL_get_peer_cert_chain() failed");
    return boost::none;
  }

  if (!vomsdata_ptr) {
    vomsdata_ptr.reset(new vomsdata);
  }
  auto ok = vomsdata_ptr->Retrieve(client_cert.get(), client_chain, RECURSE_CHAIN);
Francesco Giacomini's avatar
Francesco Giacomini committed
  if (!ok) {
    // vd.error is not interpreted correctly by the logger, which probably uses
    // errno
    ngx_log_error(NGX_LOG_ERR,
                  r->connection->log,
                  vomsdata_ptr->error,
Francesco Giacomini's avatar
Francesco Giacomini committed
                  "%s",
                  vomsdata_ptr->ErrorMessage().c_str());
Francesco Giacomini's avatar
Francesco Giacomini committed
    return boost::none;
  }

  if (vomsdata_ptr->data.empty()) {
Francesco Giacomini's avatar
Francesco Giacomini committed
    ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "no ACs in proxy");
    return boost::none;
  }

  return vomsdata_ptr->data.front();
Francesco Giacomini's avatar
Francesco Giacomini committed
}

static void clean_voms_ac(void* data)
{
  auto r = static_cast<ngx_http_request_t*>(data);
  ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "%s", __FUNCTION__);

  auto p = static_cast<MaybeVomsAc*>(
      ngx_http_get_module_ctx(r, ngx_http_voms_module));
  delete p;
}

static void cache_voms_ac(ngx_http_request_t* r, MaybeVomsAc* ac)
{
  ngx_http_set_ctx(r, ac, ngx_http_voms_module);
  auto cln = ngx_http_cleanup_add(r, 0);
  if (cln) {
    cln->handler = clean_voms_ac;
    cln->data = r;
  } else {
    ngx_log_error(
        NGX_LOG_ERR, r->connection->log, 0, "ngx_http_cleanup_add() failed");
  }
}

static MaybeVomsAc* get_voms_ac_from_cache(ngx_http_request_t* r)
{
  return static_cast<MaybeVomsAc*>(
      ngx_http_get_module_ctx(r, ngx_http_voms_module));
}

static MaybeVomsAc const& get_voms_ac(ngx_http_request_t* r)
{
  ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "%s", __FUNCTION__);

  MaybeVomsAc* acp = get_voms_ac_from_cache(r);

  if (!acp) {
    acp = new MaybeVomsAc(retrieve_voms_ac_from_proxy(r));
    cache_voms_ac(r, acp);
  }

  return *acp;
}

static ngx_int_t get_voms_fqans(ngx_http_request_t* r,
                                ngx_http_variable_value_t* v,
                                uintptr_t)
{
  ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "%s", __FUNCTION__);

  v->not_found = 1;
  v->valid = 0;

  auto& ac = get_voms_ac(r);

  if (!ac) {
    ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "get_voms_ac() failed");
    return NGX_OK;
  }

  auto fqans = boost::algorithm::join(ac->fqan, ",");
Francesco Giacomini's avatar
Francesco Giacomini committed

  auto data = static_cast<u_char*>(ngx_pnalloc(r->pool, fqans.size()));
  if (!data) {
    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pnalloc() failed");
    return NGX_OK;
  }
  ngx_memcpy(data, fqans.c_str(), fqans.size());

  v->data = data;
  v->len = fqans.size();
  v->valid = 1;
  v->not_found = 0;
  v->no_cacheable = 0;
  return NGX_OK;
}

static ngx_int_t get_voms_user(ngx_http_request_t* r,
                               ngx_http_variable_value_t* v,
                               uintptr_t)
{
  ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "%s", __FUNCTION__);

  v->not_found = 1;
  v->valid = 0;

  auto& ac = get_voms_ac(r);

  if (!ac) {
    ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "get_voms_ac() failed");
    return NGX_OK;
  }

  auto const& user = ac->user;

  auto data = static_cast<u_char*>(ngx_pnalloc(r->pool, user.size()));
  if (!data) {
    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pnalloc() failed");
    return NGX_OK;
  }
  ngx_memcpy(data, user.c_str(), user.size());

  v->data = data;
  v->len = user.size();
  v->valid = 1;
  v->not_found = 0;
  v->no_cacheable = 0;
  return NGX_OK;
}

ngx_int_t get_voms(ngx_http_request_t* r,
                   ngx_http_variable_value_t* v,
                   uintptr_t data)
{
  // to show that get_voms gets called only once, even if the variable is used
  // twice in the configuration file
  static int count = 0;
  assert(count == 0);
  ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "get_voms");

  {
    auto t0 = std::chrono::high_resolution_clock::now();

    static ngx_str_t var = ngx_string("ssl_client_raw_cert");
    u_char unused[sizeof("ssl_client_raw_cert")];

    auto hash = ngx_hash_strlow(unused, var.data, var.len);

    ngx_http_variable_value_t* raw_cert = ngx_http_get_variable(r, &var, hash);

    // da rivedere gli errori ritornati (sempre che siano errori)

    if (!raw_cert || raw_cert->not_found || !raw_cert->valid) {
      ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "invalid raw_cert");
      return NGX_OK;
    }

    BioPtr bio{BIO_new(BIO_s_mem()), BIO_free};
    if (BIO_write(bio.get(), raw_cert->data, raw_cert->len) != raw_cert->len) {
      ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "BIO_write() failed");
      return NGX_OK;
    }

    X509Ptr x509{PEM_read_bio_X509(bio.get(), NULL, NULL, NULL), X509_free};
    if (!x509) {
      ngx_log_error(
          NGX_LOG_DEBUG, r->connection->log, 0, "PEM_read_bio_X509() failed");
      return NGX_OK;
    }

    auto t1 = std::chrono::high_resolution_clock::now();

    ngx_log_error(NGX_LOG_DEBUG,
                  r->connection->log,
                  0,
                  "time 1: %f us",
                  std::chrono::duration<double, std::micro>(t1 - t0).count());

    ngx_log_error(NGX_LOG_DEBUG,
                  r->connection->log,
                  0,
                  "raw_cert: %s",
                  to_string(raw_cert).c_str());
  }

  v->valid = 1;
  v->no_cacheable = 1;
  v->not_found = 0;

  v->data = (u_char*)"VOMS";
  v->len = 4;

  return NGX_OK;
}