[recipe_wrapper] Delete BCID-related dead code
BCID attestion is not being used and the work is paused indefinitely.
Bug: 333766437
Change-Id: I9bdd165d9a8c8d84c3aeb4ea49d012017b24d206
Reviewed-on: https://fuchsia-review.googlesource.com/c/infra/infra/+/1024833
Fuchsia-Auto-Submit: Oliver Newman <olivernewman@google.com>
Reviewed-by: Vinicius Felizardo <felizardo@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/cmd/recipe_wrapper/bcid/bcid.go b/cmd/recipe_wrapper/bcid/bcid.go
deleted file mode 100644
index 29d420d..0000000
--- a/cmd/recipe_wrapper/bcid/bcid.go
+++ /dev/null
@@ -1,301 +0,0 @@
-// Copyright 2023 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package bcid
-
-import (
- "bytes"
- "context"
- "crypto/sha256"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "net/http/httputil"
- "os/exec"
- "runtime"
-
- spb "github.com/in-toto/attestation/go/v1"
- "go.chromium.org/luci/auth"
- "go.chromium.org/luci/cipd/common"
- "go.chromium.org/luci/common/logging"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/env"
- "golang.org/x/oauth2"
- "google.golang.org/protobuf/encoding/protojson"
- structpb "google.golang.org/protobuf/types/known/structpb"
-)
-
-const (
- ProdKeyID = "projects/fuchsia-bcid/locations/global/keyRings/bcid/cryptoKeys/attestation_signer/cryptoKeyVersions/1"
- TestKeyID = "projects/fuchsia-bcid-dev/locations/global/keyRings/tests/cryptoKeys/recipe_wrapper_attestation_signer/cryptoKeyVersions/1"
- StmtType = "https://in-toto.io/Statement/v1"
- PredicateType = "https://slsa.dev/provenance/v1"
- sciloEndpoint = "https://bcidsoftwareverifier-pa.googleapis.com/v1/software-artifact-verification-requests"
-)
-
-type attestOutputter struct{}
-
-func (*attestOutputter) CombinedOutput(ctx context.Context, stdin []byte, args ...string) ([]byte, error) {
- cmd := exec.CommandContext(ctx, "attestation_tool", args...)
- if stdin != nil {
- pipe, err := cmd.StdinPipe()
- if err != nil {
- return nil, fmt.Errorf("unable to pipe to stdin: %v", err)
- }
- pipe.Write(stdin)
- // This close must occur before CombinedOutput() is called otherwise the command will wait forever.
- pipe.Close()
- }
-
- logging.Infof(ctx, "Executing %+v\n", cmd)
- return cmd.CombinedOutput()
-}
-
-func CanAttestPlatform() bool {
- return runtime.GOOS == "linux" && runtime.GOARCH == "amd64"
-}
-
-func Attest(ctx context.Context, stmt *spb.Statement, keyID string) (*InTotoBundle, error) {
- if !CanAttestPlatform() {
- return nil, fmt.Errorf("platform %v/%v is unsupported for attestation", runtime.GOOS, runtime.GOARCH)
- }
- return attestWithOutputter(ctx, &attestOutputter{}, stmt, keyID)
-}
-
-type CombinedOutputer interface {
- CombinedOutput(context.Context, []byte, ...string) ([]byte, error)
-}
-
-func attestWithOutputter(ctx context.Context, cmd CombinedOutputer, stmt *spb.Statement, keyID string) (*InTotoBundle, error) {
- stmtJSON, err := protojson.Marshal(stmt)
- if err != nil {
- return nil, err
- }
-
- out, err := cmd.CombinedOutput(ctx, stmtJSON, "sign_intoto_statement",
- "--input", "-",
- "--key_id", keyID,
- "--output", "-")
- if err != nil {
- return nil, fmt.Errorf("attestation_tool failed: %v / %q", err, out)
- }
- return UnmarshalBundle(bytes.NewReader(out))
-}
-
-type Signature struct {
- KeyID string `json:"keyid"`
- Sig string `json:"sig"`
-}
-
-// InToToBundle represents the attestation bundle.
-// TODO(https://github.com/in-toto/attestation/issues/280): This struct should ideally be provided upstream.
-type InTotoBundle struct {
- ContentType string `json:"payloadType"`
- Payload string `json:"payload"`
- Signatures []Signature `json:"signatures"`
-}
-
-func UnmarshalBundle(data io.Reader) (*InTotoBundle, error) {
- all, err := io.ReadAll(data)
- if err != nil {
- return nil, fmt.Errorf("can't read input data: %v", err)
- }
-
- var itt InTotoBundle
- if err := json.Unmarshal(all, &itt); err != nil {
- return nil, fmt.Errorf("unable to pass in-toto payload: %v", err)
- }
-
- return &itt, nil
-}
-
-type Subject struct {
- Pkg *common.Pin
- Data io.Reader
-}
-
-func NewStmt(subject *Subject, buildEnv *env.Build) (*spb.Statement, error) {
- if subject == nil {
- return nil, errors.New("subject cannot be nil")
- }
- h := sha256.New()
- if _, err := io.Copy(h, subject.Data); err != nil {
- return nil, err
- }
-
- // The statement proto uses this struct proto which is dynamically typed.
- // It then uses reflection to try and figure out what it all is.
- // The only real way to work through this is by using raw types, not
- // real Go structs.
- // It's possible to just dump raw JSON here, but that feels even less
- // idomatic than this does.
- // Unless tagged as required, fields are optional.
- // Any changes to this specification should be reflected in the external
- // documentation at //slsa/buildConfig/v1
- // Do not remove fields once populated unless BCID For Software team is
- // consulted.
- pred, err := structpb.NewStruct(map[string]any{
- // Required.
- "buildDefinition": map[string]any{
- // Required.
- "buildType": "https://fuchsia.googlesource.com/infra/infra/slsa/buildConfig/v1",
- // Required, but fields within may be best-effort.
- // The spec is unclear whether this field can be included empty.
- "externalParameters": map[string]any{
- "buildConfigSource": map[string]any{
- "repository": fmt.Sprintf("git+%s@%s", buildEnv.RecipesExe.Repo(), buildEnv.RecipesExe.SHA1()),
- "path": "recipes.py",
- },
- },
- },
- // Required.
- "runDetails": map[string]any{
- // Required.
- "builder": map[string]any{
- // Required.
- "id": "//bcid.corp.google.com/builders/luci/fuchsia/l1",
- // TODO(cflewis): Add the version here.
- },
- },
- })
- if err != nil {
- return nil, err
- }
-
- return &spb.Statement{
- Type: StmtType,
- Subject: []*spb.ResourceDescriptor{
- {
- Name: subject.Pkg.PackageName,
- // cipd_instance_id is required by SCILo to understand what is being uploaded.
- // This key isn't arbitrary chosen and _mustn't be changed_.
- Digest: map[string]string{"cipd_instance_id": subject.Pkg.InstanceID},
- },
- },
- PredicateType: PredicateType,
- Predicate: pred,
- }, nil
-}
-
-type sciloContext struct {
- VerificationPurpose string `json:"verificationPurpose"`
- EnforcementPointName string `json:"enforcementPointName"`
- OccurrenceStage string `json:"occurrenceStage"`
-}
-
-type sciloArtifactInfo struct {
- Digests map[string]string `json:"digests"`
- // Attestations are _strings_ of InTotoBundles.
- // Passing bundles as JSON is not accepted by the endpoint.
- Attestations []string `json:"attestations"`
-}
-
-type sciloReq struct {
- Context *sciloContext `json:"context"`
- ResourceToVerify string `json:"resourceToVerify"`
- ArtifactInfo *sciloArtifactInfo `json:"artifactInfo"`
-}
-
-type SCILoResp struct {
- Allowed bool `json:"allowed"`
- RejectionMessage string `json:"rejectionMessage"`
- VerificationSummary string `json:"verificationSummary"`
-}
-
-// UploadToSCILo uploads the bundle to SCILo using default values.
-func UploadToSCILo(ctx context.Context, stmt *spb.Statement, bundle *InTotoBundle) (*SCILoResp, error) {
- ts, err := LUCITokenSource(ctx)
- if err != nil {
- return nil, err
- }
- return UploadToSCILoEndpoint(ctx, sciloEndpoint, ts, stmt, bundle)
-}
-
-func UploadToSCILoEndpoint(ctx context.Context,
- endpoint string, tokenSource oauth2.TokenSource, stmt *spb.Statement, bundle *InTotoBundle) (*SCILoResp, error) {
- token, err := tokenSource.Token()
- if err != nil {
- return nil, fmt.Errorf("couldn't get auth token: %v", err)
- }
- if subjs := len(stmt.GetSubject()); subjs != 1 {
- return nil, fmt.Errorf("expected 1 subject in statement, got %v", subjs)
- }
- subj := stmt.GetSubject()[0]
-
- jsonBundle, err := json.Marshal(bundle)
- if err != nil {
- return nil, fmt.Errorf("couldn't marshal bundle to JSON: %v", err)
- }
-
- sReq := &sciloReq{
- // These values are documented at go/scilo-server#artifact-occurrence-stage
- Context: &sciloContext{
- VerificationPurpose: "VERIFY_FOR_ENFORCEMENT",
- // This is an arbitrary name to identify what is attesting.
- EnforcementPointName: "fuchsia-recipe-wrapper",
- // This means that the attestation has been made after the build has completed
- // and will no longer be modified.
- OccurrenceStage: "AS_VERIFIED",
- },
- ResourceToVerify: "cipd_package://" + subj.GetName(),
- ArtifactInfo: &sciloArtifactInfo{
- Digests: subj.GetDigest(),
- Attestations: []string{string(jsonBundle)},
- },
- }
- jsonReq, err := json.Marshal(sReq)
- if err != nil {
- return nil, fmt.Errorf("couldn't marshal SCILo Request to JSON: %v", err)
- }
- logging.Infof(ctx, "Uploading attestations to SCILo:\n%s", jsonBundle)
-
- httpReq, _ := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(jsonReq))
- httpReq.Header.Set("Content-Type", "application/json")
- httpReq.Header.Set("Accept", "application/json")
- httpReq.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token.AccessToken))
- // The dump needs to happen _before_ the httpclient.Do() call.
- // Do() probably consumes the request and then dump is empty.
- // DumpRequestOut() will copy back the data it consumes so the
- // Do() call works correctly.
- dump, err := httputil.DumpRequestOut(httpReq, true)
- if err != nil {
- return nil, fmt.Errorf("unable to dump http request: %v", err)
- }
-
- // TODO(cflewis): This might need to be retried if the BCID server is observed to flake out.
- resp, err := http.DefaultClient.Do(httpReq)
- if err != nil {
- return nil, fmt.Errorf("unable to contact BCID endpoint: %v", err)
- }
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, fmt.Errorf("couldn't read HTTP body: %v", err)
- }
- // Check if the request was blocked by lack of a good token.
- // If so, give a more informative error.
- if resp.StatusCode == http.StatusUnauthorized {
- return nil, fmt.Errorf("GFE blocked access attempt, probably due to bad auth token. Output:\n%s", body)
- }
- // If there was no good status there really isn't anything meaningful to be done at
- // this point. There's no recovery that can be taken.
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("bad HTTP status code %v: body:\n%s\noriginal request:\n%s", resp.StatusCode, body, dump)
- }
-
- var sResp SCILoResp
- err = json.Unmarshal(body, &sResp)
- if err != nil {
- return nil, fmt.Errorf("unable to marshal HTTP body to SCILoResp: %v", err)
- }
- return &sResp, nil
-}
-
-func LUCITokenSource(ctx context.Context) (oauth2.TokenSource, error) {
- authenticator := auth.NewAuthenticator(ctx, auth.SilentLogin, auth.Options{
- Scopes: []string{"https://www.googleapis.com/auth/bcid_verify"},
- })
- return authenticator.TokenSource()
-}
diff --git a/cmd/recipe_wrapper/bcid/bcid_test.go b/cmd/recipe_wrapper/bcid/bcid_test.go
deleted file mode 100644
index ad17afa..0000000
--- a/cmd/recipe_wrapper/bcid/bcid_test.go
+++ /dev/null
@@ -1,326 +0,0 @@
-// Copyright 2023 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// TODO(b/299967645): Test errors, not just happy path.
-package bcid
-
-import (
- "archive/zip"
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "net/http/httptest"
- "os"
- "os/exec"
- "strings"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- spb "github.com/in-toto/attestation/go/v1"
- "go.chromium.org/luci/cipd/common"
- "go.chromium.org/luci/common/logging/gologger"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/env"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/recipes"
- "golang.org/x/oauth2"
-)
-
-func ctx() context.Context {
- ctx := context.Background()
-
- if testing.Verbose() {
- return gologger.StdConfig.Use(ctx)
- }
- return ctx
-}
-
-// These are read from a test data file because the line lengths are enormous.
-var fakeInTotoOutput []byte
-var fakeVerificationResponse []byte
-
-var buildEnv = &env.Build{RecipesExe: &recipes.Repo{}}
-
-type testAttestOutputter struct{}
-
-func (tao *testAttestOutputter) CombinedOutput(ctx context.Context, _ []byte, args ...string) ([]byte, error) {
- if args[0] != "sign_intoto_statement" {
- panic("only sign_intoto_statement is supported")
- }
- return fakeInTotoOutput, nil
-}
-
-// outputter chooses a real version of a tool if it is available and the test
-// isn't running in t.Short. Otherwise, returns a stubbed outputter.
-func outputter(t *testing.T, ctx context.Context, tool string, args ...string) CombinedOutputer {
- t.Helper()
-
- // TODO(cflewis): Find a way to make this work on a LUCI builder.
- // Tools on LUCI usually aren't provided inside a path but via
- // argument/envvar.
- _, pathErr := exec.LookPath(tool)
- // Check that the test isn't running in a sandbox.
- resp, _ := http.Get("https://example.com")
-
- var real, stub CombinedOutputer
-
- switch tool {
- case "attestation_tool":
- real = &attestOutputter{}
- stub = &testAttestOutputter{}
- default:
- t.Fatalf("no definition for real/stub commander for %v", tool)
- }
-
- if !testing.Short() && pathErr == nil && resp.StatusCode == http.StatusOK {
- return real
- }
-
- return stub
-}
-
-func TestAttest(t *testing.T) {
- ctx := ctx()
- stmt, err := NewStmt(&Subject{Pkg: &common.Pin{
- PackageName: "fuchsia/sdk/core/linux-amd64",
- InstanceID: "NubrCS3MOEEOGG1aPZJnyK3AjZnPMTVC6Of5leb7vHkC"},
- Data: strings.NewReader("")}, buildEnv)
- if err != nil {
- t.Fatalf("can't initialize statement")
- }
-
- bundle, err := attestWithOutputter(ctx, outputter(t, ctx, "attestation_tool"), stmt, TestKeyID)
- if err != nil {
- t.Fatalf("attestWithCommander() error %v, want nil error", err)
- }
-
- // A real invocation of the attestation_tool is non-deterministic because it appears
- // to rely on the clock time as an input.
- // Instead of relying on the data itself, just check that the data was formatted as
- // expected. This validates that attestation_tool did work.
- if bundle.ContentType != "application/vnd.in-toto+json" ||
- bundle.Signatures[0].KeyID != "gcpkms://"+TestKeyID || bundle.Signatures[0].Sig == "" {
- t.Errorf("InToto struct did not follow expected format\nContentType = %q\nKey ID = %q\nSig = %q",
- bundle.ContentType, bundle.Signatures[0].KeyID, bundle.Signatures[0].Sig)
- }
-}
-
-func fakeZip(t *testing.T, buf *bytes.Buffer, data string) *zip.Writer {
- w := zip.NewWriter(buf)
- f, err := w.Create(data)
- if err != nil {
- t.Fatalf("can't add to zip: %v", err)
- }
- _, err = f.Write([]byte("bar"))
- if err != nil {
- t.Fatalf("can't write to zip: %v", err)
- }
- return w
-}
-
-// CIPD packages are zip files so that functionality needs its own test.
-func TestNewStmtWithZip(t *testing.T) {
- buf := new(bytes.Buffer)
- // Note that changing these input values will change the expected SHA and fail the test.
- w := fakeZip(t, buf, "foo")
- if err := w.Close(); err != nil {
- t.Fatalf("can't close writer to zip: %v", err)
- }
-
- const name = "alice/bob.pkg"
- const instanceID = "instance_id"
- stmt, err := NewStmt(&Subject{
- Pkg: &common.Pin{
- PackageName: name,
- InstanceID: "instance_id",
- },
- Data: buf,
- }, buildEnv)
- if err != nil {
- t.Errorf("got %q, want nil err", err)
- }
-
- want := &spb.Statement{
- Type: StmtType,
- PredicateType: PredicateType,
- Subject: []*spb.ResourceDescriptor{
- {
- Name: name,
- Digest: map[string]string{
- "cipd_instance_id": instanceID,
- },
- },
- },
- }
- if diff := cmp.Diff(stmt, want,
- // Predicate is a bunch of dynamically typed values.
- // It's a pain to write a comparer for this, so just ignore it and make sure
- // it's not nil.
- cmpopts.IgnoreFields(spb.Statement{}, "Predicate"),
- cmpopts.IgnoreUnexported(spb.Statement{}),
- cmpopts.IgnoreUnexported(spb.ResourceDescriptor{})); diff != "" || stmt.Predicate == nil {
- t.Errorf("got diff, want no diff: Diff:\n%v", diff)
- }
-}
-
-type repo struct {
- repo, sha string
-}
-
-func (r *repo) LuciexeCommand() ([]string, error) {
- return nil, nil
-}
-func (r *repo) Repo() string { return r.repo }
-func (r *repo) SHA1() string { return r.sha }
-
-func TestNewStmtConfigSource(t *testing.T) {
- got, err := NewStmt(&Subject{
- Pkg: &common.Pin{PackageName: "Foo", InstanceID: "Bar"},
- Data: strings.NewReader("foo"),
- }, &env.Build{RecipesExe: &repo{repo: "foo", sha: "123456"}})
- if err != nil {
- t.Fatalf("unable to construct new statement: %v", err)
- }
-
- want := "git+foo@123456"
- // No, there is no better way to navigate through this structure.
- // Yes, it sucks.
- if got.Predicate.GetFields()["buildDefinition"].
- GetStructValue().GetFields()["externalParameters"].
- GetStructValue().GetFields()["buildConfigSource"].
- GetStructValue().GetFields()["repository"].GetStringValue() != want {
- t.Errorf("got %v, want %v", got, want)
- }
-}
-
-const fakeLUCIToken = "ya29.a0AfB_byAdIhZ9KWIh6vYkODl3VDfOHi1fNNmblLb9yp_W2CH7T23C0RTmpspLZ-EMgKO-UWGIMkm8ivmqF6j8iFPsEkW8Xywld4yI3AnFhCM6G7k433FRoUVfQkNUCOC03j_P1Q8gqK37VKPnuGmjQl_1RcPBzlv5XJpdaCgYKASISAQ8SFQGOcNnCKCsyK-Oe4wRl_HNBMAk2Rg0171"
-
-type testTokenSource struct {
-}
-
-func (tts *testTokenSource) Token() (*oauth2.Token, error) {
- return &oauth2.Token{AccessToken: fakeLUCIToken}, nil
-}
-
-func TestLUCIToken(t *testing.T) {
- ctx := context.Background()
- var tokenSource oauth2.TokenSource
-
- // Attempt to get a real token if possible.
- if !testing.Short() {
- var err error
- tokenSource, err = LUCITokenSource(ctx)
- if err != nil || tokenSource == nil {
- t.Logf("LUCI unable to get a real token source, this is expected when run non-locally and can be ignored: %v",
- err.Error())
- // There's no real token source, so use the test one instead.
- tokenSource = &testTokenSource{}
- }
- }
-
- // All Google access tokens seem to start with ya29.
- token, err := tokenSource.Token()
- if err != nil {
- t.Fatalf("unable to get token from source")
- }
- wantPrefix := "ya29"
- if !strings.HasPrefix(token.AccessToken, wantPrefix) {
- t.Errorf("got %v, want start of ya29 %v", token, wantPrefix)
- }
-}
-
-func TestUploadBundle(t *testing.T) {
- // It's simply too difficult to test hitting the BCID API
- // given the allowlists, and OAuth token exchanges (with private scopes) required.
- // Bring up a test server and just check that the requests are reasonably
- // formatted.
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- body, err := io.ReadAll(r.Body)
- if err != nil {
- http.Error(w, fmt.Sprintf("can't read body: %v", err), http.StatusBadRequest)
- }
- defer r.Body.Close()
-
- var req sciloReq
- if err := json.Unmarshal(body, &req); err != nil {
- http.Error(w, fmt.Sprintf("can't unmarshal request to valid SCILo request: %v\nBody: %s", err, body), http.StatusBadRequest)
- }
-
- fmt.Fprintln(w, string(fakeVerificationResponse))
- }))
- defer ts.Close()
-
- fakeBundle, err := UnmarshalBundle(bytes.NewReader(fakeInTotoOutput))
- if err != nil {
- t.Fatalf("couldn't marshal fake bundle: %v", err)
- }
-
- tests := []struct {
- name string
- subject *Subject
- bundle *InTotoBundle
- wantAccepted bool
- }{
- {
- name: "A good statement and bundle should produce a good response",
- subject: &Subject{
- Pkg: &common.Pin{
- PackageName: "foopkg",
- InstanceID: "bar_instance",
- },
- Data: strings.NewReader("bazdata"),
- },
- bundle: fakeBundle,
- wantAccepted: true,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- stmt, err := NewStmt(test.subject, buildEnv)
- if err != nil {
- t.Fatalf("couldn't create statement from subject: %v", err)
- }
- resp, err := UploadToSCILoEndpoint(
- context.Background(),
- ts.URL,
- &testTokenSource{},
- stmt,
- test.bundle,
- )
- if err != nil {
- t.Errorf("got error %v, want nil error", err)
- }
- if resp == nil {
- t.Error("got nil response, want valid response")
- }
- if test.wantAccepted && !(resp.Allowed && resp.RejectionMessage == "") {
- t.Errorf("wanted accepted response with no rejection message, got %+v", resp)
- }
- })
- }
-}
-
-func mustRead(filename string) []byte {
- f, err := os.Open(filename)
- if err != nil {
- fmt.Fprintf(os.Stderr, "unable to open testdata: %v", err)
- os.Exit(1)
- }
- data, err := io.ReadAll(f)
- if err != nil {
- fmt.Fprintf(os.Stderr, "unable to read in-toto testdata: %v", err)
- os.Exit(1)
- }
- return data
-}
-
-func TestMain(m *testing.M) {
- fakeInTotoOutput = mustRead("testdata/in-toto-output.json")
- fakeVerificationResponse = mustRead("testdata/verification-response.json")
-
- os.Exit(m.Run())
-}
diff --git a/cmd/recipe_wrapper/bcid/testdata/in-toto-output.json b/cmd/recipe_wrapper/bcid/testdata/in-toto-output.json
deleted file mode 100644
index 60097a1..0000000
--- a/cmd/recipe_wrapper/bcid/testdata/in-toto-output.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "payloadType": "application/vnd.in-toto+json",
- "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCAic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6IjQzNzVhNTBiYjJhODFkNTQwMGU2NmEzMWMwNzRkN2U3ODRjNjE2OTMzMzFlNzFhNzhmN2MwNzk5ZTc0YzhhNTcifX1dLCAicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vc2xzYS5kZXYvcHJvdmVuYW5jZS92MSIsICJwcmVkaWNhdGUiOnsiYnVpbGREZWZpbml0aW9uIjp7ImV4dGVybmFsUGFyYW1ldGVycyI6eyJidWlsZENvbmZpZ1NvdXJjZSI6eyJwYXRoIjoicmVjaXBlcy5weSIsICJyZXBvc2l0b3J5IjoiZ2l0K2h0dHBzOi8vZnVjaHNpYS5nb29nbGVzb3VyY2UuY29tL2luZnJhL3JlY2lwZXMifX19LCAicnVuRGV0YWlscyI6eyJidWlsZGVyIjp7ImlkIjoiLy9iY2lkLmNvcnAuZ29vZ2xlLmNvbS9idWlsZGVycy9sdWNpL2Z1Y2hzaWEvbDEifX19fQ==",
- "signatures": [
- {
- "keyid": "gcpkms://projects/fuchsia-bcid-dev/locations/global/keyRings/tests/cryptoKeys/recipe_wrapper_attestation_signer/cryptoKeyVersions/1",
- "sig": "MEUCIQDr4B4IXsKYfqRjn7uXJQdWHwYk1K+YBkQzr1gwjMtKfAIgSRUbv+sfBzsEebkt32K+Zoil/GYbu9Fu0W1n1CF8IiI="
- }
- ]
-}
\ No newline at end of file
diff --git a/cmd/recipe_wrapper/bcid/testdata/verification-response.json b/cmd/recipe_wrapper/bcid/testdata/verification-response.json
deleted file mode 100644
index 2801042..0000000
--- a/cmd/recipe_wrapper/bcid/testdata/verification-response.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "allowed": true,
- "verificationSummary": "{\"payload\":\"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi92ZXJpZmljYXRpb25fc3VtbWFyeS92MC4yIiwicHJlZGljYXRlIjp7InBvbGljeSI6eyJ1cmkiOiJnb29nbGVmaWxlOi9nb29nbGVfc3JjL2ZpbGVzLzU2NzM5NTQ4MS9kZXBvdC9nb29nbGUzL3Byb2R1Y3Rpb24vc2VjdXJpdHkvYmNpZC9zb2Z0d2FyZS9taXNjX3NvZnR3YXJlL2RhcnQvc2RrLnN3X3BvbGljeS50ZXh0cHJvdG8ifSwidGltZV92ZXJpZmllZCI6IjIwMjMtMDktMjFUMjA6Mzg6NDEuNDM2NDY1WiIsInJlc291cmNlX3VyaSI6Im1pc2Nfc29mdHdhcmU6Ly9kYXJ0L3Nkay9tYWNvcyIsInBvbGljeV9sZXZlbCI6IlNMU0FfTEVWRUxfMiIsInZlcmlmaWVyIjp7ImlkIjoiaHR0cHM6Ly9iY2lkLmNvcnAuZ29vZ2xlLmNvbS92ZXJpZmllci9iY2lkX3BhY2thZ2VfZW5mb3JjZXIvdjAuMSJ9LCJ2ZXJpZmljYXRpb25fcmVzdWx0IjoiUEFTU0VEIn0sInN1YmplY3QiOlt7Im5hbWUiOiJfIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjRkNzAwYmZlMTVhYTM2NGZiMDYzMTUzNDRmODA2NjY4MzE0NDVlZTlkNDMzZDkxMDJiOTMyNjJiZWU5YTMxNWMifX1dfQ==\",\"payloadType\":\"application/vnd.in-toto+json\",\"signatures\":[{\"sig\":\"Pvs6oNhpUKSW/B+tgeqaM2lJBmlFkeuDvuegf5GGaBTJb0Uy4Ote7uaY4CG2YA45C5Mg9DDlURO+RiE54yfslw==\",\"keyid\":\"keystore://76574:prod:vsa_signing_public_key_staging\"}]}"
-}
\ No newline at end of file
diff --git a/cmd/recipe_wrapper/cipd/cipd.go b/cmd/recipe_wrapper/cipd/cipd.go
index aca896c..c8ba2ac 100644
--- a/cmd/recipe_wrapper/cipd/cipd.go
+++ b/cmd/recipe_wrapper/cipd/cipd.go
@@ -11,17 +11,8 @@
"os/exec"
"path/filepath"
- v1 "github.com/in-toto/attestation/go/v1"
- "go.chromium.org/luci/cipd/client/cipd"
"go.chromium.org/luci/cipd/common"
"go.chromium.org/luci/common/logging"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/bcid"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/env"
-)
-
-const (
- SERVICE_URL = "https://chrome-infra-packages.appspot.com"
- SERVICE_DEV_URL = "https://chrome-infra-packages-dev.appspot.com"
)
type Package struct {
@@ -47,10 +38,6 @@
version: "version:2@2.7.18.chromium.44",
binDir: "bin",
}
- AttestToolPkg = Package{
- name: "fuchsia/infra/attestation_tool/${platform}",
- version: "A5A21ls2KKN_H59cvEI2IKtIT3-TnNnNY49sguw56BoC",
- }
)
func Install(ctx context.Context, dir string, packages ...Package) ([]string, error) {
@@ -76,90 +63,3 @@
return binDirs, nil
}
-
-type PkgFile struct {
- PackageName string `json:"cipd_package"` //`json:"package"`
- InstanceID string `json:"cipd_instance_id"` //`json:"instance_id"`
- Path string `json:"path"`
-}
-
-func (pf *PkgFile) AsPin() *common.Pin {
- return &common.Pin{
- PackageName: pf.PackageName,
- InstanceID: pf.InstanceID,
- }
-}
-
-type StmtBundle struct {
- Package *PkgFile
- Statement *v1.Statement
- Bundle *bcid.InTotoBundle
-}
-
-// AttestPkgs searches for packages that were registered to CIPD and
-// generates attestations for them.
-func AttestPkgs(pkgs []*PkgFile, keyID string, buildEnv *env.Build) ([]*StmtBundle, []error) {
- var stmtBundles []*StmtBundle
- var errs []error
- for _, pkg := range pkgs {
- data, err := os.Open(pkg.Path)
- if err != nil {
- errs = append(errs, fmt.Errorf("can't open package file %q: %v", pkg, err))
- continue
- }
- defer data.Close()
-
- stmt, err := bcid.NewStmt(&bcid.Subject{
- Pkg: pkg.AsPin(),
- Data: data,
- }, buildEnv)
- if err != nil {
- errs = append(errs, fmt.Errorf("can't create statement for package %q: %v", pkg, err))
- continue
- }
- b, err := bcid.Attest(context.TODO(), stmt, keyID)
- if err != nil {
- errs = append(errs, fmt.Errorf("can't attest package %q: %v", pkg, err))
- continue
- }
- stmtBundles = append(stmtBundles, &StmtBundle{
- Package: pkg,
- Statement: stmt,
- Bundle: b,
- })
- }
-
- return stmtBundles, errs
-}
-
-type CombinedOutputer interface {
- CombinedOutput(context.Context, ...string) ([]byte, error)
-}
-
-type cipdOutputter struct{}
-
-func (*cipdOutputter) CombinedOutput(ctx context.Context, args ...string) ([]byte, error) {
- return exec.CommandContext(ctx, "cipd", args...).CombinedOutput()
-}
-
-// AddMetadata wraps `cipd set-metadata`.
-// The value is assumed to be a text/plain content type.
-// It's named `Add` and not `Set` because using the same key multiple times
-// will result in the key appearing multiple times, not once with the last updated
-// value.
-// Package and version name examples can be found at
-// https://chromium.googlesource.com/infra/luci/luci-go/+/HEAD/cipd/README.md
-func AddMetadata(ctx context.Context, pkg *common.Pin, md []cipd.Metadata) error {
- return addMetadataWithOutputter(ctx, &cipdOutputter{}, SERVICE_URL, pkg, md)
-}
-
-func addMetadataWithOutputter(ctx context.Context, cmd CombinedOutputer, serviceURL string, pkg *common.Pin, md []cipd.Metadata) error {
- // TODO(cflewis): This code would be faster if the client was kept around rather than remade each time.
- c, err := cipd.NewClientFromEnv(ctx, cipd.ClientOptions{ServiceURL: serviceURL})
- if err != nil {
- return err
- }
- defer c.Close(ctx)
-
- return c.AttachMetadataWhenReady(ctx, *pkg, md)
-}
diff --git a/cmd/recipe_wrapper/cipd/cipd_test.go b/cmd/recipe_wrapper/cipd/cipd_test.go
deleted file mode 100644
index 8375a51..0000000
--- a/cmd/recipe_wrapper/cipd/cipd_test.go
+++ /dev/null
@@ -1,224 +0,0 @@
-// Copyright 2023 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package cipd
-
-import (
- "context"
- "encoding/json"
- "os"
- "os/exec"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "go.chromium.org/luci/auth"
- api "go.chromium.org/luci/cipd/api/cipd/v1"
- "go.chromium.org/luci/cipd/client/cipd"
- "go.chromium.org/luci/cipd/common"
- "go.chromium.org/luci/grpc/prpc"
- "go.chromium.org/luci/hardcoded/chromeinfra"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/bcid"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/env"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/recipes"
-)
-
-var buildEnv = &env.Build{RecipesExe: &recipes.Repo{}}
-
-func TestAttestPkgs(t *testing.T) {
- _, pathErr := exec.LookPath("attestation_tool")
- if testing.Short() || pathErr != nil {
- t.Skip("no attestation_tool or test ran with -short, skipping")
- }
- tmpfile, err := os.CreateTemp("", "cipd_test")
- if err != nil {
- t.Fatalf("can't create tempfile: %v", err)
- }
- pkgName := "foo"
-
- stmtBundles, errs := AttestPkgs([]*PkgFile{
- {
- PackageName: pkgName,
- InstanceID: "bar",
- Path: tmpfile.Name(),
- },
- }, bcid.TestKeyID, buildEnv)
- if errs != nil {
- t.Errorf("got errors: %+v, want nil error", errs)
- }
-
- for _, sb := range stmtBundles {
- // A real invocation of the attestation_tool is non-deterministic because it appears
- // to rely on the clock time as an input.
- // Instead of relying on the data itself, just check that the data was formatted as
- // expected. This validates that attestation_tool did work.
- if sb.Statement.GetSubject()[0].GetName() != pkgName ||
- sb.Bundle.ContentType != "application/vnd.in-toto+json" ||
- sb.Bundle.Signatures[0].KeyID != "gcpkms://"+bcid.TestKeyID || sb.Bundle.Signatures[0].Sig == "" {
- t.Errorf("InToto struct did not follow expected format\nName = %q\nContentType = %q\nKey ID = %q\nSig = %q",
- sb.Statement.GetSubject()[0].GetName(), sb.Bundle.ContentType, sb.Bundle.Signatures[0].KeyID, sb.Bundle.Signatures[0].Sig)
- }
- }
-}
-
-type testCIPDOutputter struct{}
-
-func (*testCIPDOutputter) CombinedOutput(ctx context.Context, args ...string) ([]byte, error) {
- cmd := exec.CommandContext(ctx, "cipd", args...)
- cmd.Env = append(cmd.Environ(), "CIPD_SERVICE_URL="+SERVICE_DEV_URL)
- return cmd.CombinedOutput()
-}
-
-const pkgName = "experimental/fuchsia/recipe_wrapper/cipd_test"
-
-type CIPDResult struct {
- Package string `json:"package"`
- InstanceID string `json:"instance_id"`
-}
-type CIPDResults struct {
- Result *CIPDResult `json:"result"`
-}
-
-func containsMetadata(s []*api.InstanceMetadata, md cipd.Metadata) bool {
- for _, im := range s {
- if im.GetKey() == md.Key && string(im.GetValue()) == string(md.Value) {
- return true
- }
- }
-
- return false
-}
-
-func newRepoClient(ctx context.Context, t *testing.T) api.RepositoryClient {
- c, err := auth.NewAuthenticator(ctx, auth.OptionalLogin, chromeinfra.DefaultAuthOptions()).Client()
- if err != nil {
- t.Fatalf("couldn't initialize authenticator: %v", err)
- }
- prpcC := &prpc.Client{
- C: c,
- Host: "chrome-infra-packages-dev.appspot.com",
- }
-
- return api.NewRepositoryClient(prpcC)
-}
-
-func TestAddMetadata(t *testing.T) {
- // This test reaches out to the CIPD service which assumes some base ACLs
- // that are not available on all bots that run the test.
- t.Skip("skipping non-hermetic test")
-
- _, err := exec.LookPath("cipd")
- if err != nil {
- // Testing without cipd is so trivial there's no point in doing anything.
- // Even if the code is only using the client library, it does assume that
- // the required auth flow for cipd has already been completed (e.g. `cipd auth-login`).
- // If `cipd` isn't here, then the auth flow can't have happened.
- t.Skip("`cipd` not on path")
- }
- ctx := context.Background()
-
- // An executed command is used instead of using the luci-go cipd client
- // because the package creation code included in the `cipd` executable
- // is non-trivial. It's more reliable to reuse the executable than attempt
- // to recreate the logic here.
- tmpdir, err := os.MkdirTemp("", "cipd_test")
- if err != nil {
- t.Fatalf("unable to create tempdir: %v", err)
- }
- f, err := os.CreateTemp(tmpdir, "")
- if err != nil {
- t.Fatalf("unable to create temp file: %v", err)
- }
- f.WriteString("Hello, cipd_test!")
- f.Close()
- defer os.RemoveAll(tmpdir)
-
- // Make a temporary file to output results to.
- // This is necessary so the created version can be inspected.
- // The version ID is a requirement for adding metadata.
- outF, err := os.CreateTemp("", "cipd_test_output")
- if err != nil {
- t.Fatalf("unable to create JSON output file: %v", err)
- }
- defer os.Remove(outF.Name())
-
- cmd := exec.Command(
- "cipd",
- "create",
- "-in", tmpdir,
- "-name", pkgName,
- "-json-output", outF.Name(),
- )
- cmd.Env = append(cmd.Environ(), "CIPD_SERVICE_URL="+SERVICE_DEV_URL)
- out, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatalf("unable to create test package: %s/%v", out, err)
- }
- outF.Close()
-
- // Retrieve the instance ID to add metadata to.
- resultData, err := os.ReadFile(outF.Name())
- if err != nil {
- t.Fatalf("couldn't read CIPD create JSON results file: %v", err)
- }
- var results CIPDResults
- err = json.Unmarshal(resultData, &results)
- if err != nil {
- t.Fatalf("couldn't get JSON results out of CIPD create results file: %v", err)
- }
-
- want := []cipd.Metadata{
- {
- Key: "foo",
- Value: []byte("bar"),
- ContentType: "text/plain",
- },
- {
- Key: "alice",
- Value: []byte("bob"),
- ContentType: "text/plain",
- },
- }
-
- err = addMetadataWithOutputter(
- ctx, &testCIPDOutputter{}, SERVICE_DEV_URL, &common.Pin{PackageName: pkgName, InstanceID: results.Result.InstanceID}, want)
- if err != nil {
- t.Fatalf("couldn't add metadata: %v", err)
- }
-
- repoC := newRepoClient(ctx, t)
- resp, err := repoC.ListMetadata(ctx, &api.ListMetadataRequest{
- Package: pkgName,
- Instance: common.InstanceIDToObjectRef(results.Result.InstanceID),
- })
- if err != nil {
- t.Fatalf("couldn't get metadata back: %v", err)
- }
-
- for _, md := range want {
- if !containsMetadata(resp.GetMetadata(), md) {
- t.Errorf("returned metadata does not contain %v", md)
- }
- }
-
- // The package can't be deleted here for cleanup as that requires admin privileges
- // on the package registry. It's OK for the test package to just pile up multiple
- // versions.
-}
-
-// Regression test.
-func TestPackageFileMarshal(t *testing.T) {
- data := `{"cipd_instance_id": "xUAOSrGYJ3OcV88evjHVfFOPo_Yxd4aoQOfUP8BpnMAC", "cipd_package": "fuchsia_internal/assembly/inputs/hsp.x64", "path": "/b/s/w/ir/x/w/recipe_cleanup/cipd-util-build99nbs7su/inputs.pkg"}`
-
- pkgFile := &PkgFile{}
- if err := json.Unmarshal([]byte(data), pkgFile); err != nil {
- t.Fatalf("unable to unmarshal package JSON: %v", err)
- }
- if diff := cmp.Diff(pkgFile, &PkgFile{
- PackageName: "fuchsia_internal/assembly/inputs/hsp.x64",
- InstanceID: "xUAOSrGYJ3OcV88evjHVfFOPo_Yxd4aoQOfUP8BpnMAC",
- Path: "/b/s/w/ir/x/w/recipe_cleanup/cipd-util-build99nbs7su/inputs.pkg",
- }); diff != "" {
- t.Errorf("got diff: %v", diff)
- }
-}
diff --git a/cmd/recipe_wrapper/env/env.go b/cmd/recipe_wrapper/env/env.go
deleted file mode 100644
index c11e940..0000000
--- a/cmd/recipe_wrapper/env/env.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2023 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package env
-
-import (
- buildbucketpb "go.chromium.org/luci/buildbucket/proto"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/recipes"
-)
-
-type Build struct {
- Environ []string
- RecipesExe recipes.Checkout
- Build *buildbucketpb.Build
- DeferredStepsDir string
- AttestationShouldUpload bool
-}
diff --git a/cmd/recipe_wrapper/main.go b/cmd/recipe_wrapper/main.go
index d0eda2f..ad28ac7 100644
--- a/cmd/recipe_wrapper/main.go
+++ b/cmd/recipe_wrapper/main.go
@@ -7,17 +7,13 @@
import (
"bytes"
"context"
- "encoding/json"
"errors"
"fmt"
"os"
"os/exec"
- "path/filepath"
- "runtime"
"strings"
buildbucketpb "go.chromium.org/luci/buildbucket/proto"
- cipdclient "go.chromium.org/luci/cipd/client/cipd"
"go.chromium.org/luci/common/logging"
"go.chromium.org/luci/common/logging/gologger"
"go.chromium.org/luci/logdog/client/butlerlib/bootstrap"
@@ -25,13 +21,18 @@
"go.chromium.org/luci/luciexe"
"google.golang.org/protobuf/proto"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/bcid"
"go.fuchsia.dev/infra/cmd/recipe_wrapper/cipd"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/env"
"go.fuchsia.dev/infra/cmd/recipe_wrapper/props"
"go.fuchsia.dev/infra/cmd/recipe_wrapper/recipes"
)
+// environment represents the environment within which a recipe is run.
+type environment struct {
+ environ []string
+ recipesExe recipes.Checkout
+ buildProto *buildbucketpb.Build
+}
+
// outputBuildSummary outputs error details to the current build's
// summary_markdown in case of failure.
func outputBuildSummary(ctx context.Context, buildErr error) error {
@@ -70,7 +71,7 @@
// initEnvironment sets up the environment with recipes, Python 2 etc.
// Returns particulars about the environment created as well as a cleanup function
// that the caller should run when the environment is no longer needed.
-func initEnvironment(ctx context.Context) (*env.Build, func(), error) {
+func initEnvironment(ctx context.Context) (*environment, func(), error) {
cwd, err := os.Getwd()
if err != nil {
return nil, nil, err
@@ -103,9 +104,6 @@
if err != nil {
return nil, nil, err
}
- if bcid.CanAttestPlatform() {
- pkgsToInstall = append(pkgsToInstall, cipd.AttestToolPkg)
- }
if noPy2 {
logging.Infof(ctx, "no_python2=true, not installing python 2")
} else {
@@ -119,38 +117,21 @@
path := strings.Join(append(binDirs, os.Getenv("PATH")), string(os.PathListSeparator))
os.Setenv("PATH", path)
- deferredStepsDir, err := os.MkdirTemp("", "bcid")
- if err != nil {
- return nil, nil, fmt.Errorf("couldn't create a deferred steps temp dir: %v", err)
- }
- if err := props.SetBuildInputProperty(build, "$fuchsia/recipe_wrapper",
- map[string]any{"deferred_steps_dir": deferredStepsDir}); err != nil {
- return nil, nil, fmt.Errorf("couldn't set `deferred_steps_dir` property: %v", err)
- }
- shouldUpload, err := props.Bool(build, "upload_bcid_attestation")
- if err != nil {
- logging.Infof(ctx, "couldn't find upload_bcid_attestation property, assuming false")
- shouldUpload = false
- }
-
logging.Infof(ctx, "Initialized execution environment to:\n%+v", os.Environ())
- return &env.Build{
- Environ: os.Environ(),
- RecipesExe: exe,
- Build: build,
- DeferredStepsDir: deferredStepsDir,
- AttestationShouldUpload: shouldUpload,
+ return &environment{
+ environ: os.Environ(),
+ recipesExe: exe,
+ buildProto: build,
}, func() {
// No need to check errors here, trashing temp files is best effort.
os.RemoveAll(recipesDir)
os.RemoveAll(rootBinDir)
- os.RemoveAll(deferredStepsDir)
}, err
}
-func runRecipe(ctx context.Context, buildEnv *env.Build) error {
- commandLine, err := buildEnv.RecipesExe.LuciexeCommand()
+func runRecipe(ctx context.Context, env *environment) error {
+ commandLine, err := env.recipesExe.LuciexeCommand()
if err != nil {
return err
}
@@ -160,8 +141,8 @@
cmd := exec.CommandContext(ctx, commandLine[0], commandLine[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
- cmd.Env = buildEnv.Environ
- inputData, err := proto.Marshal(buildEnv.Build)
+ cmd.Env = env.environ
+ inputData, err := proto.Marshal(env.buildProto)
if err != nil {
return fmt.Errorf("could not marshal input build: %w", err)
}
@@ -181,111 +162,6 @@
return strings.Join(errStrs, ",")
}
-func pkgsInDir(ctx context.Context, dir string) ([]*cipd.PkgFile, error) {
- pkgs := []*cipd.PkgFile{}
- files, err := os.ReadDir(dir)
- if err != nil {
- return nil, err
- }
-
- logging.Infof(ctx, "Found %v deferred step files in %v", len(files), dir)
- for _, f := range files {
- // Annoyingly f is not an *os.File but an *fs.DirEntry,
- // and *fs.DirEntries do not contain the filepath.
- // It has to be joined to the directory it was read from.
- path := filepath.Join(dir, f.Name())
- data, err := os.ReadFile(path)
- if err != nil {
- return nil, fmt.Errorf("couldn't open %q: %v", path, err)
- }
- logging.Infof(ctx, "Read %v: %s", path, data)
-
- pkgFile := &cipd.PkgFile{}
- if err := json.Unmarshal(data, pkgFile); err != nil {
- return nil, err
- }
- logging.Infof(ctx, "Unmarshaled to %+v", pkgFile)
- pkgs = append(pkgs, pkgFile)
- }
-
- return pkgs, nil
-}
-
-// uploadAttestations will upload attestations to SCILo, then attach them as metadata to the CIPD packages.
-func uploadAttestations(ctx context.Context, buildEnv *env.Build, stmtBundles []*cipd.StmtBundle) error {
- if !buildEnv.AttestationShouldUpload {
- logging.Infof(ctx, "Attestation skipped, builder does not have `upload_bcid_attestation` set")
- return nil
- }
-
- if err := uploadToSCILo(ctx, stmtBundles); err != nil {
- return fmt.Errorf("unable to upload attestations to SCILo: %v", err)
- }
- // Attestations aren't valid if they didn't go in to SCILo, so only attach them as metadata _after_
- // SCILo has accepted them.
- if err := attachAttestations(ctx, stmtBundles); err != nil {
- return fmt.Errorf("unable to attach attestations: %v", err)
- }
-
- return nil
-}
-
-func uploadToSCILo(ctx context.Context, stmtBundles []*cipd.StmtBundle) error {
- for _, sb := range stmtBundles {
- logging.Infof(ctx, "Uploading attestation for %v | %v:\n%+v",
- sb.Package.PackageName, sb.Package.InstanceID, sb.Bundle)
-
- _, err := bcid.UploadToSCILo(ctx, sb.Statement, sb.Bundle)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func attachAttestations(ctx context.Context, stmtBundles []*cipd.StmtBundle) error {
- for _, sb := range stmtBundles {
- logging.Infof(ctx, "Attaching attestation for %v | %v:\n%+v",
- sb.Package.PackageName, sb.Package.InstanceID, sb.Bundle)
- jsb, err := json.Marshal(sb.Bundle)
- if err != nil {
- return fmt.Errorf("unable to marshal %+v: %v", sb.Bundle, err)
- }
- cipd.AddMetadata(ctx, sb.Package.AsPin(), []cipdclient.Metadata{
- {
- Key: "provenance",
- Value: jsb,
- ContentType: "text/plain",
- },
- })
- }
-
- return nil
-}
-
-func attest(ctx context.Context, buildEnv *env.Build) error {
- if !bcid.CanAttestPlatform() {
- logging.Infof(ctx, "Unsupported attestation platform: %v/%v", runtime.GOOS, runtime.GOARCH)
- return nil
- }
- pkgs, err := pkgsInDir(ctx, buildEnv.DeferredStepsDir)
- if err != nil {
- return err
- }
-
- logging.Infof(ctx, "Inspecting %v packages for attestation...", len(pkgs))
- stmtBundles, errs := cipd.AttestPkgs(pkgs, bcid.ProdKeyID, buildEnv)
- if len(errs) != 0 {
- return fmt.Errorf("AttestPkgs returned errors: %v", joinErrs(errs))
- }
- if err := uploadAttestations(ctx, buildEnv, stmtBundles); err != nil {
- return fmt.Errorf("unable to upload attestations: %v", err)
- }
-
- return nil
-}
-
func main() {
ctx := context.Background()
ctx = gologger.StdConfig.Use(ctx)
@@ -303,10 +179,4 @@
logging.Errorf(ctx, fmt.Errorf("build failed: %v", err).Error())
os.Exit(1)
}
-
- logging.Infof(ctx, "Running attestation step")
- if err := attest(ctx, env); err != nil {
- logging.Errorf(ctx, fmt.Errorf("build was successful, but couldn't attest: %v", err).Error())
- os.Exit(1)
- }
}
diff --git a/cmd/recipe_wrapper/main_test.go b/cmd/recipe_wrapper/main_test.go
deleted file mode 100644
index 0a5aa59..0000000
--- a/cmd/recipe_wrapper/main_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2023 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package main
-
-import (
- "context"
- "encoding/json"
- "os"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "go.fuchsia.dev/infra/cmd/recipe_wrapper/cipd"
-)
-
-func TestOpenFilesInDir(t *testing.T) {
- tests := []struct {
- name string
- // One step to one file.
- // This makes the test simpler to reason about.
- files []*cipd.PkgFile
- }{
- {
- name: "Steps should be read back",
- files: []*cipd.PkgFile{
- {
- PackageName: "foo/bar",
- InstanceID: "baz",
- Path: "/tmp/foo/bar.pkg",
- },
- {
- PackageName: "alice/bob/charlie",
- InstanceID: "dave",
- Path: "/alice/bob/charlie.pkg",
- },
- },
- },
- {
- name: "Empty files should be acceptable",
- files: []*cipd.PkgFile{
- {},
- {
- PackageName: "foo/bar",
- InstanceID: "baz",
- Path: "/tmp/foo/bar.pkg",
- },
- {},
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- tmpdir, err := os.MkdirTemp("", "main_test")
- if err != nil {
- t.Fatalf("couldn't make tempfile: %v", err)
- }
-
- for _, pkgFile := range test.files {
- f, err := os.CreateTemp(tmpdir, "")
- if err != nil {
- t.Fatalf("couldn't create tempfile: %v", err)
- }
- pkgJSON, err := json.Marshal(pkgFile)
- if err != nil {
- t.Fatalf("couldn't marshal to JSON: %v", err)
- }
- _, err = f.WriteString(string(pkgJSON))
- if err != nil {
- t.Fatalf("couldn't write JSON: %v", err)
- }
- t.Logf("wrote to %q\n", f.Name())
- f.Close()
- }
-
- got, err := pkgsInDir(context.Background(), tmpdir)
- if err != nil {
- t.Errorf("got error %q, want nil", err)
- }
- if diff := cmp.Diff(got, test.files, cmpopts.SortSlices(func(x, y *cipd.PkgFile) bool {
- return x.PackageName > y.PackageName
- })); diff != "" {
- t.Errorf("got diff: %v", diff)
- }
- })
- }
-
-}
diff --git a/slsa/buildConfig/v1/README.md b/slsa/buildConfig/v1/README.md
deleted file mode 100644
index c8f232d..0000000
--- a/slsa/buildConfig/v1/README.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Fuchsia SLSA v1 spec
-
-This document is a loose description of the SLSA v1 spec. The spec may well _grow_, but it will not shrink to less than this size.
-
-## Statement (Proto)
-
-| Field | Description | Default |
-| --------------- | ---------------------- | --------------------------------- |
-| `Type` | Statement type/version | <https://in-toto.io/Statement/v1> |
-| `Subject` | See Resource Descriptor | See Resource Descriptor |
-| `PredicateType` | Predicate type/version | <https://slsa.dev/provenance/v1> |
-| `Predicate` | See Predicate | See Predicate |
-
-## Resource Descriptor (Proto)
-
-| Field | Description | Default |
-| -------- | --------------------- | ------- |
-| `Name` | The CIPD package name | |
-| `Digest` | The CIPD instance ID | |
-
-## Predicate (JSON)
-
-| Field | Description | Default |
-| ----------------- | -------------------- | -------------------- |
-| `buildDefinition` | See Build Definition | See Build Definition |
-| `runDetails` | See Run Details | See Run Details |
-
-## Build definition (JSON)
-
-| Field | Description | Default |
-| ------------------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------- |
-| `buildType` | Link to this document | <https://fuchsia.googlesource.com/infra/infra/+/refs/heads/main/slsa/buildConfig/v1> |
-| `externalParameters.buildConfigSource.repository` | Location of recipes repository | `git+https://fuchsia.googlesource.com/infra/recipes` |
-| `externalParameters.buildConfigSource.path` | Recipes entry point | `recipes.py` |
-
-## Run details (JSON)
-
-| Field | Description | Default |
-| --------- | --------------------------------------------- | ------------------------------------------------- |
-| `builder` | Chosen ID registered with BCID (currently L1) | `//bcid.corp.google.com/builders/luci/fuchsia/l1` |