Skip to content

Commit 5a9ff3a

Browse files
committed
Trust ROS CI workspaces inside containers
1 parent b36e76b commit 5a9ff3a

5 files changed

Lines changed: 108 additions & 2 deletions

File tree

‎.github/workflows/build_ros2_overlay.yml‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ jobs:
8080
with:
8181
fetch-depth: 0
8282

83+
- name: Trust checked-out Git worktree
84+
shell: bash
85+
run: |
86+
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
87+
git -C "${GITHUB_WORKSPACE}" rev-parse --is-inside-work-tree
88+
8389
- name: Install ROS 2 overlay dependencies
8490
run: |
8591
apt-get update
@@ -159,6 +165,12 @@ jobs:
159165
with:
160166
fetch-depth: 0
161167

168+
- name: Trust checked-out Git worktree
169+
shell: bash
170+
run: |
171+
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
172+
git -C "${GITHUB_WORKSPACE}" rev-parse --is-inside-work-tree
173+
162174
- name: Install ROS 2 overlay dependencies
163175
run: |
164176
apt-get update

‎.github/workflows/build_ros2_overlay.yml.tpl‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ jobs:
4747
with:
4848
fetch-depth: 0
4949

50+
- name: Trust checked-out Git worktree
51+
shell: bash
52+
run: |
53+
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
54+
git -C "${GITHUB_WORKSPACE}" rev-parse --is-inside-work-tree
55+
5056
- name: Install ROS 2 overlay dependencies
5157
run: |
5258
apt-get update

