Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/current.yml
Original file line number Diff line number Diff line change
Expand Up @@ -546,11 +546,19 @@ jobs:
BUNDLE_GEMFILE: ${{ github.workspace }}/gems/ast-merge-git/Appraisal.root.gemfile
K_JEM_TEMPLATING: true
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
STRUCTUREDMERGE_CORE_DEV: ${{ github.workspace }}/kernel
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Checkout kernel for the local core override
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: structuredmerge/structuredmerge
ref: main
path: kernel
persist-credentials: false
- name: Clone benchmark corpus
shell: bash
run: |
Expand All @@ -570,6 +578,7 @@ jobs:
BUNDLE_GEMFILE: ${{ github.workspace }}/gems/ast-merge-git/Appraisal.root.gemfile
K_JEM_TEMPLATING: true
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
STRUCTUREDMERGE_CORE_DEV: ${{ github.workspace }}/kernel
with:
ruby-version: ruby
bundler: latest
Expand All @@ -578,12 +587,28 @@ jobs:
working-directory: gems/ast-merge-git
appraisal-name: benchmark-dev
appraisal-root-gemfile: Appraisal.root.gemfile
pre-bundle-gems: |
nomono --source https://gem.coop --source https://rubygems.org
- name: Compile the kernel Ruby binding
working-directory: kernel/packages/ruby
shell: bash
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/kernel/packages/ruby/Gemfile
run: |
set -euo pipefail
ulimit -c 0
export CARGO_BUILD_JOBS="1" CARGO_INCREMENTAL="0"
export CARGO_TARGET_DIR="${GITHUB_WORKSPACE}/kernel/tmp/benchmark-core-target"
mkdir -p ../../tmp
bundle install --jobs 4 --retry 8
bundle exec rake compile
- name: Run affected-capability benchmark
working-directory: gems/ast-merge-git
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
K_JEM_TEMPLATING: true
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
STRUCTUREDMERGE_CORE_DEV: ${{ github.workspace }}/kernel
run: |
set -euo pipefail

Expand Down Expand Up @@ -703,15 +728,24 @@ jobs:
BUNDLE_GEMFILE: ${{ github.workspace }}/gems/tree_haver/Appraisal.root.gemfile
K_JEM_TEMPLATING: true
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
STRUCTUREDMERGE_CORE_DEV: ${{ github.workspace }}/kernel
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Checkout kernel for the local core override
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: structuredmerge/structuredmerge
ref: main
path: kernel
persist-credentials: false
- name: Setup Ruby
uses: appraisal-rb/setup-ruby-flash@6f8ad36ba7488db591541483afb5464d01e8861b # v2.14
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/gems/tree_haver/Appraisal.root.gemfile
K_JEM_TEMPLATING: true
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
STRUCTUREDMERGE_CORE_DEV: ${{ github.workspace }}/kernel
with:
ruby-version: ruby
bundler: latest
Expand All @@ -720,12 +754,28 @@ jobs:
working-directory: gems/tree_haver
appraisal-name: tslp-cold-start
appraisal-root-gemfile: Appraisal.root.gemfile
pre-bundle-gems: |
nomono --source https://gem.coop --source https://rubygems.org
- name: Compile the kernel Ruby binding
working-directory: kernel/packages/ruby
shell: bash
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/kernel/packages/ruby/Gemfile
run: |
set -euo pipefail
ulimit -c 0
export CARGO_BUILD_JOBS="1" CARGO_INCREMENTAL="0"
export CARGO_TARGET_DIR="${GITHUB_WORKSPACE}/kernel/tmp/tslp-core-target"
mkdir -p ../../tmp
bundle install --jobs 4 --retry 8
bundle exec rake compile
- name: Run cold-start TSLP checks
working-directory: gems/tree_haver
env:
K_JEM_TEMPLATING: true
K_SOUP_COV_DO: false
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
STRUCTUREDMERGE_CORE_DEV: ${{ github.workspace }}/kernel
run: |
set -euo pipefail
export TREE_SITTER_LANGUAGE_PACK_CACHE_DIR
Expand Down
2 changes: 1 addition & 1 deletion .structuredmerge/fixtures.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
repository: https://github.com/structuredmerge/structuredmerge-fixtures.git
branch: main
revision: ad5fd0b66b084e63576e6b006a1addf5bad8fcf5
revision: 9c6e4bb0625f28207d1f40e5d103936eed56154a
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Please file a bug if you notice a violation of semantic versioning.

