¿Debería protegerse contra valores inesperados de API externas?

51

Digamos que está codificando una función que toma datos de una API externa MyAPI.

Esa API externa MyAPItiene un contrato que establece que devolverá a stringo a number.

¿Se recomienda para protegerse contra cosas como null, undefined, boolean, etc, incluso aunque no es parte de la API de MyAPI? En particular, dado que no tiene control sobre esa API, no puede hacer la garantía mediante algo como el análisis de tipo estático, por lo que es mejor prevenir que curar.

Estoy pensando en relación con el principio de robustez .

Adam Thompson
fuente
16
¿Cuáles son los impactos de no manejar esos valores inesperados si se devuelven? ¿Puedes vivir con estos impactos? ¿Vale la pena manejar esos valores inesperados para evitar tener que lidiar con los impactos?
Vincent Savard
55
Si los espera, entonces, por definición, no son inesperados.
Mason Wheeler
28
Recuerde que la API no está obligada a devolverle solo JSON válido (supongo que esto es JSON). También puede obtener una respuesta como<!doctype html><html><head><title>504 Gateway Timeout</title></head><body>The server was unable to process your request. Make sure you have typed the address correctly. If the problem persists, please try again later.</body></html>
user253751
55
¿Qué significa "API externa"? ¿Sigue bajo tu control?
Deduplicador
11
"Un buen programador es alguien que mira a ambos lados antes de cruzar una calle de sentido único".
jeroen_de_schutter

Respuestas:

103

Nunca debe confiar en las entradas de su software, independientemente de la fuente. No solo es importante validar los tipos, sino también los rangos de entrada y la lógica empresarial. Según un comentario, esto está bien descrito por OWASP

Si no lo hace, en el mejor de los casos le dejará con datos basura que luego tendrá que limpiar, pero en el peor de los casos, dejará una oportunidad para explotaciones maliciosas si ese servicio ascendente se ve comprometido de alguna manera (por ejemplo, el pirateo de Target). El rango de problemas intermedios incluye poner su aplicación en un estado irrecuperable.


De los comentarios puedo ver que quizás mi respuesta podría necesitar un poco de expansión.

Por "nunca confíe en las entradas", me refiero simplemente a que no puede asumir que siempre recibirá información válida y confiable de los sistemas ascendentes o descendentes, y por lo tanto, siempre debe desinfectar esa entrada lo mejor que pueda o rechazarla eso.

Un argumento surgió en los comentarios que abordaré a modo de ejemplo. Si bien sí, tiene que confiar en su sistema operativo hasta cierto punto, no es irrazonable, por ejemplo, rechazar los resultados de un generador de números aleatorios si le pide un número entre 1 y 10 y responde con "bob".

Del mismo modo, en el caso del OP, definitivamente debe asegurarse de que su aplicación solo acepte entradas válidas del servicio ascendente. Lo que haga cuando no esté bien depende de usted y depende en gran medida de la función comercial real que esté tratando de lograr, pero mínimamente lo registraría para una depuración posterior y de lo contrario se aseguraría de que su aplicación no se ejecute en un estado irrecuperable o inseguro.

Si bien nunca puede saber cada entrada posible que alguien / algo pueda darle, ciertamente puede limitar lo que está permitido en función de los requisitos comerciales y hacer alguna forma de entrada en la lista blanca de entrada en función de eso.

Pablo
fuente
20
¿Qué quiere decir qv?
JonH
15
@JonH básicamente "ve también" ... el hack de Target es un ejemplo al que hace referencia en.oxforddictionaries.com/definition/qv .
andrewtweber
8
Esta respuesta es tal como está, simplemente no tiene sentido. Es inviable anticipar todas y cada una de las formas en que una biblioteca de terceros puede comportarse mal. Si la documentación de una función de biblioteca garantiza explícitamente que el resultado siempre tendrá algunas propiedades, entonces debería poder confiar en que los diseñadores se aseguraron de que esta propiedad realmente se mantenga. Es su responsabilidad tener un conjunto de pruebas que verifique este tipo de cosas y enviar una corrección de errores en caso de que se encuentre una situación donde no sea así. Si verifica estas propiedades en su propio código, está violando el principio DRY.
Leftaroundabout
23
@leftaroundabout no, pero debería poder predecir todas las cosas válidas que su aplicación puede aceptar y rechazar el resto.
Paul
10
@leftaroundabout No se trata de desconfiar de todo, se trata de desconfiar de fuentes externas no confiables. Esto se trata de modelar amenazas. Si no ha hecho que su software no sea seguro (¿cómo puede ser, si nunca pensó en qué tipo de actores y amenazas desea proteger su aplicación?). Para una ejecución del software empresarial de la fábrica, es un valor predeterminado razonable suponer que las personas que llaman pueden ser maliciosas, mientras que rara vez es sensato asumir que su sistema operativo es una amenaza.
Voo
33

