Documentation for a newer release is available. View Latest

Configuración de la aplicación

Se ha introducido una convención para la configuración de arranque de una aplicación IPF. El objetivo fue proporcionar un enfoque consistente y predecible para la configuración, pero lo suficientemente flexible para pruebas de integración que utilicen varias anotaciones de Spring para inyectar configuración dinámicamente.

Jerarquía de configuración

Al preparar la aplicación para su despliegue, se observará la jerarquía de configuración siempre que se sigan estas reglas:

  1. Solo se usa HOCON (archivos .conf) para la configuración de la aplicación

  2. Los desarrolladores deben poblar el archivo de configuración apropiado siguiendo esta jerarquía descendente de precedencia:

application-override.conf

¿Dónde? Configuración de despliegue (p. ej., configmap de k8s) o en el sistema de archivos montado en [app-name]/conf del contenedor.

¿Cuándo usarlo? Debe usarse en una emergencia que no pueda cumplirse con la jerarquía existente.

application.conf

¿Dónde? Configuración de despliegue (p. ej., configmap de k8s) o en el sistema de archivos montado en [app-name]/conf del contenedor.

¿Cuándo usarlo? Debe contener configuración específica del entorno como URLs o configuración de seguridad. Si tuviésemos que desplegar la misma aplicación en diferentes entornos, las diferencias en application.conf deberían consistir principalmente en configuración específica del entorno.

ipf-impl.conf

¿Dónde? src/main/resources de aplicaciones basadas en IPF.

¿Cuándo usarlo? Como defaults para levantar la aplicación para ejecución local. También puede proporcionar placeholders adicionales como overrides a ser establecidos por application.conf para configuración común entre módulos. Por ejemplo, ipf-impl.conf en sample-client proporciona overrides a través de un archivo include llamado endpoint-overrides.conf que sobreescribe la configuración de cifrado para cada endpoint conocido.

ipf.conf

¿Dónde? src/main/resources de módulos usados por aplicaciones basadas en IPF.

¿Cuándo usarlo? Definir defaults sensatos para aplicaciones que usarán este módulo. Los módulos no deben establecer la misma configuración ya que están al mismo nivel. El problema es que no será predecible qué config "ganará". La excepción es si se usa += en listas (p. ej., akka.cluster.roles). Se deben evitar los placeholders a menos que sean necesarios o recomendados por la librería subyacente. Por ejemplo, Alpakka Kafka recomienda usar herencia de configuración para asignar defaults para configuración de Kafka.

Esto puede verse como la versión IPF del reference.conf de Akka para los distintos módulos.

reference.conf

¿Dónde? src/main/resources de módulos usados por aplicaciones basadas en IPF

¿Cuándo usarlo? El propósito de los reference.conf es que librerías, como Akka, definan valores por defecto que se usan si una aplicación no define un valor más específico. También es un buen lugar para documentar la existencia y significado de las propiedades de configuración. Una librería no debe intentar sobreescribir propiedades en su propio reference.conf para propiedades originalmente definidas por el reference.conf de otra librería, porque el valor efectivo sería no determinista al cargar la configuración.

Sobrescritura adicional

Si la configuración vía la jerarquía de archivos no es suficiente, es posible sobrescribir con variables de entorno y propiedades de JVM.

Variables de entorno

Estableciendo la propiedad JVM -Dconfig.override_with_env_vars=true (vía IPF_JAVA_ARGS en contenedores IPF) es posible sobreescribir cualquier valor de configuración usando variables de entorno incluso si no se especifica una sustitución explícita.

El valor de la variable de entorno sobreescribirá cualquier valor preexistente y también cualquier valor proporcionado como propiedad Java.

Con esta opción activada, solo se consideran las variables de entorno que empiezan por CONFIG_FORCE_, y el nombre se transforma como sigue:

  • se elimina el prefijo CONFIG_FORCE_

  • un guion bajo simple (_) se convierte en un punto (.)

  • un doble guion bajo (__) se convierte en un guion (-)

  • un triple guion bajo (_) se convierte en un guion bajo (_)

ej.: La variable de entorno CONFIG_FORCE_a_bc_d establece la clave de configuración a.b-c_d

Esto solo se soporta desde com.typesafe:config:1.4.0 en adelante. Si esto no funciona, probablemente sea porque tu aplicación está trayendo una versión más antigua como dependencia transitiva.

Propiedades del sistema

Cualquier valor de configuración puede sobreescribirse como una propiedad Java -D (de nuevo vía IPF_JAVA_ARGS en contenedores IPF). Estas sobreescribirán propiedades de configuración establecidas en la jerarquía de archivos. Sin embargo, las variables de entorno establecidas con CONFIG_FORCE_ siguen teniendo precedencia.

Lightbend HOCON + Config

El proceso está alimentado por la combinación de HOCON y la librería Config de Lightbend. Aunque la librería Config también soporta YAML y .properties, adoptar solo HOCON aporta consistencia y flexibilidad ya que tiene capacidades adicionales sobre los otros dos formatos.

Destacan la herencia de bloques y las funcionalidades de sustitución, pero también que Akka es la base de IPF. Muchas opciones de configuración estándar serían difíciles o incómodas de hacer, como definir listas de seed nodes.

La legibilidad de HOCON la hace adecuada como documentación así como configuración por defecto. De esta manera se ahorra mucho esfuerzo al escribir documentación de soporte adicional cuando la configuración por defecto puede comentarse. Además, hay un impulso añadido para que los desarrolladores mantengan la configuración ordenada ya que puede usarse como material de cara al cliente.

Detalle de implementación

La jerarquía de config se usa como fallback a Spring PropertySource, cargada antes de que el application context se inicialice. Esto es deliberado para mantener el uso de varias anotaciones de Spring que pueden usarse para inyectar propiedades dinámicamente en pruebas de integración. Como evitamos usar mecanismos de configuración de Spring (p. ej., application.* o perfiles de Spring) HOCON y la jerarquía de Config deberían convertirse en la única fuente de configuración en despliegues reales.

Es posible usar tanto configuración de Spring, p. ej., application.properties, como la jerarquía de config de IPF, p. ej., application.conf, pero cuando sea posible, prefiere siempre la jerarquía de config.

El uso de Spring application.properties está fuertemente desaconsejado para mantener la consistencia al configurar nuestro creciente paisaje de componentes.

Enmascaramiento de datos sensibles en logs

El enmascaramiento se realiza con nuestra clase MaskPatternLayout, que extiende PatternLayout de logback. Básicamente, hemos reemplazado los layouts por defecto con el nuevo en DefaultLogbackConfigInitializer, que se dispara después de DefaultHoconConfigInitialiser. La configuración Hocon necesita inicializarse primero, ya que la configuración de enmascaramiento existe allí.

La configuración de enmascaramiento contiene dos partes:

  • masking.enabled - valor booleano que indica si la característica está habilitada o no

  • masking.mask-config-objects - representa un array de objetos. Cada objeto contiene información de expresión regular y estrategia para el valor que queremos enmascarar

Hemos introducido las siguientes estrategias de enmascaramiento:

  • MASK_ALL - enmascara todos los caracteres

  • MASK_FIRST_N_CHARS - enmascara los primeros n caracteres

  • MASK_LAST_N_CHARS - enmascara los últimos n caracteres

  • MASK_M_TO_N_CHARS - enmascara caracteres desde la posición m a la n

  • NO_MASK_FIRST_N_CHARS - no enmascarar los primeros n caracteres

  • NO_MASK_LAST_N_CHARS - no enmascarar los últimos n caracteres

  • NO_MASK_M_TO_N_CHARS - no enmascarar caracteres desde la posición m a la n

He aquí un ejemplo de cómo se ve la configuración de enmascaramiento:

masking {
  enabled = true
  mask-config-objects = [
    //objects matching json fields
    {
      pattern = "\"Nm\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"Id\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_FIRST_N_CHARS",
        args = {
            n = 3
        }
      }
    },
    {
      pattern = "\"Dept\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "NO_MASK_M_TO_N_CHARS",
        args = {
            m = 3,
            n = 5
        }
      }
    }
  ]
}

Como se mencionó arriba, hay una regex para cada valor que queremos enmascarar. Esto capturará la línea completa con la clave/etiqueta y el valor, pero solo el grupo que representa el valor se enmascara.

Aquí hay algunos ejemplos de enmascarar etiquetas xml, mostrando cómo debería funcionar cada estrategia:

