From 0ace282652e240408001f6dd47a007abe3df047b Mon Sep 17 00:00:00 2001 From: Salim Afiune Date: Mon, 21 Oct 2019 17:21:47 -0600 Subject: [PATCH 1/5] Use checksum to verify cookbook integrity https://github.com/chef/go-chef/issues/3 Signed-off-by: Salim Afiune --- .studiorc | 12 +++++++++++ cookbook_download.go | 42 +++++++++++++++++++++++++++++++++------ cookbook_download_test.go | 22 ++++++++++++++++++-- 3 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 .studiorc diff --git a/.studiorc b/.studiorc new file mode 100644 index 0000000..2155486 --- /dev/null +++ b/.studiorc @@ -0,0 +1,12 @@ +#!/bin/bash +# +# This is the place you can extend the funcitonality of the studio + +hab pkg install chef/studio-common >/dev/null +source "$(hab pkg path chef/studio-common)/bin/studio-common" + +function run_tests() { + install_if_missing core/go go + install_if_missing core/gcc gcc + go test +} diff --git a/cookbook_download.go b/cookbook_download.go index 8f982f9..1cc5aa0 100644 --- a/cookbook_download.go +++ b/cookbook_download.go @@ -5,6 +5,9 @@ package chef import ( + "crypto/md5" + "errors" + "fmt" "io" "os" "path" @@ -73,8 +76,7 @@ func (c *CookbookService) downloadCookbookItems(items []CookbookItem, itemType, } for _, item := range items { - itemPath := path.Join(localPath, item.Name) - if err := c.downloadCookbookFile(item.Url, itemPath); err != nil { + if err := c.downloadCookbookFile(item, localPath); err != nil { return err } } @@ -83,11 +85,14 @@ func (c *CookbookService) downloadCookbookItems(items []CookbookItem, itemType, } // downloadCookbookFile downloads a single cookbook file to disk -func (c *CookbookService) downloadCookbookFile(url, file string) error { - request, err := c.client.NewRequest("GET", url, nil) +func (c *CookbookService) downloadCookbookFile(item CookbookItem, localPath string) error { + filePath := path.Join(localPath, item.Name) + + request, err := c.client.NewRequest("GET", item.Url, nil) if err != nil { return err } + response, err := c.client.Do(request, nil) if response != nil { defer response.Body.Close() @@ -96,13 +101,38 @@ func (c *CookbookService) downloadCookbookFile(url, file string) error { return err } - f, err := os.Create(file) + f, err := os.Create(filePath) if err != nil { return err } + defer f.Close() if _, err := io.Copy(f, response.Body); err != nil { return err } - return nil + + if verifyMD5Checksum(filePath, item.Checksum) { + return nil + } + + return errors.New("wrong checksum") +} + +func verifyMD5Checksum(filePath, checksum string) bool { + file, err := os.Open(filePath) + if err != nil { + return false + } + defer file.Close() + + hash := md5.New() + if _, err := io.Copy(hash, file); err != nil { + return false + } + + md5String := fmt.Sprintf("%x", hash.Sum(nil)) + if md5String == checksum { + return true + } + return false } diff --git a/cookbook_download_test.go b/cookbook_download_test.go index d5708b3..f8df313 100644 --- a/cookbook_download_test.go +++ b/cookbook_download_test.go @@ -93,7 +93,7 @@ func TestCookbooksDownloadAt(t *testing.T) { { "name": "default.rb", "path": "recipes/default.rb", - "checksum": "320sdk2w38020827kdlsdkasbd5454b6", + "checksum": "8e751ed8663cb9b97499956b6a20b0de", "specificity": "default", "url": "` + server.URL + `/bookshelf/foo/default_rb" } @@ -103,7 +103,7 @@ func TestCookbooksDownloadAt(t *testing.T) { { "name": "metadata.rb", "path": "metadata.rb", - "checksum": "14963c5b685f3a15ea90ae51bd5454b6", + "checksum": "6607f3131919e82dc4ba4c026fcfee9f", "specificity": "default", "url": "` + server.URL + `/bookshelf/foo/metadata_rb" } @@ -152,3 +152,21 @@ func TestCookbooksDownloadAt(t *testing.T) { assert.Equal(t, "log 'this is a resource'", string(recipeBytes)) } } + +func TestVerifyMD5Checksum(t *testing.T) { + tempDir, err := ioutil.TempDir("", "md5-test") + if err != nil { + t.Error(err) + } + defer os.RemoveAll(tempDir) // clean up + + var ( + // if someone changes the test data, + // you have to also update the below md5 sum + testData = []byte("hello\nchef\n") + filePath = path.Join(tempDir, "dat") + ) + err = ioutil.WriteFile(filePath, testData, 0644) + assert.Nil(t, err) + assert.True(t, verifyMD5Checksum(filePath, "70bda176ac4db06f1f66f96ae0693be1")) +} From 98a358cad1929ce8234c0a56939dab8fa86584c1 Mon Sep 17 00:00:00 2001 From: Salim Afiune Date: Tue, 22 Oct 2019 13:02:45 -0600 Subject: [PATCH 2/5] Better error message on checksum mismatch Signed-off-by: Salim Afiune --- cookbook_download.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cookbook_download.go b/cookbook_download.go index 1cc5aa0..0ef102d 100644 --- a/cookbook_download.go +++ b/cookbook_download.go @@ -6,7 +6,6 @@ package chef import ( "crypto/md5" - "errors" "fmt" "io" "os" @@ -115,7 +114,11 @@ func (c *CookbookService) downloadCookbookFile(item CookbookItem, localPath stri return nil } - return errors.New("wrong checksum") + return fmt.Errorf( + "cookbook file '%s' checksum mismatch. (expected:%s)", + filePath, + item.Checksum, + ) } func verifyMD5Checksum(filePath, checksum string) bool { From 0500dcaecd1111ba873e09aef8e78cd300dc41ad Mon Sep 17 00:00:00 2001 From: Salim Afiune Date: Thu, 17 Oct 2019 12:53:03 -0600 Subject: [PATCH 3/5] Rename DownloadAt() to DownloadTo() Signed-off-by: Salim Afiune --- cookbook_download.go | 6 +++--- cookbook_download_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cookbook_download.go b/cookbook_download.go index 0ef102d..2d7635c 100644 --- a/cookbook_download.go +++ b/cookbook_download.go @@ -19,11 +19,11 @@ func (c *CookbookService) Download(name, version string) error { return err } - return c.DownloadAt(name, version, cwd) + return c.DownloadTo(name, version, cwd) } -// DownloadAt downloads a cookbook to the specified local directory on disk -func (c *CookbookService) DownloadAt(name, version, localDir string) error { +// DownloadTo downloads a cookbook to the specified local directory on disk +func (c *CookbookService) DownloadTo(name, version, localDir string) error { // If the version is set to 'latest' or it is empty ("") then, // we will set the version to '_latest' which is the default endpoint if version == "" || version == "latest" { diff --git a/cookbook_download_test.go b/cookbook_download_test.go index f8df313..202b942 100644 --- a/cookbook_download_test.go +++ b/cookbook_download_test.go @@ -72,7 +72,7 @@ func TestCookbooksDownloadEmptyWithVersion(t *testing.T) { assert.Nil(t, err) } -func TestCookbooksDownloadAt(t *testing.T) { +func TestCookbooksDownloadTo(t *testing.T) { setup() defer teardown() @@ -130,7 +130,7 @@ func TestCookbooksDownloadAt(t *testing.T) { fmt.Fprintf(w, "log 'this is a resource'") }) - err = client.Cookbooks.DownloadAt("foo", "0.2.1", tempDir) + err = client.Cookbooks.DownloadTo("foo", "0.2.1", tempDir) assert.Nil(t, err) var ( From d32e9a44d5f9bbcbc68c7d5165973356ed7fd560 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 30 Nov 2019 20:55:12 -0800 Subject: [PATCH 4/5] Keep DownloadAt as an alias for DownloadTo --- cookbook_download.go | 6 +++ cookbook_download_test.go | 81 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/cookbook_download.go b/cookbook_download.go index 2d7635c..91f8b33 100644 --- a/cookbook_download.go +++ b/cookbook_download.go @@ -62,6 +62,12 @@ func (c *CookbookService) DownloadTo(name, version, localDir string) error { return nil } +// DownloadAt is a deprecated alias for DownloadTo +func (c *CookbookService) DownloadAt(name, version, localDir string) error { + err := c.DownloadTo(name, version, localDir) + return err +} + // downloadCookbookItems downloads all the provided cookbook items into the provided // local path, it also ensures that the provided directory exists by creating it func (c *CookbookService) downloadCookbookItems(items []CookbookItem, itemType, localPath string) error { diff --git a/cookbook_download_test.go b/cookbook_download_test.go index 202b942..9b6b1f2 100644 --- a/cookbook_download_test.go +++ b/cookbook_download_test.go @@ -153,6 +153,87 @@ func TestCookbooksDownloadTo(t *testing.T) { } } +func TestCookbooksDownloadAt(t *testing.T) { + setup() + defer teardown() + + mockedCookbookResponseFile := ` +{ + "version": "0.2.1", + "name": "foo-0.2.1", + "cookbook_name": "foo", + "frozen?": false, + "chef_type": "cookbook_version", + "json_class": "Chef::CookbookVersion", + "attributes": [], + "definitions": [], + "files": [], + "libraries": [], + "providers": [], + "recipes": [ + { + "name": "default.rb", + "path": "recipes/default.rb", + "checksum": "8e751ed8663cb9b97499956b6a20b0de", + "specificity": "default", + "url": "` + server.URL + `/bookshelf/foo/default_rb" + } + ], + "resources": [], + "root_files": [ + { + "name": "metadata.rb", + "path": "metadata.rb", + "checksum": "6607f3131919e82dc4ba4c026fcfee9f", + "specificity": "default", + "url": "` + server.URL + `/bookshelf/foo/metadata_rb" + } + ], + "templates": [], + "metadata": {}, + "access": {} +} +` + + tempDir, err := ioutil.TempDir("", "foo-cookbook") + if err != nil { + t.Error(err) + } + defer os.RemoveAll(tempDir) // clean up + + mux.HandleFunc("/cookbooks/foo/0.2.1", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, string(mockedCookbookResponseFile)) + }) + mux.HandleFunc("/bookshelf/foo/metadata_rb", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "name 'foo'") + }) + mux.HandleFunc("/bookshelf/foo/default_rb", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "log 'this is a resource'") + }) + + err = client.Cookbooks.DownloadAt("foo", "0.2.1", tempDir) + assert.Nil(t, err) + + var ( + cookbookPath = path.Join(tempDir, "foo-0.2.1") + metadataPath = path.Join(cookbookPath, "metadata.rb") + recipesPath = path.Join(cookbookPath, "recipes") + defaultPath = path.Join(recipesPath, "default.rb") + ) + assert.DirExistsf(t, cookbookPath, "the cookbook directory should exist") + assert.DirExistsf(t, recipesPath, "the recipes directory should exist") + if assert.FileExistsf(t, metadataPath, "a metadata.rb file should exist") { + metadataBytes, err := ioutil.ReadFile(metadataPath) + assert.Nil(t, err) + assert.Equal(t, "name 'foo'", string(metadataBytes)) + } + if assert.FileExistsf(t, defaultPath, "the default.rb recipes should exist") { + recipeBytes, err := ioutil.ReadFile(defaultPath) + assert.Nil(t, err) + assert.Equal(t, "log 'this is a resource'", string(recipeBytes)) + } +} + func TestVerifyMD5Checksum(t *testing.T) { tempDir, err := ioutil.TempDir("", "md5-test") if err != nil { From 8a98980cb510aa6566a440decadc32092086aad4 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 30 Nov 2019 21:11:52 -0800 Subject: [PATCH 5/5] Remove .studiorc --- .studiorc | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .studiorc diff --git a/.studiorc b/.studiorc deleted file mode 100644 index 2155486..0000000 --- a/.studiorc +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -# -# This is the place you can extend the funcitonality of the studio - -hab pkg install chef/studio-common >/dev/null -source "$(hab pkg path chef/studio-common)/bin/studio-common" - -function run_tests() { - install_if_missing core/go go - install_if_missing core/gcc gcc - go test -}