Si por supuesto. Pero, ¿qué te hace pensar que la respuesta podría ser diferente?

Seguramente no querrás dejar que tu programa se comporte de manera impredecible en caso de que la API no devuelva lo que dice el contrato, ¿no? Entonces al menos tienes que lidiar con ese comportamiento de alguna manera . Una forma mínima de manejo de errores siempre vale el esfuerzo (¡muy mínimo!), Y no hay absolutamente ninguna excusa para no implementar algo como esto.

Sin embargo, la cantidad de esfuerzo que debe invertir para tratar un caso de este tipo depende en gran medida del caso y solo puede responderse en el contexto de su sistema. A menudo, una breve entrada de registro y dejar que la aplicación finalice correctamente puede ser suficiente. A veces, será mejor implementar un manejo detallado de excepciones, lidiar con diferentes formas de valores de retorno "incorrectos", y tal vez tenga que implementar alguna estrategia alternativa.

Pero hace una gran diferencia si está escribiendo solo una aplicación de formato de hoja de cálculo interna, para ser utilizada por menos de 10 personas y donde el impacto financiero de un bloqueo de la aplicación es bastante bajo, o si está creando una nueva conducción autónoma de automóviles sistema, donde un bloqueo de la aplicación puede costar vidas.

Por lo tanto, no hay atajos para reflexionar sobre lo que está haciendo , usar su sentido común siempre es obligatorio.

Doc Brown
fuente
Qué hacer es otra decisión. Es posible que tenga una solución de falla. Cualquier cosa asincrónica se puede volver a intentar antes de crear un registro de excepciones (o letra muerta). Una alerta activa al proveedor o proveedor puede ser una opción si el problema persiste.
mckenzm
@mckenzm: el hecho de que el OP haga una pregunta donde la respuesta literal obviamente puede ser solo "sí" es, en mi humilde opinión, una señal de que no solo están interesados ​​en una respuesta literal. Parece que están preguntando "¿es necesario protegerse contra diferentes formas de valores inesperados de una API y tratarlos de manera diferente" ?
Doc Brown
1
hmm, el enfoque de basura / carpa / dado. ¿Es nuestra culpa por pasar solicitudes malas (pero legales)? ¿Es posible la respuesta, pero no es utilizable para nosotros en particular? o es la respuesta corrupta? Diferentes escenarios, ahora suena como tarea.
mckenzm
21

El Principio de Robustez - específicamente, la mitad de "sé liberal en lo que aceptas" - es una muy mala idea en el software. Originalmente se desarrolló en el contexto del hardware, donde las restricciones físicas hacen que las tolerancias de ingeniería sean muy importantes, pero en el software, cuando alguien le envía una entrada con formato incorrecto o incorrecto, tiene dos opciones. Puede rechazarlo (preferiblemente con una explicación de lo que salió mal) o puede intentar averiguar qué se supone que significa.

EDITAR: Resulta que me equivoqué en la declaración anterior. El Principio de Robustez no proviene del mundo del hardware, sino de la arquitectura de Internet, específicamente RFC 1958 . Afirma:

3.9 Sea estricto al enviar y tolerante al recibir. Las implementaciones deben seguir las especificaciones con precisión cuando se envían a la red y tolerar la entrada defectuosa de la red. En caso de duda, descarte la entrada defectuosa en silencio, sin devolver un mensaje de error, a menos que así lo exija la especificación.

Esto es, simplemente hablando, simplemente incorrecto de principio a fin. Es difícil concebir una noción más equivocada de manejo de errores que "descartar silenciosamente la entrada defectuosa sin devolver un mensaje de error", por las razones expuestas en esta publicación.

Consulte también el documento de IETF Las consecuencias nocivas del principio de robustez para obtener más detalles sobre este punto.

