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.
- pdgseg_cli-1.0.0/.github/workflows/ci.yml +148 -0
- pdgseg_cli-1.0.0/.github/workflows/release.yml +69 -0
- pdgseg_cli-1.0.0/.gitignore +9 -0
- pdgseg_cli-1.0.0/PKG-INFO +380 -0
- pdgseg_cli-1.0.0/README.md +352 -0
- pdgseg_cli-1.0.0/pyproject.toml +60 -0
- pdgseg_cli-1.0.0/setup.cfg +4 -0
- pdgseg_cli-1.0.0/src/pdgseg/__init__.py +8 -0
- pdgseg_cli-1.0.0/src/pdgseg/cli.py +172 -0
- pdgseg_cli-1.0.0/src/pdgseg/client.py +158 -0
- pdgseg_cli-1.0.0/src/pdgseg/config.py +195 -0
- pdgseg_cli-1.0.0/src/pdgseg/errors.py +21 -0
- pdgseg_cli-1.0.0/src/pdgseg/flow.py +160 -0
- pdgseg_cli-1.0.0/src/pdgseg/output.py +115 -0
- pdgseg_cli-1.0.0/src/pdgseg/packager.py +141 -0
- pdgseg_cli-1.0.0/src/pdgseg/schema/pdgseg.schema.v1.json +63 -0
- pdgseg_cli-1.0.0/src/pdgseg/summary.py +90 -0
- pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/PKG-INFO +380 -0
- pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/SOURCES.txt +34 -0
- pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/dependency_links.txt +1 -0
- pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/entry_points.txt +2 -0
- pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/requires.txt +8 -0
- pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/scm_file_list.json +30 -0
- pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/scm_version.json +8 -0
- pdgseg_cli-1.0.0/src/pdgseg_cli.egg-info/top_level.txt +1 -0
- pdgseg_cli-1.0.0/tests/conftest.py +107 -0
- pdgseg_cli-1.0.0/tests/e2e/projects/clean/src/Greeting.java +5 -0
- pdgseg_cli-1.0.0/tests/e2e/projects/critical/src/UserDao.java +8 -0
- pdgseg_cli-1.0.0/tests/e2e/projects/high/web/render.js +4 -0
- pdgseg_cli-1.0.0/tests/e2e/projects/medium/src/ReportDao.java +4 -0
- pdgseg_cli-1.0.0/tests/e2e/test_e2e.py +220 -0
- pdgseg_cli-1.0.0/tests/test_cli.py +245 -0
- pdgseg_cli-1.0.0/tests/test_client.py +121 -0
- pdgseg_cli-1.0.0/tests/test_config.py +113 -0
- pdgseg_cli-1.0.0/tests/test_packager.py +86 -0
- 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,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.
|