Skip to content
Snippets Groups Projects
ngx_http_voms_module.cpp 11 KiB
Newer Older
  • Learn to ignore specific revisions
  • Francesco Giacomini's avatar
    Francesco Giacomini committed
    // Copyright 2018 Istituto Nazionale di Fisica Nucleare
    //
    // Licensed under the EUPL, Version 1.2 or - as soon they will be approved by
    // the European Commission - subsequent versions of the EUPL (the "Licence").
    // You may not use this work except in compliance with the Licence. You may
    // obtain a copy of the Licence at:
    //
    // https://joinup.ec.europa.eu/software/page/eupl
    //
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the Licence is distributed on an "AS IS" basis, WITHOUT
    // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    // Licence for the specific language governing permissions and limitations under
    // the Licence.
    
    
    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;
    
    
    static ngx_int_t generic_getter(  //
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
        ngx_http_request_t* r,
        ngx_http_variable_value_t* v,
        uintptr_t data);
    
    
    using getter_t = std::string(VomsAc const& voms);
    static getter_t get_voms_user;
    static getter_t get_voms_user_ca;
    static getter_t get_voms_fqans;
    static getter_t get_voms_server;
    static getter_t get_voms_server_ca;
    static getter_t get_voms_vo;
    static getter_t get_voms_server_uri;
    static getter_t get_voms_not_before;
    static getter_t get_voms_not_after;
    static getter_t get_voms_generic_attributes;
    static getter_t get_voms_serial;
    
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
    static ngx_http_variable_t variables[] = {
    
        {
            ngx_string("voms_user"),
            NULL,
            generic_getter,
            reinterpret_cast<uintptr_t>(&get_voms_user),
            NGX_HTTP_VAR_NOCACHEABLE,
            0  //
        },
        {
            ngx_string("voms_user_ca"),
            NULL,
            generic_getter,
            reinterpret_cast<uintptr_t>(&get_voms_user_ca),
            NGX_HTTP_VAR_NOCACHEABLE,
            0  //
        },
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
        {
            ngx_string("voms_fqans"),
            NULL,
    
            generic_getter,
            reinterpret_cast<uintptr_t>(&get_voms_fqans),
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
            NGX_HTTP_VAR_NOCACHEABLE,
            0  //
        },
        {
    
            ngx_string("voms_server"),
            NULL,
            generic_getter,
            reinterpret_cast<uintptr_t>(&get_voms_server),
            NGX_HTTP_VAR_NOCACHEABLE,
            0  //
        },
        {
            ngx_string("voms_server_ca"),
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
            NULL,
    
            generic_getter,
            reinterpret_cast<uintptr_t>(&get_voms_server_ca),
            NGX_HTTP_VAR_NOCACHEABLE,
            0  //
        },
        {
            ngx_string("voms_vo"),
            NULL,
            generic_getter,
            reinterpret_cast<uintptr_t>(&get_voms_vo),
            NGX_HTTP_VAR_NOCACHEABLE,
            0  //
        },
        {
            ngx_string("voms_server_uri"),
            NULL,
            generic_getter,
            reinterpret_cast<uintptr_t>(&get_voms_server_uri),
            NGX_HTTP_VAR_NOCACHEABLE,
            0  //
        },
        {
            ngx_string("voms_not_before"),
            NULL,
            generic_getter,
            reinterpret_cast<uintptr_t>(&get_voms_not_before),
            NGX_HTTP_VAR_NOCACHEABLE,
            0  //
        },
        {
            ngx_string("voms_not_after"),
            NULL,
            generic_getter,
            reinterpret_cast<uintptr_t>(&get_voms_not_after),
            NGX_HTTP_VAR_NOCACHEABLE,
            0  //
        },
        {
            ngx_string("voms_generic_attributes"),
            NULL,
            generic_getter,
            reinterpret_cast<uintptr_t>(&get_voms_generic_attributes),
            NGX_HTTP_VAR_NOCACHEABLE,
            0  //
        },
        {
            ngx_string("voms_serial"),
            NULL,
            generic_getter,
            reinterpret_cast<uintptr_t>(&get_voms_serial),
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
            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;
    }
    
    // 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", __func__);
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
    
      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", __func__);
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
    
      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", __func__);
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
    
      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 generic_getter(ngx_http_request_t* r,
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
                                    ngx_http_variable_value_t* v,
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
    {
    
      ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "%s", __func__);
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
    
      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;
      }
    
    
      using getter_p = std::string (*)(VomsAc const& voms);
      auto getter = reinterpret_cast<getter_p>(data);
      std::string const value = getter(*ac);
    
      auto buffer = static_cast<u_char*>(ngx_pnalloc(r->pool, value.size()));
      if (!buffer) {
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pnalloc() failed");
        return NGX_OK;
      }
    
      ngx_memcpy(buffer, value.c_str(), value.size());
    
      v->data = buffer;
      v->len = value.size();
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
      v->valid = 1;
      v->not_found = 0;
      v->no_cacheable = 0;
      return NGX_OK;
    }
    
    
    std::string get_voms_user(VomsAc const& ac)
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
    {
    
    std::string get_voms_user_ca(VomsAc const& ac)
    {
      return ac.userca;
    }
    
    std::string get_voms_fqans(VomsAc const& ac)
    {
      return boost::algorithm::join(ac.fqan, ",");
    }
    
    std::string get_voms_server(VomsAc const& ac)
    {
      return ac.server;
    }
    
    std::string get_voms_server_ca(VomsAc const& ac)
    {
      return ac.serverca;
    }
    
    std::string get_voms_vo(VomsAc const& ac)
    {
      return ac.voname;
    }
    
    std::string get_voms_server_uri(VomsAc const& ac)
    {
      return ac.uri;
    }
    
    std::string get_voms_not_before(VomsAc const& ac)
    {
      return ac.date1;
    }
    
    std::string get_voms_not_after(VomsAc const& ac)
    {
      return ac.date2;
    }
    
    // struct attribute {
    //   std::string name;      /*!< attribute's group */
    //   std::string qualifier; /*!< attribute's qualifier */
    //   std::string value;     /*!< attribute's value */
    // };
    
    // struct attributelist {
    //   std::string grantor;               /*!< Who granted these attributes. */
    //   std::vector<attribute> attributes; /*!< The attributes themselves.    */
    // };
    
    
    std::string escape_uri(std::string const& src)
    {
      // the following just counts the number of characters that need escaping
    
      auto const n_escape =
          ngx_escape_uri(nullptr,  // <--
                         reinterpret_cast<u_char*>(const_cast<char*>(src.data())),
                         src.size(),
                         NGX_ESCAPE_URI_COMPONENT);
    
    
      if (n_escape == 0) {
        return src;
      }
    
    
      std::string result;
      result.resize(src.size() + 2 * n_escape);
      auto last = reinterpret_cast<char*>(ngx_escape_uri(
          reinterpret_cast<u_char*>(const_cast<char*>(result.data())),
          reinterpret_cast<u_char*>(const_cast<char*>(src.data())),
          src.size(),
          NGX_ESCAPE_URI_COMPONENT));
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
      assert(last == result.data() + result.size());
    
      return result;
    
    static std::string encode(attribute const& a)
    {
    
      return "n=" + a.name + " v=" + escape_uri(a.value) + " q=" + a.qualifier;
    
    std::string get_voms_generic_attributes(VomsAc const& ac)
    {
    
      std::string result;
    
    
      // the GetAttributes method is not declared const
      auto const attributes = const_cast<VomsAc&>(ac).GetAttributes();
    
      if (!attributes.empty()) {
        auto& gas = attributes.front().attributes;
        bool first = true;
        for (auto& a : gas) {
    
          if (first) {
            first = false;
          } else {
    
            result += ',';
          }
    
          result += encode(a);
    
    }
    
    std::string get_voms_serial(VomsAc const& ac)
    {
      return ac.serial;
    
    Francesco Giacomini's avatar
    Francesco Giacomini committed
    }