Nunca, nunca, nunca elija esa segunda opción a menos que tenga recursos equivalentes al equipo de búsqueda de Google para lanzar su proyecto, porque eso es lo que se necesita para crear un programa informático que haga algo cercano a un trabajo decente en ese dominio problemático en particular. (E incluso entonces, las sugerencias de Google se sienten como si salieran directamente del campo izquierdo aproximadamente la mitad del tiempo). Si intentas hacerlo, terminarás con un gran dolor de cabeza donde tu programa tratará de interpretarlo con frecuencia. mala entrada como X, cuando el remitente realmente quiso decir Y.

Esto es malo por dos razones. La obvia es porque entonces tienes datos incorrectos en tu sistema. La menos obvia es que, en muchos casos, ni usted ni el remitente se darán cuenta de que algo salió mal hasta mucho más tarde en el camino cuando algo explota en su cara, y de repente tiene un desastre grande y costoso que arreglar y no tiene idea lo que salió mal porque el efecto notable está muy lejos de la causa raíz.

Es por eso que existe el principio Fail Fast; ahorre a todos los involucrados el dolor de cabeza al aplicarlo a sus API.

Mason Wheeler
fuente
77
Si bien estoy de acuerdo con el principio de lo que estás diciendo, creo que estás equivocado con la intención del Principio de Robustez. Nunca he visto que tenga la intención de significar, "aceptar datos incorrectos", solo, "no seas excesivamente complicado con los datos buenos". Por ejemplo, si la entrada es un archivo CSV, el Principio de robustez no sería un argumento válido para tratar de analizar las fechas en un formato inesperado, pero respaldaría un argumento de que inferir el orden de las columnas a partir de una fila de encabezado sería una buena idea .
Morgen
99
@Morgen: El principio de robustez se usó para sugerir que los navegadores deberían aceptar HTML bastante descuidado, y condujo a que los sitios web implementados fueran mucho más descuidados de lo que hubieran sido si los navegadores hubieran exigido HTML adecuado. Sin embargo, una gran parte del problema allí fue el uso de un formato común para contenido generado por humanos y por máquina, en oposición al uso de formatos separados editables por humanos y analizables por máquina junto con utilidades para convertir entre ellos.
supercat
99
@supercat: sin embargo, o simplemente por lo tanto, HTML y WWW fueron extremadamente exitosos ;-)
Doc Brown
11
@DocBrown: Muchas cosas realmente horribles se han convertido en estándares simplemente porque fueron el primer enfoque que estuvo disponible cuando alguien con mucha influencia necesitaba adoptar algo que cumplía ciertos criterios mínimos, y para cuando ganaron tracción era demasiado tarde para seleccionar algo mejor.
supercat
55
@ Supercat Exactamente. JavaScript me viene a la mente de inmediato, por ejemplo ...
Mason Wheeler
13

En general, el código debe construirse para mantener al menos las siguientes restricciones siempre que sea práctico:

  1. Cuando se le da la entrada correcta, produzca la salida correcta.

  2. Cuando se le da una entrada válida (que puede o no ser correcta), produce una salida válida (del mismo modo).

  3. Cuando reciba una entrada no válida, procese sin efectos secundarios más allá de los causados ​​por la entrada normal o los que se definen como señalización de un error.

En muchas situaciones, los programas esencialmente pasarán por varios fragmentos de datos sin preocuparse especialmente de si son válidos. Si tales fragmentos contienen datos no válidos, la salida del programa probablemente contendrá datos no válidos como consecuencia. A menos que un programa esté específicamente diseñado para validar todos los datos y garantizar que no producirá resultados no válidos, incluso cuando se les da una entrada no válida , los programas que procesan su salida deben permitir la posibilidad de datos no válidos dentro de él.

Si bien a menudo es deseable validar los datos desde el principio, no siempre es particularmente práctico. Entre otras cosas, si la validez de un fragmento de datos depende del contenido de otros fragmentos, y si la mayoría de los datos introducidos en alguna secuencia de pasos se filtrará en el camino, lo que limitará la validación a los datos que lo atraviesan Todas las etapas pueden producir un rendimiento mucho mejor que tratar de validar todo.

