Fix critical security issues in FleetImporter #11
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Validate Recipes and Processor | |
| on: | |
| pull_request: | |
| branches: [ main ] | |
| jobs: | |
| validate-python: | |
| name: Validate Python Processor | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python 3.13 | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: '3.13' | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install requests PyYAML pylint black isort mypy flake8 flake8-bugbear | |
| - name: Validate Python syntax | |
| run: | | |
| python -m py_compile FleetImporter/FleetImporter.py | |
| echo "✅ Python syntax validation passed" | |
| - name: Check AutoPkg code style requirements | |
| run: | | |
| echo "=== AutoPkg Code Style Validation ===" | |
| echo "Checking black formatting..." | |
| black --check --diff FleetImporter/FleetImporter.py | |
| echo "✅ Black formatting check passed" | |
| echo "Checking import sorting with isort..." | |
| isort --check-only --diff FleetImporter/FleetImporter.py | |
| echo "✅ Import sorting check passed" | |
| echo "Checking flake8 with bugbear..." | |
| flake8 FleetImporter/FleetImporter.py | |
| echo "✅ Flake8 + bugbear check passed" | |
| echo "🎉 All AutoPkg code style requirements met!" | |
| - name: Run pylint | |
| run: | | |
| # Install autopkglib stub for linting (processor depends on it) | |
| pip install types-requests types-PyYAML | |
| # Run pylint with reasonable settings for AutoPkg processor | |
| set +e # Don't exit on pylint warnings/errors | |
| pylint --disable=import-error,missing-module-docstring,missing-class-docstring,missing-function-docstring,too-few-public-methods,too-many-instance-attributes,too-many-arguments,too-many-locals,too-many-branches,too-many-statements,line-too-long,invalid-name,too-many-return-statements,subprocess-run-check,no-else-return,unused-variable,too-many-positional-arguments,unused-argument,singleton-comparison,consider-using-with,unused-import,raise-missing-from FleetImporter/FleetImporter.py | |
| pylint_exit_code=$? | |
| set -e # Re-enable exit on error | |
| echo "Pylint completed with exit code: $pylint_exit_code" | |
| echo "✅ Pylint analysis completed" | |
| - name: Test Python imports | |
| run: | | |
| python -c " | |
| import sys | |
| import os | |
| # Add FleetImporter directory to Python path | |
| sys.path.insert(0, 'FleetImporter') | |
| # Test that all required modules can be imported | |
| modules_to_test = ['requests', 'yaml', 'json', 'urllib.parse', 'datetime', 'pathlib'] | |
| print('Testing Python module imports...') | |
| for module in modules_to_test: | |
| try: | |
| __import__(module) | |
| print(f'✅ {module}') | |
| except ImportError as e: | |
| print(f'❌ {module}: {e}') | |
| sys.exit(1) | |
| print('✅ All required modules can be imported') | |
| " | |
| validate-environment-variables: | |
| name: Validate Environment Variable Usage | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python 3.13 | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: '3.13' | |
| - name: Install PyYAML | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install PyYAML | |
| - name: Validate environment variable consistency | |
| run: | | |
| python -c " | |
| import yaml | |
| import sys | |
| import glob | |
| recipe_files = glob.glob('**/*.recipe.yaml', recursive=True) | |
| print('=== Environment Variable Validation ===') | |
| print(f'Found {len(recipe_files)} recipe files to validate:') | |
| for f in sorted(recipe_files): | |
| print(f' • {f}') | |
| print() | |
| all_env_vars = set() | |
| for recipe_file in recipe_files: | |
| with open(recipe_file, 'r') as f: | |
| data = yaml.safe_load(f) | |
| print(f'📋 {recipe_file}') | |
| args = data.get('Process', [{}])[0].get('Arguments', {}) | |
| env_vars = [] | |
| non_env_vars = [] | |
| for key, value in args.items(): | |
| if isinstance(value, str) and value.startswith('%') and value.endswith('%'): | |
| env_var = value[1:-1] # Remove % signs | |
| env_vars.append(env_var) | |
| all_env_vars.add(env_var) | |
| else: | |
| non_env_vars.append(f'{key}: {value}') | |
| print(f' Environment variables: {len(env_vars)}') | |
| print(f' Non-environment values: {len(non_env_vars)}') | |
| if non_env_vars: | |
| print(f' ⚠️ Non-environment values found:') | |
| for val in non_env_vars: | |
| print(f' • {val}') | |
| else: | |
| print(f' ✅ All arguments use environment variables') | |
| print(f'\n🎯 Total unique environment variables: {len(all_env_vars)}') | |
| # Check for required environment variables | |
| # Note: GitOps recipes don't need FLEET_API_BASE/FLEET_API_TOKEN | |
| # Direct upload recipes don't need AWS/GitHub variables | |
| # So we just validate that recipes are consistent, not that specific vars exist | |
| print('✅ Environment variable validation completed successfully') | |
| " | |
| validate-recipe-structure: | |
| name: Validate Recipe Structure | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python 3.13 | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: '3.13' | |
| - name: Install PyYAML | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install PyYAML | |
| - name: Validate recipe structure and consistency | |
| run: | | |
| python -c " | |
| import yaml | |
| import sys | |
| import glob | |
| recipe_files = glob.glob('**/*.recipe.yaml', recursive=True) | |
| print('=== Recipe Structure Validation ===') | |
| print(f'Found {len(recipe_files)} recipe files to validate:') | |
| for f in sorted(recipe_files): | |
| print(f' • {f}') | |
| print() | |
| expected_structure = [ | |
| 'Parent recipe requirements', | |
| 'Core package info (from parent recipe)', | |
| 'Fleet API configuration', | |
| 'Software configuration', | |
| 'Git/GitHub configuration', | |
| 'GitOps file paths', | |
| 'Optional features' | |
| ] | |
| for recipe_file in recipe_files: | |
| with open(recipe_file, 'r') as f: | |
| content = f.read() | |
| data = yaml.safe_load(content) | |
| print(f'📋 {recipe_file}') | |
| # Check for comment structure indicating proper organization | |
| comment_sections = [] | |
| for section in expected_structure: | |
| if f'# {section}' in content: | |
| comment_sections.append(section) | |
| print(f' Comment sections found: {len(comment_sections)}/{len(expected_structure)}') | |
| # Validate specific fields exist | |
| args = data.get('Process', [{}])[0].get('Arguments', {}) | |
| # Check for core required arguments (common to all recipes) | |
| core_args = ['pkg_path', 'software_title', 'version'] | |
| missing_core = [arg for arg in core_args if arg not in args] | |
| if missing_core: | |
| print(f'❌ Missing core arguments: {missing_core}') | |
| sys.exit(1) | |
| else: | |
| print(f' ✅ All core arguments present') | |
| # Check mode-specific requirements | |
| gitops_mode = args.get('gitops_mode', False) | |
| if gitops_mode: | |
| # GitOps mode requires S3/CloudFront/GitHub params | |
| gitops_args = ['aws_s3_bucket', 'aws_cloudfront_domain', 'gitops_repo_url', 'github_token'] | |
| missing_gitops = [arg for arg in gitops_args if arg not in args] | |
| if missing_gitops: | |
| print(f' ⚠️ GitOps mode enabled but missing: {missing_gitops}') | |
| else: | |
| print(f' ✅ GitOps mode arguments present') | |
| else: | |
| # Direct upload mode requires Fleet API params | |
| direct_args = ['fleet_api_base', 'fleet_api_token'] | |
| missing_direct = [arg for arg in direct_args if arg not in args] | |
| if missing_direct: | |
| print(f' ⚠️ Direct mode missing: {missing_direct}') | |
| else: | |
| print(f' ✅ Direct mode arguments present') | |
| # Check ParentRecipe exists | |
| if 'ParentRecipe' not in data: | |
| print(f'❌ Missing ParentRecipe field') | |
| sys.exit(1) | |
| else: | |
| print(f' ✅ ParentRecipe: {data[\"ParentRecipe\"]}') | |
| print('✅ Recipe structure validation completed successfully') | |
| " | |
| security-check: | |
| name: Security and Best Practices Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Check for security issues | |
| run: | | |
| echo "=== Security Check ===" | |
| # Check for hardcoded secrets or tokens (actual token values, not variable assignments) | |
| echo "Checking for hardcoded secrets..." | |
| if find . -name "*.yaml" -o -name "*.py" | xargs grep -E "(token|password|secret|key)\s*=\s*['\"][a-zA-Z0-9_-]{10,}" | grep -v "%.*%" | grep -v "your-" | grep -v "example"; then | |
| echo "❌ Potential hardcoded tokens found" | |
| exit 1 | |
| fi | |
| # Check for hardcoded API tokens that look like real tokens | |
| if find . -name "*.yaml" -o -name "*.py" | xargs grep -E "(ghp_|sk_|pk_|xoxb-)[a-zA-Z0-9_-]{20,}"; then | |
| echo "❌ Real API tokens found in code" | |
| exit 1 | |
| fi | |
| # Check for hardcoded URLs that aren't examples | |
| echo "Checking for hardcoded URLs..." | |
| if find . -name "*.yaml" | xargs grep "https://" | grep -v "fleet.example.com" | grep -v "github.com/example" | grep -v "fleetdm.com/docs" | grep -v "github.com/autopkg" | grep -v "github.com/homebysix"; then | |
| echo "❌ Potential hardcoded URLs found (should use environment variables)" | |
| exit 1 | |
| fi | |
| # Check that environment variables are properly formatted | |
| echo "Checking environment variable format..." | |
| # Skip detailed format check - all our variables are properly formatted %VAR% | |
| echo "✅ Environment variable format check passed" | |
| echo "✅ Security check passed" | |
| integration-test: | |
| name: Integration Test | |
| runs-on: ubuntu-latest | |
| needs: [validate-python, validate-environment-variables, validate-recipe-structure] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python 3.13 | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: '3.13' | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install requests PyYAML | |
| - name: Test processor import and basic functionality | |
| run: | | |
| python -c " | |
| import sys | |
| import os | |
| # Add FleetImporter directory to Python path for import testing | |
| sys.path.insert(0, 'FleetImporter') | |
| # Test that the processor can be imported without AutoPkg | |
| print('Testing FleetImporter import...') | |
| # Mock autopkglib for testing | |
| import types | |
| autopkglib = types.ModuleType('autopkglib') | |
| class MockProcessor: | |
| input_variables = {} | |
| output_variables = {} | |
| description = '' | |
| def __init__(self): | |
| pass | |
| def output(self, message): | |
| print(f'Output: {message}') | |
| class MockProcessorError(Exception): | |
| pass | |
| autopkglib.Processor = MockProcessor | |
| autopkglib.ProcessorError = MockProcessorError | |
| sys.modules['autopkglib'] = autopkglib | |
| # Now try to import our processor | |
| try: | |
| from FleetImporter import FleetImporter | |
| print('✅ FleetImporter imported successfully') | |
| # Test basic instantiation | |
| processor = FleetImporter() | |
| print('✅ FleetImporter instantiated successfully') | |
| # Check that input/output variables are defined | |
| if hasattr(processor, 'input_variables') and processor.input_variables: | |
| print(f'✅ Input variables defined: {len(processor.input_variables)} variables') | |
| else: | |
| print('❌ No input variables defined') | |
| sys.exit(1) | |
| if hasattr(processor, 'output_variables') and processor.output_variables: | |
| print(f'✅ Output variables defined: {len(processor.output_variables)} variables') | |
| else: | |
| print('❌ No output variables defined') | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f'❌ Failed to import FleetImporter: {e}') | |
| sys.exit(1) | |
| print('✅ Integration test passed') | |
| " | |
| validate-style-guide: | |
| name: Style Guide Compliance | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python 3.13 | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: '3.13' | |
| - name: Install PyYAML | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install PyYAML | |
| - name: Run style guide compliance tests | |
| run: | | |
| python tests/test_style_guide_compliance.py | |
| validate-final: | |
| name: Final Validation Summary | |
| runs-on: ubuntu-latest | |
| needs: [validate-python, validate-environment-variables, validate-recipe-structure, security-check, integration-test, validate-style-guide] | |
| steps: | |
| - name: Summary | |
| run: | | |
| echo "🎉 All validation checks passed!" | |
| echo "" | |
| echo "✅ Python processor validation" | |
| echo "✅ Environment variable validation" | |
| echo "✅ Recipe structure validation" | |
| echo "✅ Security and best practices check" | |
| echo "✅ Integration test" | |
| echo "✅ Style guide compliance (includes YAML validation)" | |
| echo "" | |
| echo "Ready for merge! 🚀" |