Skip to content

Commit 251f08e

Browse files
Adds the crates.io ecosystem (#366)
* Add crates.io ecosystem * Improve docker file for go * Update build_docker.sh script * Update crates.io naming * Rename cratesio to crates * Actually rename cratesio to crates. Co-authored-by: Rex P Co-authored-by: Caleb Brown <[email protected]>
1 parent 076e3da commit 251f08e

File tree

7 files changed

+159
-1
lines changed

7 files changed

+159
-1
lines changed

build/build_docker.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ BASE_PATH="$(dirname $(dirname $(realpath $0)))"
77
REGISTRY=gcr.io/ossf-malware-analysis
88

99
# Mapping from the container name to the path containing the Dockerfile.
10-
declare -A ANALYSIS_IMAGES=( [node]=npm [python]=pypi [ruby]=rubygems [packagist]=packagist )
10+
declare -A ANALYSIS_IMAGES=( [node]=npm [python]=pypi [ruby]=rubygems [packagist]=packagist [crates.io]=crates.io )
1111

1212
pushd "$BASE_PATH/sandboxes"
1313
for image in "${!ANALYSIS_IMAGES[@]}"; do

cmd/analyze/Dockerfile

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
FROM golang@sha256:3c4de86eec9cbc619cdd72424abd88326ffcf5d813a8338a7743c55e5898734f as build
22
RUN apt-get update && apt-get install -y libpcap-dev
33
WORKDIR /src
4+
5+
# First cache the dependencies to avoid downloading again on code change
6+
COPY ./go.mod ./
7+
COPY ./go.sum ./
8+
RUN go mod download
9+
410
COPY . ./
511
RUN go build -o analyze cmd/analyze/main.go && go build -o worker cmd/worker/main.go
612

cmd/scheduler/Dockerfile

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
FROM golang@sha256:3c4de86eec9cbc619cdd72424abd88326ffcf5d813a8338a7743c55e5898734f as build
22
WORKDIR /src
3+
4+
# First cache the dependencies to avoid downloading again on code change
5+
COPY ./go.mod ./
6+
COPY ./go.sum ./
7+
RUN go mod download
8+
39
COPY . ./
410
RUN go build -o scheduler ./cmd/scheduler/main.go
511

internal/pkgecosystem/crates.io.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package pkgecosystem
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
)
8+
9+
type cratesJSON struct {
10+
Versions []struct {
11+
Num string `json:"num"`
12+
} `json:"versions"`
13+
}
14+
15+
func getCratesLatest(pkg string) (string, error) {
16+
resp, err := http.Get(fmt.Sprintf("https://crates.io/api/v1/crates/%s/versions", pkg))
17+
if err != nil {
18+
return "", err
19+
}
20+
defer resp.Body.Close()
21+
22+
decoder := json.NewDecoder(resp.Body)
23+
var details cratesJSON
24+
err = decoder.Decode(&details)
25+
if err != nil {
26+
return "", err
27+
}
28+
29+
return details.Versions[0].Num, nil
30+
}
31+
32+
var cratesPkgManager = PkgManager{
33+
name: "crates.io",
34+
image: "gcr.io/ossf-malware-analysis/crates.io",
35+
command: "/usr/local/bin/analyze.py",
36+
latest: getCratesLatest,
37+
dynamicPhases: []string{
38+
"install",
39+
},
40+
}

internal/pkgecosystem/ecosystem.go

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ var (
1818
pypiPkgManager.name: &pypiPkgManager,
1919
rubygemsPkgManager.name: &rubygemsPkgManager,
2020
packagistPkgManager.name: &packagistPkgManager,
21+
cratesPkgManager.name: &cratesPkgManager,
2122
}
2223
)
2324

sandboxes/crates.io/Dockerfile

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
FROM rust:1.63.0@sha256:54f17dc625ed83d3fc7dee68d5eac4e1977de40e72d909bcdb50cc55a462779d as image
2+
3+
RUN apt-get update && \
4+
apt-get install -y \
5+
curl \
6+
wget \
7+
git \
8+
python
9+
10+
COPY analyze.py /usr/local/bin/
11+
RUN chmod 755 /usr/local/bin/analyze.py
12+
RUN mkdir -p /app
13+
WORKDIR /app
14+
RUN cargo init
15+
16+
FROM scratch
17+
COPY --from=image / /
18+
WORKDIR /app
19+
ENV PATH="/usr/local/cargo/bin:${PATH}"
20+
ENV RUSTUP_HOME="/usr/local/rustup"
21+
ENV CARGO_HOME="/usr/local/cargo"
22+
23+
ENTRYPOINT [ "sleep" ]
24+
25+
CMD [ "30m" ]

sandboxes/crates.io/analyze.py

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env python3
2+
from dataclasses import dataclass
3+
import sys
4+
import subprocess
5+
from typing import Optional
6+
7+
@dataclass
8+
class Package:
9+
"""Class for tracking a package."""
10+
name: str
11+
version: Optional[str] = None
12+
local_path: Optional[str] = None
13+
14+
def get_dependency_line(self):
15+
if self.local_path:
16+
return f'{self.name} = {{ path = "{self.local_path}" }}'
17+
elif self.version:
18+
return f'{self.name} = "{self.version}"'
19+
else:
20+
return f'{self.name} = "*"'
21+
22+
def install(package: Package):
23+
"""Cargo build."""
24+
try:
25+
with open("Cargo.toml", 'a') as handle:
26+
handle.write(package.get_dependency_line() + '\n')
27+
handle.flush()
28+
29+
output = subprocess.check_output(['cargo', 'build'], stderr=subprocess.STDOUT)
30+
31+
print('Install succeeded:')
32+
print(output.decode())
33+
except subprocess.CalledProcessError as e:
34+
print('Failed to install:')
35+
print(e.output.decode())
36+
# Always raise.
37+
# Install failing is either an interesting issue, or an opportunity to
38+
# improve the analysis.
39+
raise
40+
41+
42+
PHASES = {
43+
"all": [install],
44+
"install": [install],
45+
}
46+
47+
def main():
48+
args = list(sys.argv)
49+
script = args.pop(0)
50+
51+
if len(args) < 2 or len(args) > 4:
52+
raise ValueError(f'Usage: {script} [--local file | --version version] phase package_name')
53+
54+
# Parse the arguments manually to avoid introducing unnecessary dependencies
55+
# and side effects that add noise to the strace output.
56+
local_path = None
57+
version = None
58+
if args[0] == '--local':
59+
args.pop(0)
60+
local_path = args.pop(0)
61+
elif args[0] == '--version':
62+
args.pop(0)
63+
version = args.pop(0)
64+
65+
phase = args.pop(0)
66+
package_name = args.pop(0)
67+
68+
if not phase in PHASES:
69+
print(f'Unknown phase {phase} specified.')
70+
exit(1)
71+
72+
package = Package(name=package_name, version=version, local_path=local_path)
73+
74+
# Execute for the specified phase.
75+
for phase in PHASES[phase]:
76+
phase(package)
77+
78+
79+
if __name__ == '__main__':
80+
main()

0 commit comments

Comments
 (0)