Además, incluso si solo se espera que un programa reciba datos prevalidados, a menudo es bueno que cumpla con las restricciones anteriores de todos modos siempre que sea práctico. Repetir la validación completa en cada paso del procesamiento a menudo sería una pérdida de rendimiento importante, pero la cantidad limitada de validación necesaria para mantener las restricciones anteriores puede ser mucho más barata.

Super gato
fuente
Entonces todo se reduce a decidir si el resultado de una llamada API es una "entrada".
mastov
@mastov: Las respuestas a muchas preguntas dependerán de cómo se definan las "entradas" y los "comportamientos observables" / "salidas". Si el propósito de un programa es procesar números almacenados en un archivo, su entrada podría definirse como la secuencia de números (en cuyo caso las cosas que no son números no son posibles entradas), o como un archivo (en cuyo caso cualquier cosa que podría aparecer en un archivo sería una posible entrada).
supercat
3

Comparemos los dos escenarios e intentemos llegar a una conclusión.

Escenario 1 Nuestra aplicación supone que la API externa se comportará según el acuerdo.

Escenario 2 Nuestra aplicación supone que la API externa puede comportarse mal, por lo tanto, agregue precauciones.

En general, existe la posibilidad de que cualquier API o software viole los acuerdos; puede deberse a un error o condiciones inesperadas. Incluso una API puede tener problemas en los sistemas internos que resultan en resultados inesperados.

Si nuestro programa está escrito asumiendo que la API externa se adherirá a los acuerdos y evitará agregar precauciones; ¿Quién será la parte que enfrenta los problemas? Seremos nosotros, los que tengamos el código de integración escrito.

Por ejemplo, los valores nulos que ha elegido. Digamos, según el acuerdo de API, la respuesta debe tener valores no nulos; pero si se viola repentinamente, nuestro programa generará NPE.

Por lo tanto, creo que será mejor asegurarse de que su aplicación tenga algún código adicional para abordar escenarios inesperados.

lkamal
fuente
1

Siempre debe validar los datos entrantes, ingresados ​​por el usuario o de otra manera, por lo que debe tener un proceso para manejar cuando los datos recuperados de esta API externa no sean válidos.

En términos generales, cualquier costura donde se unen sistemas extraorganizacionales debe requerir autenticación, autorización (si no se define simplemente por autenticación) y validación.

StarTrekRedneck
fuente
1

En general, sí, siempre debe protegerse contra entradas defectuosas, pero dependiendo del tipo de API, "proteger" significa cosas diferentes.

Para una API externa a un servidor, no desea crear accidentalmente un comando que bloquea o compromete el estado del servidor, por lo que debe protegerse contra eso.

Para una API como, por ejemplo, una clase de contenedor (lista, vector, etc.), arrojar excepciones es un resultado perfectamente bueno, comprometer el estado de la instancia de clase puede ser aceptable en cierta medida (por ejemplo, un contenedor ordenado provisto de un operador de comparación defectuoso no lo será). ser ordenado), incluso el bloqueo de la aplicación puede ser aceptable, pero comprometer el estado de la aplicación, por ejemplo, escribir en ubicaciones de memoria aleatorias no relacionadas con la instancia de clase, probablemente no lo sea.

Peter
fuente
0

Para dar una opinión ligeramente diferente: creo que puede ser aceptable simplemente trabajar con los datos que se le proporcionan, incluso si viola su contrato. Esto depende del uso: es algo que DEBE ser una cadena para usted, o es algo que simplemente está mostrando / no usa, etc. En este último caso, simplemente acéptelo. Tengo una API que solo necesita el 1% de los datos entregados por otra API. No podría importarme menos qué tipo de datos hay en el 99%, por lo que nunca lo comprobaré.

Tiene que haber un equilibrio entre "tener errores porque no verifico mis entradas lo suficiente" y "Rechazo datos válidos porque soy demasiado estricto".

