-
Notifications
You must be signed in to change notification settings - Fork 32
feat: Add GitHub Artifact Attestations support #342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
31dee61
6b189b4
34bb11b
e0e5a2f
b32d2e8
f5c70fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| name: Generate Artifact Attestations | ||
|
|
||
| on: | ||
| workflow_dispatch: # Allow manual trigger | ||
| push: | ||
| tags: | ||
| - 'v*' # Run on version tags | ||
| - 'demo-*' # Run on demo releases | ||
|
|
||
| permissions: | ||
| contents: read | ||
| packages: write | ||
| id-token: write # Needed for GitHub OIDC token | ||
|
|
||
| jobs: | ||
| generate-attestation: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Generate .deb package | ||
| run: make deb-package | ||
|
|
||
| - name: Sign and generate attestation | ||
| uses: slsa-framework/slsa-github-generator@v1 | ||
| with: | ||
| base64-subjects: ${{ steps.hash.outputs.hashes }} | ||
| provenance-trigger: 'tag' | ||
|
|
||
| - name: Upload attestation | ||
| uses: actions/upload-artifact@v3 | ||
| with: | ||
| name: attestations | ||
| path: | | ||
| *.intoto.jsonl | ||
| *.sig | ||
|
|
||
| - name: Attach to release | ||
| if: startsWith(github.ref, 'refs/tags/') | ||
| uses: softprops/action-gh-release@v1 | ||
| with: | ||
| files: | | ||
| *.intoto.jsonl | ||
| *.sig |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| // Copyright 2025 Contributors to the Veraison project. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package api | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
| ) | ||
|
|
||
| // InstallationInfo contains information about how this Veraison instance | ||
| // was installed and its artifact attestations | ||
| type InstallationInfo struct { | ||
| // Version of Veraison | ||
| Version string `json:"version"` | ||
|
|
||
| // Type of installation (deb, rpm, native, container) | ||
| InstallType string `json:"install_type"` | ||
|
|
||
| // Installation time (when package was installed) | ||
| InstallTime string `json:"install_time,omitempty"` | ||
|
|
||
| // Path to the artifact attestation file if available | ||
| AttestationPath string `json:"attestation_path,omitempty"` | ||
|
|
||
| // Attestation digest (sha256 of the installation artifact) | ||
| ArtifactDigest string `json:"artifact_digest,omitempty"` | ||
| } | ||
|
|
||
| // GetInstallationInfo attempts to gather information about how this instance | ||
| // was installed and any available attestations | ||
| func GetInstallationInfo() (*InstallationInfo, error) { | ||
| info := &InstallationInfo{} | ||
|
|
||
| // Try to detect installation type and gather info | ||
| if isDebPackage() { | ||
| if err := getDebianInfo(info); err != nil { | ||
| return nil, err | ||
| } | ||
| } else if isRpmPackage() { | ||
| if err := getRpmInfo(info); err != nil { | ||
| return nil, err | ||
| } | ||
| } else if isContainer() { | ||
| if err := getContainerInfo(info); err != nil { | ||
| return nil, err | ||
| } | ||
| } else { | ||
| // Assume native installation | ||
| if err := getNativeInfo(info); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| return info, nil | ||
| } | ||
|
|
||
| // isDebPackage checks if this is a Debian package installation | ||
| func isDebPackage() bool { | ||
|
||
| _, err := os.Stat("/var/lib/dpkg/status") | ||
| return err == nil | ||
| } | ||
|
|
||
| // isRpmPackage checks if this is an RPM package installation | ||
| func isRpmPackage() bool { | ||
| _, err := os.Stat("/var/lib/rpm/Packages") | ||
|
||
| return err == nil | ||
| } | ||
|
|
||
| // isContainer checks if running in a container | ||
| func isContainer() bool { | ||
|
||
| _, err := os.Stat("/.dockerenv") | ||
| if err == nil { | ||
| return true | ||
| } | ||
|
|
||
| data, err := os.ReadFile("/proc/1/cgroup") | ||
| if err != nil { | ||
| return false | ||
| } | ||
| return strings.Contains(string(data), "docker") || | ||
| strings.Contains(string(data), "containerd") | ||
| } | ||
|
|
||
| // getDebianInfo gathers installation info from dpkg | ||
| func getDebianInfo(info *InstallationInfo) error { | ||
| info.InstallType = "deb" | ||
| // Implementation to get deb package details | ||
| // TODO: Get version from dpkg-query | ||
| // TODO: Get installation time from /var/lib/dpkg/info | ||
| // TODO: Look for attestation file in /usr/share/doc/veraison/ | ||
| return nil | ||
| } | ||
|
|
||
| // getRpmInfo gathers installation info from RPM | ||
| func getRpmInfo(info *InstallationInfo) error { | ||
| info.InstallType = "rpm" | ||
| // Implementation to get rpm package details | ||
| // TODO: Get version from rpm -q | ||
| // TODO: Get installation time from rpm database | ||
| // TODO: Look for attestation file in /usr/share/doc/veraison/ | ||
| return nil | ||
| } | ||
|
|
||
| // getContainerInfo gathers installation info for container deployments | ||
| func getContainerInfo(info *InstallationInfo) error { | ||
| info.InstallType = "container" | ||
| // Implementation for container deployments | ||
| // TODO: Get version from environment variable | ||
| // TODO: Get container creation time | ||
| // TODO: Look for attestation file in predefined location | ||
| return nil | ||
| } | ||
|
|
||
| // getNativeInfo gathers installation info for native deployments | ||
| func getNativeInfo(info *InstallationInfo) error { | ||
| info.InstallType = "native" | ||
| // Implementation for native deployments | ||
| // TODO: Get version from build info | ||
| // TODO: Get installation time from directory timestamp | ||
| // TODO: Look for attestation file in installation directory | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| // Copyright 2025 Contributors to the Veraison project. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package api | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestGetInstallationInfo(t *testing.T) { | ||
| info, err := GetInstallationInfo() | ||
| require.NoError(t, err) | ||
| require.NotNil(t, info) | ||
|
|
||
| // Installation type should be one of the supported types | ||
| assert.Contains(t, []string{"deb", "rpm", "container", "native"}, info.InstallType) | ||
|
|
||
| // Version should not be empty | ||
| assert.NotEmpty(t, info.Version) | ||
| } | ||
|
|
||
| func TestIsContainer(t *testing.T) { | ||
| // Note: This test may need to be skipped depending on the environment | ||
| isContainer := isContainer() | ||
| t.Logf("Running in container: %v", isContainer) | ||
| } | ||
|
|
||
| func TestInstallationInfoJSON(t *testing.T) { | ||
| info := InstallationInfo{ | ||
| Version: "1.0.0", | ||
| InstallType: "deb", | ||
| InstallTime: "2025-09-23T10:00:00Z", | ||
| AttestationPath: "/usr/share/doc/veraison/attestation.json", | ||
| ArtifactDigest: "sha256:1234567890abcdef", | ||
| } | ||
|
|
||
| data, err := json.Marshal(info) | ||
| require.NoError(t, err) | ||
|
|
||
| var decoded InstallationInfo | ||
| err = json.Unmarshal(data, &decoded) | ||
| require.NoError(t, err) | ||
|
|
||
| assert.Equal(t, info, decoded) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I question the utility of this field. For one, these categories are not mutually-exclusive.
debandrpmdeployments are by defiinition native installations (they're actually thenativedeployment that was packaged afterwards). And it is possible to installdeborrpmpackage inside a container.It is also non-trivial to establish this (this is currently not being done correctly). The simplest fool-proof way would be to generate metadata during installation that tells you how Sevices were installed, if that is something that is actually needed.
Finally, the way this currently implemented requires awareness of possible deployment models. This is undesirable. The code shouldn't need to be aware of how it going to be deployment. For example what happens when some 3rd party packages Services for another system (e.g. as an Arch package, or some cross-distribution system such as Flatpak)? A new packaging mechanism should not require code modification.