If youโre like me, then working in IT means you also assume Tech Support duties for friends, family, and those distant relatives that only seem to call when theyโve got a problem.
I just clicked on this link, and my computer is doing something weird. I think my PC has a virus, what do I do?
When itโs just a single computer, the answer is simple, contain and validate the rouge software is removed, install an AV solution, change their passwords, enable MFA, and provide some education on what to look out for next time.
But now imagine youโre an organisation building a new application, or are moving applications to the cloud. Are you simply performing a lift-and-shift or are you planning to make use of cloud native services? Where are you going to store your data, specifically user uploaded files? Object Storage was built specifically to solve the challenges of how to store unstructured data in the cloud.
However, there is a catch. If you were previously storing files on a server file system, then itโs likely you were also running an anti-virus / anti-malware solution to identify malicious files. With Object Storage the underlying file system is transparent, so you canโt install AV, yet many compliance requirements still state โUploaded files must be scanned for viruses and malwareโ.
The Shared Responsibility Model
Iโve heard many times that โWeโre in the cloud, so my provider takes care of that right? Unfortunately not. Cloud Providers are responsible only for the security of their platform, you are responsible for what you put in the cloud. If youโre not familiar with the concept, take a look at: https://www.oracle.com/a/ocom/docs/cloud/shared-responsibility-model-wp.pdf
Iโm using Object Storage and need to scan for malware, what are my options?
- DIY, and build scanning integration capabilities into each application.
- Use a 3rd Party Cloud Security Posture Management tool that has AV / Malware scanning capabilities.
- Or donโt use Object Storage, store files on a file system where an AV / Malware solution is installed. Unless you have a specific requirement to interact with the underlying file system I wouldnโt recommend this for a range of reasons, but it is still an option.
Each of these options has its merits. In any case they require time, and some may end up costing more than any benefits you might realise. Saying that, Iโve also seen first hand what happens in the absence of any anti-malware controls, and object storage buckets being full with malware.
It isnโt all doom and gloom however! If youโre using Oracle Cloud Infrastructure Iโm going to show you how you can easily achieve the requirement of checking uploaded files using OCI native services and the https://virustotal.com/ API.
Itโs important to mention that Iโm using a community API key from https://virustotal.com/ for demo purposes only. And you could easily adapt this approach to use any AV / Malware scanning solution that has an API.
AV / Malware Scanning Considerations
Before I jump into showing you how to configure this in your own OCI Tenancy, there are some things that must be considered first. These considerations would also apply if you were wanting to DIY a solution, or use a Cloud Security Posture Management Tool.
- Data Sovereignty. Do you need to keep data within a specific set of geographic regions? Your chosen cloud anti-malware solution may not store data in your geographic area, or they may replicate your data across the globe.
- Local Laws & Regulations. What data resides in your files? You may not want files containing PII leaving your environment, or accessible by 3rd Parties.
- 3rd Party Risks. If you are using a cloud based anti-malware solution, youโll want to ensure they have adequate controls to protect the privacy of your data.
- AV / Malware Licensing & Infrastructure. Do you want to purchase, install and manage the solution, integrations, and support the underlying infrastructure?
- In-band vs Out-of-band scanning. If users can upload files, specifically large files, the time required to scan may have a negative impact on user experience if they have to wait. Similarly if you scan post upload youโll need to consider how to handle broken file links when a file is quarantined.
- Adequate Coverage. Files can get into object storage many different ways including web forms, APIโs, FTP, WEBDAV etc. Whatever solution you choose needs to ensure all files are scanned.
- Performance & Scalability. Youโll need to plan how to handle large files, and potentially large numbers of files. If youโre doing in-band scanning, then there is the possibility of users experiencing a Denial of Service if you donโt have adequate capacity.
- Fail open vs closed. In the case thereโs scan failure, or the scanning service is unavailable, do you want to reject the file, or let it through?
- Operating Model. If youโre doing out-of-band scanning, how will you support reviewing and responding to files identified as malicious.
Checking for Malware in Oracle Cloud Infrastructure
The image above illustrates the architecture of my proposed solution. It comprises of:
- A Compartment, Virtual Cloud Network, Public Subnet, and Internet Gateway. This will likely already exist in your own environment.
- A custom Ruby OCI Function called scan-upload.
- One or more buckets where files are uploaded, in my diagram these are called bucket-1 and bucket-2.
- An Event rule, that triggers the scan-upload function when an object is uploaded to a Bucket.
- An external API provider returning metadata for known malicious MD5 file hashes. For this demo Iโve chosen Virus Total.
- A quarantine Bucket where files that meet the quarantine criteria are moved.
- A Custom Log called scan-results where JSON file scan results are stored.
- A Service Connector that pushes logs from the scan-results Custom Log to Object Storage.
- An Object Collection Rule, Custom Parser and Dashboard in Logging Analytics to view file upload activity and files flagged as malicious.
The benefits of this approach are:
- The Event Rule will fire for every object uploaded regardless of which Bucket. You can exclude certain Buckets by specifying then in the Function configuration.
- You can deploy this in more regions, ensuring data sovereignty.
- Only the fileโs MD5 Hash is send to the 3rd party, removing PII and 3rd party risks.
- The function makes an outbound HTTPS call only, no inbound rules are required.
- Scanning is performed out-of-band / post upload, meaning weโre not impacting the user experience.
- Itโs fast, as weโre only checking the MD5 hash.
- Itโs scalable as OCI functions scale automatically.
- You can easily extend the existing Function logic to handle your specific rejection workflows e.g. Updating a file link in your database.
- You can view real-time insights in Logging Analytics.
For this demo Iโve limited the scope to a specific compartment and region, however you could easily expand this approach to multiple regions and compartments.
Setup & Configuration
Before we begin, youโll need a Compartment, Virtual Cloud Network, Public Subnet, and Internet Gateway. If required you can create the networking components via the VCN Wizard.
Youโll also need to install Docker, and the Fn Project. On OSX installing the Fn Project is easy with Homebrew:
$ brew install fn
To push the function to the OCI container repository youโll also need an Auth Token which can be generated in OCI Identity by selecting your user, and clicking โAuth Tokensโ.
Download the function source code from Github https://github.com/scotti-fletcher/oci-av-function. As always I recommend inspecting any source code that you download from the Internet for security issues.
Now weโll create a Functions application. An application is just a logical grouping of functions, and in this demo we only have one application called โred-thunder-demoโ and one function called โscan-uploadโ.
After creating the application, follow the โGetting Startedโ instructions for โLocal setupโ. The instructions displayed will be unique for your OCI environment, however Iโll explain the steps as not all are required for this demo.
In terminal, cd into the scan-upload directory containing the function files:
scott@scott-mac ~ % cd ~/Desktop/oci-av-function-main/scan-upload
scott@scott-mac scan-upload % ls
Gemfile func.rb func.yaml
Create a context for this compartment and select it for use. As my compartment is called โred-thunder-demoโ you see it in the next statement.
$ fn create context red-thunder-demo --provider oracle
$ fn use context red-thunder-demo
Update the context with the compartment OCID where you created the application and the Oracle Functions API URL.
$ fn update context oracle.compartment-id [your compartment ocid]
$ fn update context api-url https://functions.ap-sydney-1.oraclecloud.com
Create a repository where your function container image will be stored.
$ fn update context registry syd.ocir.io/[your namespace]/red-thunder-demo
Log into the OCI container registry.
$ docker login -u '[your namespace]/oracleidentitycloudservice/scott.fletcher@oracle.com' syd.ocir.io
You should see a โLogin Successfulโ message. You can now build and deploy your application. The first time you do this it might take a few minutes:
scott@scott-mac scan-upload % fn deploy --app red-thunder-demo
Deploying scan-upload to app: red-thunder-demo
Bumped to version 1.0.1
Using Container engine docker
Building image syd.ocir.io/abcd/red-thunder-demo/scan-upload:1.0.1 ...................................................
Parts: [syd.ocir.io abcd red-thunder-demo scan-upload:1.0.1]
Using Container engine docker to push
Pushing syd.ocir.io/abcd/red-thunder-demo/scan-upload:1.0.1 to docker registry...The push refers to repository [syd.ocir.io/abcd/red-thunder-demo/scan-upload]
cd394e6f57be: Pushed
71adf80122ed: Pushed
dcec050cc620: Pushed
3668420c8d32: Mounted from abcd/test-av-setup/scan-upload
5e4a9dac9f74: Mounted from abcd/test-av-setup/scan-upload
dedbd9e80d19: Mounted from abcd/test-av-setup/scan-upload
7c1c8ad8e410: Mounted from abcd/test-av-setup/scan-upload
1.0.1: digest: sha256:769bbe97af259c710033b59e494c9765413a11e7cb5eab19fade525e293c82ee size: 1783
Updating function scan-upload using image syd.ocir.io/abcd/red-thunder-demo/scan-upload:1.0.1...
Successfully created function: scan-upload with syd.ocir.io/abcd/red-thunder-demo/scan-upload:1.0.1
Once created, you should be able to see the function in the OCI console under applications and also in the Container Registry.
Now we need to create a Log Group to hold our Function specific logs and scan results. Create a log group, Iโve named mine โav-function-log-groupโ:
In logs, click โCreate custom logโ and call it โscan-resultsโ.
When prompted select โAdd configuration laterโ and click create. You should see the log โscan-resultsโ in the โav-function-log-groupโ:
Click the โscan-resultsโ log, and note down the OCID as weโll need this later.
Now we need to create the following Buckets:
When creating the buckets, Iโm using the default options however you need to ensure the โEmit Object Eventsโ checkbox is selected on bucket-1 and bucket-2. If you have other buckets that you wish to scan youโll need to update them too. By clicking โEmit Object Eventsโ when an object is uploaded the Events Service rule will trigger our function.
Note: We donโt โEmit Object Eventsโ for our quarantine bucket, otherwise the function will run twice for the same file.
Next we need to create an Event Service rule that triggers our function:
Now we need to create a Service-Connector to push the scan-results logs into a bucket so they can be collected and ingested by Logging Analytics:
Note you will need to โCreate default policyโ as prompted. When you click create this will also enable the โEmit Object Eventsโ on the scan-results bucket as itโs required for the Service Connector to run.
Now weโll go back to our Function, and update the required configuration items. Configuration items are just environment variables that are made accessible to the function:
You can configure the following items:
- VT_API_KEY: This is your Virus Total API Key.
- QUARANTINE_BUCKET_NAME: This is the name of the bucket where you want quarantined files to be moved. In this demo we created a bucket called โquarantineโ for this purpose.
- EXCLUDE_BUCKETS: If you have any buckets that you donโt want scanned you can add them here as a comma separated list (with no spaces). Because we donโt want our logs in the scan-results bucket scanned (as thereโs no point) Iโve added it here.
- QUARANTINE_THRESHOLD: Virus Total uses a number of different scan engines to provide great coverage. My value of 1 means if 1% of the scan engines report the file as malicious, then I want the function to move the file to the quarantine bucket. I suggest a low value here.
- DELETE_THRESHOLD: My value of 50 means if 50% of the scan engines report the file as malicious, then I want the function to delete the file. Depending on your specific use case you might want to increase or decrease this value.
- OCI_LOG_OCID: This is the OCID of the scan-results custom log you created earlier.
- QUARANTINE_BUCKET_REGION: This is the region where the bucket called โquarantineโ is located. Iโve entered ap-sydney-1 as this is where I created the bucket.
- ON_ERROR: As I mentioned earlier, you need to consider what to do if thereโs a scan error, network connectivity failure, or API failure (like exceeding your Virus Total API limits). Depending on your use case you might want to delete the object, quarantine the object, or fail open. You can choose to enter โQUARANTINEโ, โDELETEโ, or remove this configuration item altogether and fail open if an error occurs.
Functions also emit logs, and itโs useful to see them for debugging purposes. To enable these logs, click โEnable Logโ:
Before we test our function, we need to create a Dynamic Group, and Policies to allow our function to operate in our environment. Depending on your use-case and where your object storage buckets are, you may need to adjust the policies. My examples below are scoped to the โred-thunder-demoโ compartment.
Create a Dynamic Group that defines the Function and compartment:
Create a new policy, with the following policy statements. If youโve chosen different Dynamic Group and Compartment names, you will need to update the policy statements accordingly:
- โallow dynamic-group red-thunder-demo-scan-upload-dynamic-group to manage objects in compartment red-thunder-demoโ. This allows the function defined by the Dynamic Group to manage Objects.
- โallow dynamic-group red-thunder-demo-scan-upload-dynamic-group to use log-content in compartment red-thunder-demoโ. This allows the function to write to the custom log that we created to hold our scan results.The last two statements:
- โallow service faas to {KEY_READ} in compartment red-thunder-demo where request.operation=โGetKeyVersion’โ
- allow service faas to {KEY_VERIFY} in compartment red-thunder-demo where request.operation=โVerifyโallow the functions service to read the key used to encrypt and decrypt the files stored in Object Storage.
You should now have two policies in your compartment:
At this point the function should be installed and configured correctly. Before we continue I feel itโs important to mention that handling malware does come with risks and you should take appropriate steps to protect yourself and your organisation from inadvertently executing malware. If youโre not familiar with safe handling practises, consult someone who is, and Iโd also recommend using a sandbox environment for the next parts of this demo.
Iโve uploaded two ZIP files containing malware. I chose ZIP files specifically so the files contained are not immediately executable. I also know these files are flagged by Virus Total. Iโve uploaded them to bucket-1 and bucket-2. Once uploaded looking at the function invocation logs I can see the function has run. One object is quarantined because the confidence level is 5%, and the other is deleted outright because the confidence is 80%, which is above my configured value of 50:
At this point, weโve confirmed the scan-upload function works as expected. Great! However looking at these logs isnโt very exciting so Iโm going to show you how you can visualise these results with Logging Analytics.
If youโve been following this guide then you might have noticed weโve already set ourselves up to collect and ingest logs into Logging Analytics. We did this by creating a scan-results bucket and a Service Connector. If youโve uploaded some malware, after about 10 minutes you should see some logs in the scan-results bucket:
In Logging Analytics create a Log Group to hold our scan-results logs. Iโve called mine โmalware-scan-resultsโ:
Download the Logging Analytics Log Source & Parser configuration if you havenโt retrieved it from GitHub https://github.com/scotti-fletcher/oci-av-function/blob/main/malware-log-source.zip
Click โImport Configuration Contentโ, select the ZIP file and click Import:
Now we need to create an Object Collection Rule that enables collection of the logs from our scan-results bucket and loads them into our malware-scan-results Log Group.
$ oci log-analytics object-collection-rule create --name malware-collection-rule --compartment-id [Your Compartment OCID] --os-namespace [Your Object Storage Namespace] --os-bucket-name scan-results --log-group-id [The OCID of the malware-scan-results Log Group] --log-source-name malware-log-source --namespace-name [Your Object Storage Namespace] --collection-type HISTORIC_LIVE --poll-since BEGINNING
The CLI should return a JSON response, indicating your rule creation succeeded. You should also be able to see another Event Rule created:
Download the Logging Analytics Dashboard if you havenโt already from GitHub https://github.com/scotti-fletcher/oci-av-function/blob/main/malware-dashboard.json . Youโll need to find and replace all values of โENTER_COMPARTMENT_IDโ with the OCID of the compartment where you want to create the dashboard.
Using the CLI run the following command. Youโll also need to update the URL to the endpoint for the region where you want the Dashboard to be created:
$ oci raw-request --target-uri https://managementdashboard.ap-sydney-1.oci.oraclecloud.com/20200901/managementDashboards/actions/importDashboard --http-method POST --request-body file://malware-dashboard.json
You should then be able to view your dashboard. If you donโt see any data, make sure your scan-results bucket has logs in it, and you have selected the correct compartment from the drop down list:
The Dashboard Iโve created shows some useful information, such as the volume of files scanned, the scan results, the actions taken, the threat names, in which buckets your malware is located, and information about the files flagged as malicious.
Some Final Thoughts
Malware is one of those things we wish didnโt exist, however hopefully Iโve shown you how you can tackle the problem and perform a basic check of a fileโs MD5 hash using OCI services.
From here, you could easily extend the Functionโs logic to upload the files themselves to Virus Total, or submit them to your own anti-malware solution. You could also invoke other functions to drive your own custom workflows.
Happy Malware Scanning!