When our team was planning our desktop app, I thought about safety from the start. I saw that many update systems aren't safe. If someone broke into a download server, they could send bad code to users. And users couldn't tell real updates from fake ones.
I built a safe update system right from the beginning. I use actions pipeline to build our app and store it in S3. This setup has kept our updates safe and our users protected.
Common Update System Problems
Many desktop app update systems have these problems:
- Updates come straight from web links anyone can guess
- No checks to make sure the files haven't been changed
- Storage that anyone might get into
- Update files that could be swapped without anyone knowing
When we started building our app, I wanted to avoid these problems. I didn't want to risk our users getting bad updates.
Our DevSecOps Solution
We put safety first when building our update system. Instead of adding safety later, we made it part of our system from the start. Here's what we built:
- Safe storage: AWS S3 folders that only our system can use
- Safe delivery: Amazon CloudFront to send updates to users
- File checking: SHA-256 hash checking to make sure files are unchanged
- Split info files: Version info stored apart from the app
The best part is that even if someone broke into our storage and changed our app file, users would still be safe because the file check would fail.
How I Set It Up
Here's how I set up our system with actions pipeline:
Step 1: Build and Add Safety Checks
set up our actions pipeline to do this when we make a new version:
# Build the app
npm run build
# Make the safety check number (hash)
SHA256=$(sha256sum myapp-v1.2.0.exe | cut -d ' ' -f 1)
# Create a file with version info AND the safety check
cat > version.json << EOF
{
"version": "1.2.0",
"releaseDate": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
"downloadUrl": "https://d1example.cloudfront.net/myapp-v1.2.0.exe",
"sha256": "$SHA256",
"notes": "Fixed bugs"
}
EOF
This step makes both our update file and the info file with the safety check number.
Step 2: Safe Storage for Update Files
set up S3 storage with no public access:
# Create S3 bucket
aws s3 mb s3://myapp-updates --region us-east-1
# Block public access
aws s3api put-public-access-block \
--bucket myapp-updates \
--public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
This way nobody can directly get to our update files. Not even with the S3 web link. This is my first safety step.
Step 3: CloudFront for Safe Delivery
I set up CloudFront as the only way to get our update files:
# Create the CloudFront access ID
aws cloudfront create-cloud-front-origin-access-identity \
--cloud-front-origin-access-identity-config CallerReference=myapp-updates,Comment=myapp-updates
# Set up S3 to only allow this CloudFront ID to access files
# (JSON setup not shown here)
# Create the CloudFront setup
# (Setup steps not shown here)
This setup not only adds safety but also makes downloads faster for users since CloudFront has servers in many places.
Step 4: Update Check Code
// Check for updates
async function checkForUpdates() {
try {
// Get our info file with version and safety check
const response = await fetch('https://d1example.cloudfront.net/version.json');
const metadata = await response.json();
// Is it newer than what we're running?
const currentVersion = app.getVersion();
if (isNewerVersion(metadata.version, currentVersion)) {
console.log(`New version ready: ${metadata.version}`);
return metadata;
}
return null;
} catch (error) {
console.error('Update check failed:', error);
return null;
}
}
// Download and check update file
async function downloadAndCheckUpdate(metadata) {
try {
// Download the update
const updateFile = await downloadFile(metadata.downloadUrl);
// Make our own safety check number
const calculatedHash = await calculateSHA256(updateFile);
// Compare our number with the expected one
if (calculatedHash !== metadata.sha256) {
// File has been changed!
throw new Error('Safety alert: File check failed');
}
console.log('Update checked and safe to install');
return updateFile;
} catch (error) {
console.error('Update check failed:', error);
throw error;
}
}
// Make SHA-256 safety check
async function calculateSHA256(file) {
const crypto = require('crypto');
const hash = crypto.createHash('sha256');
hash.update(file);
return hash.digest('hex');
}
The key part is checking if calculatedHash !== metadata.sha256
. If someone changes our update file, these numbers won't match, and the update is stopped.
Safety Built In From Start
I built safety into the system from the beginning. The update process has checking as a basic part, not added later.
Automatic Checks
The safety checks happen without humans. The hash is made during builds in our actions pipeline, and checking happens in the app. No manual steps.
Layers of Safety
I created more than one safety step:
- S3 access rules (first layer)
- CloudFront as the only way in (second layer)
- File safety check (strongest layer)
This way, even if one part fails, the others still keep users safe.
Wrapping Up
The safe update system I built works well. It's much safer and works better too.
Users will get faster downloads with CloudFront. Our team feels sure about our releases. And we don't worry about our update system being hacked.
Top comments (0)