Definición del servicio

En este apartado se describirá el proceso de definición de un servicio. Para ello, se explicará por medio de un ejemplo cómo se define un servicio de listas en xlistd y, a continuación, se tratarán en profundidad los diferentes tipos de piezas que tendremos a nuestra disposición.

Recuerde que dispone de Guías con configuraciones ya realizadas que podrá fácilmente modificar según sus necesidades. Estas guías pueden servir también de apoyo para entender esta parte de la documentación.

Explicación

Simplificando mucho, xlistd podría considerarse una especie de servicio de agregación de listas negras y blancas. Dicho servicio se configura a través de un fichero (o ficheros) de configuración en el que se lleva a cabo la definición del mismo. Definir un servicio xlistd es como construir un castillo de juguete con piezas. Se explicará con mayor detalle a continuación, pero podemos decir brevemente que hay dos tipos de piezas: los componentes y los envoltorios.

Para realizar la explicación realizaremos un ejemplo en el que:

  1. Se realizará un diagrama de bloques explicativo con el diseño deseado.

  2. Se incluirá un diagrama de secuencia que explique cómo responderá a las solicitudes.

  3. Se plasmará el diseño en un fichero de definición del servicio.

Componentes

xlistd
Figura 1. Ejemplo de composición

En Ejemplo de composición puede verse gráficamente cómo podría ser una posible definición del servicio. Para completar la información de la imagen y poder interpretarla correctamente aclarar que:

  1. Los componentes del servicio son los elementos encuadrados.

  2. Los componentes tienen un nombre único identificador. Ej: root, ip-service, my-whitelist-ip4, etc.

  3. Los componentes tienen un arquetipo o una "clase" que indica el comportamiento del componente. Ej: selector, wbefore, file, etc.

  4. Los componentes dan soporte a un conjunto de tipos de recursos. Ej: [ip4,domain], [ip4], etc.

  5. Las líneas indican la comunicación que se lleva a cabo entre los componentes, es decir, por dónde seguirán las peticiones.

Como puede observarse también:

  1. Todas las peticiones se dirigen a un componente raíz o root.

  2. Los componentes pueden tener nodos hijos a los que reenviar la petición. Esto evidentemente dependerá del arquetipo. En el ejemplo tenemos: selector, wbefore y parallel.

  3. Existen componentes que no tienen nodos hijos y que por lo tanto procesarán y responderán a la petición. En el ejemplo tenemos: file, geoip2, grpc y dnsxl.

  4. Los componentes pueden ser referenciados varias veces desde distintas ramas como puede verse en los componentes remote1-blacklist y remote2-blacklist.

sequence-domain
Figura 2. Ejemplo de secuencia

En el Ejemplo de secuencia podemos ver cómo seria la secuencia de un chequeo. La explicación es la siguiente:

  1. La apixlist traslada la petición a la lista fijada como root.

  2. La lista root es de la clase selector, lo que hará que seleccione de sus componentes hijas aquella que tenga registrada para servir peticiones de tipo domain. En este caso es el componente domain-service.

  3. El componente domain-service es de la clase wbefore, esta clase lo que hace es chequear primero en una lista blanca y si está en la lista blanca retornará inmediatamente negativo. Así lo hace, y al no encontrarse traslada al consulta al componente blacklist-domain.

  4. El componente blacklist-domain, al ser de la clase parallel, consultará en paralelo a sus componentes hijos: remote1-blacklist y remote2-blacklist.

  5. Estos componentes, chequearán en servidores remotos usando los protocolos GRPC y DNSxL y devolverán su resultado. Si alguno de estos componentes devuelve un resultado afirmativo, el componente de la clase parallel retornará el primero que lo haga.