MASK_ALL -> <Dept>****</Dept>,
MASK_FIRST_N_CHARS(3) -> <SubDept>***Dept</SubDept>,
MASK_LAST_N_CHARS(2) -> <StrtNm>Strt**</StrtNm>,
NO_MASK_FIRST_N_CHARS(4) -> <BldgNb>Bldg**</BldgNb>,
NO_MASK_LAST_N_CHARS(2) -> <BldgNm>****Nm</BldgNm>.
MASK_M_TO_N_CHARS(4,7) -> <TwnLctnNm>Twn****Nm</TwnLctnNm>,
NO_MASK_M_TO_N_CHARS(5,7) -> <CtrySubDvsn>****Sub****</CtrySubDvsn>

Objetos de enmascaramiento ya definidos

  mask-config-objects = [
    //objects matching json fields
    {
      pattern = "\"Nm\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"Id\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"Dept\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"SubDept\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"StrtNm\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"BldgNb\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"BldgNm\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"Flr\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"PstBx\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"Room\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"PstCd\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"TwnNm\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"TwnLctnNm\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"DstrctNm\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"CtrySubDvsn\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"Ctry\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"Adrline\"\\s*:\\s*\\[\\s*\"([^\"]+)\\s*\\]\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"BirthDt\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"PrvcOfBirth\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"CityOfBirth\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"CtryOfRes\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"CtryOfBirth\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"NmPrfx\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"PhneNb\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"MobNb\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"FaxNb\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"TaxId\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"RegnId\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"EmailAdr\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"EmailPurp\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"JobTitl\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"Rspnsblty\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "\"Titl\"\\s*:\\s*\"([^\"]+)\"",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },

    //objects matching xml tags
    {
      pattern = "<(?:\\w+:)?Nm>(.+)</(?:\\w+:)?Nm>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?Id>(.+)</(?:\\w+:)?Id>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?Dept>(.+)</(?:\\w+:)?Dept>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?SubDept>(.+)</(?:\\w+:)?SubDept>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?StrtNm>(.+)</(?:\\w+:)?StrtNm>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?BldgNb>(.+)</(?:\\w+:)?BldgNb>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?BldgNm>(.+)</(?:\\w+:)?BldgNm>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?Flr>(.+)</(?:\\w+:)?Flr>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?PstBx>(.+)</(?:\\w+:)?PstBx>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?Room>(.+)</(?:\\w+:)?Room>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?PstCd>(.+)</(?:\\w+:)?PstCd>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?TwnNm>(.+)</(?:\\w+:)?TwnNm>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?TwnLctnNm>(.+)</(?:\\w+:)?TwnLctnNm>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?DstrctNm>(.+)</(?:\\w+:)?DstrctNm>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?CtrySubDvsn>(.+)</(?:\\w+:)?CtrySubDvsn>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?Ctry>(.+)</(?:\\w+:)?Ctry>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?Adrline>(.+)</(?:\\w+:)?Adrline>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?BirthDt>(.+)</(?:\\w+:)?BirthDt>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?PrvcOfBirth>(.+)</(?:\\w+:)?PrvcOfBirth>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?CityOfBirth>(.+)</(?:\\w+:)?CityOfBirth>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?CtryOfRes>(.+)</(?:\\w+:)?CtryOfRes>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?CtryOfBirth>(.+)</(?:\\w+:)?CtryOfBirth>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?NmPrfx>(.+)</(?:\\w+:)?NmPrfx>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?PhneNb>(.+)</(?:\\w+:)?PhneNb>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?MobNb>(.+)</(?:\\w+:)?MobNb>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?FaxNb>(.+)</(?:\\w+:)?FaxNb>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?TaxId>(.+)</(?:\\w+:)?TaxId>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?RegnId>(.+)</(?:\\w+:)?RegnId>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?EmailAdr>(.+)</(?:\\w+:)?EmailAdr>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?EmailPurp>(.+)</(?:\\w+:)?EmailPurp>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?JobTitl>(.+)</(?:\\w+:)?JobTitl>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?Rspnsblty>(.+)</(?:\\w+:)?Rspnsblty>",
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    },
    {
      pattern = "<(?:\\w+:)?Titl>(.+)</(?:\\w+:)?Titl>"
      strategy {
        name = "MASK_ALL",
        args = {}
      }
    }
  ]
La implementación de esta funcionalidad depende de la librería logback; para que funcione, no puede usarse otra implementación de logger.