### Added

- kettle-jem: the `current.yml` workflow template now removes the eMudhra-related roots that Apple still ships in `/etc/ssl/cert.pem` as its first step (before checkout) on macOS runners, when `macos` is a declared platform. The inlined script, `lib/kettle/jem/ci/harden_macos_ca_bundle.sh`, matches by certificate subject, logs each removed fingerprint and fails if any remains; it is not emitted for destinations without macOS lanes (galadran/we-have-a-problem.com#157)

- ast-crispr-yaml-psych: `Selectors.mapping_entry` and `Selectors.sequence_item` can be used as `Insert` destinations (insert after the matched owner) (#105)

### Changed
Expand Down
28 changes: 28 additions & 0 deletions gems/ast-merge-git/Appraisal.root.gemfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# frozen_string_literal: true

# kettle-jem:freeze
# To retain chunks of comments & code during kettle-jem templating:
# Wrap custom sections with freeze markers (e.g., as above and below this comment chunk).
# kettle-jem will then preserve content between those markers across template runs.
# kettle-jem:unfreeze

source 'https://gem.coop'

# Shared baseline for the Appraisal2 CI lanes in this gem.
Expand All @@ -25,3 +31,25 @@ if !structuredmerge_dev_root.empty? && !%w[false 0 no off].include?(structuredme
end
end
end

if respond_to?(:generator_only)
generator_only do
if ENV.fetch('K_JEM_TEMPLATING', 'false').casecmp('true').zero?
eval_gemfile 'gemfiles/modular/templating.gemfile'
end
end
end
# kettle-jem:freeze
# The lockstep tree_haver depends on structuredmerge-core ~> 0.2, which no
# registry carries before the kernel publishes. Appraisal's root resolution
# runs under plain Bundler, where generator_only is unavailable, so wire core
# from the local kernel checkout through the modular templating gemfile
# whenever the family dev env is engaged. Without that env the modular file
# retains registry mode, and published gems resolve normally.
unless respond_to?(:generator_only)
if ENV.fetch('K_JEM_TEMPLATING', 'false').casecmp('true').zero? ||
!ENV.fetch('STRUCTUREDMERGE_DEV', '').strip.empty?
eval_gemfile 'gemfiles/modular/templating.gemfile'
end
end
# kettle-jem:unfreeze
43 changes: 40 additions & 3 deletions gems/kettle-jem/lib/kettle/jem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7825,8 +7825,10 @@ def finalize_gemfile_template_source(recipe, content, destination_content, facts
end
removable = [package_name, *(runtime_dependencies - named_fragment_dependencies)]
# A modular Gemfile may repeat a runtime gem only to select its local
# source. Keep explicit path overrides while pruning ordinary duplicates.
removable -= gemfile_path_source_override_names(template_content)
# source. Keep explicit path overrides while pruning ordinary
# duplicates. The package's own gem is never exempt: a self-reference
# is a prohibited declaration, not a local source override.
removable -= (gemfile_path_source_override_names(template_content) - [package_name])
output = remove_gemfile_dependency_blocks(content, removable)
output = remove_gemfile_percent_w_entries(output, removable)
end
Expand Down Expand Up @@ -9483,7 +9485,9 @@ def merge_gemfile_template_policy(content, facts:, template_content: nil, preser
# A Gemfile path declaration can override the source of a runtime gemspec
# dependency. Keep those declarations so local development can resolve an
# unpublished dependency without changing the runtime dependency graph.
removable_gems -= gemfile_path_source_override_names(template_content.to_s)
# The package's own gem is never exempt: a self-reference is a prohibited
# declaration, not a local source override.
removable_gems -= (gemfile_path_source_override_names(template_content.to_s) - [package_name])
removable_gems << "version_gem" unless version_gem_enabled?(facts)
pruned = remove_gemfile_dependency_blocks(content, removable_gems)
pruned = remove_gemfile_percent_w_entries(pruned, [package_name]) unless preserve_self_word_entries
Expand Down Expand Up @@ -14985,6 +14989,7 @@ def template_tokens(facts, funding)
"KJ|CI:EXEC_CMD" => facts.dig(:ci, :exec_cmd).to_s,
"KJ|CI:OS_MATRIX_ENTRIES" => github_actions_current_os_matrix_entries(facts),
"KJ|CI:OS_TEST_STEPS" => github_actions_current_os_test_steps(facts),
"KJ|CI:MACOS_HARDENING_STEP" => github_actions_macos_hardening_step(facts),
"KJ|README:CURRENT_MRI_PLATFORMS" => readme_current_mri_platforms(facts),
"KJ|RAKE:TOP_LEVEL_DOCS_TASK" => rake_top_level_docs_task_token(facts).rstrip,
"KJ|GITHUB_ACTIONS:COVERAGE_UPLOAD_STEPS" => github_actions_coverage_steps(disabled_integrations: facts.dig(:integrations, :disabled))
Expand Down Expand Up @@ -20960,6 +20965,38 @@ def github_actions_current_os_matrix_entries(facts)
end.join("\n")
end

MACOS_CA_HARDENING_SCRIPT = File.expand_path("jem/ci/harden_macos_ca_bundle.sh", __dir__)
private_constant :MACOS_CA_HARDENING_SCRIPT

# The first step of current.yml's test job when macos is a declared platform:
# removes the eMudhra-related roots Apple still ships in /etc/ssl/cert.pem
# (https://github.com/galadran/we-have-a-problem.com/issues/157). The script is
# inlined, not fetched or run from the repo, because it has to run before the
# first download, and actions/checkout runs git. Like the Windows guard, the
# step exists only when its platform is declared, so no dead `if:` is emitted
# for linux-only or linux+windows destinations. Returns the step plus a blank
# line, or "" so the token vanishes without leaving a stray line behind. The
# script is a copy of structuredmerge/structuredmerge
# workspace-scripts/harden_macos_ca_bundle.sh (tested there against a bundle
# containing an eMudhra root).
def github_actions_macos_hardening_step(facts)
platforms = facts.dig(:ci, :platforms)
platforms = DEFAULT_PLATFORMS if platforms.nil? || platforms.empty?
return "" unless platforms.include?("macos")

header = [
" - name: Remove eMudhra roots from the macOS CA bundle",
" if: runner.os == 'macOS'",
" shell: bash",
" run: |"
]
script = File.read(MACOS_CA_HARDENING_SCRIPT).rstrip.lines(chomp: true).map do |line|
line.empty? ? "" : " #{line}"
end

"#{(header + script).join("\n")}\n\n"
end

# The current.yml test steps, generated from the same platforms facts. The
# Windows-only guard and step exist only when windows is declared, so no
# tautological `if:` survives in a linux+macos destination (spec slice
Expand Down
75 changes: 75 additions & 0 deletions gems/kettle-jem/lib/kettle/jem/ci/harden_macos_ca_bundle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/sh
# Remove the eMudhra-related roots from the macOS system CA bundle.
#
# Apple's /etc/ssl/cert.pem still carries roots from the eMudhra incident, and the
# developer tools Apple ships (git, curl, python3) read that file, so a network
# attacker could tamper with downloads made during a release build. GitHub's macOS
# runners are ephemeral, so removing the roots at job start needs no re-application
# after OS updates. Reference: https://github.com/galadran/we-have-a-problem.com/issues/157
#
# It must run BEFORE the first step that downloads anything (actions/checkout runs
# git), so the workflows inline this exact file. A contract test keeps them identical.
#
# Matching is by certificate subject so no fingerprints need to be guessed; every
# removed certificate is printed with its SHA-256 fingerprint for the audit log, and
# the script fails if any matching certificate is still present afterwards.
#
# Environment (for tests): CA_BUNDLE (default /etc/ssl/cert.pem), SUDO (default sudo).
set -eu

bundle="${CA_BUNDLE:-/etc/ssl/cert.pem}"
sudo_cmd="${SUDO-sudo}"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT

matches() {
case "$1" in
*"AAA Certificate Services"* | *"emSign ECC Root CA - G3"* | *[eE][mM]udhra*) return 0 ;;
*) return 1 ;;
esac
}

total="$(grep -c -- '-----BEGIN CERTIFICATE-----' "$bundle")"
[ "$total" -ge 100 ] || { echo "refusing to edit $bundle: only $total certificates" >&2; exit 1; }

awk -v dir="$work" '
/-----BEGIN CERTIFICATE-----/ { if (out != "") close(out); n++; out = dir "/c" n ".pem" }
out != "" { print > out }
' "$bundle"

: > "$work/clean.pem"
removed=0
i=1
while [ "$i" -le "$total" ]; do
file="$work/c$i.pem"
subject="$(openssl x509 -in "$file" -noout -subject 2>/dev/null || true)"
if [ -n "$subject" ] && matches "$subject"; then
fingerprint="$(openssl x509 -in "$file" -noout -fingerprint -sha256)"
echo "removing: $subject ($fingerprint)"
removed=$((removed + 1))
else
cat "$file" >> "$work/clean.pem"
fi
i=$((i + 1))
done

if [ "$removed" -gt 0 ]; then
$sudo_cmd tee "$bundle" < "$work/clean.pem" > /dev/null
fi

# Verify what is on disk now, not what we intended to write.
after="$(grep -c -- '-----BEGIN CERTIFICATE-----' "$bundle")"
[ "$after" -eq $((total - removed)) ] || { echo "certificate count mismatch: $after != $total - $removed" >&2; exit 1; }
rm -f "$work"/c*.pem
awk -v dir="$work" '
/-----BEGIN CERTIFICATE-----/ { if (out != "") close(out); n++; out = dir "/v" n ".pem" }
out != "" { print > out }
' "$bundle"
for file in "$work"/v*.pem; do
subject="$(openssl x509 -in "$file" -noout -subject 2>/dev/null || true)"
if [ -n "$subject" ] && matches "$subject"; then
echo "still trusted after cleanup: $subject" >&2
exit 1
fi
done
echo "CA bundle $bundle: $total certificates, removed $removed, $after remain."
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
{KJ|CI:OS_MATRIX_ENTRIES}

steps:
- name: Checkout
{KJ|CI:MACOS_HARDENING_STEP} - name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1072,9 +1072,11 @@
{
"KJ|CI:EXEC_CMD" => "bundle exec kettle-test",
"KJ|CI:OS_MATRIX_ENTRIES" => described_class.send(:github_actions_current_os_matrix_entries, {ci: {exec_cmd: "bundle exec kettle-test", platforms: %w[linux macos windows]}}),
"KJ|CI:OS_TEST_STEPS" => described_class.send(:github_actions_current_os_test_steps, {ci: {exec_cmd: "bundle exec kettle-test", platforms: %w[linux macos windows]}})
"KJ|CI:OS_TEST_STEPS" => described_class.send(:github_actions_current_os_test_steps, {ci: {exec_cmd: "bundle exec kettle-test", platforms: %w[linux macos windows]}}),
"KJ|CI:MACOS_HARDENING_STEP" => described_class.send(:github_actions_macos_hardening_step, {ci: {exec_cmd: "bundle exec kettle-test", platforms: %w[linux macos windows]}})
}
)
expect(resolved_current_workflow.index("Remove eMudhra roots from the macOS CA bundle")).to be < resolved_current_workflow.index("- name: Checkout")
expect(resolved_current_workflow).to include("appraisal: \"current\"\n os: ubuntu-latest")
expect(resolved_current_workflow).to include("appraisal: \"current\"\n os: macos-latest")
expect(resolved_current_workflow).to include("appraisal: \"current\"\n os: windows-latest")
Expand Down
Loading
Loading