Ejemplo de configuración
[
  {
    "id": "remote1-blacklist",
    "class": "grpc",
    "resources": [ "ip4", "domain" ],
    "source": "unix:///run/xlistd1.socket"
  },
  {
    "id": "remote2-blacklist",
    "class": "dnsxl",
    "resources": [ "ip4", "domain" ],
    "source": "rbl.dns-zone.com"
  },
  {
    "id": "root",
    "class": "selector",
    "resources": [ "ip4", "domain" ],
    "contains": [
      {
        "id": "ip-service",
        "class": "wbefore",
        "resources": [ "ip4" ],
        "contains": [
          {
            "id": "my-whitelist-ip4",
            "class": "file",
            "resources": [ "ip4" ],
            "source": "my-whitelist-ip4.xlist"
          },
          {
            "id": "blacklist-ip4",
            "class": "parallel",
            "resources": [ "ip4" ],
            "contains": [
              {
                "id": "geoip2-spanish",
                "class": "geoip2",
                "resources": [ "ip4" ],
                "source": "GeoLite2-Country.mmdb",
                "opts": {
                  "countries": [
                    "es"
                  ],
                  "reverse": true,
                }
              },
              {  "id": "remote1-blacklist"  },
              {  "id": "remote2-blacklist"  }
            ]
          }
        ]
      },
      {
        "id": "domain-service",
        "class": "wbefore",
        "resources": [ "domain" ],
        "contains": [
          {
            "id": "my-whitelist-domain",
            "class": "file",
            "resources": [ "domain" ],
            "source": "my-whitelist-domain.xlist"
          },
          {
            "id": "blacklist-domain",
            "class": "parallel",
            "resources": [ "domain" ],
            "contains": [
              {  "id": "remote1-blacklist"  },
              {  "id": "remote2-blacklist"  }
            ]
          }
        ]
      }
    ]
  }
]

En el Ejemplo de configuración puede verse cómo se implementaría la configuración vista en un fichero services.json. Como se observa, se trata de un fichero en formato JSON.

Los ficheros de definición deben contener un array JSON, esto es, empezar con [ y finalizar con ] y dentro de dicho array contener objetos JSON con las definiciones de los componentes que compondrán nuestro servicio.

Podemos dividir la configuración en diferentes ficheros si nos es más cómodo o más claro de interpretar. El resultado final será un array único formado por la concatenación de los arrays definidos en cada fichero. Lo único que hay que tener en cuenta es que será determinante el orden en el que se definan.

Cada definición deberá contener obligatoriamente un campo id que será único y un campo class que indicará cómo se construirá el componente. Luego, en función del campo class, los componentes podrán requerir de más información. Esto es, habrá componentes que requerirán de un valor en source, otros que requerirán que existan valores en contains, etc. Esto se verá en la documentación de cada uno de los componentes.

Obsérvese en Ejemplo de configuración cómo algunos componentes contienen a otros componentes (ejemplos: root, ip-service). En estos casos, se construirán de forma recursiva los componentes y no se pasará al siguiente componente del array principal hasta que se construya todo el árbol de componentes definidos.

Existen casos en los que es necesario "reutilizar" componentes en distintas ramas del árbol como puede verse en Ejemplo de composición con los componentes remote1-blacklist y remote2-blacklist. Aunque también es posible crear dos instancias con diferentes identificadores, no es recomendable hacerlo debido a que se duplican los recursos consumidos (esto es especialmente poco recomendable si reutilizamos componentes que carguen en memoria bases de datos). Por ello simplemente hay que declarar el componente una vez y en las posteriores apariciones, referenciarlo únicamente con el campo id.

Aunque la referencias mediante definiciones que contengan el campo id están pensadas para la reutilización de componentes, también pueden ser útiles para hacer un fichero de definición secuencial, evitando la creación de un enorme árbol de estructuras JSON más difícil de interpretar y de mantener.

Envoltorios

Hasta aquí hemos visto la definición de las listas mediante el uso de componentes, que siguen un arquetipo o clase para responder a la petición. Sin embargo puede ser requeridas funcionalidades adicionales como cachear las peticiones, registrar las peticiones que se realizan, estadísticas sobre tiempos de respuesta, alterar las respuestas, etc. Nótese que estas funcionalidades son genéricas y pueden aplicarse a todos los componentes, independientemente de la clase o arquetipo al que pertenezca. Para ello se han desarrollado los wrappers o en una traducción más o menos desafortunada, los envoltorios.

