Skip to content

Commit 0f32a05

Browse files
authored
add article audit preparation ios (#66)
1 parent 590eaea commit 0f32a05

File tree

3 files changed

+244
-2
lines changed

3 files changed

+244
-2
lines changed

_posts/2025-01-21-audit-preparation.markdown

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
layout: post
3-
title: "Audit Preparation in E-Rezept App"
3+
title: "Audit Preparation in E-Rezept App (Android)"
44
date: 2025-01-21 10:10:10 +0200
55
author: Dinesh Gangatharan
66
categories: tech
7-
tags: Audits
7+
tags: Audits Android
88
---
99

1010
As an open-source and publicly funded application, the E-Rezept app must consistently pass rigorous audits. These audits are critical, and while auditors have access to the entire codebase, it’s our responsibility to make their task easier by providing well-documented and structured audit artifacts.
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
---
2+
layout: post
3+
title: "Audit Preparation in E-Rezept App (iOS)"
4+
date: 2025-05-06 14:00:00 +0200
5+
author: Martin Fiebig
6+
categories: tech
7+
tags: Audits iOS
8+
excerpt: <br />Our technical approach for preparing for audits explained.<br /><br />
9+
---
10+
11+
<div class="note">
12+
<p>
13+
This is the continuation of a previous article <a href="/tech/2025/01/21/audit-preparation">Audit Preparation in E-Rezept App (Android)</a>. If you are interested in how we prepare audits for our Android E-Rezept App, feel free to read that article first.
14+
</p>
15+
</div>
16+
17+
18+
# Preparing for Audits in the E-Rezept App iOS
19+
20+
The iOS Application is subject to audits much like the Android application. Our application is mainly written using Swift. At the point of writing this article, Swift does not support annotations as they can be used with Gradle plugins, so there is no formal way of annotating code as we would need.
21+
22+
In this article we will present a distinct approach to annotate the code differently from Android and walk through the steps of combining requirements with code to form an output document to help with auditing the code.
23+
24+
---
25+
26+
## Requirements
27+
28+
In order to deliver a secure product we have to implement various formal requirements. These requirements are published regularly for all gematik products. You can find them on our website *gemSpecPages* ([https://gemspec.gematik.de](https://gemspec.gematik.de)). An example (and our *main*) specification for the E-Rezept App is called `gemSpec_eRp_FdV`. You can find the latest version of the actual specification under [https://gemspec.gematik.de/docs/gemSpec/gemSpec_eRp_FdV/latest/](https://gemspec.gematik.de/docs/gemSpec/gemSpec_eRp_FdV/latest/).
29+
30+
While preparing the document for our auditors, we will use gemspec pages as a source for the specification text, as well as another specification that is provided by the BSI. The BSI specification is published as a PDF document, to use it within the scripts, we transformed parts of that PDF into an HTML document with the same structure as gemspecPages.
31+
32+
## Annotations
33+
34+
Auditors expect an explanation of how and where a specific requirement is implemented. For that purpose, we use special annotations within our code for each requirement as well as a text document for more general comments that do not fit to any parts of the code. The special annotations are simple comments within the code, that flag a line or a region of code to be important for a specific requirement.
35+
36+
The format of the annotation looks like this:
37+
38+
```
39+
[REQ:<SPEC>:<AFO>#<ORDER>|<LOC_TO_INCLUDE>] <NOTE>
40+
```
41+
42+
Annotations are composed with these parts:
43+
44+
- `[REQ:`: marks the start of a requirement annotation.
45+
- `<SPEC>`: Technical name of the specification (document name), that originates the requirement.
46+
- `:<AFO>`: Static `:` followed by the identifier of the implemented requirement.
47+
- `#<ORDER>` (optional): Static `#` followed by a number. All references of a single requirement are ordered by this number in the document. This allows a deterministic order of all code parts and makes the explanation easier to understand.
48+
- `|<LOC_TO_INCLUDE>` (optional): The number of lines that the annotation is meant for. Defaults to 1.
49+
- `] `: Marks the end of the technical part of the annotation.
50+
- `<NOTE>` (optional): Textual explanation of the implementation or why this part is important for the requirement.
51+
52+
### Example
53+
54+
Looking at [DefaultIDPSession.swift#L196](https://github.com/gematik/E-Rezept-App-iOS/blob/master/Sources/IDP/DefaultIDPSession.swift#L196) we can see the annotation:
55+
56+
```
57+
[REQ:gemSpec_IDP_Frontend:A_21323,A_21324#1|6] Crypto box contains `Token-Key`
58+
```
59+
60+
containing the following information:
61+
62+
- `gemSpec_IDP_Frontend`: The specification document where the requirements can be found
63+
- `A_21323,A_21324#1`: The part of the code spans two requirements, one being `A_21323` the other being `A_21324`. The annotation is ranked #1 for the latter.
64+
- `|6`: The annotation is meant for the following 6 lines of code.
65+
66+
## Document generation
67+
68+
The actual generation of the document is written in Ruby, called by the `list_requirements` lane of our [Fastfile](https://github.com/gematik/E-Rezept-App-iOS/blob/master/fastlane/Fastfile). We extracted the actual code into a fastlane plugin to clean up the fastfile. You can find the plugin on Github [gematik/Fastlane-Plugin-CI-CD](https://github.com/gematik/Fastlane-Plugin-CI-CD). The action that generates the audit document is named `audit_generator`.
69+
70+
### Processing Annotations
71+
72+
Let's have a look into that plugin, to see how this works [audit_generator.rb#L15](https://github.com/gematik/Fastlane-Plugin-CI-CD/blob/main/lib/fastlane/plugin/ci_cd/actions/audit_generator.rb#L15):
73+
74+
```ruby
75+
def self.run(params)
76+
UI.message("The audit_generator plugin is working!")
77+
78+
# Load data and initialize processors
79+
data_loader = Fastlane::Helper::AuditDataLoader.new(params[:audit_afos_json])
80+
file_processor = Fastlane::Helper::FileProcessor.new
81+
spec_fetcher = Fastlane::Helper::SpecFetcher.new(data_loader.audit_afos_json)
82+
83+
# Process requirement notes if provided
84+
if params[:requirement_notes_glob]
85+
UI.message("Processing requirement notes from: #{params[:requirement_notes_glob]}")
86+
Dir.glob(params[:requirement_notes_glob]).each do |file|
87+
file_processor.process_requirement_notes(file)
88+
end
89+
end
90+
91+
# Process code files
92+
source_files = []
93+
params[:source_file_globs].each do |glob|
94+
UI.message("Adding source files from glob: #{glob}")
95+
source_files.concat(Dir.glob(glob))
96+
end
97+
98+
source_files.each do |file|
99+
file_processor.process_code_file(file)
100+
end
101+
102+
# Get specs and prepare data for templates
103+
original_texts = spec_fetcher.fetch_specs
104+
audit_data = prepare_audit_data(data_loader.audit_afos, file_processor.specs, original_texts)
105+
106+
# Generate output files
107+
generate_output_files(params, audit_data)
108+
end
109+
```
110+
111+
After some basic initialization the process consists of 4 steps:
112+
113+
1. Parsing the requirement notes document (if present) containing general information on some of the implemented requirements
114+
2. Scanning all given source files for requirement annotations
115+
3. Processing
116+
1. Download the original specifications
117+
2. Processing all found annotation information, grouping by spec, sorting
118+
4. Rendering the information into all given templates
119+
120+
#### Parsing requirement notes & scanning all given source files
121+
122+
The actual parsing of the requirement notes document as well as the given source files is similar. We identify blocks that contain annotations. While in the requirement notes each block will consist of one line in the document, the source files are scanned for comment sections. Subsequent commented lines are treated as one block.
123+
124+
Each block is then parsed using a regular expression:
125+
126+
```ruby
127+
block.each do |line|
128+
# Format: [REQ:<SPEC>:<AFOS>:<SUBAFOS?>] <DESC>
129+
if (matches = line.match(/\[REQ:(?<SPEC>[^:]*):(?<AFOS>[^:^|\s]*)(?:(?::)(?<SUBAFOS>[^:^|\s]*))?(?:(?:\|)(?<NUMBEROFLINES>[^:\s]*))?\](?<DESC>.*)/))
130+
register(matches, file, number, trimmed_code, source, trimming)
131+
end
132+
number += 1
133+
end
134+
```
135+
136+
The `register(...)` method then gathers all the information in a structured way.
137+
138+
#### Processing
139+
140+
The processing starts by downloading all original requirements from the specifications listed in the `audit_afos.json` file. This allows us to include the official requirement text alongside our implementation details.
141+
142+
##### Original Requirement Texts
143+
144+
The `audit_afos.json` file serves as a central registry of all requirements (AFOs) that will be checked by the auditors, as well as a list of specifications that are used. Here's an example of what this file might look like:
145+
146+
```json
147+
{
148+
"audit_afos": [
149+
"A_20742", "A_20743", "A_20744", "A_21323", "A_21324"
150+
],
151+
"specs": {
152+
"gemSpec_eRp_FdV": "https://gemspec.gematik.de/docs/gemSpec/gemSpec_eRp_FdV/latest/",
153+
"gemSpec_IDP_Frontend": "https://gemspec.gematik.de/docs/gemSpec/gemSpec_IDP_Frontend/latest/",
154+
"BSI_eRp": "file:///path/to/local/bsi_requirements.html"
155+
}
156+
}
157+
```
158+
159+
For each specification, we download and parse the HTML content using the `Nokogiri` gem. Since each requirement within the HTML document is identified by a unique ID, we can extract the original requirement text and include it in our audit document:
160+
161+
```ruby
162+
def fetch_requirement_text(spec_doc, afo)
163+
# Find the element with the AFO ID
164+
element = spec_doc.css("##{afo}")
165+
return "Original text not found" if element.empty?
166+
167+
# Extract the requirement text
168+
element.inner_html
169+
end
170+
```
171+
172+
For BSI requirements, which are typically provided as PDF documents, we convert the relevant sections to HTML with the same structure as gemspecPages to maintain consistency in our processing pipeline.
173+
174+
##### Filtering and Organization
175+
176+
Before generating the final output, we apply several filters and organizational steps to ensure the audit document is comprehensive and easy to navigate:
177+
178+
1. **Completeness Check:** We identify any requirements from the `audit_afos.json` file that don't have corresponding implementations, marking them as "missing" in the output.
179+
2. **Grouping by Specification:** All implementations are grouped by their source specification, making it easier for auditors to verify compliance with specific documents.
180+
3. **Sorting:** Within each specification, implementations are sorted by AFO ID and then by the optional order parameter, ensuring a logical and consistent presentation.
181+
4. **Deduplication:** If the same code section is referenced multiple times for the same requirement, we consolidate these references to avoid redundancy.
182+
183+
#### Output Generation
184+
185+
The final step is generating the output files based on the processed data and the provided ERB templates:
186+
187+
```ruby
188+
def generate_output_files(params, audit_data)
189+
FileUtils.mkdir_p(params[:output_directory])
190+
191+
params[:erb_templates].each do |template_path|
192+
template_name = File.basename(template_path, '.erb')
193+
output_path = File.join(params[:output_directory], template_name)
194+
195+
UI.message("Generating output file: #{output_path}")
196+
197+
# Load and render the ERB template
198+
template = ERB.new(File.read(template_path))
199+
result = template.result_with_hash(
200+
specs: audit_data[:specs],
201+
missing_afos: audit_data[:missing_afos],
202+
original_texts: audit_data[:original_texts]
203+
)
204+
205+
# Write the output file
206+
File.write(output_path, result)
207+
end
208+
end
209+
```
210+
211+
By using ERB templates we support multiple output formats. This flexibility allows us to generate:
212+
213+
1. **Markdown** documents for easy reading and version control
214+
2. **HTML** reports with interactive features for easier navigation
215+
3. **PDF** documents (via HTML or Markdown + `pandoc` CLI tool) for formal submission to auditors
216+
217+
### Conclusion
218+
219+
By using this systematic approach to documenting our implementation of security requirements, we've significantly streamlined the audit preparation process for the iOS E-Rezept App. The combination of code annotations, automated document generation, and integration with our CI/CD pipeline ensures that:
220+
221+
1. Developers clearly document how they've implemented each requirement
222+
2. Auditors can easily trace requirements to their implementations
223+
3. Missing implementations are quickly identified and addressed
224+
4. The documentation stays synchronized with the codebase
225+
226+
This approach has not only made the audit process more efficient but has also improved the overall quality of our codebase by encouraging developers to think explicitly about security requirements as they write code.
227+
228+
The fastlane plugin we've developed for this purpose is open-source and available on GitHub, so other teams facing similar audit requirements can benefit from our approach.
229+
230+
---
231+
232+
# About the author
233+
234+
Martin Fiebig has worked as a software engineer for 21 years, 13 years on iOS and Apple platforms. His focus topics include large scale mobile applications and developer experience. Martin leads the iOS team at gematik since 2020 and the Mobile Chapter since 2023.

assets/css/style.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,11 @@ img {
4343
background-repeat: no-repeat;
4444
background-size: contain;
4545
}
46+
47+
.note {
48+
background-color: #fffadb;
49+
border: 1px #c2af4a solid;
50+
padding: 10px 10px 0;
51+
border-radius: 8px;
52+
margin: 10px 0;
53+
}

0 commit comments

Comments
 (0)