├── .docker └── config.json ├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── deploy.yml │ ├── integration-dev.yml │ └── integration.yml ├── .gitignore ├── .semgrepignore ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── examples │ ├── U_Docker_Enterprise_2-x_Linux-UNIX_V1R1_STIG.zip │ └── docker.ckl ├── images │ └── STIG_Parser.png └── mapping.md ├── requirements.txt ├── setup.py ├── src └── stig_parser │ ├── __init__.py │ ├── stig_parser.py │ ├── version.py │ └── xccdf-1.1.4.xsd └── tests ├── __init__.py ├── resources └── U_Docker_Enterprise_2-x_Linux-UNIX_V1R1_STIG.zip └── stig_parser_test.py /.docker/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "python:3.10.4-bullseye" 3 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master, development ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '16 22 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v3 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v2 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v2 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v2 72 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | ## NAME GITHUB ACTION WORKFLOW 2 | name: Build & Deploy PyPi Package 3 | 4 | ## RUN WORKFLOW WHEN 5 | on: 6 | ## CREATION OF NEW RELEASE 7 | release: 8 | types: [published] 9 | 10 | ## DEFINE WORKFLOW 11 | jobs: 12 | ## DEFINE BUILD JOB 13 | build-publish: 14 | ## DEFINE NAME 15 | name: "Build & Publish" 16 | 17 | ## DEFINE WORKER 18 | runs-on: ubuntu-latest 19 | 20 | ## DEFINE STEPS 21 | steps: 22 | ## CLONE REPO CODE 23 | - name: "Checkout Source Code" 24 | uses: actions/checkout@v3 25 | 26 | ## SETUP PYTHON3 27 | - name: "Configure Runner Python" 28 | uses: actions/setup-python@v3 29 | with: 30 | python-version: 3.8 31 | 32 | ## DEBUG 33 | - name: "Debug Print Step" 34 | run: | 35 | ls -al 36 | pwd 37 | 38 | ## CONFIGURE WORKER TO PUBLISH PACKAGE 39 | - name: "Install Dependencies" 40 | run: | 41 | # Upgrade pip 42 | python3 -m pip install --upgrade pip 43 | # Install Dependencies 44 | python3 -m pip install --upgrade build xmltodict 45 | 46 | ## BUILD PACKAGE 47 | - name: "Build Python Package" 48 | run: | 49 | # Build Python Package 50 | python3 -m build 51 | 52 | ## PUBLISH PACKAGE TO PYPI 53 | - name: "Publish Package to PyPi" 54 | uses: pypa/gh-action-pypi-publish@master 55 | with: 56 | password: ${{ secrets.PYPI_TOKEN }} 57 | #repository_url: https://test.pypi.org/legacy/ 58 | -------------------------------------------------------------------------------- /.github/workflows/integration-dev.yml: -------------------------------------------------------------------------------- 1 | ## NAME GITHUB ACTION WORKFLOW 2 | name: STIG Parser Integration (DEV) 3 | 4 | ## RUN WORKFLOW WHEN 5 | on: 6 | ## PULL REQUEST TO DEV 7 | pull_request: 8 | branches: [ development ] 9 | 10 | ## DEFINE WORKFLOW 11 | jobs: 12 | ## DEFINE LINT TEST JOB 13 | linting: 14 | ## DEFINE JOB NAME 15 | name: "LINTING" 16 | 17 | ## DEFINE WORKER 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | python-version: [3.8, 3.9] 22 | 23 | ## DEFINE STEPS 24 | steps: 25 | ## CLONE REPO CODE 26 | - uses: actions/checkout@v3 27 | 28 | ## SETUP PYTHON3 29 | - name: Set up Python ${{ matrix.python-version }} 30 | uses: actions/setup-python@v3 31 | with: 32 | python-version: ${{ matrix.python-version }} 33 | 34 | ## INSTALL DEPENDENCIES ON WORKER 35 | - name: Install Dependencies 36 | run: | 37 | python -m pip install --upgrade pip 38 | pip install flake8 39 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 40 | 41 | ## PERFORM LINT WITH FLAKE8 42 | - name: Lint with flake8 43 | run: | 44 | # stop the build if there are Python syntax errors or undefined names 45 | flake8 ./src --count --select=E9,F63,F7,F82 --show-source --statistics 46 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 47 | flake8 ./src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 48 | 49 | ## DEFINE UNIT TESTING JOB 50 | testing: 51 | ## DEFINE JOB NAME 52 | name: "TESTING" 53 | 54 | ## WAIT FOR PREVIOUS JOB 55 | needs: linting 56 | 57 | ## DEFINE WORKER 58 | runs-on: ubuntu-latest 59 | strategy: 60 | matrix: 61 | python-version: [3.8, 3.9] 62 | 63 | ## DEFINE STEPS 64 | steps: 65 | ## CLONE REPO CODE 66 | - uses: actions/checkout@v3 67 | 68 | ## SETUP PYTHON3 69 | - name: Set up Python ${{ matrix.python-version }} 70 | uses: actions/setup-python@v3 71 | with: 72 | python-version: ${{ matrix.python-version }} 73 | 74 | ## INSTALL DEPENDENCIES ON WORKER 75 | - name: Install Dependencies 76 | run: | 77 | python -m pip install --upgrade pip 78 | pip install pytest pytest-cov 79 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 80 | 81 | ## PERFORM UNIT TEST WITH PYTEST 82 | - name: Test with pytest 83 | run: | 84 | pytest -v --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html 85 | 86 | ## DEFINE CODE COVERAGE JOB 87 | code-cov: 88 | ## DEFINE NAME: 89 | name: "CODE COVERAGE" 90 | 91 | ## WAIT FOR PREVIOUS JOB 92 | needs: testing 93 | 94 | ## DEFINE WORKER 95 | runs-on: ubuntu-latest 96 | 97 | ## DEFINE ENVIRONMENT VARIABLES 98 | env: 99 | COVERAGE_SINGLE: 60 ## MINIMUM COVERAGE PERCENTAGE PER FILE 100 | COVERAGE_TOTAL: 60 ## MINIMUM COVERAGE PERCENTAGE TOTAL 101 | 102 | ## DEFINE STEPS 103 | steps: 104 | ## CLONE REPO CODE 105 | - uses: actions/checkout@v3 106 | 107 | ## SETUP PYTHON 108 | - name: Set up Python 3.9 109 | uses: actions/setup-python@v3 110 | with: 111 | python-version: 3.9 112 | 113 | ## INSTALL DEPENDENCIES 114 | - name: Install dependencies 115 | run: | 116 | python -m pip install --upgrade pip 117 | pip install flake8 pytest xmltodict 118 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 119 | 120 | ## PERFORM CODE COVERAGE TEST 121 | - name: "PyTest & CodeCov" 122 | id: pytest 123 | uses: programmingwithalex/pytester-cov@v1.2.4 124 | with: 125 | #pytest-root-dir: './src/' 126 | cov-omit-list: 'test/*, dev/*, docs/*, src/version.py' 127 | cov-threshold-single: ${{ env.COVERAGE_SINGLE }} 128 | cov-threshold-total: ${{ env.COVERAGE_TOTAL }} 129 | 130 | ## ADD RESULTS TO COMMIT 131 | #- name: Commit pytest coverage table 132 | # uses: peter-evans/commit-comment@v1 133 | # with: 134 | # body: ${{ steps.pytest.outputs.output-table }} 135 | 136 | ## SEND NOTIFICATION TO SLACK 137 | #- name: Slack Notification 138 | # uses: rtCamp/action-slack-notify@v2 139 | # env: 140 | # SLACK_TITLE: 'CodeCov Test Results' 141 | # SLACK_MESSAGE: ${{ steps.pytest.outputs.output-table }} 142 | # SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 143 | 144 | ## DEFINE SECURITY JOB 145 | security: 146 | ## DEFINE NAME 147 | name: "SECURITY" 148 | 149 | ## WAIT FOR PREVIOUS JOB 150 | needs: code-cov 151 | 152 | ## DEFINE WORKER 153 | runs-on: ubuntu-latest 154 | 155 | ## SKIP IF PR WAS CREATED BY DEPENDABOT (PERMISSIONS ISSUE) 156 | if: (github.actor != 'dependabot[bot]') 157 | 158 | ## DEFINE STEPS 159 | steps: 160 | ## CLONE REPO CODE 161 | - uses: actions/checkout@v3 162 | 163 | ## CACHE VULNERABILITY DATABASE 164 | - name: Cache multiple paths 165 | uses: actions/cache@v3 166 | with: 167 | path: | 168 | ${{ github.workspace }}/db 169 | key: ${{ runner.os }}-${{ hashFiles('requirements.txt') }} 170 | 171 | ## PERFORM SECURITY SCANS 172 | - name: Perform Scan 173 | uses: ShiftLeftSecurity/scan-action@master 174 | env: 175 | VDB_HOME: ${{ github.workspace }}/db 176 | WORKSPACE: "" 177 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 178 | with: 179 | output: reports 180 | 181 | ## UPLOAD RESULTS TO GITHUB SECURITY TAB 182 | - name: Upload SARIF file for GitHub Advanced Security Dashboard 183 | uses: github/codeql-action/upload-sarif@v2 184 | with: 185 | sarif_file: reports -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | ## NAME GITHUB ACTION WORKFLOW 2 | name: STIG Parser Integration 3 | 4 | ## RUN WORKFLOW WHEN 5 | on: 6 | ## PULL REQUEST TO MASTER 7 | pull_request: 8 | branches: [ master ] 9 | 10 | ## DEFINE WORKFLOW 11 | jobs: 12 | ## DEFINE LINT TEST JOB 13 | linting: 14 | ## DEFINE JOB NAME 15 | name: "LINTING" 16 | 17 | ## DEFINE WORKER 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | python-version: [3.8, 3.9] 22 | 23 | ## DEFINE STEPS 24 | steps: 25 | ## CLONE REPO CODE 26 | - uses: actions/checkout@v3 27 | 28 | ## SETUP PYTHON3 29 | - name: Set up Python ${{ matrix.python-version }} 30 | uses: actions/setup-python@v3 31 | with: 32 | python-version: ${{ matrix.python-version }} 33 | 34 | ## INSTALL DEPENDENCIES ON WORKER 35 | - name: Install Dependencies 36 | run: | 37 | python -m pip install --upgrade pip 38 | pip install flake8 39 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 40 | 41 | ## PERFORM LINT WITH FLAKE8 42 | - name: Lint with flake8 43 | run: | 44 | # stop the build if there are Python syntax errors or undefined names 45 | flake8 ./src --count --select=E9,F63,F7,F82 --show-source --statistics 46 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 47 | flake8 ./src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 48 | 49 | ## DEFINE UNIT TESTING JOB 50 | testing: 51 | ## DEFINE JOB NAME 52 | name: "TESTING" 53 | 54 | ## WAIT FOR PREVIOUS JOB 55 | needs: linting 56 | 57 | ## DEFINE WORKER 58 | runs-on: ubuntu-latest 59 | strategy: 60 | matrix: 61 | python-version: [3.8, 3.9] 62 | 63 | ## DEFINE STEPS 64 | steps: 65 | ## CLONE REPO CODE 66 | - uses: actions/checkout@v3 67 | 68 | ## SETUP PYTHON3 69 | - name: Set up Python ${{ matrix.python-version }} 70 | uses: actions/setup-python@v3 71 | with: 72 | python-version: ${{ matrix.python-version }} 73 | 74 | ## INSTALL DEPENDENCIES ON WORKER 75 | - name: Install Dependencies 76 | run: | 77 | python -m pip install --upgrade pip 78 | pip install pytest pytest-cov 79 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 80 | 81 | ## PERFORM UNIT TEST WITH PYTEST 82 | - name: Test with pytest 83 | run: | 84 | pytest -v --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html 85 | 86 | ## DEFINE CODE COVERAGE JOB 87 | code-cov: 88 | ## DEFINE NAME: 89 | name: "CODE COVERAGE" 90 | 91 | ## WAIT FOR PREVIOUS JOB 92 | needs: testing 93 | 94 | ## DEFINE WORKER 95 | runs-on: ubuntu-latest 96 | 97 | ## DEFINE ENVIRONMENT VARIABLES 98 | env: 99 | COVERAGE_SINGLE: 60 ## MINIMUM COVERAGE PERCENTAGE PER FILE 100 | COVERAGE_TOTAL: 60 ## MINIMUM COVERAGE PERCENTAGE TOTAL 101 | 102 | ## DEFINE STEPS 103 | steps: 104 | ## CLONE REPO CODE 105 | - uses: actions/checkout@v3 106 | 107 | ## SETUP PYTHON 108 | - name: Set up Python 3.9 109 | uses: actions/setup-python@v3 110 | with: 111 | python-version: 3.9 112 | 113 | ## INSTALL DEPENDENCIES 114 | - name: Install dependencies 115 | run: | 116 | python -m pip install --upgrade pip 117 | pip install flake8 pytest xmltodict 118 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 119 | 120 | ## PERFORM CODE COVERAGE TEST 121 | - name: "PyTest & CodeCov" 122 | id: pytest 123 | uses: programmingwithalex/pytester-cov@v1.2.4 124 | with: 125 | #pytest-root-dir: './src/' 126 | cov-omit-list: 'test/*, dev/*, docs/*, src/version.py' 127 | cov-threshold-single: ${{ env.COVERAGE_SINGLE }} 128 | cov-threshold-total: ${{ env.COVERAGE_TOTAL }} 129 | 130 | ## ADD RESULTS TO COMMIT 131 | #- name: Commit pytest coverage table 132 | # uses: peter-evans/commit-comment@v1 133 | # with: 134 | # body: ${{ steps.pytest.outputs.output-table }} 135 | 136 | ## SEND NOTIFICATION TO SLACK 137 | #- name: Slack Notification 138 | # uses: rtCamp/action-slack-notify@v2 139 | # env: 140 | # SLACK_TITLE: 'CodeCov Test Results' 141 | # SLACK_MESSAGE: ${{ steps.pytest.outputs.output-table }} 142 | # SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 143 | 144 | ## DEFINE SECURITY JOB 145 | security: 146 | ## DEFINE NAME 147 | name: "SECURITY" 148 | 149 | ## WAIT FOR PREVIOUS JOB 150 | needs: code-cov 151 | 152 | ## DEFINE WORKER 153 | runs-on: ubuntu-latest 154 | 155 | ## SKIP IF PR WAS CREATED BY DEPENDABOT (PERMISSIONS ISSUE) 156 | if: (github.actor != 'dependabot[bot]') 157 | 158 | ## DEFINE STEPS 159 | steps: 160 | ## CLONE REPO CODE 161 | - uses: actions/checkout@v3 162 | 163 | ## CACHE VULNERABILITY DATABASE 164 | - name: Cache multiple paths 165 | uses: actions/cache@v3 166 | with: 167 | path: | 168 | ${{ github.workspace }}/db 169 | key: ${{ runner.os }}-${{ hashFiles('requirements.txt') }} 170 | 171 | ## PERFORM SECURITY SCANS 172 | - name: Perform Scan 173 | uses: ShiftLeftSecurity/scan-action@master 174 | env: 175 | VDB_HOME: ${{ github.workspace }}/db 176 | WORKSPACE: "" 177 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 178 | with: 179 | output: reports 180 | 181 | ## UPLOAD RESULTS TO GITHUB SECURITY TAB 182 | - name: Upload SARIF file for GitHub Advanced Security Dashboard 183 | uses: github/codeql-action/upload-sarif@v2 184 | with: 185 | sarif_file: reports 186 | ## DEFINE NAME 187 | #name: "Build & Publish" 188 | 189 | ## WAIT FOR SAST JOBS 190 | #needs: [ "sast-bandit", "sast-semgrep" ] 191 | 192 | ## DEFINE WORKER 193 | #runs-on: ubuntu-latest 194 | 195 | ## DEFINE STEPS 196 | #steps: 197 | ## CLONE REPO CODE 198 | #- uses: actions/checkout@v3 199 | 200 | ## SETUP PYTHON3 201 | #- uses: actions/setup-python@v3 202 | # with: 203 | # python-version: 3.8 204 | 205 | ## CONFIGURE WORKER TO PUBLISH PACKAGE 206 | #- name: "Install Dependencies" 207 | # run: | 208 | # # Upgrade pip 209 | # python3 -m pip install --upgrade pip 210 | # Install Dependencies 211 | # python3 -m pip install --upgrade build xmltodict 212 | 213 | ## BUILD PACKAGE 214 | #- name: "Build Python Package" 215 | # run: | 216 | # # Build Python Package 217 | # python3 -m build 218 | 219 | ## PUBLISH PACKAGE TO TESTPYPI 220 | #- name: "Publish Package to Test PyPi" 221 | #if: github.repository == 'pkeech/stig_parser' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 222 | # uses: pypa/gh-action-pypi-publish@master 223 | # with: 224 | # password: ${{ secrets.TEST_PYPI_TOKEN }} 225 | # repository_url: https://test.pypi.org/legacy/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## VSCODE 2 | .vscode/ 3 | 4 | ## IGNORE STIGS USED FOR TESTING 5 | *.zip 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | pip-wheel-metadata/ 30 | share/python-wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | MANIFEST 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .nox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | *.py,cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | db.sqlite3-journal 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | -------------------------------------------------------------------------------- /.semgrepignore: -------------------------------------------------------------------------------- 1 | ## SEMGREP IGNORE FILE 2 | 3 | ## IGNORE DIRECTORIES 4 | dev/ 5 | docs/ 6 | tests/ 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.0.0] - 2020-09-24 8 | 9 | ### Added 10 | 11 | * Initial Release of STIG Parser 12 | 13 | ## [1.0.1] - 2020-12-31 14 | 15 | ### Added 16 | 17 | * Updated logic to handle parsing of new STIG schema. Resolved Issue [#3](https://github.com/pkeech/stig_parser/issues/3). 18 | 19 | 20 | ## [1.0.2] - 2021-06-14 21 | 22 | ### Added 23 | 24 | * Updated README 25 | * Additional fields to STIG JSON object; 26 | * Release Info 27 | * Source 28 | * Notice 29 | * Classification 30 | * CCI Number 31 | * STIG ID 32 | * Rule ID 33 | 34 | ## [1.1.0] - 2021-06-14 35 | 36 | ### Added 37 | 38 | * `extract_stig` function to extract the manual stig from a STIG ZIP. 39 | * `convert_stig` function to convert a STIG ZIP to a STIG JSON file. 40 | * `generate-ckl` function to generate a blank checklist object based upon the STIG passed to it. 41 | * `generate_ckl_file` function to generate a blank checklist based upon a checklist object passed to it. 42 | * Additional fields to STIG JSON object; 43 | * VulnID 44 | * RuleID 45 | * StigID 46 | * Severity 47 | * Cat 48 | * GroupTitle 49 | * RuleTitle 50 | * Description 51 | * FalsePositives 52 | * FalseNegatives 53 | * Documentable 54 | * Mitigations 55 | * SeverityOverrideGuidance 56 | * PotentialImpacts 57 | * ThirdPartyTools 58 | * MitigationControl 59 | * Responsibility 60 | * IAControls 61 | * CheckText 62 | 63 | ### Changed 64 | 65 | * Standardized Variable Names. 66 | * Standardized JSON output Variables. 67 | 68 | ## [1.1.1] - 2022-05-11 69 | 70 | ### Fixed 71 | 72 | * ([#37](https://github.com/pkeech/stig_parser/issues/37)) Resolved Issues Concerning STIG Rules with Multiple CCIs. Credit: @gregelin -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Peter Keech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkeech/stig_parser/e8b5fd92e382148c4f806530f36878fa1723dc1e/MANIFEST.in -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | 5 | Logo 6 | 7 | 8 |

9 | Explore the docs » 10 |
11 |
12 | Report Bug 13 | · 14 | Request Feature 15 |

16 |

17 | 18 | 19 | ![GitHub last commit][commit-shield] 20 | [![PyPi][pypi-shield]][pypi-url] 21 | [![GitHub Workflow Status][workflow-shield]][workflow-url] 22 | [![GitHub Open Issues][issues-shield]][issues-url] 23 | [![GitHub Open PRs][pr-shield]][pr-url] 24 | ![Python Versions][python-version-shield] 25 | [![GitHub License][license-shield]][license-url] 26 | [![LinkedIN Profile][linkedin-shield]][linkedin-url] 27 | 28 | > **NOTE**: As of version 1.1.0, the JSON output fields have been renamed and CamelCased. This was an effort to standardize the variables being used. When using Versions less than 1.1.0, please ensure you update your field names prior to updating. 29 | 30 | ### About 31 | A basic Python package to parse DISA STIGs (XCCDF) into a readable JSON format. 32 | 33 | ### Installation 34 | To install stig-parser, simple run the following command: 35 | 36 | `pip install stig-parser` 37 | 38 | ### Version Updates 39 | The table below briefly describes each update. For more information, view the releases page. 40 | 41 | | Version | Description | 42 | | :---: | --- | 43 | | 1.0.0 | Initial Creation of **stig-parser** | 44 | | 1.0.1 | Updated to handle change to STIG schema ([Issue #3](https://github.com/pkeech/stig_parser/issues/3)) | 45 | | 1.0.2 | Added Additional Fields to Output JSON. View Release Notes for Full Details ([Issue #9](https://github.com/pkeech/stig_parser/issues/9))| 46 | | 1.1.0 | Added Additional Fields to Output JSON, Included BETA release of CKL creation and added the ability to parse a STIG directly from the ZIP file. View Release Notes for Full Details | 47 | | 1.1.1 | Resolved Issues Concerning STIG Rules with Multiple CCIs. Credit: @gregelin | 48 | 49 | ### Documentation 50 | Documentation hasn't been created at this time. For the current development documentation, please visit the [repository](https://github.com/pkeech/stig_parser). 51 | 52 | ### Testing 53 | This project leverages GitHub Actions for its CI/CD workflow. During a Push to any branch, with the exception of `Master` and `Dev`, the workflow will perform Unit Testing and Linting. 54 | 55 | For manual testing, run the following commands; 56 | 57 | ``` bash 58 | ## START PYTHON DEV CONTAINER 59 | docker run -it --rm -v $(PWD):/stig-parser python /bin/bash 60 | 61 | ## INSTALL DEPENDENCIES 62 | pip install pytest pytest-cov xmltodict 63 | 64 | ## CHANGE WORKING DIRECTORY 65 | cd stig-parser 66 | 67 | ## RUN PYTEST 68 | pytest -v 69 | 70 | ## RUN PYTEST COVERAGE 71 | pytest --cov src 72 | ``` 73 | 74 | ### Usage 75 | This module contains the following functions; 76 | 77 | | Function | Description | Parameters | 78 | | --- | --- | --- | 79 | | `convert_stig(STIG_FILE)` | This function will extract the STIG from a ZIP archive, and parse the results into a JSON object | `STIG_FILE` == Path to STIG ZIP File | 80 | | `convert_xccdf(STIG_XML)` | This function will parse a raw bytes of a STIG file (XML) and return a JSON object| `STIG_XML` == Bytes object of STIG xccdf.xml File | 81 | | `generate_stig_json(STIG_JSON, EXPORT_PATH)` | This function will write the STIG JSON object to a File | `STIG_JSON` == JSON Object of STIG, `EXPORT_PATH` == Path to create JSON File | 82 | | `generate_ckl(STIGFILE, CHECKLIST_INFO)` | This function will generate an XML Object of a CKL based upon a passed STIG | `STIG_FILE` == Path to STIG ZIP File , `CHECKLIST_INFO` == JSON Object of additional information needed (see below) | 83 | | `generate_ckl_file(CKL, EXPORT_PATH)` | This function will write the CKL XML Object to a File | `CKL` == XML Object of CKL , `EXPORT_PATH` == Path to create CKL File | 84 | 85 | When creating a Checklist (CKL), additional information is required. This information is added to the CKL but is required to be defined prior to creation. For an example of usage, please see the examples below. 86 | 87 | ``` json 88 | { 89 | "ROLE": "None", 90 | "ASSET_TYPE": "Computing", 91 | "HOST_NAME": "Test_Host", 92 | "HOST_IP": "1.2.3.4", 93 | "HOST_MAC": "", 94 | "HOST_FQDN": "test.hostname.dev", 95 | "TARGET_COMMENT": "", 96 | "TECH_AREA": "", 97 | "TARGET_KEY": "3425", 98 | "WEB_OR_DATABASE": "false", 99 | "WEB_DB_SITE": "", 100 | "WEB_DB_INSTANCE": "" 101 | } 102 | ``` 103 | 104 | ### Examples 105 | This module has several use cases that will either generate a JSON object of a STIG file, or an XML object of a CKL file. 106 | 107 | #### STIGs 108 | To convert a STIG file to a JSON object, you can utilize the following example. 109 | 110 | ``` python 111 | ## LOAD PYTHON MODULE 112 | from stig_parser import convert_stig 113 | 114 | ## PARSE STIG ZIP FILE 115 | ## ASSUMES ZIP FILE IS IN CURRENT WORKING DIRECTORY 116 | json_results = convert_stig('./U_Docker_Enterprise_2-x_Linux-UNIX_V1R1_STIG.zip') 117 | ``` 118 | 119 | Additionally, this example demonstrates how to generate the STIG JSON object from an **xccdf** file. 120 | 121 | ``` python 122 | ## LOAD PYTHON MODULE 123 | from stig_parser import convert_xccdf 124 | 125 | ## LOAD XML FILE (OPTIONAL) 126 | import os 127 | 128 | with open("U_Docker_Enterprise_2-x_Linux-UNIX_STIG_V1R1_Manual-xccdf.xml", "r") as fh: 129 | raw_file = fh.read() 130 | 131 | ## PARSE XCCDF(XML) to JSON 132 | json_results = convert_xccdf(raw_file) 133 | ``` 134 | 135 | #### Checklists (CKL) 136 | To generate a CKL from a given STIG, you can utilize the following example; 137 | 138 | ``` python 139 | ## LOAD PYTHON MODULE 140 | from stig_parser import generate_ckl, generate_ckl_file 141 | 142 | ## DEFINE STIG FILE LOCATION 143 | ## ASSUMES ZIP FILE IS IN CURRENT WORKING DIRECTORY 144 | STIG = './U_Docker_Enterprise_2-x_Linux-UNIX_V1R1_STIG.zip' 145 | 146 | ## DEFINE EXPORT LOCATION 147 | EXPORT = './ myCKL.ckl' 148 | 149 | ## DEFINE ADDITIONAL CHECKLIST INFORMATION 150 | CHECKLIST_INFO ={ 151 | "ROLE": "None", 152 | "ASSET_TYPE": "Computing", 153 | "HOST_NAME": "Test_Host", 154 | "HOST_IP": "1.2.3.4", 155 | "HOST_MAC": "", 156 | "HOST_FQDN": "test.hostname.dev", 157 | "TARGET_COMMENT": "", 158 | "TECH_AREA": "", 159 | "TARGET_KEY": "3425", 160 | "WEB_OR_DATABASE": "false", 161 | "WEB_DB_SITE": "", 162 | "WEB_DB_INSTANCE": "" 163 | } 164 | 165 | 166 | ## GENERATE CKL XML OBJECT 167 | RAW_CKL = generate_ckl(STIG, CHECKLIST_INFO) 168 | 169 | ## SAVE CHECKLIST TO FILE 170 | generate_ckl_file(RAW_CKL, EXPORT) 171 | ``` 172 | 173 | 174 | ### Output 175 | Outlined below is the expected JSON output: 176 | 177 | ``` json 178 | { 179 | "Title": "xxxxxxx", 180 | "Description": "xxxxxxx", 181 | "Version": "x", 182 | "Release": "x ", 183 | "BenchmarkDate": "xxxxxxx", 184 | "ReleaseInfo": "xxxxxxx", 185 | "Source": "xxxxxxx", 186 | "Notice": "xxxxxxx", 187 | "Rules": [ 188 | { 189 | "VulnID": "xxxxxxx", 190 | "RuleID": "xxxxxxx", 191 | "StigID": "xxxxxxx", 192 | "Severity": "high | medium | low", 193 | "Cat": "CAT I | CAT II | CAT III", 194 | "Classification": "", 195 | "GroupTitle": "xxxxxxx", 196 | "RuleTitle": "xxxxxxx", 197 | "Description": "xxxxxxx", 198 | "VulnDiscussion": "xxxxxxx", 199 | "FalsePositives": "xxxxxxx", 200 | "FalseNegatives": "xxxxxxx", 201 | "Documentable": "xxxxxxx", 202 | "Mitigations": "xxxxxxx", 203 | "SeverityOverrideGuidance": "xxxxxxx", 204 | "PotentialImpacts": "xxxxxxx", 205 | "ThirdPartyTools": "xxxxxxx", 206 | "MitigationControl": "xxxxxxx", 207 | "Responsibility": "xxxxxxx", 208 | "IAControls": "xxxxxxx", 209 | "CheckText": "xxxxxxx", 210 | "FixText": "xxxxxxx", 211 | "CCI": "xxxxxxx" 212 | } 213 | ] 214 | } 215 | ``` 216 | 217 | 218 | ### Dependencies 219 | The following packages are required for this package: 220 | 221 | | Package Name | Reason | 222 | | :---: | --- | 223 | | xmltodict | This converts the raw XML file to a python dictionary for ease of processing | 224 | 225 | ### Comments, Concerns and Gripes 226 | If you have any comments, concerns and/or gripes, please feel free to submit an issue on the [repository](https://github.com/pkeech/stig_parser). 227 | 228 | 229 | [commit-shield]: https://img.shields.io/github/last-commit/pkeech/stig_parser?style=for-the-badge 230 | [pypi-shield]: https://img.shields.io/pypi/v/stig-parser?style=for-the-badge 231 | [pypi-url]: https://pypi.org/project/stig-parser/ 232 | [workflow-shield]: https://img.shields.io/github/workflow/status/pkeech/stig_parser/Build%20&%20Deploy%20PyPi%20Package?style=for-the-badge 233 | [workflow-url]: https://github.com/pkeech/stig_parser/actions 234 | 235 | 236 | [issues-shield]: https://img.shields.io/github/issues/pkeech/stig_parser?style=for-the-badge 237 | [issues-url]: https://github.com/pkeech/stig_parser/issues 238 | [pr-shield]: https://img.shields.io/github/issues-pr/pkeech/stig_parser?style=for-the-badge 239 | [pr-url]: https://github.com/pkeech/stig_parser/pulls 240 | [python-version-shield]: https://img.shields.io/pypi/pyversions/stig-parser?style=for-the-badge 241 | [license-shield]: https://img.shields.io/github/license/pkeech/stig_parser?style=for-the-badge 242 | [license-url]: https://github.com/pkeech/stig_parser/blob/master/LICENSE 243 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 244 | [linkedin-url]: https://www.linkedin.com/in/peter-keech-b88183a2/ 245 | -------------------------------------------------------------------------------- /docs/examples/U_Docker_Enterprise_2-x_Linux-UNIX_V1R1_STIG.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkeech/stig_parser/e8b5fd92e382148c4f806530f36878fa1723dc1e/docs/examples/U_Docker_Enterprise_2-x_Linux-UNIX_V1R1_STIG.zip -------------------------------------------------------------------------------- /docs/images/STIG_Parser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkeech/stig_parser/e8b5fd92e382148c4f806530f36878fa1723dc1e/docs/images/STIG_Parser.png -------------------------------------------------------------------------------- /docs/mapping.md: -------------------------------------------------------------------------------- 1 | # CKL to STIG Mappings 2 | 3 | ### CKL Layout 4 | 5 | ``` xml 6 | 7 | 8 | 9 | 10 | ... 11 | 12 | 13 | 14 | 15 | ... 16 | 17 | 18 | ... 19 | 20 | 21 | 22 | 23 | ``` 24 | 25 | 26 | 27 | ### Asset Breakdown 28 | 29 | **Layout** 30 | ``` xml 31 | 32 | None 33 | Computing 34 | Test Hostname 35 | 1.1.1.1 36 | 0A:0A:0A:0A:0A 37 | test.hostname.dev 38 | 39 | 40 | 3425 41 | false 42 | 43 | 44 | 45 | ``` 46 | 47 | **Fields** 48 | 49 | | Field | CKL FILE | STIG FILE | VALUES | 50 | | :---: | --- | --- | :---: | 51 | | Role | `````` | - | `None`, `Workstation`, `Member Server`, `Domain Controller` | 52 | | Asset Type | `````` | - | `Computing`, `Non-Computing` | 53 | | Host Name | `````` | - | Free Text | 54 | | Host IP | `````` | - | Free Text | 55 | | Host MAC | `````` | - | Free Text | 56 | | Host FQDN | `````` | - | Free Text | 57 | | Target Comment | `````` | - | Free Text | 58 | | Tech Area | `````` | - | `Application Review`, `Boundary Security`, `CDS Admin Review`, `CDS Technical Review`, `Database Review`, `Domain Name System (DNS)`, `Exchange Server`, `Host Based System Security (HBSS)`, `Internal Network`, `Mobility`, `Releasable Networks (REL)`, `Traditional Security`, `UNIX OS`, `VVOIP Review`, `Web Review`, `Windows OS`, `Other Review` | 59 | | Target Key | `````` | - | Free Text | 60 | | Web or DB | `````` | - | `true`, `false` | 61 | | Web/DB Site | `````` | - | Free Text | 62 | | Web/DB Instance | `````` | - | Free Text | 63 | 64 | ### STIG Breakdown 65 | 66 | **Layout** 67 | ``` xml 68 | 69 | 70 | 71 | 72 | version 73 | 1 74 | 75 | 76 | classification 77 | UNCLASSIFIED 78 | 79 | 80 | customname 81 | 82 | 83 | stigid 84 | Docker_Enterprise_2-x_Linux-UNIX_STIG 85 | 86 | 87 | description 88 | This Security Technical Implementation Guide is published as a tool to improve the security of Department of Defense (DoD) information systems. The requirements are derived from the National Institute of Standards and Technology (NIST) 800-53 and related documents. Comments or proposed revisions to this document should be sent via email to the following address: disa.stig_spt@mail.mil. 89 | 90 | 91 | filename 92 | U_Docker_Enterprise_2-x_Linux-UNIX_STIG_V1R1_Manual-xccdf.xml 93 | 94 | 95 | releaseinfo 96 | Release: 1 Benchmark Date: 19 Jul 2019 97 | 98 | 99 | title 100 | Docker Enterprise 2.x Linux/UNIX Security Technical Implementation Guide 101 | 102 | 103 | uuid 104 | eaab6cca-d77d-4787-868b-766afc44845d 105 | 106 | 107 | notice 108 | terms-of-use 109 | 110 | 111 | source 112 | 113 | 114 | 115 | 116 | Vuln_Num 117 | V-94863 118 | 119 | 120 | Severity 121 | low 122 | 123 | 124 | Group_Title 125 | SRG-APP-000001 126 | 127 | 128 | Rule_ID 129 | SV-104693r1_rule 130 | 131 | 132 | Rule_Ver 133 | DKER-EE-001000 134 | 135 | 136 | Rule_Title 137 | The Docker Enterprise Per User Limit Login Session Control in the Universal Control Plane (UCP) Admin Settings must be set to an organization-defined value for all accounts and/or account types. 138 | 139 | 140 | Vuln_Discuss 141 | The UCP component of Docker Enterprise includes a built-in access authorization mechanism called eNZi which can be integrated with an LDAP server and subsequently configured to limit the number of concurrent sessions to an organization-defined number for all accounts and/or account types. Per-user session control limits are configured with a default of 10. For reference, the per user limit in UCP specifies the maximum number of sessions that any user can have active at any given time. If creating a new session would put a user over this limit then the least recently used session will be deleted. A value of zero disables limiting the number of sessions that users may have. This configuration applies to both the UCP and DTR management consoles. 142 | 143 | 144 | IA_Controls 145 | 146 | 147 | 148 | Check_Content 149 | Check that the "Per User Limit" Login Session Control in the UCP Admin Settings is set according to the values defined in the System Security Plan. 150 | 151 | via UI: 152 | 153 | In the UCP web console, navigate to "Admin Settings" | "Authentication & Authorization" and verify the "Per User Limit" field is set according to the number specified in the System Security Plan. 154 | 155 | via CLI: 156 | 157 | Linux (requires curl and jq): As a Docker EE Admin, execute the following commands from a machine with connectivity to the UCP management console. Replace [ucp_url] with the UCP URL, [ucp_username] with the username of a UCP administrator and [ucp_password] with the password of a UCP administrator. 158 | 159 | AUTHTOKEN=$(curl -sk -d '{"username":"[ucp_username]","password":"[ucp_password]"}' https://[ucp_url]/auth/login | jq -r .auth_token) 160 | curl -sk -H "Authorization: Bearer $AUTHTOKEN" https://[ucp_url]/api/ucp/config-toml|grep per_user_limit 161 | 162 | If the "per_user_limit" entry under the "[auth.sessions]" section in the output is not set according to the value defined in the SSP, this is a finding. 163 | 164 | 165 | Fix_Text 166 | Set the "Per User Limit" Login Session Control in the UCP Admin Settings per the requirements set forth by the System Security Plan (SSP). 167 | 168 | via UI: 169 | 170 | In the UCP web console, navigate to "Admin Settings" | "Authentication & Authorization" and set the "Per User Limit" field according to the requirements of this control. 171 | 172 | via CLI: 173 | 174 | Linux (requires curl and jq): As a Docker EE Admin, execute the following commands on either a UCP Manager node or using a UCP client bundle. Replace [ucp_url] with the UCP URL, [ucp_username] with the username of a UCP administrator and [ucp_password] with the password of a UCP administrator. 175 | 176 | AUTHTOKEN=$(curl -sk -d '{"username":"[ucp_username]","password":"[ucp_password]"}' https://[ucp_url]/auth/login | jq -r .auth_token) 177 | curl -sk -H "Authorization: Bearer $AUTHTOKEN" https://[ucp_url]/api/ucp/config-toml > ucp-config.toml 178 | 179 | Open the "ucp-config.toml" file, set the "per_user_limit" entry under the "[auth.sessions]" section according to the requirements of this control. Save the file. 180 | 181 | Execute the following commands to update UCP with the new configuration: 182 | 183 | curl -sk -H "Authorization: Bearer $AUTHTOKEN" --upload-file ucp-config.toml https://[ucp_url]/api/ucp/config-toml 184 | 185 | 186 | False_Positives 187 | 188 | 189 | 190 | False_Negatives 191 | 192 | 193 | 194 | Documentable 195 | false 196 | 197 | 198 | Mitigations 199 | 200 | 201 | 202 | Potential_Impact 203 | 204 | 205 | 206 | Third_Party_Tools 207 | 208 | 209 | 210 | Mitigation_Control 211 | 212 | 213 | 214 | Responsibility 215 | 216 | 217 | 218 | Security_Override_Guidance 219 | 220 | 221 | 222 | Check_Content_Ref 223 | M 224 | 225 | 226 | Weight 227 | 10.0 228 | 229 | 230 | Class 231 | Unclass 232 | 233 | 234 | STIGRef 235 | Docker Enterprise 2.x Linux/UNIX Security Technical Implementation Guide :: Version 1, Release: 1 Benchmark Date: 19 Jul 2019 236 | 237 | 238 | TargetKey 239 | 3425 240 | 241 | 242 | STIG_UUID 243 | 9dc3babd-6054-4e33-a4e8-a939ec0b2fc8 244 | 245 | 246 | LEGACY_ID 247 | 248 | 249 | 250 | LEGACY_ID 251 | 252 | 253 | 254 | CCI_REF 255 | CCI-000054 256 | 257 | NotAFinding 258 | Test Finding Details. 259 | 260 | This has been set to "Not a Finding" 261 | This are my test comments 262 | 263 | 264 | 265 | ``` 266 | 267 | **Fields (STIG Data)** 268 | 269 | | Field | CKL FILE | STIG FILE | 270 | | :---: | --- | --- | 271 | | Version | ```version1``` | ```1```| 272 | | Classification | ```classificationUNCLASSIFIED```| ??? | 273 | | Custom Name | ```customname``` | ??? | 274 | | STIG ID | ```stigidDocker_Enterprise_2-x_Linux-UNIX_STIG``` | `````` | 275 | | Description | ```descriptionThis Security Technical ...``` | ```This Security Technical Implementation ...>``` | 276 | | Filename | ```filenameU_Docker_Enterprise_2-x_Linux-UNIX_STIG_V1R1_Manual-xccdf.xml``` | From Passed STIG File | 277 | | Release Info | ```releaseinfoRelease: 1 Benchmark Date: 19 Jul 2019``` | ```Release: 1 Benchmark Date: 19 Jul 2019``` | 278 | | Title | ```titleDocker Enterprise 2.x Linux/UNIX Security Technical Implementation Guide``` | ```Docker Enterprise 2.x Linux/UNIX Security Technical Implementation Guide``` | 279 | | UUID | ```uuideaab6cca-d77d-4787-868b-766afc44845d``` | ??? | 280 | | Notice | ```noticeterms-of-use``` | `````` | 281 | 282 | 283 | 284 | **Fields (Vulnerabilities)** 285 | 286 | | Field | CKL FILE | STIG FILE | 287 | | :---: | --- | --- | 288 | | Vulnerability Number | ```Vuln_NumV-94863``` | `````` | 289 | | Severity | ```Severitylow``` | `````` | 290 | | Group Title | ```Group_TitleSRG-APP-000001``` | ```SRG-APP-000001``` | 291 | | Rule ID | ```Rule_IDSV-104693r1_rule``` | `````` | 292 | | Rule Version | ```Rule_VerDKER-EE-001000``` | ```DKER-EE-001000``` | 293 | | Rule Title | ```Rule_TitleThe Docker Enterprise ... ``` | ```The Docker Enterprise ... ``` | 294 | | Vulnerability Discussion | ```Vuln_DiscussThe UCP ...``` | ```<VulnDiscussion>The UCP ...</VulnDiscussion>...``` | 295 | | IA Controls | ```IA_Controls``` | ```...<IAControls></IAControls>``` | 296 | | Check Content | ```Check_ContentCheck that the ...>``` | ```Check that the ...>``` | 297 | | Fix Text | ```Fix_TextSet the "Per ...``` | ```Set the "Per ...>``` | 298 | | False Positives | ```False_Positives``` | ``` ``` | 299 | | False Negatives | ``` ``` | ```...<FalseNegatives></FalseNegatives><...>``` | 300 | | Documentable | ``` ``` | ```...<Documentable></Documentable><...>``` | 301 | | Mitigations | ``` ``` | ``` ``` | 302 | | Potential Impact | ``` ``` | ``` ``` | 303 | | Third Party Tools | ``` ``` | ``` ``` | 304 | | Mitigation Control | ``` ``` | ``` ``` | 305 | | Responsibility | ``` ``` | ``` ``` | 306 | | Security Override Guidance | ``` ``` | ``` ``` | 307 | | Check Content Reference | ``` ``` | ``` ``` | 308 | | Weight | ``` ``` | ``` ``` | 309 | | Classification | ``` ``` | ``` ``` | 310 | | STIG Reference | ``` ``` | ``` ``` | 311 | | Target Key | ``` ``` | ``` ``` | 312 | | STIG UUID | ``` ``` | ``` ``` | 313 | | Legacy ID | ``` ``` | ``` ``` | 314 | | Legacy ID | ``` ``` | ``` ``` | 315 | | CCI Reference | ``` ``` | ``` ``` | 316 | | Status | ``` ``` | ``` ``` | 317 | | Finding Details | ``` ``` | ``` ``` | 318 | | Comments | ``` ``` | ``` ``` | 319 | | Severity Override | ``` ``` | ``` ``` | 320 | | Severity Justification | ``` ``` | ``` ``` | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==21.2.0 2 | coverage==5.5 3 | iniconfig==1.1.1 4 | packaging==21.0 5 | pluggy==0.13.1 6 | py==1.10.0 7 | pyparsing==2.4.7 8 | pytest==6.2.4 9 | pytest-cov==2.12.1 10 | toml==0.10.2 11 | xmltodict==0.12.0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ## IMPORT REQUIRED DOCUMENTS 2 | import setuptools, subprocess, os 3 | 4 | ## PARSE README TO LONG DESCRIPTION 5 | with open("README.md", "r") as fh: 6 | long_description = fh.read() 7 | 8 | ## QUERY GITHUB FOR TAG TO GENERATE VERSION 9 | new_version = ( 10 | subprocess.run(["git", "describe", "--tags"], cwd="/home/runner/work/stig_parser/stig_parser", stdout=subprocess.PIPE) 11 | .stdout.decode("utf-8") 12 | .strip() 13 | ) 14 | 15 | ## DEFINE PACKAGE VERSION 16 | assert "." in new_version 17 | assert os.path.isfile("src/stig_parser/version.py") 18 | with open("src/stig_parser/VERSION", "w", encoding="utf-8") as fh: 19 | fh.write(f"{new_version}\n") 20 | 21 | ## DEFINE PACKAGE 22 | setuptools.setup( 23 | name='stig_parser', 24 | version=new_version, 25 | license='MIT', 26 | author="Peter Keech", 27 | author_email="peter.a.keech@gmail.com", 28 | description="A Python module to parse DISA STIG (XCCDF) Files", 29 | long_description=long_description, 30 | long_description_content_type="text/markdown", 31 | url="https://github.com/pkeech/stig_parser", 32 | packages=setuptools.find_packages('src'), 33 | package_dir={'': 'src'}, 34 | install_requires=[ 35 | 'xmltodict' 36 | ], 37 | classifiers=[ 38 | "Programming Language :: Python :: 3.8", 39 | "Programming Language :: Python :: 3.9", 40 | "License :: OSI Approved :: MIT License", 41 | "Operating System :: OS Independent", 42 | ], 43 | python_requires='>=3.8' 44 | ) 45 | -------------------------------------------------------------------------------- /src/stig_parser/__init__.py: -------------------------------------------------------------------------------- 1 | ## EXPOSE FUNCTIONS 2 | from .stig_parser import convert_stig, convert_xccdf, generate_stig_json, generate_ckl, generate_ckl_file -------------------------------------------------------------------------------- /src/stig_parser/stig_parser.py: -------------------------------------------------------------------------------- 1 | ## ============================== 2 | ## ===== STIG PARSER MODULE ===== 3 | ## ============================== 4 | 5 | ## Created By : Peter Keech 6 | ## Email : peter.a.keech@gmail.com 7 | ## Version : 1.1.0 8 | ## Description : STIG-Parser 1.1.0 Module 9 | ## Requirements: xmltodict 10 | 11 | ## IMPORT REQUIRED EXTERNAL MODULES 12 | import os, xmltodict, zipfile, json, uuid 13 | import xml.etree.cElementTree as ET 14 | 15 | ## ----------------------------- 16 | ## ----- PRIVATE FUNCTIONS ----- 17 | ## ----------------------------- 18 | 19 | ## FUNCTION: GENERATE STIG INFO ELEMENTS 20 | def generate_stig_info(PARENT, KEY, VALUE): 21 | SI_DATA = ET.SubElement(PARENT, 'SI_DATA') 22 | SID_NAME = ET.SubElement(SI_DATA, 'SID_NAME').text = KEY 23 | 24 | if VALUE != None: 25 | SID_DATA = ET.SubElement(SI_DATA, 'SID_DATA').text = VALUE 26 | 27 | ## FUNCTION: GENERATE VULN ELEMENTS 28 | def generate_vuln(PARENT, KEY, VALUE): 29 | STIG_DATA = ET.SubElement(PARENT, 'STIG_DATA') 30 | VULN_ATTRIBUTE = ET.SubElement(STIG_DATA, 'VULN_ATTRIBUTE').text = KEY 31 | ATTRIBUTE_DATA = ET.SubElement(STIG_DATA, 'ATTRIBUTE_DATA').text = VALUE 32 | 33 | ## FUNCTION: FIND BETWEEN TWO POINTS IN A STRING 34 | ## NEEDED DUE TO XML TAGS BEING CONTAINED WITHIN DESCRIPTION TEXT 35 | def find_between( s, first, last ): 36 | try: 37 | start = s.index( first ) + len( first ) 38 | end = s.index( last, start ) 39 | return s[start:end] 40 | except ValueError: 41 | return "" 42 | 43 | ## FUNCTION: PRETTY PRINT ELEMENTTREE OUTPUT 44 | ## THIS IS NOT NEEDED AS OF NOW 45 | ## TO USE, CALL FUNCTION 46 | ## PrettyPrint(ElementTree_Object) 47 | def PrettyPrint(elem, level=0): 48 | i = "\n" + level*" " 49 | j = "\n" + (level-1)*" " 50 | if len(elem): 51 | if not elem.text or not elem.text.strip(): 52 | elem.text = i + " " 53 | if not elem.tail or not elem.tail.strip(): 54 | elem.tail = i 55 | for subelem in elem: 56 | PrettyPrint(subelem, level+1) 57 | if not elem.tail or not elem.tail.strip(): 58 | elem.tail = j 59 | else: 60 | if level and (not elem.tail or not elem.tail.strip()): 61 | elem.tail = j 62 | return elem 63 | 64 | ## FUNCTION: HANDLE STIG RULES WITH MULTIPLE CCI ENTRIES 65 | ## PROVIDED BY: gregelin 66 | 67 | def get_cci_list(IDENT): 68 | ## HANDLE MULTIPLE IDENT ENTRIES (CCI) 69 | if len(IDENT) == 2: 70 | IDENT = IDENT['#text'] 71 | else: 72 | ## DEFINE EMPTY RESULTS 73 | RESULTS = "" 74 | 75 | ## LOOP THROUGH ALL CCI NUMBERS 76 | for RESULT in IDENT: 77 | RESULTS += RESULT['#text'] + "," 78 | 79 | ## REMOVE LAST ',' 80 | IDENT = RESULTS.rstrip(RESULTS[-1]) 81 | 82 | ## RETURN RESULTS 83 | return IDENT 84 | 85 | ## ---------------------------- 86 | ## ----- PUBLIC FUNCTIONS ----- 87 | ## ---------------------------- 88 | 89 | ## FUNCTION: CONVERT RAW XCCDF (XML) TO JSON 90 | def convert_xccdf(RAW): 91 | 92 | ## CONVERT XML TO PYTHON DICTIONARY 93 | CONTENT_DICT = xmltodict.parse(RAW, dict_constructor=dict) 94 | 95 | ## HANDLE NEW VS. OLD VERSION OF STIG SCHEMA (ISSUE #3) 96 | if isinstance(CONTENT_DICT['Benchmark']['plain-text'], list): 97 | ## NEW VERSION 98 | RAW_VERSION = CONTENT_DICT['Benchmark']['plain-text'][0]['#text'] 99 | else: 100 | ## OLD VERSION 101 | RAW_VERSION = CONTENT_DICT['Benchmark']['plain-text']['#text'] 102 | 103 | ## SAVE RELEASE INFO 104 | RELEASE_INFO = RAW_VERSION 105 | 106 | ## FORMAT RELEASE INFO FOR SPECIFIC DATA FIELDS 107 | RAW_VERSION = RAW_VERSION.split('Benchmark Date: ') 108 | BENCH_DATE = RAW_VERSION[1] 109 | REL = RAW_VERSION[0].replace('Release: ','') 110 | 111 | ## CREATE RETURNED STIG JSON 112 | ## TODO: ADD STIG FILENAME 113 | JSON_RESULTS = { 114 | "Title": CONTENT_DICT['Benchmark']['title'], 115 | "Description": CONTENT_DICT['Benchmark']['description'], 116 | "Version": CONTENT_DICT['Benchmark']['version'], 117 | "Release": REL, 118 | "BenchmarkDate": BENCH_DATE, 119 | "ReleaseInfo": RELEASE_INFO, 120 | "Source": CONTENT_DICT['Benchmark']['reference']['dc:source'], 121 | "Notice": CONTENT_DICT['Benchmark']['notice']['@id'] 122 | } 123 | 124 | ## GENERATE EMPTY ARRAY 125 | STIGS = [] 126 | 127 | ## LOOP THROUGH STIGS 128 | for STIG in CONTENT_DICT['Benchmark']['Group']: 129 | 130 | ## PARSE IDENT 131 | IDENT = STIG['Rule']['ident'] 132 | 133 | ## HANDLE ARRAY CASE 134 | IDENT_LIST = "" 135 | if type(IDENT) == list: 136 | for IDENT_ITEM in IDENT: 137 | IDENT_LIST += get_cci_list(IDENT_ITEM) + "," 138 | else: 139 | IDENT_LIST += get_cci_list(IDENT) + "," 140 | ## REMOVE LAST ',' 141 | IDENT = IDENT_LIST.rstrip(IDENT_LIST[-1]) 142 | 143 | ## FORMAT SEVERITY 144 | ## TODO: DOCUMENT WHY THIS IS HAPPENING. STIGVIEWER CONVERTS LOW/MED/HIGH TO CAT III/CAT II/CAT I 145 | if STIG['Rule']['@severity'] == "high": 146 | CAT = "CAT I" 147 | elif STIG['Rule']['@severity'] == "medium": 148 | CAT = "CAT II" 149 | elif STIG['Rule']['@severity'] == "low": 150 | CAT = "CAT III" 151 | else: 152 | CAT = "" 153 | 154 | ## DEFINE RULE STRUCTURE 155 | oSTIG = { 156 | 'VulnID': STIG['@id'], 157 | 'RuleID': STIG['Rule']['@id'], 158 | 'StigID': STIG['Rule']['version'], 159 | 'Severity': STIG['Rule']['@severity'], 160 | 'Cat': CAT, 161 | ## TODO: DETERMINE STIG RULE CLASSIFICATION 162 | 'Classification': "", 163 | 'GroupTitle': STIG['title'], 164 | 'RuleTitle': STIG['Rule']['title'], 165 | 'Description': STIG['Rule']['description'], 166 | 'VulnDiscussion': find_between(STIG['Rule']['description'], "", ""), 167 | 'FalsePositives': find_between(STIG['Rule']['description'], "", ""), 168 | 'FalseNegatives': find_between(STIG['Rule']['description'], "", ""), 169 | 'Documentable': find_between(STIG['Rule']['description'], "", ""), 170 | 'Mitigations': find_between(STIG['Rule']['description'], "", ""), 171 | 'SeverityOverrideGuidance': find_between(STIG['Rule']['description'], "", ""), 172 | 'PotentialImpacts': find_between(STIG['Rule']['description'], "", ""), 173 | 'ThirdPartyTools': find_between(STIG['Rule']['description'], "", ""), 174 | 'MitigationControl': find_between(STIG['Rule']['description'], "", ""), 175 | 'Responsibility': find_between(STIG['Rule']['description'], "", ""), 176 | 'IAControls': find_between(STIG['Rule']['description'], "", ""), 177 | 'CheckText': STIG['Rule']['check']['check-content'], 178 | 'FixText': STIG['Rule']['fixtext']['#text'], 179 | 'CCI': IDENT, 180 | } 181 | 182 | ## ADD TO ARRAY 183 | STIGS.append(oSTIG) 184 | 185 | ## ADD STIGS TO JSON OBJECT 186 | JSON_RESULTS['Rules'] = STIGS 187 | 188 | ## RETURN RESULTS 189 | return JSON_RESULTS 190 | 191 | ## FUNCTION: EXTRACT STIG FROM ZIP FILE 192 | def extract_stig(FILENAME): 193 | ## OPEN XML FILE FROM ZIP FILE AND OBTAIN LIST OF FILES 194 | ZIP = zipfile.ZipFile(FILENAME) 195 | FILES = ZIP.namelist() 196 | 197 | ## FIND MANUAL STIG FILE 198 | for FILE in FILES: 199 | if FILE.endswith('_Manual-xccdf.xml'): 200 | ## HANDLE MACOS 201 | if not FILE.startswith('__MACOS'): 202 | ## DETERMINE FILE NAME 203 | STIG_FILENAME = FILE 204 | break 205 | 206 | ## ENSURE FILE IS FOUND 207 | assert STIG_FILENAME is not None, 'Manual STIG File was NOT FOUND' 208 | 209 | ## READ STIG FILE 210 | RAW_FILE = ZIP.read(STIG_FILENAME) 211 | 212 | ## RETURN RAW STIG 213 | return RAW_FILE 214 | 215 | ## FUNCTION: CONVERT STIG (ZIP) TO JSON FILE 216 | def convert_stig(FILENAME): 217 | ## EXTRACT STIG FROM ZIP FILE 218 | RAW_STIG = extract_stig(FILENAME) 219 | 220 | ## CONVERT TO JSON 221 | RESULTS = convert_xccdf(RAW_STIG) 222 | 223 | ## RETURN JSON STIG 224 | return RESULTS 225 | 226 | ## FUNCTION: CREATE STIG JSON FILE 227 | def generate_stig_json(JSON_STIG, EXPORT_FILE): 228 | ## CREATE JSON FILE AND SAVE 229 | with open(EXPORT_FILE, 'w') as FILE: 230 | json.dump(JSON_STIG, FILE, indent=4) 231 | 232 | ## FUNCTION: GENERATE BLANK CHECKLIST 233 | def generate_ckl(FILENAME, CHECKLIST_INFO): 234 | ## EXTRACT STIG FROM ZIP FILE 235 | RAW_STIG = extract_stig(FILENAME) 236 | 237 | ## CONVERT TO JSON 238 | JSON_STIG = convert_xccdf(RAW_STIG) 239 | 240 | ## GENERATE STIG_UUID 241 | STIG_UUID = uuid.uuid4() 242 | 243 | ## GENERATE XML STRUCTURE 244 | ROOT = ET.Element('CHECKLIST') 245 | 246 | ## ADD STIG VIEWER COMMENT 247 | COMMENT = ET.Comment('DISA STIG Viewer :: 2.14') 248 | ROOT.append(COMMENT) 249 | 250 | ## ADD ASSET ELEMENT 251 | ASSET = ET.SubElement(ROOT, 'ASSET') 252 | 253 | ## GENERATE ASSET ELEMENTS 254 | ET.SubElement(ASSET, 'ROLE').text = CHECKLIST_INFO.get('ROLE') ## ASSET ROLE 255 | ET.SubElement(ASSET, 'ASSET_TYPE').text = CHECKLIST_INFO.get('ASSET_TYPE') ## ASSET TYPE 256 | ET.SubElement(ASSET, 'HOST_NAME').text = CHECKLIST_INFO.get('HOST_NAME') ## ASSET HOSTNAME 257 | ET.SubElement(ASSET, 'HOST_IP').text = CHECKLIST_INFO.get('HOST_IP') ## ASSET IP 258 | ET.SubElement(ASSET, 'HOST_MAC').text = CHECKLIST_INFO.get('HOST_MAC') ## ASSET MAC ADDRESS 259 | ET.SubElement(ASSET, 'HOST_FQDN').text = CHECKLIST_INFO.get('HOST_FQDN') ## ASSET FQDN 260 | ET.SubElement(ASSET, 'TARGET_COMMENT').text = CHECKLIST_INFO.get('TARGET_COMMENT') ## ASSET TARGET COMMENT 261 | ET.SubElement(ASSET, 'TECH_AREA').text = CHECKLIST_INFO.get('TECH_AREA') ## ASSET TECH AREA 262 | ET.SubElement(ASSET, 'TARGET_KEY').text = CHECKLIST_INFO.get('TARGET_KEY') ## ASSET TARGET KEY 263 | ET.SubElement(ASSET, 'WEB_OR_DATABASE').text = CHECKLIST_INFO.get('WEB_OR_DATABASE') ## ASSET WEB OR DATABASE 264 | ET.SubElement(ASSET, 'WEB_DB_SITE').text = CHECKLIST_INFO.get('WEB_DB_SITE') ## ASSET DB SITE 265 | ET.SubElement(ASSET, 'WEB_DB_INSTANCE').text = CHECKLIST_INFO.get('WEB_DB_INSTANCE') ## ASSET DB INSTANCE 266 | 267 | ## GENERATE STIG, ISTIG, STIG_INFO AND VULN ELEMENTS 268 | STIGS = ET.SubElement(ROOT, 'STIGS') 269 | ISTIG = ET.SubElement(STIGS, 'iSTIG') 270 | STIG_INFO = ET.SubElement(ISTIG, 'STIG_INFO') 271 | 272 | ## FORMAT STIG_ID FIELD 273 | STIG_ID = JSON_STIG['Title'].replace(' Security Technical Implementation Guide', '').replace(' ', '_').replace('.', '-').replace('/', '-') 274 | 275 | ## GENERATE STIG_INFO FIELDS 276 | ## TODO: CLASSIFICATION, CUSTOM NAME, FILENAME 277 | generate_stig_info(STIG_INFO, 'version', JSON_STIG['Version']) ## STIG VERSION 278 | generate_stig_info(STIG_INFO, 'classification', 'UNCLASSIFIED') ## STIG CLASSIFICATION 279 | generate_stig_info(STIG_INFO, 'customname', None) ## STIG CUSTOM NAME 280 | generate_stig_info(STIG_INFO, 'stigid', STIG_ID) ## STIG ID 281 | generate_stig_info(STIG_INFO, 'description', JSON_STIG['Description']) ## STIG DESCRIPTION 282 | generate_stig_info(STIG_INFO, 'filename', 'TEMP FILENAME') ## STIG FILENAME 283 | generate_stig_info(STIG_INFO, 'releaseinfo', JSON_STIG['ReleaseInfo']) ## STIG RELEASE INFO 284 | generate_stig_info(STIG_INFO, 'title', JSON_STIG['Title']) ## STIG TITLE 285 | generate_stig_info(STIG_INFO, 'uuid', str(uuid.uuid4())) ## STIG UUID 286 | generate_stig_info(STIG_INFO, 'notice', JSON_STIG['Notice']) ## STIG NOTICE 287 | generate_stig_info(STIG_INFO, 'source', JSON_STIG['Source']) ## STIG SOURCE 288 | 289 | ## GENERATE VULNERABILITIES 290 | for RULE in JSON_STIG['Rules']: 291 | 292 | ## CREATE VULN ELEMENT 293 | VULN = ET.SubElement(ISTIG, 'VULN') 294 | 295 | ## FORMAT RULE OUTPUTS 296 | STIG_REF = JSON_STIG['Title'] + " :: " + JSON_STIG['Version'] 297 | 298 | ## CREATE RULES 299 | ## TODO: Group_Title, Check_Content_Ref, Weight, Classification, TargetKey, Legacy_ID 300 | generate_vuln(VULN, 'Vuln_Num', RULE['VulnID']) ## VULN ID 301 | generate_vuln(VULN, 'Severity', RULE['Severity']) ## VULN SEVERITY 302 | generate_vuln(VULN, 'Group_Title', 'SRG-APP-000001') ## VULNERABILITY GROUP TITLE 303 | generate_vuln(VULN, 'Rule_ID', RULE['RuleID']) ## VULNERABILITY RULE ID 304 | generate_vuln(VULN, 'Rule_Ver', RULE['StigID']) ## VULNERABILITY RULE VERSION 305 | generate_vuln(VULN, 'Rule_Title', RULE['RuleTitle']) ## VULNERABILITY RULE TITLE 306 | generate_vuln(VULN, 'Vuln_Discuss', RULE['VulnDiscussion']) ## VULNERABILITY DISCUSSION 307 | generate_vuln(VULN, 'IA_Controls', RULE['IAControls']) ## VULNERABILITY IA CONTROL 308 | generate_vuln(VULN, 'Check_Content', RULE['CheckText']) ## VULNERABILITY CHECK CONTENT 309 | generate_vuln(VULN, 'Fix_Text', RULE['FixText']) ## VULNERABILITY FIX TEXT 310 | generate_vuln(VULN, 'False_Positives', RULE['FalsePositives']) ## VULNERABILITY FALSE POSITIVIES 311 | generate_vuln(VULN, 'False_Negatives', RULE['FalseNegatives']) ## VULNERABILITY FALSE NEGATIVES 312 | generate_vuln(VULN, 'Documentable', RULE['Documentable']) ## VULNERABILITY DOCUMENTABLE 313 | generate_vuln(VULN, 'Mitigations', RULE['Mitigations']) ## VULNERABILITY MITIGATION 314 | generate_vuln(VULN, 'Potential_Impact', RULE['PotentialImpacts']) ## VULNERABILITY POTENTIAL IMPACT 315 | generate_vuln(VULN, 'Third_Party_Tools', RULE['ThirdPartyTools']) ## VULNERABILITY THIRD PARTY TOOLS 316 | generate_vuln(VULN, 'Mitigation_Control', RULE['MitigationControl']) ## VULNERABILITY MITIGATION CONTROLS 317 | generate_vuln(VULN, 'Responsibility', RULE['Responsibility']) ## VULNERABILITY RESPONSIBILITY 318 | generate_vuln(VULN, 'Security_Override_Guidance', RULE['SeverityOverrideGuidance']) ## VULNERABILITY SECURITY OVERRIDE GUIDE 319 | generate_vuln(VULN, 'Check_Content_Ref', 'M') ## VULNERABILITY CHECK CONTENT REFERENCE 320 | generate_vuln(VULN, 'Weight', '10.0') ## VULNERABILITY WEIGHT 321 | generate_vuln(VULN, 'Class', 'Unclass') ## VULNERABILITY CLASSIFICATION 322 | generate_vuln(VULN, 'STIGRef', STIG_REF) ## VULNERABILITY STIG REFERENCE 323 | generate_vuln(VULN, 'TargetKey', '3425') ## VULNERABILITY TARGET KEY 324 | generate_vuln(VULN, 'STIG_UUID', str(STIG_UUID)) ## VULNERABILITY UUID 325 | generate_vuln(VULN, 'LEGACY_ID', '') ## VULNERABILITY LEGACY ID 326 | generate_vuln(VULN, 'CCI_REF', RULE['CCI']) ## VULNERABILITY CCI REFERENCE 327 | 328 | ## RULE STATUS ELEMENTS 329 | ET.SubElement(VULN, "STATUS").text = "Not_Reviewed" ## VULNERABILITY FINDING STATUS 330 | ET.SubElement(VULN, 'FINDING_DETAILS').text = "" ## VULNERABILITY FINDING DETAILS 331 | ET.SubElement(VULN, 'COMMENTS').text = "" ## VULNERABILITY COMMENTS 332 | ET.SubElement(VULN, 'SEVERITY_OVERRIDE').text = "" ## VULNERABILITY SEVERITY OVERRIDE 333 | ET.SubElement(VULN, 'SEVERITY_JUSTIFICATION').text = "" ## VULNERABILITY SEVERITY JUSTIFICATION 334 | 335 | ## FORMAT EXPORT 336 | OUTPUT = ET.tostring(ROOT, encoding='UTF-8', xml_declaration=True, short_empty_elements=False) 337 | 338 | ## RETURN CHECKLIST 339 | return OUTPUT 340 | 341 | ## FUNCTION: GENERATE CHECKLIST FILE (CKL) 342 | def generate_ckl_file(CKL, FILENAME): 343 | ## SAVE XML OBJECT TO FILE 344 | with open(FILENAME, "wb") as FILE: 345 | FILE.write(CKL) -------------------------------------------------------------------------------- /src/stig_parser/version.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def string(): 5 | try: 6 | with open(os.path.dirname(__file__) + "/VERSION", "r", encoding="utf-8") as fh: 7 | version = fh.read().strip() 8 | if version: 9 | return version 10 | except: 11 | pass 12 | return "unknown (git checkout)" -------------------------------------------------------------------------------- /src/stig_parser/xccdf-1.1.4.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | 16 | This schema defines the eXtensible Configuration Checklist 17 | Description Format (XCCDF), a data format for defining 18 | security benchmarks and checklists, and for recording 19 | the results of applying such benchmarks. 20 | For more information, consult the specification 21 | document, "Specification for the Extensible Configuration 22 | Checklist Description Format", version 1.1 revision 4. 23 | 24 | This schema was developed by Neal Ziring, with ideas and 25 | assistance from David Waltermire. The following helpful 26 | individuals also contributed ideas to the definition 27 | of this schema: David Proulx, Andrew Buttner, Ryan Wilson, 28 | Matthew Kerr, Stephen Quinn. Ian Crawford found numerous 29 | discrepancies between this schema and the spec document. 30 | Peter Mell and his colleagues also made many suggestions. 31 | 1.1.4.3 32 | 33 | 34 | XCCDF Language 35 | Neal Ziring 36 | 1.1.4.3 37 | 2007-10-10 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | Import the XML namespace because this schema uses 47 | the xml:lang and xml:base attributes. 48 | 49 | 50 | 51 | 52 | 53 | 55 | 56 | 57 | Import the simple Dublin Core namespace because this 58 | schema uses it for benchmark metadata and for references. 59 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | Import the CIS platform schema, which we use for 69 | describing target IT platforms in the Benchmark. The 70 | CIS platform schema was designed by David Waltermire. 71 | Use of the CIS platform schema in XCCDF benchmarks is 72 | deprecated. The CIS platform schema is included only for 73 | backward compatibility with XCCDF 1.0. Use CPE 2.0 instead. 74 | 75 | 76 | 77 | 78 | 79 | 81 | 82 | 83 | Import the XCCDF-P platform schema, which we use 84 | for describing target IT platforms in the Benchmark. 85 | The CIS platform schema was designed by Neal Ziring 86 | using ideas and concepts developed by DISA, CIS, and 87 | others. Use of XCCDF-P platform specification in 88 | XCCDF benchmarks is deprecated. XCCDF-P is included 89 | in this schema only for backward compatibility with 90 | version 1.1 and 1.1.2. Use CPE 2.0 instead. 91 | 92 | 93 | 94 | 95 | 96 | 98 | 99 | 100 | Import the Common Platform Enumeration XML schema, 101 | which can be used for naming and describing target 102 | IT platforms in the Benchmark. Every CPE name is 103 | a URI that begins with "cpe:". For more details see 104 | "Common Platform Enumeration (CPE) - Specification", 105 | by Buttner, Wittbold, and Ziring (2007). Use of CPE 1.0 106 | definitions in XCCDF benchmarks is deprecated. CPE 1.0 107 | is included in this schema only for backward compatibility 108 | with XCCDF 1.1.2 and 1.1.3. CPE 2.0 Names should be used 109 | for simple (unitary) platforms, and CPE 2.0 Language 110 | definitions used for complex platforms. 111 | 112 | 113 | 114 | 115 | 116 | 118 | 119 | 120 | Import the Common Platform Enumeration language schema, 121 | which can be used for defining compound CPE tests for 122 | complex IT platforms in the Benchmark. For more info 123 | see "Common Platform Enumeration (CPE) - Specification", 124 | Version 2.0" by Buttner and Ziring (Sept 2007). 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | The benchmark tag is the top level element representing a 136 | complete security checklist, including descriptive text, 137 | metadata, test items, and test results. A Benchmark may 138 | be signed with a XML-Signature. 139 | 140 | 141 | 142 | 143 | 145 | 147 | 149 | 151 | 153 | 155 | 157 | 159 | 161 | 162 | 163 | 165 | 166 | 168 | 169 | 171 | 172 | 174 | 175 | 177 | 179 | 181 | 183 | 185 | 187 | 188 | 189 | 190 | 191 | 193 | 195 | 196 | 197 | 198 | 199 | 201 | 203 | 205 | 206 | 207 | 208 | 209 | 210 | Legal notices must have unique id values. 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | Items must have unique id values, and also they 219 | must not collide 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | Model system attributes must be unique. 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | Value item ids are special keys, need this for 236 | the valueIdKeyRef and valueExtIdKeyRef keyrefs below. 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | Group item ids are special keys, need this for 245 | the groupIdKeyRef keyref below. 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | Rule items have a unique key, we need 254 | this for the ruleIdKeyRef keyref below. 255 | (Rule key refs are used by rule-results.) 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | Group and Rule item ids are special keys, we 264 | need this for the requiresIdKeyRef keyref below. 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | Plaintext objects and Value objects each have 273 | and id, and they must be unique and not overlap. 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | Profile objects have a unique id, it is used 282 | for extension, too. 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | An extends attribute on Value object 291 | must reference an existing Value. 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | An extends attribute on Group object 300 | must reference an existing Group. 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | An extends attribute on Rule object 309 | must reference an existing Rule. 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | An extends attribute on Profile object 318 | must reference an existing Profile. 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | Check-export elements must reference existing values. 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | Sub elements must reference existing Value or 335 | plain-text ids. 336 | 337 | 338 | 339 | 340 | 341 | 343 | 344 | The rule-result element idref must refer to an 345 | existing Rule. 346 | 347 | 348 | 349 | 350 | 351 | 353 | 354 | The requires a profile element in a TestResult 355 | element to refer to an existing Profile 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | Data type for legal notice element that has text 367 | content and a unique id attribute. 368 | 369 | 370 | 371 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | Data type for a reusable text block, with an 384 | unique id attribute. 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | Data type for a reference citation, an href URL attribute 398 | (optional), with content of text or simple Dublin Core 399 | elements. Elements of this type can also have an override 400 | attribute to help manage inheritance. 401 | 402 | 403 | 404 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | XML-Signature over the Benchmark; note that this will 416 | always be an 'enveloped' signature, so the single 417 | element child of this element should be dsig:Signature. 418 | 419 | 420 | 421 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | Metadata for the Benchmark, should be Dublin Core 430 | or some other well-specified and accepted metadata 431 | format. If Dublin Core, then it will be a sequence 432 | of simple Dublin Core elements. The NIST checklist 433 | metadata should also be supported, although the 434 | specification document is still in draft in NIST 435 | special pub 800-70. 436 | 437 | 438 | 439 | 440 | 442 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | The acceptance status of an Item with an optional date attribute 456 | that signifies the date of the status change. 457 | 458 | 459 | 460 | 461 | 462 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | A suggested scoring model for a Benchmark, also 473 | encapsulating any parameters needed by the model. 474 | Every model is designated with a URI, which 475 | appears here as the system attribute. 476 | 477 | 478 | 479 | 480 | 482 | 483 | 485 | 486 | 487 | 488 | 489 | Parameter names must be unique. 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | Type for a scoring model parameter: a name and a 500 | string value. 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | The possible status codes for an Benchmark or Item to be 515 | inherited from the parent element if it is not defined. 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | Type for a version number, with a timestamp attribute 531 | for when the version was made. 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | Type for a string with an xml:lang attribute. 549 | Elements of this type can also have an override 550 | attribute to help manage inheritance. 551 | 552 | 553 | 554 | 555 | 556 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | Type for a string with XHTML elements and xml:lang attribute. 566 | Elements of this type can also have an override 567 | attribute to help manage inheritance. 568 | 569 | 570 | 571 | 574 | 575 | 576 | 578 | 579 | 580 | 581 | 582 | 583 | Type for a string with embedded Value substitutions and 584 | XHTML elements, and an xml:lang attribute. Elements of 585 | this type can also have an override attribute to help 586 | manage inheritance. [Note: this definition is rather 587 | loose, it allows anything whatsoever to occur insides 588 | XHTML tags inside here. Further, constraints of the XHTML 589 | schema do not get checked! It might be possible to solve 590 | this using XML Schema redefinition features.] 591 | 592 | 593 | 594 | 595 | 597 | 598 | 599 | 601 | 602 | 603 | 604 | 605 | 606 | Type for a string with embedded Value substitutions and 607 | XHTML elements, an xml:lang attribute, and a profile-note tag. 608 | 609 | 610 | 611 | 612 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | Type for a string with embedded Value substitutions 623 | and XHTML elements, and an xml:lang attribute. 624 | Elements of this type can also have an override 625 | attribute to help manage inheritance. 626 | 627 | 628 | 629 | 631 | 632 | 633 | 635 | 636 | 637 | 638 | 639 | 640 | Data type for elements that have no content, 641 | just a mandatory id reference. 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | Data type for elements that have no content, 651 | just a space-separated list of id references. 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | Data type for elements that have no content, 661 | just a mandatory id reference, but also have 662 | an override attribute for controlling inheritance. 663 | 664 | 665 | 666 | 667 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | Data type for elements that have no content, 677 | just a mandatory URI as an id. (This is mainly for the 678 | platform element, which uses CPE URIs and CPE Language 679 | identifers used as platform identifiers.) When referring 680 | to a local CPE Language identifier, the URL should use 681 | local reference syntax: "#cpeid1". 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | Data type for elements that have no content, 691 | just a mandatory URI reference, but also have 692 | an override attribute for controlling inheritance. 693 | 694 | 695 | 696 | 697 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | Type element type imposes constraints shared by all 711 | Groups, Rules and Values. The itemType is abstract, so 712 | the element Item can never appear in a valid XCCDF document. 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | This abstract item type represents the basic data shared by all 721 | Groups, Rules and Values 722 | 723 | 724 | 725 | 727 | 729 | 731 | 733 | 735 | 737 | 739 | 740 | 741 | 743 | 745 | 746 | 748 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | This abstract item type represents the basic data shared by all 763 | Groups and Rules. It extends the itemType given above. 764 | 765 | 766 | 767 | 768 | 769 | 772 | 775 | 777 | 779 | 780 | 782 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | Data type for the Group element that represents a grouping of 797 | Groups, Rules and Values. 798 | 799 | 800 | 801 | 802 | 803 | 805 | 806 | 807 | 808 | 809 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | The Rule element contains the description for 824 | a single item of guidance or constraint. Rules 825 | form the basis for testing a target platform for 826 | compliance with a benchmark, for scoring, and 827 | for conveying descriptive prose, identifiers, 828 | references, and remediation information. 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | Data type for the Rule element that represents a 846 | specific benchmark test. 847 | 848 | 849 | 850 | 851 | 852 | 854 | 856 | 859 | 861 | 863 | 864 | 866 | 868 | 869 | 871 | 872 | 874 | 876 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | Type for a long-term globally meaningful identifier, 890 | consisting of a string (ID) and a URI of the naming 891 | scheme within which the name is meaningful. 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | Data type for the warning element under the Rule 905 | object, a rich text string with substitutions 906 | allowed, plus an attribute for the kind of warning. 907 | 908 | 909 | 910 | 911 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | Allowed warning category keywords for the warning 922 | element. The allowed categories are: 923 | general=broad or general-purpose warning (default 924 | for compatibility for XCCDF 1.0) 925 | functionality=warning about possible impacts to 926 | functionality or operational features 927 | performance=warning about changes to target 928 | system performance or throughput 929 | hardware=warning about hardware restrictions or 930 | possible impacts to hardware 931 | legal=warning about legal implications 932 | regulatory=warning about regulatory obligations 933 | or compliance implications 934 | management=warning about impacts to the mgmt 935 | or administration of the target system 936 | audit=warning about impacts to audit or logging 937 | dependency=warning about dependencies between 938 | this Rule and other parts of the target 939 | system, or version dependencies. 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | 959 | Data type for the fixText element that represents 960 | a rich text string, with substitutions allowed, and 961 | a series of attributes that qualify the fix. 962 | 963 | 964 | 965 | 966 | 968 | 970 | 972 | 974 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | Type for a string with embedded Value and instance 984 | substitutions and an optional platform id ref attribute, but 985 | no embedded XHTML markup. 986 | The platform attribute should refer to a platform-definition 987 | element in the platform-definitions child of the Benchmark. 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 997 | 999 | 1001 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | Allowed strategy keyword values for a Rule fix or 1011 | fixtext. The allowed values are: 1012 | unknown= strategy not defined (default for forward 1013 | compatibility for XCCDF 1.0) 1014 | configure=adjust target config or settings 1015 | patch=apply a patch, hotfix, or update 1016 | policy=remediation by changing policies/procedures 1017 | disable=turn off or deinstall something 1018 | enable=turn on or install something 1019 | restrict=adjust permissions or ACLs 1020 | update=install upgrade or update the system 1021 | combination=combo of two or more of the above 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | Allowed rating values values for a Rule fix 1041 | or fixtext: disruption, complexity, and maybe overhead. 1042 | The possible values are: 1043 | unknown= rating unknown or impossible to estimate 1044 | (default for forward compatibility for XCCDF 1.0) 1045 | low = little or no potential for disruption, 1046 | very modest complexity 1047 | medium= some chance of minor disruption, 1048 | substantial complexity 1049 | high = likely to cause serious disruption, very complex 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | Type for an instance element in a fix element. The 1064 | instance element inside a fix element designates a 1065 | spot where the name of the instance should be 1066 | substituted into the fix template to generate the 1067 | final fix data. The instance element in this usage 1068 | has one optional attribute: context. 1069 | 1070 | 1071 | 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | The type for an element that can contains a boolean 1079 | expression based on checks. This element can have only 1080 | complex-check and check elements as children. It has two 1081 | attributes: operator and negate. The operator attribute 1082 | can have values "OR" or "AND", and the negate attribute is 1083 | boolean. See the specification document for truth tables 1084 | for the operators and negations. Note: complex-check is 1085 | defined in this way for conceptual equivalence with OVAL. 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1094 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | The type for the allowed operator names for the 1102 | complex-check operator attribute. For now, we just 1103 | allow boolean AND and OR as operators. (The 1104 | complex-check has a separate mechanism for negation.) 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | Data type for the check element, a checking system 1117 | specification URI, and XML content. The content of the 1118 | check element is: zero or more check-export elements, 1119 | zero or more check-content-ref elements, and finally 1120 | an optional check-content element. An content-less 1121 | check element isn't legal, but XSD cannot express that! 1122 | 1123 | 1124 | 1125 | 1127 | 1129 | 1132 | 1135 | 1136 | 1137 | 1138 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | 1146 | Data type for the check-import element, which specifies a 1147 | value that the benchmark author wishes to retrieve from the 1148 | the checking system. The import-name attribute gives the 1149 | name or id of the value in the checking system. When the 1150 | check-import element appears in the context of a rule-result, 1151 | then the element's content is the desired value. When the 1152 | check-import element appears in the context of a Rule, then 1153 | it should be empty and any content must be ignored. 1154 | 1155 | 1156 | 1157 | 1158 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | Data type for the check-export element, which specifies 1168 | a mapping between an XCCDF internal Value id and a 1169 | value name to be used by the checking system or processor. 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | Data type for the check-content-ref element, which 1180 | points to the code for a detached check in another file. 1181 | This element has no body, just a couple of attributes: 1182 | href and name. The name is optional, if it does not appear 1183 | then this reference is to the entire other document. 1184 | 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 1192 | 1193 | Data type for the check-content element, which holds 1194 | the actual code of an enveloped check in some other 1195 | (non-XCCDF) language. This element can hold almost 1196 | anything; XCCDF tools do not process its content directly. 1197 | 1198 | 1199 | 1200 | 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | 1207 | Data type for a Rule's weight, a non-negative real number. 1208 | 1209 | 1210 | 1211 | 1212 | 1213 | 1214 | 1215 | 1216 | 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | Data type for the Value element, which represents a 1234 | tailorable string, boolean, or number in the Benchmark. 1235 | 1236 | 1237 | 1238 | 1239 | 1240 | 1242 | 1244 | 1246 | 1248 | 1250 | 1252 | 1254 | 1256 | 1257 | 1259 | 1261 | 1263 | 1265 | 1266 | 1267 | 1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | The choice element specifies a list of legal or suggested 1277 | choices for a Value object. It holds one or more choice 1278 | elements, a mustMatch attribute, and a selector attribute. 1279 | 1280 | 1281 | 1282 | 1284 | 1285 | 1286 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | This type is for an element that has string content 1294 | and a selector attribute. It is used for some of 1295 | the child elements of Value. 1296 | 1297 | 1298 | 1299 | 1300 | 1302 | 1303 | 1304 | 1305 | 1306 | 1307 | 1308 | 1309 | This type is for an element that has numeric content 1310 | and a selector attribute. It is used for two of 1311 | the child elements of Value. 1312 | 1313 | 1314 | 1315 | 1316 | 1318 | 1319 | 1320 | 1321 | 1322 | 1323 | 1324 | 1325 | Data type for elements that have no content, just a URI. 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | Allowed data types for Values, just string, numeric, 1335 | and true/false. 1336 | 1337 | 1338 | 1339 | 1340 | 1341 | 1342 | 1343 | 1344 | 1345 | 1346 | 1347 | 1348 | Allowed operators for Values. Note that most of 1349 | these are valid only for numeric data, but the 1350 | schema doesn't enforce that. 1351 | 1352 | 1353 | 1354 | 1355 | 1356 | 1357 | 1358 | 1359 | 1360 | 1361 | 1362 | 1363 | 1364 | 1365 | 1366 | 1367 | Allowed interface hint values. When an interfaceHint 1368 | appears on the Value, it provides a suggestion to a 1369 | tailoring or benchmarking tool about how to present the 1370 | UI for adjusting a Value. 1371 | 1372 | 1373 | 1374 | 1375 | 1376 | 1377 | 1378 | 1379 | 1380 | 1381 | 1382 | 1383 | 1384 | 1385 | 1386 | 1387 | 1388 | 1389 | 1390 | 1391 | 1392 | 1393 | 1394 | 1395 | 1396 | 1397 | 1398 | 1399 | 1400 | 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | 1407 | 1408 | Data type for the Profile element, which holds a 1409 | specific tailoring of the Benchmark. The main part 1410 | of a Profile is the selectors: select, set-value, 1411 | refine-rule, and refine-value. A Profile may also be 1412 | signed with an XML-Signature. 1413 | 1414 | 1415 | 1416 | 1418 | 1420 | 1422 | 1424 | 1426 | 1428 | 1429 | 1431 | 1433 | 1435 | 1437 | 1438 | 1440 | 1441 | 1442 | 1444 | 1446 | 1448 | 1449 | 1450 | 1451 | 1452 | 1453 | 1454 | 1455 | 1456 | 1457 | 1458 | 1459 | Type for the select element in a Profile; all it has are two 1460 | attributes, no content. The two attributes are idref which 1461 | refers to a Group or Rule, and selected which is boolean. 1462 | As content, the select element can contain zero or more 1463 | remark elements, which allows the benchmark author to 1464 | add explanatory material or other additional prose. 1465 | 1466 | 1467 | 1468 | 1470 | 1471 | 1472 | 1473 | 1474 | 1475 | 1476 | 1477 | 1478 | Type for the set-value element in a Profile; it 1479 | has one required attribute and string content. The 1480 | attribute is 'idref', it refers to a Value. 1481 | 1482 | 1483 | 1484 | 1485 | 1486 | 1487 | 1488 | 1489 | 1490 | 1491 | 1492 | 1493 | Type for the refine-value element in a Profile; all it has 1494 | are three attributes, no content. The three attributes are 1495 | 'idref' which refers to a Value, 'selector' which designates 1496 | certain element children of the Value, and 'operator' which 1497 | can override the operator attribute of the Value. 1498 | As content, the refine-value element can contain zero or more 1499 | remark elements, which allows the benchmark author to 1500 | add explanatory material or other additional prose. 1501 | 1502 | 1503 | 1504 | 1506 | 1507 | 1509 | 1511 | 1513 | 1514 | 1515 | 1516 | 1517 | 1518 | Type for the refine-rule element in a Profile; all it has 1519 | are four attributes, no content. The main attribute is 1520 | 'idref' which refers to a Rule, and three attributes that 1521 | allow the Profile author to adjust aspects of how a Rule is 1522 | processed during a benchmark run: weight, severity, role. 1523 | As content, the refine-rule element can contain zero or more 1524 | remark elements, which allows the benchmark author to 1525 | add explanatory material or other additional prose. 1526 | 1527 | 1528 | 1529 | 1531 | 1532 | 1533 | 1534 | 1536 | 1538 | 1540 | 1541 | 1542 | 1543 | 1544 | 1545 | 1546 | 1547 | 1548 | 1549 | 1550 | Data type for the TestResult element, which holds the 1551 | results of one application of the Benchmark. The optional 1552 | test-system attribute gives the name of the benchmarking tool. 1553 | 1554 | 1555 | 1556 | 1557 | 1558 | 1560 | 1561 | 1562 | 1564 | 1566 | 1568 | 1570 | 1572 | 1574 | 1576 | 1578 | 1580 | 1582 | 1584 | 1585 | 1586 | 1587 | 1588 | 1589 | 1590 | 1591 | 1592 | 1593 | 1594 | 1595 | 1597 | 1599 | 1600 | 1601 | 1602 | 1603 | 1604 | 1605 | 1606 | 1607 | 1608 | 1609 | 1610 | 1611 | Type for a score value in a TestResult, the content is a 1612 | real number and the element can have two optional attributes. 1613 | 1614 | 1615 | 1616 | 1617 | 1619 | 1621 | 1622 | 1623 | 1624 | 1625 | 1626 | 1627 | 1628 | This element holds a list of facts about the target system 1629 | or platform. Each fact is an element of type factType. 1630 | Each fact must have a name, but duplicate names are allowed. 1631 | (For example, if you had a fact about MAC addresses, and the 1632 | target system had three NICs, then you'd need three 1633 | instance of the "urn:xccdf:fact:ethernet:MAC" fact.) 1634 | 1635 | 1636 | 1637 | 1639 | 1640 | 1641 | 1642 | 1643 | 1644 | 1645 | Type for an identity element in a TestResult. 1646 | The content is a string, the name of the identity. 1647 | The authenticated attribute indicates whether the 1648 | test system authenticated using that identity in order 1649 | to apply the benchmark. The privileged attribute indicates 1650 | whether the identity has access rights beyond those of 1651 | normal system users (e.g. "root" on Unix") 1652 | 1653 | 1654 | 1655 | 1656 | 1658 | 1660 | 1661 | 1662 | 1663 | 1664 | 1665 | 1666 | 1667 | Element type for a fact about a target system: a 1668 | name-value pair with a type. The content of the element 1669 | is the value, the type attribute gives the type. This 1670 | is an area where XML schema is weak: we can't make the 1671 | schema validator check that the content matches the type. 1672 | 1673 | 1674 | 1675 | 1676 | 1678 | 1680 | 1681 | 1682 | 1683 | 1684 | 1685 | 1686 | 1687 | This element holds all the information about the 1688 | application of one rule to a target. It may only 1689 | appear as part of a TestResult object. 1690 | 1691 | 1692 | 1693 | 1695 | 1697 | 1699 | 1701 | 1703 | 1705 | 1706 | 1708 | 1709 | 1710 | 1712 | 1714 | 1715 | 1716 | 1718 | 1719 | 1720 | 1721 | 1722 | 1723 | Type for an instance element in a rule-result. 1724 | The content is a string, but the element may 1725 | also have two attribute: context and parentContext. 1726 | This type records the details of the target system 1727 | instance for multiply instantiated rules. 1728 | 1729 | 1730 | 1731 | 1732 | 1734 | 1736 | 1737 | 1738 | 1739 | 1740 | 1741 | 1742 | 1743 | Type for an override block in a rule-result. 1744 | It contains five mandatory parts: time, authority, 1745 | old-result, new-result, and remark. 1746 | 1747 | 1748 | 1749 | 1751 | 1753 | 1755 | 1756 | 1757 | 1758 | 1759 | 1760 | 1761 | 1762 | 1763 | Type for a message generated by the checking 1764 | engine or XCCDF tool during benchmark testing. 1765 | Content is string plus required severity attribute. 1766 | 1767 | 1768 | 1769 | 1770 | 1772 | 1773 | 1774 | 1775 | 1776 | 1777 | 1778 | 1779 | Allowed values for message severity. 1780 | 1781 | 1782 | 1783 | 1784 | 1785 | 1786 | 1787 | 1788 | 1789 | 1790 | 1791 | 1792 | Allowed result indicators for a test, several possibilities: 1793 | pass= the test passed, target complies w/ benchmark 1794 | fail= the test failed, target does not comply 1795 | error= an error occurred and test could not complete, 1796 | or the test does not apply to this plaform 1797 | unknown= could not tell what happened, results 1798 | with this status are not to be scored 1799 | notapplicable=Rule did not apply to test target 1800 | fixed=rule failed, but was later fixed (score as pass) 1801 | notchecked=Rule did not cause any evaluation by 1802 | the checking engine (role of "unchecked") 1803 | notselected=Rule was not selected in the Benchmark, 1804 | and therefore was not checked (selected="0") 1805 | informational=Rule was evaluated by the checking 1806 | engine, but isn't to be scored (role of "unscored") 1807 | 1808 | 1809 | 1810 | 1811 | 1812 | 1813 | 1814 | 1815 | 1816 | 1817 | 1818 | 1819 | 1820 | 1821 | 1822 | 1823 | 1824 | 1825 | Allowed severity values for a Rule. 1826 | there are several possible values: 1827 | unknown= severity not defined (default, for forward 1828 | compatibility from XCCDF 1.0) 1829 | info = rule is informational only, failing the 1830 | rule does not imply failure to conform to 1831 | the security guidance of the benchmark. 1832 | (usually would also have a weight of 0) 1833 | low = not a serious problem 1834 | medium= fairly serious problem 1835 | high = a grave or critical problem 1836 | 1837 | 1838 | 1839 | 1840 | 1841 | 1842 | 1843 | 1844 | 1845 | 1846 | 1847 | 1848 | 1849 | 1850 | Allowed checking and scoring roles for a Rule. 1851 | There are several possible values: 1852 | full = if the rule is selected, then check it and let the 1853 | result contribute to the score and appear in reports 1854 | (default, for compatibility for XCCDF 1.0). 1855 | unscored = check the rule, and include the results in 1856 | any report, but do not include the result in 1857 | score computations (in the default scoring model 1858 | the same effect can be achieved with weight=0) 1859 | unchecked = don't check the rule, just force the result 1860 | status to 'unknown'. Include the rule's 1861 | information in any reports. 1862 | 1863 | 1864 | 1865 | 1866 | 1867 | 1868 | 1869 | 1870 | 1871 | 1872 | 1873 | 2012 | 2013 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkeech/stig_parser/e8b5fd92e382148c4f806530f36878fa1723dc1e/tests/__init__.py -------------------------------------------------------------------------------- /tests/resources/U_Docker_Enterprise_2-x_Linux-UNIX_V1R1_STIG.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkeech/stig_parser/e8b5fd92e382148c4f806530f36878fa1723dc1e/tests/resources/U_Docker_Enterprise_2-x_Linux-UNIX_V1R1_STIG.zip -------------------------------------------------------------------------------- /tests/stig_parser_test.py: -------------------------------------------------------------------------------- 1 | ## =================================== 2 | ## ===== STIG PARSER TEST SCRIPT ===== 3 | ## =================================== 4 | 5 | ## Created By : Peter Keech 6 | ## Email : peter.a.keech@gmail.com 7 | ## Version : 1.0.2 8 | ## Description : PyTest file for STIG-Parser v1.0.2 9 | ## Requirements: PyTest, XmltoDict 10 | ## Testing : docker run -it --rm -v $(PWD):/stig-parser python /bin/bash 11 | ## cd stig-parser && pip install pytest xmltodict 12 | ## pytest -s 13 | 14 | ## IMPORT REQUIRED MODULES 15 | import zipfile, os, json, xmltodict, pytest 16 | 17 | ## IMPORT STIG-PARSER 18 | import src.stig_parser as stig_parser 19 | 20 | ## ---------------------------------------- 21 | ## ---- STATICALLY SET TEST VARIABLES ----- 22 | ## ---------------------------------------- 23 | 24 | ## STIG FILENAME 25 | FILENAME = "tests/resources/U_Docker_Enterprise_2-x_Linux-UNIX_V1R1_STIG.zip" 26 | 27 | ## EXPORT PATHS 28 | EXPORT_PATH_JSON = './tests/pytest-stig.json' 29 | EXPORT_PATH_CKL = './tests/pytest-stig.ckl' 30 | 31 | ## CHECKLIST METADATA 32 | CHECKLIST_INFO ={ 33 | "ROLE": "None", 34 | "ASSET_TYPE": "Computing", 35 | "HOST_NAME": "Test_Host", 36 | "HOST_IP": "1.2.3.4", 37 | "HOST_MAC": "", 38 | "HOST_FQDN": "test.hostname.dev", 39 | "TARGET_COMMENT": "", 40 | "TECH_AREA": "", 41 | "TARGET_KEY": "3425", 42 | "WEB_OR_DATABASE": "false", 43 | "WEB_DB_SITE": "", 44 | "WEB_DB_INSTANCE": "" 45 | } 46 | 47 | ## ----------------------------- 48 | ## ----- PRIVATE FUNCTIONS ----- 49 | ## ----------------------------- 50 | 51 | ## FUNCTION: CONVERT CHECKLIST (XML) TO DICTIONARY 52 | def convert_ckl_to_dict(RAW_CKL): 53 | ## CONVERT XML TO PYTHON DICTIONARY 54 | CHECKLIST_DICT = xmltodict.parse(RAW_CKL, dict_constructor=dict) 55 | 56 | ## RETURN DICTIONARY 57 | return CHECKLIST_DICT 58 | 59 | ## ----------------- 60 | ## ----- TESTS ----- 61 | ## ----------------- 62 | 63 | ## TEST: ENSURE STIG FILE EXISTS 64 | ## REQUIRES: N/A 65 | def test_requirements() -> None: 66 | ## CHECK FILE EXISTS 67 | assert os.path.isfile(FILENAME), 'File does not exist: %s' % FILENAME 68 | 69 | ## TEST: ATTEMPT TO PARSE STIG FILE 70 | ## REQUIRES: STIG (XML) 71 | def test_convert_xccdf() -> None: 72 | ## OPEN XML FILE FROM ZIP FILE AND OBTAIN LIST OF FILES 73 | z = zipfile.ZipFile(FILENAME) 74 | files = z.namelist() 75 | 76 | ## FIND MANUAL STIG FILE 77 | for file in files: 78 | if file.endswith('_Manual-xccdf.xml'): 79 | ## HANDLE MACOS 80 | if not file.startswith('__MACOS'): 81 | ## DETERMINE FILE NAME 82 | STIG_FILENAME = file 83 | break 84 | 85 | ## ENSURE FILE IS FOUND 86 | assert STIG_FILENAME is not None, 'Manual STIG File was NOT FOUND' 87 | 88 | ## READ STIG FILE 89 | RAW_FILE = z.read(STIG_FILENAME) 90 | 91 | ## ENSURE FILE READ CORRECTLY 92 | assert RAW_FILE is not None, 'Unable to Read STIG File (%s)' % STIG_FILENAME 93 | 94 | ## CONVERT RAW STIG TO JSON OBJECT 95 | STIG_JSON = stig_parser.convert_xccdf(RAW_FILE) 96 | 97 | ## ENSURE STIG WAS PARSED 98 | assert STIG_JSON is not None, 'Unable to Parse STIG File (%s)' % STIG_FILENAME 99 | 100 | ## VALIDATE KNOWN ENTRIES 101 | assert STIG_JSON['Title'] == "Docker Enterprise 2.x Linux/UNIX Security Technical Implementation Guide", "STIG Title Parsed Incorrectly" ## STIG TITLE 102 | assert STIG_JSON['BenchmarkDate'] == "19 Jul 2019" ## BENCHMARK DATE 103 | assert STIG_JSON['Rules'][0]['VulnID'] == "V-94863" ## FIRST RULE ID 104 | assert STIG_JSON['ReleaseInfo'] == "Release: 1 Benchmark Date: 19 Jul 2019" ## RELEASE INFO 105 | assert STIG_JSON['Source'] == "STIG.DOD.MIL" ## SOURCE 106 | assert STIG_JSON['Notice'] == "terms-of-use" ## NOTICE 107 | assert STIG_JSON['Rules'][0]['CCI'] == "CCI-000054" ## FIRST RULE CCI NUMBER 108 | assert STIG_JSON['Rules'][0]['StigID'] == "DKER-EE-001000" ## FIRST RULE STIG ID 109 | assert STIG_JSON['Rules'][0]['RuleID'] == "SV-104693r1_rule" ## FIRST RULE ID 110 | 111 | ## TEST: ATTEMPT TO PARSE STIG FILE W/O EXTRACTING FILE 112 | ## REQUIRES: STIG (ZIP) 113 | def test_convert_stig() -> None: 114 | ## CONVERT STIG TO JSON OBJECT 115 | STIG_JSON = stig_parser.convert_stig(FILENAME) 116 | 117 | ## ENSURE STIG WAS PARSED 118 | assert STIG_JSON is not None, 'Unable to Parse STIG File (%s)' % FILENAME 119 | 120 | ## VALIDATE KNOWN ENTRIES 121 | assert STIG_JSON['Title'] == "Docker Enterprise 2.x Linux/UNIX Security Technical Implementation Guide", "STIG Title Parsed Incorrectly" ## STIG TITLE 122 | assert STIG_JSON['BenchmarkDate'] == "19 Jul 2019" ## BENCHMARK DATE 123 | assert STIG_JSON['Rules'][0]['VulnID'] == "V-94863" ## FIRST RULE ID 124 | assert STIG_JSON['ReleaseInfo'] == "Release: 1 Benchmark Date: 19 Jul 2019" ## RELEASE INFO 125 | assert STIG_JSON['Source'] == "STIG.DOD.MIL" ## SOURCE 126 | assert STIG_JSON['Notice'] == "terms-of-use" ## NOTICE 127 | assert STIG_JSON['Rules'][0]['CCI'] == "CCI-000054" ## FIRST RULE CCI NUMBER 128 | assert STIG_JSON['Rules'][0]['StigID'] == "DKER-EE-001000" ## FIRST RULE STIG ID 129 | assert STIG_JSON['Rules'][0]['RuleID'] == "SV-104693r1_rule" ## FIRST RULE ID 130 | 131 | ## TEST: EXTRACT STIG FROM ZIP 132 | ## REQUIRES: STIG (ZIP) 133 | def test_extract_stig() -> None: 134 | ## CONVERT STIG TO JSON OBJECT 135 | STIG_JSON = stig_parser.convert_stig(FILENAME) 136 | 137 | ## ENSURE STIG WAS PARSED 138 | assert STIG_JSON is not None, 'Unable to Parse STIG File (%s)' % FILENAME 139 | 140 | ## TEST: ATTEMPT TO SAVE STIG TO JSON FILE 141 | ## REQUIRES: STIG (ZIP), EXPORT FILE PATH 142 | def test_generate_stig_json() -> None: 143 | ## CONVERT STIG TO JSON OBJECT 144 | STIG_JSON = stig_parser.convert_stig(FILENAME) 145 | 146 | ## CREATE JSON FILE 147 | stig_parser.generate_stig_json(STIG_JSON, EXPORT_PATH_JSON) 148 | 149 | ## ENSURE FILE CREATION WAS SUCCESSFUL 150 | assert os.path.exists(EXPORT_PATH_JSON), "Exported File (%s) Doesn't Exist!" % EXPORT_PATH_JSON 151 | 152 | ## ATTEMPT TO READ JSON FILE TO ENSURE VALID EXPORT 153 | FILE = open(EXPORT_PATH_JSON, "r") 154 | STIG = json.load(FILE) 155 | 156 | ## ENSURE FIELDS ARE READABLE 157 | assert STIG['Title'] == "Docker Enterprise 2.x Linux/UNIX Security Technical Implementation Guide", "Unable to read JSON File (%s)" % EXPORT_PATH_JSON 158 | assert STIG['Rules'][0]['VulnID'] == "V-94863", "Unable to read JSON File (%s)" % EXPORT_PATH_JSON 159 | 160 | ## DELETE TEST FILES 161 | os.remove(EXPORT_PATH_JSON) 162 | 163 | ## TEST: ATTEMPT TO GENERATE A BLANK CHECKLIST (CKL) FILE 164 | ## REQUIRES: STIG (ZIP), CHECKLIST INFO (JSON) 165 | def test_generate_ckl() -> None: 166 | ## ATTEMPT TO GENERATE CKL 167 | CKL = stig_parser.generate_ckl(FILENAME, CHECKLIST_INFO) 168 | 169 | ## VALIDATE RESPONSE RETURNED 170 | assert CKL is not None, 'Unable to generate CKL based upon the passed STIG File (%s)' % FILENAME 171 | 172 | ## CONVERT CHECKLIST (CKL) TO DICTIONARY 173 | CHECKLIST_DICT = convert_ckl_to_dict(CKL) 174 | 175 | ## VALIDATE CKL FIELDS (ASSET INFO) 176 | ASSET = CHECKLIST_DICT['CHECKLIST']['ASSET'] 177 | assert ASSET['ROLE'] == "None", 'Checklist Asset Role is Incorrect. %s (From Function) =/= None' % ASSET['ROLE'] ## ASSET ROLE FIELD 178 | 179 | ## VALIDATE CKL FIELDS (STIG INFO) 180 | STIG = CHECKLIST_DICT['CHECKLIST']['STIGS']['iSTIG']['STIG_INFO']['SI_DATA'] 181 | assert STIG[0]['SID_DATA'] == "1", 'STIG Version is Incorrect. %s (From Function) =/= 1' % STIG[0]['SID_DATA'] ## STIG VERSION FIELD 182 | assert STIG[3]['SID_DATA'] == "Docker_Enterprise_2-x_Linux-UNIX", 'STIG ID is Incorrect. %s (From Function) =/= Docker_Enterprise_2-x_Linux-UNIX_STIG' % STIG[3]['SID_DATA'] ## STIG ID 183 | 184 | ## TEST: GENERATE CKL FILE 185 | ## REQUIRES: CHECKLIST XML, OUTPUT FILENAME 186 | def test_generate_ckl_file() -> None: 187 | ## ATTEMPT TO GENERATE CKL 188 | CKL = stig_parser.generate_ckl(FILENAME, CHECKLIST_INFO) 189 | 190 | ## VALIDATE RESPONSE RETURNED 191 | assert CKL is not None, 'Unable to generate CKL based upon the passed STIG File (%s)' % FILENAME 192 | 193 | ## OUTPUT CKL TO FILE 194 | stig_parser.generate_ckl_file(CKL, EXPORT_PATH_CKL) 195 | 196 | ## ENSURE FILE WAS CREATED 197 | assert os.path.exists(EXPORT_PATH_CKL), "Exported File (%s) Doesn't Exist!" % EXPORT_PATH_CKL 198 | 199 | ## ATTEMPT TO LOAD FILE TO ENSURE A VALID EXPORT 200 | FILE = open(EXPORT_PATH_CKL, "r") 201 | CHECKLIST = xmltodict.parse(FILE.read(), dict_constructor=dict) 202 | 203 | ## ENSURE FIELDS ARE READABLE 204 | assert CHECKLIST['CHECKLIST']['ASSET']['ROLE'] == "None", "Unable to read CKL File (%s)" % EXPORT_PATH_CKL 205 | 206 | ## DELETE TEST FILES 207 | os.remove(EXPORT_PATH_CKL) --------------------------------------------------------------------------------