‎tests/cmake/VerifyTemplateProjectRos2Overlay.cmake‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,26 @@ if(NOT _workflow_metadata_sync_count EQUAL 2)
879879
"Both ROS 2 workflow jobs must synchronize project metadata before rosdep; "
880880
"found ${_workflow_metadata_sync_count} invocations.")
881881
endif()
882+
string(REGEX MATCHALL
883+
"git config --global --add safe\\.directory \"\\$\\{GITHUB_WORKSPACE\\}\""
884+
_workflow_workspace_trusts
885+
"${_workflow_contents}")
886+
list(LENGTH _workflow_workspace_trusts _workflow_workspace_trust_count)
887+
string(REGEX MATCHALL
888+
"git -C \"\\$\\{GITHUB_WORKSPACE\\}\" rev-parse --is-inside-work-tree"
889+
_workflow_worktree_probes
890+
"${_workflow_contents}")
891+
list(LENGTH _workflow_worktree_probes _workflow_worktree_probe_count)
892+
if(NOT _workflow_workspace_trust_count EQUAL 2
893+
OR NOT _workflow_worktree_probe_count EQUAL 2)
894+
message(FATAL_ERROR
895+
"Both ROS 2 workflow jobs must trust and validate the exact mounted Git workspace; "
896+
"found ${_workflow_workspace_trust_count} trust commands and "
897+
"${_workflow_worktree_probe_count} worktree probes.")
898+
endif()
899+
if(_workflow_contents MATCHES "safe\\.directory[^\n]*[\"']?\\*")
900+
message(FATAL_ERROR "ROS 2 workflow must not trust every Git repository with safe.directory '*'.")
901+
endif()
882902
string(REGEX MATCHALL
883903
"git diff --exit-code -- ros2/\\*/package\\.xml"
884904
_workflow_manifest_drift_guards
@@ -978,6 +998,30 @@ if(NOT _project_workflow_metadata_sync_count EQUAL 1)
978998
"Generic ROS workflow must synchronize metadata exactly once; "
979999
"found ${_project_workflow_metadata_sync_count} invocations.")
9801000
endif()
1001+
string(REGEX MATCHALL
1002+
"git config --global --add safe\\.directory \"\\$\\{GITHUB_WORKSPACE\\}\""
1003+
_project_workflow_workspace_trusts
1004+
"${_workflow_template_contents}")
1005+
list(LENGTH
1006+
_project_workflow_workspace_trusts
1007+
_project_workflow_workspace_trust_count)
1008+
string(REGEX MATCHALL
1009+
"git -C \"\\$\\{GITHUB_WORKSPACE\\}\" rev-parse --is-inside-work-tree"
1010+
_project_workflow_worktree_probes
1011+
"${_workflow_template_contents}")
1012+
list(LENGTH
1013+
_project_workflow_worktree_probes
1014+
_project_workflow_worktree_probe_count)
1015+
if(NOT _project_workflow_workspace_trust_count EQUAL 1
1016+
OR NOT _project_workflow_worktree_probe_count EQUAL 1)
1017+
message(FATAL_ERROR
1018+
"Generic ROS workflow must trust and validate the exact mounted Git workspace once; "
1019+
"found ${_project_workflow_workspace_trust_count} trust commands and "
1020+
"${_project_workflow_worktree_probe_count} worktree probes.")
1021+
endif()
1022+
if(_workflow_template_contents MATCHES "safe\\.directory[^\n]*[\"']?\\*")
1023+
message(FATAL_ERROR "Generic ROS workflow must not trust every Git repository with safe.directory '*'.")
1024+
endif()
9811025
string(REGEX MATCHALL
9821026
"git diff --exit-code -- ros2/\\*/package\\.xml"
9831027
_project_workflow_manifest_drift_guards

‎tests/template_test/testRos2OverlayStatic.py‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,9 +364,21 @@ def test_workflowSyncsMetadataBeforeRosdepInstall(self) -> None:
364364
overlayJob_, rolloutJob_ = workflowText_.split(" rollout-rehearsal:", maxsplit=1)
365365
for jobText_ in (overlayJob_, rolloutJob_):
366366
installIndex_ = jobText_.find("apt-get install")
367+
trustIndex_ = jobText_.find(
368+
'git config --global --add safe.directory "${GITHUB_WORKSPACE}"'
369+
)
370+
worktreeIndex_ = jobText_.find(
371+
'git -C "${GITHUB_WORKSPACE}" rev-parse --is-inside-work-tree'
372+
)
367373
syncIndex_ = jobText_.find("./generate_version.sh --sync-ros2")
368374
rosdepIndex_ = jobText_.find("rosdep install --from-paths ros2")
369-
assert min(installIndex_, syncIndex_, rosdepIndex_) >= 0
370-
assert installIndex_ < syncIndex_ < rosdepIndex_
375+
assert min(
376+
installIndex_, trustIndex_, worktreeIndex_, syncIndex_, rosdepIndex_
377+
) >= 0
378+
assert trustIndex_ < worktreeIndex_ < syncIndex_ < rosdepIndex_
379+
assert installIndex_ < syncIndex_
371380
assert 'grep -q -- "--sync-ros2"' in jobText_
372381
assert 'grep -q -- "ROS2_PROJECT_METADATA_SYNC=1"' in jobText_
382+
383+
assert "safe.directory '*'" not in workflowText_
384+
assert 'safe.directory "*"' not in workflowText_

‎tests/template_test/testWorkflowTemplates.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717

1818
_PROJECT_WORKFLOW_MARKER = "# project-ci-template: generic"
1919
_MANIFEST_DRIFT_GUARD: str = "git diff --exit-code -- ros2/*/package.xml"
20+
_WORKSPACE_TRUST: str = (
21+
'git config --global --add safe.directory "${GITHUB_WORKSPACE}"'
22+
)
23+
_WORKTREE_PROBE: str = (
24+
'git -C "${GITHUB_WORKSPACE}" rev-parse --is-inside-work-tree'
25+
)
2026

2127
_TEMPLATE_ONLY_PATTERNS = (
2228
"VerifyTemplateProject",
@@ -240,6 +246,32 @@ def test_dormantWorkflowsContainOnlyProjectCi(self) -> None:
240246
requiredGate_,
241247
)
242248

249+
def test_ros2ContainerJobsTrustExactWorkspaceBeforeMetadataSync(self) -> None:
250+
workflowRoot_ = _RepoRoot() / ".github/workflows"
251+
activeText_ = (
252+
workflowRoot_ / "build_ros2_overlay.yml"
253+
).read_text(encoding="utf-8")
254+
templateText_ = (
255+
workflowRoot_ / "build_ros2_overlay.yml.tpl"
256+
).read_text(encoding="utf-8")
257+
258+
overlayJob_, rolloutJob_ = activeText_.split(
259+
" rollout-rehearsal:", maxsplit=1
260+
)
261+
for jobText_ in (overlayJob_, rolloutJob_, templateText_):
262+
trustIndex_ = jobText_.find(_WORKSPACE_TRUST)
263+
probeIndex_ = jobText_.find(_WORKTREE_PROBE)
264+
syncIndex_ = jobText_.find("./generate_version.sh --sync-ros2")
265+
assert min(trustIndex_, probeIndex_, syncIndex_) >= 0
266+
assert trustIndex_ < probeIndex_ < syncIndex_
267+
assert "safe.directory '*'" not in jobText_
268+
assert 'safe.directory "*"' not in jobText_
269+
270+
assert activeText_.count(_WORKSPACE_TRUST) == 2
271+
assert activeText_.count(_WORKTREE_PROBE) == 2
272+
assert templateText_.count(_WORKSPACE_TRUST) == 1
273+
assert templateText_.count(_WORKTREE_PROBE) == 1
274+
243275
def test_manifestDriftGuardRejectsTrackedChanges(self, tmp_path: Path) -> None:
244276
repositoryRoot_ = tmp_path / "manifest-drift"
245277
manifestPath_ = repositoryRoot_ / "ros2" / "demo" / "package.xml"

0 commit comments

Comments
 (0)