diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index f576a1b..6182392 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -106,6 +106,33 @@ stages: applicationEnvironment: Staging deploymentEnvironment: TestFlight +# MOBSF SECURITY SCAN STAGING +- stage: Security_Scan_Build_Staging + dependsOn: Build_Staging + jobs: + - template: stage-build-security-android.yml + parameters: + applicationEnvironment: Staging + androidKeyStoreFile: $(InternalKeystore) + androidVariableGroup: 'ApplicationTemplate.Distribution.Internal.Android' + firebaseJsonFile: $(InternalFirebaseJson) + firebaseOptionsDartFile: $(InternalFirebaseOptionsDart) + androidKeyPropertiesFile: $(InternalKeyProperties) + googleServicesJsonFile: $(InternalGoogleServicesJson) + enableSecurityScan: true + + - template: stage-build-security-ios.yml + parameters: + applicationEnvironment: Staging + iosProvisioningProfileFile: $(InternalProvisioningProfile) + iosExportOptionsFile: $(InternalExportOptions) + iosCertificateFile: $(InternalCertificate) + iosVariableGroup: 'FlutterApplicationTemplate.Distribution.Internal.iOS' + firebaseJsonFile: $(InternalFirebaseJson) + firebaseOptionsDartFile: $(InternalFirebaseOptionsDart) + googleServicesJsonFile: $(InternalGoogleServicesJson) + enableSecurityScan: true + - stage: Build_Production dependsOn: Build_Staging condition: and(succeeded(), eq(variables['IsPullRequestBuild'], 'false')) @@ -138,6 +165,33 @@ stages: appCenterServiceConnectionName: $(AppCenterServiceConnection) appCenterDistributionGroup: $(AppCenterDistributionGroup) +# MOBSF SECURITY SCAN PRODUCTION +- stage: Security_Scan_Build_Production + dependsOn: Build_Production + jobs: + - template: stage-build-security-android.yml + parameters: + applicationEnvironment: Production + androidKeyStoreFile: $(GooglePlayKeystore) + androidKeyPropertiesFile: $(GooglePlayKeyProperties) + androidVariableGroup: 'ApplicationTemplate.Distribution.GooglePlay' + firebaseJsonFile: $(FirebaseJson) + firebaseOptionsDartFile: $(FirebaseOptionsDart) + googleServicesJsonFile: $(GoogleServicesJson) + enableSecurityScan: true + + - template: stage-build-security-ios.yml + parameters: + applicationEnvironment: Production + iosProvisioningProfileFile: $(AppStoreProvisioningProfile) + iosExportOptionsFile: $(AppStoreExportOptions) + iosCertificateFile: $(AppStoreCertificate) + iosVariableGroup: 'FlutterApplicationTemplate.Distribution.AppStore' + firebaseJsonFile: $(FirebaseJson) + firebaseOptionsDartFile: $(FirebaseOptionsDart) + googleServicesJsonFile: $(GoogleServicesJson) + enableSecurityScan: true + - stage: AppStore condition: and(succeeded(), eq(variables['IsPullRequestBuild'], 'false')) dependsOn: Build_Production diff --git a/build/stage-build-security-android.yml b/build/stage-build-security-android.yml new file mode 100644 index 0000000..2a1fda6 --- /dev/null +++ b/build/stage-build-security-android.yml @@ -0,0 +1,43 @@ +parameters: +- name: applicationEnvironment + type: string + default: '' +- name: androidKeyStoreFile + type: string + default: '' +- name: androidVariableGroup + type: string + default: '' +- name: artifactName + type: string + default: '' +- name: pathToSrc + type: string + default: '$(Build.SourcesDirectory)/src' +- name: androidKeyPropertiesFile + type: string + default: '' +- name: projectName + type: string + default: '$(ProjectName)' +- name: firebaseJsonFile + type: string +- name: firebaseOptionsDartFile + type: string +- name: googleServicesJsonFile + type: string +- name: enableSecurityScan + type: boolean + default: false + +jobs: + - job: OnLinux_Android_SecurityScan + condition: eq(${{parameters.enableSecurityScan}}, true) + dependsOn: [] + pool: + vmImage: $(ubuntuHostedAgentImage) + steps: + - template: templates/mobsf-android-scan.yml + parameters: + mobSfApiKey: '8181' + artifactName: '$(AndroidArtifactName)_${{ parameters.applicationEnvironment }}' \ No newline at end of file diff --git a/build/stage-build-security-ios.yml b/build/stage-build-security-ios.yml new file mode 100644 index 0000000..256b821 --- /dev/null +++ b/build/stage-build-security-ios.yml @@ -0,0 +1,49 @@ +parameters: +- name: applicationEnvironment + type: string + default: '' +- name: artifactName + type: string + default: '' +- name: pathToSrc + type: string + default: '$(Build.SourcesDirectory)/src' +- name: iosCertificateFile + type: string + default: '' +- name: iosProvisioningProfileFile + type: string + default: '' +- name: iosExportOptionsFile + type: string + default: '' +- name: iosCertificatePassword + type: string + default: '' +- name: iosVariableGroup + type: string + default: '' +- name: projectName + type: string + default: '$(ProjectName)' +- name: firebaseJsonFile + type: string +- name: firebaseOptionsDartFile + type: string +- name: googleServicesJsonFile + type: string +- name: enableSecurityScan + type: boolean + default: false + +jobs: + - job: OnLinux_iOS_SecurityScan + condition: eq(${{parameters.enableSecurityScan}}, true) + dependsOn: [] + pool: + vmImage: $(ubuntuHostedAgentImage) + steps: + - template: templates/mobsf-ios-scan.yml + parameters: + mobSfApiKey: '8181' + artifactName: '$(iOSArtifactName)_${{ parameters.applicationEnvironment }}' \ No newline at end of file diff --git a/build/steps-build-ios.yml b/build/steps-build-ios.yml index d342b19..d19eed7 100644 --- a/build/steps-build-ios.yml +++ b/build/steps-build-ios.yml @@ -84,7 +84,7 @@ steps: bannerVersionNumberText: '$(MajorMinorPatch)' - task: FlutterBuild@0 - displayName: Build Project for Release + displayName: Build Project for Release inputs: target: 'ios' buildName: '$(MajorMinorPatch)' @@ -103,7 +103,7 @@ steps: condition: failed() - task: Xcode@5 - condition: and(succeeded(), eq(variables['IsPullRequestBuild'], 'false')) + condition: succeeded() displayName: 'Xcode Archive iOS' inputs: actions: 'archive' @@ -122,7 +122,7 @@ steps: provisioningProfileName: '$(provisioningProfile.provisioningProfileName)' - task: CopyFiles@2 - condition: and(succeeded(), eq(variables['IsPullRequestBuild'], 'false')) + condition: succeeded() displayName: 'Copy Binary Files' inputs: sourceFolder: '${{ parameters.pathToSrc }}/app' diff --git a/build/templates/mobsf-android-scan.yml b/build/templates/mobsf-android-scan.yml new file mode 100644 index 0000000..462afea --- /dev/null +++ b/build/templates/mobsf-android-scan.yml @@ -0,0 +1,143 @@ +parameters: +- name: mobSfApiKey + type: string + default: '8181' +- name: artifactName + type: string + default: '' + +steps: +- task: DownloadPipelineArtifact@1 + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: "${{parameters.artifactName}}" + downloadPath: '$(System.ArtifactsDirectory)' + condition: succeeded() + +- task: DockerInstaller@0 + displayName: 'Install Docker' + inputs: + dockerVersion: $(DockerVersion) + condition: succeeded() + +- script: docker pull opensecurity/mobile-security-framework-mobsf:latest + displayName: 'Pull MobSF Docker Image' + condition: succeeded() + +- script: docker run -d -it --rm -e MOBSF_API_KEY='${{parameters.mobSfApiKey}}' -e DATA_UPLOAD_MAX_MEMORY_SIZE=209715200 -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latest + displayName: 'Run MobSF Docker Image' + condition: succeeded() + +- script: ls -R "$(System.ArtifactsDirectory)" + displayName: 'List Downloaded Artifacts' + +- task: PowerShell@2 + displayName: 'Upload Android file to MobSF' + inputs: + targetType: 'inline' + script: | + $FILENAME = Get-ChildItem -Path "$(System.ArtifactsDirectory)/" -File | Select-Object -First 1 -ExpandProperty Name + Write-Host "Filename: $FILENAME" + $file = "$(System.ArtifactsDirectory)/$FILENAME" + Write-Host "Uploading file $file to MobSF..." + + # Check if the server is up and running + $serverIsRunning = $false + $retryCount = 0 + while (-not $serverIsRunning -and $retryCount -lt 10) { + $response = curl --url http://127.0.0.1:8000/api/v1/scans -H "Authorization: ${{parameters.mobSfApiKey}}" + if ($response.StatusCode -eq 200) { + Write-Host "MobSF server is up and running" + $serverIsRunning = $true + } else { + Write-Host "Waiting for the MobSF server to start..." + $retryCount++ + Start-Sleep -Seconds 5 + } + } + + $uploadResponse = curl -X POST -F "file=@$file;type=application/octet-stream" http://127.0.0.1:8000/api/v1/upload -H "Authorization: ${{parameters.mobSfApiKey}}" + $uploadResponseJson = $uploadResponse | ConvertFrom-Json + $hash = $uploadResponseJson.hash + echo "##vso[task.setvariable variable=fileHash]$hash" + Write-Host "Uploaded file hash: $hash" + Write-Host "Android file uploaded to MobSF" + condition: succeeded() + +- task: PowerShell@2 + displayName: 'Run MobSF Scan' + inputs: + targetType: 'inline' + script: | + Write-Host "MobSF Scan started" + $hash = "$(fileHash)" + $scanUrl = "http://127.0.0.1:8000/api/v1/scan" + Write-Host "Scanning Uploaded file hash: $hash" + $headers = @{ + "Authorization" = ${{parameters.mobSfApiKey}} + } + $scanBody = @{ + "scan_type" = "$(aabFileExtension)" + "hash" = $hash # use the hash from the upload response + } + $response = Invoke-RestMethod -Uri $scanUrl -Method Post -Headers $headers -Body $scanBody + Write-Host "Scan completed" + condition: succeeded() + +- task: PowerShell@2 + displayName: 'Download MobSF PDF report' + inputs: + targetType: 'inline' + script: | + Write-Host "Downloading MobSF PDF report..." + $hash = "$(fileHash)" + Write-Host "File hash: $hash" + $reportUrl = "http://127.0.0.1:8000/api/v1/download_pdf" + $headers = @{ + "Authorization" = "${{parameters.mobSfApiKey}}" + } + $reportBody = @{ + "hash" = $hash # use the hash from the upload response + } + $directoryPath = "${{parameters.artifactName}}/Android" + if (!(Test-Path -Path $directoryPath)) { + New-Item -ItemType Directory -Path $directoryPath | Out-Null + } + $pdfFilePath = "$directoryPath/report.pdf" + Invoke-WebRequest -Uri $reportUrl -Method Post -Headers $headers -Body $reportBody -OutFile $pdfFilePath + Write-Host "PDF report downloaded to $pdfFilePath" + condition: succeeded() + +- task: PowerShell@2 + displayName: 'Download MobSF Json report' + inputs: + targetType: 'inline' + script: | + Write-Host "Downloading MobSF Json report..." + $hash = "$(fileHash)" + Write-Host "File hash: $hash" + $reportUrl = "http://127.0.0.1:8000/api/v1/report_json" + $headers = @{ + "Authorization" = "${{parameters.mobSfApiKey}}" + } + $reportBody = @{ + "hash" = $hash # use the hash from the upload response + } + $reportResponse = Invoke-RestMethod -Uri $reportUrl -Method Post -Headers $headers -Body $reportBody + Write-Host "Response: $reportResponse" + $jsonFilePath = "${{parameters.artifactName}}/Android/Report.json" + $reportResponse | ConvertTo-Json | Out-File -FilePath $jsonFilePath + Write-Host "JSON report downloaded to $jsonFilePath" + condition: succeeded() + +- task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact' + inputs: + PathtoPublish: '${{parameters.artifactName}}/Android' + ArtifactName: ${{parameters.artifactName}}_Security_Reports + publishLocation: 'Container' + +- task: PostBuildCleanup@3 + displayName: 'Post-Build Cleanup: Cleanup files to keep build server clean!' + condition: always() \ No newline at end of file diff --git a/build/templates/mobsf-ios-scan.yml b/build/templates/mobsf-ios-scan.yml new file mode 100644 index 0000000..1728b9b --- /dev/null +++ b/build/templates/mobsf-ios-scan.yml @@ -0,0 +1,143 @@ +parameters: +- name: mobSfApiKey + type: string + default: '8181' +- name: artifactName + type: string + default: '' + +steps: +- task: DownloadPipelineArtifact@1 + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: "${{parameters.artifactName}}" + downloadPath: '$(System.ArtifactsDirectory)' + condition: succeeded() + +- task: DockerInstaller@0 + displayName: 'Install Docker' + inputs: + dockerVersion: $(DockerVersion) + condition: succeeded() + +- script: docker pull opensecurity/mobile-security-framework-mobsf:latest + displayName: 'Pull MobSF Docker Image' + condition: succeeded() + +- script: docker run -d -it --rm -e MOBSF_API_KEY='${{parameters.mobSfApiKey}}' -e DATA_UPLOAD_MAX_MEMORY_SIZE=209715200 -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latest + displayName: 'Run MobSF Docker Image' + condition: succeeded() + +- script: ls -R "$(System.ArtifactsDirectory)" + displayName: 'List Downloaded Artifacts' + +- task: PowerShell@2 + displayName: 'Upload IPA file to MobSF' + inputs: + targetType: 'inline' + script: | + $FILENAME = Get-ChildItem -Path "$(System.ArtifactsDirectory)/" -File | Select-Object -First 1 -ExpandProperty Name + Write-Host "Filename: $FILENAME" + $file = "$(System.ArtifactsDirectory)/$FILENAME" + Write-Host "Uploading file $file to MobSF..." + + # Check if the server is up and running + $serverIsRunning = $false + $retryCount = 0 + while (-not $serverIsRunning -and $retryCount -lt 10) { + $response = curl --url http://127.0.0.1:8000/api/v1/scans -H "Authorization: ${{parameters.mobSfApiKey}}" + if ($response.StatusCode -eq 200) { + Write-Host "MobSF server is up and running" + $serverIsRunning = $true + } else { + Write-Host "Waiting for the MobSF server to start..." + $retryCount++ + Start-Sleep -Seconds 5 + } + } + + $uploadResponse = curl -X POST -F "file=@$file;type=application/octet-stream" http://127.0.0.1:8000/api/v1/upload -H "Authorization: ${{parameters.mobSfApiKey}}" + $uploadResponseJson = $uploadResponse | ConvertFrom-Json + $hash = $uploadResponseJson.hash + echo "##vso[task.setvariable variable=fileHash]$hash" + Write-Host "Uploaded file hash: $hash" + Write-Host "IPA file uploaded to MobSF" + condition: succeeded() + +- task: PowerShell@2 + displayName: 'Run MobSF Scan' + inputs: + targetType: 'inline' + script: | + Write-Host "MobSF Scan started" + $hash = "$(fileHash)" + $scanUrl = "http://127.0.0.1:8000/api/v1/scan" + Write-Host "Scanning Uploaded file hash: $hash" + $headers = @{ + "Authorization" = ${{parameters.mobSfApiKey}} + } + $scanBody = @{ + "scan_type" = "$(ipaFileExtension)" + "hash" = $hash # use the hash from the upload response + } + $response = Invoke-RestMethod -Uri $scanUrl -Method Post -Headers $headers -Body $scanBody + Write-Host "Scan completed" + condition: succeeded() + +- task: PowerShell@2 + displayName: 'Download MobSF PDF report' + inputs: + targetType: 'inline' + script: | + Write-Host "Downloading MobSF PDF report..." + $hash = "$(fileHash)" + Write-Host "File hash: $hash" + $reportUrl = "http://127.0.0.1:8000/api/v1/download_pdf" + $headers = @{ + "Authorization" = "${{parameters.mobSfApiKey}}" + } + $reportBody = @{ + "hash" = $hash # use the hash from the upload response + } + $directoryPath = "${{parameters.artifactName}}/ios" + if (!(Test-Path -Path $directoryPath)) { + New-Item -ItemType Directory -Path $directoryPath | Out-Null + } + $pdfFilePath = "$directoryPath/report.pdf" + Invoke-WebRequest -Uri $reportUrl -Method Post -Headers $headers -Body $reportBody -OutFile $pdfFilePath + Write-Host "PDF report downloaded to $pdfFilePath" + condition: succeeded() + +- task: PowerShell@2 + displayName: 'Download MobSF Json report' + inputs: + targetType: 'inline' + script: | + Write-Host "Downloading MobSF Json report..." + $hash = "$(fileHash)" + Write-Host "File hash: $hash" + $reportUrl = "http://127.0.0.1:8000/api/v1/report_json" + $headers = @{ + "Authorization" = "${{parameters.mobSfApiKey}}" + } + $reportBody = @{ + "hash" = $hash # use the hash from the upload response + } + $reportResponse = Invoke-RestMethod -Uri $reportUrl -Method Post -Headers $headers -Body $reportBody + Write-Host "Response: $reportResponse" + $jsonFilePath = "${{parameters.artifactName}}/ios/Report.json" + $reportResponse | ConvertTo-Json | Out-File -FilePath $jsonFilePath + Write-Host "JSON report downloaded to $jsonFilePath" + condition: succeeded() + +- task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact' + inputs: + PathtoPublish: '${{parameters.artifactName}}/ios' + ArtifactName: ${{parameters.artifactName}}_Security_Reports + publishLocation: 'Container' + +- task: PostBuildCleanup@3 + displayName: 'Post-Build Cleanup: Cleanup files to keep build server clean!' + condition: always() \ No newline at end of file diff --git a/build/variables.yml b/build/variables.yml index 32800f4..c4d7f5a 100644 --- a/build/variables.yml +++ b/build/variables.yml @@ -32,6 +32,9 @@ InternalKeyProperties: com.nventive.internal.flutterapptemplate.key.properties # This is the internal key properties used for internal builds. GooglePlayKeystore: com.nventive.flutterapptemplate.jks # This is the official keystore used for Google Play. GooglePlayKeyProperties: com.nventive.flutterapptemplate.key.properties # This is the official key properties used for Google Play. + aabFileExtension: aab # This is the file extension for Android App Bundles. + # apkFileExtension: apk # This is the file extension for Android APKs. + # iOS. InternalProvisioningProfile: com.nventive.internal.flutterapptemplate.mobileprovision # This is the internal provisioning profile for internal builds. InternalExportOptions: com.nventive.internal.flutterapptemplate.exportOptions.plist # This is the export options file for internal builds. @@ -39,7 +42,8 @@ AppStoreProvisioningProfile: com.nventive.flutterapptemplate.mobileprovision # This is the client provisioning profile for the AppStore (Production distribution). AppStoreExportOptions: com.nventive.flutterapptemplate.exportOptions.plist # This is the export options file for the AppStore (Production distribution) builds. AppStoreCertificate: nventive.p12 # This is the client production certificate used to sign AppStore builds. - + ipaFileExtension: ipa # This is the file extension for iOS IPA files. + # Firebase InternalFirebaseJson: firebase-flutter-internal.json InternalFirebaseOptionsDart: firebase_options-flutter-internal.dart @@ -69,9 +73,13 @@ # Flutter version. FlutterVersion: '3.22.1' + # Docker version. + DockerVersion: '25.0.5' + # Virtual machine images. windowsHostedAgentImage: 'windows-2022' macOSHostedAgentImage: 'macOS-13' + ubuntuHostedAgentImage: 'ubuntu-latest' # Name of the folder where the artefacts will be placed. Variable used in build and release phases. # We make seperate folders so that releases can each download only the folder they need. diff --git a/src/cli/CHANGELOG.md b/src/cli/CHANGELOG.md index 8dd7eb4..453a04f 100644 --- a/src/cli/CHANGELOG.md +++ b/src/cli/CHANGELOG.md @@ -1,10 +1,13 @@ -# Changelog +# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) Prefix your items with `(Template)` if the change is about the template and not the resulting application. +## 0.20.0 +- Configured MobSF security scan on Android and iOS for Stanging and Production builds + ## 0.19.4 - Fix CI/CD artifact name for iOS stage.