pdgseg-cli 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. pdgseg_cli-1.0.0/.github/workflows/ci.yml +148 -0
  2. pdgseg_cli-1.0.0/.github/workflows/release.yml +69 -0
  3. pdgseg_cli-1.0.0/.gitignore +9 -0
  4. pdgseg_cli-1.0.0/PKG-INFO +380 -0
  5. pdgseg_cli-1.0.0/README.md +352 -0
  6. pdgseg_cli-1.0.0/pyproject.toml +60 -0
  7. pdgseg_cli-1.0.0/setup.cfg +4 -0
  8. pdgseg_cli-1.0.0/src/pdgseg/__init__.py +8 -0
  9. pdgseg_cli-1.0.0/src/pdgseg/cli.py +172 -0
  10. pdgseg_cli-1.0.0/src/pdgseg/client.py +158 -0
  11. pdgseg_cli-1.0.0/src/pdgseg/config.py +195 -0
  12. pdgseg_cli-1.0.0/src/pdgseg/errors.py +21 -0
  13. pdgseg_cli-1.0.0/src/pdgseg/flow.py +160 -0
  14. pdgseg_cli-1.0.0/src/pdgseg/output.py +115 -0
  15. pdgseg_cli-1.0.0/src/pdgseg/packager.py +141 -0
  16. pdgseg_cli-1.0.0/src/pdgseg/schema/pdgseg.schema.v1.json +63 -0
  17. pdgseg_cli-1.0.0/src/pdgseg/summary.py +90 -0
  18. pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/PKG-INFO +380 -0
  19. pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/SOURCES.txt +34 -0
  20. pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/dependency_links.txt +1 -0
  21. pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/entry_points.txt +2 -0
  22. pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/requires.txt +8 -0
  23. pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/scm_file_list.json +30 -0
  24. pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/scm_version.json +8 -0
  25. pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/top_level.txt +1 -0
  26. pdgseg_cli-1.0.0/tests/conftest.py +107 -0
  27. pdgseg_cli-1.0.0/tests/e2e/projects/clean/src/Greeting.java +5 -0
  28. pdgseg_cli-1.0.0/tests/e2e/projects/critical/src/UserDao.java +8 -0
  29. pdgseg_cli-1.0.0/tests/e2e/projects/high/web/render.js +4 -0
  30. pdgseg_cli-1.0.0/tests/e2e/projects/medium/src/ReportDao.java +4 -0
  31. pdgseg_cli-1.0.0/tests/e2e/test_e2e.py +220 -0
  32. pdgseg_cli-1.0.0/tests/test_cli.py +245 -0
  33. pdgseg_cli-1.0.0/tests/test_client.py +121 -0
  34. pdgseg_cli-1.0.0/tests/test_config.py +113 -0
  35. pdgseg_cli-1.0.0/tests/test_packager.py +86 -0
  36. pdgseg_cli-1.0.0/tests/test_summary.py +48 -0