Supongamos que en el ejemplo visto, queremos lo siguiente:

  • Incorporar una caché global al servicio en el componente root.

  • Registrar las peticiones que devuelvan positivo.

  • Tener métricas sobre respuestas y tiempos de respuesta de: root, remote1-blacklist y remote2-blacklist.

  • Agregar el identificador de la lista como prefijo en las respuestas afirmativas que provengan de remote1-blacklist y remote2-blacklist.

xlistd
Figura 3. Ejemplo de envoltorios

En el Ejemplo de envoltorios podemos ver cómo se aplicarían los envoltorios (wrappers) sobre los componentes. Observe lo siguiente:

  1. Cada envoltorio (wrapper) que se agrega tiene un arquetipo o clase. En el ejemplo se muestran los arquetipos: cache, log, metrics y response.

  2. No tienen identificadores ni van asociados a ningún tipo de recurso determinado.

  3. Tienen un orden de aplicación, esto es, la petición seguirá el orden de definición de los envoltorios. En el ejemplo de los envoltorios sobre el componente identificado como root, las métricas medirán los efectos de la caché al encontrarse definida con anterioridad.

wrappers-sequence-example
Figura 4. Ejemplo de secuencia
Ejemplo de configuración de wrappers
[
  {
    "id": "remote1-blacklist",
     ....
    "wrappers": [
      { "class": "metrics" },
      { "class": "response", "opts": { "prefixid": true } }
    ]
  },
  {
    "id": "remote2-blacklist",
     ....
    "wrappers": [
      { "class": "metrics" },
      { "class": "response", "opts": { "prefixid": true } }
    ]
  },
  {
    "id": "root",
     ....
    "wrappers": [
      { "class": "cache", "opts": { "ttl": 30 } },
      { "class": "log"     },
      { "class": "metrics" }
    ]
  }
]

En el Ejemplo de configuración de wrappers podemos ver que la agregación de wrappers o envoltorios se realiza mediante el campo wrappers, que es un array de objetos de deben contener el atributo class que indica el tipo de envoltorio que representa. De manera adicional, algunos componentes pueden requerir de más campos que deberán definirse en un objeto referenciado por el campo opts. Estos campos dependerán del tipo de envoltorio. Finalmente, como se describió en Ejemplo de secuencia, es importante el orden en que se declaran los envoltorios, ya que esto afecta sobre el comportamiento.

Utilización de Componentes

A continuación se muestra una tabla con todos los componentes disponibles, si pincha en cada uno de los enlaces, podrá acceder a la documentación completa del componente.

Nombre Descripción Recursos Contenedor

mock

Se usa para realizar pruebas

Todos

No

mem

Chequea lista en memoria

Todos

No

file

Chequea lista en fichero

todos

No

dnsxl

Chequea en zonas dnsxl

ip4 ip6 domain

No

apicheck

Chequea en una lista grpc remota definida en ids.api

Todos

No

grpc

Chequea en una lista grpc remota

Todos

No

geoip2

Emplea geolocalización

ip4

No

sblookup

Chequea en una lista que utiliza la api de Google Safe Browsing

domain

No

sequence

Chequea en las listas de forma secuencial

Todos

parallel

Chequea en las listas en paralelo

Todos

wbefore

Chequea primero en una lista blanca

Todos

selector

Selecciona

Todos

Utilización de Envoltorios

A continuación se muestra una tabla con todos los envoltorios disponibles, si pincha en cada uno de los enlaces, podrá acceder a la documentación completa del envoltorio.

Nombre Descripción

cache

Crea una caché en memoria con las respuestas

logger

Registra las peticiones

metrics

Realiza métricas de las peticiones

score

Agrega puntuaciones a las respuestas

policy

Agrega política a las respuestas

response

Modifica las respuestas

timeout

Define un timeout para las consultas

A continuación: Gestión de fuentes