Implement ISO 27001 information security management system (ISMS) in engineering organizations. Covers risk assessment, control implementation, documentation, audit preparation, and continuous improvement.
Engineering organizations implement ISO 27001 to formalize information security management across their development, deployment, and operational lifecycles. This guide details how to map ISO 27001 controls to engineering practices, focusing on what engineers do daily—code, infrastructure, pipelines, and systems—and how to make compliance not a checklist, but a living part of the engineering workflow.
Before engineering teams can implement ISO 27001, they must define the information security context and conduct a risk assessment aligned with organizational goals and threat landscape.
Begin by gathering stakeholders from engineering, product, security, and operations. Use the ISO 27001 Annex A controls as a reference, but tailor the scope to the engineering organization’s systems. For example, if the team maintains a microservices architecture with CI/CD and cloud-native services, the scope should include:
Define the information security policy in a docs/security-policy.md file:
## Scope
- All engineering systems: code, builds, deployments, infrastructure, monitoring
- All teams: frontend, backend, DevOps, data engineering, mobile
## Objectives
- Ensure confidentiality, integrity, and availability of all engineering artifacts
- Protect source code and deployment artifacts from unauthorized access and tampering
- Reduce risk of security incidents in production through proactive engineering practices
## Roles
- Security Lead: responsible for policy enforcement
- Engineering Team Leads: ensure team-level compliance
- CI/CD Pipeline Owners: manage automated security checks
Use git to version the policy and enforce a change control process:
git add docs/security-policy.md
git commit -m "Update policy: added role for CI/CD Pipeline Owners"
git push origin main
The risk assessment is documented in risk-assessment/risks.csv. Each row represents a risk, with columns: Id, Description, Asset, Threat, Likelihood, Impact, RiskScore, Control, Owner.
Id,Description,Asset,Threat,Likelihood,Impact,RiskScore,Control,Owner
R-001,Codebase exposed to unauthorized access,Source code repository,Data breach,High,High,8,ISO 27001 A.9.1.1,DevOps Lead
R-002,CI/CD pipeline compromised,CI/CD pipeline,Supply chain attack,Medium,High,7,ISO 27001 A.14.1.1,CI/CD Lead
To validate risk scores, run a script that calculates risk scores using a formula:
def calculate_risk_score(likelihood, impact):
likelihood_map = {"Low": 1, "Medium": 3, "High": 5}
impact_map = {"Low": 1, "Medium": 3, "High": 5}
return likelihood_map[likelihood] * impact_map[impact]
# Example usage
risks = [
{"Id": "R-001", "Description": "Codebase exposed to unauthorized access", "Asset": "Source code repository", "Threat": "Data breach", "Likelihood": "High", "Impact": "High"},
{"Id": "R-002", "Description": "CI/CD pipeline compromised", "Asset": "CI/CD pipeline", "Threat": "Supply chain attack", "Likelihood": "Medium", "Impact": "High"},
]
for risk in risks:
risk["RiskScore"] = calculate_risk_score(risk["Likelihood"], risk["Impact"])
The risk assessment becomes the input for control selection. Each high-risk item triggers one or more ISO 27001 controls, documented in controls/iso-27001-controls.yaml:
A.9.1.1:
title: "Information Security Policies"
description: "Establish, maintain, and communicate information security policies across the organization."
responsible: "Security Lead"
evidence: "docs/security-policy.md"
status: "Implemented"
linked_risks:
- R-001
- R-002
A.14.1.1:
title: "Security in Development Lifecycle"
description: "Integrate security into the software development lifecycle from requirements to deployment."
responsible: "Engineering Team Leads"
evidence: "ci-cd-pipelines/continuous-integration.yaml"
status: "In progress"
linked_risks:
- R-002
ISO 27001 becomes operational when controls are embedded in the engineering workflow. The most common failure is treating controls as separate artifacts rather than lived practices.
Ensure that access control (ISO 27001 A.9.2) is enforced through infrastructure as code and CI/CD.
Use terraform to define roles and permissions:
resource "aws_iam_role" "devops_team" {
name = "engineering-devops"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
tags = {
Project = "Engineering"
Owner = "DevOps Team"
Complies = "ISO 27001 A.9.2.1"
}
}
resource "aws_iam_policy" "code_access" {
name = "code-repository-access"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"codecommit:GetBranch",
"codecommit:GetRepository",
"codecommit:GitPull",
"codecommit:GitPush"
]
Resource = "arn:aws:codecommit:us-east-1:123456789012:my-repo"
}
]
})
}
resource "aws_iam_role_policy_attachment" "devops_code_access" {
role = aws_iam_role.devops_team.name
policy_arn = aws_iam_policy.code_access.arn
}
Apply with terraform apply -var-file=env/prod.tfvars and commit the state to Git.
To verify compliance, use aws-cli to list roles and their attached policies:
aws iam list-attached-role-policies --role-name engineering-devops
Expected output:
{
"AttachedPolicies": [
{
"PolicyName": "code-repository-access",
"PolicyArn": "arn:aws:iam::123456789012:policy/code-repository-access"
}
]
}
If the policy-arn does not match the expected value, the control fails.
The CI/CD pipeline is the engine of ISO 27001 compliance. Each job in the pipeline must provide evidence for one or more controls.
Define a pipeline in ci-cd/jobs.yaml using yaml for declarative configuration:
jobs:
- name: build
steps:
- run: |
docker build -t myapp:latest .
docker tag myapp:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest
description: "Build Docker image"
requires: ["source-code"]
evidence:
- "build.log"
- "Dockerfile"
- "docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}'"
- run: |
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest
description: "Push image to ECR"
requires: ["build"]
evidence:
- "push.log"
- "aws ecr describe-images --repository-name myapp --query 'imageDetails[].imageTags'"
Use curl to verify that each pipeline job logs its evidence:
curl -X POST https://ci.example.com/api/jobs \
-H "Content-Type: application/json" \
-d '{
"jobId": "build-2024-05-15",
"status": "success",
"timestamp": "2024-05-15T12:00:00Z",
"evidence": [
"build.log",
"Dockerfile",
"docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}'"
]
}'
The endpoint responds with:
{
"jobId": "build-2024-05-15",
"status": "received",
"timestamp": "2024-05-15T12:00:00Z"
}
If the evidence list is missing or malformed, the job fails, and the pipeline halts.
ISO 27001 A.12.6.1 requires change management for all information systems.
Use git to track changes to configuration and infrastructure. Every change must be a pull request with a change request template in .github/pull_request_template.md:
## Change Request
### Description
[Describe the change, e.g., "Add monitoring to the auth service."]
### Justification
[Why this change is needed, e.g., "The service has been down twice in the last month."]
### Impact
- [ ] Security
- [ ] Availability
- [ ] Performance
- [ ] Compliance (ISO 27001 A.12.6.1)
### Reviewers
- [ ] DevOps Lead
- [ ] Security Engineer
### Evidence
- [ ] PR with configuration changes
- [ ] CI/CD pipeline run
- [ ] Updated documentation
When the PR is merged, update the change log in changelog.md:
## v1.2.0 (2024-05-15)
- Added monitoring to auth service (PR #112)
- Added Prometheus metrics endpoint
- Configured Grafana dashboard
- Updated `kubernetes/auth-service.yaml`
- Changed database connection pooling (PR #115)
- Increased max connections from 20 to 50
- Added connection health check
Ensure that the change management process is auditable by tracking each change in a changes/changes.csv file:
Id,Title,ChangeType,StartDate,EndDate,Status,Approval,Owner,RelatedControl
C-001,"Add monitoring to auth service","Infrastructure","2024-05-10","2024-05-15","Completed","Approved","DevOps Lead","A.12.6.1"
C-002,"Increase database connection pooling","Configuration","2024-05-12","2024-05-15","Completed","Approved","Database Team","A.12.6.1"
To demonstrate compliance with ISO 27001 A.11.2.1 (Security Testing), integrate automated scanning into the pipeline.
Use docker to run a static analysis tool, bandit, on Python code:
docker run --rm \
-v $(pwd)/src:/app \
-w /app \
bandit:latest \
-f json \
-o reports/bandit-results.json \
-r src/
The bandit tool produces a reports/bandit-results.json file with a standard schema:
{
"results": [
{
"issue_severity": "HIGH",
"issue_confidence": "HIGH",
"issue_text": "Use of eval() in user input",
"filename": "src/auth.py",
"line_number": 45,
"test_id": "B307",
"test_name": "Use of eval()"
}
],
"summary": {
"total": 3,
"high": 1,
"medium": 1,
"low": 1
}
}
Use jq to validate that the report contains at least one high-severity issue:
jq -e '.results[] | select(.issue_severity == "HIGH")' reports/bandit-results.json
If no high-severity issues are found, the pipeline fails with:
jq: error (at reports/bandit-results.json:10): Cannot index object with string "issue_severity"
This failure indicates that the security testing control has not been met.
ISO 27001 is not a one-time setup. The organization must continuously monitor and review compliance.
Use git to maintain an audit trail for each control:
git log --since="2024-05-01" --until="2024-05-31" \
--grep="complies: ISO 27001 A.14.1.1"
This command returns:
commit 8a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b
Author: Jane Doe <[email protected]>
Date: Thu May 16 10:30:00 2024 +0000
Update CI/CD pipeline to include security scanning (complies: ISO 27001 A.14.1.1)
commit 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
Author: John Smith <[email protected]>
Date: Wed May 15 14:20:00 2024 +0000
Add security testing step to CI/CD (complies: ISO 27001 A.14.1.1)
Each commit links to a specific control, making the audit trail traceable.
Use psql to manage a database of control reviews. Create a table:
CREATE TABLE control_reviews (
id SERIAL PRIMARY KEY,
control_code TEXT NOT NULL,
review_date DATE NOT NULL,
reviewer TEXT NOT NULL,
status TEXT CHECK (status IN ('Pending', 'Reviewed', 'Approved', 'Rejected')),
comments TEXT,
evidence_path TEXT,
reviewed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
Insert a review:
INSERT INTO control_reviews (control_code, review_date, reviewer, status, comments, evidence_path)
VALUES ('A.14.1.1', '2024-05-15', 'Sarah Chen', 'Approved', 'Pipeline runs show consistent security testing.', 'reports/2024-05-15-a14-1-1.pdf');
The control review becomes part of the compliance evidence.
Failures often occur silently. A common issue is that bandit runs but the report is not committed to Git:
# After running bandit, but missing git commit
docker run --rm \
-v $(pwd)/src:/app \
-w /app \
bandit:latest \
-f json \
-o reports/bandit-results.json \
-r src/
The pipeline succeeds, but reports/bandit-results.json is not tracked. The review team finds the file missing, and the control fails.
To catch this, add a check-evidence.sh script to the pipeline:
#!/bin/bash
set -e
EVIDENCE_DIR="reports"
if [ ! -f "$EVIDENCE_DIR/bandit-results.json" ]; then
echo "Error: Evidence file $EVIDENCE_DIR/bandit-results.json not found"
exit 1
fi
if ! git ls-files --error-unmatch "$EVIDENCE_DIR/bandit-results.json" >/dev/null 2>&1; then
echo "Error: Evidence file $EVIDENCE_DIR/bandit-results.json not tracked in git"
exit 1
fi
echo "Evidence check passed"
Run this script before publishing the results.
A.14.1.1 is used instead of A.14.1.1.1.changelog.md.evidence field in the CI/CD job is a string instead of a list.These failures are silent until an audit. The only way to catch them is through automation and strict validation at each stage.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy