Skip to content
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

Add demo package manager + cedar impl #190

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
8 changes: 7 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ source "https://rubygems.org"
gemspec
gemspec path: "cli"

gem "base64", "~> 0.2.0" # Until https://github.com/vcr/vcr/commit/5c9230b43b6a51dec78941d16bf8e2954042964c is released
gem "rake", "~> 13.2"
gem "rubocop", "~> 1.67"
gem "rubocop-performance", "~> 1.23"
Expand All @@ -17,3 +16,10 @@ gem "thor", "~> 1.3"
gem "timecop", "~> 0.9.10"
gem "vcr", "~> 6.3"
gem "webmock", "~> 3.24"

gem "literal"
# gem "xdg", "~> 8.8"

gem "kdl", "~> 1.0"

gem "paramesan", "~> 1.0"
16 changes: 15 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,17 @@ GEM
hashdiff (1.1.1)
json (2.8.2)
json (2.8.2-java)
kdl (1.0.6)
base64 (~> 0.2.0)
bigdecimal (~> 3.1.6)
racc (~> 1.5)
simpleidn (~> 0.2.1)
language_server-protocol (3.17.0.3)
literal (1.1.0)
net-http (0.5.0)
uri
parallel (1.26.3)
paramesan (1.0.1)
parser (3.3.6.0)
ast (~> 2.4.1)
racc
Expand Down Expand Up @@ -78,6 +85,7 @@ GEM
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
simpleidn (0.2.3)
test-unit (3.6.4)
power_assert
thor (1.3.2)
Expand All @@ -101,7 +109,9 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
base64 (~> 0.2.0)
kdl (~> 1.0)
literal
paramesan (~> 1.0)
rake (~> 13.2)
rubocop (~> 1.67)
rubocop-performance (~> 1.23)
Expand All @@ -126,9 +136,12 @@ CHECKSUMS
hashdiff (1.1.1) sha256=c7966316726e0ceefe9f5c6aef107ebc3ccfef8b6db55fe3934f046b2cf0936a
json (2.8.2) sha256=dd4fa6c9c81daecf72b86ea36e56ed8955fdbb4d4dc379c93d313a59344486cf
json (2.8.2-java) sha256=7a7321efd8fad215a1afe92b5f16546203f193781da2d5c01587600cc00aa302
kdl (1.0.6) sha256=372b05de298a7fd757fbc421f71464807ffbf24f32862938236e807858d6f574
language_server-protocol (3.17.0.3) sha256=3d5c58c02f44a20d972957a9febe386d7e7468ab3900ce6bd2b563dd910c6b3f
literal (1.1.0) sha256=92dc4f78fc7b6e1eaefd9929eacfc80bd14182a55e883aadfe52f2a62b2c9275
net-http (0.5.0) sha256=ed7f88205afe03bf53142a4b81ded91f2c01522dcf03089cb6ad4acb476ce1da
parallel (1.26.3) sha256=d86babb7a2b814be9f4b81587bf0b6ce2da7d45969fab24d8ae4bf2bb4d4c7ef
paramesan (1.0.1) sha256=b1b2c0f855273201b94f0a4fb5c2ab11e67265ec4e1448181823438de2562db0
parser (3.3.6.0) sha256=25d4e67cc4f0f7cab9a2ae1f38e2005b6904d2ea13c34734511d0faad038bc3b
power_assert (2.0.4) sha256=43da564b535c758f2fc8c80fee031b744b4d4b388362d8c1ba669a0dc81be0c5
protobug (0.1.0) sha256=5bf1356cedf99dcf311890743b78f5e602f62ca703e574764337f1996b746bf2
Expand All @@ -152,6 +165,7 @@ CHECKSUMS
simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5
simplecov-html (0.12.3) sha256=4b1aad33259ffba8b29c6876c12db70e5750cb9df829486e4c6e5da4fa0aa07b
simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428
simpleidn (0.2.3) sha256=08ce96f03fa1605286be22651ba0fc9c0b2d6272c9b27a260bc88be05b0d2c29
test-unit (3.6.4) sha256=7a4bd7dd2fb6d372c8b7499d04fd6478b7163518c6e577cbf2a4cddc8cda7688
thor (1.3.2) sha256=eef0293b9e24158ccad7ab383ae83534b7ad4ed99c09f96f1a6b036550abbeda
timecop (0.9.10) sha256=12ba45ce57cdcf6b1043cb6cdffa6381fd89ce10d369c28a7f6f04dc1b0cd8eb
Expand Down
8 changes: 7 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ task :find_action_versions do # rubocop:disable Rake/Desc
@action_versions = actions.transform_values(&:first)
end

task test: %w[sigstore_conformance]
task test: %w[sigstore_conformance cedar_integration_tests]

desc "Update the vendored data files"
task :update_data do
Expand Down Expand Up @@ -168,6 +168,12 @@ GitRepo.define_task(tuf_conformance: %w[find_action_versions]).tap do |task|
task.commit = -> { @action_versions.fetch("theupdateframework/tuf-conformance") }
end

GitRepo.define_task(cedar_integration_tests: []).tap do |task|
task.path = "test/cedar-integration-tests"
task.url = "https://github.com/cedar-policy/cedar-integration-tests.git"
task.commit = "3903b933e29fd60f2c40d779b250cd4ffb150f5d"
end

namespace :tuf_conformance do
file "test/tuf-conformance/env/pyvenv.cfg" => :tuf_conformance do
sh "make", "dev", chdir: "test/tuf-conformance"
Expand Down
221 changes: 221 additions & 0 deletions bin/demo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
require "bundler/setup"

require "json"
require "rake"
require "base64"
require "tmpdir"
require "uri"
require "digest"
require "pathname"
require "xdg"
require "literal"
require "sigstore"
require "sigstore/cedar"

include FileUtils # rubocop:disable Style/MixinUsage

chdir Dir.mktmpdir

mkdir_p ["index"]

sh "curl", "--fail", "--silent", "http://localhost:8000/targets/trusted_root.json", "-o", "trusted_root.json"

def id_token(**claims)
claims.merge!(iss: "http://foo.com", aud: "sigstore", sub: "https://github.com/foo/workflow.yml@refs/heads/main",
iat: 0, exp: 9_999_999_999, nbf: 0)
claims[:email] = claims[:sub]

[
"",
JSON.dump(claims),
""
].map { Base64.strict_encode64(_1).chomp }.join(".")
end

def publish(name, version)
artifact = JSON.dump(name:, version:)
full_name = URI.encode_www_form({ name:, version: })
index = begin
JSON.parse File.read("index.json")
rescue Errno::ENOENT
[]
end
index << { name:, version:, full_name:, sha256: Digest::SHA256.hexdigest(artifact) }
File.write "index.json", JSON.dump(index)
File.write "index/#{full_name}", artifact
sign("index/#{full_name}", bundle: "index/#{full_name}.sigstore.jsonl")
end

def sign(file, bundle:, **kwargs)
sh "/Users/segiddins/Development/github.com/sigstore/sigstore-ruby/bin/sigstore-ruby", "sign",
"--trusted-root", "trusted_root.json", "--identity-token=#{id_token(**kwargs)}", "--bundle=#{bundle}",
file
end

publish "rails", "1.0"

module PolicyConstraint
def self.from_json(constraint)
Literal.check(actual: constraint, expected: Hash)
keys = constraint.keys
if keys == ["all"]
all = constraint["all"]
Literal.check(actual: all, expected: Array)
AllOf.new(all.map { from_json(_1) })
else
ID.new(**constraint)
end
end
end

class ID < Literal::Struct
include PolicyConstraint
prop :matchers, _Hash(
_Union(String), _Union(String, Integer)
), :**
end

