| 
 | 1 | +package analyze  | 
 | 2 | + | 
 | 3 | +import (  | 
 | 4 | +	"context"  | 
 | 5 | +	"fmt"  | 
 | 6 | +	"strings"  | 
 | 7 | +	"testing"  | 
 | 8 | + | 
 | 9 | +	"github.com/boostsecurityio/poutine/formatters/noop"  | 
 | 10 | +	"github.com/boostsecurityio/poutine/models"  | 
 | 11 | +	"github.com/boostsecurityio/poutine/opa"  | 
 | 12 | +	"github.com/stretchr/testify/assert"  | 
 | 13 | +	"github.com/stretchr/testify/require"  | 
 | 14 | +)  | 
 | 15 | + | 
 | 16 | +// newTestOpa creates an OPA client for testing  | 
 | 17 | +func newTestOpa(ctx context.Context) (*opa.Opa, error) {  | 
 | 18 | +	config := models.DefaultConfig()  | 
 | 19 | +	opaClient, err := opa.NewOpa(ctx, config)  | 
 | 20 | +	if err != nil {  | 
 | 21 | +		return nil, fmt.Errorf("failed to create opa client: %w", err)  | 
 | 22 | +	}  | 
 | 23 | +	return opaClient, nil  | 
 | 24 | +}  | 
 | 25 | + | 
 | 26 | +func TestAnalyzeManifestDirectly(t *testing.T) {  | 
 | 27 | +	ctx := context.Background()  | 
 | 28 | + | 
 | 29 | +	tests := []struct {  | 
 | 30 | +		name           string  | 
 | 31 | +		content        string  | 
 | 32 | +		manifestType   string  | 
 | 33 | +		expectedType   string  | 
 | 34 | +		validateResult func(t *testing.T, insights *models.PackageInsights)  | 
 | 35 | +	}{  | 
 | 36 | +		{  | 
 | 37 | +			name: "valid github actions workflow",  | 
 | 38 | +			content: `name: Test Workflow  | 
 | 39 | +on: [push, pull_request]  | 
 | 40 | +
  | 
 | 41 | +jobs:  | 
 | 42 | +  test:  | 
 | 43 | +    runs-on: ubuntu-latest  | 
 | 44 | +    steps:  | 
 | 45 | +      - uses: actions/checkout@v4  | 
 | 46 | +      - name: Run tests  | 
 | 47 | +        run: echo "Running tests"`,  | 
 | 48 | +			manifestType: "github-actions",  | 
 | 49 | +			expectedType: "github-actions",  | 
 | 50 | +			validateResult: func(t *testing.T, insights *models.PackageInsights) {  | 
 | 51 | +				assert.Equal(t, "manifest", insights.SourceScmType)  | 
 | 52 | +				assert.Contains(t, insights.Purl, "pkg:generic/github-actions-workflow")  | 
 | 53 | +				assert.Equal(t, "YAML", insights.PrimaryLanguage)  | 
 | 54 | +				assert.Len(t, insights.GithubActionsWorkflows, 1, "Should detect GitHub Actions workflow")  | 
 | 55 | +			},  | 
 | 56 | +		},  | 
 | 57 | +		{  | 
 | 58 | +			name: "valid gitlab ci config",  | 
 | 59 | +			content: `stages:  | 
 | 60 | +  - build  | 
 | 61 | +  - test  | 
 | 62 | +
  | 
 | 63 | +variables:  | 
 | 64 | +  DOCKER_DRIVER: overlay2  | 
 | 65 | +
  | 
 | 66 | +build_job:  | 
 | 67 | +  stage: build  | 
 | 68 | +  script:  | 
 | 69 | +    - echo "Building application"  | 
 | 70 | +
  | 
 | 71 | +test_job:  | 
 | 72 | +  stage: test  | 
 | 73 | +  script:  | 
 | 74 | +    - echo "Running tests"`,  | 
 | 75 | +			manifestType: "gitlab-ci",  | 
 | 76 | +			expectedType: "gitlab-ci",  | 
 | 77 | +			validateResult: func(t *testing.T, insights *models.PackageInsights) {  | 
 | 78 | +				assert.Equal(t, "manifest", insights.SourceScmType)  | 
 | 79 | +				assert.Contains(t, insights.Purl, "pkg:generic/gitlab-ci-config")  | 
 | 80 | +				assert.Len(t, insights.GitlabciConfigs, 1, "Should detect GitLab CI config")  | 
 | 81 | +			},  | 
 | 82 | +		},  | 
 | 83 | +		{  | 
 | 84 | +			name: "vulnerable github actions workflow",  | 
 | 85 | +			content: `name: Vulnerable Workflow  | 
 | 86 | +on: pull_request_target  | 
 | 87 | +
  | 
 | 88 | +jobs:  | 
 | 89 | +  test:  | 
 | 90 | +    runs-on: ubuntu-latest  | 
 | 91 | +    steps:  | 
 | 92 | +      - uses: actions/checkout@main  | 
 | 93 | +      - name: Vulnerable command  | 
 | 94 | +        run: |  | 
 | 95 | +          curl -fsSL https://example.com/script.sh | bash  | 
 | 96 | +          echo "${{ github.event.pull_request.title }}" | bash`,  | 
 | 97 | +			manifestType: "github-actions",  | 
 | 98 | +			expectedType: "github-actions",  | 
 | 99 | +			validateResult: func(t *testing.T, insights *models.PackageInsights) {  | 
 | 100 | +				assert.Contains(t, insights.Purl, "pkg:generic/github-actions-workflow")  | 
 | 101 | +				assert.Len(t, insights.GithubActionsWorkflows, 1, "Should detect workflow")  | 
 | 102 | + | 
 | 103 | +				workflow := insights.GithubActionsWorkflows[0]  | 
 | 104 | +				assert.Equal(t, "Vulnerable Workflow", workflow.Name)  | 
 | 105 | + | 
 | 106 | +				assert.Len(t, insights.FindingsResults.Findings, 3, "May have security findings for vulnerable workflow")  | 
 | 107 | +			},  | 
 | 108 | +		},  | 
 | 109 | +		{  | 
 | 110 | +			name: "azure pipelines config",  | 
 | 111 | +			content: `trigger:  | 
 | 112 | +  - main  | 
 | 113 | +
  | 
 | 114 | +pool:  | 
 | 115 | +  vmImage: ubuntu-latest  | 
 | 116 | +
  | 
 | 117 | +steps:  | 
 | 118 | +  - task: UseDotNet@2  | 
 | 119 | +    displayName: 'Install .NET'  | 
 | 120 | +    inputs:  | 
 | 121 | +      version: '6.0.x'  | 
 | 122 | +
  | 
 | 123 | +  - script: dotnet build  | 
 | 124 | +    displayName: 'Build application'`,  | 
 | 125 | +			manifestType: "azure-pipelines",  | 
 | 126 | +			expectedType: "azure-pipelines",  | 
 | 127 | +			validateResult: func(t *testing.T, insights *models.PackageInsights) {  | 
 | 128 | +				assert.Contains(t, insights.Purl, "pkg:generic/azure-pipelines-config")  | 
 | 129 | +				assert.Len(t, insights.AzurePipelines, 1, "Should detect Azure Pipeline")  | 
 | 130 | +			},  | 
 | 131 | +		},  | 
 | 132 | +	}  | 
 | 133 | + | 
 | 134 | +	for _, tt := range tests {  | 
 | 135 | +		t.Run(tt.name, func(t *testing.T) {  | 
 | 136 | +			opaClient, err := newTestOpa(ctx)  | 
 | 137 | +			require.NoError(t, err, "Failed to create OPA client")  | 
 | 138 | + | 
 | 139 | +			formatter := &noop.Format{}  | 
 | 140 | +			analyzer := NewAnalyzer(nil, nil, formatter, models.DefaultConfig(), opaClient)  | 
 | 141 | + | 
 | 142 | +			manifestReader := strings.NewReader(tt.content)  | 
 | 143 | +			result, err := analyzer.AnalyzeManifest(ctx, manifestReader, tt.manifestType)  | 
 | 144 | + | 
 | 145 | +			require.NoError(t, err, "AnalyzeManifest should not return an error")  | 
 | 146 | +			require.NotNil(t, result, "Result should not be nil")  | 
 | 147 | + | 
 | 148 | +			if tt.validateResult != nil {  | 
 | 149 | +				tt.validateResult(t, result)  | 
 | 150 | +			}  | 
 | 151 | +		})  | 
 | 152 | +	}  | 
 | 153 | +}  | 
 | 154 | + | 
 | 155 | +func TestAnalyzeManifestErrorHandling(t *testing.T) {  | 
 | 156 | +	ctx := context.Background()  | 
 | 157 | + | 
 | 158 | +	opaClient, err := newTestOpa(ctx)  | 
 | 159 | +	require.NoError(t, err)  | 
 | 160 | + | 
 | 161 | +	formatter := &noop.Format{}  | 
 | 162 | +	analyzer := NewAnalyzer(nil, nil, formatter, models.DefaultConfig(), opaClient)  | 
 | 163 | + | 
 | 164 | +	t.Run("empty content", func(t *testing.T) {  | 
 | 165 | +		manifestReader := strings.NewReader("")  | 
 | 166 | +		result, err := analyzer.AnalyzeManifest(ctx, manifestReader, "github-actions")  | 
 | 167 | + | 
 | 168 | +		require.NoError(t, err)  | 
 | 169 | +		require.NotNil(t, result)  | 
 | 170 | +		assert.Equal(t, "manifest", result.SourceScmType)  | 
 | 171 | +	})  | 
 | 172 | + | 
 | 173 | +	t.Run("invalid yaml", func(t *testing.T) {  | 
 | 174 | +		invalidYaml := `name: Test  | 
 | 175 | +on: push  | 
 | 176 | +jobs:  | 
 | 177 | +  test:  | 
 | 178 | +    runs-on: ubuntu-latest  | 
 | 179 | +    steps:  | 
 | 180 | +      - uses: actions/checkout@v4  | 
 | 181 | +      - name: "Unclosed quote  | 
 | 182 | +        run: echo "test"`  | 
 | 183 | + | 
 | 184 | +		manifestReader := strings.NewReader(invalidYaml)  | 
 | 185 | +		result, err := analyzer.AnalyzeManifest(ctx, manifestReader, "github-actions")  | 
 | 186 | + | 
 | 187 | +		require.NoError(t, err)  | 
 | 188 | +		require.NotNil(t, result)  | 
 | 189 | +	})  | 
 | 190 | +}  | 
0 commit comments