@@ -0,0 +1,148 @@
1
+ name: CI
2
+
3
+ # Construye el paquete y prueba el wheel instalado (no el código fuente) en Linux, macOS y
4
+ # Windows, con todas las versiones de Python soportadas.
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ pull_request:
10
+ workflow_call:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ build:
17
+ name: Build
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 0 # setuptools-scm reads the version from the git tags
23
+ - uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.12"
26
+ - run: python -m pip install --upgrade pip build twine
27
+ - run: python -m build
28
+ - run: python -m twine check --strict dist/*
29
+ - uses: actions/upload-artifact@v4
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+
34
+ test:
35
+ name: Test (${{ matrix.os }}, Python ${{ matrix.python }})
36
+ needs: build
37
+ runs-on: ${{ matrix.os }}
38
+ strategy:
39
+ fail-fast: false
40
+ matrix:
41
+ os: [ubuntu-latest, macos-latest, windows-latest]
42
+ python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
43
+ defaults:
44
+ run:
45
+ shell: bash
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+ - uses: actions/setup-python@v5
49
+ with:
50
+ python-version: ${{ matrix.python }}
51
+ - uses: actions/download-artifact@v4
52
+ with:
53
+ name: dist
54
+ path: dist/
55
+ - name: Install the built wheel
56
+ run: |
57
+ python -m pip install --upgrade pip
58
+ python -m pip install dist/*.whl "pytest>=8" "responses>=0.25"
59
+ - name: The command is on the PATH
60
+ run: |
61
+ pdgseg --version
62
+ pdgseg analyze --help
63
+ - name: Unit tests against the installed package
64
+ # (the E2E tests are deselected by default: see the e2e job)
65
+ run: |
66
+ # src/ is never on sys.path (src layout), so "import pdgseg" loads the installed wheel.
67
+ python -c "import pdgseg; print(pdgseg.__file__)"
68
+ python -m pytest -q -p no:cacheprovider --rootdir=. tests
69
+
70
+ e2e:
71
+ name: E2E contra el backend real
72
+ needs: build
73
+ runs-on: ubuntu-latest
74
+ services:
75
+ postgres:
76
+ image: postgres:15
77
+ env:
78
+ POSTGRES_DB: pdgseg
79
+ POSTGRES_USER: pdgseg
80
+ POSTGRES_PASSWORD: pdgseg
81
+ ports: ["5432:5432"]
82
+ options: >-
83
+ --health-cmd "pg_isready -U pdgseg" --health-interval 5s --health-timeout 5s --health-retries 20
84
+ env:
85
+ INTERNAL_SERVICE_TOKEN: e2e-internal-token
86
+ steps:
87
+ - uses: actions/checkout@v4
88
+ - name: Backend (main)
89
+ uses: actions/checkout@v4
90
+ with:
91
+ repository: JEGH18/segsoft-backend
92
+ path: stack/backend
93
+ - name: Motor de análisis (main)
94
+ uses: actions/checkout@v4
95
+ with:
96
+ repository: JEGH18/segsoft-motorpython
97
+ path: stack/engine
98
+ - uses: actions/setup-java@v4
99
+ with:
100
+ distribution: temurin
101
+ java-version: "17"
102
+ cache: maven
103
+ cache-dependency-path: stack/backend/pom.xml
104
+ - uses: actions/setup-python@v5
105
+ with:
106
+ python-version: "3.11"
107
+ - uses: actions/download-artifact@v4
108
+ with:
109
+ name: dist
110
+ path: dist/
111
+
112
+ - name: Start the engine
113
+ run: |
114
+ python -m venv /tmp/engine-venv
115
+ /tmp/engine-venv/bin/pip install -q ./stack/engine
116
+ cd stack/engine
117
+ nohup /tmp/engine-venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 18001 > /tmp/engine.log 2>&1 &
118
+
119
+ - name: Start the backend
120
+ env:
121
+ DB_URL: jdbc:postgresql://localhost:5432/pdgseg
122
+ DB_USER: pdgseg
123
+ DB_PASSWORD: pdgseg
124
+ ENGINE_BASE_URL: http://127.0.0.1:18001
125
+ JWT_SECRET: e2e-ci-secret-minimum-32-characters-long-ok
126
+ run: |
127
+ mvn -q -B -f stack/backend/pom.xml package -DskipTests
128
+ # Without the prod profile the backend seeds dev/dev123 (DEVELOPER) and admin/admin123.
129
+ nohup java -jar stack/backend/target/*.jar > /tmp/backend.log 2>&1 &
130
+ curl -sf --retry 60 --retry-connrefused --retry-delay 2 http://localhost:8080/actuator/health
131
+
132
+ - name: Install the built CLI
133
+ run: python -m pip install dist/*.whl "pytest>=8" "responses>=0.25"
134
+
135
+ - name: E2E tests
136
+ env:
137
+ PDGSEG_E2E_SERVER_URL: http://localhost:8080
138
+ PDGSEG_E2E_USERNAME: dev
139
+ PDGSEG_E2E_PASSWORD: dev123
140
+ PDGSEG_E2E_ADMIN_USERNAME: admin
141
+ PDGSEG_E2E_ADMIN_PASSWORD: admin123
142
+ run: python -m pytest -m e2e -v -p no:cacheprovider --rootdir=. tests/e2e
143
+
144
+ - name: Logs of the stack
145
+ if: failure()
146
+ run: |
147
+ tail -n 200 /tmp/backend.log || true
148
+ tail -n 100 /tmp/engine.log || true
@@ -0,0 +1,69 @@
1
+ name: Release
2
+
3
+ # Al empujar un tag de versión (v1.2.3): build, tests en los tres sistemas operativos, empaquetado
4
+ # y publicación en PyPI con twine. A mano (workflow_dispatch) publica en TestPyPI para ensayar.
5
+ #
6
+ # Secretos del repositorio (Settings → Secrets and variables → Actions):
7
+ # PYPI_API_TOKEN token de API de pypi.org con alcance al proyecto pdgseg-cli
8
+ # TEST_PYPI_API_TOKEN token de test.pypi.org (solo para el ensayo manual)
9
+
10
+ on:
11
+ push:
12
+ tags: ["v*"]
13
+ workflow_dispatch:
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ ci:
20
+ uses: ./.github/workflows/ci.yml
21
+
22
+ publish:
23
+ name: Publish
24
+ needs: ci
25
+ runs-on: ubuntu-latest
26
+ permissions:
27
+ contents: write # to attach the files to the GitHub release
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ with:
31
+ fetch-depth: 0
32
+ - uses: actions/download-artifact@v4
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+ - uses: actions/setup-python@v5
37
+ with:
38
+ python-version: "3.12"
39
+ - run: python -m pip install --upgrade twine
40
+
41
+ - name: The package version matches the tag
42
+ if: startsWith(github.ref, 'refs/tags/v')
43
+ run: |
44
+ expected="${GITHUB_REF_NAME#v}"
45
+ wheel=$(ls dist/*.whl)
46
+ case "$wheel" in
47
+ *"pdgseg_cli-${expected}-"*) echo "Versión $expected" ;;
48
+ *) echo "::error::El wheel ($wheel) no tiene la versión del tag ($expected)"; exit 1 ;;
49
+ esac
50
+
51
+ - name: Publish to PyPI
52
+ if: startsWith(github.ref, 'refs/tags/v')
53
+ env:
54
+ TWINE_USERNAME: __token__
55
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
56
+ run: python -m twine upload --non-interactive dist/*
57
+
58
+ - name: Publish to TestPyPI (manual run)
59
+ if: github.event_name == 'workflow_dispatch'
60
+ env:
61
+ TWINE_USERNAME: __token__
62
+ TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
63
+ run: python -m twine upload --non-interactive --repository testpypi --skip-existing dist/*
64
+
65
+ - name: GitHub release with the files
66
+ if: startsWith(github.ref, 'refs/tags/v')
67
+ env:
68
+ GH_TOKEN: ${{ github.token }}
69
+ run: gh release create "$GITHUB_REF_NAME" dist/* --title "pdgseg-cli $GITHUB_REF_NAME" --generate-notes
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ results.sarif
@@ -0,0 +1,380 @@
1
+ Metadata-Version: 2.4
2
+ Name: pdgseg-cli
3
+ Version: 1.0.0
4
+ Summary: CLI de SegSoft: ejecuta el análisis de cumplimiento de seguridad desde pipelines CI/CD
5
+ Author: PDG SegSoft - Universidad Icesi
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/JEGH18/segsoft-cli
8
+ Project-URL: Source, https://pi2tools.icesi.edu.co/bitbucket/projects/SEGSOFT/repos/segsoft-cli
9
+ Keywords: security,compliance,sast,ci,sarif,owasp
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Security
18
+ Classifier: Topic :: Software Development :: Quality Assurance
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: click<9,>=8.1
22
+ Requires-Dist: requests<3,>=2.31
23
+ Requires-Dist: PyYAML<7,>=6.0
24
+ Requires-Dist: jsonschema<5,>=4.18
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest>=8; extra == "test"
27
+ Requires-Dist: responses>=0.25; extra == "test"
28
+
29
+ # pdgseg-cli
30
+
31
+ CLI de **SegSoft** para ejecutar el análisis de cumplimiento de seguridad desde la línea de comandos
32
+ y desde pipelines CI/CD (GitHub Actions, GitLab CI, Jenkins). Sube el proyecto al backend de SegSoft,
33
+ aplica las políticas indicadas en `.pdgseg.yml`, espera el resultado, imprime un resumen por severidad
34
+ y categoría, y termina con un **código de salida** que permite bloquear un merge cuando hay
35
+ incumplimientos críticos.
36
+
37
+ ```console
38
+ $ pdgseg analyze
39
+ pdgseg 1.0.0 -> https://segsoft-icesi.westus3.cloudapp.azure.com
40
+ Políticas: Policy Set «Perfil PDG ICESI»
41
+ Comprimidos 128 archivos (0.4 MB); subiendo...
42
+ Análisis e46dde8b-… iniciado.
43
+ RUNNING: 3/4 reglas
44
+ COMPLETED
45
+
46
+ Resultado del análisis de SegSoft
47
+ Cumplimiento: 75.0% (ponderado 74.8%)
48
+ Hallazgos: 1
49
+
50
+ Hallazgos por severidad
51
+ CRITICAL 1
52
+ HIGH 0
53
+ ...
54
+ [CRITICAL] src/UserDao.java:6 - Prevención de SQL Injection con consultas parametrizadas
55
+
56
+ FALLA: hay hallazgos CRITICAL (exit code 2).
57
+ ```
58
+
59
+ ## Contenido
60
+
61
+ - [Instalación](#instalación)
62
+ - [Token de API](#token-de-api)
63
+ - [Uso rápido](#uso-rápido)
64
+ - [Referencia de `pdgseg analyze`](#referencia-de-pdgseg-analyze)
65
+ - [Códigos de salida](#códigos-de-salida)
66
+ - [Configuración: `.pdgseg.yml`](#configuración-pdgsegyml)
67
+ - [Integración con GitHub Actions](#integración-con-github-actions)
68
+ - [Integración con GitLab CI](#integración-con-gitlab-ci)
69
+ - [Integración con Jenkins](#integración-con-jenkins)
70
+ - [Problemas frecuentes](#problemas-frecuentes)
71
+ - [Desarrollo](#desarrollo)
72
+
73
+ ## Instalación
74
+
75
+ Requiere Python 3.9 o superior. Funciona en Linux, macOS y Windows (el CI lo prueba en los tres con
76
+ Python 3.9 a 3.13).
77
+
78
+ ```bash
79
+ pip install pdgseg-cli
80
+ pdgseg --version
81
+ ```
82
+
83
+ Mientras el paquete no esté publicado en PyPI, se instala desde GitHub:
84
+
85
+ ```bash
86
+ pip install "git+https://github.com/JEGH18/segsoft-cli.git@main"
87
+ ```
88
+
89
+ ## Token de API
90
+
91
+ El CLI se autentica con un **token de API** personal (empieza por `pdgseg_`), distinto de la sesión web:
92
+
93
+ 1. Entra a SegSoft y abre **Tokens de API** (ícono de llave junto a tu usuario).
94
+ 2. Escribe un nombre (por ejemplo, «GitHub Actions de mi-proyecto»), elige la duración (30 días a
95
+ 1 año) y pulsa **Crear token**.
96
+ 3. Copia el valor: **solo se muestra una vez**. En el servidor solo queda su hash.
97
+ 4. Guárdalo como secreto del pipeline y pásalo en la variable `PDGSEG_API_TOKEN`.
98
+
99
+ El token actúa con tu usuario y tus permisos (un DEVELOPER solo ve sus propios análisis). No sirve para
100
+ crear ni revocar otros tokens. Revócalo desde la misma página si deja de usarse o se filtra: los
101
+ pipelines que lo usen dejan de autenticarse de inmediato.
102
+
103
+ ## Uso rápido
104
+
105
+ ```bash
106
+ cd mi-proyecto
107
+ pdgseg init # crea un .pdgseg.yml de ejemplo
108
+ # edita server y policySet
109
+ export PDGSEG_API_TOKEN=pdgseg_... # en Windows (PowerShell): $env:PDGSEG_API_TOKEN = "pdgseg_..."
110
+ pdgseg analyze
111
+ echo $? # 0, 1, 2 o 10
112
+ ```
113
+
114
+ ## Referencia de `pdgseg analyze`
115
+
116
+ ```
117
+ pdgseg analyze [--path DIR] [--config ARCHIVO] [--output text|json|sarif] [--output-path ARCHIVO]
118
+ [--dry-run] [--quiet | --verbose] [--token TOKEN] [--server URL]
119
+ ```
120
+
121
+ | Flag | Por defecto | Descripción |
122
+ |---|---|---|
123
+ | `--path DIR` | `.` | Directorio del proyecto a analizar. Se comprime y se sube. |
124
+ | `--config ARCHIVO` | `<path>/.pdgseg.yml` | Archivo de configuración. |
125
+ | `--output text` | sí | Resumen legible: cumplimiento, hallazgos por severidad y por categoría, políticas incumplidas y los primeros 20 hallazgos con archivo y línea. |
126
+ | `--output json` | | Un documento JSON para scripts (ver abajo). Va a la salida estándar, o a `--output-path`. |
127
+ | `--output sarif` | | Escribe un archivo [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/) (para GitHub Code Scanning) y además imprime el resumen en texto. |
128
+ | `--output-path ARCHIVO` | `results.sarif` con `sarif` | Dónde escribir la salida `json` o `sarif`. Crea las carpetas que falten. |
129
+ | `--dry-run` | | Ejecuta el análisis completo pero **siempre devuelve 0** por los hallazgos, con una advertencia visible y el código que habría devuelto. Un error técnico sigue devolviendo 10. |
130
+ | `--quiet`, `-q` | | Sin mensajes de progreso ni colores. Con `--output json`, la salida estándar contiene solo el JSON. |
131
+ | `--verbose`, `-v` | | Muestra cada petición HTTP, los reintentos y el detalle de los errores inesperados. No se puede combinar con `--quiet`. |
132
+ | `--token TOKEN` | `$PDGSEG_API_TOKEN` | Token de API. Mejor por la variable: lo que va en la línea de comandos puede quedar en el historial y en los logs. |
133
+ | `--server URL` | `$PDGSEG_SERVER_URL` | URL del backend; reemplaza `server` del `.pdgseg.yml`. |
134
+
135
+ Otros comandos: `pdgseg init [--path DIR]` crea un `.pdgseg.yml` de ejemplo (no sobrescribe uno
136
+ existente); `pdgseg --version`; `pdgseg analyze --help`.
137
+
138
+ ### Qué hace
139
+
140
+ 1. Lee y valida `.pdgseg.yml` y resuelve las políticas (antes de subir nada: un nombre mal escrito
141
+ falla rápido).
142
+ 2. Comprime el directorio, sin `.git/`, `node_modules/`, entornos virtuales, cachés ni carpetas de
143
+ IDE, ni lo que indique `exclude`. No sigue enlaces simbólicos. El ZIP puede pesar hasta 100 MB.
144
+ 3. Lo sube, aplica la selección de políticas e inicia el análisis.
145
+ 4. Consulta el estado cada `pollIntervalSeconds` hasta que termine (o lo cancela al llegar a
146
+ `timeoutMinutes`).
147
+ 5. Trae los resultados (y el SARIF, con `--output sarif`), imprime el resumen y sale con el código
148
+ correspondiente.
149
+
150
+ La conexión siempre verifica el certificado TLS (para una CA propia, `REQUESTS_CA_BUNDLE=/ruta/ca.pem`).
151
+ Ante un fallo de red reintenta tres veces, esperando 1 s, 2 s y 4 s; una subida o un inicio de análisis
152
+ no se repiten si el servidor pudo haberlos recibido, para no duplicarlos.
153
+
154
+ ### Salida JSON
155
+
156
+ ```json
157
+ {
158
+ "analysisId": "e46dde8b-…",
159
+ "repositoryId": "f9150a8e-…",
160
+ "status": "COMPLETED",
161
+ "compliancePercentage": 75.0,
162
+ "weightedCompliancePercentage": 74.8,
163
+ "totalFindings": 1,
164
+ "findingsBySeverity": { "CRITICAL": 1, "HIGH": 0, "MEDIUM": 0, "LOW": 0 },
165
+ "findingsByCategory": { "SQL_INJECTION": 1 },
166
+ "policies": [{ "name": "Prevención de SQL Injection…", "status": "NON_COMPLIANT", "findings": 1 }],
167
+ "ruleExecutionErrors": 0,
168
+ "failOnSeverity": "high",
169
+ "dryRun": false,
170
+ "exitCode": 2,
171
+ "findings": [{ "severity": "CRITICAL", "category": "SQL_INJECTION", "policyName": "…",
172
+ "filePath": "src/UserDao.java", "lineNumber": 6, "cweId": "CWE-89",
173
+ "suggestedAction": "…" }]
174
+ }
175
+ ```
176
+
177
+ Con `--dry-run` se agrega `"wouldExitWith"`. Si hay un error técnico, la salida JSON es
178
+ `{"error": "mensaje", "exitCode": 10}`.
179
+
180
+ ## Códigos de salida
181
+
182
+ | Resultado | Exit code |
183
+ |---|---|
184
+ | Todas las políticas COMPLIANT (sin hallazgos) | `0` |
185
+ | Solo hallazgos LOW o MEDIUM (políticas REQUIRES_REVIEW) | `0`, o `1` con `failOnSeverity: medium` |
186
+ | Al menos un hallazgo HIGH (y ningún CRITICAL) | `1`, o `0` con `failOnSeverity: critical` |
187
+ | Al menos un hallazgo CRITICAL | `2` |
188
+ | Error técnico | `10` |
189
+
190
+ `failOnSeverity` (en `.pdgseg.yml`, por defecto `high`) es la severidad mínima que hace fallar: por
191
+ debajo de ella el resultado es 0; desde ella es 1, o 2 si hay algún CRITICAL. Un hallazgo LOW nunca
192
+ hace fallar.
193
+
194
+ Son **errores técnicos (10)**: `.pdgseg.yml` inexistente o inválido, falta el token, token inválido,
195
+ vencido o revocado (401), sin permiso (403), cuenta bloqueada (423), el backend no responde después de
196
+ los reintentos, el Policy Set no existe, el proyecto supera 100 MB, el análisis falla, se cancela o no
197
+ termina a tiempo.
198
+
199
+ ## Configuración: `.pdgseg.yml`
200
+
201
+ Va en la raíz del repositorio. Se valida contra el schema
202
+ [`pdgseg.schema.v1.json`](src/pdgseg/schema/pdgseg.schema.v1.json) (`schemaVersion: 1`); cualquier
203
+ campo desconocido o inválido se informa con su nombre y el CLI termina con 10.
204
+
205
+ | Campo | Obligatorio | Valores | Descripción |
206
+ |---|---|---|---|
207
+ | `schemaVersion` | sí | `1` | Versión del formato. |
208
+ | `server` | sí* | URL `https://…` | Backend de SegSoft. `http://` solo para `localhost`. *Puede venir de `PDGSEG_SERVER_URL` o `--server`. |
209
+ | `policySet` | una de las tres | nombre o id | Policy Set activo a aplicar. |
210
+ | `policies` | una de las tres | lista de ids | Políticas concretas. |
211
+ | `framework` | una de las tres | `OWASP_TOP_10_2021`, `ISO_27001`, `OWASP_ASVS`, `NIST_SP_800_53`, `DEVSECOPS`, `CUSTOM` | Todas las políticas activas del framework. |
212
+ | `failOnSeverity` | no | `medium`, `high` (defecto), `critical` | Severidad mínima que hace fallar el pipeline. |
213
+ | `exclude` | no | lista de patrones | Lo que no se sube (además de `.git/`, `node_modules/`, etc.). |
214
+ | `timeoutMinutes` | no | 1 a 120 (defecto 15) | Espera máxima del análisis. |
215
+ | `pollIntervalSeconds` | no | 2 a 60 (defecto 5) | Cada cuánto se consulta el estado. |
216
+
217
+ Patrones de `exclude` (subconjunto de `.gitignore`): `dist/` es la carpeta `dist` en cualquier nivel;
218
+ `*.min.js` compara el nombre en cualquier nivel; con `/` en medio o al inicio (`src/gen/*.py`,
219
+ `/docs`) se compara desde la raíz; `*` no cruza carpetas y `**` sí (`docs/**`, `src/**/*.py`).
220
+
221
+ ### Ejemplos
222
+
223
+ Con un Policy Set (lo recomendado: lo mantiene un SECURITY_ADMIN en SegSoft):
224
+
225
+ ```yaml
226
+ schemaVersion: 1
227
+ server: https://segsoft-icesi.westus3.cloudapp.azure.com
228
+ policySet: "Perfil PDG ICESI"
229
+ failOnSeverity: high
230
+ exclude: ["dist/", "target/", "src/test/"]
231
+ ```
232
+
233
+ Con todas las políticas de un framework, fallando también por MEDIUM:
234
+
235
+ ```yaml
236
+ schemaVersion: 1
237
+ server: https://segsoft-icesi.westus3.cloudapp.azure.com
238
+ framework: OWASP_TOP_10_2021
239
+ failOnSeverity: medium
240
+ ```
241
+
242
+ Con políticas concretas y más tiempo de espera:
243
+
244
+ ```yaml
245
+ schemaVersion: 1
246
+ server: https://segsoft-icesi.westus3.cloudapp.azure.com
247
+ policies:
248
+ - cb9b2b2c-0000-0000-0000-000000000000
249
+ - d6e19077-0000-0000-0000-000000000000
250
+ timeoutMinutes: 30
251
+ exclude: ["*.min.js", "docs/**"]
252
+ ```
253
+
254
+ ## Integración con GitHub Actions
255
+
256
+ `.github/workflows/security.yml`:
257
+
258
+ ```yaml
259
+ name: Seguridad
260
+ on:
261
+ pull_request:
262
+ push:
263
+ branches: [main]
264
+
265
+ jobs:
266
+ segsoft:
267
+ runs-on: ubuntu-latest
268
+ permissions:
269
+ contents: read
270
+ security-events: write # para subir el SARIF a Code Scanning
271
+ steps:
272
+ - uses: actions/checkout@v4
273
+ - uses: actions/setup-python@v5
274
+ with:
275
+ python-version: "3.12"
276
+ - run: pip install pdgseg-cli
277
+ - run: pdgseg analyze --output sarif --output-path results.sarif
278
+ env:
279
+ PDGSEG_API_TOKEN: ${{ secrets.PDGSEG_TOKEN }}
280
+ - uses: github/codeql-action/upload-sarif@v4
281
+ if: always() && hashFiles('results.sarif') != '' # también cuando el análisis falla con 1 o 2
282
+ with:
283
+ sarif_file: results.sarif
284
+ category: segsoft
285
+ ```
286
+
287
+ - Crea el secreto `PDGSEG_TOKEN` en *Settings → Secrets and variables → Actions*.
288
+ - Los hallazgos aparecen como anotaciones en los archivos del PR y en *Security → Code scanning*.
289
+ Code Scanning es gratuito en repositorios públicos; en privados requiere GitHub Advanced Security.
290
+ - Si hay un CRITICAL, el paso falla con exit code 2 y el workflow falla. Para **bloquear el merge**,
291
+ marca el job `segsoft` como *required status check* en la protección de la rama `main`.
292
+ - Para empezar sin bloquear (mientras se corrigen los hallazgos existentes), agrega `--dry-run`.
293
+
294
+ ## Integración con GitLab CI
295
+
296
+ `.gitlab-ci.yml`:
297
+
298
+ ```yaml
299
+ segsoft:
300
+ stage: test
301
+ image: python:3.12-slim
302
+ rules:
303
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
304
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
305
+ script:
306
+ - pip install pdgseg-cli
307
+ - pdgseg analyze --output json --output-path pdgseg.json
308
+ artifacts:
309
+ when: always
310
+ paths: [pdgseg.json]
311
+ ```
312
+
313
+ - Define `PDGSEG_API_TOKEN` en *Settings → CI/CD → Variables*, marcada como *Masked* y *Protected*.
314
+ - Un exit code distinto de 0 hace fallar el job; con *Pipelines must succeed* en la configuración de
315
+ merge requests, el merge queda bloqueado.
316
+ - GitLab no muestra SARIF en el merge request (sus reportes de SAST usan otro formato), así que aquí el
317
+ resultado queda como artefacto JSON; el resumen también aparece en el log del job.
318
+
319
+ ## Integración con Jenkins
320
+
321
+ ```groovy
322
+ stage('SegSoft') {
323
+ agent { docker { image 'python:3.12-slim' } }
324
+ environment { HOME = "${env.WORKSPACE}" }
325
+ steps {
326
+ withCredentials([string(credentialsId: 'pdgseg-api-token', variable: 'PDGSEG_API_TOKEN')]) {
327
+ sh 'pip install --user pdgseg-cli && python -m pdgseg.cli analyze --output sarif --output-path results.sarif'
328
+ }
329
+ }
330
+ post { always { archiveArtifacts artifacts: 'results.sarif', allowEmptyArchive: true } }
331
+ }
332
+ ```
333
+
334
+ La credencial `pdgseg-api-token` es de tipo *Secret text*. Un exit code distinto de 0 marca el build
335
+ como fallido.
336
+
337
+ ## Problemas frecuentes
338
+
339
+ | Mensaje | Qué hacer |
340
+ |---|---|
341
+ | `Falta el token de API` | Define `PDGSEG_API_TOKEN` (en CI, como secreto) o usa `--token`. |
342
+ | `El backend rechazó el token (401): … venció o fue revocado` | Crea un token nuevo en SegSoft y actualiza el secreto. |
343
+ | `No se encontró …/.pdgseg.yml` | Crea uno con `pdgseg init` o indica otro con `--config`. |
344
+ | `No hay un Policy Set activo llamado «…»` | Revisa el nombre exacto (el mensaje sugiere parecidos) o usa su id. |
345
+ | `No se pudo conectar con … después de 4 intento(s)` | El backend no responde o no es alcanzable desde el runner. |
346
+ | `El ZIP del proyecto pesa … MB y el máximo es 100 MB` | Agrega a `exclude` builds, datos o binarios. |
347
+ | `no terminó en N minutos` | Sube `timeoutMinutes`. El análisis se cancela en el servidor. |
348
+
349
+ Con `--verbose` se ven las peticiones, los códigos HTTP y los reintentos.
350
+
351
+ ## Desarrollo
352
+
353
+ ```bash
354
+ python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
355
+ pip install -e ".[test]"
356
+ pytest # pruebas unitarias (API simulada)
357
+ ```
358
+
359
+ **Pruebas E2E** contra un SegSoft real (backend + motor + PostgreSQL), en `tests/e2e`:
360
+
361
+ ```bash
362
+ export PDGSEG_E2E_SERVER_URL=http://localhost:8080
363
+ export PDGSEG_E2E_USERNAME=dev PDGSEG_E2E_PASSWORD=dev123 # cuenta DEVELOPER
364
+ export PDGSEG_E2E_ADMIN_USERNAME=admin PDGSEG_E2E_ADMIN_PASSWORD=admin123 # opcional, para policySet
365
+ pytest -m e2e tests/e2e
366
+ ```
367
+
368
+ El CI (`.github/workflows/ci.yml`) construye el paquete, prueba el wheel instalado en Linux, macOS y
369
+ Windows con Python 3.9 a 3.13, y corre los E2E levantando el backend y el motor desde `main` de sus
370
+ repositorios.
371
+
372
+ **Publicar una versión:** con el secreto `PYPI_API_TOKEN` configurado en el repositorio,
373
+
374
+ ```bash
375
+ git tag v1.0.0 && git push origin v1.0.0
376
+ ```
377
+
378
+ `release.yml` vuelve a correr todo el CI, comprueba que la versión del paquete coincide con el tag,
379
+ publica en PyPI con twine y crea el release de GitHub con los archivos. Lanzándolo a mano desde
380
+ *Actions* publica en TestPyPI (secreto `TEST_PYPI_API_TOKEN`) para ensayar.