class AllOf < Literal::Struct
include PolicyConstraint
prop :of, _Array(PolicyConstraint), :positional
end

class AtLeast < Literal::Struct
include PolicyConstraint
prop :count, Integer, :positional, default: 1
prop :of, _Array(PolicyConstraint)
end

class Policy < Literal::Struct
prop :name, String
prop :min, _String?
prop :max, _String?
prop :source, _String?
prop :platform, _String?
prop :expected, _Array(PolicyConstraint)

def self.from_json(json)
new(
name: json["name"],
min: json["min"],
max: json["max"],
expected: json["expected"].map do |constraint|
PolicyConstraint.from_json(constraint)
end
)
end
end

require "yaml"
pp Policy.from_json(YAML.load(<<~YAML).dig("rubygems", 0))
rubygems:
- name: rails
min: 0.0.0
max: 7.0.0
source: "https://rubygems.org"
expected:
- issuer: "https://token.actions.githubusercontent.com"
- all:
- Issuer: "https://token.actions.githubusercontent.com"
Build Config Digest: "sha256:1234"
YAML

class Installer
attr_reader :home, :dir

def initialize(machine, project, requirements = {})
@machine = machine
@project = project
@requirements = requirements

@dir = Pathname("projects") / @project
@home = Pathname(@machine).join("home")
[@dir, @home].each(&:mkpath)
@xdg = XDG::Environment.new(environment: { "HOME" => @home.to_s })
end

def self.call(...)
new(...).call
end

def call
installed = install(resolve)
lock(installed)
end

def resolve
index = JSON.parse File.read("index.json")
index.select! do |artifact|
@requirements[artifact["name"]] == artifact["version"]
end

raise StandardError, "Missing artifacts" unless index.size == @requirements.size

index
end

def install(resolve)
# 1) check if existing signatures satisfy the policy
# 2) if not, download the signatures and verify it
# 2) download artifact if missing from the cache, verifying checksum
# 3) copy artifact to the project's directory

resolve.map do |artifact|
signatures = JSON.parse File.read("index/#{artifact["full_name"]}.sigstore.jsonl")
{ artifact:, attestations: [signatures] }
end

# policy_set = Sigstore::Cedar::PolicySet.parse <<~CEDAR
# @Foo("bar")
# permit (
# principal,
# action,
# resource in package::"name=rails"
# )
# when { resource }
# // when { resource.version.greaterThanOrEqual(gem_version("1.0")) }
# // when { resource.version.lessThan(gem_version("2.0")) }
# ;
# CEDAR
# entities = Sigstore::Cedar::Authorizer::Entities.new(packages.map do |pkg|
# parents = pkg[:attestations].flat_map do |a|
# pp a
# end
# Sigstore::Cedar::Entity.new(
# uid: Sigstore::Cedar::Entity::UID.new(type: "package", id: pkg[:artifact]["full_name"]),
# parents: [
# Sigstore::Cedar::Entity::UID.new(type: "package", id: "name=#{pkg[:artifact].fetch("name")}"),
# Sigstore::Cedar::Entity::UID.new(type: "sigstore::issuer", id: "https://token.actions.githubusercontent.com")
# ],
# attrs: {}
# )
# end)
# authorizer = Sigstore::Cedar::Authorizer.new(policy_set:, entities:)

# unverified = packages.reject do |package|
# resp.verb == "allow"
# end
# # pp authorizer
# # pp unverified
end

def signatures_path(artifact)
purl = "pkg:demo/#{artifact["name"]}"
@xdg.state_home / "signatures" / "#{URI.encode_uri_component(purl)}.jsonl"
end

def lock(installed)
File.write dir / "requirements.json", JSON.dump(@requirements)
File.write dir / "requirements.lock.json", JSON.dump(installed.map do |i|
i[:artifact]
end)
end
end

Installer.call("machine_1", "a", { "rails" => "1.0" })

# sh "code", "."
Loading
Loading