Paso a paso: Cómo mejorar la seguridad de tu aplicación frontend usando AWS Secret Manager, ejemplo con VueJs
Tags: webdev, security, aws, javascript
Published: 2023-04-03 12:30:00


Hoy vamos a tratar un tema que muchas veces pasamos por alto, la seguridad en aplicaciones frontend (aplicable también para backend), creemos porque son API KEY podemos simplemente dejarla de forma estática en el código, pero cuando se ven comprometidas no sabemos cómo remediar esta situación, hoy te traigo una idea de como lidiar con este problema. 

---

## Agenda del post:
* [La responsabilidad en términos de seguridad para aplicaciones Frontend](#chapter-1)
* [Localstorage, SessionStorage, inputs hiddens](#chapter-3)
* [Archivos .ENV ¿Que debemos guardar?](#chapter-4)
* [Por qué no debemos tener API KEYS de terceros en nuestro código](#chapter-5)
* [AWS Secrets manager como vault de datos](#chapter-6)
* [AWS Cognito para acceso temporal a nuestros recursos de AWS](#chapter-7)
* [Código final usando AWS SDK v3 en VueJs](#chapter-8)


---

## La responsabilidad en términos de seguridad para aplicaciones Frontend: <a name="chapter-1"></a>

**Contexto:**
El desarrollo de aplicaciones frontend requiere de un certificado de seguridad SSL para evitar problemas con los navegadores web y mejorar el posicionamiento en los motores de búsqueda. Además, es necesario asegurar la información sensible como las credenciales de servicios de terceros (API KEY nominadas) no queden insertadas en el código.  Por último y no menos importante, este certificado (SSL) nos va permitir encriptar las conexiones que hagamos con nuestra aplicación backend (propia o de terceros). 

Durante el proceso de desarrollo de aplicaciones, de seguro has tenido acceso a credenciales de servicios de terceros (API KEY) que solamente deben ser utilizadas para ambientes de pruebas (localhost) 

> ¿Como realmente aseguramos eso? 
Hoy en este artículo vamos a estar hablando de un proceso de gestión seguro y actualizado para las variables de entorno en el equipo de desarrollo utilizando AWS Secret Manager. 


Ya sea que recibas a un nuevo miembro al equipo o alguien salga del mismo, mantener esas credenciales de forma segura y actualizadas siempre fue un dolor de cabeza para cada encargado de seguridad en los equipos de desarrollo. 

Cómo líder por muchos años de equipos he probado muchas herramientas de gestión y casi todas recaen en la "buena fé" del desarrollador/a, por eso hoy te mostraré un proceso que he venido haciendo estos últimos tiempos. 


Comencemos hablando de situaciones que de seguro se te hacen familiares. 



## Guardar información sensible en LocalStorage <a name="chapter-3"></a>

![(API KEY) que solamente deben ser utilizadas para ambientes de pruebas (localhost) ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e12tf2h14ek6t8mgssij.png)


_**Te hago un spoiler:**_ lo primero que verá cualquier analista de seguridad a tu aplicación frontend es buscar datos sensibles que el usuario final pueda tener acceso, con solo hacer F12 (consola de desarrollador) puedes entrar y ver el LocalStorage.
![Spoiler seguridad](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4d6gilf6xb6dffqyrcn7.jpg)

Así que la próxima vez que quieras guardar un número de identificación legal (C.I. / D.N.I / S.S.N), una contraseña o algo similar piensalo dos veces, hay cosas debajo del sofá que son más difíciles de encontrar que acá. 

## Archivos .ENV con información sensible <a name="chapter-4"></a>

![Archivos .ENV con información sensible](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u1naejzb3ozpjuea6z4r.png)

**Partimos de un punto:** Dentro de tu repositorio de GIT no estás haciendo "commit" de datos sensibles en los archivos .ENV 

> ¿Porque así lo estás haciendo no? 

Recuerda que las aplicaciones fronted se empaquetan y se entregan al navegador de forma completa (no entraremos acá en detalle del "_lazy load_" o patrones similares), por lo tanto con simplemente examinar el código tendremos a todo lo que contiene la aplicación, este punto es clave para cuando quieras hacer algo cómo en tu .ENV:
GOOLE_API_KEY=J64j564Mknss


## Por qué no debemos tener API KEYS de terceros en nuestro código 
![stop](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ernvntnljmts10z3o71o.png)
<a name="chapter-5"></a>
> ¡No lo hagas!, ya que al hacerlo estarás exponiendo esas credenciales a cualquier usuario de tu web. Sí, sí ya me dirás “Pero yo entro en la app de Google y le indico el dominio que quiero como whitelist”, y te diré es correcto **PERO NO SUFICIENTE**, en términos de seguridad cada puerta que dejemos abierta es una oportunidad para que nuestros atacantes (intencionados o no) utilicen esa información para otros fines diferentes a los originales.

De seguro ya te estarás preguntando: _¿Si no tengo esa API KEY como hago para que mi aplicación funcione correctamente?_, la respuesta corta es: necesitas **llamar a un tercero para solicitar** esa información y recién ahí poder hacer el “request” que necesites. 

Antes de que pienses en la ineficiencia de este patrón de seguridad, déjame que te explique las ventajas antes.


---


Imaginemos que por algún motivo esa API KEY se encuentra comprometida y quieres sustituirla por una nueva para remediar la situación, deberías hacer algo parecido a estos pasos:

1. Si es una APP "Mobile", tendrás que crear una nueva versión, subirla al store correspondiente, luego liberar esa nueva versión y esperar a que todos tus usuarios se descarguen esta versión. Es un proceso lento y no escalable. 

2. Por otro lado si es "Web", te tocará hacer algo similar; liberar una nueva versión, invalidar el caché (cosa que muchos no hacen en sus procesos de CI/CD) de aplicaciones web y esperar a que tus usuarios te visiten nuevamente y el navegador les entregue la última versión desplegada. 

Ambos escenarios como puedes observar podemos estar hablando de horas o días (en el caso de “APP Mobile”) que el **atacante** podrá seguir utilizando nuestras credenciales para otros usos. Durante ese tiempo estaremos **comprometiendo** a nuestros usuarios también. 


Acá es donde entra en juego un servicio de tercero para almacenar esta información , sin importar si tu aplicación en frontend o backend el funcionamiento es el mismo (una AWS Lambda por ejemplo).

Un tercero almacena los datos sensibles y cada vez que los necesitemos los consultamos y los usamos. Así cuando nuestros datos lleguen a estar comprometidos o como medida de nuestras políticas de seguridad continua cambiaremos en un solo lugar y en cuestión de segundos ya nuestros usuarios estarán remediados. 



---
<a name="chapter-6"></a>
![AWS Secrets Manager como vault de datos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lps5275f0r2eo44dlce7.png)
## AWS Secrets manager como vault de datos

Concepto copiado textualmente de [AWS](https://aws.amazon.com/es/secrets-manager/faqs/): 

> AWS Secrets Manager es un servicio de administración de datos confidenciales que ayuda a proteger el acceso a sus aplicaciones, servicios y recursos de TI. El servicio le permite alternar, administrar y recuperar fácilmente credenciales de bases de datos, claves de API y otros datos confidenciales durante todo su ciclo de vida. Con Secrets Manager, puede proteger y administrar los datos confidenciales utilizados para acceder a los recursos en la nube de AWS, en servicios de terceros y localmente.

Acá podemos ver que no tenemos la necesidad de crear una solución propia para paliar con este problema de seguridad, utilizando un servicio como AWS Secrets Manager te quitas ese dolor de cabeza y lo mejor de todo que es compatible con motores de base de datos o API de terceros. 


### Resumamos a muy alto nivel el servicio como: 
1. Guardas un “Secret” como le suelen llamar, en tu cuenta de AWS
2. Ese “Secret” es cifrado en reposo (nadie más que tú tendrá acceso al dato plano), por lo tanto no hay forma de que quede expuesto. 
3. Mediante API o SDK de AWS vas a poder consumirlo fácilmente
4. 100% integrable con otros servicios como IAM y AWS Cognito (más adelante veremos el caso práctico).
5. Podrás entrar en tu consola de AWS y cambiar el valor las veces que quieras sin necesidad de informar a quien lo esté consumiendo. 

Este servicio te permite como desarrollador frontend hacer despliegue de tus aplicaciones sin necesidad de exponer datos sensibles durante el proceso de CI/CD ni durante el empaquetado final. 

_¿Cuántas veces no le preguntaste a tu colega por una API KEY de tercero y mandarla mediante chats? Sin hablar de cuando cambiaban y nadie te avisaba al respecto y solo recibías un “401 Unauthorized” sin entender el motivo aparente._


---

## Amazon Cognito: para acceso temporal a nuestros recursos dentro de AWS <a name="chapter-7"></a>
![Amazon Cognito es un servicio de administración de acceso e identidad del cliente (IAM)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/od31l61x5r1dcvcgcpw0.png)
Esta solución parece perfecta para una aplicación backend, ya que podemos limitar la conexión de forma centralizada hacia el secret manager, pero acá estamos hablando de aplicaciones frontend, por eso entra en juego el servicio de “AWS Cognito”. 

_Texto copiado de AWS:_
> Amazon Cognito es un servicio de administración de acceso e identidad del cliente (IAM) rentable y centrado en el desarrollador. Proporciona un almacén de identidad seguro y opciones de federación que pueden escalar a millones de usuarios.

Mediante AWS Cognito, vamos a poder otorgar credenciales temporales a nuestros usuarios anónimos para que puedan consumir los secretos almacenados en AWS Secret Manager sin necesidad de exponer datos sensibles. 

> Las identidades de Amazon Cognito NO son credenciales. Se intercambian por credenciales utilizando la compatibilidad con las identidades web federadas de AWS Security Token Service (AWS STS).
 



En los siguientes pasos vamos a poder ver cómo lograr intercambiar identidades de AWS Cognito por las credenciales temporales (con acceso limitado) que podremos utilizar con AWS Secret Manager para finalmente consumir nuestra aplicación de terceros. 

Parece super largo el proceso, pero son simplemente unas líneas de código. 

---

## Código de ejemplo (Vue Js)
Primero debemos usar Identidades de **AWS Cognito** para acceder a nuestros recursos de AWS

![Usar Identidades de AWS Cognito](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rkqrb1l9mdngy4a50223.png)

Lo primero que vamos hacer es crear una “identity pool” dentro de las entidades federadas de AWS Cognito. 

![crear “identity pool” dentro de las entidades federadas de AWS Cognito](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ave39ikgansn0930qcpx.png)

Link:
`https://us-east-1.console.aws.amazon.com/cognito/federated?region=us-east-1`

Definimos nuestro nombre para la “Identity pool name” y nos aseguramos que esté habilitado la opción de “Enable access to unauthenticated identities”. Creamos el “Pool”


Vamos a necesitar crear un ROL para los usuarios “unauthenticated“, con permisos de acceder a Secret Manager usando la siguiente Policy, para eso en el paso 2 hacer click en ver detalles.
El primer Rol lo dejamos por defecto, nos centraremos en el “unauthenticated Role”. 

![ROL para los usuarios “unauthenticated“ AWS Cognito](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6dwjr34vybblzwan5u9s.png)

Editamos la Policy y añadimos lo siguiente dentro de statement:

```
{
  "Sid": "VisualEditor0",
  "Effect": "Allow",
  "Action": "secretsmanager:GetSecretValue",
  "Resource": "*"
}
```

---

Dejamos la consola de AWS por un momento, y vamos a nuestro proyecto javascript. Para fines prácticos lo haremos en VueJs pero lo puedes hacer en cualquier librería (Angular, React, Vanilla JS).

Vamos a nuestro proyecto a instalamos la siguiente librería de AWS SDK V3, la cual nos permite instalar las dependencias de forma bastante granular, haciendo que nuestra aplicación sea lo más liviana posible: 

```
npm i @aws-sdk/credential-providers
```
> Esta librería nos va permitir intercambiar nuestro IdentityPool ID por unas credenciales basadas en accessKeyId y secretAccessKey


```
npm i @aws-sdk/client-cognito-identity
```
> Es el cliente de Cognito


```
npm i @aws-sdk/client-secrets-manager
```
> Nos permite acceder a AWS Secret Manager mediante su SDK, teniendo en texto plano del valor guardado en el “secreto”.

###**TIP:**

> Siempre que sea posible, utiliza la **[AWS SDK v3 Javascript](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/index.html)**, te quitará dolores de cabeza en el futuro,  aún cuando los ejemplos que veas en internet tengan la referencia a la SDK v2 (deprecated). 

Siempre investigando el método que quieras utilizar lograrás encontrar la documentación oficial. 

---
Volvemos a la consola: en AWS Secret Manager para crear nuestro secreto (“Secret”)

![AWS Secret Manager para crear nuestro secreto (“Secret”)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t3hhbx88vyvqtvtqq1l1.png)


En la parte de tipo seleccionamos otro (“Other”), y escribimos un texto plano

Y completamos los datos como la siguiente imagen

![Texto plano secret Manager](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ajbfngw9lkss87qw4a99.png)

Damos siguiente y dejamos un nombre para nuestro secreto.

Una buena práctica es indicar como prefijo el ambiente seguido del nombre que le queremos dar por ejemplo para local/dev sería: 
`dev/google_maps_apikey`

Ambiente te Testing/QA
`test/google_maps_apikey`

Ambiente Productivo
`prod/google_maps_apikey`


De esta forma tendremos granularidad en los permisos y/o personas que tengan acceso.
![De esta forma tendremos granularidad en los permisos y/o personas que tengan acceso en secret manager](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ptcgphqv2nm2ee6y201g.png)

**Importante:** dale continuar, continuar hasta que te diga “Guardar Secreto” ya que los otros pasos son opcionales para este ejemplo. 

Ya con esto tendremos nuestro secreto almacenado en la nube, encriptado y seguro. 

_(opcional)_
Si queremos ver el valor que tenemos almacenado simplemente 

- Vamos a la lista de secretos, seleccionamos el secreto .
- Dentro hacemos click en retrieve value.


![lista de secretos en aws secret manager](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3qvhkdajfekgscc5uexm.png)

(opcional ver el secreto)
![opcional ver el secreto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fw1716jbpj7b3sfjrdld.png)

####Tomen nota del ARN 
(lo utilizaremos más adelante).
`arn:aws:secretsmanager:us-east-1:058452286234:secret:dev/google_maps_apikey-mMforO`


---
### Última parte, conectar todo ¡Ya casi estamos!

Continuemos con nuestra app frontend, volvemos al editor de código. 

En pasos anteriores habíamos instalado una serie de dependencias, nosotros vamos a estar utilizando la función “fromCognitoIdentityPool” la cual nos permite hacer el intercambio del ID por credenciales temporales como dijimos anteriormente. 

Importamos todo lo que necesitamos en nuestro componente de la siguiente manera: 

```
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-providers";
import {
  SecretsManagerClient,
  GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";
```
_(importación usando método ECMAScript 6 si estás aplicando esta config a un sistema legacy puedes buscar en la documentación como importar usando ES5)_

Para poder tener en texto plano nuestro secreto debemos crear un cliente y pasarle un comando con el secreto que queremos ver, algo como esto (_NO USAR ésta solución en ambientes, meramente educativo. Más adelante tienes la solución_)

![Cliente de secret manager](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9om92so8rkonm0thxy0e.png)

#### Problema de seguridad encontrado:
 **_NUNCA debemos colocar accessKeyId ni secretAccessKey
 en nuestros códigos_**, por eso esta solucion no es la más recomendada. 

Al principio de todo el capitulo estabamos hablando de Amazon Cognito, y acá es cuando entra en juego, vamos a cambiar nuestro identitypoolId por una credenciales temporales de la siguiente manera:

```
 const region = "us-east-1"; 
// Cambia esto a la región donde está configurado tu recurso de Secret Manager
      const identityPoolId = "us-east-1:1b5cc45e-490e-439e-8692-a27a54ae029a"; 
// Cambia esto a tu ID de Identity Pool
const tempCredentials = fromCognitoIdentityPool({
        client: new CognitoIdentityClient({ region }),
        identityPoolId,
        clientConfig: { region },
});
```

Te resumo lo que estamos usando: 

`CognitoIdentityClient `
Para obtener las credenciales temporales desde AWS Identity Pool 

`fromCognitoIdentityPool `
de @aws-sdk/credential-provider-cognito-identity para crear un proveedor de credenciales que utilizará el cliente de Secret Manager. 

`GetSecretValueCommand`
Obtener el valor del secreto.

###** Código con la solución final **<a name="chapter-8"></a>

![AWS Secret Manager, con Amazon Cognito Identity Pool mediante VueJs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jlzarc0798twhi9savcx.png)


##Repositorio GIT de ejemplo

[https://github.com/hectorfernandez02/cloudparatodos-aws-secret-manager-vuejs](https://github.com/hectorfernandez02/cloudparatodos-aws-secret-manager-vuejs)

---


## Conclusiones: 
Recomendamos guardar información sensible, como claves de API, contraseñas y números de identificación legal (C.I. / D.N.I / S.S.N), en Secret Manager en lugar de en variables de entorno. Por otro lado, se pueden guardar en variables de entorno otras configuraciones de la aplicación, como la URL de la aplicación, la configuración del servidor, las claves de API públicas (no nominadas), etc.