Por aquí ya hemos hablado una vez y otra sobre como gestionar los datos sensibles (como las cadenas de conexión) de nuestras aplicaciones, porque, claro, nadie debería de poner los datos de producción en el código fuente que grabamos en nuestro repo, ¿verdad?
Anteriormente las opciones pasaban por efectuar mezclas/transformaciones de tal manera que en tiempo de compilación se generara un archivo final con los datos sensibles correspondientes a cada entorno, lo cual si bien nos daba simplificación para colocar la clave correcta en el paquete y entorno correcto, nos obligaba a seguir versionando las cadenas de conexión en el código fuente (por no mencionar que había que compilar mas de una vez); la otra opción pasaba por gestionar dentro del entorno de destino (en nuestros ejemplos: WebApps) sobrescribiendo de esta manera los valores que vinieran desde el código fuente.
Dado que ni nuestro repositorio ni nuestra herramienta de integración/despliegue deberían contener datos sensibles, usaremos un servicio de nube que nos da la seguridad necesaria para gestionar nuestras credenciales, el Azure KeyVault, el cual nos permite almacenar de manera segura y granular ya sea certificados digitales como data sensible, pudiendo restringir quienes pueden acceder a que datos y a que no (aplicando Control de acceso basado en roles: RBAC). Un mecanismo usual de accesos a estos recursos sensibles es programar dentro de nuestra aplicación un código que lea los valores en tiempo de ejecución (procurando no releer el valor a cada uso, sino hacerlo una única vez), en este caso usaremos un enfoque distinto que consistirá en leer los valores almacenados en KeyVault durante nuestro pipeline de despliegue, alterando secciones del archivo appsettings.json (porque nuestro ejemplo se basara en .Net Core) que se alojara en nuestra Web App destino (aunque también funciona perfectamente en un despliegue sobre IIS).
Así que estos serán los pasos que seguiremos:
- Crear un KeyVault
- Crear un «Secret» dentro de nuestro KeyVault, ahí sera donde almacenaremos una cadena de conexión a la base de datos
- Identificar el «Principal» mediante el cual nuestro Team Project se conecta a Azure
- Dar los permisos sobre nuestro KeyVault a dicho principal
- Identificar en nuestra aplicación las secciones a modificar en tiempo de ejecución
- Enlazar nuestro TeamProject contra el KeyVault, fijando el scope respectivo
- Configurar el reemplazo de valores en el appsettings.json
- Validar que nuestros cambios han sido desplegados en el entorno destino
Para esta demo asumiremos que tendremos tanto un pipeline de Build como de Release, de una aplicación en ASP.Net Core, en nuestro ejemplo usare la aplicación de ejemplo del libro de EF Core in Action (excelente libro que tuve el honor de revisar) cuyo código fuente se puede descargar aquí, y claro, como es usual el despliegue lo haremos contra una Azure Web App.
Para el primer paso seguiremos las instrucciones dadas en la primera parte de este articulo, en mi caso he usado estos nombres: RG_DevmoVault01 para el Resource Group y ErnestoDemoKeyVault para el KeyVault, como en este caso la creación la hemos hecho vía linea de comandos verificaremos que podemos revisar este recurso en el Portal de Azure, así:
En este caso ya hemos definido un espacio (el Key Vault) para guardar nuestros secretos, en este caso lo que grabaremos sera una cadena de conexión a una BD SQL Database, por lo que siguiente el estilo del articulo mencionado seguiremos usando el CLI de Azure:
az keyvault secret set --vault-name "ErnestoDemoKeyVault" --name "SQLPassword" --value "<micadenadeconexión>"
Por supuesto que estamos suponiendo que ya tenemos una bd totalmente configurada y operativa, de la cual hemos definido una cadena de conexión que queremos manejar de manera segura.
Solo por precaución verificamos en el Portal que ya esta creado el secreto…
Ya que estamos aquí, revisaremos un detalle del Secreto, para lo cual lo seleccionaremos y veremos una pantalla como esta:
En esta pantalla podemos ver algunas cosas muy importantes, la primera, KeyVault maneja versiones y fechas de expiración, lo cual puede ser muy conveniente para ir cambiando el contenido del secreto con impacto mínimo sobre los «clientes» que estén usando el secreto, pero también que es posible ver el valor almacenado, en ese sentido es muy pero muy recomendable que esta gestión de los secretos este reservada solo a los usuarios de Azure con el perfil adecuado, posteriormente veremos como con este flujo lograremos trabajar con los secretos keyvault sin necesidad de leer el contenido en si.
Ya tenemos creado el Secreto, ahora debemos lograr que nuestro Team Project y por consiguiente nuestros pipelines puedan recuperar dicho valor, para esto debemos recordar algo sobre las relaciones entre los Team Project y las suscripciones de Azure, y es el hecho de que por cada TP es necesario establecer explícitamente un vinculo llamado Service Connection , como se indica aquí, nótese que al crear un Service Connection se puede establecer un «mapeo» directo entre el TP y la Subscripcion, lo cual permite que desde el TP se pueda usar o impersonar los privilegios del usuario que creo la Service Connection (el usuario logueado en Azure DevOps), o usar un «Principal» explícitamente creados desde Azure, dicho Principal tendrá privilegios acotados (lo cual es una buena practica de seguridad), por lo que sera necesario agregar explícitamente el permiso de acceso a nuestro secreto.
Como mencionamos, ya tenemos un pipeline de despliegue el cual usa la «tarea» Azure App Service Deploy, en este caso ya teníamos creado un Service Connection, como se ve aquí:
Ya que lo que queremos es habilitar a nuestro SC para acceder al KeyVault, haremos clic en «Manage» lo cual nos abrira una pestaña adicional donde veremos los SC registrados para nuestro Team Project:
En este caso nos corresponderá hacer clic en el enlace «Manage Service Principal», vale la pena hacer notar que ya sea que creamos nuestro SC de manera «directa» con nuestra suscripción o usamos un Principal ya acotado, siempre habra un Principal/Application en nuestro SC, que es el que vemos aquí:
Se puede notar que el nombre sigue la convención: MiOrganizaciónAzureDevOps-MiTeamProject-SubscriptionID, al parecer es posible (desde esta pantalla) cambiar el nombre, pero de momento no lo haremos, lo que si haremos sera acordarnos de dicho nombre, pues nos sera de utilidad para el siguiente paso, para lo cual volveremos al KeyVault, y revisamos los Access policies, así:
Y elegimos Add New, seleccionando los permisos Get y List relativos a los Secretos:
Para luego proceder a la selección del Principal. podemos ver que se nos listan muchas opciones, upss:
Entonces toca buscar, como sabemos la convención le pondremos las primeras letras de nuestra Organización Azure DevOps, luego de lo cual es fácil ubicar y seleccionar el que corresponde a nuestro Team Project:
Revisamos y aceptamos:
Vemos que se ha agregado un item mas a la lista de Access Policies, llegados a este punto debemos acordarnos de grabar, pues de lo contrario lo que hemos seleccionado no tendrá efecto:
Bueno, ya establecimos el enlace, ahora toca valernos de esa conexión en nuestro Team Project, para esto primero revisaremos el appsettings.json de nuestro proyecto, para identificar donde utilizar el secreto:
La documentación inline de la tarea Azure App Service Deploy nos indica como se efectuaría una transformación de valores durante el despliegue:
Esto nos viene a decir que si queremos reemplazar el valor que figuraba en el archivo appsettings.json nuestro pipeline deberá tener una variable llamada ConnnectionStrings.DefaultConnection, dicha variable almacenara el valor deseado (que hemos guardado en el Secreto), y ya que estamos en eso aprovechamos para definir en que archivos se efectuara el reemplazo, de ahí que colocaremos **/appsettings.json en el campo JSON variable substitution, como pudimos ver en el gráfico anterior.
***Cabe mencionar que las opciones de File Transforms & Variable Substitution también están disponibles para los tareas de despliegue a IIS, como en el caso de Deployment Groups, por lo que estos pasos tranquilamente aplican a un escenario IaaS y no solo PaaS como el de este ejemplo***
Entonces como primer paso crearemos una variable para nuestro pipeline, en este caso definiremos como Scope a «Release», de esta manera aseguraremos que la variable este disponible en todos los stages de nuestro pipeline, nótese el curioso valor que estamos usando, esto nos permitirá hacer un seguimiento del resultado de nuestros despliegues:
Ahora encolaremos un Build y Release para ver nuestros cambios:
Cuando el Release ha concluido, iremos al Portal de Azure, visitaremos nuestra Web App de destino, visitaremos la consola a fin de analizar el contenido del appsettings.json, mediante el comando type:
Ok, hemos reemplazado el valor original con el que definimos en la mencionada variable, lo cual no esta mal, pero lo que queremos es usar el valor que estaba dentro del Secreto, para lo cual regresaremos a la sección Variables de nuestro pipeline, solo que en este caso elegiremos la subsección Variable groups:
Elegimos Manage variable groups:
Agregamos un nuevo Variable Group, pero nótese que estamos activando la opción «Link secrets from an Azure key vault variables» :
Lo cual como es lógico nos permite acceder a nuestro KeyVault:
Luego de lo cual, seleccionaremos «+ Add» accederemos al Secreto esperado, en este caso SQLPassword, que ahora estara disponible como variable en nuestro pipeline:
Ahora toca definir el scope de este grupo de variables, nótese que este Variable Group pertenece al Team Project, no al pipeline, por lo que toca configurar su uso, para lo cual regresamos al pipeline->Variable Groups, y esta vez elegimos Link variable Group configurando de esta manera (luego dando clic a Link):
En esta ocasión hemos definido como scope al stage Dev, esto permitira (de ser necesario) establecer juegos de secretos diferentes por cada Stage de manera sencilla, luego de lo cual queda asi nuestra sección de Variables groups, y claro grabamos para actualizar la configuración de nuestro pipeline:
Ok, llegados a este punto tenemos una variable llamada ConnnectionStrings.DefaultConnection que es la que se usara para hacer el reemplazo en el .json, y por otro lado una variable SQLPassword que almacena la cadena de conexión como secreto, por lo que corresponderá copiar de un lado a otro los valores, a estas alturas el lector atento se preguntará que porque no hemos denominado a nuestro secreto directamente con el nombre de la variable de reemplazo, y la respuesta es simple, el nombre del Secreto solo admite valores alfanuméricos.
Entonces para hacer el copiado, volvemos a las «Pipeline variables» y editamos el valor de ConnnectionStrings.DefaultConnection colocando $(SQLPassword), asi:
Grabamos, y encolamos un nuevo Release:
Se completa el despliegue, asi que regresemos al Portal de Azure para ver el valor de nuestra variable….
Y ya esta, el valor de la cadena de conexión ha sido «inyectado» dentro de la aplicación desplegada en nuestra Web App, pero ojo, esto de revisar «en producción» en contenido del archivo de configuración lo hemos hecho para validar nuestro procedimiento, pero por lo mismo debe de quedar claro que entornos reales deben estar adecuadamente protegidos en su acceso desde el Portal, regulando el nivel de acceso que los diversos usuarios tienen sobre los recursos de la Suscripción, lo cual como hemos visto no es impedimento para configurar nuestros pipelines.
Espero que les sea de utilidad, espero sus comentarios.
One thought on “Viendo la seguridad de la configuración con Azure KeyVault, .Net Core y Azure DevOps”