Christian Sauer
fuente
2
"Tengo una API que solo necesita el 1% de los datos entregados por otra API". Esto abre la pregunta de por qué su API espera 100 veces más datos de los que realmente necesita. Si necesita almacenar datos opacos para transmitir, realmente no tiene que ser específico en cuanto a qué es y no tiene que declararlo en ningún formato específico, en cuyo caso la persona que llama no violaría su contrato .
Voo
1
@Voo: sospecho que están llamando a alguna API externa (como "obtener detalles del clima para la ciudad X") y luego seleccionan los datos que necesitan ("temperatura actual") e ignoran el resto de los datos devueltos ("lluvia "," viento "," temperatura pronosticada "," sensación térmica ", etc ...)
Stobor
@ChristianSauer: creo que no está tan lejos de lo que es el consenso más amplio: el 1% de los datos que usa tiene sentido para verificar, pero el 99% que no necesita no necesariamente debe verificarse. Solo necesita verificar las cosas que podrían hacer tropezar su código.
Stobor
0

Mi opinión sobre esto es siempre, siempre verificar cada entrada a mi sistema. Eso significa que todos los parámetros devueltos por una API deben verificarse, incluso si mi programa no lo usa. También tiendo a verificar la corrección de cada parámetro que envío a una API. Solo hay dos excepciones a esta regla, ver más abajo.

La razón de la prueba es que si por alguna razón la API / entrada es incorrecta, mi programa no puede confiar en nada. ¿Quizás mi programa estaba vinculado a una versión anterior de la API que hace algo diferente de lo que creo? Tal vez mi programa tropezó con un error en el programa externo que nunca antes había sucedido. O peor aún, sucede todo el tiempo, ¡pero a nadie le importa! ¿Quizás el programa externo está siendo engañado por un hacker para devolver cosas que pueden dañar mi programa o el sistema?

Las dos excepciones para probar todo en mi mundo son:

  1. Rendimiento después de una cuidadosa medición del rendimiento:

    • nunca optimice antes de haber medido. La prueba de todos los datos de entrada / devueltos suele llevar un tiempo muy pequeño en comparación con la llamada real, por lo que eliminarlos a menudo ahorra poco o nada. Todavía mantendría el código de detección de errores, pero lo comentaría, tal vez por una macro o simplemente lo comentaría.
  2. Cuando no tienes idea de qué hacer con un error

    • Hay momentos, no muy a menudo, en que su diseño simplemente no permite manejar el tipo de error que encontraría. Quizás lo que debería hacer es registrar un error, pero no hay registro de errores en el sistema. Casi siempre es posible encontrar alguna forma de "recordar" el error que le permita al menos a usted como desarrollador verificarlo más tarde. Los contadores de errores son una buena cosa para tener en un sistema, incluso si elige no tener un registro.

Exactamente qué tan cuidadosamente verificar las entradas / valores de retorno es una pregunta importante. Como ejemplo, si se dice que la API devuelve una cadena, comprobaría que:

  • el tipo de datos actully es una cadena

  • y esa longitud está entre los valores mínimo y máximo. Siempre verifique las cadenas para el tamaño máximo que mi programa puede esperar manejar (devolver cadenas demasiado grandes es un problema de seguridad clásico en los sistemas en red).

  • Algunas cadenas deben ser verificadas por caracteres o contenido "ilegales" cuando sea relevante. Si su programa puede enviar la cadena para decir una base de datos más tarde, es una buena idea verificar los ataques a la base de datos (buscar inyección SQL). Estas pruebas se realizan mejor en los límites de mi sistema, donde puedo determinar de dónde vino el ataque y puedo fallar temprano. Hacer una prueba de inyección SQL completa puede ser difícil cuando las cadenas se combinan más tarde, por lo que esa prueba debe hacerse antes de llamar a la base de datos, pero si puede encontrar algunos problemas temprano, puede ser útil.

La razón para probar los parámetros que envío a la API es para asegurarme de que obtengo un resultado correcto. Nuevamente, hacer estas pruebas antes de llamar a una API puede parecer innecesario, pero requiere muy poco rendimiento y puede detectar errores en mi programa. Por lo tanto, las pruebas son más valiosas cuando se desarrolla un sistema (pero hoy en día todos los sistemas parecen estar en continuo desarrollo). Dependiendo de los parámetros, las pruebas pueden ser más o menos exhaustivas, pero tiendo a encontrar que a menudo puede establecer valores mínimos y máximos permitidos en la mayoría de los parámetros que mi programa podría crear. ¿Quizás una cadena siempre debe tener al menos 2 caracteres y tener un máximo de 2000 caracteres? El mínimo y el máximo deben estar dentro de lo que permite la API, ya que sé que mi programa nunca usará el rango completo de algunos parámetros.

ghellquist
fuente