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
224 changes: 123 additions & 101 deletions .github/workflows/update-ca-cert.yaml

Large diffs are not rendered by default.

71 changes: 64 additions & 7 deletions gpos/xml/scap/ssg/content/ssg-chainguard-gpos-ds.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6224,9 +6224,14 @@
or certificate store maintains a list of trusted root certificates. <html:pre>
Script Verification:
To manually verify, Ensure the ca-certificates package has not been
modified using apk audit or by ensuring the sha256 value matches the trusted
value, and that the SSL_CERT_FILE environment variable configured on the
image or container is set to /etc/ssl/certs/ca-certificates.crt.
modified using apk audit, or by running 'cd /etc/ssl/certs &amp;&amp;
sha256sum -c .ca-certificates.crt.sha256' so the bundle matches the digest
the package recorded for it, and that the SSL_CERT_FILE environment variable
configured on the image or container is set to
/etc/ssl/certs/ca-certificates.crt. On Java-based images, which additionally
ship a truststore at /etc/ssl/certs/java/cacerts, also run 'cd
/etc/ssl/certs/java &amp;&amp; sha256sum -c .cacerts.sha256'. Images with no
/etc/ssl/certs/java/cacerts have no truststore to verify.
</html:pre>
</ns0:description>
<ns0:ident system="http://cyber.mil/cci">CCI-004909</ns0:ident>
Expand Down Expand Up @@ -6682,30 +6687,51 @@
<definition id="oval:org.CABundleHash:def:1" version="1" class="compliance">
<metadata>
<title>Validate SHA-256 hash of CA bundle</title>
<description>Passes only if the CA bundle exists, has the correct hash, and the SSL_CERT_FILE environment variable configured on the image or container is set to /etc/ssl/certs/ca-certificates.crt.</description>
<description>Passes only if the CA bundle exists, its SHA-256 matches the digest recorded in the ca-certificates package stamp file /etc/ssl/certs/.ca-certificates.crt.sha256, and the SSL_CERT_FILE environment variable configured on the image or container is set to /etc/ssl/certs/ca-certificates.crt. Java images additionally ship a JKS/PKCS12 truststore at /etc/ssl/certs/java/cacerts; when that file is present it must likewise match the digest in /etc/ssl/certs/java/.cacerts.sha256. Images without Java carry no truststore and are unaffected.</description>
<affected family="unix">
<platform>Chainguard</platform>
</affected>
</metadata>
<criteria operator="AND">
<criterion test_ref="oval:org.CABundleHash:tst:1" comment="Ensure CA bundle file exists"/>
<criterion test_ref="oval:org.CABundleHash:tst:2" comment="Ensure CA bundle hash matches expected"/>
<criterion test_ref="oval:org.CABundleHash:tst:4" comment="Ensure CA bundle checksum stamp file exists and is well formed"/>
<criterion test_ref="oval:org.CABundleHash:tst:2" comment="Ensure CA bundle hash matches the checksum stamp file"/>
<criterion test_ref="oval:org.CABundleHash:tst:3" comment="Ensure SSL_CERT_FILE is set to the system CA bundle path"/>
<criteria operator="OR" comment="Java truststore, where one is present, matches its checksum stamp file">
<criterion test_ref="oval:org.CABundleHash:tst:5" comment="No Java truststore present (non-Java image)"/>
<criteria operator="AND">
<criterion test_ref="oval:org.CABundleHash:tst:6" comment="Ensure Java truststore checksum stamp file exists and is well formed"/>
<criterion test_ref="oval:org.CABundleHash:tst:7" comment="Ensure Java truststore hash matches the checksum stamp file"/>
</criteria>
</criteria>
</criteria>
</definition>
</definitions>
<tests>
<unix:file_test id="oval:org.CABundleHash:tst:1" version="1" check="all" check_existence="only_one_exists" comment="Ensure CA bundle file exists">
<unix:object object_ref="oval:org.CABundleHash:obj:1"/>
</unix:file_test>
<ind:filehash58_test id="oval:org.CABundleHash:tst:2" version="1" check="all" check_existence="only_one_exists" comment="Check that CA bundle file matches expected SHA-256 hash">
<ind:filehash58_test id="oval:org.CABundleHash:tst:2" version="1" check="all" check_existence="only_one_exists" comment="Check that CA bundle file matches the SHA-256 recorded in its checksum stamp file">
<ind:object object_ref="oval:org.CABundleHash:obj:2"/>
<ind:state state_ref="oval:org.CABundleHash:ste:1"/>
</ind:filehash58_test>
<ind:textfilecontent54_test id="oval:org.CABundleHash:tst:4" version="1" check="all" check_existence="only_one_exists" comment="Checksum stamp file exists and contains a single SHA-256 digest for ca-certificates.crt">
<ind:object object_ref="oval:org.CABundleHash:obj:4"/>
</ind:textfilecontent54_test>
<ind:environmentvariable58_test id="oval:org.CABundleHash:tst:3" version="1" check="all" check_existence="all_exist" comment="SSL_CERT_FILE equals /etc/ssl/certs/ca-certificates.crt">
<ind:object object_ref="oval:org.CABundleHash:obj:3"/>
<ind:state state_ref="oval:org.CABundleHash:ste:2"/>
</ind:environmentvariable58_test>
<unix:file_test id="oval:org.CABundleHash:tst:5" version="1" check="all" check_existence="none_exist" comment="No Java truststore at /etc/ssl/certs/java/cacerts">
<unix:object object_ref="oval:org.CABundleHash:obj:5"/>
</unix:file_test>
<ind:textfilecontent54_test id="oval:org.CABundleHash:tst:6" version="1" check="all" check_existence="only_one_exists" comment="Java truststore stamp file exists and contains a single SHA-256 digest for cacerts">
<ind:object object_ref="oval:org.CABundleHash:obj:6"/>
</ind:textfilecontent54_test>
<ind:filehash58_test id="oval:org.CABundleHash:tst:7" version="1" check="all" check_existence="only_one_exists" comment="Check that the Java truststore matches the SHA-256 recorded in its checksum stamp file">
<ind:object object_ref="oval:org.CABundleHash:obj:7"/>
<ind:state state_ref="oval:org.CABundleHash:ste:3"/>
</ind:filehash58_test>
</tests>
<objects>
<unix:file_object id="oval:org.CABundleHash:obj:1" version="1">
Expand All @@ -6719,16 +6745,47 @@
<ind:pid xsi:nil="true" datatype="int"/>
<ind:name>SSL_CERT_FILE</ind:name>
</ind:environmentvariable58_object>
<ind:textfilecontent54_object id="oval:org.CABundleHash:obj:4" version="1" comment="SHA-256 digest recorded by the ca-certificates package alongside the bundle">
<ind:path>/etc/ssl/certs</ind:path>
<ind:filename>.ca-certificates.crt.sha256</ind:filename>
<ind:pattern operation="pattern match">^([0-9a-fA-F]{64})[ \t]+\*?ca-certificates\.crt$</ind:pattern>
<ind:instance datatype="int">1</ind:instance>
</ind:textfilecontent54_object>
<unix:file_object id="oval:org.CABundleHash:obj:5" version="1" comment="Java truststore, present only in Java-based images">
<unix:filepath>/etc/ssl/certs/java/cacerts</unix:filepath>
</unix:file_object>
<ind:textfilecontent54_object id="oval:org.CABundleHash:obj:6" version="1" comment="SHA-256 digest recorded alongside the Java truststore">
<ind:path>/etc/ssl/certs/java</ind:path>
<ind:filename>.cacerts.sha256</ind:filename>
<ind:pattern operation="pattern match">^([0-9a-fA-F]{64})[ \t]+\*?cacerts$</ind:pattern>
<ind:instance datatype="int">1</ind:instance>
</ind:textfilecontent54_object>
<ind:filehash58_object id="oval:org.CABundleHash:obj:7" version="1">
<ind:filepath>/etc/ssl/certs/java/cacerts</ind:filepath>
<ind:hash_type>SHA-256</ind:hash_type>
</ind:filehash58_object>
</objects>
<states>
<ind:filehash58_state id="oval:org.CABundleHash:ste:1" version="1">
<ind:hash_type>SHA-256</ind:hash_type>
<ind:hash>61efbd6d3f829f71039c57b29dd37d15ac7f33c4ece861aaef8c7d7a519cd1d9</ind:hash>
<ind:hash datatype="string" operation="case insensitive equals" var_ref="oval:org.CABundleHash:var:1" var_check="all"/>
</ind:filehash58_state>
<ind:environmentvariable58_state id="oval:org.CABundleHash:ste:2" version="1">
<ind:value datatype="string" operation="equals">/etc/ssl/certs/ca-certificates.crt</ind:value>
</ind:environmentvariable58_state>
<ind:filehash58_state id="oval:org.CABundleHash:ste:3" version="1">
<ind:hash_type>SHA-256</ind:hash_type>
<ind:hash datatype="string" operation="case insensitive equals" var_ref="oval:org.CABundleHash:var:2" var_check="all"/>
</ind:filehash58_state>
</states>
<variables>
<local_variable id="oval:org.CABundleHash:var:1" version="1" datatype="string" comment="Expected CA bundle SHA-256, read from /etc/ssl/certs/.ca-certificates.crt.sha256">
<object_component object_ref="oval:org.CABundleHash:obj:4" item_field="subexpression"/>
</local_variable>
<local_variable id="oval:org.CABundleHash:var:2" version="1" datatype="string" comment="Expected Java truststore SHA-256, read from /etc/ssl/certs/java/.cacerts.sha256">
<object_component object_ref="oval:org.CABundleHash:obj:6" item_field="subexpression"/>
</local_variable>
</variables>
</oval_definitions>
</ds:extended-component>
</ds:data-stream-collection>
3 changes: 2 additions & 1 deletion tests/e2e/fixtures/baseline-clean/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
# - LibraryPermissions: /usr/lib owned by root:root
# - VarLogPermissions: /var/log owned by root:root
# - NoUsers: no interactive user accounts beyond the image default
# - CertificateAudit: /etc/ssl/certs/ca-certificates.crt matches the pinned SHA-256
# - CertificateAudit: /etc/ssl/certs/ca-certificates.crt matches the SHA-256 in
# /etc/ssl/certs/.ca-certificates.crt.sha256
#
# Expected result: a clean scan with no failures attributable to these rules.
FROM cgr.dev/chainguard/wolfi-base:latest@sha256:30f03343947c7ae3581fda727a6e2aa7b8ce7009b7bfc2ab8d5c9483ace5812f
Expand Down
7 changes: 4 additions & 3 deletions tests/e2e/fixtures/cabundle-tampered/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
#
# CertificateAudit violation fixture.
#
# Appends a bogus trust anchor to /etc/ssl/certs/ca-certificates.crt so
# the SHA-256 of the baked bundle diverges from the pinned value the
# CertificateAudit OVAL check expects. The rule must FAIL.
# Appends a bogus trust anchor to /etc/ssl/certs/ca-certificates.crt without
# touching the ca-certificates stamp file, so the SHA-256 of the baked bundle
# diverges from the digest recorded in /etc/ssl/certs/.ca-certificates.crt.sha256
# that the CertificateAudit OVAL check compares it against. The rule must FAIL.
FROM cgr.dev/chainguard/wolfi-base:latest@sha256:30f03343947c7ae3581fda727a6e2aa7b8ce7009b7bfc2ab8d5c9483ace5812f

