Skip to content

Commit

Permalink
feat: add a new check to validate npm provenance and extract facts fo…
Browse files Browse the repository at this point in the history
…r policy engine

Signed-off-by: behnazh-w <[email protected]>
  • Loading branch information
behnazh-w committed Oct 24, 2024
1 parent 6a210f4 commit 178c057
Show file tree
Hide file tree
Showing 8 changed files with 491 additions and 15 deletions.
191 changes: 184 additions & 7 deletions src/macaron/database/table_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

from macaron.artifact.maven import MavenSubjectPURLMatcher
from macaron.database.database_manager import ORMBase
from macaron.database.db_custom_types import RFC3339DateTime
from macaron.database.db_custom_types import DBJsonDict, RFC3339DateTime
from macaron.errors import InvalidPURLError
from macaron.slsa_analyzer.provenance.intoto import InTotoPayload, ProvenanceSubjectPURLMatcher
from macaron.slsa_analyzer.slsa_req import ReqName
Expand Down Expand Up @@ -161,7 +161,7 @@ class Component(PackageURLMixin, ORMBase):
checkfacts: Mapped[list["CheckFacts"]] = relationship(back_populates="component", lazy="immediate")

#: The one-to-many relationship with provenances.
provenance: Mapped[list["Provenance"]] = relationship(back_populates="component", lazy="immediate")
provenance: Mapped[list["ProvenanceFacts"]] = relationship(back_populates="component", lazy="immediate")

#: The bidirectional many-to-many relationship for component dependencies.
dependencies: Mapped[list["Component"]] = relationship(
Expand Down Expand Up @@ -464,7 +464,7 @@ class CheckFacts(ORMBase):
}


class Provenance(ORMBase):
class ProvenanceFacts(ORMBase):
"""ORM class for a provenance document."""

__tablename__ = "_provenance"
Expand All @@ -479,7 +479,7 @@ class Provenance(ORMBase):
component: Mapped["Component"] = relationship(back_populates="provenance")

#: The SLSA version.
version: Mapped[str] = mapped_column(String, nullable=False)
version: Mapped[str] = mapped_column(String, nullable=True)

#: The release tag commit sha.
release_commit_sha: Mapped[str] = mapped_column(String, nullable=True)
Expand All @@ -488,12 +488,189 @@ class Provenance(ORMBase):
release_tag: Mapped[str] = mapped_column(String, nullable=True)

#: The provenance payload content in JSON format.
provenance_json: Mapped[str] = mapped_column(String, nullable=False)
provenance_json: Mapped[dict] = mapped_column(DBJsonDict, nullable=False)

#: The provenance statement.
statement: Mapped["Statement"] = relationship(back_populates="provenance")

#: A one-to-many relationship with the release artifacts.
artifact: Mapped[list["ReleaseArtifact"]] = relationship(back_populates="provenance")


class Statement(ORMBase):
"""The ORM class for provenance statement."""

__tablename__ = "_statement"

#: The primary key.
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003

#: The foreign key to the software component.
provenance_id: Mapped[int] = mapped_column(Integer, ForeignKey(ProvenanceFacts.id), nullable=False)

#: A one-to-one relationship with software components.
provenance: Mapped["ProvenanceFacts"] = relationship(back_populates="statement")

#: Statement type.
_type: Mapped[str] = mapped_column(String, nullable=False)

#: Predicate Type.
predicate_type: Mapped[str] = mapped_column(String, nullable=False)

#: Provenance Subjects.
subject: Mapped[list["ProvenanceSubjectRaw"]] = relationship(back_populates="statement")

#: Provenance predicate.
predicate: Mapped["Predicate"] = relationship(back_populates="statement")


class ProvenanceSubjectRaw(ORMBase):
"""The ORM class for the provenance subject containing all the information."""

__tablename__ = "_subject"

#: The primary key.
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003

#: The foreign key to the software component.
statement_id: Mapped[int] = mapped_column(Integer, ForeignKey(Statement.id), nullable=False)

#: A one-to-one relationship with provenance statement.
statement: Mapped["Statement"] = relationship(back_populates="subject")

#: Subject name.
name: Mapped[str] = mapped_column(String, nullable=False)

#: Subject digests.
digest: Mapped["SubjectDigest"] = relationship(back_populates="subject")


class SubjectDigest(ORMBase):
"""The ORM class for the provenance subject digest."""

__tablename__ = "_subject_digest"

#: The primary key.
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003

#: The foreign key to the provenance subject.
subject_id: Mapped[int] = mapped_column(Integer, ForeignKey(ProvenanceSubjectRaw.id), nullable=False)

#: A one-to-one relationship with provenance subject.
subject: Mapped["ProvenanceSubjectRaw"] = relationship(back_populates="digest")

#: Digest.
sha512: Mapped[str] = mapped_column(String, nullable=False)


class Predicate(ORMBase):
"""The ORM class for provenance predicate."""

__tablename__ = "_predicate"

#: The primary key.
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003

#: The foreign key to the software component.
statement_id: Mapped[int] = mapped_column(Integer, ForeignKey(Statement.id), nullable=False)

#: A one-to-one relationship with provenance statement.
statement: Mapped["Statement"] = relationship(back_populates="predicate")

#: Build definition.
build_definition: Mapped["BuildDefinition"] = relationship(back_populates="predicate")


class BuildDefinition(ORMBase):
"""The ORM class for provenance predicate build definition."""

__tablename__ = "_build_definition"

#: The primary key.
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003

#: The foreign key to the software component.
predicate_id: Mapped[int] = mapped_column(Integer, ForeignKey(Predicate.id), nullable=False)

#: A one-to-one relationship with provenance predicate.
predicate: Mapped["Predicate"] = relationship(back_populates="build_definition")

#: Build type.
build_type: Mapped[str] = mapped_column(String, nullable=False)

#: External parameters in build definitions.
external_parameters: Mapped["ExternalParameters"] = relationship(back_populates="build_definition")

#: Internal parameters in build definitions.
internal_parameters: Mapped["InternalParameters"] = relationship(back_populates="build_definition")


class ExternalParameters(ORMBase):
"""The ORM class for provenance predicate build definition external parameters."""

__tablename__ = "_external_parameters"

#: The primary key.
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003

#: The foreign key to the software component.
build_definition_id: Mapped[int] = mapped_column(Integer, ForeignKey(BuildDefinition.id), nullable=False)

#: A one-to-one relationship with build definition.
build_definition: Mapped["BuildDefinition"] = relationship(back_populates="external_parameters")

#: External parameters in build definitions.
workflow: Mapped["Workflow"] = relationship(back_populates="external_parameters")


class Workflow(ORMBase):
"""The ORM class for provenance predicate build definition external parameters workflows."""

__tablename__ = "_workflow"

#: The primary key.
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003

#: The foreign key to the software component.
external_parameters_id: Mapped[int] = mapped_column(Integer, ForeignKey(ExternalParameters.id), nullable=False)

#: A one-to-one relationship with external_parameters.
external_parameters: Mapped["ExternalParameters"] = relationship(back_populates="workflow")

#: Workflow reference.
ref: Mapped[str] = mapped_column(String, nullable=False)

#: Workflow repository.
repository: Mapped[str] = mapped_column(String, nullable=False)

#: Workflow path.
path: Mapped[str] = mapped_column(String, nullable=False)


class InternalParameters(ORMBase):
"""The ORM class for provenance predicate build definition internal parameters."""

__tablename__ = "_internal_parameters"

#: The primary key.
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003

#: The foreign key to the software component.
build_definition_id: Mapped[int] = mapped_column(Integer, ForeignKey(BuildDefinition.id), nullable=False)

#: A one-to-one relationship with build definition.
build_definition: Mapped["BuildDefinition"] = relationship(back_populates="internal_parameters")

#: The GitHub event that triggered the publish.
github_event_name: Mapped[str] = mapped_column(String, nullable=False)

#: The GitHub repository ID that triggered the publish.
github_repository_id: Mapped[str] = mapped_column(String, nullable=False)

#: The GitHub repository owner ID that triggered the publish.
github_repository_owner_id: Mapped[str] = mapped_column(String, nullable=False)


class ReleaseArtifact(ORMBase):
"""The ORM class for release artifacts."""

Expand All @@ -509,10 +686,10 @@ class ReleaseArtifact(ORMBase):
slsa_verified: Mapped[bool] = mapped_column(Boolean, nullable=True)

#: The foreign key to the SLSA provenance.
provenance_id: Mapped[int] = mapped_column(Integer, ForeignKey(Provenance.id), nullable=True)
provenance_id: Mapped[int] = mapped_column(Integer, ForeignKey(ProvenanceFacts.id), nullable=True)

#: A many-to-one relationship with the SLSA provenance.
provenance: Mapped["Provenance"] = relationship(back_populates="artifact")
provenance: Mapped["ProvenanceFacts"] = relationship(back_populates="artifact")

#: The one-to-many relationship with the hash digests for this artifact.
digests: Mapped[list["HashDigest"]] = relationship(back_populates="artifact")
Expand Down
90 changes: 90 additions & 0 deletions src/macaron/policy_engine/prelude/intoto_policies.dl
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved. */
/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */

/* Souffle datalog rules to assist in authoring in-toto policies.*/

/**
* This relation provides the external parameters of a SLSA v1 provenance generated by npm.
The external parameters include details of the triggering hosted build service workflow.

Here is the related section of an example predicate we process in this relation:
"externalParameters": {
"workflow": {
"ref": "refs/heads/main",
"repository": "https://github.com/npm/node-semver",
"path": ".github/workflows/release.yml"
}
},

Parameters:
component_id: number
The target software component id.
purl: symbol
The Package URL identifier for the provenance subject.
ref: symbol
The Git reference.
repository: symbol
The repository URL.
path: symbol
The GitHub Actions workflow path.

*/
.decl slsa_v1_npm_external_parameters(component_id: number, purl: symbol, ref: symbol, repository: symbol, path: symbol)

slsa_v1_npm_external_parameters(component_id, purl, ref, repository, path):-
provenance(prov_id, component_id, _, _, _, _),
statement(stmt_id, prov_id, "https://in-toto.io/Statement/v1", "https://slsa.dev/provenance/v1"),
subject(sub_id, stmt_id, purl),
predicate(pred_id, stmt_id),
build_definition(build_id, pred_id, _),
external_parameters(external_params_id, build_id),
workflow(_, external_params_id, ref, repository, path).

/**
* This relation provides the external parameters of a SLSA v1 provenance generated by npm.
The external parameters include details of the triggering hosted build service workflow.

Here is the related section of an example predicate we process in this relation:
"internalParameters": {
"github": {
"event_name": "push",
"repository_id": "1357199",
"repository_owner_id": "6078720"
}
},


Parameters:
component_id: number
The target software component id.
purl: symbol
The Package URL identifier for the provenance subject.
github_event_name: symbol
TODO
github_repository_id: symbol
TODO
github_repository_owner_id: symbol
TODO

*/
.decl slsa_v1_npm_internal_parameters(
component_id: number,
purl: symbol,
github_event_name: symbol,
github_repository_id: symbol,
github_repository_owner_id: symbol
)

slsa_v1_npm_internal_parameters(
component_id,
purl,
github_event_name,
github_repository_id,
github_repository_owner_id
):-
provenance(prov_id, component_id, _, _, _, _),
statement(stmt_id, prov_id, "https://in-toto.io/Statement/v1", "https://slsa.dev/provenance/v1"),
subject(sub_id, stmt_id, purl),
predicate(pred_id, stmt_id),
build_definition(build_id, pred_id, _),
internal_parameters(_, build_id, github_event_name, github_repository_id, github_repository_owner_id).
3 changes: 2 additions & 1 deletion src/macaron/policy_engine/prelude/prelude.dl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (c) 2023 - 2023, Oracle and/or its affiliates. All rights reserved. */
/* Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved. */
/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */

/**
Expand All @@ -13,6 +13,7 @@
#include "helper_rules.dl"
#include "policy.dl"
#include "aggregate_rules.dl"
#include "intoto_policies.dl"

/* The fact import statements generated by the policy engine */
#include "import_data.dl"
Expand Down
53 changes: 53 additions & 0 deletions src/macaron/resources/policies/generic/npm_attestation.dl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved. */
/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */

#include "prelude.dl"

Policy("test_policy", component_id, "") :-
// Checks if the npm attestation has been successfully processed.
check_passed(component_id, "mcn_npm_attestation_validation_1"),
// This relation provides the external parameters of a SLSA v1 provenance generated by npm.
slsa_v1_npm_external_parameters(component_id, purl, ref, repository, path),
// This relation provides the internal parameters of a SLSA v1 provenance generated by npm.
slsa_v1_npm_internal_parameters(component_id, purl, event_name, repository_id, repository_owner_id),
// This match constraint makes sure the subjects we are interested in exist in the provenance.
match("pkg:npm/semver@.*", purl),
// Here we can add constraints that we are interested in.
approved_refs(ref),
approved_repository_owner_ids(repository_owner_id),
repository = "https://github.com/npm/node-semver",
path = ".github/workflows/release.yml".

Policy("test_policy", component_id, "") :-
// Checks if the npm attestation has been successfully processed.
check_passed(component_id, "mcn_npm_attestation_validation_1"),
// Checks if the repository URL in the provenance matches the repository metadata on deps.dev.
check_passed(component_id, "mcn_provenance_derived_repo_1"),
// Checks if the commit hash in the provenance matches the release tag.
check_passed(component_id, "mcn_provenance_derived_commit_1"),
// This relation provides the external parameters of a SLSA v1 provenance generated by npm.
slsa_v1_npm_external_parameters(component_id, purl, ref, repository, path),
// This relation provides the internal parameters of a SLSA v1 provenance generated by npm.
slsa_v1_npm_internal_parameters(component_id, purl, event_name, repository_id, repository_owner_id),
// This match constraint makes sure the subjects we are interested in exist in the provenance.
match("pkg:npm/semver@.*", purl),
// Here we can add constraints that we are interested in.
approved_refs(ref),
approved_repository_owner_ids(repository_owner_id),
path = ".github/workflows/release.yml".

// Create a relation containing the approved Git branches for publishing the artifact.
.decl approved_refs(name: symbol)
approved_refs("refs/heads/main").
approved_refs("refs/heads/master").
approved_refs("refs/heads/release").

// Create a relation containing the approved repository owner IDs for publishing the artifact.
.decl approved_repository_owner_ids(name: symbol)
approved_repository_owner_ids("6078720").
approved_repository_owner_ids("71096353").

// Apply the policy to the desired software components.
apply_policy_to("test_policy", component_id) :-
is_component(component_id, purl),
match("pkg:npm/semver@.*", purl).
Loading

0 comments on commit 178c057

Please sign in to comment.