Skip to content

Commit

Permalink
feat: Add lockfile attestor
Browse files Browse the repository at this point in the history
This commit introduces a new lockfiles attestor to capture and attest
the contents of common lockfiles in the project. The changes include:

- Add new file attestation/lockfiles/lockfiles.go implementing the lockfiles attestor
- Update imports.go to include the new lockfiles package

The lockfiles attestor captures contents of various lockfiles such as
Gemfile.lock, package-lock.json, yarn.lock, and others. It stores the
information in a slice of LockfileInfo structs, allowing for flexible
handling of multiple lockfiles.

This feature enhances the project's capability to track and verify
dependency information as part of the attestation process."

Signed-off-by: Frederick F. Kautz IV <frederick@testifysec.com>
  • Loading branch information
fkautz committed Oct 8, 2024
1 parent 192d5d8 commit c4cffcc
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 0 deletions.
113 changes: 113 additions & 0 deletions attestation/lockfiles/lockfiles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2024 The Witness Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package lockfiles

import (
"fmt"
"os"
"path/filepath"

"github.com/invopop/jsonschema"

"github.com/in-toto/go-witness/attestation"
)

const (
Name = "lockfiles"
Type = "https://witness.dev/attestations/lockfiles/v0.1"
RunType = attestation.PreMaterialRunType
)

func init() {
attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor {
return NewLockfilesAttestor()
})
}

func NewLockfilesAttestor() attestation.Attestor {
return &Attestor{
Lockfiles: []LockfileInfo{},
}
}

// Attestor implements the lockfiles attestation type
type Attestor struct {
Lockfiles []LockfileInfo `json:"lockfiles"`
}

// LockfileInfo stores information about a lockfile
type LockfileInfo struct {
Filename string `json:"filename"`
Content string `json:"content"`
}

// Name returns the name of the attestation type
func (a *Attestor) Name() string {
return "lockfiles"
}

// Attest captures the contents of common lockfiles
func (a *Attestor) Attest(ctx *attestation.AttestationContext) error {
lockfilePatterns := []string{
"Gemfile.lock", // Ruby
"package-lock.json", // Node.js (npm)
"yarn.lock", // Node.js (Yarn)
"Cargo.lock", // Rust
"poetry.lock", // Python (Poetry)
"Pipfile.lock", // Python (Pipenv)
"composer.lock", // PHP
"go.sum", // Go
"Podfile.lock", // iOS/macOS (CocoaPods)
"gradle.lockfile", // Gradle
"pnpm-lock.yaml", // Node.js (pnpm)
}

a.Lockfiles = []LockfileInfo{}

for _, pattern := range lockfilePatterns {
matches, err := filepath.Glob(pattern)
if err != nil {
return fmt.Errorf("error searching for %s: %w", pattern, err)
}

for _, match := range matches {
content, err := os.ReadFile(match)
if err != nil {
return fmt.Errorf("error reading %s: %w", match, err)
}
a.Lockfiles = append(a.Lockfiles, LockfileInfo{
Filename: filepath.Base(match),
Content: string(content),
})
}
}

return nil
}

// RunType implements attestation.Attestor.
func (o *Attestor) RunType() attestation.RunType {
return RunType
}

// // Schema implements attestation.Attestor.
func (o *Attestor) Schema() *jsonschema.Schema {
return jsonschema.Reflect(&o)
}

// Type implements attestation.Attestor.
func (o *Attestor) Type() string {
return Type
}
99 changes: 99 additions & 0 deletions attestation/lockfiles/lockfiles_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2024 The Witness Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package lockfiles

import (
"os"
"path/filepath"
"testing"

"github.com/in-toto/go-witness/attestation"
)

func TestAttestor_Attest(t *testing.T) {
// Create a temporary directory for test files
tempDir, err := os.MkdirTemp("", "lockfiles_test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)

// Create test lockfiles
testFiles := map[string]string{
"Gemfile.lock": "test content for Gemfile.lock",
"package-lock.json": "test content for package-lock.json",
}

for filename, content := range testFiles {
err := os.WriteFile(filepath.Join(tempDir, filename), []byte(content), 0644)
if err != nil {
t.Fatalf("Failed to create test file %s: %v", filename, err)
}
}

// Change to the temp directory
oldWd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current working directory: %v", err)
}
defer func() {
if err := os.Chdir(oldWd); err != nil {
t.Errorf("Failed to change back to original directory: %v", err)
}
}()

err = os.Chdir(tempDir)
if err != nil {
t.Fatalf("Failed to change to temp directory: %v", err)
}

// Create an Attestor and AttestationContext
attestor := &Attestor{}
ctx := &attestation.AttestationContext{}

// Run the Attest method
err = attestor.Attest(ctx)
if err != nil {
t.Fatalf("Attest failed: %v", err)
}

// Check if the lockfiles were captured correctly
if len(attestor.Lockfiles) != len(testFiles) {
t.Errorf("Expected %d lockfiles, but got %d", len(testFiles), len(attestor.Lockfiles))
}

for _, lockfile := range attestor.Lockfiles {
expectedContent, ok := testFiles[lockfile.Filename]
if !ok {
t.Errorf("Unexpected lockfile %s found in attestation", lockfile.Filename)
} else if lockfile.Content != expectedContent {
t.Errorf("Lockfile %s content mismatch. Got %s, want %s", lockfile.Filename, lockfile.Content, expectedContent)
}
delete(testFiles, lockfile.Filename)
}

if len(testFiles) > 0 {
for filename := range testFiles {
t.Errorf("Expected lockfile %s not found in attestation", filename)
}
}
}

func TestAttestor_Name(t *testing.T) {
attestor := &Attestor{}
if name := attestor.Name(); name != "lockfiles" {
t.Errorf("Incorrect attestor name. Got %s, want lockfiles", name)
}
}
1 change: 1 addition & 0 deletions imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
_ "github.com/in-toto/go-witness/attestation/jenkins"
_ "github.com/in-toto/go-witness/attestation/jwt"
_ "github.com/in-toto/go-witness/attestation/link"
_ "github.com/in-toto/go-witness/attestation/lockfiles"
_ "github.com/in-toto/go-witness/attestation/material"
_ "github.com/in-toto/go-witness/attestation/maven"
_ "github.com/in-toto/go-witness/attestation/oci"
Expand Down

0 comments on commit c4cffcc

Please sign in to comment.