LABEL dev.orbstack.add-ca-certificates=false
Expand Down
3 changes: 2 additions & 1 deletion tests/e2e/fixtures/cabundle-tampered/expected.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Fixture: cabundle-tampered
#
# /etc/ssl/certs/ca-certificates.crt has extra content appended, so its
# SHA-256 no longer matches the pinned hash. CertificateAudit must FAIL.
# SHA-256 no longer matches the digest recorded in the package's stamp file
# /etc/ssl/certs/.ca-certificates.crt.sha256. CertificateAudit must FAIL.
xccdf_mil.disa.stig_rule_SV-263659r982563_rule=fail
26 changes: 26 additions & 0 deletions tests/oscap-offline/internal/overlay/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,32 @@ func AppendFile(path string, extra []byte) Op {
}
}

// ReplaceFile overwrites an existing entry's content, leaving its header
// (mode, ownership, position in the tar) intact. The path must already exist
// and be a regular file, otherwise Apply returns a wrapped ErrNotFound or
// ErrNotRegular.
func ReplaceFile(path string, content []byte) Op {
return func(p *plan) {
e := p.requireReg(path)
if e == nil {
return
}
e.data = bytes.Clone(content)
e.hdr.Size = int64(len(e.data))
}
}

// RemoveFile drops an existing entry from the produced tar. The path must
// already exist, otherwise Apply returns a wrapped ErrNotFound.
func RemoveFile(path string) Op {
return func(p *plan) {
if p.require(path) == nil {
return
}
delete(p.byName, path)
}
}

