Cómo gestionar secretos en Bitbucket

secrets

Los datos sensibles, como contraseñas, tokens de API y credenciales de bases de datos, son fundamentales para el desarrollo y las pruebas de proyectos, pero nunca deberían guardarse en texto plano dentro del repositorio. En esta entrada, explicaremos cómo en WATA Factory manejamos estos secretos usando las herramientas integradas de Bitbucket: variables de repositorio y variables de despliegue.

Para utilizar cualquiera de estas soluciones, solo necesitas permisos de escritura. Sin embargo, para configurar cualquier tipo de variable, es necesario tener permisos de administrador. Si necesitas más información técnica, puedes consultar este artículo.

Cypress es uno de los frameworks de pruebas automatizadas que usamos habitualmente en WATA Factory, así que usaremos un proyecto de Cypress como ejemplo, aunque este método se puede aplicar a cualquier tecnología.

En este proyecto, tenemos una referencia a un archivo credentials.ts dentro de cypress.config.ts. Si queremos ejecutar nuestras pruebas automatizadas en el pipeline, necesitaríamos tener el archivo de credenciales en el repositorio, de lo contrario, el pipeline fallará. Sabemos que esta no es una buena práctica, así que ¿cómo lo solucionamos?

Aquí es donde entran en juego las variables de repositorio. Con esta funcionalidad nativa de Bitbucket, podemos referenciar variables definidas en nuestros pipelines y usarlas cuando lo necesitemos.

La solución tiene solo cuatro pasos:

  1. Crear la variable en Bitbucket.
  2. Crear un archivo temporal credentials.ts.
  3. Copiar el contenido de la variable en este archivo temporal.
  4. ¡LISTO! Nuestro proyecto ya tiene el archivo necesario con todos los secretos.

Variables de repositorio:

Supongamos que nuestro archivo credentials.ts tiene la siguiente estructura:

export const USERS = {
    admin: {
      username: 'admin',
      password: 'adminPassword',
    },
    regularuser: {
      username: 'justAnUser',
      password: 'regularPassword',
    },
  };
   
export const DB_CREDENTIALS = {
    host: 'your-server.com',
    user: 'dbUser',
    password: 'anotherPassword',
    database: 'my_sample_db',
    port: 1234,
  };

Y que nuestro archivo cypress.config.ts necesita importarlo para utilizar esas credenciales:

import { DB_CREDENTIALS } from './cypress/properties/credentials';

//my DB connection
const connections: Record<string, any> = {
  atDashboard: {
    host: DB_CREDENTIALS.host,
    user: DB_CREDENTIALS.user,
    password: DB_CREDENTIALS.password,
    database: DB_CREDENTIALS.database,
    port: DB_CREDENTIALS.port,
  }
};

Las variables pueden contener incluso un archivo JSON completo si es necesario. Por lo tanto, copiamos el contenido de nuestro archivo credentials.ts, y nos dirigimos a «Configuración del repositorio > Variables de repositorio» en Bitbucket.

secretos
secretos

Solo necesitamos asignar un nombre a la variable, pegar el contenido del archivo y hacer clic en Agregar. Si prefieres que estas credenciales estén cifradas y no se muestren en los registros de compilación, puedes marcar la opción Seguro, pero ten en cuenta que ya no podrás ver ni editar su contenido, solo podrás asignar un nuevo valor o eliminar la variable.

Una vez configurada la variable, solo debemos utilizar el símbolo ‘$’ para llamarla y redirigir la salida de echo usando el comando ‘>’ dentro de nuestro pipeline. En la raíz de tu proyecto, debe haber un archivo bitbucket-pipeline.yml:

image: cypress/base:20.11.0

definitions:
  steps:
    - step: &install-and-check
        name: Installation and Static Analysis
        caches:
          - node
        script:
          - npm install
        artifacts:
          - node_modules/**
    - step: &test
        name: Run tests with coverage
        caches:
          - node
        script:
          - npm run test -- --coverage
          - node_modules/.bin/cypress install --force
          - echo $OUR_CREDENTIALS > ./cypress/properties/credentials.ts
          - node_modules/.bin/cypress run --component --env coverage=true
          - node_modules/.bin/cypress run --env PLUGIN_ENABLED=false
          ...

Con este comando, creamos un archivo temporal con el contenido de nuestra variable. Así, el archivo de credenciales se elimina tras la ejecución del pipeline.

echo $VARIABLE > ./your/path/filename.ts

Variables de despliegue:

En WATA Factory, para adaptarnos a las necesidades de nuestros clientes y ofrecer un servicio de calidad, necesitamos trabajar con diferentes entornos: desarrollo, producción y, por supuesto, uno dedicado a pruebas. Cada entorno puede requerir credenciales diferentes o configuraciones específicas. Para resolver este desafío, Bitbucket ofrece las variables de despliegue.

Estas variables funcionan de manera similar a las variables de repositorio, pero debes especificar en el pipeline a qué entorno te refieres.

Para crear una variable de despliegue:

  1. Ve a «Configuración del repositorio > Despliegues».
  2. Crea un nuevo entorno en la sección que desees (pruebas, staging o producción).
  3. Añade una nueva variable como hicimos antes con las variables de repositorio.
  4. Especifica en el pipeline a qué entorno hace referencia:

image: cypress/base:20.11.0

definitions:
  steps:
    - step: &install-and-check
        name: Installation and Static Analysis
        caches:
          - node
        script:
          - npm install
        artifacts:
          - node_modules/**
    - step: &test
        name: Run tests with coverage
        caches:
          - node
        script:
          - npm run test -- --coverage
          - node_modules/.bin/cypress install --force
          - echo $CREDENTIALS > ./cypress/properties/credentials.ts
          - node_modules/.bin/cypress run --component --env coverage=true
          - node_modules/.bin/cypress run --env PLUGIN_ENABLED=false
        artifacts:
          - coverage/**
          - coverage-component/**
          - cypress-image-diff-html-report/**
          - cypress-image-diff-screenshots/**
    - step: &sonar
        name: SonarQube analysis
        image: sonarsource/sonar-scanner-cli:latest
        script:
          - sonar-scanner -Dsonar.projectKey=Gesa-Dashboard---Angular -Dsonar.host.url=$SONAR_URL -Dsonar.login=$SONAR_SECRET -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info,coverage-component/lcov.info
    - step: &deploy
        name: Deploy
        script:
          - npm run build -- --base-href /dashboard_ang/ --output-hashing=all --aot --configuration=$DEPLOYMENT_PROFILE
          - ssh $FTP_USERNAME@$FTP_HOST -p222 'mkdir -p ~/public_html/dashboard_ang'
          - scp -P 222 -r dist/new_angular_poc/browser/* $FTP_USERNAME@$FTP_HOST:~/public_html/dashboard_ang/

pipelines:
  branches:
    develop:
      - step:
          <<: *install-and-check
      - step:
          deployment: automatictesting
          <<: *test
      - step:
          <<: *sonar
  custom:
    deploy-company1-dev:
      - step:
          <<: *install-and-check
      - step:
          name: Deploy to Company 1
          deployment: company1
          <<: *deploy
    deploy-at:
      - step:
          <<: *install-and-check
      - step:
          name: Deploy to AT
          deployment: automatictesting
          <<: *deploy

Como puedes ver, en la sección de pipelines definimos el entorno y en qué paso se utilizará. El nombre del paso se define más arriba.

Un detalle importante: si nombras una variable de despliegue igual que una variable de repositorio, esta última será sobrescrita.