// AddFile adds a new regular-file entry after the base entries. The path must
// not already exist, otherwise Apply returns a wrapped ErrExists.
func AddFile(path string, content []byte, mode int64, uid, gid int) Op {
Expand Down
56 changes: 56 additions & 0 deletions tests/oscap-offline/internal/overlay/overlay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,62 @@ func TestAppendFileRejectsNonRegular(t *testing.T) {
}
}

func TestReplaceFile(t *testing.T) {
t.Parallel()

base := buildTar(t, map[string]fileSpec{
"etc/f": {content: randBytes(40), mode: 0o444, uid: 3, gid: 5},
})

content := randBytes(12)
got := apply(t, base, ReplaceFile("etc/f", content))
_, byName := readEntries(t, got)

want := fileSpec{content: content, mode: 0o444, uid: 3, gid: 5}
if diff := cmp.Diff(want, byName["etc/f"], cmp.AllowUnexported(fileSpec{})); diff != "" {
t.Errorf("ReplaceFile changed more than content (-want,+got):\n%s", diff)
}
}

func TestRemoveFile(t *testing.T) {
t.Parallel()

base := buildTar(t, map[string]fileSpec{
"etc/keep": {content: []byte("k"), mode: 0o644, uid: 0, gid: 0},
"etc/drop": {content: []byte("d"), mode: 0o644, uid: 0, gid: 0},
})

got := apply(t, base, RemoveFile("etc/drop"))
order, _ := readEntries(t, got)

if diff := cmp.Diff([]string{"etc/keep"}, order); diff != "" {
t.Errorf("order mismatch after RemoveFile (-want,+got):\n%s", diff)
}
}

// TestReplaceAndRemoveMissingPath proves both ops surface ErrNotFound for a
// path absent from the base rather than silently no-op'ing.
func TestReplaceAndRemoveMissingPath(t *testing.T) {
t.Parallel()

base := buildTar(t, map[string]fileSpec{
"etc/f": {content: []byte("x"), mode: 0o644, uid: 0, gid: 0},
})

for name, op := range map[string]Op{
"ReplaceFile": ReplaceFile("etc/missing", []byte("y")),
"RemoveFile": RemoveFile("etc/missing"),
} {
t.Run(name, func(t *testing.T) {
t.Parallel()
var out bytes.Buffer
if err := Apply(bytes.NewReader(base), []Op{op}, &out); !errors.Is(err, ErrNotFound) {
t.Fatalf("Apply error = %v, want errors.Is ErrNotFound", err)
}
})
}
}

func TestChown(t *testing.T) {
t.Parallel()

Expand Down
Loading